From 2ff8d4220707512fdbf4a39b158b85a739fd5c7d Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Mon, 12 Oct 2026 13:47:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E8=87=AA=E4=B8=BB=E5=A1=AB=E6=8A=A5):=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=87=8D=E6=8F=90=E8=B7=B3=E8=BD=AC=E6=8C=89?= =?UTF-8?q?=E5=A1=AB=E6=8A=A5=E7=B1=BB=E5=9E=8B=E3=80=81=E8=A1=A5=20wx:key?= =?UTF-8?q?=20=E5=B9=B6=E6=B8=85=E7=90=86=E5=BA=9F=E5=BC=83=20API=20?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、填报记录「重新提交」跳错表单(用户反馈) 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 且会被依赖重装覆盖,属组件库问题。 --- components/AppNavbar.vue | 26 +++++++++++++++-- packageUser/self-report/records.vue | 45 +++++++++++++++++++++-------- packageUser/self-report/select.vue | 20 +++++++++++-- packageUser/self-report/visit.vue | 29 +++++++++++++++---- 4 files changed, 97 insertions(+), 23 deletions(-) diff --git a/components/AppNavbar.vue b/components/AppNavbar.vue index 9e33222..415a4f4 100644 --- a/components/AppNavbar.vue +++ b/components/AppNavbar.vue @@ -27,21 +27,41 @@ export default { }, data() { return { - currentPages: [] + currentPages: [], + // 状态栏高度只在挂载时取一次:getSafeTop 在 render 中被调用, + // 每次都同步读系统信息会反复触发 wx.getSystemInfoSync 废弃告警。 + safeTop: 0 } }, methods: { getSafeTop() { - const systemInfo = uni.getSystemInfoSync() - return systemInfo.statusBarHeight + return this.safeTop }, back(){ if (this.currentPages.length === 1) uni.switchTab({url: '/pages/index/index'}) 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() { this.currentPages = getCurrentPages() + this.safeTop = this.readSafeTop() } } diff --git a/packageUser/self-report/records.vue b/packageUser/self-report/records.vue index 678351e..02423c6 100644 --- a/packageUser/self-report/records.vue +++ b/packageUser/self-report/records.vue @@ -61,7 +61,7 @@ {{ statusText(item.status) }} - + 活动时间 {{ item.activityTime || '-' }} @@ -69,7 +69,7 @@ 巡店门店数 {{ item.storeCount || 0 }}家 - + 评分 {{ item.rating || '-' }}星 @@ -88,7 +88,7 @@ - + 暂无巡店记录 @@ -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) { diff --git a/packageUser/self-report/select.vue b/packageUser/self-report/select.vue index 694dcdd..3fef2da 100644 --- a/packageUser/self-report/select.vue +++ b/packageUser/self-report/select.vue @@ -82,10 +82,26 @@ export default { } }, onLoad() { - const systemInfo = uni.getSystemInfoSync() - this.statusBarHeight = systemInfo.statusBarHeight || 0 + this.statusBarHeight = this.readStatusBarHeight() }, 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() { if (getCurrentPages().length === 1) { uni.switchTab({url: '/pages/index/index'}) diff --git a/packageUser/self-report/visit.vue b/packageUser/self-report/visit.vue index 1e17d12..678cc3e 100644 --- a/packageUser/self-report/visit.vue +++ b/packageUser/self-report/visit.vue @@ -8,7 +8,7 @@ {{ storeList.length }} 家门店 暂无巡店记录,请点击下方「新增巡店」 - + 巡店{{ idx + 1 }} @@ -70,7 +70,7 @@ - + x @@ -117,7 +117,7 @@ @change="onChainPickerChange" > - + {{ item.text }} @@ -148,7 +148,7 @@ @change="onMerchantPickerChange" > - + {{ item.text }} @@ -233,6 +233,8 @@ export default { created() { // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化。 this.merchantStore = [] + // 巡店项 key 的自增种子,同样放实例上,不必进 data 树 + this.storeKeySeed = 0 }, async onLoad(options = {}) { 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() { return { + // 小程序 wx:key 只认属性名,模板字面量会被编译丢弃; + // 该字段同时让增删巡店时的节点复用稳定(删除靠后的项不会让前面的项重建) + _rowKey: this.buildStoreKey(), visitTime: this.formatDateTime(new Date()), merchantId: '', merchantName: '', @@ -729,7 +746,7 @@ export default { const nextForm = Object.assign(this.buildForm(), this.cloneValue(draft.form || {})) this.form = nextForm 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.showMerchantPicker = false this.activePickerIndex = -1 @@ -762,7 +779,7 @@ export default { const summary = this.parseStoredJson(record.summary, record.summary || '') this.form.summary = typeof summary === 'string' ? summary : '' 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) { await this.toast(error && error.message ? error.message : '填报记录加载失败') } finally {