/** * 客户主数据 Mock Router * * 拦截 crmCustomer/* 和 crmCustomerAddress/* 的所有 API, * 提供完整的 CRUD + 地址管理 mock。 */ import { loadMockState, saveMockState } from '../storage' import { createSeedData } from './seed' const KEY = 'mdm-customer' function getState() { return loadMockState(KEY, createSeedData) } function save(state: any) { saveMockState(KEY, state) } const ok = (data: any) => ({ code: 800, msg: '操作成功', data }) const fail = (msg: string) => ({ code: 500, msg, data: null }) function paginate(rows: any[], pageIndex: number, pageRows: number) { const start = (pageIndex - 1) * pageRows return { total: rows.length, pageIndex, pageRows, rows: rows.slice(start, start + pageRows) } } const genId = (state: any) => ++state.idSeq const genAddressId = (state: any) => ++state.addressIdSeq const genCode = (state: any) => { const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '') return `CUS-${dateStr}-${String(genId(state)).padStart(4, '0')}` } const now = () => { const d = new Date() const offset = 8 * 60 const local = new Date(d.getTime() + offset * 60000) return local.toISOString().replace('T', ' ').slice(0, 19) } /** * 获取所有客户(含集团客户)的扁平列表 */ function allCustomers(state: any): any[] { return [...state.groupCustomers, ...state.customers] } /** * 递归检查 parentId 是否会形成循环引用 */ function wouldCircular(state: any, customerId: number, parentId: number): boolean { let current = parentId const visited = new Set() while (current) { if (current === customerId) return true if (visited.has(current)) return true visited.add(current) const parent = allCustomers(state).find((c: any) => c.customerId === current) current = parent?.parentId || 0 } return false } export function mdmCustomerRouter(url: string, method: string, data?: any): any | null { const u = url.replace(/^\//, '') const state = getState() // ============================================================ // 分页查询客户 // ============================================================ if (u.includes('crmCustomer/find-pagination') && method === 'post') { // 请求体结构:{ pageIndex, pageRows, params: { customerCode, customerName, tradeType, customerStatus } } const pageIndex = data?.pageIndex || 1 const pageRows = data?.pageRows || 10 const filterParams = data?.params || {} let list = allCustomers(state) // 按条件筛选 if (filterParams.customerCode) { list = list.filter((c: any) => c.customerCode?.includes(filterParams.customerCode)) } if (filterParams.customerName) { list = list.filter((c: any) => c.customerName?.includes(filterParams.customerName)) } if (filterParams.tradeType) { list = list.filter((c: any) => c.tradeType === filterParams.tradeType) } // customerStatus: null/undefined → 默认"正常"(保护 M06/M07 下游调用方) // customerStatus: "" → 展示全部状态(M03 自身列表页) // customerStatus: 具体值 → 精确筛选 if (filterParams.customerStatus === null || filterParams.customerStatus === undefined) { list = list.filter((c: any) => c.customerStatus === '正常') } else if (filterParams.customerStatus !== '') { list = list.filter((c: any) => c.customerStatus === filterParams.customerStatus) } // 默认按创建时间倒序 list.sort((a: any, b: any) => (b.creationDate || '').localeCompare(a.creationDate || '')) // 分页结果不含 addressList(减少传输量) const paged = paginate(list, pageIndex, pageRows) const flatRows = paged.rows.map(({ addressList, ...rest }: any) => rest) return ok({ ...paged, rows: flatRows }) } // ============================================================ // 按 ID 查询客户详情(含集团归属 + 地址列表) // ============================================================ if (u.includes('crmCustomer/get-id') && method === 'get') { const queryStr = u.split('?')[1] || '' const searchParams = new URLSearchParams(queryStr) const id = Number(searchParams.get('id')) if (!id) return fail('参数缺失:id') const customer = allCustomers(state).find((c: any) => c.customerId === id) if (!customer) return fail('客户不存在') // 构建详情(含地址列表) const detail = { ...customer } // 如果 addressList 已内嵌就使用,否则从 standaloneAddresses 查询 if (!detail.addressList || detail.addressList.length === 0) { detail.addressList = state.standaloneAddresses .filter((a: any) => a.customerId === id) .sort((a: any, b: any) => (b.isDefault - a.isDefault) || (b.creationDate || '').localeCompare(a.creationDate || '')) } else { detail.addressList = [...detail.addressList].sort( (a: any, b: any) => (b.isDefault - a.isDefault) || (b.creationDate || '').localeCompare(a.creationDate || '') ) } return ok(detail) } // ============================================================ // 按编码精确查询客户 // ============================================================ if (u.includes('crmCustomer/get-by-code') && method === 'get') { const queryStr = u.split('?')[1] || '' const searchParams = new URLSearchParams(queryStr) const customerCode = searchParams.get('customerCode') if (!customerCode) return fail('参数缺失:customerCode') const customer = allCustomers(state).find((c: any) => c.customerCode === customerCode) if (!customer) return fail('客户不存在') // 返回含 parent 信息但不含 addressList 的精简数据 const { addressList, ...rest } = customer return ok(rest) } // ============================================================ // 新增客户 // ============================================================ if (u.includes('crmCustomer/save') && method === 'post') { const dto = data?.params || data || {} // 必填校验 if (!dto.customerName) return fail('客户全称不能为空') if (!dto.tradeType) return fail('贸易类型不能为空') // 全称唯一性校验 const exists = allCustomers(state).find( (c: any) => c.customerName === dto.customerName && c.deleteFlag === 0, ) if (exists) return fail('客户全称已存在') // parentId 校验 if (dto.parentId) { const parent = allCustomers(state).find((c: any) => c.customerId === dto.parentId) if (!parent || parent.customerStatus !== '正常') { return fail('所属集团客户不存在或已停用') } } const newId = genId(state) const newCustomer: any = { customerId: newId, customerCode: genCode(state), customerName: dto.customerName, shortName: dto.shortName || null, tradeType: dto.tradeType, creditCode: dto.creditCode || null, parentId: dto.parentId || null, parentCustomerCode: null, parentCustomerName: null, responsibleUserId: dto.responsibleUserId || 1001, responsibleUserName: null, customerStatus: '正常', creationDate: now(), createdBy: 1001, createdByName: '演示用户', lastUpdateDate: now(), lastUpdatedBy: 1001, deleteFlag: 0, versionNum: 1, addressList: [], } // 关联集团客户信息 if (dto.parentId) { const parent = allCustomers(state).find((c: any) => c.customerId === dto.parentId) if (parent) { newCustomer.parentCustomerCode = parent.customerCode newCustomer.parentCustomerName = parent.customerName } } // 处理地址列表 if (dto.addressList && Array.isArray(dto.addressList) && dto.addressList.length > 0) { let hasDefault = false newCustomer.addressList = dto.addressList.map((addr: any, idx: number) => { const isDefault = addr.isDefault === 1 && !hasDefault ? 1 : 0 if (isDefault) hasDefault = true return { addressId: genAddressId(state), customerId: newId, addressDetail: addr.addressDetail || '', isDefault: addr.isDefault === 1 ? (idx === 0 ? 1 : 0) : 0, creationDate: now(), } }) // 如果没有标记默认,第一个为默认 if (!hasDefault && newCustomer.addressList.length > 0) { newCustomer.addressList[0].isDefault = 1 } } state.customers.push(newCustomer) save(state) return ok(newCustomer) } // ============================================================ // 更新客户 // ============================================================ if (u.includes('crmCustomer/update') && method === 'post') { const dto = data?.params || data || {} if (!dto.customerId) return fail('客户ID不能为空') // 在 customers 或 groupCustomers 中查找 let idx = state.customers.findIndex((c: any) => c.customerId === dto.customerId) let isGroup = false if (idx === -1) { idx = state.groupCustomers.findIndex((c: any) => c.customerId === dto.customerId) isGroup = true } if (idx === -1) return fail('客户不存在') const list = isGroup ? state.groupCustomers : state.customers const entity = list[idx] // 编码不可修改 if (dto.customerCode && dto.customerCode !== entity.customerCode) { return fail('客户编码不可修改') } // 全称唯一性校验(排除自身) if (dto.customerName && dto.customerName !== entity.customerName) { const dup = allCustomers(state).find( (c: any) => c.customerName === dto.customerName && c.customerId !== dto.customerId, ) if (dup) return fail('客户全称已存在') } // parentId 校验:不能指向自身、不能形成循环引用 if (dto.parentId !== undefined) { if (dto.parentId === dto.customerId) { return fail('所属集团客户不能是当前客户自身') } if (dto.parentId && wouldCircular(state, dto.customerId, dto.parentId)) { return fail('所属集团客户形成循环引用') } if (dto.parentId) { const parent = allCustomers(state).find((c: any) => c.customerId === dto.parentId) if (!parent || parent.customerStatus !== '正常') { return fail('所属集团客户不存在或已停用') } entity.parentCustomerCode = parent.customerCode entity.parentCustomerName = parent.customerName } else { entity.parentCustomerCode = null entity.parentCustomerName = null } } // 状态变更幂等(仅纯状态变更操作时校验:只有 customerId + customerStatus + versionNum) // 普通编辑(同时修改其他字段)时若状态未变,仅跳过状态更新,不报错 if (dto.customerStatus && dto.customerStatus === entity.customerStatus) { const isPureStatusChange = !dto.customerName && !dto.shortName && !dto.tradeType && dto.creditCode === undefined && dto.parentId === undefined && dto.responsibleUserId === undefined && dto.addressList === undefined if (isPureStatusChange) { return fail(entity.customerStatus === '正常' ? '该客户已处于正常状态' : '该客户已处于停用状态') } // 普通编辑:状态未变化,从可更新字段中移除 customerStatus delete dto.customerStatus } // 合并更新 const updatableFields = [ 'customerName', 'shortName', 'tradeType', 'creditCode', 'parentId', 'responsibleUserId', 'customerStatus', ] for (const field of updatableFields) { if (dto[field] !== undefined) { entity[field] = dto[field] } } entity.lastUpdateDate = now() entity.lastUpdatedBy = 1001 // 处理地址列表(如果传入) if (dto.addressList !== undefined) { // 先删旧地址,再写入新地址 if (!isGroup) { const addrList = Array.isArray(dto.addressList) ? dto.addressList : [] // 标记旧地址为删除(软删除) // 这里直接替换地址列表 let hasDefault = false entity.addressList = addrList.map((addr: any) => { const isDef = addr.isDefault === 1 && !hasDefault ? 1 : 0 if (isDef) hasDefault = true return { addressId: addr.addressId || genAddressId(state), customerId: dto.customerId, addressDetail: addr.addressDetail || '', isDefault: isDef, creationDate: addr.creationDate || now(), } }) if (!hasDefault && entity.addressList.length > 0) { entity.addressList[0].isDefault = 1 } } } entity.versionNum = (entity.versionNum || 0) + 1 save(state) return ok(entity) } // ============================================================ // 地址列表查询 // ============================================================ if (u.includes('crmCustomerAddress/list-by-customer-id') && method === 'get') { const queryStr = u.split('?')[1] || '' const searchParams = new URLSearchParams(queryStr) const customerId = Number(searchParams.get('customerId')) if (!customerId) return fail('参数缺失:customerId') // 先在 customers 中查找内嵌地址 const customer = state.customers.find((c: any) => c.customerId === customerId) if (customer && customer.addressList) { const list = [...customer.addressList].sort( (a: any, b: any) => (b.isDefault - a.isDefault) || (b.creationDate || '').localeCompare(a.creationDate || '') ) return ok(list) } // 查 standaloneAddresses const list = state.standaloneAddresses .filter((a: any) => a.customerId === customerId) .sort((a: any, b: any) => (b.isDefault - a.isDefault) || (b.creationDate || '').localeCompare(a.creationDate || '')) return ok(list) } // ============================================================ // 新增/更新地址 // ============================================================ if (u.includes('crmCustomerAddress/save') && method === 'post') { const dto = data?.params || data || {} if (!dto.customerId) return fail('客户ID不能为空') if (!dto.addressDetail) return fail('地址详情不能为空') const customer = state.customers.find((c: any) => c.customerId === dto.customerId) if (!customer) return fail('客户不存在或为集团客户') // 初始化 addressList if (!customer.addressList) customer.addressList = [] // 判断是新增还是编辑 if (dto.addressId) { // 编辑模式 const addrIdx = customer.addressList.findIndex((a: any) => a.addressId === dto.addressId) if (addrIdx === -1) return fail('地址不存在') // 如果设为默认,取消其他默认 if (dto.isDefault === 1) { customer.addressList.forEach((a: any) => (a.isDefault = 0)) } customer.addressList[addrIdx] = { ...customer.addressList[addrIdx], addressDetail: dto.addressDetail, isDefault: dto.isDefault !== undefined ? dto.isDefault : customer.addressList[addrIdx].isDefault, } save(state) return ok(customer.addressList[addrIdx]) } else { // 新增模式 if (dto.isDefault === 1) { // 取消原有默认地址 customer.addressList.forEach((a: any) => (a.isDefault = 0)) } const newAddr = { addressId: genAddressId(state), customerId: dto.customerId, addressDetail: dto.addressDetail, isDefault: dto.isDefault === 1 ? 1 : 0, creationDate: now(), } // 如果是第一个地址,自动设为默认 if (customer.addressList.length === 0) { newAddr.isDefault = 1 } customer.addressList.push(newAddr) save(state) return ok(newAddr) } } // ============================================================ // 删除地址 // ============================================================ if (u.includes('crmCustomerAddress') && method === 'delete') { // URL 格式:.../crmCustomerAddress/{id} 或 .../crmCustomerAddress/{id}?... const pathPart = u.split('crmCustomerAddress')[1] || '' const idStr = pathPart.replace(/^\//, '').split('?')[0] const addressId = Number(idStr) if (!addressId) return fail('参数缺失:地址ID') // 在所有 customer 的 addressList 中查找 let found = false for (const customer of state.customers) { if (!customer.addressList) continue const idx = customer.addressList.findIndex((a: any) => a.addressId === addressId) if (idx !== -1) { const addr = customer.addressList[idx] if (addr.isDefault === 1) { return fail('默认地址不可删除') } customer.addressList.splice(idx, 1) found = true break } } // 也查 standaloneAddresses if (!found) { const idx = state.standaloneAddresses.findIndex((a: any) => a.addressId === addressId) if (idx !== -1) { const addr = state.standaloneAddresses[idx] if (addr.isDefault === 1) { return fail('默认地址不可删除') } state.standaloneAddresses.splice(idx, 1) found = true } } if (!found) return fail('地址不存在') save(state) return ok(null) } return null }