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:
2026-10-12 13:47:03 +08:00
parent aff1e52e3e
commit 2ff8d42207
4 changed files with 97 additions and 23 deletions
+33 -12
View File
@@ -61,7 +61,7 @@
<view :class="['status-badge', statusClass(item.status)]">{{ statusText(item.status) }}</view>
</view>
<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-value">{{ item.activityTime || '-' }}</text>
</view>
@@ -69,7 +69,7 @@
<text class="record-label">巡店门店数</text>
<text class="record-value">{{ item.storeCount || 0 }}家</text>
</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-value">{{ item.rating || '-' }}星</text>
</view>
@@ -88,7 +88,7 @@
</view>
<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-for="(store, sIdx) in item.storeRows" :key="`store-${sIdx}`" class="store-block">
<view class="store-block-head">
@@ -246,6 +246,8 @@ export default {
],
activeStatus: 'all',
records: [],
// 列表请求自增序号:切 tab 会连发请求且响应顺序不保证,只认最后一次,丢弃过期响应
listRequestId: 0,
loading: false,
chainLoading: false,
showChainPicker: false,
@@ -336,19 +338,30 @@ export default {
])
},
async fetchList() {
const requestId = ++this.listRequestId
const type = this.activeType
this.loading = true
try {
const req = this.activeType === 'visit' ? visitReportUserListReq : selfReportUserListReq
const req = type === 'visit' ? visitReportUserListReq : selfReportUserListReq
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) {
if (requestId !== this.listRequestId) return
this.records = []
uni.showToast({
title: '记录加载失败',
icon: 'none'
})
} finally {
this.loading = false
if (requestId === this.listRequestId) {
this.loading = false
}
}
},
async fetchChainList() {
@@ -368,8 +381,13 @@ export default {
this.chainLoading = false
}
},
// 记录所属填报类型:优先取记录自带的 reportType,历史/异常数据回退当前 tab
recordType(item) {
const type = item && item.reportType
return type === 'visit' || type === 'store' ? type : this.activeType
},
normalizeRecord(item = {}) {
if (this.activeType === 'visit') {
if (this.recordType(item) === 'visit') {
return this.normalizeVisitRecord(item)
}
const createRaw = item.createTime || item.createdAt || item.date || ''
@@ -381,6 +399,7 @@ export default {
const summary = typeof summaryRaw === 'string' ? summaryRaw : JSON.stringify(summaryRaw || '')
return {
id: item.id,
reportType: 'store',
merchantId: item.merchantId || item.merchant_id || '',
merchantName: item.merchantName || item.merchant_name || '',
chainName: item.chainName || item.chain_name || '-',
@@ -419,6 +438,7 @@ export default {
}))
return {
id: item.id,
reportType: 'visit',
merchantId: '',
merchantName: '',
chainName: '-',
@@ -509,13 +529,13 @@ export default {
matchesItemChain(item) {
const selectedId = this.filterForm.chainId
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 this.matchesSelectedChain(item.chainIdNormalized, item.chainName, selectedId)
},
matchesItemKeyword(item, keyword) {
if (this.activeType === 'visit') {
if (this.recordType(item) === 'visit') {
return (item.storeRows || []).some(store => String(store.merchantName || '').toLowerCase().includes(keyword))
}
return String(item.merchantName || '').toLowerCase().includes(keyword)
@@ -584,13 +604,14 @@ export default {
},
handleResubmit(item) {
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/create?id=${item.id}&mode=resubmit`
uni.navigateTo({url})
},
cardTitle(item) {
if (this.activeType === 'visit') {
if (this.recordType(item) === 'visit') {
const names = (item.storeRows || [])
.map(store => store.merchantName)
.filter(Boolean)
@@ -602,7 +623,7 @@ export default {
return item.merchantName || '-'
},
cardSubTitle(item) {
if (this.activeType === 'visit') return ''
if (this.recordType(item) === 'visit') return ''
return item.chainName && item.chainName !== '-' ? `连锁:${item.chainName}` : ''
},
changeType(value) {