fix(自主填报): 修正重提跳转按填报类型、补 wx:key 并清理废弃 API 调用
一、填报记录「重新提交」跳错表单(用户反馈)
records.vue 原先按 activeType(当前 tab)决定跳驻店还是跑店表单,
但列表请求无守卫:切 tab 会连发请求且响应顺序不保证,
旧响应回来会盖掉当前 tab 的数据,导致 tab 与实际数据错位。
此时点「重新提交」会拿 A 表的 id 去打开 B 表的表单
(两表 id 各自从 1 开始,撞号常见,故不报错、只静默进错页面)。
改法:把填报类型钉在记录上,而不是靠 tab 状态猜
- fetchList 拉到数据后给每条记录打 reportType
- 新增 recordType(item):优先取记录自带类型,异常数据回退当前 tab
- 归一化、筛选、卡片标题/副标题、展开详情形态、重新提交跳转全部改走它
- 列表请求加自增 listRequestId 守卫,丢弃过期响应(loading 同理)
二、visit.vue 缺 wx:key 告警
小程序 wx:key 只接受简单属性路径,模板字面量写法
(:key="`store-${idx}`")会被编译器静默丢弃,四处循环因此无 key。
按项目既有 _rowKey 模式改:
- 巡店列表 :key="item._rowKey",新增 buildStoreKey/ensureStoreKeys
(重提与草稿的旧数据无该字段,加载时补齐;提交走显式白名单,不外泄)
- 照片 :key="photo",两个 picker :key="item.id"
三、getSystemInfoSync 废弃告警
该调用在 AppNavbar 的 render(getSafeTop)里,每渲染一次告警一次。
改为挂载时取一次并缓存,优先用新接口 uni.getWindowInfo,
取不到再回退旧接口;select.vue 同源调用一并处理。
注:uview 组件库内部 u-overlay/u-popup 的 mode 传 null 告警
(u-popup 的 position computed 无 else 分支)未改,
uni_modules 已被 gitignore 且会被依赖重装覆盖,属组件库问题。
This commit is contained in:
@@ -27,21 +27,41 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentPages: []
|
currentPages: [],
|
||||||
|
// 状态栏高度只在挂载时取一次:getSafeTop 在 render 中被调用,
|
||||||
|
// 每次都同步读系统信息会反复触发 wx.getSystemInfoSync 废弃告警。
|
||||||
|
safeTop: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getSafeTop() {
|
getSafeTop() {
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
return this.safeTop
|
||||||
return systemInfo.statusBarHeight
|
|
||||||
},
|
},
|
||||||
back(){
|
back(){
|
||||||
if (this.currentPages.length === 1) uni.switchTab({url: '/pages/index/index'})
|
if (this.currentPages.length === 1) uni.switchTab({url: '/pages/index/index'})
|
||||||
else uni.navigateBack()
|
else uni.navigateBack()
|
||||||
|
},
|
||||||
|
// 优先用新接口,取不到时回退旧接口,避免旧接口的废弃告警
|
||||||
|
readSafeTop() {
|
||||||
|
try {
|
||||||
|
if (uni.getWindowInfo) {
|
||||||
|
const info = uni.getWindowInfo()
|
||||||
|
if (info && info.statusBarHeight !== undefined) return info.statusBarHeight
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore:新接口不可用时走下面的回退
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
return (systemInfo && systemInfo.statusBarHeight) || 0
|
||||||
|
} catch (error) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.currentPages = getCurrentPages()
|
this.currentPages = getCurrentPages()
|
||||||
|
this.safeTop = this.readSafeTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<view :class="['status-badge', statusClass(item.status)]">{{ statusText(item.status) }}</view>
|
<view :class="['status-badge', statusClass(item.status)]">{{ statusText(item.status) }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="record-grid">
|
<view class="record-grid">
|
||||||
<view v-if="activeType === 'store'" class="record-kv">
|
<view v-if="item.reportType === 'store'" class="record-kv">
|
||||||
<text class="record-label">活动时间</text>
|
<text class="record-label">活动时间</text>
|
||||||
<text class="record-value">{{ item.activityTime || '-' }}</text>
|
<text class="record-value">{{ item.activityTime || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<text class="record-label">巡店门店数</text>
|
<text class="record-label">巡店门店数</text>
|
||||||
<text class="record-value">{{ item.storeCount || 0 }}家</text>
|
<text class="record-value">{{ item.storeCount || 0 }}家</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="activeType === 'store'" class="record-kv">
|
<view v-if="item.reportType === 'store'" class="record-kv">
|
||||||
<text class="record-label">评分</text>
|
<text class="record-label">评分</text>
|
||||||
<text class="record-value">{{ item.rating || '-' }}星</text>
|
<text class="record-value">{{ item.rating || '-' }}星</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view v-if="isExpanded(item.id)" class="record-detail">
|
<view v-if="isExpanded(item.id)" class="record-detail">
|
||||||
<!-- 跑店填报:巡店列表(驻店填报没有门店列表,走下面的固定区块) -->
|
<!-- 跑店填报:巡店列表(驻店填报没有门店列表,走下面的固定区块) -->
|
||||||
<block v-if="activeType === 'visit'">
|
<block v-if="item.reportType === 'visit'">
|
||||||
<view v-if="!item.storeRows.length" class="detail-empty">暂无巡店记录</view>
|
<view v-if="!item.storeRows.length" class="detail-empty">暂无巡店记录</view>
|
||||||
<view v-for="(store, sIdx) in item.storeRows" :key="`store-${sIdx}`" class="store-block">
|
<view v-for="(store, sIdx) in item.storeRows" :key="`store-${sIdx}`" class="store-block">
|
||||||
<view class="store-block-head">
|
<view class="store-block-head">
|
||||||
@@ -246,6 +246,8 @@ export default {
|
|||||||
],
|
],
|
||||||
activeStatus: 'all',
|
activeStatus: 'all',
|
||||||
records: [],
|
records: [],
|
||||||
|
// 列表请求自增序号:切 tab 会连发请求且响应顺序不保证,只认最后一次,丢弃过期响应
|
||||||
|
listRequestId: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
chainLoading: false,
|
chainLoading: false,
|
||||||
showChainPicker: false,
|
showChainPicker: false,
|
||||||
@@ -336,20 +338,31 @@ export default {
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
async fetchList() {
|
async fetchList() {
|
||||||
|
const requestId = ++this.listRequestId
|
||||||
|
const type = this.activeType
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const req = this.activeType === 'visit' ? visitReportUserListReq : selfReportUserListReq
|
const req = type === 'visit' ? visitReportUserListReq : selfReportUserListReq
|
||||||
const {data} = await req({})
|
const {data} = await req({})
|
||||||
this.records = this.unwrapList(data)
|
// 响应顺序不保证:切 tab 后返回的旧响应直接丢弃,避免另一类型的数据盖进当前 tab
|
||||||
|
if (requestId !== this.listRequestId) return
|
||||||
|
this.records = this.unwrapList(data).map(item => {
|
||||||
|
if (!item || typeof item !== 'object') return item
|
||||||
|
// 类型钉在记录上:后续归一化/筛选/重新提交都以它为准,不再依赖当前 tab
|
||||||
|
return Object.assign({}, item, {reportType: type})
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (requestId !== this.listRequestId) return
|
||||||
this.records = []
|
this.records = []
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '记录加载失败',
|
title: '记录加载失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
|
if (requestId === this.listRequestId) {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async fetchChainList() {
|
async fetchChainList() {
|
||||||
this.chainLoading = true
|
this.chainLoading = true
|
||||||
@@ -368,8 +381,13 @@ export default {
|
|||||||
this.chainLoading = false
|
this.chainLoading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 记录所属填报类型:优先取记录自带的 reportType,历史/异常数据回退当前 tab
|
||||||
|
recordType(item) {
|
||||||
|
const type = item && item.reportType
|
||||||
|
return type === 'visit' || type === 'store' ? type : this.activeType
|
||||||
|
},
|
||||||
normalizeRecord(item = {}) {
|
normalizeRecord(item = {}) {
|
||||||
if (this.activeType === 'visit') {
|
if (this.recordType(item) === 'visit') {
|
||||||
return this.normalizeVisitRecord(item)
|
return this.normalizeVisitRecord(item)
|
||||||
}
|
}
|
||||||
const createRaw = item.createTime || item.createdAt || item.date || ''
|
const createRaw = item.createTime || item.createdAt || item.date || ''
|
||||||
@@ -381,6 +399,7 @@ export default {
|
|||||||
const summary = typeof summaryRaw === 'string' ? summaryRaw : JSON.stringify(summaryRaw || '')
|
const summary = typeof summaryRaw === 'string' ? summaryRaw : JSON.stringify(summaryRaw || '')
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
reportType: 'store',
|
||||||
merchantId: item.merchantId || item.merchant_id || '',
|
merchantId: item.merchantId || item.merchant_id || '',
|
||||||
merchantName: item.merchantName || item.merchant_name || '',
|
merchantName: item.merchantName || item.merchant_name || '',
|
||||||
chainName: item.chainName || item.chain_name || '-',
|
chainName: item.chainName || item.chain_name || '-',
|
||||||
@@ -419,6 +438,7 @@ export default {
|
|||||||
}))
|
}))
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
reportType: 'visit',
|
||||||
merchantId: '',
|
merchantId: '',
|
||||||
merchantName: '',
|
merchantName: '',
|
||||||
chainName: '-',
|
chainName: '-',
|
||||||
@@ -509,13 +529,13 @@ export default {
|
|||||||
matchesItemChain(item) {
|
matchesItemChain(item) {
|
||||||
const selectedId = this.filterForm.chainId
|
const selectedId = this.filterForm.chainId
|
||||||
if (selectedId === '' || selectedId === null || selectedId === undefined) return true
|
if (selectedId === '' || selectedId === null || selectedId === undefined) return true
|
||||||
if (this.activeType === 'visit') {
|
if (this.recordType(item) === 'visit') {
|
||||||
return (item.storeRows || []).some(store => this.matchesSelectedChain('', store.chainName, selectedId))
|
return (item.storeRows || []).some(store => this.matchesSelectedChain('', store.chainName, selectedId))
|
||||||
}
|
}
|
||||||
return this.matchesSelectedChain(item.chainIdNormalized, item.chainName, selectedId)
|
return this.matchesSelectedChain(item.chainIdNormalized, item.chainName, selectedId)
|
||||||
},
|
},
|
||||||
matchesItemKeyword(item, keyword) {
|
matchesItemKeyword(item, keyword) {
|
||||||
if (this.activeType === 'visit') {
|
if (this.recordType(item) === 'visit') {
|
||||||
return (item.storeRows || []).some(store => String(store.merchantName || '').toLowerCase().includes(keyword))
|
return (item.storeRows || []).some(store => String(store.merchantName || '').toLowerCase().includes(keyword))
|
||||||
}
|
}
|
||||||
return String(item.merchantName || '').toLowerCase().includes(keyword)
|
return String(item.merchantName || '').toLowerCase().includes(keyword)
|
||||||
@@ -584,13 +604,14 @@ export default {
|
|||||||
},
|
},
|
||||||
handleResubmit(item) {
|
handleResubmit(item) {
|
||||||
if (!item || item.id === undefined || item.id === null) return
|
if (!item || item.id === undefined || item.id === null) return
|
||||||
const url = this.activeType === 'visit'
|
// 按记录自带的类型跳转,不用当前 tab —— tab 与实际数据错位时也不会进错表单
|
||||||
|
const url = this.recordType(item) === 'visit'
|
||||||
? `/packageUser/self-report/visit?id=${item.id}&mode=resubmit`
|
? `/packageUser/self-report/visit?id=${item.id}&mode=resubmit`
|
||||||
: `/packageUser/self-report/create?id=${item.id}&mode=resubmit`
|
: `/packageUser/self-report/create?id=${item.id}&mode=resubmit`
|
||||||
uni.navigateTo({url})
|
uni.navigateTo({url})
|
||||||
},
|
},
|
||||||
cardTitle(item) {
|
cardTitle(item) {
|
||||||
if (this.activeType === 'visit') {
|
if (this.recordType(item) === 'visit') {
|
||||||
const names = (item.storeRows || [])
|
const names = (item.storeRows || [])
|
||||||
.map(store => store.merchantName)
|
.map(store => store.merchantName)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -602,7 +623,7 @@ export default {
|
|||||||
return item.merchantName || '-'
|
return item.merchantName || '-'
|
||||||
},
|
},
|
||||||
cardSubTitle(item) {
|
cardSubTitle(item) {
|
||||||
if (this.activeType === 'visit') return ''
|
if (this.recordType(item) === 'visit') return ''
|
||||||
return item.chainName && item.chainName !== '-' ? `连锁:${item.chainName}` : ''
|
return item.chainName && item.chainName !== '-' ? `连锁:${item.chainName}` : ''
|
||||||
},
|
},
|
||||||
changeType(value) {
|
changeType(value) {
|
||||||
|
|||||||
@@ -82,10 +82,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
this.statusBarHeight = this.readStatusBarHeight()
|
||||||
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 优先用新接口,取不到时回退旧接口,避免 getSystemInfoSync 的废弃告警
|
||||||
|
readStatusBarHeight() {
|
||||||
|
try {
|
||||||
|
if (uni.getWindowInfo) {
|
||||||
|
const info = uni.getWindowInfo()
|
||||||
|
if (info && info.statusBarHeight !== undefined) return info.statusBarHeight || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore:新接口不可用时走下面的回退
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
return (systemInfo && systemInfo.statusBarHeight) || 0
|
||||||
|
} catch (error) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
},
|
||||||
back() {
|
back() {
|
||||||
if (getCurrentPages().length === 1) {
|
if (getCurrentPages().length === 1) {
|
||||||
uni.switchTab({url: '/pages/index/index'})
|
uni.switchTab({url: '/pages/index/index'})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<view class="section-extra">{{ storeList.length }} 家门店</view>
|
<view class="section-extra">{{ storeList.length }} 家门店</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!storeList.length" class="audit-empty-tip">暂无巡店记录,请点击下方「新增巡店」</view>
|
<view v-if="!storeList.length" class="audit-empty-tip">暂无巡店记录,请点击下方「新增巡店」</view>
|
||||||
<view v-for="(item, idx) in storeList" :key="`store-${idx}`" class="visit-store-card">
|
<view v-for="(item, idx) in storeList" :key="item._rowKey" class="visit-store-card">
|
||||||
<view class="visit-store-head">
|
<view class="visit-store-head">
|
||||||
<view>
|
<view>
|
||||||
<view class="visit-store-title">巡店{{ idx + 1 }}</view>
|
<view class="visit-store-title">巡店{{ idx + 1 }}</view>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="photo-grid">
|
<view v-else class="photo-grid">
|
||||||
<view v-for="(photo, photoIdx) in item.photos" :key="`store-${idx}-photo-${photoIdx}`" class="photo-item">
|
<view v-for="(photo, photoIdx) in item.photos" :key="photo" class="photo-item">
|
||||||
<image :src="photo" mode="aspectFill" class="photo-image" @tap="previewStorePhotos(idx, photoIdx)"/>
|
<image :src="photo" mode="aspectFill" class="photo-image" @tap="previewStorePhotos(idx, photoIdx)"/>
|
||||||
<view class="photo-remove" @tap="removeStorePhoto(idx, photoIdx)">x</view>
|
<view class="photo-remove" @tap="removeStorePhoto(idx, photoIdx)">x</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
@change="onChainPickerChange"
|
@change="onChainPickerChange"
|
||||||
>
|
>
|
||||||
<picker-view-column>
|
<picker-view-column>
|
||||||
<view v-for="(item, idx) in merchantChainOptions" :key="`chain-${idx}`" class="merchant-picker-item">
|
<view v-for="(item, idx) in merchantChainOptions" :key="item.id" class="merchant-picker-item">
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</view>
|
</view>
|
||||||
</picker-view-column>
|
</picker-view-column>
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
@change="onMerchantPickerChange"
|
@change="onMerchantPickerChange"
|
||||||
>
|
>
|
||||||
<picker-view-column>
|
<picker-view-column>
|
||||||
<view v-for="(item, idx) in merchantStoreOptions" :key="`merchant-${idx}`" class="merchant-picker-item">
|
<view v-for="(item, idx) in merchantStoreOptions" :key="item.id" class="merchant-picker-item">
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</view>
|
</view>
|
||||||
</picker-view-column>
|
</picker-view-column>
|
||||||
@@ -233,6 +233,8 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
// 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化。
|
// 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化。
|
||||||
this.merchantStore = []
|
this.merchantStore = []
|
||||||
|
// 巡店项 key 的自增种子,同样放实例上,不必进 data 树
|
||||||
|
this.storeKeySeed = 0
|
||||||
},
|
},
|
||||||
async onLoad(options = {}) {
|
async onLoad(options = {}) {
|
||||||
const id = options && options.id !== undefined && options.id !== null ? String(options.id).trim() : ''
|
const id = options && options.id !== undefined && options.id !== null ? String(options.id).trim() : ''
|
||||||
@@ -523,8 +525,23 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ---------- 巡店列表 ----------
|
// ---------- 巡店列表 ----------
|
||||||
|
// wx:key 需要稳定且互不相同的标识;用自增序号,避免同一份数据里出现重复 key
|
||||||
|
buildStoreKey() {
|
||||||
|
this.storeKeySeed = (this.storeKeySeed || 0) + 1
|
||||||
|
return `store_${this.storeKeySeed}`
|
||||||
|
},
|
||||||
|
// 给数组里缺失 _rowKey 的项补齐(重提加载的旧数据没有该字段)
|
||||||
|
ensureStoreKeys(list) {
|
||||||
|
return (Array.isArray(list) ? list : []).map(item => {
|
||||||
|
if (item && item._rowKey) return item
|
||||||
|
return Object.assign({}, item, {_rowKey: this.buildStoreKey()})
|
||||||
|
})
|
||||||
|
},
|
||||||
createStoreItem() {
|
createStoreItem() {
|
||||||
return {
|
return {
|
||||||
|
// 小程序 wx:key 只认属性名,模板字面量会被编译丢弃;
|
||||||
|
// 该字段同时让增删巡店时的节点复用稳定(删除靠后的项不会让前面的项重建)
|
||||||
|
_rowKey: this.buildStoreKey(),
|
||||||
visitTime: this.formatDateTime(new Date()),
|
visitTime: this.formatDateTime(new Date()),
|
||||||
merchantId: '',
|
merchantId: '',
|
||||||
merchantName: '',
|
merchantName: '',
|
||||||
@@ -729,7 +746,7 @@ export default {
|
|||||||
const nextForm = Object.assign(this.buildForm(), this.cloneValue(draft.form || {}))
|
const nextForm = Object.assign(this.buildForm(), this.cloneValue(draft.form || {}))
|
||||||
this.form = nextForm
|
this.form = nextForm
|
||||||
const storeList = Array.isArray(draft.storeList) ? draft.storeList : []
|
const storeList = Array.isArray(draft.storeList) ? draft.storeList : []
|
||||||
this.storeList = storeList.map(item => this.normalizeStoreItem(item))
|
this.storeList = this.ensureStoreKeys(storeList.map(item => this.normalizeStoreItem(item)))
|
||||||
this.showChainPicker = false
|
this.showChainPicker = false
|
||||||
this.showMerchantPicker = false
|
this.showMerchantPicker = false
|
||||||
this.activePickerIndex = -1
|
this.activePickerIndex = -1
|
||||||
@@ -762,7 +779,7 @@ export default {
|
|||||||
const summary = this.parseStoredJson(record.summary, record.summary || '')
|
const summary = this.parseStoredJson(record.summary, record.summary || '')
|
||||||
this.form.summary = typeof summary === 'string' ? summary : ''
|
this.form.summary = typeof summary === 'string' ? summary : ''
|
||||||
const storeList = this.parseStoredJson(record.storeList, [])
|
const storeList = this.parseStoredJson(record.storeList, [])
|
||||||
this.storeList = (Array.isArray(storeList) ? storeList : []).map(item => this.normalizeStoreItem(item))
|
this.storeList = this.ensureStoreKeys((Array.isArray(storeList) ? storeList : []).map(item => this.normalizeStoreItem(item)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.toast(error && error.message ? error.message : '填报记录加载失败')
|
await this.toast(error && error.message ? error.message : '填报记录加载失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user