Эх сурвалжийг харах

新增发放计划模块并完善权益首页与客群任务配置

- 发放计划:独立的列表/新建/编辑/查看页面,支持状态筛选(未开始/进行中/已结束)、客群多选、按类型(累计/每月/每季度/每年)配置发放数量、权益有效期(固定/相对日期),进行中支持结束、禁止编辑删除
- 菜单结构:将"发放计划"提升为"权益管理"同级
- 权益首页:顶部新增"客户身份模块配置",按 MSC(Advance/Premier/Premier Elite/Private Bank)分别配置背景图、身份标识文字与日均余额规则
- 权益审核:新增/变更类型仅在最后一步显示通过/驳回
- 客群任务:移除圈客周期与客群类型,开始/结束日期独立成行,查询按钮放回搜索行首

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fade 1 сар өмнө
parent
commit
8d1105ae96

+ 3 - 0
pc/src/components/Layout.vue

@@ -45,6 +45,9 @@
                 权益审核
               </el-menu-item>
             </el-submenu>
+            <el-menu-item index="/customization/hsbc/equity-grant-campaign">
+              发放计划
+            </el-menu-item>
             <el-submenu index="customization-hsbc-crowd">
               <template slot="title">人群管理</template>
               <el-menu-item index="/customization/hsbc/crowd-tasks">

+ 148 - 0
pc/src/mock/index.js

