Przeglądaj źródła

pc-v3: 发放计划复制、审核模块、文案调整与规则组审核

- 发放计划增加复制功能(路由、列表按钮、编辑页 isCopy 逻辑)
- 发放计划菜单改为二级子菜单「发放计划管理」,新增「发放计划审核」
- 新增 CampaignAuditList/CampaignAuditDetail 页面与 Mock 接口
- 人群管理新增「规则组审核」,详情页复用规则组详情 + 操作按钮
- 权益审核增加权益名称筛选项(模糊匹配)
- 多处文案调整:是否有详情页提示、权益首页、券码使用路径、不满足提示默认值、领取成功按钮仅银行自有显示、权益介绍非必填

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fade 1 miesiąc temu
rodzic
commit
67b10bfa49

+ 12 - 3
pc-v3/src/components/Layout.vue

@@ -45,9 +45,15 @@
                 权益审核
               </el-menu-item>
             </el-sub-menu>
-            <el-menu-item index="/customization/hsbc/equity-grant-campaign">
-              发放计划
-            </el-menu-item>
+            <el-sub-menu index="customization-hsbc-campaign">
+              <template #title>发放计划管理</template>
+              <el-menu-item index="/customization/hsbc/equity-grant-campaign">
+                发放计划
+              </el-menu-item>
+              <el-menu-item index="/customization/hsbc/campaign-audit">
+                发放计划审核
+              </el-menu-item>
+            </el-sub-menu>
             <el-sub-menu index="customization-hsbc-crowd">
               <template #title>人群管理</template>
               <el-menu-item index="/customization/hsbc/crowd-tasks">
@@ -56,6 +62,9 @@
               <el-menu-item index="/customization/hsbc/rule-groups">
                 规则组管理
               </el-menu-item>
+              <el-menu-item index="/customization/hsbc/rule-group-audit">
+                规则组审核
+              </el-menu-item>
             </el-sub-menu>
             <el-sub-menu index="customization-hsbc-sms">
               <template #title>短信管理</template>

+ 1 - 1
pc-v3/src/components/ShowPagesDialog.vue

@@ -6,7 +6,7 @@
     @open="onOpen"
   >
     <el-form :model="form" label-width="140px" label-position="left">
-      <el-form-item label="权益首页/列表页">
+      <el-form-item label="权益首页">
         <el-radio-group v-model="form.showOnHome">
           <el-radio label="显示">显示</el-radio>
           <el-radio label="不显示">不显示</el-radio>

+ 220 - 0
pc-v3/src/mock/index.js

