/** * 连锁 / 门店两级选择的公用逻辑。 * * 抽出来的原因:驻店填报(packageUser/self-report/create.vue)与跑店填报 * (packageUser/self-report/visit.vue)都要「先选连锁、再选门店」, * 且门店全量可达 8000+ 条 —— 明细必须挂在实例上而不是页面 data, * 否则页面 data 树膨胀到 1MB+,每次 setData 的 diff/序列化都会从 ~490ms 涨到 ~1000ms。 * * 用法:在页面 methods 里混入本文件的 methods(页面 data 仍需自己声明 * merchantChainList / merchantLoading / merchantStoreSearchKeyword / 各 picker 的开关与 value)。 */ export function pickFirstText(source, keys = []) { if (!source || typeof source !== 'object') return '' for (let i = 0; i < keys.length; i += 1) { const value = source[keys[i]] if (value !== undefined && value !== null && String(value).trim()) { return String(value).trim() } } return '' } export function unwrapList(data) { if (Array.isArray(data)) return data if (!data || typeof data !== 'object') return [] const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'chainList'] for (let i = 0; i < keys.length; i += 1) { const value = data[keys[i]] if (Array.isArray(value)) return value if (value && typeof value === 'object') { const nested = unwrapList(value) if (nested.length) return nested } } return [data] } export function normalizeMerchant(item) { const id = item.id ?? item.merchantId ?? item.merchant_id const text = item.name || item.merchantName || item.merchant_name || item.title || '未命名门店' const lat = Number(item.lat ?? item.latitude ?? item.merchantLat ?? item.merchant_lat) const lng = Number(item.lng ?? item.longitude ?? item.merchantLng ?? item.merchant_lng) const address = [item.province, item.city, item.region, item.area, item.address].filter(Boolean).join('') return { id, text, chainId: item.chainId ?? item.chain_id ?? '', chainName: item.chainName || item.chain || item.chainAbb || '', lat: Number.isFinite(lat) ? lat : null, lng: Number.isFinite(lng) ? lng : null, province: item.province || item.province_str || '', city: item.city || item.city_str || '', address } } export function normalizeMerchantChain(item, index) { const chainName = pickFirstText(item, ['chainName', 'chain', 'chainAbb', 'name', 'title']) || `连锁${index + 1}` const rawChainId = item.chainId ?? item.chain_id ?? item.id ?? item.chainCode ?? item.chain_code ?? chainName const chainId = rawChainId === undefined || rawChainId === null || String(rawChainId).trim() === '' ? `chain_${index}` : rawChainId const childCandidates = ['children', 'merchantList', 'merchants', 'shopList', 'list', 'records'] let rawChildren = [] for (let i = 0; i < childCandidates.length; i += 1) { const list = item[childCandidates[i]] if (Array.isArray(list) && list.length) { rawChildren = list break } } let merchants = rawChildren .map(child => normalizeMerchant(child)) .filter(merchant => merchant.id !== undefined && merchant.id !== null && String(merchant.id).trim() !== '') .map(merchant => Object.assign({}, merchant, {chainId, chainName})) if (!merchants.length) { const selfMerchant = normalizeMerchant(item) if (selfMerchant.id !== undefined && selfMerchant.id !== null && String(selfMerchant.id).trim() !== '') { merchants = [Object.assign({}, selfMerchant, {chainId, chainName})] } } return { id: chainId, text: chainName, merchants } } // 接口可能返回「扁平门店列表(带 chainName)」,也可能返回「连锁嵌套门店」,两种都兼容 export function groupMerchantListByChain(list) { const chainMap = {} ;(Array.isArray(list) ? list : []).forEach((item, index) => { const merchant = normalizeMerchant(item) if (merchant.id === undefined || merchant.id === null || String(merchant.id).trim() === '') return const chainName = pickFirstText(item, ['chainName', 'chain', 'chainAbb', 'chain_name']) if (!chainName) return const rawChainId = item.chainId ?? item.chain_id ?? item.chainCode ?? item.chain_code ?? chainName const chainId = rawChainId === undefined || rawChainId === null || String(rawChainId).trim() === '' ? `chain_${index}` : rawChainId const key = String(chainId) if (!chainMap[key]) { chainMap[key] = {id: chainId, text: chainName, merchants: []} } chainMap[key].merchants.push(Object.assign({}, merchant, {chainId, chainName})) }) return Object.keys(chainMap).map(key => chainMap[key]) } export function flattenMerchantsFromChains(chainList) { const list = [] ;(Array.isArray(chainList) ? chainList : []).forEach(chain => { const merchants = Array.isArray(chain.merchants) ? chain.merchants : [] merchants.forEach(merchant => { if (!merchant || merchant.id === '' || merchant.id === null || merchant.id === undefined) return list.push(Object.assign({}, merchant, { chainId: merchant.chainId || chain.id || '', chainName: merchant.chainName || chain.text || '' })) }) }) return list } // 门店关键词过滤:连锁名命中由调用方另判,这里只按门店名匹配 export function filterMerchantStores(merchants, keyword) { const list = Array.isArray(merchants) ? merchants : [] const text = String(keyword || '').trim() if (!text) return list return list.filter(merchant => (merchant.text || '').includes(text)) } export default { pickFirstText, unwrapList, normalizeMerchant, normalizeMerchantChain, groupMerchantListByChain, flattenMerchantsFromChains, filterMerchantStores }