feat(填报记录): 被驳回记录支持修改后重新提交
- records.vue:已驳回记录卡片底部新增“重新提交”按钮,跳转 create?id=<id>&mode=resubmit;同时补上驳回原因展示 (原 normalizeRecord 未提取 rejectReason,列表页看不到驳回理由) - create.vue:复用同一表单页做编辑模式,onLoad 读 id/mode, 回填门店、日期、打卡照、摆台照、小票、销量、小结、评分、定位; 标题与按钮切换为“重新提交填报/重新提交”,隐藏保存草稿 (草稿 key 与新增共用,会互相覆盖) - 提交走 resubmit 接口,成功后返回列表,onShow 自动刷新为待审核 - api/self-report.js:新增 selfReportResubmitReq
This commit is contained in:
@@ -16,4 +16,7 @@ export const selfReportInfoReq = id => get(`/yxe/yxe-self-report/${id}`)
|
||||
|
||||
export const selfReportChangeReq = data => put('/yxe/yxe-self-report', data)
|
||||
|
||||
// 驳回后重新提交:仅本人、仅已驳回记录可调用,状态由服务端置回待审核
|
||||
export const selfReportResubmitReq = data => put('/yxe/yxe-self-report/resubmit', data)
|
||||
|
||||
export const selfReportDeleteReq = id => del(`/yxe/yxe-self-report/${id}`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="self-report-page">
|
||||
<AppNavbar title="自主填报" showBack/>
|
||||
<AppNavbar :title="isResubmit ? '重新提交填报' : '自主填报'" showBack/>
|
||||
|
||||
<view class="sr-merchant-row" @tap="openChainPicker">
|
||||
<view class="sr-merchant-label">连锁筛选</view>
|
||||
@@ -185,9 +185,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card block" v-if="isResubmit && rejectReason">
|
||||
<view class="section-title">驳回原因</view>
|
||||
<view class="reject-reason-text">{{ rejectReason }}</view>
|
||||
</view>
|
||||
|
||||
<view class="form-actions">
|
||||
<view class="draft-btn" @tap="saveDraft">保存草稿</view>
|
||||
<view class="primary-btn submit" @tap="submit">{{ submitLoading ? '提交中...' : '提交销量' }}</view>
|
||||
<view v-if="!isResubmit" class="draft-btn" @tap="saveDraft">保存草稿</view>
|
||||
<view class="primary-btn submit" @tap="submit">{{ submitLoading ? '提交中...' : (isResubmit ? '重新提交' : '提交销量') }}</view>
|
||||
</view>
|
||||
<view class="form-actions-placeholder"/>
|
||||
|
||||
@@ -322,7 +327,7 @@ import Login from "@/components/Login.vue";
|
||||
import AppNavbar from "@/components/AppNavbar.vue";
|
||||
import {merchantGroupByChainReq} from "@/api/shop";
|
||||
import {productGroupByCateReq} from "@/api/product";
|
||||
import {selfReportAddReq} from "@/api/self-report";
|
||||
import {selfReportAddReq, selfReportInfoReq, selfReportResubmitReq} from "@/api/self-report";
|
||||
import {getUserInfo} from "@/util/user";
|
||||
import {chooseImg, getLocationOnly} from "@/util";
|
||||
|
||||
@@ -362,7 +367,12 @@ export default {
|
||||
currentProvince: '',
|
||||
currentCity: '',
|
||||
draftPrompting: false,
|
||||
submitLoading: false
|
||||
submitLoading: false,
|
||||
// 驳回后重新提交:记录 id 与驳回原因,提交时走 resubmit 接口
|
||||
isResubmit: false,
|
||||
resubmitId: '',
|
||||
rejectReason: '',
|
||||
resubmitLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -449,12 +459,23 @@ export default {
|
||||
// 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化。
|
||||
this.merchantStore = []
|
||||
},
|
||||
async onLoad(options = {}) {
|
||||
const id = options && options.id !== undefined && options.id !== null ? String(options.id).trim() : ''
|
||||
if (id && String(options.mode || '') === 'resubmit') {
|
||||
this.isResubmit = true
|
||||
this.resubmitId = id
|
||||
}
|
||||
},
|
||||
async onShow() {
|
||||
this.isLoggedIn = !!getUserInfo().token
|
||||
if (!this.hasInitialized) {
|
||||
await this.init()
|
||||
this.hasInitialized = true
|
||||
await this.checkDraftOnEntry()
|
||||
if (this.isResubmit) {
|
||||
await this.loadResubmitRecord()
|
||||
} else {
|
||||
await this.checkDraftOnEntry()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -501,6 +522,138 @@ export default {
|
||||
const uid = user.uid || user.id || user.userId || 'guest'
|
||||
return `yxe_self_report_create_draft_${uid}`
|
||||
},
|
||||
parseStoredJson(value, fallback) {
|
||||
if (value === null || value === undefined || value === '') return fallback
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
// 记录里的图片字段可能是字符串/对象/数组混用,统一取 URL
|
||||
pickMediaUrl(media) {
|
||||
if (!media) return ''
|
||||
if (typeof media === 'string') return media
|
||||
return media.url || media.path || media.image || ''
|
||||
},
|
||||
normalizeResubmitClockPhotos(value) {
|
||||
const list = this.parseStoredJson(value, [])
|
||||
const source = Array.isArray(list) ? list : []
|
||||
const defaults = [
|
||||
{key: 'in', label: '上班'},
|
||||
{key: 'out', label: '下班'}
|
||||
]
|
||||
return defaults.map((item, index) => {
|
||||
const raw = source[index] && typeof source[index] === 'object' ? source[index] : {}
|
||||
return {
|
||||
key: raw.key || item.key,
|
||||
label: raw.label || item.label,
|
||||
time: raw.time || '',
|
||||
date: raw.date || '',
|
||||
url: this.pickMediaUrl(raw.url || raw) || '',
|
||||
lat: raw.lat || '',
|
||||
lng: raw.lng || '',
|
||||
location: raw.location || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
normalizeResubmitPhotos(value) {
|
||||
const list = this.parseStoredJson(value, [])
|
||||
return (Array.isArray(list) ? list : []).map(item => this.pickMediaUrl(item)).filter(Boolean)
|
||||
},
|
||||
// 销量条目回填成 create 页的 salesItems 结构,specOptions 缺失时用已有 spec 兜底
|
||||
normalizeResubmitSalesItems(value) {
|
||||
const groups = this.parseStoredJson(value, [])
|
||||
const items = []
|
||||
;(Array.isArray(groups) ? groups : []).forEach((group, groupIndex) => {
|
||||
const rows = Array.isArray(group && group.items) ? group.items : []
|
||||
rows.forEach((detail, rowIndex) => {
|
||||
if (!detail || typeof detail !== 'object') return
|
||||
const spec = detail.spec || detail.specification || ''
|
||||
const specOptions = Array.isArray(detail.specOptions) && detail.specOptions.length
|
||||
? detail.specOptions.map(item => `${item}`.trim()).filter(Boolean)
|
||||
: (spec ? [spec] : [])
|
||||
items.push({
|
||||
_key: `resubmit_${groupIndex}_${rowIndex}`,
|
||||
productId: detail.productId || detail.goodsId || '',
|
||||
goodsCode: detail.goodsCode || detail.code || '',
|
||||
name: detail.name || detail.goodsName || '',
|
||||
cover: detail.cover || '',
|
||||
makerAbb: detail.makerAbb || detail.manufacturer || '',
|
||||
groupTitle: (group && group.title) || detail.groupTitle || '自定义商品',
|
||||
spec: spec || specOptions[0] || '',
|
||||
specOptions,
|
||||
unit: detail.unit || '盒',
|
||||
taskIncome: detail.taskIncome !== undefined && detail.taskIncome !== null ? detail.taskIncome : '',
|
||||
qty: detail.qty !== undefined && detail.qty !== null ? `${detail.qty}` : ''
|
||||
})
|
||||
})
|
||||
})
|
||||
return items
|
||||
},
|
||||
async loadResubmitRecord() {
|
||||
if (!this.resubmitId || this.resubmitLoading) return
|
||||
this.resubmitLoading = true
|
||||
try {
|
||||
const res = await selfReportInfoReq(this.resubmitId)
|
||||
// get 请求在业务失败时也会 resolve,需要显式看 code
|
||||
if (res && res.code !== 0) {
|
||||
await this.toast(res.message || '填报记录加载失败')
|
||||
uni.navigateBack()
|
||||
return
|
||||
}
|
||||
const record = res && res.data && typeof res.data === 'object' ? res.data : null
|
||||
if (!record) {
|
||||
await this.toast('填报记录不存在')
|
||||
uni.navigateBack()
|
||||
return
|
||||
}
|
||||
if (Number(record.status) !== 2) {
|
||||
await this.toast('该记录不是已驳回状态,无法重新提交')
|
||||
uni.navigateBack()
|
||||
return
|
||||
}
|
||||
this.rejectReason = record.rejectReason || record.reject_reason || ''
|
||||
this.form = Object.assign(this.buildForm(), {
|
||||
chainId: record.chainId || record.chain_id || '',
|
||||
chainName: record.chainName || record.chain_name || '',
|
||||
merchantId: record.merchantId || record.merchant_id || '',
|
||||
merchantName: record.merchantName || record.merchant_name || '',
|
||||
date: record.date || '',
|
||||
province: record.province || '',
|
||||
city: record.city || '',
|
||||
summary: this.parseStoredJson(record.summary, record.summary || ''),
|
||||
rating: Number(record.rating) || 5
|
||||
})
|
||||
if (typeof this.form.summary !== 'string') {
|
||||
this.form.summary = ''
|
||||
}
|
||||
this.clockPhotos = this.normalizeResubmitClockPhotos(record.clockPhotos)
|
||||
this.boothPhotos = this.normalizeResubmitPhotos(record.boothPhotos)
|
||||
this.receiptPhotos = this.normalizeResubmitPhotos(record.receiptPhoto || record.receiptPhotos)
|
||||
this.salesItems = this.normalizeResubmitSalesItems(record.salesGroups)
|
||||
this.currentAddress = record.location || ''
|
||||
this.lat = record.lat === '' || record.lat === null || record.lat === undefined ? null : Number(record.lat)
|
||||
this.lng = record.lng === '' || record.lng === null || record.lng === undefined ? null : Number(record.lng)
|
||||
this.currentProvince = record.province || ''
|
||||
this.currentCity = record.city || ''
|
||||
// 原记录没有定位时用当前位置兜底,否则校验会卡在“请先完成定位”且用户无从修改
|
||||
if (!this.currentAddress || !this.lat || !this.lng) {
|
||||
await this.fetchCurrentLocation()
|
||||
}
|
||||
// 销量商品按连锁重新拉取,保证规格选项与当前连锁一致
|
||||
if (this.form.chainName) {
|
||||
await this.getProductGroups()
|
||||
}
|
||||
} catch (error) {
|
||||
await this.toast(error && error.message ? error.message : '填报记录加载失败')
|
||||
} finally {
|
||||
this.resubmitLoading = false
|
||||
}
|
||||
},
|
||||
cloneValue(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return value
|
||||
@@ -1437,9 +1590,18 @@ export default {
|
||||
if (!this.validateSubmit()) return
|
||||
this.submitLoading = true
|
||||
try {
|
||||
await selfReportAddReq(this.buildPayload())
|
||||
this.clearDraft()
|
||||
await this.toast('提交成功,请等待审核')
|
||||
if (this.isResubmit) {
|
||||
const payload = this.buildPayload()
|
||||
// 状态与驳回原因由服务端决定,这里不传,避免被当成审核端改状态
|
||||
delete payload.status
|
||||
payload.id = this.resubmitId
|
||||
await selfReportResubmitReq(payload)
|
||||
await this.toast('重新提交成功,请等待审核')
|
||||
} else {
|
||||
await selfReportAddReq(this.buildPayload())
|
||||
this.clearDraft()
|
||||
await this.toast('提交成功,请等待审核')
|
||||
}
|
||||
uni.navigateBack({
|
||||
fail: () => {
|
||||
uni.switchTab({url: '/pages/mine/index'})
|
||||
@@ -1592,6 +1754,18 @@ export default {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.reject-reason-text {
|
||||
margin-top: 14rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fef2f2;
|
||||
border: 1rpx solid #fecaca;
|
||||
color: #dc2626;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
margin-left: 8rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="summary-line">小结:{{ item.summary || '-' }}</view>
|
||||
<view v-if="isRejected(item.status) && item.rejectReason" class="reject-line">
|
||||
驳回原因:{{ item.rejectReason }}
|
||||
</view>
|
||||
<view class="record-toggle" @tap="toggleExpanded(item.id)">
|
||||
<text>{{ isExpanded(item.id) ? '收起详情' : '展开详情' }}</text>
|
||||
<u-icon :name="isExpanded(item.id) ? 'arrow-up' : 'arrow-down'" size="20" color="#2fc6a7"/>
|
||||
@@ -137,6 +140,10 @@
|
||||
<view v-else class="detail-empty">暂无销量数据</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isRejected(item.status)" class="record-actions">
|
||||
<view class="resubmit-btn" @tap="handleResubmit(item)">重新提交</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -330,6 +337,7 @@ export default {
|
||||
activityTime: item.activityTime || item.activity_time || '',
|
||||
date: item.date || '',
|
||||
status: item.status,
|
||||
rejectReason: item.rejectReason || item.reject_reason || '',
|
||||
reward: item.reward,
|
||||
rating: item.rating,
|
||||
summary,
|
||||
@@ -464,6 +472,15 @@ export default {
|
||||
if (Number(status) === 2) return 'danger'
|
||||
return 'pending'
|
||||
},
|
||||
isRejected(status) {
|
||||
return Number(status) === 2
|
||||
},
|
||||
handleResubmit(item) {
|
||||
if (!item || item.id === undefined || item.id === null) return
|
||||
uni.navigateTo({
|
||||
url: `/packageUser/self-report/create?id=${item.id}&mode=resubmit`
|
||||
})
|
||||
},
|
||||
changeStatus(value) {
|
||||
this.activeStatus = value
|
||||
},
|
||||
@@ -743,6 +760,37 @@ export default {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.reject-line {
|
||||
margin-top: 10rpx;
|
||||
color: #dc2626;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.record-actions {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #eef2f7;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.resubmit-btn {
|
||||
min-width: 200rpx;
|
||||
min-height: 72rpx;
|
||||
padding: 0 32rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #2fc6a7, #1db896);
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 20rpx rgba(47, 198, 167, 0.22);
|
||||
}
|
||||
|
||||
.record-toggle {
|
||||
margin-top: 16rpx;
|
||||
display: inline-flex;
|
||||
|
||||
Reference in New Issue
Block a user