@@ -837,9 +837,11 @@ Mock.mock(/\/api\/equity\/delete/, 'post', (options) => {
 Mock.mock(/\/api\/audit\/list/, 'get', (options) => {
   const url = new URL(options.url, 'http://localhost')
   const auditStatus = url.searchParams.get('auditStatus') || ''
+  const bizName = url.searchParams.get('bizName') || ''
   const bizId = url.searchParams.get('bizId') || ''
   let filtered = auditList
   if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
+  if (bizName) filtered = filtered.filter(a => a.bizName.includes(bizName))
   if (bizId) filtered = filtered.filter(a => String(a.bizId).includes(bizId))
   return { code: 200, data: filtered, msg: 'ok' }
 })
@@ -1023,4 +1025,222 @@ Mock.mock(/\/api\/equity-grant-campaign\/end/, 'post', (options) => {
   return { code: 200, data: null, msg: '已结束' }
 })
 
+// =============================================
+// 规则组审核
+// =============================================
+let ruleGroupAuditIdCounter = 200
+
+function nextRuleGroupAuditId() {
+  ruleGroupAuditIdCounter += 1
+  const d = new Date()
+  const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
+  return 'RGA' + ymd + String(ruleGroupAuditIdCounter).padStart(4, '0')
+}
+
+const ruleGroupAuditList = [
+  {
+    id: 'RGA202605250001',
+    auditType: 'new',
+    bizModuleName: '规则组',
+    bizId: 'RG20260420003',
+    bizName: '借记卡-跨境候选客群',
+    submitterName: '赵市场',
+    submitTime: '2026-05-25 10:00:00',
+    auditorName: '',
+    auditTime: '',
+    auditStatus: 'pending',
+    auditComment: ''
+  },
+  {
+    id: 'RGA202605230001',
+    auditType: 'new',
+    bizModuleName: '规则组',
+    bizId: 'RG20260401001',
+    bizName: '层层礼-借记卡新客圈选',
+    submitterName: '张运营',
+    submitTime: '2026-05-23 14:30:00',
+    auditorName: '王审核',
+    auditTime: '2026-05-24 09:00:00',
+    auditStatus: 'approved',
+    auditComment: '规则配置合理,准予上线'
+  }
+]
+
+Mock.mock(/\/api\/rule-group-audit\/list/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const auditStatus = url.searchParams.get('auditStatus') || ''
+  const bizName = url.searchParams.get('bizName') || ''
+  const bizId = url.searchParams.get('bizId') || ''
+  let filtered = ruleGroupAuditList
+  if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
+  if (bizName) filtered = filtered.filter(a => a.bizName.includes(bizName))
+  if (bizId) filtered = filtered.filter(a => String(a.bizId).includes(bizId))
+  return { code: 200, data: filtered, msg: 'ok' }
+})
+
+Mock.mock(/\/api\/rule-group-audit\/detail/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const id = url.searchParams.get('id')
+  const item = ruleGroupAuditList.find(a => a.id === id)
+  return item
+    ? { code: 200, data: item, msg: 'ok' }
+    : { code: 404, data: null, msg: '审核单不存在' }
+})
+
+Mock.mock(/\/api\/rule-group-audit\/approve/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = ruleGroupAuditList.findIndex(a => a.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
+  const audit = ruleGroupAuditList[idx]
+  if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
+  const now = nowStr()
+  ruleGroupAuditList[idx] = { ...audit, auditStatus: 'approved', auditorName: body.auditorName || '王审核', auditTime: now, auditComment: body.comment || '' }
+  return { code: 200, data: null, msg: '审核通过' }
+})
+
+Mock.mock(/\/api\/rule-group-audit\/reject/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = ruleGroupAuditList.findIndex(a => a.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
+  const audit = ruleGroupAuditList[idx]
+  if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
+  const now = nowStr()
+  ruleGroupAuditList[idx] = { ...audit, auditStatus: 'rejected', auditorName: body.auditorName || '王审核', auditTime: now, auditComment: body.comment || '' }
+  return { code: 200, data: null, msg: '已驳回' }
+})
+
+// =============================================
+// 发放计划审核
+// =============================================
+let campaignAuditIdCounter = 100
+
+function nextCampaignAuditId() {
+  campaignAuditIdCounter += 1
+  const d = new Date()
+  const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
+  return 'CA' + ymd + String(campaignAuditIdCounter).padStart(4, '0')
+}
+
+const campaignAuditList = [
+  {
+    id: 'CA202605200001',
+    auditType: 'new',
+    bizModuleName: '发放计划',
+    bizId: 'P000003',
+    bizName: '秋季养生权益季',
+    snapshot: {
+      name: '秋季养生权益季',
+      startTime: '2026-09-01 00:00:00',
+      endTime: '2026-11-30 23:59:59',
+      grantMode: 'claim',
+      crowdList: ['A0000120251224'],
+      equityList: ['Q100001', 'Q100004'],
+      grantCycle: 'monthly',
+      grantQuantity: 1,
+      validity: { type: 'relative', fixedRange: [], relativeDays: 30 },
+      smsTemplateId: 'GRANT_SMS_001',
+      smsTemplate: '【银行】尊敬的${customerName},您已获得${productName}权益,发放时间:${grantTime},请于${expireDate}前使用。'
+    },
+    submitterName: '王运营',
+    submitTime: '2026-05-20 09:30:00',
+    auditorName: '',
+    auditTime: '',
+    auditStatus: 'pending',
+    auditComment: ''
+  },
+  {
+    id: 'CA202605220001',
+    auditType: 'new',
+    bizModuleName: '发放计划',
+    bizId: 'P000004',
+    bizName: '冬季暖心权益发放',
+    snapshot: {
+      name: '冬季暖心权益发放',
+      startTime: '2026-12-01 00:00:00',
+      endTime: '2027-02-28 23:59:59',
+      grantMode: 'direct',
+      crowdList: ['A0000120251224'],
+      equityList: ['Q100001'],
+      grantCycle: 'total',
+      grantQuantity: 3,
+      validity: { type: 'fixed', fixedRange: ['2026-12-01', '2027-02-28'], relativeDays: 30 },
+      smsTemplateId: 'GRANT_SMS_002',
+      smsTemplate: '【银行】尊敬的${customerName},您已获得${productName}权益,有效期至${expireDate},请登录APP查看详情。'
+    },
+    submitterName: '陈运营',
+    submitTime: '2026-05-22 10:15:00',
+    auditorName: '',
+    auditTime: '',
+    auditStatus: 'pending',
+    auditComment: ''
+  },
+  {
+    id: 'CA202605180001',
+    auditType: 'new',
+    bizModuleName: '发放计划',
+    bizId: 'P000001',
+    bizName: '春季会员尊享礼遇',
+    snapshot: {
+      name: '春季会员尊享礼遇',
+      startTime: '2026-04-01 00:00:00',
+      endTime: '2026-06-30 23:59:59',
+      grantMode: 'direct',
+      crowdList: ['A0000120251224'],
+      equityList: ['Q100001'],
+      grantCycle: 'monthly',
+      grantQuantity: 2,
+      validity: { type: 'fixed', fixedRange: ['2026-04-01', '2026-06-30'], relativeDays: 30 },
+      smsTemplateId: 'GRANT_SMS_003',
+      smsTemplate: '【银行】${customerName}您好,${productName}已为您发放成功,有效期至${expireDate}。'
+    },
+    submitterName: '李运营',
+    submitTime: '2026-05-18 14:20:00',
+    auditorName: '王五',
+    auditTime: '2026-05-19 09:00:00',
+    auditStatus: 'approved',
+    auditComment: '发放计划内容合规,准予通过'
+  }
+]
+
+Mock.mock(/\/api\/campaign-audit\/list/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const auditStatus = url.searchParams.get('auditStatus') || ''
+  const bizName = url.searchParams.get('bizName') || ''
+  let filtered = campaignAuditList
+  if (auditStatus) filtered = filtered.filter(a => a.auditStatus === auditStatus)
+  if (bizName) filtered = filtered.filter(a => a.bizName.includes(bizName))
+  return { code: 200, data: filtered, msg: 'ok' }
+})
+
+Mock.mock(/\/api\/campaign-audit\/detail/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const id = url.searchParams.get('id')
+  const item = campaignAuditList.find(a => a.id === id)
+  return item
+    ? { code: 200, data: item, msg: 'ok' }
+    : { code: 404, data: null, msg: '审核单不存在' }
+})
+
+Mock.mock(/\/api\/campaign-audit\/approve/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = campaignAuditList.findIndex(a => a.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
+  const audit = campaignAuditList[idx]
+  if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
+  const now = nowStr()
+  campaignAuditList[idx] = { ...audit, auditStatus: 'approved', auditorName: body.auditorName || '王五', auditTime: now, auditComment: body.comment || '' }
+  return { code: 200, data: null, msg: '审核通过' }
+})
+
+Mock.mock(/\/api\/campaign-audit\/reject/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = campaignAuditList.findIndex(a => a.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '审核单不存在' }
+  const audit = campaignAuditList[idx]
+  if (audit.auditStatus !== 'pending') return { code: 400, data: null, msg: '该审核单已完成' }
+  const now = nowStr()
+  campaignAuditList[idx] = { ...audit, auditStatus: 'rejected', auditorName: body.auditorName || '王五', auditTime: now, auditComment: body.comment || '' }
+  return { code: 200, data: null, msg: '已驳回' }
+})
+
 export default Mock

+ 30 - 0
pc-v3/src/router/index.js

@@ -114,6 +114,18 @@ const routes = [
         component: () => import('@/views/RuleGroupView.vue'),
         meta: { title: '规则组详情' }
       },
+      {
+        path: 'customization/hsbc/rule-group-audit',
+        name: 'RuleGroupAuditList',
+        component: () => import('@/views/RuleGroupAuditList.vue'),
+        meta: { title: '规则组审核' }
+      },
+      {
+        path: 'customization/hsbc/rule-group-audit/:id',
+        name: 'RuleGroupAuditDetail',
+        component: () => import('@/views/RuleGroupAuditDetail.vue'),
+        meta: { title: '规则组审核详情' }
+      },
       {
         path: 'customization/hsbc/equity-products/add',
         name: 'AddEquityProduct',
@@ -240,11 +252,29 @@ const routes = [
         component: () => import('@/views/EquityGrantCampaignEdit.vue'),
         meta: { title: '编辑发放计划' }
       },
+      {
+        path: 'customization/hsbc/equity-grant-campaign/copy/:id',
+        name: 'EquityGrantCampaignCopy',
+        component: () => import('@/views/EquityGrantCampaignEdit.vue'),
+        meta: { title: '复制发放计划' }
+      },
       {
         path: 'customization/hsbc/equity-grant-campaign/view/:id',
         name: 'EquityGrantCampaignView',
         component: () => import('@/views/EquityGrantCampaignView.vue'),
         meta: { title: '查看发放计划' }
+      },
+      {
+        path: 'customization/hsbc/campaign-audit',
+        name: 'CampaignAuditList',
+        component: () => import('@/views/CampaignAuditList.vue'),
+        meta: { title: '发放计划审核' }
+      },
+      {
+        path: 'customization/hsbc/campaign-audit/:id',
+        name: 'CampaignAuditDetail',
+        component: () => import('@/views/CampaignAuditDetail.vue'),
+        meta: { title: '发放计划审核详情' }
       }
     ]
   },

