From 575b4a723ac776d825261e923e9e8f23ea8a5b30 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Sun, 11 Oct 2026 15:18:06 +0800 Subject: [PATCH] =?UTF-8?q?perf(=E9=97=A8=E5=BA=97=E9=80=89=E6=8B=A9):=20?= =?UTF-8?q?=E9=97=A8=E5=BA=97=E6=98=8E=E7=BB=86=E7=A7=BB=E5=87=BA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=20data=EF=BC=8C=E4=BF=AE=E5=A4=8D=E9=A6=96=E5=B1=8F?= =?UTF-8?q?=E5=8D=A1=E9=A1=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 门店接口 /shop/merchant/groupByChain 一次返回 8105 家门店(约 1.1MB)。 此前把门店明细原样放进页面 data,data 树膨胀到 1118KB,导致此后每次 setData 都要重新 diff/序列化整棵树——即使载荷只有 1 个字段,耗时也从 约 490ms 涨到约 1000ms,首屏因此多花约 3.5s。 改为把明细挂在实例属性 merchantStore 上(不进页面 data), merchantChainList 只保留 {id, text, merchantCount} 骨架, 新增 getMerchantsByChainId() / getVisibleMerchants() 按需取数。 涉及页面: - packageUser/self-report/create.vue - packageUser/chronic/index.vue - pages/task/detail.vue - pages/share/create.vue - pages/share/index.vue - pages/audit/components/AuditShareItem.vue 实测(微信开发者工具自动化): - 页面 data 门店明细 1118KB -> 1KB - 单字段 setData 998ms -> 10~53ms - self-report/create 首屏 9055ms -> 5341ms - 门店 picker 选项数不变(大参林 2700 项),搜索与选中回填均正常 --- packageUser/chronic/index.vue | 36 ++++++++++-- packageUser/self-report/create.vue | 39 +++++++++++-- pages/audit/components/AuditShareItem.vue | 65 +++++++++++++++------ pages/share/create.vue | 69 ++++++++++++++++------- pages/share/index.vue | 64 +++++++++++++++------ pages/task/detail.vue | 68 +++++++++++++++------- 6 files changed, 258 insertions(+), 83 deletions(-) diff --git a/packageUser/chronic/index.vue b/packageUser/chronic/index.vue index 79afbbc..911033d 100644 --- a/packageUser/chronic/index.vue +++ b/packageUser/chronic/index.vue @@ -446,8 +446,10 @@ export default { return list.length ? list.map(item => ({id: item.id, text: item.text})) : [{id: '', text: '暂无连锁'}] }, merchantOptions() { - const chain = this.merchantChainList.find(item => String(item.id) === String(this.form.chainId)) - const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : [] + // 门店明细挂在实例的 merchantStore 上,不进页面 data(见 setMerchantChains) + const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] + const hasChain = chainList.some(item => String(item.id) === String(this.form.chainId)) + const merchants = hasChain ? this.getMerchantsByChainId(this.form.chainId) : [] return merchants.length ? merchants : [{id: '', text: '无门店'}] }, merchantFilteredOptions() { @@ -494,6 +496,10 @@ export default { this.merchantPickerValue = [0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化 + this.merchantStore = [] + }, onShow() { this.fetchMerchantList() }, @@ -503,11 +509,12 @@ export default { try { const {data} = await merchantGroupByChainReq({}) const list = this.unwrapList(data) - this.merchantChainList = list + const chains = list .map((item, index) => this.normalizeMerchantChain(item, index)) .filter(item => Array.isArray(item.merchants) && item.merchants.length > 0) + this.setMerchantChains(chains) } catch (error) { - this.merchantChainList = [] + this.setMerchantChains([]) uni.showToast({ title: '门店列表获取失败', icon: 'none' @@ -516,6 +523,27 @@ export default { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, openChainPicker() { this.showChainPicker = true }, diff --git a/packageUser/self-report/create.vue b/packageUser/self-report/create.vue index fb4d70f..180efd6 100644 --- a/packageUser/self-report/create.vue +++ b/packageUser/self-report/create.vue @@ -385,11 +385,14 @@ export default { }, merchantChainOptions() { const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] - return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}] + return list.length ? list : [{id: '', text: '暂无连锁', merchantCount: 0}] }, merchantStoreOptions() { - const currentChain = this.merchantChainOptions.find(chain => String(chain.id) === String(this.form.chainId)) || {} - const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : [] + // 门店明细不放在页面 data 里(见 setMerchantChains),从实例上的 store 取。 + // 这里读 merchantChainList 是为了建立响应式依赖:门店数据到达后需要重算。 + const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] + const hasChain = chainList.some(chain => String(chain.id) === String(this.form.chainId)) + const merchants = hasChain ? this.getMerchantsByChainId(this.form.chainId) : [] const keyword = (this.merchantStoreSearchKeyword || '').trim() const filteredMerchants = this.filterMerchantStores(merchants) return filteredMerchants.length ? filteredMerchants : [{id: '', text: keyword ? '无匹配门店' : '无门店'}] @@ -442,6 +445,10 @@ export default { this.merchantPickerValue = [0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化。 + this.merchantStore = [] + }, async onShow() { this.isLoggedIn = !!getUserInfo().token if (!this.hasInitialized) { @@ -803,12 +810,13 @@ export default { const {data} = await merchantGroupByChainReq({}) const list = this.unwrapList(data) const groupedList = this.groupMerchantListByChain(list) - this.merchantChainList = (groupedList.length + const chains = (groupedList.length ? groupedList : list.map((item, index) => this.normalizeMerchantChain(item, index))) .filter(item => Array.isArray(item.merchants) && item.merchants.length > 0) + this.setMerchantChains(chains) } catch (error) { - this.merchantChainList = [] + this.setMerchantChains([]) uni.showToast({ title: '门店列表获取失败', icon: 'none' @@ -817,6 +825,27 @@ export default { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, openChainPicker() { if (!this.merchantChainList.length) return const chainIndex = this.merchantChainOptions.findIndex(item => String(item.id) === String(this.form.chainId)) diff --git a/pages/audit/components/AuditShareItem.vue b/pages/audit/components/AuditShareItem.vue index f7044ba..3d22eab 100644 --- a/pages/audit/components/AuditShareItem.vue +++ b/pages/audit/components/AuditShareItem.vue @@ -370,26 +370,20 @@ export default { editMerchantChainOptions() { const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] const keyword = (this.editMerchantSearchKeyword || '').trim() - if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}] + if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchantCount: 0}] const filtered = list .map(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] - const chainMatch = (chain.text || '').includes(keyword) - if (chainMatch) return {...chain} - const matchedMerchants = merchants.filter(merchant => (merchant.text || '').includes(keyword)) - if (!matchedMerchants.length) return null - return { - ...chain, - merchants: matchedMerchants - } + const visible = this.getVisibleMerchants(chain) + if (!visible.length) return null + return Object.assign({}, chain, {merchantCount: visible.length}) }) .filter(Boolean) - return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchants: []}] + return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchantCount: 0}] }, editMerchantStoreOptions() { const chainIndex = Number(this.editMerchantPickerValue[0] || 0) const currentChain = this.editMerchantChainOptions[chainIndex] || this.editMerchantChainOptions[0] || {} - const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : [] + const merchants = this.getVisibleMerchants(currentChain) return merchants.length ? merchants : [{id: '', text: '无门店'}] }, editMerchantPickerIndexes() { @@ -398,12 +392,12 @@ export default { let chainIndex = chainList.findIndex(item => item.id === this.editForm.chainId) if (chainIndex < 0 && this.editForm.merchantId) { chainIndex = chainList.findIndex(item => { - const merchants = Array.isArray(item.merchants) ? item.merchants : [] + const merchants = this.getMerchantsByChainId(item && item.id) return merchants.some(merchant => merchant.id === this.editForm.merchantId) }) } if (chainIndex < 0) chainIndex = 0 - const merchants = Array.isArray(chainList[chainIndex].merchants) ? chainList[chainIndex].merchants : [] + const merchants = this.getMerchantsByChainId(chainList[chainIndex] && chainList[chainIndex].id) let merchantIndex = merchants.findIndex(item => item.id === this.editForm.merchantId) if (merchantIndex < 0) merchantIndex = 0 return [chainIndex, merchantIndex] @@ -470,10 +464,28 @@ export default { this.editMerchantPickerValue = [0, 0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化 + this.merchantStore = [] + }, beforeDestroy() { this.clearGoodsCodeTimers() }, methods: { + // 门店明细只存在实例的 merchantStore 上(不进页面 data),按连锁 id 取 + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, + // 搜索关键词命中的门店:连锁名命中则整列保留,否则按门店名过滤 + getVisibleMerchants(chain) { + const merchants = this.getMerchantsByChainId(chain && chain.id) + const keyword = (this.editMerchantSearchKeyword || '').trim() + if (!keyword) return merchants + if ((chain.text || '').includes(keyword)) return merchants + return merchants.filter(merchant => (merchant.text || '').includes(keyword)) + }, async toggleDetailExpanded() { const nextExpanded = !this.detailExpanded this.detailExpanded = nextExpanded @@ -1048,11 +1060,12 @@ export default { try { const {data} = await merchantGroupByChainReq({}) const list = this.unwrapNestedList(data) - this.merchantChainList = list + const chains = list .map((item, index) => this.normalizeMerchantChain(item, index)) .filter(item => Array.isArray(item.merchants) && item.merchants.length > 0) + this.setMerchantChains(chains) } catch (error) { - this.merchantChainList = [] + this.setMerchantChains([]) uni.showToast({ title: '门店列表获取失败', icon: 'none' @@ -1061,6 +1074,22 @@ export default { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, async openEditMerchantPicker() { if (!this.merchantChainList || this.merchantChainList.length === 0) { await this.getMerchantList() @@ -1075,7 +1104,7 @@ export default { const chainIndex = Number(this.editMerchantPickerValue[0] || 0) const merchantIndex = Number(this.editMerchantPickerValue[1] || 0) const chain = this.editMerchantChainOptions[chainIndex] || null - const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) const selected = merchants[merchantIndex] if (!selected || selected.id === '' || selected.id === null || selected.id === undefined) { return @@ -1096,7 +1125,7 @@ export default { if (!Number.isFinite(chainIndex) || chainIndex < 0) chainIndex = 0 if (!Number.isFinite(merchantIndex) || merchantIndex < 0) merchantIndex = 0 const chain = this.editMerchantChainOptions[chainIndex] || this.editMerchantChainOptions[0] || {} - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) if (!merchants.length) { merchantIndex = 0 } else if (chainIndex !== currentChainIndex || merchantIndex > merchants.length - 1) { diff --git a/pages/share/create.vue b/pages/share/create.vue index 5fcaa78..08792ab 100644 --- a/pages/share/create.vue +++ b/pages/share/create.vue @@ -266,40 +266,35 @@ export default { merchantChainOptions() { const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] const keyword = (this.merchantSearchKeyword || '').trim() - if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}] + if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchantCount: 0}] const filtered = list .map(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] - const chainMatch = (chain.text || '').includes(keyword) - if (chainMatch) return {...chain} - const matchedMerchants = merchants.filter(merchant => (merchant.text || '').includes(keyword)) - if (!matchedMerchants.length) return null - return { - ...chain, - merchants: matchedMerchants - } + const visible = this.getVisibleMerchants(chain) + if (!visible.length) return null + return Object.assign({}, chain, {merchantCount: visible.length}) }) .filter(Boolean) - return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchants: []}] + return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchantCount: 0}] }, merchantStoreOptions() { const chainIndex = Number(this.merchantPickerValue[0] || 0) const currentChain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : [] + const merchants = this.getVisibleMerchants(currentChain) return merchants.length ? merchants : [{id: '', text: '无门店'}] }, + // 门店明细只存在实例的 merchantStore 上(不进页面 data),按连锁 id 取 merchantPickerIndexes() { const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] if (!chainList.length) return [0, 0] let chainIndex = chainList.findIndex(item => item.id === this.form.chainId) if (chainIndex < 0 && this.form.merchantId) { chainIndex = chainList.findIndex(item => { - const merchants = Array.isArray(item.merchants) ? item.merchants : [] + const merchants = this.getMerchantsByChainId(item.id) return merchants.some(merchant => merchant.id === this.form.merchantId) }) } if (chainIndex < 0) chainIndex = 0 - const merchants = Array.isArray(chainList[chainIndex].merchants) ? chainList[chainIndex].merchants : [] + const merchants = this.getMerchantsByChainId(chainList[chainIndex].id) let merchantIndex = merchants.findIndex(item => item.id === this.form.merchantId) if (merchantIndex < 0) merchantIndex = 0 return [chainIndex, merchantIndex] @@ -365,6 +360,10 @@ export default { this.merchantPickerValue = [0, 0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化 + this.merchantStore = [] + }, onLoad(options = {}) { this.lat = this.parseNumber(options.lat) this.lng = this.parseNumber(options.lng) @@ -373,6 +372,20 @@ export default { this.init() }, methods: { + // 门店明细只存在实例的 merchantStore 上(不进页面 data),按连锁 id 取 + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, + // 搜索关键词命中的门店:连锁名命中则整列保留,否则按门店名过滤 + getVisibleMerchants(chain) { + const merchants = this.getMerchantsByChainId(chain && chain.id) + const keyword = (this.merchantSearchKeyword || '').trim() + if (!keyword) return merchants + if ((chain.text || '').includes(keyword)) return merchants + return merchants.filter(merchant => (merchant.text || '').includes(keyword)) + }, parseNumber(value) { if (value === '' || value === null || value === undefined) return null const n = Number(value) @@ -448,8 +461,9 @@ export default { }, flattenMerchantsFromChains(chainList) { const rows = [] + // chainList 可能只是骨架(无 merchants),明细统一从实例的 merchantStore 取 ;(Array.isArray(chainList) ? chainList : []).forEach(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getMerchantsByChainId(chain && chain.id) merchants.forEach(merchant => { rows.push({ ...merchant, @@ -897,11 +911,12 @@ export default { try { const {data} = await merchantGroupByChainReq({}) const list = this.unwrapList(data) - this.merchantChainList = list + const chains = list .map((item, index) => this.normalizeMerchantChain(item, index)) .filter(item => Array.isArray(item.merchants) && item.merchants.length > 0) + this.setMerchantChains(chains) } catch (error) { - this.merchantChainList = [] + this.setMerchantChains([]) uni.showToast({ title: '门店列表获取失败', icon: 'none' @@ -910,6 +925,22 @@ export default { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, async getProductsByMerchant() { if (!this.form.merchantId) { this.form.list = [] @@ -991,7 +1022,7 @@ export default { if (!Number.isFinite(chainIndex) || chainIndex < 0) chainIndex = 0 if (!Number.isFinite(merchantIndex) || merchantIndex < 0) merchantIndex = 0 const chain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) if (!merchants.length) { merchantIndex = 0 } else if (chainIndex !== currentChainIndex || merchantIndex > merchants.length - 1) { @@ -1003,7 +1034,7 @@ export default { const chainIndex = Number(this.merchantPickerValue[0] || 0) const merchantIndex = Number(this.merchantPickerValue[1] || 0) const chain = this.merchantChainOptions[chainIndex] || null - const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) const selected = merchants[merchantIndex] if (!selected || selected.id === '' || selected.id === null || selected.id === undefined) { return diff --git a/pages/share/index.vue b/pages/share/index.vue index dbc230b..0d36fa4 100644 --- a/pages/share/index.vue +++ b/pages/share/index.vue @@ -380,26 +380,20 @@ export default { merchantChainOptions() { const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] const keyword = (this.merchantSearchKeyword || '').trim() - if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}] + if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchantCount: 0}] const filtered = list .map(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] - const chainMatch = (chain.text || '').includes(keyword) - if (chainMatch) return {...chain} - const matchedMerchants = merchants.filter(merchant => (merchant.text || '').includes(keyword)) - if (!matchedMerchants.length) return null - return { - ...chain, - merchants: matchedMerchants - } + const visible = this.getVisibleMerchants(chain) + if (!visible.length) return null + return Object.assign({}, chain, {merchantCount: visible.length}) }) .filter(Boolean) - return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchants: []}] + return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchantCount: 0}] }, merchantStoreOptions() { const chainIndex = Number(this.merchantPickerValue[0] || 0) const currentChain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : [] + const merchants = this.getVisibleMerchants(currentChain) return merchants.length ? merchants : [{id: '', text: '无门店'}] }, merchantPickerIndexes() { @@ -408,12 +402,12 @@ export default { let chainIndex = chainList.findIndex(item => item.id === this.merchantChainId) if (chainIndex < 0 && this.merchantId) { chainIndex = chainList.findIndex(item => { - const merchants = Array.isArray(item.merchants) ? item.merchants : [] + const merchants = this.getMerchantsByChainId(item.id) return merchants.some(merchant => merchant.id === this.merchantId) }) } if (chainIndex < 0) chainIndex = 0 - const merchants = Array.isArray(chainList[chainIndex].merchants) ? chainList[chainIndex].merchants : [] + const merchants = this.getMerchantsByChainId(chainList[chainIndex].id) let merchantIndex = merchants.findIndex(item => item.id === this.merchantId) if (merchantIndex < 0) merchantIndex = 0 return [chainIndex, merchantIndex] @@ -446,6 +440,10 @@ export default { this.merchantPickerValue = [0, 0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化 + this.merchantStore = [] + }, async onShow() { await this.refreshPageData() }, @@ -458,6 +456,20 @@ export default { }, methods: { dayjs, + // 门店明细只存在实例的 merchantStore 上(不进页面 data),按连锁 id 取 + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, + // 搜索关键词命中的门店:连锁名命中则整列保留,否则按门店名过滤 + getVisibleMerchants(chain) { + const merchants = this.getMerchantsByChainId(chain && chain.id) + const keyword = (this.merchantSearchKeyword || '').trim() + if (!keyword) return merchants + if ((chain.text || '').includes(keyword)) return merchants + return merchants.filter(merchant => (merchant.text || '').includes(keyword)) + }, async refreshPageData() { if (getUserInfo().token) { await this.getUserData() @@ -561,11 +573,11 @@ export default { const chainList = list .map((item, index) => this.normalizeMerchantChain(item, index)) .filter(item => Array.isArray(item.merchants) && item.merchants.length) - this.merchantChainList = [{ + this.setMerchantChains([{ id: '', text: '全部连锁', merchants: [{id: '', text: '全部门店'}] - }].concat(chainList) + }].concat(chainList)) } catch (error) { uni.showToast({ title: '门店列表获取失败', @@ -575,6 +587,22 @@ export default { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, async openMerchantPicker() { if (!this.merchantChainList || this.merchantChainList.length <= 1) { await this.getMerchantList() @@ -594,7 +622,7 @@ export default { if (!Number.isFinite(chainIndex) || chainIndex < 0) chainIndex = 0 if (!Number.isFinite(merchantIndex) || merchantIndex < 0) merchantIndex = 0 const chain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) if (!merchants.length) { merchantIndex = 0 } else if (chainIndex !== currentChainIndex || merchantIndex > merchants.length - 1) { @@ -606,7 +634,7 @@ export default { const chainIndex = Number(this.merchantPickerValue[0] || 0) const merchantIndex = Number(this.merchantPickerValue[1] || 0) const chain = this.merchantChainOptions[chainIndex] || null - const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) const selected = merchants[merchantIndex] || null if (!selected) return this.merchantChainId = chain ? (chain.id ?? '') : '' diff --git a/pages/task/detail.vue b/pages/task/detail.vue index 768e55a..a56ac80 100644 --- a/pages/task/detail.vue +++ b/pages/task/detail.vue @@ -644,26 +644,20 @@ export default { merchantChainOptions() { const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : [] const keyword = (this.merchantSearchKeyword || '').trim() - if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}] + if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchantCount: 0}] const filtered = list .map(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] - const chainMatch = (chain.text || '').includes(keyword) - if (chainMatch) return {...chain} - const matchedMerchants = merchants.filter(merchant => (merchant.text || '').includes(keyword)) - if (!matchedMerchants.length) return null - return { - ...chain, - merchants: matchedMerchants - } + const visible = this.getVisibleMerchants(chain) + if (!visible.length) return null + return Object.assign({}, chain, {merchantCount: visible.length}) }) .filter(Boolean) - return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchants: []}] + return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchantCount: 0}] }, merchantStoreOptions() { const chainIndex = Number(this.merchantPickerValue[0] || 0) const currentChain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : [] + const merchants = this.getVisibleMerchants(currentChain) return merchants.length ? merchants : [{id: '', text: '无门店'}] }, merchantIndicatorStyle() { @@ -747,6 +741,10 @@ export default { this.merchantPickerValue = [0, 0] } }, + created() { + // 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化 + this.merchantStore = [] + }, async onLoad(options) { this.auditEntry = String(options.fromAudit || '') === '1' this.auditId = options.auditId || '' @@ -781,6 +779,20 @@ export default { } }, methods: { + // 门店明细只存在实例的 merchantStore 上(不进页面 data),按连锁 id 取 + getMerchantsByChainId(chainId) { + const store = Array.isArray(this.merchantStore) ? this.merchantStore : [] + const chain = store.find(item => String(item.id) === String(chainId)) + return chain && Array.isArray(chain.merchants) ? chain.merchants : [] + }, + // 搜索关键词命中的门店:连锁名命中则整列保留,否则按门店名过滤 + getVisibleMerchants(chain) { + const merchants = this.getMerchantsByChainId(chain && chain.id) + const keyword = (this.merchantSearchKeyword || '').trim() + if (!keyword) return merchants + if ((chain.text || '').includes(keyword)) return merchants + return merchants.filter(merchant => (merchant.text || '').includes(keyword)) + }, calReward() { const reward = this.resolveTaskAmount([ this.task && this.task.reward, @@ -978,8 +990,9 @@ export default { }, flattenMerchantsFromChains(chainList) { const list = [] + // chainList 可能只是骨架(无 merchants),明细统一从实例的 merchantStore 取 ;(Array.isArray(chainList) ? chainList : []).forEach(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getMerchantsByChainId(chain && chain.id) merchants.forEach(merchant => { if (!merchant || merchant.id === '' || merchant.id === null || merchant.id === undefined) return list.push(Object.assign({}, merchant, { @@ -1110,16 +1123,33 @@ export default { try { const {data} = await merchantGroupByChainReq({}) const list = this.unwrapList(data) - this.merchantChainList = list + const chains = list .map((item, index) => this.normalizeMerchantChain(item, index)) .filter(item => Array.isArray(item.merchants) && item.merchants.length) + this.setMerchantChains(chains) } catch (error) { - this.merchantChainList = [] + this.setMerchantChains([]) this.toast('门店列表获取失败') } finally { this.merchantLoading = false } }, + // 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。 + // 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新 + // diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。 + setMerchantChains(chains) { + const list = Array.isArray(chains) ? chains : [] + this.merchantStore = list.map(chain => ({ + id: chain.id, + text: chain.text, + merchants: Array.isArray(chain.merchants) ? chain.merchants : [] + })) + this.merchantChainList = this.merchantStore.map(chain => ({ + id: chain.id, + text: chain.text, + merchantCount: chain.merchants.length + })) + }, async prepareStoreTaskData() { await this.getMerchantList() this.syncStoreMerchantMeta() @@ -1157,12 +1187,12 @@ export default { let chainIndex = chainList.findIndex(chain => String(chain.id) === String(item && item.chainId !== undefined ? item.chainId : '')) if (chainIndex < 0 && item && item.merchantId) { chainIndex = chainList.findIndex(chain => { - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getMerchantsByChainId(chain && chain.id) return merchants.some(merchant => String(merchant.id) === String(item.merchantId)) }) } if (chainIndex < 0) chainIndex = 0 - const merchants = Array.isArray(chainList[chainIndex].merchants) ? chainList[chainIndex].merchants : [] + const merchants = this.getMerchantsByChainId(chainList[chainIndex] && chainList[chainIndex].id) let merchantIndex = merchants.findIndex(merchant => String(merchant.id) === String(item && item.merchantId !== undefined ? item.merchantId : '')) if (merchantIndex < 0) merchantIndex = 0 return [chainIndex, merchantIndex] @@ -1194,7 +1224,7 @@ export default { if (!Number.isFinite(chainIndex) || chainIndex < 0) chainIndex = 0 if (!Number.isFinite(merchantIndex) || merchantIndex < 0) merchantIndex = 0 const chain = this.merchantChainOptions[chainIndex] || this.merchantChainOptions[0] || {} - const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) if (!merchants.length) { merchantIndex = 0 } else if (chainIndex !== currentChainIndex || merchantIndex > merchants.length - 1) { @@ -1211,7 +1241,7 @@ export default { const chainIndex = Number(this.merchantPickerValue[0] || 0) const merchantIndex = Number(this.merchantPickerValue[1] || 0) const chain = this.merchantChainOptions[chainIndex] || null - const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : [] + const merchants = this.getVisibleMerchants(chain) const selected = merchants[merchantIndex] || null if (!selected || selected.id === '' || selected.id === null || selected.id === undefined) return const current = this.storeList[index] || this.createStoreItem()