index.js 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456
  1. import Mock from 'mockjs'
  2. // =============================================
  3. // 登录
  4. // =============================================
  5. Mock.mock(/\/api\/login/, 'post', (options) => {
  6. const { username, password } = JSON.parse(options.body || '{}')
  7. if (!username || !password) {
  8. return { code: 400, data: null, msg: '账号或密码不能为空' }
  9. }
  10. if (username === 'admin' && password === '123456') {
  11. return {
  12. code: 200,
  13. data: { token: 'mock-token-' + Date.now(), username },
  14. msg: '登录成功'
  15. }
  16. }
  17. return { code: 401, data: null, msg: '账号或密码错误' }
  18. })
  19. // =============================================
  20. // 页面管理 - 权益介绍页配置
  21. // =============================================
  22. const homeConfigData = {
  23. premier: {
  24. tabName: '卓越理财',
  25. buttons: { unlocked: '进入权益专区', locked: '待解锁' },
  26. header: {
  27. imageUrl: 'https://images.unsplash.com/photo-1518893494013-481c1d8ed3fd?w=800',
  28. mainTitle: '卓越理财',
  29. subTitle: '为您和您的家人提供尊崇专属礼遇和权益'
  30. },
  31. privileges: [
  32. { imageUrl: '', title: '国际教育', desc: '享专属国际化学校优惠及背景提升项目' },
  33. { imageUrl: '', title: '酒店礼遇', desc: '高端酒店及商场会籍匹配' }
  34. ]
  35. },
  36. elite: {
  37. tabName: '卓越理财·尊尚',
  38. buttons: { unlocked: '进入权益专区', locked: '待解锁' },
  39. header: {
  40. imageUrl: 'https://images.unsplash.com/photo-1518893494013-481c1d8ed3fd?w=800',
  41. mainTitle: '卓越理财·尊尚',
  42. subTitle: '为您和您的家人提供尊崇专属礼遇和权益'
  43. },
  44. privileges: [
  45. { imageUrl: '', title: '国际教育', desc: '享专属国际化学校优惠及背景提升项目' },
  46. { imageUrl: '', title: '酒店礼遇', desc: '高端酒店及商场会籍匹配' }
  47. ]
  48. },
  49. global: {
  50. tabName: '环球私人银行',
  51. buttons: { unlocked: '进入权益专区', locked: '待解锁' },
  52. header: {
  53. imageUrl: 'https://images.unsplash.com/photo-1518893494013-481c1d8ed3fd?w=800',
  54. mainTitle: '环球私人银行',
  55. subTitle: '为您和您的家人提供尊崇专属礼遇和权益'
  56. },
  57. privileges: [
  58. { imageUrl: '', title: '国际教育', desc: '享专属国际化学校优惠及背景提升项目' },
  59. { imageUrl: '', title: '酒店礼遇', desc: '高端酒店及商场会籍匹配' }
  60. ]
  61. }
  62. }
  63. let homeStatus = 'published'
  64. Mock.mock(/\/api\/page-config\/home/, 'get', () => {
  65. return { code: 200, data: { config: homeConfigData, status: homeStatus }, msg: 'ok' }
  66. })
  67. Mock.mock(/\/api\/page-config\/home/, 'put', (options) => {
  68. const body = JSON.parse(options.body)
  69. Object.assign(homeConfigData, body.config || {})
  70. homeStatus = body.status || homeStatus
  71. return { code: 200, data: null, msg: '保存成功' }
  72. })
  73. // =============================================
  74. // 页面管理 - 权益首页配置
  75. // =============================================
  76. const zoneConfigData = {
  77. identityModule: {
  78. advance: {
  79. bgImage: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800',
  80. identityText: '汇丰运筹理财',
  81. showQualifyStatus: false,
  82. showAvgBalance: true,
  83. avgBalanceRule: '月内日均余额 = 当月内每日账户余额之和 / 当月天数。'
  84. },
  85. premier: {
  86. bgImage: 'https://images.unsplash.com/photo-1583847268964-b28dc8f51f92?w=800',
  87. identityText: '汇丰卓越理财',
  88. showQualifyStatus: true,
  89. showAvgBalance: true,
  90. avgBalanceRule: '计算规则:截止到昨日的账户月内日均余额。'
  91. },
  92. premierElite: {
  93. bgImage: 'https://images.unsplash.com/photo-1557682250-33bd709cbe85?w=800',
  94. identityText: '汇丰卓越理财·尊尚',
  95. showQualifyStatus: true,
  96. showAvgBalance: true,
  97. avgBalanceRule: '计算规则:截止到昨日的账户月内日均余额。支持查看合格状态及复制编号。'
  98. },
  99. privateBank: {
  100. bgImage: 'https://images.unsplash.com/photo-1492684223066-81342ee5ff30?w=800',
  101. identityText: '汇丰环球私人银行',
  102. showPbLevel: true
  103. }
  104. },
  105. adCarousel: [{
  106. imageUrl: 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800',
  107. linkH5: '', linkApp: '', wechatAppId: '', wechatPath: '',
  108. title: '汇丰卓越理财新户达标礼',
  109. subtitle: '报名参加汇丰活动,体验更多精彩礼遇',
  110. tags: ['t1', 't2']
  111. }],
  112. exchangeConfig: {
  113. items: [
  114. { imageUrl: 'https://images.unsplash.com/photo-1436491865332-7a61a109cc05?w=200', title: '航空里程', link: '/flight' },
  115. { imageUrl: 'https://images.unsplash.com/photo-1518893494013-481c1d8ed3fd?w=200', title: '贵宾厅', link: '/lounge' }
  116. ]
  117. },
  118. ruleModal: {
  119. title: '积分规则',
  120. content: '<p>本积分使用规则为汇丰银行(中国)有限公司...</p>'
  121. }
  122. }
  123. let zoneStatus = 'published'
  124. const availableTags = Array.from({ length: 8 }, (_, i) => ({ id: `t${i + 1}`, name: `人群标签${i + 1}` }))
  125. Mock.mock(/\/api\/page-config\/zone/, 'get', () => {
  126. return { code: 200, data: { config: zoneConfigData, status: zoneStatus, availableTags }, msg: 'ok' }
  127. })
  128. Mock.mock(/\/api\/page-config\/zone/, 'put', (options) => {
  129. const body = JSON.parse(options.body)
  130. Object.assign(zoneConfigData, body.config || {})
  131. zoneStatus = body.status || zoneStatus
  132. return { code: 200, data: null, msg: '保存成功' }
  133. })
  134. // =============================================
  135. // 页面管理 - 组装页
  136. // =============================================
  137. let assemblyIdCounter = 3000
  138. const assemblyList = [
  139. {
  140. id: 'A7538', name: '双十一迎新专属活动页', status: 'published',
  141. createTime: '2025-10-01 10:00:00', updateTime: '2025-10-15 14:30:00',
  142. blocks: [
  143. { type: 'image', id: 'b_init1', data: { imageUrl: 'https://images.unsplash.com/photo-1557683316-973673baf926?w=800' } },
  144. { type: 'privilege', id: 'b_init2', data: { title: '专属权益', items: [{ id: '1', title: '机场贵宾厅服务', desc: '全年免费使用6次指定机场贵宾厅' }, { id: '2', title: '境内礼宾车', desc: '免费2次接送' }] } },
  145. { type: 'richtext', id: 'b_init3', data: { content: '<p>欢迎参与双十一专属福利活动...</p>' } },
  146. { type: 'buttonGroup', id: 'b_init4', data: { buttons: [{ text: '立即参与', link: '/join', style: 'primary', actionType: 'link', dialog: { title: '', desc: '', confirmText: '确认', confirmLink: '' } }] } }
  147. ]
  148. },
  149. {
  150. id: 'A2091', name: '私行高净值沙龙引导页', status: 'draft',
  151. createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00',
  152. blocks: [
  153. { type: 'image', id: 'b_init5', data: { imageUrl: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800' } },
  154. { type: 'privilege', id: 'b_init6', data: { title: '尊享体验', items: [{ id: '1', title: '高尔夫权益', desc: 'VIP高尔夫俱乐部免会籍预约' }] } }
  155. ]
  156. }
  157. ]
  158. Mock.mock(/\/api\/assembly\/list/, 'get', (options) => {
  159. const url = new URL(options.url, 'http://localhost')
  160. const id = url.searchParams.get('id') || ''
  161. const name = url.searchParams.get('name') || ''
  162. let filtered = assemblyList
  163. if (id) filtered = filtered.filter(a => a.id.includes(id))
  164. if (name) filtered = filtered.filter(a => a.name.includes(name))
  165. return { code: 200, data: filtered, msg: 'ok' }
  166. })
  167. Mock.mock(/\/api\/assembly\/detail/, 'get', (options) => {
  168. const url = new URL(options.url, 'http://localhost')
  169. const id = url.searchParams.get('id')
  170. const item = assemblyList.find(a => a.id === id)
  171. return item
  172. ? { code: 200, data: item, msg: 'ok' }
  173. : { code: 404, data: null, msg: '未找到' }
  174. })
  175. Mock.mock(/\/api\/assembly\/save/, 'post', (options) => {
  176. const body = JSON.parse(options.body)
  177. const now = new Date().toLocaleString('zh-CN', { hour12: false })
  178. if (body.id) {
  179. const idx = assemblyList.findIndex(a => a.id === body.id)
  180. if (idx >= 0) {
  181. assemblyList[idx] = { ...assemblyList[idx], ...body, updateTime: now }
  182. }
  183. } else {
  184. body.id = 'A' + (++assemblyIdCounter)
  185. body.createTime = now
  186. body.updateTime = now
  187. assemblyList.unshift(body)
  188. }
  189. return { code: 200, data: { id: body.id }, msg: '保存成功' }
  190. })
  191. Mock.mock(/\/api\/assembly\/delete/, 'post', (options) => {
  192. const body = JSON.parse(options.body)
  193. const idx = assemblyList.findIndex(a => a.id === body.id)
  194. if (idx >= 0) assemblyList.splice(idx, 1)
  195. return { code: 200, data: null, msg: '删除成功' }
  196. })
  197. // =============================================
  198. // 渠道管理 - Channel
  199. // =============================================
  200. const channelDict = {
  201. 'Human-assisted': {
  202. '汇丰中国客户服务号': ['汇丰中国客户服务号'],
  203. 'MGM': ['MGM - Mortgage', 'MGM - Premier'],
  204. 'CCSS': ['CCSS SMS'],
  205. 'Referral': ['GPB Referral', 'HK referral', 'Non WPB staff Referral', 'Pinnacle Referral', 'RBB Refferal'],
  206. 'New staff': ['New staff'],
  207. 'IBC': ['IBC referral staff code'],
  208. 'Group Acct': ['HTC', 'NIVIDA']
  209. },
  210. 'Unassisted': {
  211. '汇丰中国APP': ['Mobile X'],
  212. '汇丰中国官网': ['PWS site'],
  213. '汇丰中国小程序': ['汇丰中国小程序'],
  214. '汇丰中国订阅号': ['汇丰中国订阅号', '汇丰中国订阅号推文', '推文尾部入口'],
  215. 'Chatbot': ['Chatbot'],
  216. 'SMS': ['SMS'],
  217. 'Paid Media': ['汇丰中国订阅号推文', 'others'],
  218. 'Others': ['others', 'River APP']
  219. }
  220. }
  221. let channelIdCounter = 9
  222. const channelRows = [
  223. { id: 'CH0001', type: 'Human-assisted', category: '汇丰中国客户服务号', name: '汇丰中国客户服务号', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  224. { id: 'CH0002', type: 'Human-assisted', category: 'MGM', name: 'MGM - Mortgage', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  225. { id: 'CH0003', type: 'Human-assisted', category: 'MGM', name: 'MGM - Premier', subName: '双十一特惠', owner: '张经理', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-21 14:30:00' },
  226. { id: 'CH0004', type: 'Human-assisted', category: 'CCSS', name: 'CCSS SMS', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  227. { id: 'CH0005', type: 'Human-assisted', category: 'Referral', name: 'GPB Referral', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  228. { id: 'CH0006', type: 'Unassisted', category: '汇丰中国APP', name: 'Mobile X', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  229. { id: 'CH0007', type: 'Unassisted', category: '汇丰中国官网', name: 'PWS site', subName: '私行沙龙引导', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  230. { id: 'CH0008', type: 'Unassisted', category: '汇丰中国订阅号', name: '汇丰中国订阅号', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' },
  231. { id: 'CH0009', type: 'Unassisted', category: '汇丰中国订阅号', name: '推文尾部入口', subName: '', owner: '', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' }
  232. ]
  233. Mock.mock(/\/api\/channel\/dict/, 'get', () => {
  234. return { code: 200, data: channelDict, msg: 'ok' }
  235. })
  236. Mock.mock(/\/api\/channel\/dict\/save/, 'post', (options) => {
  237. const body = JSON.parse(options.body)
  238. const { target, type, category, oldValue, newValue, mode } = body
  239. if (target === 'type') {
  240. if (mode === 'add' && channelDict[newValue]) return { code: 400, data: null, msg: 'Channel 类型已存在,不能重复新增' }
  241. if (mode === 'edit' && oldValue !== newValue && channelDict[newValue]) return { code: 400, data: null, msg: 'Channel 类型已存在,不能修改为重复值' }
  242. if (mode === 'add') { channelDict[newValue] = {} }
  243. else { channelDict[newValue] = channelDict[oldValue]; delete channelDict[oldValue]; channelRows.forEach(r => { if (r.type === oldValue) r.type = newValue }) }
  244. } else if (target === 'category') {
  245. if (mode === 'add' && channelDict[type][newValue]) return { code: 400, data: null, msg: 'Channel 类别已存在,不能重复新增' }
  246. if (mode === 'edit' && oldValue !== newValue && channelDict[type][newValue]) return { code: 400, data: null, msg: 'Channel 类别已存在,不能修改为重复值' }
  247. if (mode === 'add') { channelDict[type][newValue] = [] }
  248. else { channelDict[type][newValue] = channelDict[type][oldValue]; delete channelDict[type][oldValue]; channelRows.forEach(r => { if (r.type === type && r.category === oldValue) r.category = newValue }) }
  249. } else if (target === 'name') {
  250. const list = channelDict[type][category]
  251. if (mode === 'add' && list.includes(newValue)) return { code: 400, data: null, msg: 'Channel 名称已存在,不能重复新增' }
  252. if (mode === 'edit' && oldValue !== newValue && list.includes(newValue)) return { code: 400, data: null, msg: 'Channel 名称已存在,不能修改为重复值' }
  253. if (mode === 'add') { list.push(newValue) }
  254. else { const idx = list.indexOf(oldValue); if (idx >= 0) list[idx] = newValue; channelRows.forEach(r => { if (r.type === type && r.category === category && r.name === oldValue) r.name = newValue }) }
  255. }
  256. return { code: 200, data: channelDict, msg: '保存成功' }
  257. })
  258. Mock.mock(/\/api\/channel\/list/, 'get', (options) => {
  259. const url = new URL(options.url, 'http://localhost')
  260. const sId = (url.searchParams.get('id') || '').toLowerCase()
  261. const sType = url.searchParams.get('type') || ''
  262. const sCategory = (url.searchParams.get('category') || '').toLowerCase()
  263. const sName = (url.searchParams.get('name') || '').toLowerCase()
  264. const sSubName = (url.searchParams.get('subName') || '').toLowerCase()
  265. const sOwner = (url.searchParams.get('owner') || '').toLowerCase()
  266. let filtered = channelRows
  267. if (sId) filtered = filtered.filter(c => c.id.toLowerCase().includes(sId))
  268. if (sType) filtered = filtered.filter(c => c.type === sType)
  269. if (sCategory) filtered = filtered.filter(c => c.category.toLowerCase().includes(sCategory))
  270. if (sName) filtered = filtered.filter(c => c.name.toLowerCase().includes(sName))
  271. if (sSubName) filtered = filtered.filter(c => c.subName.toLowerCase().includes(sSubName))
  272. if (sOwner) filtered = filtered.filter(c => c.owner.toLowerCase().includes(sOwner))
  273. return { code: 200, data: filtered, msg: 'ok' }
  274. })
  275. Mock.mock(/\/api\/channel\/save/, 'post', (options) => {
  276. const body = JSON.parse(options.body)
  277. const now = new Date().toLocaleString('zh-CN', { hour12: false })
  278. if (body.id) {
  279. const idx = channelRows.findIndex(c => c.id === body.id)
  280. if (idx >= 0) channelRows[idx] = { ...channelRows[idx], ...body, updateTime: now }
  281. } else {
  282. body.id = 'CH' + String(++channelIdCounter).padStart(4, '0')
  283. body.createTime = now
  284. body.updateTime = now
  285. channelRows.unshift(body)
  286. }
  287. return { code: 200, data: { id: body.id }, msg: '保存成功' }
  288. })
  289. Mock.mock(/\/api\/channel\/delete/, 'post', (options) => {
  290. const body = JSON.parse(options.body)
  291. const idx = channelRows.findIndex(c => c.id === body.id)
  292. if (idx >= 0) channelRows.splice(idx, 1)
  293. return { code: 200, data: null, msg: '删除成功' }
  294. })
  295. // =============================================
  296. // 渠道管理 - Campaign
  297. // =============================================
  298. let campaignIdCounter = 9002
  299. const campaignRows = [
  300. { id: 'CP9001', name: 'Q3 大客户回馈', owner: 'Market Team', staff: '王专员', createTime: '2025-10-01 10:00:00', updateTime: '2025-10-15 14:30:00' },
  301. { id: 'CP9002', name: '新春开户活动', owner: 'Retail Team', staff: '李专员', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' }
  302. ]
  303. Mock.mock(/\/api\/campaign\/list/, 'get', (options) => {
  304. const url = new URL(options.url, 'http://localhost')
  305. const sId = (url.searchParams.get('id') || '').toLowerCase()
  306. const sName = (url.searchParams.get('name') || '').toLowerCase()
  307. const sOwner = (url.searchParams.get('owner') || '').toLowerCase()
  308. const sStaff = (url.searchParams.get('staff') || '').toLowerCase()
  309. let filtered = campaignRows
  310. if (sId) filtered = filtered.filter(c => c.id.toLowerCase().includes(sId))
  311. if (sName) filtered = filtered.filter(c => c.name.toLowerCase().includes(sName))
  312. if (sOwner) filtered = filtered.filter(c => c.owner.toLowerCase().includes(sOwner))
  313. if (sStaff) filtered = filtered.filter(c => c.staff.toLowerCase().includes(sStaff))
  314. return { code: 200, data: filtered, msg: 'ok' }
  315. })
  316. Mock.mock(/\/api\/campaign\/save/, 'post', (options) => {
  317. const body = JSON.parse(options.body)
  318. const now = new Date().toLocaleString('zh-CN', { hour12: false })
  319. if (body.id) {
  320. const idx = campaignRows.findIndex(c => c.id === body.id)
  321. if (idx >= 0) campaignRows[idx] = { ...campaignRows[idx], ...body, updateTime: now }
  322. } else {
  323. body.id = 'CP' + (++campaignIdCounter)
  324. body.createTime = now
  325. body.updateTime = now
  326. campaignRows.unshift(body)
  327. }
  328. return { code: 200, data: { id: body.id }, msg: '保存成功' }
  329. })
  330. Mock.mock(/\/api\/campaign\/delete/, 'post', (options) => {
  331. const body = JSON.parse(options.body)
  332. const idx = campaignRows.findIndex(c => c.id === body.id)
  333. if (idx >= 0) campaignRows.splice(idx, 1)
  334. return { code: 200, data: null, msg: '删除成功' }
  335. })
  336. // =============================================
  337. // 借记卡权益 + 审核中心
  338. // =============================================
  339. // 商品状态:未上架 / 已上架
  340. // 审核状态:草稿 / 上架待审核 / 下架待审核 / 审核通过
  341. // 合法组合(5 种):
  342. // 未上架+草稿 = 草稿
  343. // 未上架+上架待审核 = 新增待审核
  344. // 未上架+审核通过 = 已下架
  345. // 已上架+审核通过 = 已上架
  346. // 已上架+下架待审核 = 已上架·下架待审核
  347. const equityList = [
  348. {
  349. productId: 'Q100001',
  350. equityName: '权益A',
  351. source: '银行自有',
  352. isPlatformExchange: '是',
  353. jumpMethod: '-',
  354. jumpLink: '-',
  355. exchangeMethod: '权益平台',
  356. productType: '券码',
  357. skuNo: '',
  358. merchantName: '商户A',
  359. creator: '李运营',
  360. createTime: '2026-04-10 09:32:15',
  361. reviewer: '王五',
  362. reviewTime: '2026-04-15 10:30:00',
  363. shelfStatus: '已上架',
  364. auditStatus: '审核通过',
  365. showOnHome: '显示',
  366. showOnAssembly: '不显示',
  367. assemblyPages: []
  368. },
  369. {
  370. productId: 'Q100002',
  371. equityName: '权益B',
  372. source: '第三方',
  373. isPlatformExchange: '否',
  374. jumpMethod: '没有',
  375. jumpLink: '太古地产',
  376. exchangeMethod: '第三方(列表跳转)',
  377. productType: '',
  378. skuNo: '',
  379. merchantName: '商户B',
  380. creator: '王运营',
  381. createTime: '2026-04-12 14:08:42',
  382. reviewer: '',
  383. reviewTime: '',
  384. shelfStatus: '未上架',
  385. auditStatus: '上架待审核',
  386. showOnHome: '显示',
  387. showOnAssembly: '不显示',
  388. assemblyPages: []
  389. },
  390. {
  391. productId: 'Q100003',
  392. equityName: '权益C',
  393. source: '第三方',
  394. isPlatformExchange: '否',
  395. jumpMethod: '有',
  396. jumpLink: '龙腾',
  397. exchangeMethod: '第三方(详情页跳转)',
  398. productType: '',
  399. skuNo: '',
  400. merchantName: '商户C',
  401. creator: '李运营',
  402. createTime: '2026-04-08 11:20:05',
  403. reviewer: '王五',
  404. reviewTime: '2026-04-14 16:45:00',
  405. shelfStatus: '已上架',
  406. auditStatus: '审核通过',
  407. showOnHome: '显示',
  408. showOnAssembly: '不显示',
  409. assemblyPages: []
  410. },
  411. {
  412. productId: 'Q100004',
  413. equityName: '权益D',
  414. source: '第三方',
  415. isPlatformExchange: '否',
  416. jumpMethod: '有',
  417. jumpLink: 'h5链接',
  418. exchangeMethod: '第三方(详情页跳转)',
  419. productType: '',
  420. skuNo: '',
  421. merchantName: '商户D',
  422. creator: '陈运营',
  423. createTime: '2026-04-05 15:50:18',
  424. reviewer: '赵六',
  425. reviewTime: '2026-04-13 09:15:00',
  426. shelfStatus: '已上架',
  427. auditStatus: '下架待审核',
  428. showOnHome: '显示',
  429. showOnAssembly: '不显示',
  430. assemblyPages: []
  431. },
  432. {
  433. productId: 'Q100005',
  434. equityName: '权益E',
  435. source: '银行自有',
  436. isPlatformExchange: '是',
  437. jumpMethod: '-',
  438. jumpLink: '-',
  439. exchangeMethod: '权益平台',
  440. productType: '券码',
  441. skuNo: '',
  442. merchantName: '商户E',
  443. creator: '王运营',
  444. createTime: '2026-03-28 09:00:11',
  445. reviewer: '钱七',
  446. reviewTime: '2026-04-12 11:20:00',
  447. shelfStatus: '未上架',
  448. auditStatus: '审核通过',
  449. showOnHome: '显示',
  450. showOnAssembly: '不显示',
  451. assemblyPages: []
  452. },
  453. {
  454. productId: 'Q100006',
  455. equityName: '权益F(草稿)',
  456. source: '银行自有',
  457. isPlatformExchange: '是',
  458. jumpMethod: '-',
  459. jumpLink: '-',
  460. exchangeMethod: '权益平台',
  461. productType: '券码',
  462. skuNo: '',
  463. merchantName: '商户F',
  464. creator: '李运营',
  465. createTime: '2026-05-16 10:00:00',
  466. reviewer: '',
  467. reviewTime: '',
  468. shelfStatus: '未上架',
  469. auditStatus: '草稿',
  470. showOnHome: '不显示',
  471. showOnAssembly: '不显示',
  472. assemblyPages: []
  473. },
  474. {
  475. productId: 'Q100007',
  476. equityName: '微信立减金 5元(满10减5)',
  477. source: '荣数商品',
  478. isPlatformExchange: '是',
  479. jumpMethod: '-',
  480. jumpLink: '-',
  481. exchangeMethod: '权益平台',
  482. productType: '微信立减金',
  483. skuNo: 'V0000003900000801',
  484. rongshuProduct: '微信立减金 5元(满10减5)',
  485. merchantName: '微信支付',
  486. stockCount: 2000,
  487. creator: '张三',
  488. createTime: '2026-05-20 10:00:00',
  489. reviewer: '李四',
  490. reviewTime: '2026-05-22 15:30:00',
  491. shelfStatus: '已上架',
  492. auditStatus: '审核通过',
  493. showOnHome: '显示',
  494. showOnAssembly: '不显示',
  495. assemblyPages: []
  496. },
  497. {
  498. productId: 'Q100008',
  499. equityName: '微信立减金 50元(满200减50)',
  500. source: '荣数商品',
  501. isPlatformExchange: '是',
  502. jumpMethod: '-',
  503. jumpLink: '-',
  504. exchangeMethod: '权益平台',
  505. productType: '微信立减金',
  506. skuNo: 'V0000003900000807',
  507. rongshuProduct: '微信立减金 50元(满200减50)',
  508. merchantName: '微信支付',
  509. stockCount: 400,
  510. creator: '张三',
  511. createTime: '2026-05-18 09:30:00',
  512. reviewer: '',
  513. reviewTime: '',
  514. shelfStatus: '未上架',
  515. auditStatus: '上架待审核',
  516. showOnHome: '显示',
  517. showOnAssembly: '显示',
  518. assemblyPages: ['组装页1']
  519. }
  520. ]
  521. let equityIdCounter = 100008
  522. const auditList = [
  523. {
  524. id: 'AR202605150001',
  525. auditType: 'new',
  526. bizModule: 'equity_product',
  527. bizModuleName: '借记卡权益',
  528. bizId: 'Q100002',
  529. bizName: '权益B',
  530. snapshot: {
  531. equityName: '权益B',
  532. source: '第三方',
  533. isPlatformExchange: '否',
  534. jumpMethod: '没有',
  535. jumpLink: '太古地产',
  536. productType: '',
  537. skuNo: '',
  538. merchantName: '商户B',
  539. subTitle: '外部列表页副标题'
  540. },
  541. submitReason: '新建第三方权益,链接已对齐',
  542. submitterId: 'u001',
  543. submitterName: '王运营',
  544. submitTime: '2026-05-15 10:00:00',
  545. auditorId: null,
  546. auditorName: '',
  547. auditTime: '',
  548. auditStatus: 'pending',
  549. auditComment: ''
  550. },
  551. {
  552. id: 'AR202605170001',
  553. auditType: 'offshelf',
  554. bizModule: 'equity_product',
  555. bizModuleName: '借记卡权益',
  556. bizId: 'Q100004',
  557. bizName: '权益D',
  558. snapshot: {
  559. equityName: '权益D',
  560. source: '第三方',
  561. isPlatformExchange: '否',
  562. jumpMethod: '有',
  563. jumpLink: 'h5链接',
  564. productType: '',
  565. skuNo: '',
  566. merchantName: '商户D',
  567. subTitle: '外部自定义链接副标题'
  568. },
  569. submitReason: '业务合作到期,申请下架',
  570. submitterId: 'u003',
  571. submitterName: '陈运营',
  572. submitTime: '2026-05-17 09:00:00',
  573. auditorId: null,
  574. auditorName: '',
  575. auditTime: '',
  576. auditStatus: 'pending',
  577. auditComment: ''
  578. },
  579. {
  580. id: 'AR202605120001',
  581. auditType: 'new',
  582. bizModule: 'equity_product',
  583. bizModuleName: '借记卡权益',
  584. bizId: 'Q100001',
  585. bizName: '权益A',
  586. snapshot: {
  587. equityName: '权益A',
  588. source: '银行自有',
  589. isPlatformExchange: '是',
  590. jumpMethod: '-',
  591. jumpLink: '-',
  592. productType: '券码',
  593. skuNo: '',
  594. merchantName: '商户A',
  595. subTitle: '副标题示例'
  596. },
  597. submitReason: '',
  598. submitterId: 'u001',
  599. submitterName: '李运营',
  600. submitTime: '2026-04-10 09:32:15',
  601. auditorId: 'u099',
  602. auditorName: '王五',
  603. auditTime: '2026-04-15 10:30:00',
  604. auditStatus: 'approved',
  605. auditComment: '内容合规,准予上架'
  606. }
  607. ]
  608. let auditIdCounter = 4
  609. function nowStr() {
  610. return new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-')
  611. }
  612. function nextAuditId() {
  613. auditIdCounter += 1
  614. const d = new Date()
  615. const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
  616. return 'AR' + ymd + String(auditIdCounter).padStart(4, '0')
  617. }
  618. function snapshotOf(equity) {
  619. return {
  620. equityName: equity.equityName,
  621. source: equity.source,
  622. isPlatformExchange: equity.isPlatformExchange || (equity.source === '第三方' ? '否' : '是'),
  623. productType: equity.isPlatformExchange === '否' ? '' : (equity.productType || '券码'),
  624. jumpMethod: equity.jumpMethod,
  625. jumpLink: equity.jumpLink,
  626. skuNo: equity.skuNo,
  627. merchantName: equity.merchantName,
  628. subTitle: equity.subTitle || ''
  629. }
  630. }
  631. // 借记卡权益 - 列表
  632. Mock.mock(/\/api\/equity\/list/, 'get', (options) => {
  633. const url = new URL(options.url, 'http://localhost')
  634. const equityName = url.searchParams.get('equityName') || ''
  635. const productId = url.searchParams.get('productId') || ''
  636. const shelfStatus = url.searchParams.get('shelfStatus') || ''
  637. const auditStatus = url.searchParams.get('auditStatus') || ''
  638. let filtered = equityList
  639. if (equityName) filtered = filtered.filter(e => e.equityName.includes(equityName))
  640. if (productId) filtered = filtered.filter(e => e.productId.includes(productId))
  641. if (shelfStatus) filtered = filtered.filter(e => e.shelfStatus === shelfStatus)
  642. if (auditStatus) filtered = filtered.filter(e => e.auditStatus === auditStatus)
  643. return { code: 200, data: filtered, msg: 'ok' }
  644. })
  645. // 借记卡权益 - 详情
  646. Mock.mock(/\/api\/equity\/detail/, 'get', (options) => {
  647. const url = new URL(options.url, 'http://localhost')
  648. const id = url.searchParams.get('id')
  649. const item = equityList.find(e => e.productId === id)
  650. return item
  651. ? { code: 200, data: item, msg: 'ok' }
  652. : { code: 404, data: null, msg: '权益不存在' }
  653. })
  654. // 保存草稿(新建或修改草稿)
  655. Mock.mock(/\/api\/equity\/save-draft/, 'post', (options) => {
  656. const body = JSON.parse(options.body)
  657. if (body.productId) {
  658. // 修改草稿
  659. const idx = equityList.findIndex(e => e.productId === body.productId)
  660. if (idx < 0) return { code: 404, data: null, msg: '权益不存在' }
  661. const cur = equityList[idx]
  662. if (cur.auditStatus !== '草稿') {
  663. return { code: 400, data: null, msg: '仅草稿状态可保存为草稿' }
  664. }
  665. equityList[idx] = { ...cur, ...body, shelfStatus: '未上架', auditStatus: '草稿' }
  666. return { code: 200, data: { productId: cur.productId }, msg: '草稿已保存' }
  667. }
  668. // 新建草稿
  669. equityIdCounter += 1
  670. const newId = 'Q' + String(equityIdCounter)
  671. const newRow = {
  672. productId: newId,
  673. equityName: body.equityName || '未命名权益',
  674. source: body.source || '银行自有',
  675. isPlatformExchange: body.isPlatformExchange || '是',
  676. jumpMethod: body.jumpMethod || '-',
  677. jumpLink: body.jumpLink || '-',
  678. exchangeMethod: body.exchangeMethod || '权益平台',
  679. productType: body.isPlatformExchange === '否' ? '' : (body.productType || '券码'),
  680. skuNo: body.skuNo || '',
  681. rongshuProduct: body.rongshuProduct || '',
  682. stockCount: body.stockCount ?? null,
  683. merchantName: body.merchantName || '',
  684. creator: body.creator || '李运营',
  685. createTime: nowStr(),
  686. reviewer: '',
  687. reviewTime: '',
  688. shelfStatus: '未上架',
  689. auditStatus: '草稿',
  690. showOnHome: body.showOnHome || '不显示',
  691. showOnAssembly: body.showOnAssembly || '不显示',
  692. assemblyPages: body.assemblyPages || [],
  693. subTitle: body.subTitle || ''
  694. }
  695. equityList.unshift(newRow)
  696. return { code: 200, data: { productId: newId }, msg: '草稿已保存' }
  697. })
  698. // 提交审核(新增上架审核 / 已上架编辑提审 / 已下架重新提审)
  699. Mock.mock(/\/api\/equity\/submit/, 'post', (options) => {
  700. const body = JSON.parse(options.body)
  701. let cur
  702. let idx = -1
  703. if (body.productId) {
  704. idx = equityList.findIndex(e => e.productId === body.productId)
  705. if (idx < 0) return { code: 404, data: null, msg: '权益不存在' }
  706. cur = equityList[idx]
  707. }
  708. const submitReason = body.submitReason || ''
  709. const submitter = body.submitterName || '李运营'
  710. // 新建 + 直接提审
  711. if (!cur) {
  712. equityIdCounter += 1
  713. const newId = 'Q' + String(equityIdCounter)
  714. const newRow = {
  715. productId: newId,
  716. equityName: body.equityName || '未命名权益',
  717. source: body.source || '银行自有',
  718. isPlatformExchange: body.isPlatformExchange || '是',
  719. jumpMethod: body.jumpMethod || '-',
  720. jumpLink: body.jumpLink || '-',
  721. exchangeMethod: body.exchangeMethod || '权益平台',
  722. productType: body.isPlatformExchange === '否' ? '' : (body.productType || '券码'),
  723. skuNo: body.skuNo || '',
  724. rongshuProduct: body.rongshuProduct || '',
  725. stockCount: body.stockCount ?? null,
  726. merchantName: body.merchantName || '',
  727. creator: submitter,
  728. createTime: nowStr(),
  729. reviewer: '',
  730. reviewTime: '',
  731. shelfStatus: '未上架',
  732. auditStatus: '上架待审核',
  733. showOnHome: body.showOnHome || '不显示',
  734. showOnAssembly: body.showOnAssembly || '不显示',
  735. assemblyPages: body.assemblyPages || [],
  736. subTitle: body.subTitle || ''
  737. }
  738. equityList.unshift(newRow)
  739. auditList.unshift({
  740. id: nextAuditId(),
  741. auditType: 'new',
  742. bizModule: 'equity_product',
  743. bizModuleName: '借记卡权益',
  744. bizId: newId,
  745. bizName: newRow.equityName,
  746. snapshot: snapshotOf(newRow),
  747. submitReason,
  748. submitterId: 'u001',
  749. submitterName: submitter,
  750. submitTime: nowStr(),
  751. auditorId: null,
  752. auditorName: '',
  753. auditTime: '',
  754. auditStatus: 'pending',
  755. auditComment: ''
  756. })
  757. return { code: 200, data: { productId: newId }, msg: '已提交审核' }
  758. }
  759. // 草稿 → 上架待审核(new)
  760. if (cur.shelfStatus === '未上架' && cur.auditStatus === '草稿') {
  761. const merged = { ...cur, ...body }
  762. equityList[idx] = { ...merged, shelfStatus: '未上架', auditStatus: '上架待审核' }
  763. auditList.unshift({
  764. id: nextAuditId(),
  765. auditType: 'new',
  766. bizModule: 'equity_product',
  767. bizModuleName: '借记卡权益',
  768. bizId: cur.productId,
  769. bizName: equityList[idx].equityName,
  770. snapshot: snapshotOf(equityList[idx]),
  771. submitReason,
  772. submitterId: 'u001',
  773. submitterName: submitter,
  774. submitTime: nowStr(),
  775. auditorId: null,
  776. auditorName: '',
  777. auditTime: '',
  778. auditStatus: 'pending',
  779. auditComment: ''
  780. })
  781. return { code: 200, data: { productId: cur.productId }, msg: '已提交审核' }
  782. }
  783. // 已下架 → 重新上架(也是 new)
  784. if (cur.shelfStatus === '未上架' && cur.auditStatus === '审核通过') {
  785. equityList[idx] = { ...cur, ...body, shelfStatus: '未上架', auditStatus: '上架待审核' }
  786. auditList.unshift({
  787. id: nextAuditId(),
  788. auditType: 'new',
  789. bizModule: 'equity_product',
  790. bizModuleName: '借记卡权益',
  791. bizId: cur.productId,
  792. bizName: equityList[idx].equityName,
  793. snapshot: snapshotOf(equityList[idx]),
  794. submitReason,
  795. submitterId: 'u001',
  796. submitterName: submitter,
  797. submitTime: nowStr(),
  798. auditorId: null,
  799. auditorName: '',
  800. auditTime: '',
  801. auditStatus: 'pending',
  802. auditComment: ''
  803. })
  804. return { code: 200, data: { productId: cur.productId }, msg: '已提交审核' }
  805. }
  806. // 已上架商品禁止直接编辑(需先走下架审核)
  807. return { code: 400, data: null, msg: '当前状态不可提交审核' }
  808. })
  809. // 申请下架
  810. Mock.mock(/\/api\/equity\/request-offshelf/, 'post', (options) => {
  811. const body = JSON.parse(options.body)
  812. const idx = equityList.findIndex(e => e.productId === body.productId)
  813. if (idx < 0) return { code: 404, data: null, msg: '权益不存在' }
  814. const cur = equityList[idx]
  815. if (cur.shelfStatus !== '已上架' || cur.auditStatus !== '审核通过') {
  816. return { code: 400, data: null, msg: '仅已上架且无审核中的权益可申请下架' }
  817. }
  818. equityList[idx] = { ...cur, auditStatus: '下架待审核' }
  819. auditList.unshift({
  820. id: nextAuditId(),
  821. auditType: 'offshelf',
  822. bizModule: 'equity_product',
  823. bizModuleName: '借记卡权益',
  824. bizId: cur.productId,
  825. bizName: cur.equityName,
  826. snapshot: snapshotOf(cur),
  827. submitReason: '',
  828. submitterId: 'u001',
  829. submitterName: body.submitterName || '李运营',
  830. submitTime: nowStr(),
  831. auditorId: null,
  832. auditorName: '',
  833. auditTime: '',
  834. auditStatus: 'pending',
  835. auditComment: ''
  836. })
  837. return { code: 200, data: null, msg: '已提交下架审核' }
  838. })
  839. // 删除权益(仅草稿/已下架)
  840. Mock.mock(/\/api\/equity\/delete/, 'post', (options) => {
  841. const body = JSON.parse(options.body)
  842. const idx = equityList.findIndex(e => e.productId === body.productId)
  843. if (idx < 0) return { code: 404, data: null, msg: '权益不存在' }
  844. const cur = equityList[idx]
  845. const canDelete = (cur.shelfStatus === '未上架' && (cur.auditStatus === '草稿' || cur.auditStatus === '审核通过'))
  846. if (!canDelete) return { code: 400, data: null, msg: '当前状态不可删除' }
  847. equityList.splice(idx, 1)
  848. return { code: 200, data: null, msg: '删除成功' }
  849. })
  850. // 审核中心 - 列表
  851. Mock.mock(/\/api\/audit\/list/, 'get', (options) => {
  852. const url = new URL(options.url, 'http://localhost')
  853. const auditStatus = url.searchParams.get('auditStatus') || ''
  854. const bizName = url.searchParams.get('bizName') || ''
  855. const bizId = url.searchParams.get('bizId') || ''
  856. let filtered = auditList
  857. if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
  858. if (bizName) filtered = filtered.filter(a => a.bizName.includes(bizName))
  859. if (bizId) filtered = filtered.filter(a => String(a.bizId).includes(bizId))
  860. return { code: 200, data: filtered, msg: 'ok' }
  861. })
  862. // 审核中心 - 详情
  863. Mock.mock(/\/api\/audit\/detail/, 'get', (options) => {
  864. const url = new URL(options.url, 'http://localhost')
  865. const id = url.searchParams.get('id')
  866. const item = auditList.find(a => a.id === id)
  867. return item
  868. ? { code: 200, data: item, msg: 'ok' }
  869. : { code: 404, data: null, msg: '审核单不存在' }
  870. })
  871. // 审核中心 - 通过
  872. Mock.mock(/\/api\/audit\/approve/, 'post', (options) => {
  873. const body = JSON.parse(options.body)
  874. const idx = auditList.findIndex(a => a.id === body.id)
  875. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  876. const audit = auditList[idx]
  877. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  878. const eqIdx = equityList.findIndex(e => e.productId === audit.bizId)
  879. if (eqIdx < 0) return { code: 404, data: null, msg: '关联权益不存在' }
  880. const eq = equityList[eqIdx]
  881. const now = nowStr()
  882. const auditor = body.auditorName || '王五'
  883. if (audit.auditType === 'new') {
  884. equityList[eqIdx] = { ...eq, shelfStatus: '已上架', auditStatus: '审核通过', reviewer: auditor, reviewTime: now }
  885. } else if (audit.auditType === 'offshelf') {
  886. equityList[eqIdx] = { ...eq, shelfStatus: '未上架', auditStatus: '审核通过', reviewer: auditor, reviewTime: now }
  887. }
  888. auditList[idx] = { ...audit, auditStatus: 'approved', auditorId: 'u099', auditorName: auditor, auditTime: now, auditComment: body.comment || '' }
  889. return { code: 200, data: null, msg: '审核通过' }
  890. })
  891. // 审核中心 - 驳回
  892. Mock.mock(/\/api\/audit\/reject/, 'post', (options) => {
  893. const body = JSON.parse(options.body)
  894. const idx = auditList.findIndex(a => a.id === body.id)
  895. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  896. const audit = auditList[idx]
  897. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  898. const eqIdx = equityList.findIndex(e => e.productId === audit.bizId)
  899. if (eqIdx < 0) return { code: 404, data: null, msg: '关联权益不存在' }
  900. const eq = equityList[eqIdx]
  901. const now = nowStr()
  902. const auditor = body.auditorName || '王五'
  903. // 驳回后权益主表回退到提审前的状态
  904. if (audit.auditType === 'new') {
  905. equityList[eqIdx] = { ...eq, shelfStatus: '未上架', auditStatus: '草稿' }
  906. } else if (audit.auditType === 'offshelf') {
  907. equityList[eqIdx] = { ...eq, shelfStatus: '已上架', auditStatus: '审核通过' }
  908. }
  909. auditList[idx] = { ...audit, auditStatus: 'rejected', auditorId: 'u099', auditorName: auditor, auditTime: now, auditComment: body.comment || '' }
  910. return { code: 200, data: null, msg: '已驳回' }
  911. })
  912. // =============================================
  913. // 发放计划
  914. // =============================================
  915. const equityGrantCampaignList = [
  916. {
  917. id: 'P000002',
  918. name: '夏日畅享权益月',
  919. startTime: '2026-05-01 00:00:00',
  920. endTime: '2026-08-31 23:59:59',
  921. crowdList: [],
  922. isAutoGrant: '是',
  923. grantMode: 'claim',
  924. equityList: ['Q100001', 'Q100004', 'Q100005'],
  925. grantCycle: 'total',
  926. grantQuantity: 1,
  927. validity: { type: 'relative', fixedRange: [], relativeDays: 30 },
  928. auditStatus: '审核通过',
  929. createTime: '2026-04-20 14:10:00',
  930. updateTime: '2026-04-20 14:10:00'
  931. },
  932. {
  933. id: 'P000001',
  934. name: '春季会员尊享礼遇',
  935. startTime: '2026-04-01 00:00:00',
  936. endTime: '2026-06-30 23:59:59',
  937. crowdList: ['00000120251224'],
  938. isAutoGrant: '否',
  939. grantMode: 'direct',
  940. equityList: ['Q100001'],
  941. grantCycle: 'monthly',
  942. grantQuantity: 2,
  943. validity: { type: 'fixed', fixedRange: ['2026-04-01', '2026-06-30'], relativeDays: 30 },
  944. auditStatus: '新增待审核',
  945. createTime: '2026-03-25 10:20:30',
  946. updateTime: '2026-03-25 10:20:30'
  947. }
  948. ]
  949. let equityGrantCampaignCounter = equityGrantCampaignList.length
  950. function nextGrantCampaignId() {
  951. equityGrantCampaignCounter += 1
  952. return 'P' + String(equityGrantCampaignCounter).padStart(6, '0')
  953. }
  954. Mock.mock(/\/api\/equity-grant-campaign\/list/, 'get', (options) => {
  955. const url = new URL(options.url, 'http://localhost')
  956. const name = url.searchParams.get('name') || ''
  957. const startDate = url.searchParams.get('startDate') || ''
  958. const endDate = url.searchParams.get('endDate') || ''
  959. let filtered = equityGrantCampaignList
  960. if (name) filtered = filtered.filter(c => c.name.includes(name))
  961. if (startDate) filtered = filtered.filter(c => c.endTime.slice(0, 10) >= startDate)
  962. if (endDate) filtered = filtered.filter(c => c.startTime.slice(0, 10) <= endDate)
  963. return { code: 200, data: filtered, msg: 'ok' }
  964. })
  965. Mock.mock(/\/api\/equity-grant-campaign\/save/, 'post', (options) => {
  966. const body = JSON.parse(options.body)
  967. const now = nowStr()
  968. const defaultValidity = { type: 'fixed', fixedRange: [], relativeDays: 30 }
  969. if (body.id) {
  970. const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
  971. if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
  972. equityGrantCampaignList[idx] = {
  973. ...equityGrantCampaignList[idx],
  974. name: body.name,
  975. startTime: body.startTime,
  976. endTime: body.endTime,
  977. crowdList: body.crowdList || [],
  978. isAutoGrant: body.isAutoGrant || '是',
  979. equityList: body.equityList || [],
  980. grantCycle: body.grantCycle || 'total',
  981. grantQuantity: body.grantQuantity || 1,
  982. validity: body.validity || defaultValidity,
  983. updateTime: now
  984. }
  985. return { code: 200, data: { id: body.id }, msg: '保存成功' }
  986. }
  987. const newId = nextGrantCampaignId()
  988. equityGrantCampaignList.unshift({
  989. id: newId,
  990. name: body.name,
  991. startTime: body.startTime,
  992. endTime: body.endTime,
  993. crowdList: body.crowdList || [],
  994. isAutoGrant: body.isAutoGrant || '是',
  995. equityList: body.equityList || [],
  996. grantCycle: body.grantCycle || 'total',
  997. grantQuantity: body.grantQuantity || 1,
  998. validity: body.validity || defaultValidity,
  999. auditStatus: '新增待审核',
  1000. createTime: now,
  1001. updateTime: now
  1002. })
  1003. return { code: 200, data: { id: newId }, msg: '新建成功' }
  1004. })
  1005. Mock.mock(/\/api\/equity-grant-campaign\/delete/, 'post', (options) => {
  1006. const body = JSON.parse(options.body)
  1007. const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
  1008. if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
  1009. equityGrantCampaignList.splice(idx, 1)
  1010. return { code: 200, data: null, msg: '删除成功' }
  1011. })
  1012. // =============================================
  1013. // 发放任务
  1014. // =============================================
  1015. const grantTaskList = [
  1016. {
  1017. taskId: 'T20260501001',
  1018. planName: '夏日畅享权益月',
  1019. planId: 'P000002',
  1020. taskType: '自动',
  1021. taskCycle: '2026-05-01 ~ 2026-05-31',
  1022. status: '已完成',
  1023. createTime: '2026-05-01 00:00:00',
  1024. executeTime: '2026-05-01 00:05:12',
  1025. executor: '系统'
  1026. },
  1027. {
  1028. taskId: 'T20260401001',
  1029. planName: '春季会员尊享礼遇',
  1030. planId: 'P000001',
  1031. taskType: '手动',
  1032. taskCycle: '2026-04-01 ~ 2026-04-30',
  1033. status: '待执行',
  1034. createTime: '2026-04-01 00:00:00',
  1035. executeTime: '',
  1036. executor: ''
  1037. },
  1038. {
  1039. taskId: 'T20260401002',
  1040. planName: '春季会员尊享礼遇',
  1041. planId: 'P000001',
  1042. taskType: '手动',
  1043. taskCycle: '2026-05-01 ~ 2026-05-31',
  1044. status: '执行中',
  1045. createTime: '2026-05-01 00:00:00',
  1046. executeTime: '2026-05-01 10:30:00',
  1047. executor: '张三'
  1048. }
  1049. ]
  1050. Mock.mock(/\/api\/grant-task\/list/, 'get', (options) => {
  1051. const url = new URL(options.url, 'http://localhost')
  1052. const planName = url.searchParams.get('planName') || ''
  1053. const status = url.searchParams.get('status') || ''
  1054. let filtered = grantTaskList
  1055. if (planName) filtered = filtered.filter(t => t.planName.includes(planName))
  1056. if (status) filtered = filtered.filter(t => t.status === status)
  1057. return { code: 200, data: filtered, msg: 'ok' }
  1058. })
  1059. Mock.mock(/\/api\/grant-task\/detail/, 'get', (options) => {
  1060. const url = new URL(options.url, 'http://localhost')
  1061. const taskId = url.searchParams.get('taskId') || ''
  1062. const task = grantTaskList.find(t => t.taskId === taskId)
  1063. if (!task) return { code: 404, data: null, msg: '任务不存在' }
  1064. const plan = equityGrantCampaignList.find(c => c.id === task.planId)
  1065. const cycleMap = {
  1066. 'total': '整个周期',
  1067. 'monthly': '每月',
  1068. 'quarterly': '每季度',
  1069. 'yearly': '每年'
  1070. }
  1071. const planData = {
  1072. ...task,
  1073. grantMode: plan ? plan.grantMode : 'claim',
  1074. isAutoGrant: plan ? plan.isAutoGrant : '是',
  1075. planStartTime: plan ? plan.startTime : '',
  1076. planEndTime: plan ? plan.endTime : '',
  1077. grantQuantityLabel: plan ? `${cycleMap[plan.grantCycle] || plan.grantCycle} ${plan.grantQuantity} 个` : '',
  1078. validityLabel: plan ? (plan.validity.type === 'fixed' ? (plan.validity.fixedRange.join(' 至 ')) : `自发放起 ${plan.validity.relativeDays} 天内有效`) : '',
  1079. crowdLabel: plan ? (plan.crowdList.length ? plan.crowdList.map(id => {
  1080. const crowdNames = { '00000120251224': '层层礼圈选客群1' }
  1081. return crowdNames[id] ? `${crowdNames[id]} (${id})` : id
  1082. }).join('、') : '-') : '-',
  1083. equityLabel: plan ? (plan.equityList.length ? plan.equityList.map(id => {
  1084. const e = equityList.find(p => p.productId === id)
  1085. return e ? `${e.equityName} (${id})` : id
  1086. }).join('、') : '-') : '-',
  1087. smsLabel: plan ? '发放成功通用模板' : '-'
  1088. }
  1089. return { code: 200, data: planData, msg: 'ok' }
  1090. })
  1091. Mock.mock(/\/api\/grant-task\/execute/, 'post', (options) => {
  1092. const body = JSON.parse(options.body)
  1093. const idx = grantTaskList.findIndex(t => t.taskId === body.taskId)
  1094. if (idx < 0) return { code: 404, data: null, msg: '任务不存在' }
  1095. if (grantTaskList[idx].status !== '待执行') return { code: 400, data: null, msg: '该任务不在待执行状态' }
  1096. grantTaskList[idx].status = '执行中'
  1097. grantTaskList[idx].executeTime = nowStr()
  1098. grantTaskList[idx].executor = '运营人员'
  1099. return { code: 200, data: null, msg: '执行成功' }
  1100. })
  1101. Mock.mock(/\/api\/equity-grant-campaign\/detail/, 'get', (options) => {
  1102. const url = new URL(options.url, 'http://localhost')
  1103. const id = url.searchParams.get('id')
  1104. const item = equityGrantCampaignList.find(c => c.id === id)
  1105. return item
  1106. ? { code: 200, data: item, msg: 'ok' }
  1107. : { code: 404, data: null, msg: '发放计划不存在' }
  1108. })
  1109. Mock.mock(/\/api\/equity-grant-campaign\/end/, 'post', (options) => {
  1110. const body = JSON.parse(options.body)
  1111. const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
  1112. if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
  1113. const cur = equityGrantCampaignList[idx]
  1114. if (cur.auditStatus !== '审核通过') return { code: 400, data: null, msg: '仅审核通过状态可提交结束审核' }
  1115. // 创建审核单并更新发放计划状态为结束待审核
  1116. const auditId = nextCampaignAuditId()
  1117. const now = nowStr()
  1118. equityGrantCampaignList[idx] = { ...cur, auditStatus: '结束待审核', updateTime: now }
  1119. campaignAuditList.unshift({
  1120. id: auditId,
  1121. auditType: 'end',
  1122. bizModuleName: '发放计划',
  1123. bizId: cur.id,
  1124. bizName: cur.name,
  1125. snapshot: {
  1126. name: cur.name,
  1127. startTime: cur.startTime,
  1128. endTime: cur.endTime,
  1129. grantMode: cur.grantMode,
  1130. crowdList: cur.crowdList || [],
  1131. equityList: cur.equityList || [],
  1132. grantCycle: cur.grantCycle,
  1133. grantQuantity: cur.grantQuantity,
  1134. validity: cur.validity,
  1135. smsTemplateId: cur.smsTemplateId || '',
  1136. smsTemplate: cur.smsTemplate || ''
  1137. },
  1138. submitterName: body.submitterName || '运营',
  1139. submitTime: now,
  1140. auditorName: '',
  1141. auditTime: '',
  1142. auditStatus: 'pending',
  1143. auditComment: ''
  1144. })
  1145. return { code: 200, data: { auditId }, msg: '已提交结束审核' }
  1146. })
  1147. // =============================================
  1148. // 规则组审核
  1149. // =============================================
  1150. let ruleGroupAuditIdCounter = 200
  1151. function nextRuleGroupAuditId() {
  1152. ruleGroupAuditIdCounter += 1
  1153. const d = new Date()
  1154. const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
  1155. return 'RGA' + ymd + String(ruleGroupAuditIdCounter).padStart(4, '0')
  1156. }
  1157. const ruleGroupAuditList = [
  1158. {
  1159. id: 'RGA202605250001',
  1160. auditType: 'new',
  1161. bizModuleName: '规则组',
  1162. bizId: 'RG20260420003',
  1163. bizName: '借记卡-跨境候选客群',
  1164. submitterName: '赵市场',
  1165. submitTime: '2026-05-25 10:00:00',
  1166. auditorName: '',
  1167. auditTime: '',
  1168. auditStatus: 'pending',
  1169. auditComment: ''
  1170. },
  1171. {
  1172. id: 'RGA202605230001',
  1173. auditType: 'new',
  1174. bizModuleName: '规则组',
  1175. bizId: 'RG20260401001',
  1176. bizName: '层层礼-借记卡新客圈选',
  1177. submitterName: '张运营',
  1178. submitTime: '2026-05-23 14:30:00',
  1179. auditorName: '王审核',
  1180. auditTime: '2026-05-24 09:00:00',
  1181. auditStatus: 'approved',
  1182. auditComment: '规则配置合理,准予上线'
  1183. }
  1184. ]
  1185. Mock.mock(/\/api\/rule-group-audit\/list/, 'get', (options) => {
  1186. const url = new URL(options.url, 'http://localhost')
  1187. const auditStatus = url.searchParams.get('auditStatus') || ''
  1188. const bizName = url.searchParams.get('bizName') || ''
  1189. const bizId = url.searchParams.get('bizId') || ''
  1190. let filtered = ruleGroupAuditList
  1191. if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
  1192. if (bizName) filtered = filtered.filter(a => a.bizName.includes(bizName))
  1193. if (bizId) filtered = filtered.filter(a => String(a.bizId).includes(bizId))
  1194. return { code: 200, data: filtered, msg: 'ok' }
  1195. })
  1196. Mock.mock(/\/api\/rule-group-audit\/detail/, 'get', (options) => {
  1197. const url = new URL(options.url, 'http://localhost')
  1198. const id = url.searchParams.get('id')
  1199. const item = ruleGroupAuditList.find(a => a.id === id)
  1200. return item
  1201. ? { code: 200, data: item, msg: 'ok' }
  1202. : { code: 404, data: null, msg: '审核单不存在' }
  1203. })
  1204. Mock.mock(/\/api\/rule-group-audit\/approve/, 'post', (options) => {
  1205. const body = JSON.parse(options.body)
  1206. const idx = ruleGroupAuditList.findIndex(a => a.id === body.id)
  1207. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  1208. const audit = ruleGroupAuditList[idx]
  1209. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  1210. const now = nowStr()
  1211. ruleGroupAuditList[idx] = { ...audit, auditStatus: 'approved', auditorName: body.auditorName || '王审核', auditTime: now, auditComment: body.comment || '' }
  1212. return { code: 200, data: null, msg: '审核通过' }
  1213. })
  1214. Mock.mock(/\/api\/rule-group-audit\/reject/, 'post', (options) => {
  1215. const body = JSON.parse(options.body)
  1216. const idx = ruleGroupAuditList.findIndex(a => a.id === body.id)
  1217. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  1218. const audit = ruleGroupAuditList[idx]
  1219. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  1220. const now = nowStr()
  1221. ruleGroupAuditList[idx] = { ...audit, auditStatus: 'rejected', auditorName: body.auditorName || '王审核', auditTime: now, auditComment: body.comment || '' }
  1222. return { code: 200, data: null, msg: '已驳回' }
  1223. })
  1224. // =============================================
  1225. // 发放计划审核
  1226. // =============================================
  1227. let campaignAuditIdCounter = 100
  1228. function nextCampaignAuditId() {
  1229. campaignAuditIdCounter += 1
  1230. const d = new Date()
  1231. const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
  1232. return 'CA' + ymd + String(campaignAuditIdCounter).padStart(4, '0')
  1233. }
  1234. const campaignAuditList = [
  1235. {
  1236. id: 'CA202605200001',
  1237. auditType: 'new',
  1238. bizModuleName: '发放计划',
  1239. bizId: 'P000003',
  1240. bizName: '秋季养生权益季',
  1241. snapshot: {
  1242. name: '秋季养生权益季',
  1243. startTime: '2026-09-01 00:00:00',
  1244. endTime: '2026-11-30 23:59:59',
  1245. grantMode: 'claim',
  1246. crowdList: ['00000120251224'],
  1247. equityList: ['Q100001', 'Q100004'],
  1248. grantCycle: 'monthly',
  1249. grantQuantity: 1,
  1250. validity: { type: 'relative', fixedRange: [], relativeDays: 30 },
  1251. smsTemplateId: 'GRANT_SMS_001',
  1252. smsTemplate: '【银行】尊敬的${customerName},您已获得${productName}权益,发放时间:${grantTime},请于${expireDate}前使用。'
  1253. },
  1254. submitterName: '王运营',
  1255. submitTime: '2026-05-20 09:30:00',
  1256. auditorName: '',
  1257. auditTime: '',
  1258. auditStatus: 'pending',
  1259. auditComment: ''
  1260. },
  1261. {
  1262. id: 'CA202605220001',
  1263. auditType: 'new',
  1264. bizModuleName: '发放计划',
  1265. bizId: 'P000004',
  1266. bizName: '冬季暖心权益发放',
  1267. snapshot: {
  1268. name: '冬季暖心权益发放',
  1269. startTime: '2026-12-01 00:00:00',
  1270. endTime: '2027-02-28 23:59:59',
  1271. grantMode: 'direct',
  1272. crowdList: ['00000120251224'],
  1273. equityList: ['Q100001'],
  1274. grantCycle: 'total',
  1275. grantQuantity: 3,
  1276. validity: { type: 'fixed', fixedRange: ['2026-12-01', '2027-02-28'], relativeDays: 30 },
  1277. smsTemplateId: 'GRANT_SMS_002',
  1278. smsTemplate: '【银行】尊敬的${customerName},您已获得${productName}权益,有效期至${expireDate},请登录APP查看详情。'
  1279. },
  1280. submitterName: '陈运营',
  1281. submitTime: '2026-05-22 10:15:00',
  1282. auditorName: '',
  1283. auditTime: '',
  1284. auditStatus: 'pending',
  1285. auditComment: ''
  1286. },
  1287. {
  1288. id: 'CA202605180001',
  1289. auditType: 'new',
  1290. bizModuleName: '发放计划',
  1291. bizId: 'P000001',
  1292. bizName: '春季会员尊享礼遇',
  1293. snapshot: {
  1294. name: '春季会员尊享礼遇',
  1295. startTime: '2026-04-01 00:00:00',
  1296. endTime: '2026-06-30 23:59:59',
  1297. grantMode: 'direct',
  1298. crowdList: ['00000120251224'],
  1299. equityList: ['Q100001'],
  1300. grantCycle: 'monthly',
  1301. grantQuantity: 2,
  1302. validity: { type: 'fixed', fixedRange: ['2026-04-01', '2026-06-30'], relativeDays: 30 },
  1303. smsTemplateId: 'GRANT_SMS_003',
  1304. smsTemplate: '【银行】${customerName}您好,${productName}已为您发放成功,有效期至${expireDate}。'
  1305. },
  1306. submitterName: '李运营',
  1307. submitTime: '2026-05-18 14:20:00',
  1308. auditorName: '王五',
  1309. auditTime: '2026-05-19 09:00:00',
  1310. auditStatus: 'approved',
  1311. auditComment: '发放计划内容合规,准予通过'
  1312. }
  1313. ]
  1314. Mock.mock(/\/api\/campaign-audit\/list/, 'get', (options) => {
  1315. const url = new URL(options.url, 'http://localhost')
  1316. const auditStatus = url.searchParams.get('auditStatus') || ''
  1317. const bizId = url.searchParams.get('bizId') || ''
  1318. let filtered = campaignAuditList
  1319. if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
  1320. if (bizId) filtered = filtered.filter(a => a.bizId === bizId)
  1321. return { code: 200, data: filtered, msg: 'ok' }
  1322. })
  1323. Mock.mock(/\/api\/campaign-audit\/detail/, 'get', (options) => {
  1324. const url = new URL(options.url, 'http://localhost')
  1325. const id = url.searchParams.get('id')
  1326. const item = campaignAuditList.find(a => a.id === id)
  1327. return item
  1328. ? { code: 200, data: item, msg: 'ok' }
  1329. : { code: 404, data: null, msg: '审核单不存在' }
  1330. })
  1331. Mock.mock(/\/api\/campaign-audit\/approve/, 'post', (options) => {
  1332. const body = JSON.parse(options.body)
  1333. const idx = campaignAuditList.findIndex(a => a.id === body.id)
  1334. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  1335. const audit = campaignAuditList[idx]
  1336. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  1337. const now = nowStr()
  1338. campaignAuditList[idx] = { ...audit, auditStatus: 'approved', auditorName: body.auditorName || '王五', auditTime: now, auditComment: body.comment || '' }
  1339. // 同步更新发放计划主表
  1340. const campaignIdx = equityGrantCampaignList.findIndex(c => c.id === audit.bizId)
  1341. if (campaignIdx >= 0) {
  1342. if (audit.auditType === 'new') {
  1343. equityGrantCampaignList[campaignIdx].auditStatus = '审核通过'
  1344. } else if (audit.auditType === 'end') {
  1345. // 结束审核通过:实际结束发放计划
  1346. equityGrantCampaignList[campaignIdx] = {
  1347. ...equityGrantCampaignList[campaignIdx],
  1348. endTime: now,
  1349. auditStatus: '审核通过',
  1350. updateTime: now
  1351. }
  1352. }
  1353. }
  1354. return { code: 200, data: null, msg: '审核通过' }
  1355. })
  1356. Mock.mock(/\/api\/campaign-audit\/reject/, 'post', (options) => {
  1357. const body = JSON.parse(options.body)
  1358. const idx = campaignAuditList.findIndex(a => a.id === body.id)
  1359. if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
  1360. const audit = campaignAuditList[idx]
  1361. if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
  1362. const now = nowStr()
  1363. campaignAuditList[idx] = { ...audit, auditStatus: 'rejected', auditorName: body.auditorName || '王五', auditTime: now, auditComment: body.comment || '' }
  1364. // 驳回后回退发放计划审核状态为审核通过,允许重新编辑/结束
  1365. const campaignIdx = equityGrantCampaignList.findIndex(c => c.id === audit.bizId)
  1366. if (campaignIdx >= 0) equityGrantCampaignList[campaignIdx] = {
  1367. ...equityGrantCampaignList[campaignIdx],
  1368. auditStatus: '审核通过',
  1369. updateTime: now
  1370. }
  1371. return { code: 200, data: null, msg: '已驳回' }
  1372. })
  1373. export default Mock