+ 20 - 20
pc-v3/src/views/AddEquityProduct.vue

@@ -78,7 +78,7 @@
               <el-col :span="8" v-if="form.source === '第三方'">
                 <el-form-item required label-width="140px">
                   <template #label>
-                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
+                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页/组装页直接跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
                   </template>
                   <el-radio-group v-model="form.externalJumpMethod">
                     <el-radio-button label="没有"></el-radio-button>
@@ -253,7 +253,7 @@
             </el-row>
             <el-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="权益介绍" required>
+                <el-form-item label="权益介绍">
                   <el-input
                     v-model="form.introduction"
                     type="textarea"
@@ -319,7 +319,7 @@
                             结束日期:${expireDate}<br/>
                             卡号:${exchangeNum}<br/>
                             卡密:${exchangePwd}<br/>
-                            券码路径:${qrCodeDownPaths}
+                            券码使用路径:${qrCodeDownPaths}
                           </template>
                           <el-icon class="tip-icon"><QuestionFilled /></el-icon>
                         </el-tooltip>
@@ -347,7 +347,7 @@
                   </el-form-item>
                 </el-col>
               </el-row>
-              <el-row :gutter="20">
+              <el-row :gutter="20" v-if="form.source === '银行自有'">
                 <el-col :span="8">
                   <el-form-item label="领取成功按钮" required>
                     <el-input v-model="form.successButtonText" placeholder="请输入按钮文案"></el-input>
@@ -475,7 +475,7 @@
             <div class="section-title">显示页面</div>
           </template>
           <el-form :model="form" label-width="120px" label-position="left">
-            <el-form-item label="权益首页/列表页">
+            <el-form-item label="权益首页">
               <el-radio-group v-model="form.showOnHome">
                 <el-radio label="显示">显示</el-radio>
                 <el-radio label="不显示">不显示</el-radio>