@@ -80,6 +80,34 @@ Mock.mock(/\/api\/page-config\/home/, 'put', (options) => {
 // 页面管理 - 权益首页配置
 // =============================================
 const zoneConfigData = {
+  identityModule: {
+    advance: {
+      bgImage: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800',
+      identityText: '汇丰运筹理财',
+      showQualifyStatus: false,
+      showAvgBalance: true,
+      avgBalanceRule: '月内日均余额 = 当月内每日账户余额之和 / 当月天数。'
+    },
+    premier: {
+      bgImage: 'https://images.unsplash.com/photo-1583847268964-b28dc8f51f92?w=800',
+      identityText: '汇丰卓越理财',
+      showQualifyStatus: true,
+      showAvgBalance: true,
+      avgBalanceRule: '计算规则:截止到昨日的账户月内日均余额。'
+    },
+    premierElite: {
+      bgImage: 'https://images.unsplash.com/photo-1557682250-33bd709cbe85?w=800',
+      identityText: '汇丰卓越理财·尊尚',
+      showQualifyStatus: true,
+      showAvgBalance: true,
+      avgBalanceRule: '计算规则:截止到昨日的账户月内日均余额。支持查看合格状态及复制编号。'
+    },
+    privateBank: {
+      bgImage: 'https://images.unsplash.com/photo-1492684223066-81342ee5ff30?w=800',
+      identityText: '汇丰环球私人银行',
+      showPbLevel: true
+    }
+  },
   adCarousel: [{
     imageUrl: 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800',
     linkH5: '', linkApp: '', wechatAppId: '', wechatPath: '',
@@ -937,4 +965,124 @@ Mock.mock(/\/api\/audit\/reject/, 'post', (options) => {
   return { code: 200, data: null, msg: '已驳回' }
 })
 
+// =============================================
+// 发放计划
+// =============================================
+const equityGrantCampaignList = [
+  {
+    id: 'GC202604010001',
+    name: '春季会员尊享礼遇',
+    startTime: '2026-04-01 00:00:00',
+    endTime: '2026-06-30 23:59:59',
+    crowdList: ['A0000120251224'],
+    equityList: ['100001', '100003'],
+    grantCycle: 'monthly',
+    grantQuantity: 2,
+    validity: { type: 'fixed', fixedRange: ['2026-04-01', '2026-06-30'], relativeDays: 30 },
+    createTime: '2026-03-25 10:20:30',
+    updateTime: '2026-03-25 10:20:30'
+  },
+  {
+    id: 'GC202605010001',
+    name: '夏日畅享权益月',
+    startTime: '2026-05-01 00:00:00',
+    endTime: '2026-08-31 23:59:59',
+    crowdList: [],
+    equityList: ['100001', '100004', '100005'],
+    grantCycle: 'total',
+    grantQuantity: 1,
+    validity: { type: 'relative', fixedRange: [], relativeDays: 30 },
+    createTime: '2026-04-20 14:10:00',
+    updateTime: '2026-04-20 14:10:00'
+  }
+]
+let equityGrantCampaignCounter = equityGrantCampaignList.length
+
+function nextGrantCampaignId() {
+  equityGrantCampaignCounter += 1
+  const d = new Date()
+  const ymd = d.getFullYear().toString() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0')
+  return 'GC' + ymd + String(equityGrantCampaignCounter).padStart(4, '0')
+}
+
+Mock.mock(/\/api\/equity-grant-campaign\/list/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const name = url.searchParams.get('name') || ''
+  const startDate = url.searchParams.get('startDate') || ''
+  const endDate = url.searchParams.get('endDate') || ''
+  let filtered = equityGrantCampaignList
+  if (name) filtered = filtered.filter(c => c.name.includes(name))
+  if (startDate) filtered = filtered.filter(c => c.endTime.slice(0, 10) >= startDate)
+  if (endDate) filtered = filtered.filter(c => c.startTime.slice(0, 10) <= endDate)
+  return { code: 200, data: filtered, msg: 'ok' }
+})
+
+Mock.mock(/\/api\/equity-grant-campaign\/save/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const now = nowStr()
+  const defaultValidity = { type: 'fixed', fixedRange: [], relativeDays: 30 }
+  if (body.id) {
+    const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
+    if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
+    equityGrantCampaignList[idx] = {
+      ...equityGrantCampaignList[idx],
+      name: body.name,
+      startTime: body.startTime,
+      endTime: body.endTime,
+      crowdList: body.crowdList || [],
+      equityList: body.equityList || [],
+      grantCycle: body.grantCycle || 'total',
+      grantQuantity: body.grantQuantity || 1,
+      validity: body.validity || defaultValidity,
+      updateTime: now
+    }
+    return { code: 200, data: { id: body.id }, msg: '保存成功' }
+  }
+  const newId = nextGrantCampaignId()
+  equityGrantCampaignList.unshift({
+    id: newId,
+    name: body.name,
+    startTime: body.startTime,
+    endTime: body.endTime,
+    crowdList: body.crowdList || [],
+    equityList: body.equityList || [],
+    grantCycle: body.grantCycle || 'total',
+    grantQuantity: body.grantQuantity || 1,
+    validity: body.validity || defaultValidity,
+    createTime: now,
+    updateTime: now
+  })
+  return { code: 200, data: { id: newId }, msg: '新建成功' }
+})
+
+Mock.mock(/\/api\/equity-grant-campaign\/delete/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
+  equityGrantCampaignList.splice(idx, 1)
+  return { code: 200, data: null, msg: '删除成功' }
+})
+
+Mock.mock(/\/api\/equity-grant-campaign\/detail/, 'get', (options) => {
+  const url = new URL(options.url, 'http://localhost')
+  const id = url.searchParams.get('id')
+  const item = equityGrantCampaignList.find(c => c.id === id)
+  return item
+    ? { code: 200, data: item, msg: 'ok' }
+    : { code: 404, data: null, msg: '发放计划不存在' }
+})
+
+Mock.mock(/\/api\/equity-grant-campaign\/end/, 'post', (options) => {
+  const body = JSON.parse(options.body)
+  const idx = equityGrantCampaignList.findIndex(c => c.id === body.id)
+  if (idx < 0) return { code: 404, data: null, msg: '发放计划不存在' }
+  const now = nowStr()
+  equityGrantCampaignList[idx] = {
+    ...equityGrantCampaignList[idx],
+    endTime: now,
+    updateTime: now
+  }
+  return { code: 200, data: null, msg: '已结束' }
+})
+
 export default Mock

+ 28 - 4
pc/src/router/index.js

@@ -61,25 +61,25 @@ const routes = [
         path: 'customization/hsbc/crowd-tasks/add',
         name: 'CrowdTaskAdd',
         component: () => import('@/views/CrowdTaskEdit.vue'),
-        meta: { title: '新增客群规则任务' }
+        meta: { title: '新建客群任务' }
       },
       {
         path: 'customization/hsbc/crowd-tasks/edit/:id',
         name: 'CrowdTaskEdit',
         component: () => import('@/views/CrowdTaskEdit.vue'),
-        meta: { title: '编辑客群规则任务' }
+        meta: { title: '编辑客群任务' }
       },
       {
         path: 'customization/hsbc/crowd-tasks/copy/:id',
         name: 'CrowdTaskCopy',
         component: () => import('@/views/CrowdTaskEdit.vue'),
-        meta: { title: '复制客群规则任务' }
+        meta: { title: '复制客群任务' }
       },
       {
         path: 'customization/hsbc/crowd-tasks/view/:id',
         name: 'CrowdTaskView',
         component: () => import('@/views/CrowdTaskView.vue'),
-        meta: { title: '客群规则任务详情' }
+        meta: { title: '客群任务详情' }
       },
       {
         path: 'customization/hsbc/crowd-name-lists',
@@ -230,6 +230,30 @@ const routes = [
         name: 'AuditDetail',
         component: () => import('@/views/AuditDetail.vue'),
         meta: { title: '审核详情' }
+      },
+      {
+        path: 'customization/hsbc/equity-grant-campaign',
+        name: 'EquityGrantCampaign',
+        component: () => import('@/views/EquityGrantCampaign.vue'),
+        meta: { title: '发放计划', icon: 'el-icon-present' }
+      },
+      {
+        path: 'customization/hsbc/equity-grant-campaign/add',
+        name: 'EquityGrantCampaignAdd',
+        component: () => import('@/views/EquityGrantCampaignEdit.vue'),
+        meta: { title: '新建发放计划' }
+      },
+      {
+        path: 'customization/hsbc/equity-grant-campaign/edit/:id',
+        name: 'EquityGrantCampaignEdit',
+        component: () => import('@/views/EquityGrantCampaignEdit.vue'),
+        meta: { title: '编辑发放计划' }
+      },
+      {
+        path: 'customization/hsbc/equity-grant-campaign/view/:id',
+        name: 'EquityGrantCampaignView',
+        component: () => import('@/views/EquityGrantCampaignView.vue'),
+        meta: { title: '查看发放计划' }
       }
     ]
   },

+ 0 - 12
pc/src/store/crowdTaskStore.js

@@ -48,9 +48,6 @@ const state = Vue.observable({
       id: '00000120251224',
       name: '层层礼圈选客群1',
       description: '圈选层层礼报名客群',
-      type: 'display', // display | reward
-      cycle: 'once',
-      cycleConfig: null,
       startDate: '2025-12-25',
       endDate: '2025-12-31',
       ruleGroupLogic: 'AND',
@@ -118,9 +115,6 @@ export default {
       id: genTaskId(state.seq++),
       name: payload.name,
       description: payload.description || '',
-      type: payload.type,
-      cycle: payload.cycle || 'once',
-      cycleConfig: payload.cycleConfig || null,
       startDate: payload.startDate,
       endDate: payload.endDate,
       ruleGroupLogic: payload.ruleGroupLogic || 'AND',
@@ -141,9 +135,6 @@ export default {
     return this.create({
       name: src.name + '-副本',
       description: src.description,
-      type: src.type,
-      cycle: src.cycle,
-      cycleConfig: src.cycleConfig ? { ...src.cycleConfig } : null,
       startDate: src.startDate,
       endDate: src.endDate,
       ruleGroupLogic: src.ruleGroupLogic,
@@ -155,9 +146,6 @@ export default {
     if (!target) return null
     target.name = payload.name
     target.description = payload.description || ''
-    target.type = payload.type
-    target.cycle = payload.cycle || 'once'
-    target.cycleConfig = payload.cycleConfig || null
     target.startDate = payload.startDate
     target.endDate = payload.endDate
     target.ruleGroupLogic = payload.ruleGroupLogic || 'AND'

+ 1 - 1
pc/src/views/AddEquityProduct.vue

@@ -390,7 +390,7 @@
                 <template v-if="form.claimGroupEnabled">
                   <div style="width: 600px;background: #f1f2f5;padding: 20px;margin-top: 10px;">
                     <el-alert
-                      title="需在【权益管理-权益发放】中选择权益发放,未发放的权益移动端不展示"
+                      title="需在【发放计划】中选择发放计划,未发放的权益移动端不展示"
                       type="info"
                       :closable="false"
                       show-icon

+ 7 - 1
pc/src/views/AuditDetail.vue

@@ -243,7 +243,7 @@
       <!-- 操作栏 -->
       <div class="action-bar">
         <el-button @click="goBack">返回</el-button>
-        <template v-if="audit.auditStatus === 'pending'">
+        <template v-if="canAudit">
           <el-button type="danger" @click="handleReject">驳回</el-button>
           <el-button type="primary" @click="handleApprove">通过</el-button>
         </template>
@@ -349,6 +349,12 @@ export default {
       if (s === '银行自有' || s === '荣数商品') return true
       if (s === '第三方' && this.form.externalJumpMethod === '有') return true
       return false
+    },
+    canAudit() {
+      if (!this.audit || this.audit.auditStatus !== 'pending') return false
+      const t = this.audit.auditType
+      if (t === 'new' || t === 'change') return this.activeStep === 2
+      return true
     }
   },
   created() {

+ 1 - 1
pc/src/views/AuditEquityProduct.vue

@@ -300,7 +300,7 @@
                 <template v-if="form.claimGroupEnabled">
                   <div style="width: 600px;background: #f1f2f5;padding: 20px;margin-top: 10px;">
                     <el-alert
-                      title="需在【权益管理-权益发放】中选择权益发放,未发放的权益移动端不展示"
+                      title="需在【发放计划】中选择发放计划,未发放的权益移动端不展示"
                       type="info"
                       :closable="false"
                       show-icon

+ 6 - 60
pc/src/views/CrowdTaskEdit.vue

@@ -30,7 +30,7 @@
                 <el-input v-model="form.id" disabled placeholder="系统自动生成" suffix-icon="el-icon-circle-close" />
               </el-form-item>
             </el-col>
-            <el-col :span="12">
+            <el-col :span="24">
               <el-form-item label="客群描述" prop="description">
                 <el-input
                   v-model="form.description"
@@ -42,43 +42,8 @@
                 />
               </el-form-item>
             </el-col>
-            <el-col :span="12">
-              <el-form-item label="客群类型" prop="type">
-                <el-select v-model="form.type" placeholder="请选择" style="width: 100%">
-                  <el-option label="展示规则" value="display" />
-                  <el-option label="奖励规则" value="reward" />
-                </el-select>
-              </el-form-item>
-              <el-form-item label="圈客周期" prop="cycle">
-                <el-radio-group v-model="form.cycle" @change="onCycleChange">
-                  <el-radio label="once">永久一次</el-radio>
-                  <el-radio label="daily">每日一次</el-radio>
-                  <el-radio label="monthly">每月指定日</el-radio>
-                  <el-radio label="weekly">每周指定日</el-radio>
-                </el-radio-group>
-                <div v-if="form.cycle === 'monthly'" style="margin-top: 8px">
-                  <el-select v-model="form.cycleConfig.day" placeholder="请选择" style="width: 160px">
-                    <el-option
-                      v-for="d in 31"
-                      :key="d"
-                      :label="`每月${d}号`"
-                      :value="d"
-                    />
-                  </el-select>
-                </div>
-                <div v-if="form.cycle === 'weekly'" style="margin-top: 8px">
-                  <el-select v-model="form.cycleConfig.weekday" placeholder="请选择" style="width: 160px">
-                    <el-option label="每周一" :value="1" />
-                    <el-option label="每周二" :value="2" />
-                    <el-option label="每周三" :value="3" />
-                    <el-option label="每周四" :value="4" />
-                    <el-option label="每周五" :value="5" />
-                    <el-option label="每周六" :value="6" />
-                    <el-option label="每周日" :value="7" />
-                  </el-select>
-                </div>
-              </el-form-item>
-            </el-col>
+          </el-row>
+          <el-row :gutter="32">
             <el-col :span="12">
               <el-form-item label="客群任务开始日期" prop="startDate">
                 <el-date-picker
@@ -165,9 +130,6 @@ const emptyForm = () => ({
   name: '',
   id: '',
   description: '',
-  type: '',
-  cycle: 'once',
-  cycleConfig: { day: 1, weekday: 1 },
   startDate: '',
   endDate: '',
   ruleGroupLogic: '',
@@ -186,8 +148,6 @@ export default {
       rules: {
         name: [{ required: true, message: '请输入客群名称', trigger: 'blur' }],
         description: [{ required: true, message: '请输入客群描述', trigger: 'blur' }],
-        type: [{ required: true, message: '请选择客群类型', trigger: 'change' }],
-        cycle: [{ required: true, message: '请选择圈客周期', trigger: 'change' }],
         startDate: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
         endDate: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
       }
@@ -201,9 +161,9 @@ export default {
       return this.$route.name === 'CrowdTaskCopy'
     },
     pageTitle() {
-      if (this.isEdit) return '编辑客群规则任务'
-      if (this.isCopy) return '复制客群规则任务'
-      return '新增客群规则任务'
+      if (this.isEdit) return '编辑客群任务'
+      if (this.isCopy) return '复制客群任务'
+      return '新建客群任务'
     },
     selectedRuleGroupIds() {
       return this.form.ruleGroups.map((r) => r.id)
@@ -231,9 +191,6 @@ export default {
         name: this.isCopy ? source.name + '-副本' : source.name,
         id: this.isCopy ? '复制保存后生成' : source.id,
         description: source.description,
-        type: source.type,
-        cycle: source.cycle,
-        cycleConfig: source.cycleConfig ? { ...source.cycleConfig } : { day: 1, weekday: 1 },
         startDate: source.startDate,
         endDate: source.endDate,
         ruleGroupLogic: source.ruleGroupLogic,
@@ -244,10 +201,6 @@ export default {
     }
   },
   methods: {
-    onCycleChange(v) {
-      if (v === 'monthly' && !this.form.cycleConfig.day) this.form.cycleConfig.day = 1
-      if (v === 'weekly' && !this.form.cycleConfig.weekday) this.form.cycleConfig.weekday = 1
-    },
     handleNext() {
       this.$refs.baseForm.validate((ok) => {
         if (!ok) return
@@ -282,13 +235,6 @@ export default {
         return
       }
       const payload = { ...this.form }
-      if (payload.cycle !== 'monthly' && payload.cycle !== 'weekly') {
-        payload.cycleConfig = null
-      } else if (payload.cycle === 'monthly') {
-        payload.cycleConfig = { day: this.form.cycleConfig.day }
-      } else {
-        payload.cycleConfig = { weekday: this.form.cycleConfig.weekday }
-      }
       if (this.isEdit) {
         // 校验状态下的编辑需回退到新建并重置运行信息
         const resetRun = this.current.status === 'verifying'

+ 3 - 7
pc/src/views/CrowdTaskList.vue

@@ -15,10 +15,12 @@
             <el-form-item label="客群名称:">
               <el-input v-model="searchForm.name" placeholder="请输入" clearable style="width: 180px" />
             </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+            </el-form-item>
           </el-form>
         </div>
         <div class="action-row">
-          <el-button type="primary" @click="handleSearch">查询</el-button>
           <el-button type="primary" icon="el-icon-plus" @click="handleCreate">新增任务</el-button>
           <el-button type="primary" icon="el-icon-plus" plain @click="handleCopy">复制任务</el-button>
         </div>
@@ -32,9 +34,6 @@
       >
         <el-table-column prop="id" label="客群编号" width="160" />
         <el-table-column prop="name" label="客群名称" min-width="160" show-overflow-tooltip />
-        <el-table-column label="客群类型" width="110" align="center">
-          <template slot-scope="scope">{{ typeLabel(scope.row.type) }}</template>
-        </el-table-column>
         <el-table-column label="状态" width="100" align="center">
           <template slot-scope="scope">
             <el-tag :type="statusTagType(scope.row.status)" size="small">
@@ -120,9 +119,6 @@ export default {
     }
   },
   methods: {
-    typeLabel(t) {
-      return { display: '展示规则', reward: '奖励规则' }[t] || t
-    },
     statusLabel(s) {
       return { draft: '新建', verifying: '校验', online: '上线', stopped: '已停止' }[s] || s
     },

+ 2 - 19
pc/src/views/CrowdTaskView.vue

@@ -2,7 +2,7 @@
   <div class="crowd-task-view">
     <el-card v-if="current">
       <div slot="header" class="header">
-        <span class="title">客群规则任务详情</span>
+        <span class="title">客群任务详情</span>
       </div>
 
       <!-- 基本信息 -->
@@ -13,10 +13,8 @@
         <el-descriptions-item label="状态">
           <el-tag :type="statusTagType(current.status)" size="small">{{ statusLabel(current.status) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="客群类型">{{ typeLabel(current.type) }}</el-descriptions-item>
         <el-descriptions-item label="客群描述" :span="2">{{ current.description || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="圈客周期">{{ cycleLabel(current) }}</el-descriptions-item>
-        <el-descriptions-item label="任务开始/结束日期">
+        <el-descriptions-item label="任务开始/结束日期" :span="2">
           {{ current.startDate }} ~ {{ current.endDate }}
         </el-descriptions-item>
         <el-descriptions-item label="任务创建时间">{{ current.createdAt }}</el-descriptions-item>
@@ -149,21 +147,6 @@ export default {
     statusTagType(s) {
       return { draft: 'info', verifying: 'warning', online: 'success', stopped: 'danger' }[s] || ''
     },
-    typeLabel(t) {
-      return { display: '展示规则', reward: '奖励规则' }[t] || t
-    },
-    cycleLabel(row) {
-      if (!row) return ''
-      if (row.cycle === 'once') return '永久一次'
-      if (row.cycle === 'daily') return '每日一次'
-      if (row.cycle === 'monthly') return `每月${row.cycleConfig && row.cycleConfig.day}号`
-      if (row.cycle === 'weekly') {
-        const w = ['一', '二', '三', '四', '五', '六', '日']
-        const idx = row.cycleConfig && row.cycleConfig.weekday
-        return idx ? `每周${w[idx - 1]}` : '每周'
-      }
-      return row.cycle
-    },
     handleRerun() {
       this.$confirm('重新校验将重新运行 count 和 sample,不影响 fullist。', '重新校验', {
         confirmButtonText: '运行', cancelButtonText: '取消', type: 'info'

+ 1 - 1
pc/src/views/EditEquityProduct.vue

@@ -388,7 +388,7 @@
             <template v-if="form.claimGroupEnabled">
               <div style="width: 600px;background: #f1f2f5;padding: 20px;margin-top: 10px;">
                 <el-alert
-                  title="需在【权益管理-权益发放】中选择权益发放,未发放的权益移动端不展示"
+                  title="需在【发放计划】中选择发放计划,未发放的权益移动端不展示"
                   type="info"
                   :closable="false"
                   show-icon

+ 202 - 0
pc/src/views/EquityGrantCampaign.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="equity-grant-campaign">
+    <el-card>
+      <div slot="header" class="search-section">
+        <div class="search-row">
+          <el-form :model="searchForm" label-width="100px" inline>
+            <el-form-item label="发放计划名称:">
+              <el-input v-model="searchForm.name" placeholder="关键字" clearable style="width:220px" />
+            </el-form-item>
+            <el-form-item label="发放计划时间:">
+              <el-date-picker
+                v-model="searchForm.dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                style="width:360px"
+              />
+            </el-form-item>
+            <el-form-item label="状态:">
+              <el-select v-model="searchForm.status" placeholder="全部" clearable style="width:140px">
+                <el-option label="未开始" value="未开始" />
+                <el-option label="进行中" value="进行中" />
+                <el-option label="已结束" value="已结束" />
+              </el-select>
+            </el-form-item>
+            <div class="button-group">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </div>
+          </el-form>
+        </div>
+        <div class="action-row">
+          <el-button type="primary" icon="el-icon-plus" @click="handleCreate">新建发放计划</el-button>
+        </div>
+      </div>
+
+      <el-table :data="filteredData" stripe border style="width:100%">
+        <el-table-column label="操作" width="240" fixed="left">
+          <template slot-scope="scope">
+            <el-button type="text" size="small" @click="handleView(scope.row)">查看</el-button>
+            <el-button
+              v-if="statusLabel(scope.row) !== '进行中'"
+              type="text"
+              size="small"
+              @click="handleEdit(scope.row)"
+            >编辑</el-button>
+            <el-button
+              v-if="statusLabel(scope.row) === '进行中'"
+              type="text"
+              size="small"
+              style="color:#E6A23C"
+              @click="handleEnd(scope.row)"
+            >结束</el-button>
+            <el-button
+              v-if="statusLabel(scope.row) !== '进行中'"
+              type="text"
+              size="small"
+              style="color:#F56C6C"
+              @click="handleDelete(scope.row)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+        <el-table-column prop="id" label="发放计划 ID" width="160" align="center" />
+        <el-table-column prop="name" label="发放计划名称" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="startTime" label="开始时间" width="170" align="center" />
+        <el-table-column prop="endTime" label="结束时间" width="170" align="center" />
+        <el-table-column label="状态" width="100" align="center">
+          <template slot-scope="scope">
+            <el-tag :type="statusTag(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="发放数量" width="150" align="center">
+          <template slot-scope="scope">
+            {{ grantSummary(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="权益有效期" min-width="200" align="center">
+          <template slot-scope="scope">
+            {{ validitySummary(scope.row.validity) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="170" align="center" />
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+
+const grantCycleLabelMap = { total: '累计', monthly: '每月', quarterly: '每季度', yearly: '每年' }
+
+export default {
+  name: 'EquityGrantCampaign',
+  data() {
+    return {
+      searchForm: { name: '', dateRange: [], status: '' },
+      tableData: []
+    }
+  },
+  computed: {
+    filteredData() {
+      if (!this.searchForm.status) return this.tableData
+      return this.tableData.filter(r => this.statusLabel(r) === this.searchForm.status)
+    }
+  },
+  created() {
+    this.fetchList()
+  },
+  activated() {
+    this.fetchList()
+  },
+  watch: {
+    $route(to) {
+      if (to.path === '/customization/hsbc/equity-grant-campaign') {
+        this.fetchList()
+      }
+    }
+  },
+  methods: {
+    async fetchList() {
+      const params = { name: this.searchForm.name }
+      if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
+        params.startDate = this.searchForm.dateRange[0]
+        params.endDate = this.searchForm.dateRange[1]
+      }
+      const res = await request.get('/api/equity-grant-campaign/list', { params })
+      this.tableData = res.data || []
+    },
+    handleSearch() { this.fetchList() },
+    handleReset() {
+      this.searchForm = { name: '', dateRange: [], status: '' }
+      this.fetchList()
+    },
+    handleCreate() {
+      this.$router.push('/customization/hsbc/equity-grant-campaign/add')
+    },
+    handleEdit(row) {
+      this.$router.push(`/customization/hsbc/equity-grant-campaign/edit/${row.id}`)
+    },
+    handleView(row) {
+      this.$router.push(`/customization/hsbc/equity-grant-campaign/view/${row.id}`)
+    },
+    async handleEnd(row) {
+      await this.$confirm(
+        `确定结束发放计划「${row.name}」吗?结束后将立即生效,且不再继续发放权益。`,
+        '结束发放计划',
+        { type: 'warning', confirmButtonText: '确认结束', cancelButtonText: '取消' }
+      )
+      await request.post('/api/equity-grant-campaign/end', { id: row.id })
+      this.$message.success('已结束')
+      this.fetchList()
+    },
+    async handleDelete(row) {
+      await this.$confirm('您确定要删除此条发放计划吗?删除后将无法恢复。', '确认删除', {
+        type: 'warning',
+        confirmButtonText: '确认删除',
+        cancelButtonText: '取消'
+      })
+      await request.post('/api/equity-grant-campaign/delete', { id: row.id })
+      this.$message.success('删除成功')
+      this.fetchList()
+    },
+    validitySummary(v) {
+      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 '-'
+    },
+    grantSummary(row) {
+      if (!row) return '-'
+      const cycle = grantCycleLabelMap[row.grantCycle] || '累计'
+      return `${cycle} ${row.grantQuantity || 0} 个`
+    },
+    statusLabel(row) {
+      const now = Date.now()
+      const s = new Date(row.startTime).getTime()
+      const e = new Date(row.endTime).getTime()
+      if (now < s) return '未开始'
+      if (now > e) return '已结束'
+      return '进行中'
+    },
+    statusTag(row) {
+      const l = this.statusLabel(row)
+      return { 未开始: 'info', 进行中: 'success', 已结束: '' }[l] || ''
+    }
+  }
+}
+</script>
+
+<style scoped>
+.search-section .search-row { margin-bottom: 0; }
+.search-section .action-row { margin-top: 15px; }
+.button-group { display: inline-block; margin-left: 20px; }
+</style>

+ 272 - 0
pc/src/views/EquityGrantCampaignEdit.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="equity-grant-campaign-edit">
+    <el-card>
+      <div slot="header" class="header">
+        <span class="title">{{ pageTitle }}</span>
+      </div>
+
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="rules"
+        label-width="150px"
+        style="max-width: 920px; margin: 0 auto"
+      >
+        <el-form-item v-if="isEdit" label="发放计划 ID">
+          <el-input :value="form.id" disabled style="width:320px" />
+        </el-form-item>
+        <el-form-item label="发放计划名称" prop="name">
+          <el-input
+            v-model="form.name"
+            placeholder="请输入发放计划名称"
+            maxlength="50"
+            show-word-limit
+            style="width:420px"
+          />
+        </el-form-item>
+        <el-form-item label="发放计划开始时间" prop="startTime">
+          <el-date-picker
+            v-model="form.startTime"
+            type="datetime"
+            placeholder="选择开始时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width:320px"
+          />
+        </el-form-item>
+        <el-form-item label="发放计划结束时间" prop="endTime">
+          <el-date-picker
+            v-model="form.endTime"
+            type="datetime"
+            placeholder="选择结束时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width:320px"
+          />
+        </el-form-item>
+        <el-form-item label="选择客群" prop="crowdList">
+          <el-select
+            v-model="form.crowdList"
+            multiple
+            filterable
+            collapse-tags
+            placeholder="请选择客群(可多选,支持搜索)"
+            style="width:100%"
+          >
+            <el-option
+              v-for="item in crowdOptions"
+              :key="item.id"
+              :label="`${item.name} (${item.id})`"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="选择权益" prop="equityList">
+          <el-select
+            v-model="form.equityList"
+            multiple
+            filterable
+            collapse-tags
+            placeholder="请选择借记卡权益(可多选)"
+            style="width:100%"
+          >
+            <el-option
+              v-for="item in equityOptions"
+              :key="item.productId"
+              :label="`${item.equityName} (${item.productId})`"
+              :value="item.productId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发放数量" required>
+          <div class="grant-row">
+            <el-select v-model="form.grantCycle" placeholder="类型" style="width:140px">
+              <el-option label="累计" value="total" />
+              <el-option label="每月" value="monthly" />
+              <el-option label="每季度" value="quarterly" />
+              <el-option label="每年" value="yearly" />
+            </el-select>
+            <el-input-number
+              v-model="form.grantQuantity"
+              :min="1"
+              :max="9999"
+              :controls-position="'right'"
+              style="width:200px;margin-left:12px"
+            />
+            <span class="grant-suffix">个</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="权益有效期" required>
+          <el-radio-group v-model="form.validity.type">
+            <el-radio label="fixed">固定日期</el-radio>
+            <el-radio label="relative">相对日期</el-radio>
+          </el-radio-group>
+          <div v-if="form.validity.type === 'fixed'" class="validity-row">
+            <el-date-picker
+              v-model="form.validity.fixedRange"
+              type="daterange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              value-format="yyyy-MM-dd"
+              style="width:360px"
+            />
+          </div>
+          <div v-else class="validity-row">
+            <el-input-number
+              v-model="form.validity.relativeDays"
+              :min="1"
+              :max="3650"
+              :controls-position="'right'"
+              style="width:200px"
+            />
+            <span class="grant-suffix">天(自发放起算)</span>
+          </div>
+        </el-form-item>
+      </el-form>
+
+      <div class="footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">保存</el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+import crowdNameListStore from '@/store/crowdNameListStore'
+
+function emptyForm() {
+  return {
+    id: '',
+    name: '',
+    startTime: '',
+    endTime: '',
+    crowdList: [],
+    equityList: [],
+    grantCycle: 'total',
+    grantQuantity: 1,
+    validity: { type: 'fixed', fixedRange: [], relativeDays: 30 }
+  }
+}
+
+export default {
+  name: 'EquityGrantCampaignEdit',
+  data() {
+    return {
+      form: emptyForm(),
+      equityOptions: [],
+      rules: {
+        name: [{ required: true, message: '请输入发放计划名称', trigger: 'blur' }],
+        startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
+        crowdList: [
+          { type: 'array', required: true, message: '请至少选择一个客群', trigger: 'change' }
+        ],
+        equityList: [
+          { type: 'array', required: true, message: '请至少选择一个权益', trigger: 'change' }
+        ]
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return this.$route.name === 'EquityGrantCampaignEdit'
+    },
+    pageTitle() {
+      return this.isEdit ? '编辑发放计划' : '新建发放计划'
+    },
+    crowdOptions() {
+      return crowdNameListStore.list
+    }
+  },
+  async created() {
+    await this.fetchEquityOptions()
+    if (this.isEdit) await this.loadDetail()
+  },
+  methods: {
+    async fetchEquityOptions() {
+      const res = await request.get('/api/equity/list', { params: { shelfStatus: '已上架' } })
+      this.equityOptions = res.data || []
+    },
+    async loadDetail() {
+      const id = this.$route.params.id
+      const res = await request.get('/api/equity-grant-campaign/detail', { params: { id } })
+      const row = res.data
+      if (!row) {
+        this.$message.error('发放计划不存在')
+        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) {
+        this.$message.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,
+        startTime: row.startTime,
+        endTime: row.endTime,
+        crowdList: [...(row.crowdList || [])],
+        equityList: [...(row.equityList || [])],
+        grantCycle: row.grantCycle || 'total',
+        grantQuantity: row.grantQuantity || 1,
+        validity: {
+          type: v.type || 'fixed',
+          fixedRange: Array.isArray(v.fixedRange) ? [...v.fixedRange] : [],
+          relativeDays: v.relativeDays || 30
+        }
+      }
+    },
+    handleSubmit() {
+      this.$refs.form.validate(async valid => {
+        if (!valid) return
+        if (this.form.startTime && this.form.endTime && this.form.startTime >= this.form.endTime) {
+          this.$message.warning('结束时间必须晚于开始时间')
+          return
+        }
+        const v = this.form.validity
+        if (v.type === 'fixed') {
+          if (!v.fixedRange || v.fixedRange.length !== 2) {
+            this.$message.warning('请选择权益有效期的开始与结束日期')
+            return
+          }
+        } else if (!v.relativeDays || v.relativeDays < 1) {
+          this.$message.warning('请输入权益有效期的天数')
+          return
+        }
+        if (!this.form.grantQuantity || this.form.grantQuantity < 1) {
+          this.$message.warning('请输入发放数量')
+          return
+        }
+        await request.post('/api/equity-grant-campaign/save', this.form)
+        this.$message.success(this.isEdit ? '保存成功' : '新建成功')
+        this.$router.push('/customization/hsbc/equity-grant-campaign')
+      })
+    },
+    handleCancel() {
+      this.$router.push('/customization/hsbc/equity-grant-campaign')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.equity-grant-campaign-edit { padding: 0; }
+.header { display: flex; align-items: center; justify-content: center; }
+.title { font-size: 16px; font-weight: 600; color: #409EFF; }
+.grant-row { display: flex; align-items: center; }
+.grant-suffix { margin-left: 8px; color: #606266; }
+.validity-row { margin-top: 8px; display: flex; align-items: center; }
+.footer {
+  text-align: center;
+  margin-top: 24px;
+  padding-top: 16px;
+  border-top: 1px solid #ebeef5;
+}
+.footer .el-button { min-width: 100px; }
+</style>

+ 140 - 0
pc/src/views/EquityGrantCampaignView.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="equity-grant-campaign-view">
+    <el-card v-if="current">
+      <div slot="header" class="header">
+        <span class="title">查看发放计划</span>
+      </div>
+
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="发放计划 ID">{{ current.id }}</el-descriptions-item>
+        <el-descriptions-item label="发放计划名称">{{ current.name }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="statusTag" size="small">{{ statusLabel }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="发放数量">{{ grantSummary }}</el-descriptions-item>
+        <el-descriptions-item label="开始时间">{{ current.startTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="结束时间">{{ current.endTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="权益有效期" :span="2">{{ validitySummary }}</el-descriptions-item>
+        <el-descriptions-item label="选择客群" :span="2">
+          <template v-if="(current.crowdList || []).length">
+            <el-tag
+              v-for="cid in current.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="(current.equityList || []).length">
+            <el-tag
+              v-for="pid in current.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="创建时间">{{ current.createTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ current.updateTime || '-' }}</el-descriptions-item>
+      </el-descriptions>
+
+      <div class="footer">
+        <el-button type="primary" @click="goBack">返回</el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+import crowdNameListStore from '@/store/crowdNameListStore'
+
+const grantCycleLabelMap = { total: '累计', monthly: '每月', quarterly: '每季度', yearly: '每年' }
+
+export default {
+  name: 'EquityGrantCampaignView',
+  data() {
+    return { current: null, equityOptions: [] }
+  },
+  computed: {
+    crowdOptions() {
+      return crowdNameListStore.list
+    },
+    statusLabel() {
+      if (!this.current) return ''
+      const now = Date.now()
+      const s = new Date(this.current.startTime).getTime()
+      const e = new Date(this.current.endTime).getTime()
+      if (now < s) return '未开始'
+      if (now > e) return '已结束'
+      return '进行中'
+    },
+    statusTag() {
+      return { 未开始: 'info', 进行中: 'success', 已结束: '' }[this.statusLabel] || ''
+    },
+    grantSummary() {
+      if (!this.current) return '-'
+      const cycle = grantCycleLabelMap[this.current.grantCycle] || '累计'
+      return `${cycle} ${this.current.grantQuantity || 0} 个`
+    },
+    validitySummary() {
+      const v = this.current && this.current.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 '-'
+    }
+  },
+  async created() {
+    await this.fetchEquityOptions()
+    await this.loadDetail()
+  },
+  methods: {
+    async fetchEquityOptions() {
+      const res = await request.get('/api/equity/list', { params: { shelfStatus: '已上架' } })
+      this.equityOptions = res.data || []
+    },
+    async loadDetail() {
+      const id = this.$route.params.id
+      const res = await request.get('/api/equity-grant-campaign/detail', { params: { id } })
+      this.current = res.data
+      if (!this.current) {
+        this.$message.error('发放计划不存在')
+        this.$router.replace('/customization/hsbc/equity-grant-campaign')
+      }
+    },
+    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
+    },
+    goBack() {
+      this.$router.push('/customization/hsbc/equity-grant-campaign')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.equity-grant-campaign-view { padding: 0; }
+.header { display: flex; align-items: center; justify-content: center; }
+.title { font-size: 16px; font-weight: 600; color: #409EFF; }
+.footer {
+  text-align: center;
+  margin-top: 24px;
+  padding-top: 16px;
+  border-top: 1px solid #ebeef5;
+}
+</style>

+ 163 - 1
pc/src/views/PageZoneConfig.vue

@@ -15,6 +15,86 @@
       </div>
     </div>
 
+    <!-- 客户身份模块配置 -->
+    <el-card class="section-card" shadow="hover">
+      <div slot="header" class="section-header">
+        <span><i class="el-icon-postcard"></i> 客户身份模块配置</span>
+      </div>
+      <el-alert
+        type="info"
+        :closable="false"
+        show-icon
+        title="前端将根据登录客户的实际客群 (MSC) 自动匹配展示以下对应的身份卡片内容。若关闭“显示账户月内日均余额”开关,则对应的计算规则弹窗配置项将自动隐藏,前端也不会展示。"
+        style="margin-bottom:16px"
+      />
+
+      <div v-for="m in identityMscList" :key="m.key" class="identity-card">
+        <div class="identity-title"><span class="title-bar"></span>{{ m.label }}</div>
+
+        <!-- 客群背景图 -->
+        <div class="identity-row">
+          <div class="identity-field-label">客群背景图</div>
+          <div class="bg-upload-area">
+            <div class="bg-upload-box" @click="triggerIdentityUpload(m.key)">
+              <img v-if="configData.identityModule[m.key].bgImage" :src="configData.identityModule[m.key].bgImage" class="preview-img" />
+              <div v-else class="upload-placeholder">
+                <i class="el-icon-picture"></i>
+                <span>未上传</span>
+              </div>
+              <div class="upload-overlay">
+                <i class="el-icon-upload2"></i>
+                <span>点击上传</span>
+              </div>
+            </div>
+            <div class="upload-tip">建议图片尺寸:<b>750 × 185 px</b></div>
+            <input :ref="'identityUpload_' + m.key" type="file" accept="image/*" style="display:none" @change="onIdentityImageUpload($event, m.key)" />
+          </div>
+        </div>
+
+        <!-- 身份标识文字 + 右侧开关 -->
+        <div class="identity-row identity-text-row">
+          <div class="identity-field">
+            <div class="identity-field-label">身份标识文字 <span class="muted">(显示于移动端卡片左上角)</span></div>
+            <el-input
+              v-model="configData.identityModule[m.key].identityText"
+              maxlength="20"
+              placeholder="请输入"
+              style="width:360px"
+              @input="markModified"
+            />
+          </div>
+          <div class="identity-toggles">
+            <template v-if="m.key !== 'privateBank'">
+              <el-checkbox v-model="configData.identityModule[m.key].showQualifyStatus" @change="markModified">显示合格状态 (及编号)</el-checkbox>
+              <el-checkbox v-model="configData.identityModule[m.key].showAvgBalance" @change="markModified">显示“账户月内日均余额”</el-checkbox>
+            </template>
+            <template v-else>
+              <el-checkbox v-model="configData.identityModule.privateBank.showPbLevel" @change="markModified">显示PB权益等级及编号 (支持复制)</el-checkbox>
+              <div class="pb-hint">(PB客群不显示月内日均余额)</div>
+            </template>
+          </div>
+        </div>
+
+        <!-- 日均余额计算规则说明 -->
+        <div
+          v-if="m.key !== 'privateBank' && configData.identityModule[m.key].showAvgBalance"
+          class="rule-block"
+        >
+          <div class="rule-label">
+            <i class="el-icon-question"></i>
+            日均余额计算规则说明 <span class="muted">(前端点击问号展示)</span>
+          </div>
+          <el-input
+            v-model="configData.identityModule[m.key].avgBalanceRule"
+            type="textarea"
+            :rows="3"
+            placeholder="请输入计算规则说明"
+            @input="markModified"
+          />
+        </div>
+      </div>
+    </el-card>
+
     <!-- 活动广告轮播图 -->
     <el-card class="section-card" shadow="hover">
       <div slot="header" class="section-header">
@@ -213,10 +293,17 @@ export default {
   data() {
     return {
       configData: {
+        identityModule: this.defaultIdentityModule(),
         adCarousel: [],
         exchangeConfig: { items: [] },
         ruleModal: { title: '', content: '' }
       },
+      identityMscList: [
+        { key: 'advance', label: '运筹理财 (Advance)' },
+        { key: 'premier', label: '卓越理财 (Premier)' },
+        { key: 'premierElite', label: '卓越理财·尊尚 (Premier Elite)' },
+        { key: 'privateBank', label: '环球私人银行 (Private Bank)' }
+      ],
       availableTags: [],
       pageStatus: 'draft',
       dataLoaded: false,
@@ -259,13 +346,39 @@ export default {
     this.fetchData()
   },
   methods: {
+    defaultIdentityModule() {
+      const base = { bgImage: '', identityText: '', showQualifyStatus: false, showAvgBalance: true, avgBalanceRule: '' }
+      return {
+        advance: { ...base },
+        premier: { ...base },
+        premierElite: { ...base },
+        privateBank: { bgImage: '', identityText: '', showPbLevel: true }
+      }
+    },
     async fetchData() {
       const res = await request.get('/api/page-config/zone')
-      this.configData = res.data.config
+      const cfg = res.data.config || {}
+      if (!cfg.identityModule) cfg.identityModule = this.defaultIdentityModule()
+      this.configData = cfg
       this.availableTags = res.data.availableTags
       this.pageStatus = res.data.status
       this.$nextTick(() => { this.dataLoaded = true })
     },
+    triggerIdentityUpload(key) {
+      const ref = this.$refs['identityUpload_' + key]
+      if (ref) (Array.isArray(ref) ? ref[0] : ref).click()
+    },
+    onIdentityImageUpload(event, key) {
+      const file = event.target.files[0]
+      if (!file) return
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.$set(this.configData.identityModule[key], 'bgImage', e.target.result)
+        this.markModified()
+      }
+      reader.readAsDataURL(file)
+      event.target.value = ''
+    },
     markModified() {
       if (!this.dataLoaded) return
       if (this.pageStatus === 'published') {
@@ -426,4 +539,53 @@ export default {
 .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
 .dot-green { background: #67C23A; }
 .dot-blue { background: #409EFF; }
+
+/* 客户身份模块配置 */
+.identity-card {
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  background: #fff;
+}
+.identity-title {
+  display: flex; align-items: center;
+  font-size: 14px; font-weight: 600; color: #303133;
+  margin-bottom: 14px;
+}
+.title-bar {
+  display: inline-block; width: 4px; height: 14px;
+  background: #409EFF; border-radius: 2px; margin-right: 8px;
+}
+.identity-row { margin-bottom: 14px; }
+.identity-field-label {
+  font-size: 13px; color: #606266; margin-bottom: 6px;
+}
+.muted { color: #909399; font-weight: normal; font-size: 12px; }
+.bg-upload-area {
+  display: flex; align-items: center; gap: 16px;
+}
+.bg-upload-box {
+  width: 300px; height: 75px;
+  background: #eef6fc; border: 1px solid #dcdfe6; border-radius: 4px;
+  position: relative; overflow: hidden; cursor: pointer;
+  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
+}
+.bg-upload-box:hover .upload-overlay { display: flex; }
+.identity-text-row {
+  display: flex; align-items: flex-start; justify-content: space-between; gap: 24px;
+}
+.identity-field { flex: 1; }
+.identity-toggles {
+  display: flex; flex-direction: column; gap: 6px; padding-top: 22px;
+}
+.pb-hint { font-size: 12px; color: #909399; margin-top: 4px; }
+.rule-block {
+  background: #f5f7fa; border: 1px solid #ebeef5; border-radius: 4px;
+  padding: 12px 14px;
+}
+.rule-label {
+  font-size: 13px; color: #606266; margin-bottom: 8px;
+}
+.rule-label i { color: #409EFF; margin-right: 4px; }
 </style>

+ 1 - 1
pc/src/views/ViewEquityProduct.vue

@@ -303,7 +303,7 @@
                 <template v-if="form.claimGroupEnabled">
                   <div style="width: 600px;background: #f1f2f5;padding: 20px;margin-top: 10px;">
                     <el-alert
-                      title="需在【权益管理-权益发放】中选择权益发放,未发放的权益移动端不展示"
+                      title="需在【发放计划】中选择发放计划,未发放的权益移动端不展示"
                       type="info"
                       :closable="false"
                       show-icon