@@ -653,9 +653,9 @@ export default {
         claimGroupEnabled: false,
         claimGroupIsPotential: false,
         claimGroupTagId: '',
-        claimDenyMessage: '',
-        claimDenyButtonText: '',
-        claimDenyButtonLink: '',
+        claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+        claimDenyButtonText: '返回',
+        claimDenyButtonLink: '权益首页',
         showOnHome: '显示',
         showOnAssembly: '不显示',
         assemblyPages: []
@@ -707,9 +707,9 @@ export default {
           claimEndTime: '2026-12-31 23:59:59',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100002': {
           source: '第三方',
@@ -740,9 +740,9 @@ export default {
           claimGroupEnabled: true,
           claimGroupIsPotential: false,
           claimGroupTagId: '客群奖励活动A',
-          claimDenyMessage: '您暂不满足领取条件',
-          claimDenyButtonText: '去升级',
-          claimDenyButtonLink: 'https://example.com/upgrade'
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100003': {
           source: '第三方',
@@ -772,9 +772,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100004': {
           source: '第三方',
@@ -802,9 +802,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         }
       }
 

+ 2 - 2
pc-v3/src/views/AuditDetail.vue

@@ -133,7 +133,7 @@
                 </el-form-item>
               </el-col>
             </el-row>
-            <el-row :gutter="20" v-if="form.source === '银行自有' || form.source === '荣数商品'">
+            <el-row :gutter="20" v-if="form.source === '银行自有'">
               <el-col :span="8">
                 <el-form-item label="领取成功按钮">
                   <el-input :value="form.successButtonText" readonly></el-input>
@@ -201,7 +201,7 @@
             <div class="section-title">显示页面</div>
           </template>
           <el-form :model="form" label-width="120px" label-position="left">
-            <el-form-item label="权益首页/列表页">
+            <el-form-item label="权益首页">
               <el-radio-group v-model="form.showOnHome" disabled>
                 <el-radio label="显示">显示</el-radio>
                 <el-radio label="不显示">不显示</el-radio>

+ 17 - 17
pc-v3/src/views/AuditEquityProduct.vue

@@ -75,7 +75,7 @@
               <el-col :span="8" v-if="form.source === '第三方'">
                 <el-form-item label-width="140px">
                   <template #label>
-                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
+                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页/组装页直接跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
                   </template>
                   <el-radio-group v-model="form.externalJumpMethod" disabled>
                     <el-radio-button label="没有"></el-radio-button>
@@ -363,7 +363,7 @@
             <div class="section-title">显示页面</div>
           </template>
           <el-form :model="form" label-width="120px" label-position="left">
-            <el-form-item label="权益首页/列表页">
+            <el-form-item label="权益首页">
               <el-radio-group v-model="form.showOnHome" disabled>
                 <el-radio label="显示">显示</el-radio>
                 <el-radio label="不显示">不显示</el-radio>
@@ -490,9 +490,9 @@ export default {
         claimGroupEnabled: false,
         claimGroupIsPotential: false,
         claimGroupTagId: '',
-        claimDenyMessage: '',
-        claimDenyButtonText: '',
-        claimDenyButtonLink: '',
+        claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+        claimDenyButtonText: '返回',
+        claimDenyButtonLink: '权益首页',
         showOnHome: '显示',
         showOnAssembly: '不显示',
         assemblyPages: []
@@ -543,9 +543,9 @@ export default {
           claimEndTime: '2026-12-31 23:59:59',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100002': {
           source: '第三方',
@@ -576,9 +576,9 @@ export default {
           claimGroupEnabled: true,
           claimGroupIsPotential: false,
           claimGroupTagId: '客群奖励活动A',
-          claimDenyMessage: '您暂不满足领取条件',
-          claimDenyButtonText: '了解更多',
-          claimDenyButtonLink: 'https://example.com'
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100003': {
           source: '第三方',
@@ -608,9 +608,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100004': {
           source: '第三方',
@@ -638,9 +638,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         }
       }
       if (mockMap[id]) {

+ 6 - 1
pc-v3/src/views/AuditList.vue

@@ -9,6 +9,9 @@
       <div class="search-section">
         <div class="search-row">
           <el-form :model="searchForm" label-width="80px" inline>
+            <el-form-item label="权益名称:">
+              <el-input v-model="searchForm.bizName" placeholder="关键字" style="width: 150px"></el-input>
+            </el-form-item>
             <el-form-item label="权益编号:">
               <el-input v-model="searchForm.bizId" placeholder="请输入" style="width: 150px"></el-input>
             </el-form-item>
@@ -83,6 +86,7 @@ export default {
     return {
       activeTab: 'pending',
       searchForm: {
+        bizName: '',
         bizId: ''
       },
       tableData: []
@@ -105,13 +109,14 @@ export default {
     loadData() {
       const params = new URLSearchParams()
       params.append('auditStatus', this.activeTab)
+      if (this.searchForm.bizName) params.append('bizName', this.searchForm.bizName)
       if (this.searchForm.bizId) params.append('bizId', this.searchForm.bizId)
       request.get('/api/audit/list?' + params.toString()).then(res => {
         this.tableData = res.data || []
       }).catch(() => {})
     },
     handleReset() {
-      this.searchForm = { bizId: '' }
+      this.searchForm = { bizName: '', bizId: '' }
       this.loadData()
     },
     auditTypeLabel(t) {

+ 233 - 0
pc-v3/src/views/CampaignAuditDetail.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="campaign-audit-detail" v-if="audit">
+    <el-card>
+      <template #header>
+        <div class="header">
+          <span class="title">发放计划审核详情</span>
+        </div>
+      </template>
+
+      <el-card shadow="never" class="section-card">
+        <template #header>
+          <div class="section-title">发放计划信息</div>
+        </template>
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="发放计划 ID">{{ audit.bizId }}</el-descriptions-item>
+          <el-descriptions-item label="发放计划名称">{{ audit.bizName }}</el-descriptions-item>
+          <el-descriptions-item label="审核类型">
+            <el-tag :type="auditTypeTag(audit.auditType)" size="small">
+              {{ auditTypeLabel(audit.auditType) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="状态">
+            <el-tag :type="auditStatusTag(audit.auditStatus)" size="small">
+              {{ auditStatusLabel(audit.auditStatus) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="获取方式">
+            <el-tag :type="snapshot.grantMode === 'direct' ? 'warning' : 'success'" size="small">
+              {{ snapshot.grantMode === 'direct' ? '直接发放' : '用户领取' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="权益数量">{{ grantSummary }}</el-descriptions-item>
+          <el-descriptions-item label="开始时间">{{ snapshot.startTime || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="结束时间">{{ snapshot.endTime || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="选择客群" :span="2">
+            <template v-if="(snapshot.crowdList || []).length">
+              <el-tag
+                v-for="cid in snapshot.crowdList"
+                :key="cid"
+                size="small"
+                type="info"
+                style="margin-right:6px;margin-top:4px"
+              >{{ crowdLabel(cid) }}</el-tag>
+            </template>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="选择权益" :span="2">
+            <template v-if="(snapshot.equityList || []).length">
+              <el-tag
+                v-for="pid in snapshot.equityList"
+                :key="pid"
+                size="small"
+                style="margin-right:6px;margin-top:4px"
+              >{{ equityLabel(pid) }}</el-tag>
+            </template>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="权益有效期" :span="2">{{ validitySummary }}</el-descriptions-item>
+          <el-descriptions-item label="短信通知" :span="2">
+            <div class="sms-info-row">
+              <span class="sms-tpl-name">{{ smsTemplateName }}</span>
+            </div>
+            <div v-if="snapshot.smsTemplate" class="sms-template-preview">{{ snapshot.smsTemplate }}</div>
+          </el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <el-card shadow="never" class="section-card">
+        <template #header>
+          <div class="section-title">提交信息</div>
+        </template>
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="提交人">{{ audit.submitterName }}</el-descriptions-item>
+          <el-descriptions-item label="提交时间">{{ audit.submitTime }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <div class="action-bar">
+        <el-button @click="goBack">返回</el-button>
+        <template v-if="audit.auditStatus === 'pending'">
+          <el-button type="danger" @click="handleReject">驳回</el-button>
+          <el-button type="primary" @click="handleApprove">通过</el-button>
+        </template>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import crowdNameListStore from '@/store/crowdNameListStore'
+
+const grantCycleLabelMap = { total: '整个周期', monthly: '每月', quarterly: '每季度', yearly: '每年' }
+const SMS_TEMPLATE_NAME_MAP = {
+  GRANT_SMS_001: '发放成功通用模板',
+  GRANT_SMS_002: '发放成功-含查看入口',
+  GRANT_SMS_003: '发放成功-简洁版'
+}
+
+export default {
+  name: 'CampaignAuditDetail',
+  data() {
+    return { audit: null, equityOptions: [] }
+  },
+  computed: {
+    crowdOptions() {
+      return crowdNameListStore.list
+    },
+    snapshot() {
+      return (this.audit && this.audit.snapshot) || {}
+    },
+    grantSummary() {
+      const cycle = grantCycleLabelMap[this.snapshot.grantCycle] || '整个周期'
+      return `${cycle} ${this.snapshot.grantQuantity || 0} 个`
+    },
+    validitySummary() {
+      const v = this.snapshot.validity
+      if (!v) return '-'
+      if (v.type === 'fixed') {
+        if (!v.fixedRange || v.fixedRange.length !== 2) return '固定日期'
+        return `${v.fixedRange[0]} 至 ${v.fixedRange[1]}`
+      }
+      if (v.type === 'relative') return `自发放起 ${v.relativeDays || 0} 天内有效`
+      return '-'
+    },
+    smsTemplateName() {
+      return SMS_TEMPLATE_NAME_MAP[this.snapshot.smsTemplateId] || this.snapshot.smsTemplateId || '-'
+    }
+  },
+  async created() {
+    await this.fetchEquityOptions()
+    this.loadAudit()
+  },
+  methods: {
+    async fetchEquityOptions() {
+      const res = await request.get('/api/equity/list', { params: { shelfStatus: '已上架' } })
+      this.equityOptions = res.data || []
+    },
+    equityLabel(productId) {
+      const m = this.equityOptions.find(i => i.productId === productId)
+      return m ? `${m.equityName} (${m.productId})` : productId
+    },
+    crowdLabel(crowdId) {
+      const m = this.crowdOptions.find(i => i.id === crowdId)
+      return m ? `${m.name} (${m.id})` : crowdId
+    },
+    loadAudit() {
+      const id = this.$route.params.id
+      request.get('/api/campaign-audit/detail', { params: { id } }).then(res => {
+        this.audit = res.data
+        if (!this.audit) {
+          ElMessage.error('审核单不存在')
+          this.$router.replace('/customization/hsbc/campaign-audit')
+        }
+      }).catch(() => {})
+    },
+    auditTypeLabel(t) {
+      return { new: '新增', end: '结束' }[t] || t
+    },
+    auditTypeTag(t) {
+      return { new: 'success', end: 'warning' }[t] || ''
+    },
+    auditStatusLabel(s) {
+      return { pending: '待审核', approved: '已通过', rejected: '已驳回' }[s] || s
+    },
+    auditStatusTag(s) {
+      return { pending: 'warning', approved: 'success', rejected: 'danger' }[s] || ''
+    },
+    handleApprove() {
+      ElMessageBox.confirm('确定通过该发放计划审核吗?', '提示', {
+        confirmButtonText: '通过',
+        cancelButtonText: '取消',
+        type: 'success'
+      }).then(() => {
+        request.post('/api/campaign-audit/approve', { id: this.audit.id }).then(() => {
+          ElMessage.success('审核通过')
+          this.$router.push('/customization/hsbc/campaign-audit')
+        }).catch(() => {})
+      }).catch(() => {})
+    },
+    handleReject() {
+      ElMessageBox.confirm('确定驳回该发放计划审核吗?', '提示', {
+        confirmButtonText: '驳回',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        request.post('/api/campaign-audit/reject', { id: this.audit.id }).then(() => {
+          ElMessage.success('已驳回')
+          this.$router.push('/customization/hsbc/campaign-audit')
+        }).catch(() => {})
+      }).catch(() => {})
+    },
+    goBack() {
+      this.$router.push('/customization/hsbc/campaign-audit')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.campaign-audit-detail { padding: 0; }
+.header { display: flex; align-items: center; justify-content: center; }
+.title { font-size: 16px; font-weight: 600; color: #409EFF; }
+.section-card { margin-bottom: 16px; border: none; }
+.section-card >>> .el-card__header {
+  padding: 12px 20px;
+  background: #fafafa;
+  border-bottom: 1px solid #ebeef5;
+}
+.section-title { font-size: 14px; font-weight: 600; color: #303133; }
+.sms-info-row { display: flex; align-items: center; gap: 8px; }
+.sms-tpl-name { color: #303133; }
+.sms-template-preview {
+  margin-top: 8px;
+  padding: 10px 12px;
+  background: #f5f7fa;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.6;
+  word-break: break-all;
+}
+.action-bar {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #ebeef5;
+}
+</style>

+ 141 - 0
pc-v3/src/views/CampaignAuditList.vue

@@ -0,0 +1,141 @@
+<template>
+  <div class="campaign-audit-list">
+    <el-card>
+      <el-tabs v-model="activeTab" @tab-click="loadData">
+        <el-tab-pane label="待审核" name="pending"></el-tab-pane>
+        <el-tab-pane label="审核通过" name="approved"></el-tab-pane>
+        <el-tab-pane label="审核驳回" name="rejected"></el-tab-pane>
+      </el-tabs>
+      <div class="search-section">
+        <div class="search-row">
+          <el-form :model="searchForm" label-width="120px" inline>
+            <el-form-item label="发放计划名称:">
+              <el-input v-model="searchForm.bizName" placeholder="关键字" style="width: 200px" />
+            </el-form-item>
+            <div class="button-group" style="margin-left: 20px;">
+              <el-button type="primary" @click="loadData">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </div>
+          </el-form>
+        </div>
+      </div>
+
+      <el-table :data="tableData" stripe style="width: 100%">
+        <el-table-column label="操作" min-width="100" fixed="left">
+          <template #default="scope">
+            <el-button
+              v-if="activeTab === 'pending'"
+              type="text"
+              size="small"
+              @click="goDetail(scope.row.id)"
+            >审批</el-button>
+            <el-button
+              v-else
+              type="text"
+              size="small"
+              @click="goDetail(scope.row.id)"
+            >查看</el-button>
+          </template>
+        </el-table-column>
+        <el-table-column prop="bizName" label="发放计划名称" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="bizId" label="发放计划 ID" width="160" align="center" />
+        <el-table-column label="审核类型" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="auditTypeTag(scope.row.auditType)" size="small">
+              {{ auditTypeLabel(scope.row.auditType) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="submitterName" label="提交人" width="120" align="center" />
+        <el-table-column prop="submitTime" label="提交时间" width="180" align="center" />
+        <el-table-column prop="auditorName" label="审核人" width="120" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.auditorName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditTime" label="审核时间" width="180" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.auditTime || '-' }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-section">
+        <span>Total {{ tableData.length }}</span>
+        <el-pagination
+          layout="prev, pager, next, sizes, jumper"
+          :page-size="10"
+          :page-sizes="[10, 20, 50]"
+          :total="tableData.length"
+        />
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+
+export default {
+  name: 'CampaignAuditList',
+  data() {
+    return {
+      activeTab: 'pending',
+      searchForm: { bizName: '' },
+      tableData: []
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  activated() {
+    this.loadData()
+  },
+  watch: {
+    $route(to) {
+      if (to.path === '/customization/hsbc/campaign-audit') {
+        this.loadData()
+      }
+    }
+  },
+  methods: {
+    loadData() {
+      const params = new URLSearchParams()
+      params.append('auditStatus', this.activeTab)
+      if (this.searchForm.bizName) params.append('bizName', this.searchForm.bizName)
+      request.get('/api/campaign-audit/list?' + params.toString()).then(res => {
+        this.tableData = res.data || []
+      }).catch(() => {})
+    },
+    handleReset() {
+      this.searchForm = { bizName: '' }
+      this.loadData()
+    },
+    auditTypeLabel(t) {
+      return { new: '新增', end: '结束' }[t] || t
+    },
+    auditTypeTag(t) {
+      return { new: 'success', end: 'warning' }[t] || ''
+    },
+    goDetail(id) {
+      this.$router.push(`/customization/hsbc/campaign-audit/${id}`)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.campaign-audit-list { padding: 0; }
+.search-section { display: flex; flex-direction: column; }
+.search-row { margin-bottom: 0; }
+.search-row .el-form { display: flex; flex-wrap: wrap; gap: 0; }
+.search-row .el-form-item { margin-right: 0; margin-bottom: 8px; }
+.button-group { display: flex; gap: 5px; }
+.pagination-section {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  gap: 20px;
+  margin-top: 20px;
+}
+</style>

+ 1 - 1
pc-v3/src/views/CreditCardEquity.vue

@@ -169,7 +169,7 @@ export default {
     visiblePages() {
       return row => {
         const pages = []
-        if (row.showOnHome === '显示') pages.push("权益首页/列表页")
+        if (row.showOnHome === '显示') pages.push("权益首页")
         if (row.showOnAssembly === '显示' && Array.isArray(row.assemblyPages)) {
           pages.push(...row.assemblyPages)
         }

+ 20 - 20
pc-v3/src/views/EditEquityProduct.vue

@@ -79,7 +79,7 @@
             <el-col :span="8" v-if="form.source === '第三方'">
               <el-form-item required label-width="140px">
                 <template #label>
-                  <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
+                  <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页/组装页直接跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
                 </template>
                 <el-radio-group v-model="form.externalJumpMethod">
                   <el-radio-button label="没有"></el-radio-button>
@@ -254,7 +254,7 @@
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="权益介绍" required>
+              <el-form-item label="权益介绍">
                 <el-input
                   v-model="form.introduction"
                   type="textarea"
@@ -321,7 +321,7 @@
                             结束日期:${expireDate}<br/>
                             卡号:${exchangeNum}<br/>
                             卡密:${exchangePwd}<br/>
-                            券码路径:${qrCodeDownPaths}
+                            券码使用路径:${qrCodeDownPaths}
                           </div>
                         </template>
                         <el-icon class="tip-icon"><QuestionFilled /></el-icon>
@@ -350,7 +350,7 @@
                 </el-form-item>
               </el-col>
             </el-row>
-            <el-row :gutter="20">
+            <el-row :gutter="20" v-if="form.source === '银行自有'">
               <el-col :span="8">
                 <el-form-item label="领取成功按钮" required>
                   <el-input v-model="form.successButtonText" placeholder="请输入按钮文案"></el-input>
@@ -474,7 +474,7 @@
             <div class="section-title">显示页面</div>
           </template>
           <el-form :model="form" label-width="120px" label-position="left">
-            <el-form-item label="权益首页/列表页">
+            <el-form-item label="权益首页">
               <el-radio-group v-model="form.showOnHome">
                 <el-radio label="显示">显示</el-radio>
                 <el-radio label="不显示">不显示</el-radio>
@@ -662,9 +662,9 @@ export default {
         claimGroupEnabled: false,
         claimGroupIsPotential: false,
         claimGroupTagId: '',
-        claimDenyMessage: '',
-        claimDenyButtonText: '',
-        claimDenyButtonLink: '',
+        claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+        claimDenyButtonText: '返回',
+        claimDenyButtonLink: '权益首页',
         showOnHome: '显示',
         showOnAssembly: '不显示',
         assemblyPages: []
@@ -778,9 +778,9 @@ export default {
           claimEndTime: '2026-12-31 23:59:59',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100002': {
           source: '第三方',
@@ -811,9 +811,9 @@ export default {
           claimGroupEnabled: true,
           claimGroupIsPotential: false,
           claimGroupTagId: '客群奖励活动A',
-          claimDenyMessage: '您暂不满足领取条件',
-          claimDenyButtonText: '去升级',
-          claimDenyButtonLink: 'https://example.com/upgrade'
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100003': {
           source: '第三方',
@@ -843,9 +843,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100004': {
           source: '第三方',
@@ -873,9 +873,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         }
       }
       if (id && mockMap[id]) {

+ 9 - 1
pc-v3/src/views/EquityGrantCampaign.vue

@@ -41,7 +41,7 @@
       </template>
 
       <el-table :data="filteredData" stripe border style="width:100%">
-        <el-table-column label="操作" width="240" fixed="left">
+        <el-table-column label="操作" width="280" fixed="left">
           <template #default="scope">
             <el-button type="text" size="small" @click="handleView(scope.row)">查看</el-button>
             <el-button
@@ -50,6 +50,11 @@
               size="small"
               @click="handleEdit(scope.row)"
             >编辑</el-button>
+            <el-button
+              type="text"
+              size="small"
+              @click="handleCopy(scope.row)"
+            >复制</el-button>
             <el-button
               v-if="statusLabel(scope.row) === '进行中'"
               type="text"
@@ -152,6 +157,9 @@ export default {
     handleEdit(row) {
       this.$router.push(`/customization/hsbc/equity-grant-campaign/edit/${row.id}`)
     },
+    handleCopy(row) {
+      this.$router.push(`/customization/hsbc/equity-grant-campaign/copy/${row.id}`)
+    },
     handleView(row) {
       this.$router.push(`/customization/hsbc/equity-grant-campaign/view/${row.id}`)
     },

+ 16 - 11
pc-v3/src/views/EquityGrantCampaignEdit.vue

@@ -229,6 +229,9 @@ export default {
     isEdit() {
       return this.$route.name === 'EquityGrantCampaignEdit'
     },
+    isCopy() {
+      return this.$route.name === 'EquityGrantCampaignCopy'
+    },
     crowdOptions() {
       return crowdNameListStore.list
     },
@@ -246,7 +249,7 @@ export default {
   },
   async created() {
     await this.fetchEquityOptions()
-    if (this.isEdit) await this.loadDetail()
+    if (this.isEdit || this.isCopy) await this.loadDetail()
   },
   methods: {
     async fetchEquityOptions() {
@@ -262,18 +265,20 @@ export default {
         this.$router.replace('/customization/hsbc/equity-grant-campaign')
         return
       }
-      const now = Date.now()
-      const s = new Date(row.startTime).getTime()
-      const e = new Date(row.endTime).getTime()
-      if (now >= s && now <= e) {
-        ElMessage.warning('进行中的发放计划不可编辑')
-        this.$router.replace('/customization/hsbc/equity-grant-campaign')
-        return
+      if (this.isEdit) {
+        const now = Date.now()
+        const s = new Date(row.startTime).getTime()
+        const e = new Date(row.endTime).getTime()
+        if (now >= s && now <= e) {
+          ElMessage.warning('进行中的发放计划不可编辑')
+          this.$router.replace('/customization/hsbc/equity-grant-campaign')
+          return
+        }
       }
       const v = row.validity || { type: 'fixed', fixedRange: [], relativeDays: 30 }
       this.form = {
-        id: row.id,
-        name: row.name,
+        id: this.isCopy ? '' : row.id,
+        name: this.isCopy ? row.name + '-副本' : row.name,
         startTime: row.startTime,
         endTime: row.endTime,
         crowdList: [...(row.crowdList || [])],
@@ -329,7 +334,7 @@ export default {
           return
         }
         await request.post('/api/equity-grant-campaign/save', this.form)
-        ElMessage.success(this.isEdit ? '保存成功' : '新建成功')
+        ElMessage.success(this.isEdit ? '保存成功' : this.isCopy ? '复制成功' : '新建成功')
         this.$router.push('/customization/hsbc/equity-grant-campaign')
       })
     },

+ 1 - 1
pc-v3/src/views/EquityProducts.vue

@@ -257,7 +257,7 @@ export default {
     },
     visiblePages(row) {
       const pages = []
-      if (row.showOnHome === '显示') pages.push('权益首页/列表页')
+      if (row.showOnHome === '显示') pages.push('权益首页')
       if (row.showOnAssembly === '显示' && Array.isArray(row.assemblyPages)) {
         pages.push(...row.assemblyPages)
       }

+ 180 - 0
pc-v3/src/views/RuleGroupAuditDetail.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="rule-group-audit-detail" v-if="audit">
+    <el-card>
+      <template #header>
+        <div class="header">
+          <span class="title">规则组审核详情</span>
+          <span class="rg-id">审核单号:{{ audit.id }}</span>
+        </div>
+      </template>
+
+      <div v-if="current" class="section">
+        <div class="section-title">规则组信息</div>
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="规则组编号">{{ current.id }}</el-descriptions-item>
+          <el-descriptions-item label="规则组名称">{{ current.name }}</el-descriptions-item>
+          <el-descriptions-item label="审核类型">
+            <el-tag :type="auditTypeTag(audit.auditType)" size="small">
+              {{ auditTypeLabel(audit.auditType) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="规则组类型">{{ typeLabel(current.type) }}</el-descriptions-item>
+          <el-descriptions-item label="规则逻辑">
+            <el-tag size="small" :type="current.logic === 'AND' ? '' : 'warning'">
+              {{ current.logic === 'AND' ? '且(AND)' : '或(OR)' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="规则数量">{{ (current.rules || []).length }}</el-descriptions-item>
+          <el-descriptions-item label="备注说明" :span="2">{{ current.description || '-' }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div v-if="current" class="section">
+        <div class="section-title">
+          规则组配置
+          <span class="section-tip">规则之间使用「{{ current.logic === 'AND' ? '且' : '或' }}」逻辑组合</span>
+        </div>
+        <el-table :data="current.rules || []" border style="width: 100%" empty-text="暂无规则">
+          <el-table-column prop="metaCode" label="元规则编号" width="110" align="center" />
+          <el-table-column label="元规则名称" min-width="180">
+            <template #default="scope">{{ metaName(scope.row.metaCode) }}</template>
+          </el-table-column>
+          <el-table-column prop="ruleName" label="规则名称" min-width="160" show-overflow-tooltip />
+          <el-table-column label="规则描述" min-width="240" show-overflow-tooltip>
+            <template #default="scope">{{ buildDesc(scope.row) }}</template>
+          </el-table-column>
+          <el-table-column label="规则参数" min-width="220" show-overflow-tooltip>
+            <template #default="scope">
+              <span class="params-text">{{ buildParam(scope.row) }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div class="section">
+        <div class="section-title">提交信息</div>
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="提交人">{{ audit.submitterName }}</el-descriptions-item>
+          <el-descriptions-item label="提交时间">{{ audit.submitTime }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div class="action-bar">
+        <el-button @click="goBack">返回</el-button>
+        <template v-if="audit.auditStatus === 'pending'">
+          <el-button type="danger" @click="handleReject">驳回</el-button>
+          <el-button type="primary" @click="handleApprove">通过</el-button>
+        </template>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ruleGroupStore from '@/store/ruleGroupStore'
+import {
+  GROUP_TYPES,
+  getMetaRule,
+  buildRuleDescription,
+  buildRuleParams
+} from '@/config/metaRules'
+
+export default {
+  name: 'RuleGroupAuditDetail',
+  data() {
+    return { audit: null, current: null }
+  },
+  created() {
+    this.loadAudit()
+  },
+  methods: {
+    loadAudit() {
+      const id = this.$route.params.id
+      request.get('/api/rule-group-audit/detail', { params: { id } }).then(res => {
+        this.audit = res.data
+        if (!this.audit) {
+          ElMessage.error('审核单不存在')
+          this.$router.replace('/customization/hsbc/rule-group-audit')
+          return
+        }
+        this.current = ruleGroupStore.getById(this.audit.bizId)
+      }).catch(() => {})
+    },
+    typeLabel(type) {
+      const item = GROUP_TYPES.find(g => g.value === type)
+      return item ? item.label : type
+    },
+    metaName(code) {
+      const m = getMetaRule(code)
+      return m ? m.name : code
+    },
+    buildDesc(rule) {
+      return buildRuleDescription(rule.metaCode, rule.config) || '-'
+    },
+    buildParam(rule) {
+      return buildRuleParams(rule.metaCode, rule.config) || '-'
+    },
+    auditTypeLabel(t) {
+      return { new: '新增', online: '上线' }[t] || t
+    },
+    auditTypeTag(t) {
+      return { new: 'success', online: 'warning' }[t] || ''
+    },
+    handleApprove() {
+      ElMessageBox.confirm('确定通过该规则组审核吗?', '提示', {
+        confirmButtonText: '通过',
+        cancelButtonText: '取消',
+        type: 'success'
+      }).then(() => {
+        request.post('/api/rule-group-audit/approve', { id: this.audit.id }).then(() => {
+          ElMessage.success('审核通过')
+          this.$router.push('/customization/hsbc/rule-group-audit')
+        }).catch(() => {})
+      }).catch(() => {})
+    },
+    handleReject() {
+      ElMessageBox.confirm('确定驳回该规则组审核吗?', '提示', {
+        confirmButtonText: '驳回',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        request.post('/api/rule-group-audit/reject', { id: this.audit.id }).then(() => {
+          ElMessage.success('已驳回')
+          this.$router.push('/customization/hsbc/rule-group-audit')
+        }).catch(() => {})
+      }).catch(() => {})
+    },
+    goBack() {
+      this.$router.push('/customization/hsbc/rule-group-audit')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rule-group-audit-detail { padding: 0; }
+.header { display: flex; align-items: center; justify-content: space-between; }
+.title { font-size: 16px; font-weight: 600; color: #409EFF; }
+.rg-id { color: #909399; font-size: 13px; }
+.section { margin-bottom: 24px; }
+.section-title {
+  font-size: 14px; font-weight: 600; color: #303133;
+  background: #f5f7fa; padding: 10px 14px; border-radius: 4px;
+  margin-bottom: 16px;
+}
+.section-tip { font-weight: normal; color: #909399; margin-left: 12px; font-size: 12px; }
+.params-text {
+  font-family: 'Menlo', 'Consolas', monospace;
+  color: #606266; font-size: 12px;
+}
+.action-bar {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #ebeef5;
+}
+</style>

+ 145 - 0
pc-v3/src/views/RuleGroupAuditList.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="rule-group-audit-list">
+    <el-card>
+      <el-tabs v-model="activeTab" @tab-click="loadData">
+        <el-tab-pane label="待审核" name="pending"></el-tab-pane>
+        <el-tab-pane label="审核通过" name="approved"></el-tab-pane>
+        <el-tab-pane label="审核驳回" name="rejected"></el-tab-pane>
+      </el-tabs>
+      <div class="search-section">
+        <div class="search-row">
+          <el-form :model="searchForm" label-width="100px" inline>
+            <el-form-item label="规则组名称:">
+              <el-input v-model="searchForm.bizName" placeholder="关键字" style="width: 160px" />
+            </el-form-item>
+            <el-form-item label="规则组编号:">
+              <el-input v-model="searchForm.bizId" placeholder="请输入" style="width: 160px" />
+            </el-form-item>
+            <div class="button-group" style="margin-left: 20px;">
+              <el-button type="primary" @click="loadData">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </div>
+          </el-form>
+        </div>
+      </div>
+
+      <el-table :data="tableData" stripe style="width: 100%">
+        <el-table-column label="操作" min-width="100" fixed="left">
+          <template #default="scope">
+            <el-button
+              v-if="activeTab === 'pending'"
+              type="text"
+              size="small"
+              @click="goDetail(scope.row.id)"
+            >审批</el-button>
+            <el-button
+              v-else
+              type="text"
+              size="small"
+              @click="goDetail(scope.row.id)"
+            >查看</el-button>
+          </template>
+        </el-table-column>
+        <el-table-column prop="bizName" label="规则组名称" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="bizId" label="规则组编号" width="160" align="center" />
+        <el-table-column label="审核类型" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="auditTypeTag(scope.row.auditType)" size="small">
+              {{ auditTypeLabel(scope.row.auditType) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="submitterName" label="提交人" width="120" align="center" />
+        <el-table-column prop="submitTime" label="提交时间" width="180" align="center" />
+        <el-table-column prop="auditorName" label="审核人" width="120" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.auditorName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditTime" label="审核时间" width="180" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.auditTime || '-' }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-section">
+        <span>Total {{ tableData.length }}</span>
+        <el-pagination
+          layout="prev, pager, next, sizes, jumper"
+          :page-size="10"
+          :page-sizes="[10, 20, 50]"
+          :total="tableData.length"
+        />
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+
+export default {
+  name: 'RuleGroupAuditList',
+  data() {
+    return {
+      activeTab: 'pending',
+      searchForm: { bizName: '', bizId: '' },
+      tableData: []
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  activated() {
+    this.loadData()
+  },
+  watch: {
+    $route(to) {
+      if (to.path === '/customization/hsbc/rule-group-audit') {
+        this.loadData()
+      }
+    }
+  },
+  methods: {
+    loadData() {
+      const params = new URLSearchParams()
+      params.append('auditStatus', this.activeTab)
+      if (this.searchForm.bizName) params.append('bizName', this.searchForm.bizName)
+      if (this.searchForm.bizId) params.append('bizId', this.searchForm.bizId)
+      request.get('/api/rule-group-audit/list?' + params.toString()).then(res => {
+        this.tableData = res.data || []
+      }).catch(() => {})
+    },
+    handleReset() {
+      this.searchForm = { bizName: '', bizId: '' }
+      this.loadData()
+    },
+    auditTypeLabel(t) {
+      return { new: '新增', online: '上线' }[t] || t
+    },
+    auditTypeTag(t) {
+      return { new: 'success', online: 'warning' }[t] || ''
+    },
+    goDetail(id) {
+      this.$router.push(`/customization/hsbc/rule-group-audit/${id}`)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rule-group-audit-list { padding: 0; }
+.search-section { display: flex; flex-direction: column; }
+.search-row { margin-bottom: 0; }
+.search-row .el-form { display: flex; flex-wrap: wrap; gap: 0; }
+.search-row .el-form-item { margin-right: 0; margin-bottom: 8px; }
+.button-group { display: flex; gap: 5px; }
+.pagination-section {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  gap: 20px;
+  margin-top: 20px;
+}
+</style>

+ 1 - 1
pc-v3/src/views/SmsTemplateList.vue

@@ -111,7 +111,7 @@
               <code>${exchangePwd}</code>
             </div>
             <div class="placeholder-tip__item">
-              <span class="placeholder-tip__label">券码路径:</span>
+              <span class="placeholder-tip__label">券码使用路径:</span>
               <code>${qrCodeDownPaths}</code>
             </div>
           </div>

+ 18 - 18
pc-v3/src/views/ViewEquityProduct.vue

@@ -75,7 +75,7 @@
               <el-col :span="8" v-if="form.source === '第三方'">
                 <el-form-item label-width="140px">
                   <template #label>
-                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
+                    <span style="white-space: nowrap;">是否有详情页<el-tooltip content="没有详情页时直接从首页/列表页/组装页直接跳转" placement="top"><el-icon class="tip-icon"><QuestionFilled /></el-icon></el-tooltip></span>
                   </template>
                   <el-radio-group v-model="form.externalJumpMethod" disabled>
                     <el-radio-button label="没有"></el-radio-button>
@@ -271,7 +271,7 @@
                   </el-form-item>
                 </el-col>
               </el-row>
-              <el-row :gutter="20">
+              <el-row :gutter="20" v-if="form.source === '银行自有'">
                 <el-col :span="8">
                   <el-form-item label="领取成功按钮">
                     <el-input :value="form.successButtonText" readonly></el-input>
@@ -366,7 +366,7 @@
             <div class="section-title">显示页面</div>
           </template>
           <el-form :model="form" label-width="120px" label-position="left">
-            <el-form-item label="权益首页/列表页">
+            <el-form-item label="权益首页">
               <el-radio-group v-model="form.showOnHome" disabled>
                 <el-radio label="显示">显示</el-radio>
                 <el-radio label="不显示">不显示</el-radio>
@@ -521,9 +521,9 @@ export default {
         claimGroupEnabled: false,
         claimGroupIsPotential: false,
         claimGroupTagId: '',
-        claimDenyMessage: '',
-        claimDenyButtonText: '',
-        claimDenyButtonLink: '',
+        claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+        claimDenyButtonText: '返回',
+        claimDenyButtonLink: '权益首页',
         showOnHome: '显示',
         showOnAssembly: '不显示',
         assemblyPages: []
@@ -579,9 +579,9 @@ export default {
           claimEndTime: '2026-12-31 23:59:59',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100002': {
           source: '第三方',
@@ -612,9 +612,9 @@ export default {
           claimGroupEnabled: true,
           claimGroupIsPotential: false,
           claimGroupTagId: '客群奖励活动A',
-          claimDenyMessage: '您暂不满足领取条件',
-          claimDenyButtonText: '去升级',
-          claimDenyButtonLink: 'https://example.com/upgrade'
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100003': {
           source: '第三方',
@@ -644,9 +644,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         },
         'Q100004': {
           source: '第三方',
@@ -674,9 +674,9 @@ export default {
           claimEndTime: '',
           claimGroupEnabled: false,
           claimGroupTagId: '',
-          claimDenyMessage: '',
-          claimDenyButtonText: '',
-          claimDenyButtonLink: ''
+          claimDenyMessage: '很抱歉,您暂不符合领取条件。',
+          claimDenyButtonText: '返回',
+          claimDenyButtonLink: '权益首页'
         }
       }
       if (id && mockMap[id]) {