Selaa lähdekoodia

信用卡权益:调整显示顺序改为输入排序值,编辑新增白名单客群多选

- 调整显示顺序弹窗:拖拽改为表格 + 操作列【调整顺序】,点击输入目标序号;
  排序成功后自动关闭浮窗,输入框默认空、打开即自动聚焦(teleported=false 规避弹窗焦点陷阱)
- 信用卡权益编辑:新增【白名单客群】多选字段,选项取自客群任务库(crowdTaskStore)
- 同步其余权益/券码原型与需求文档的在途更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
csk 1 viikko sitten
vanhempi
commit
c00dfd89d1
23 muutettua tiedostoa jossa 255 lisäystä ja 380 poistoa
  1. BIN
      .resources/image-1782119847775-1782226941099.png
  2. BIN
      .resources/image-1782203227828.png
  3. BIN
      .resources/image-1782205619361.png
  4. BIN
      .resources/image-1782205632153.png
  5. BIN
      .resources/image-1782205666065.png
  6. BIN
      .resources/image-1782205707784.png
  7. BIN
      .resources/image-1782205717590.png
  8. BIN
      .resources/image-1782205745564.png
  9. BIN
      .resources/image-1782205780642.png
  10. BIN
      .resources/image-1782205865905.png
  11. BIN
      .resources/image-1782206270660.png
  12. BIN
      .resources/image-1782206300420.png
  13. 2 12
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/CouponBatchPicker.vue
  14. 107 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/EquityOrderDialog.vue
  15. 27 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/ShowPagesDialog.vue
  16. 0 12
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/mock/index.js
  17. 36 19
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CouponBatchList.vue
  18. 36 91
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CreditCardEquity.vue
  19. 32 91
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/EquityProducts.vue
  20. 9 3
      工作空间/产研/权益/汇丰全行/需求文档/券码管理优化-20260622.md
  21. 1 85
      工作空间/产研/权益/汇丰全行/需求文档/权益介绍页移动端.md
  22. 4 36
      工作空间/产研/权益/汇丰全行/需求文档/汇丰借记卡移动端——权益.md
  23. 1 31
      工作空间/产研/权益/汇丰全行/需求文档/组装页移动端.md

BIN
.resources/image-1782119847775-1782226941099.png


BIN
.resources/image-1782203227828.png


BIN
.resources/image-1782205619361.png


BIN
.resources/image-1782205632153.png


BIN
.resources/image-1782205666065.png


BIN
.resources/image-1782205707784.png


BIN
.resources/image-1782205717590.png


BIN
.resources/image-1782205745564.png


BIN
.resources/image-1782205780642.png


BIN
.resources/image-1782205865905.png


BIN
.resources/image-1782206270660.png


BIN
.resources/image-1782206300420.png


+ 2 - 12
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/CouponBatchPicker.vue

@@ -13,10 +13,9 @@
           <el-form-item label="批次ID:">
             <el-input v-model="search.batchId" placeholder="精准匹配" style="width: 220px" />
           </el-form-item>
-          <div class="button-group" style="margin-left: 12px;">
+          <el-form-item>
             <el-button type="primary" @click="loadData">查询</el-button>
-            <el-button @click="resetSearch">重置</el-button>
-          </div>
+          </el-form-item>
         </el-form>
       </div>
       <el-table :data="pagedData" stripe style="width: 100%">
@@ -81,10 +80,6 @@ export default {
         this.currentPage = 1
       }).catch(() => {})
     },
-    resetSearch() {
-      this.search = { batchId: '' }
-      this.loadData()
-    },
     choose(row) {
       this.$emit('update:modelValue', row.batchId)
       this.dialogVisible = false
@@ -118,11 +113,6 @@ export default {
   margin-bottom: 8px;
 }
 
-.button-group {
-  display: flex;
-  gap: 5px;
-}
-
 .pagination-section {
   display: flex;
   justify-content: flex-end;

+ 107 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/EquityOrderDialog.vue

@@ -0,0 +1,107 @@
+<template>
+  <el-dialog v-model="visible" title="调整显示顺序" width="560px" @open="syncList">
+    <div class="order-tip">移动端权益首页和列表页按照该顺序展示,点击【调整顺序】输入目标序号</div>
+    <el-table v-if="list.length" :data="list" border size="small" max-height="360">
+      <el-table-column label="序号" type="index" width="64" align="center" />
+      <el-table-column label="权益名称" prop="equityName" min-width="160" show-overflow-tooltip />
+      <el-table-column label="产品ID" prop="productId" min-width="140" />
+      <el-table-column label="操作" width="120" align="center">
+        <template #default="scope">
+          <el-popover
+            placement="bottom"
+            trigger="click"
+            width="200"
+            :teleported="false"
+            :visible="sortVisibleIndex === scope.$index"
+            @update:visible="val => onSortToggle(scope.$index, val)"
+            @after-enter="focusSortInput"
+          >
+            <div>
+              <div style="margin-bottom:8px;font-size:13px;color:#666">移动到序号:</div>
+              <div style="display:flex;gap:8px">
+                <el-input-number :ref="setSortInputRef" v-model="sortTarget" :min="1" :max="list.length" size="small" controls-position="right" style="width:110px" />
+                <el-button size="small" type="primary" @click="moveItem(scope.$index, sortTarget - 1)">确定</el-button>
+              </div>
+            </div>
+            <template #reference>
+              <el-button size="small" type="primary" link>调整顺序</el-button>
+            </template>
+          </el-popover>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-empty v-else description="暂无已上架且审核通过的权益" :image-size="80" />
+    <template #footer>
+      <el-button @click="visible = false">取消</el-button>
+      <el-button type="primary" :disabled="!list.length" @click="onConfirm">确定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'EquityOrderDialog',
+  props: {
+    modelValue: { type: Boolean, default: false },
+    items: { type: Array, default: () => [] }
+  },
+  emits: ['update:modelValue', 'confirm'],
+  data() {
+    return {
+      list: [],
+      sortTarget: null,
+      sortVisibleIndex: -1
+    }
+  },
+  computed: {
+    visible: {
+      get() {
+        return this.modelValue
+      },
+      set(val) {
+        this.$emit('update:modelValue', val)
+      }
+    }
+  },
+  methods: {
+    syncList() {
+      this.list = this.items.map(item => ({ ...item }))
+      this.sortTarget = null
+      this.sortVisibleIndex = -1
+    },
+    onSortToggle(index, visible) {
+      this.sortVisibleIndex = visible ? index : -1
+      if (visible) this.sortTarget = null
+    },
+    setSortInputRef(el) {
+      this.sortInputRef = el
+    },
+    focusSortInput() {
+      this.sortInputRef && this.sortInputRef.focus()
+    },
+    moveItem(from, to) {
+      if (!Number.isInteger(to) || to < 0 || to >= this.list.length || from === to) return
+      const item = this.list.splice(from, 1)[0]
+      this.list.splice(to, 0, item)
+      this.sortVisibleIndex = -1
+    },
+    onConfirm() {
+      this.$emit('confirm', this.list.map(item => item.productId))
+      this.visible = false
+    }
+  }
+}
+</script>
+
+<style scoped>
+.order-tip {
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.5;
+  padding: 8px 10px;
+  margin-bottom: 12px;
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+
+</style>

+ 27 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/ShowPagesDialog.vue

@@ -43,6 +43,23 @@
           </el-select>
         </div>
       </el-form-item>
+      <el-form-item label="白名单客群">
+        <el-select
+          v-model="form.whitelistCrowds"
+          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="权益名称" required>
         <el-input
           v-model="form.equityName"
@@ -85,6 +102,7 @@
 <script>
 import request from '@/utils/request'
 import { ElMessage } from 'element-plus'
+import crowdTaskStore from '@/store/crowdTaskStore'
 
 export default {
   name: 'ShowPagesDialog',
@@ -103,6 +121,7 @@ export default {
         showOnHome: '显示',
         showOnAssembly: '不显示',
         assemblyPages: [],
+        whitelistCrowds: [],
         equityName: '',
         listIcon: []
       }
@@ -112,6 +131,9 @@ export default {
     dialogVisible: {
       get() { return this.modelValue },
       set(val) { this.$emit('update:modelValue', val) }
+    },
+    crowdOptions() {
+      return crowdTaskStore.list
     }
   },
   methods: {
@@ -132,6 +154,9 @@ export default {
         this.form.assemblyPages = Array.isArray(this.row.assemblyPages)
           ? [...this.row.assemblyPages]
           : []
+        this.form.whitelistCrowds = Array.isArray(this.row.whitelistCrowds)
+          ? [...this.row.whitelistCrowds]
+          : []
         this.form.equityName = this.row.equityName || ''
         this.form.listIcon = this.row.equityIcon
           ? [{ name: '权益图标', url: this.row.equityIcon }]
@@ -141,6 +166,7 @@ export default {
         this.form.showOnHome = '显示'
         this.form.showOnAssembly = '不显示'
         this.form.assemblyPages = []
+        this.form.whitelistCrowds = []
         this.form.equityName = ''
         this.form.listIcon = []
       }
@@ -170,6 +196,7 @@ export default {
       this.$emit('confirm', {
         ...this.form,
         assemblyPages: [...this.form.assemblyPages],
+        whitelistCrowds: [...this.form.whitelistCrowds],
         equityIcon
       })
       this.dialogVisible = false

+ 0 - 12
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/mock/index.js

@@ -1994,21 +1994,9 @@ Mock.mock(/\/api\/coupon\/pending-edit/, 'post', (options) => {
   const p = pendingCouponList.find(x => x.batchId === body.batchId)
   if (!p) return { code: 404, data: null, msg: '记录不存在' }
   if (p.auditStatus !== 'rejected') return { code: 400, data: null, msg: '仅审核拒绝的记录可编辑' }
-  let productId = ''
-  let equityName = ''
-  if (body.productId) {
-    const equity = equityList.find(e => e.productId === body.productId)
-    if (!equity) return { code: 404, data: null, msg: '权益商品不存在' }
-    productId = equity.productId
-    equityName = equity.equityName
-  }
-  p.productId = productId
-  p.equityName = equityName
   p.auditStatus = 'pending'
   const audit = couponAuditList.find(a => a.type === 'import' && a.batchId === p.batchId)
   if (audit) {
-    audit.productId = productId
-    audit.productName = equityName
     audit.auditStatus = 'pending'
     audit.auditorName = ''
     audit.auditTime = ''

+ 36 - 19
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CouponBatchList.vue

@@ -220,21 +220,29 @@
     <el-dialog title="编辑待导入券码" v-model="editDialogVisible" width="520px" @closed="resetEditForm">
       <el-form :model="editForm" label-width="120px" label-position="left">
         <el-form-item label="批次ID">{{ editForm.batchId }}</el-form-item>
-        <el-form-item label="权益商品">
-          <el-select
-            v-model="editForm.productId"
-            filterable
-            clearable
-            placeholder="可输入权益名称/编号搜索(非必填)"
-            style="width: 100%"
+        <el-form-item label="券码文件" required>
+          <el-upload
+            action=""
+            :auto-upload="false"
+            :file-list="editForm.files"
+            :on-change="handleEditFileChange"
+            :on-remove="handleEditFileRemove"
+            :limit="1"
           >
-            <el-option
-              v-for="item in productOptions"
-              :key="item.productId"
-              :label="`${item.equityName}(${item.productId})`"
-              :value="item.productId"
-            ></el-option>
-          </el-select>
+            <el-button>
+              <el-icon><Upload /></el-icon>选择文件
+            </el-button>
+            <template #tip>
+              <div class="el-upload__tip">
+                <div>*只支持模板格式,请先下载模板再导入</div>
+                <div>*不能导入相同的券码</div>
+                <div>*最多支持50000条,若超过请分次导入</div>
+              </div>
+              <el-button type="text" @click="downloadTemplate">
+                <el-icon><Download /></el-icon>模板下载
+              </el-button>
+            </template>
+          </el-upload>
         </el-form-item>
       </el-form>
       <template #footer>
@@ -299,7 +307,7 @@ export default {
       },
       editForm: {
         batchId: '',
-        productId: ''
+        files: []
       },
       productOptions: []
     }
@@ -425,16 +433,25 @@ export default {
       }).catch(() => {})
     },
     openEdit(row) {
-      this.editForm = { batchId: row.batchId, productId: row.productId || '' }
+      this.editForm = { batchId: row.batchId, files: [] }
       this.editDialogVisible = true
     },
+    handleEditFileChange(file) {
+      this.editForm.files = [file]
+    },
+    handleEditFileRemove() {
+      this.editForm.files = []
+    },
     resetEditForm() {
-      this.editForm = { batchId: '', productId: '' }
+      this.editForm = { batchId: '', files: [] }
     },
     confirmEdit() {
+      if (!this.editForm.files.length) {
+        ElMessage.warning('请选择券码文件')
+        return
+      }
       request.post('/api/coupon/pending-edit', {
-        batchId: this.editForm.batchId,
-        productId: this.editForm.productId
+        batchId: this.editForm.batchId
       }).then(() => {
         ElMessage.success('已重新提交审核')
         this.editDialogVisible = false

+ 36 - 91
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CreditCardEquity.vue

@@ -14,6 +14,11 @@
             </div>
           </el-form>
         </div>
+        <div>
+          <el-button @click="openOrderDialog">
+            <el-icon><Sort /></el-icon>调整显示顺序
+          </el-button>
+        </div>
         </div>
       </template>
 
@@ -21,37 +26,6 @@
         <el-table-column label="操作" width="280" fixed="left">
           <template #default="scope">
             <div class="row-actions">
-              <el-popover
-                placement="bottom"
-                trigger="click"
-                width="240"
-                @show="onAdjustOrderShow(scope.$index)"
-                :ref="`adjustPopover-${scope.$index}`"
-              >
-                <div class="adjust-order-form">
-                  <div class="adjust-order-title">调整显示顺序</div>
-                  <div class="adjust-order-tip">移动端权益首页和列表页按照该顺序展示</div>
-                  <div class="adjust-order-input">
-                    <span>调整到第</span>
-                    <el-input-number
-                      v-model.number="adjustOrderPosition"
-                      :min="1"
-                      :max="tableData.length"
-                      size="small"
-                      :controls="false"
-                      style="width: 70px;margin: 0 6px;"
-                    />
-                    <span>位</span>
-                  </div>
-                  <div class="adjust-order-actions">
-                    <el-button size="small" @click="closeAdjustOrder(scope.$index)">取消</el-button>
-                    <el-button size="small" type="primary" @click="confirmAdjustOrder(scope.$index)">确定</el-button>
-                  </div>
-                </div>
-                <template #reference>
-                  <el-button v-if="scope.row.shelfStatus === '已上架'" type="text" size="small">调整显示顺序</el-button>
-                </template>
-              </el-popover>
               <el-button type="text" size="small" @click="openShowPagesDialog(scope.row)">编辑</el-button>
               <el-popover
                 placement="bottom"
@@ -116,17 +90,24 @@
       path-name="equity/credit"
       title="生成信用卡权益链接"
     ></equity-link-dialog>
+
+    <equity-order-dialog
+      v-model="orderDialogVisible"
+      :items="orderableItems"
+      @confirm="onOrderConfirm"
+    ></equity-order-dialog>
   </div>
 </template>
 
 <script>
 import ShowPagesDialog from '@/components/ShowPagesDialog.vue'
 import EquityLinkDialog from '@/components/EquityLinkDialog.vue'
+import EquityOrderDialog from '@/components/EquityOrderDialog.vue'
 import { ElMessage } from 'element-plus'
 
 export default {
   name: 'CreditCardEquity',
-  components: { ShowPagesDialog, EquityLinkDialog },
+  components: { ShowPagesDialog, EquityLinkDialog, EquityOrderDialog },
   data() {
     return {
       searchForm: {
@@ -136,7 +117,8 @@ export default {
       showPagesRow: null,
       linkDialogVisible: false,
       linkTargetId: '',
-      adjustOrderPosition: 1,
+      orderDialogVisible: false,
+      displayOrder: [],
       tableData: [
         {
           equityName: '白金卡机场贵宾厅',
@@ -148,7 +130,8 @@ export default {
           enabled: '不启用',
           showOnHome: '显示',
           showOnAssembly: '不显示',
-          assemblyPages: []
+          assemblyPages: [],
+          whitelistCrowds: []
         },
         {
           equityName: '钻石卡高尔夫体验',
@@ -160,7 +143,8 @@ export default {
           enabled: '不启用',
           showOnHome: '显示',
           showOnAssembly: '不显示',
-          assemblyPages: []
+          assemblyPages: [],
+          whitelistCrowds: []
         },
         {
           equityName: '金卡洗车服务',
@@ -172,7 +156,8 @@ export default {
           enabled: '不启用',
           showOnHome: '显示',
           showOnAssembly: '不显示',
-          assemblyPages: []
+          assemblyPages: [],
+          whitelistCrowds: []
         }
       ]
     }
@@ -193,6 +178,15 @@ export default {
         if (this.searchForm.equityName && !item.equityName.includes(this.searchForm.equityName)) return false
         return true
       })
+    },
+    orderableItems() {
+      const qualifying = this.tableData.filter(row => row.shelfStatus === '已上架')
+      if (!this.displayOrder.length) return qualifying
+      const rank = id => {
+        const i = this.displayOrder.indexOf(id)
+        return i === -1 ? Number.MAX_SAFE_INTEGER : i
+      }
+      return [...qualifying].sort((a, b) => rank(a.productId) - rank(b.productId))
     }
   },
   methods: {
@@ -225,6 +219,7 @@ export default {
         this.showPagesRow.showOnHome = values.showOnHome
         this.showPagesRow.showOnAssembly = values.showOnAssembly
         this.showPagesRow.assemblyPages = [...values.assemblyPages]
+        this.showPagesRow.whitelistCrowds = [...values.whitelistCrowds]
         this.showPagesRow.equityName = values.equityName
         this.showPagesRow.equityIcon = values.equityIcon
         ElMessage.success('已保存')
@@ -235,28 +230,12 @@ export default {
       if (!ref) return null
       return Array.isArray(ref) ? ref[0] : ref
     },
-    onAdjustOrderShow(index) {
-      this.adjustOrderPosition = index + 1
+    openOrderDialog() {
+      this.orderDialogVisible = true
     },
-    closeAdjustOrder(index) {
-      const adjustRef = this.getPopoverInstance(`adjustPopover-${index}`)
-      if (adjustRef) {
-        adjustRef.hide()
-      }
-    },
-    confirmAdjustOrder(index) {
-      const target = Number(this.adjustOrderPosition)
-      if (!target || target < 1 || target > this.tableData.length) {
-        ElMessage.warning('请输入有效的顺序位置')
-        return
-      }
-      const targetIndex = target - 1
-      if (targetIndex !== index) {
-        const moved = this.tableData.splice(index, 1)[0]
-        this.tableData.splice(targetIndex, 0, moved)
-      }
-      ElMessage.success('顺序已调整')
-      this.closeAdjustOrder(index)
+    onOrderConfirm(productIds) {
+      this.displayOrder = productIds
+      ElMessage.success('显示顺序已保存')
     }
   }
 }
@@ -333,38 +312,4 @@ export default {
   background: #f5f7fa;
   color: #409eff;
 }
-
-.adjust-order-form {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.adjust-order-title {
-  font-size: 14px;
-  font-weight: 500;
-  color: #303133;
-}
-
-.adjust-order-tip {
-  font-size: 12px;
-  color: #909399;
-  line-height: 1.5;
-  padding: 8px 10px;
-  background: #f5f7fa;
-  border-radius: 4px;
-}
-
-.adjust-order-input {
-  display: flex;
-  align-items: center;
-  font-size: 14px;
-  color: #606266;
-}
-
-.adjust-order-actions {
-  display: flex;
-  justify-content: flex-end;
-  gap: 8px;
-}
 </style>

+ 32 - 91
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/EquityProducts.vue

@@ -36,6 +36,9 @@
           <el-button type="primary" @click="createProduct">
             <el-icon><Plus /></el-icon>添加权益
           </el-button>
+          <el-button @click="openOrderDialog">
+            <el-icon><Sort /></el-icon>调整显示顺序
+          </el-button>
         </div>
         </div>
       </template>
@@ -63,37 +66,6 @@
               :ref="`morePopover-${scope.$index}`"
             >
               <div class="more-actions-menu">
-                <el-popover
-                  placement="left"
-                  trigger="click"
-                  width="240"
-                  @show="onAdjustOrderShow(scope.$index)"
-                  :ref="`adjustPopover-${scope.$index}`"
-                >
-                  <div class="adjust-order-form">
-                    <div class="adjust-order-title">调整显示顺序</div>
-                    <div class="adjust-order-tip">移动端权益首页和列表页按照该顺序展示</div>
-                    <div class="adjust-order-input">
-                      <span>调整到第</span>
-                      <el-input-number
-                        v-model.number="adjustOrderPosition"
-                        :min="1"
-                        :max="tableData.length"
-                        size="small"
-                        :controls="false"
-                        style="width: 70px;margin: 0 6px;"
-                      />
-                      <span>位</span>
-                    </div>
-                    <div class="adjust-order-actions">
-                      <el-button size="small" @click="closeAdjustOrder(scope.$index)">取消</el-button>
-                      <el-button size="small" type="primary" @click="confirmAdjustOrder(scope.$index)">确定</el-button>
-                    </div>
-                  </div>
-                  <template #reference>
-                    <div v-if="scope.row.shelfStatus === '已上架'" class="more-actions-item">调整显示顺序</div>
-                  </template>
-                </el-popover>
                 <div
                   class="more-actions-item"
                   @click="handleCopyFromMore(scope.row, scope.$index)"
@@ -183,6 +155,12 @@
         title="生成借记卡权益链接"
       ></equity-link-dialog>
 
+      <equity-order-dialog
+        v-model="orderDialogVisible"
+        :items="orderableItems"
+        @confirm="onOrderConfirm"
+      ></equity-order-dialog>
+
       <!-- 分页 -->
       <div class="pagination-section">
         <span>Total {{ tableData.length }}</span>
@@ -202,10 +180,11 @@
 import { ElMessage, ElMessageBox } from 'element-plus'
 import request from '@/utils/request'
 import EquityLinkDialog from '@/components/EquityLinkDialog.vue'
+import EquityOrderDialog from '@/components/EquityOrderDialog.vue'
 
 export default {
   name: 'EquityProducts',
-  components: { EquityLinkDialog },
+  components: { EquityLinkDialog, EquityOrderDialog },
   data() {
     return {
       searchForm: {
@@ -215,9 +194,23 @@ export default {
         auditStatus: ''
       },
       tableData: [],
-      adjustOrderPosition: 1,
       linkDialogVisible: false,
-      linkTargetId: ''
+      linkTargetId: '',
+      orderDialogVisible: false,
+      displayOrder: []
+    }
+  },
+  computed: {
+    orderableItems() {
+      const qualifying = this.tableData.filter(
+        row => row.shelfStatus === '已上架' && row.auditStatus === '审核通过'
+      )
+      if (!this.displayOrder.length) return qualifying
+      const rank = id => {
+        const i = this.displayOrder.indexOf(id)
+        return i === -1 ? Number.MAX_SAFE_INTEGER : i
+      }
+      return [...qualifying].sort((a, b) => rank(a.productId) - rank(b.productId))
     }
   },
   created() {
@@ -317,30 +310,12 @@ export default {
       if (!ref) return null
       return Array.isArray(ref) ? ref[0] : ref
     },
-    onAdjustOrderShow(index) {
-      this.adjustOrderPosition = index + 1
-      const moreRef = this.getPopoverInstance(`morePopover-${index}`)
-      if (moreRef) moreRef.hide()
-    },
-    closeAdjustOrder(index) {
-      const adjustRef = this.getPopoverInstance(`adjustPopover-${index}`)
-      if (adjustRef) adjustRef.hide()
-      const moreRef = this.getPopoverInstance(`morePopover-${index}`)
-      if (moreRef) moreRef.hide()
+    openOrderDialog() {
+      this.orderDialogVisible = true
     },
-    confirmAdjustOrder(index) {
-      const target = Number(this.adjustOrderPosition)
-      if (!target || target < 1 || target > this.tableData.length) {
-        ElMessage.warning('请输入有效的顺序位置')
-        return
-      }
-      const targetIndex = target - 1
-      if (targetIndex !== index) {
-        const moved = this.tableData.splice(index, 1)[0]
-        this.tableData.splice(targetIndex, 0, moved)
-      }
-      ElMessage.success('顺序已调整')
-      this.closeAdjustOrder(index)
+    onOrderConfirm(productIds) {
+      this.displayOrder = productIds
+      ElMessage.success('显示顺序已保存')
     },
     showLinkDialog(row) {
       this.linkTargetId = row.productId
@@ -447,38 +422,4 @@ export default {
 .more-actions-item-danger:hover {
   color: #f56c6c;
 }
-
-.adjust-order-form {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.adjust-order-title {
-  font-size: 14px;
-  font-weight: 500;
-  color: #303133;
-}
-
-.adjust-order-tip {
-  font-size: 12px;
-  color: #909399;
-  line-height: 1.5;
-  padding: 8px 10px;
-  background: #f5f7fa;
-  border-radius: 4px;
-}
-
-.adjust-order-input {
-  display: flex;
-  align-items: center;
-  font-size: 14px;
-  color: #606266;
-}
-
-.adjust-order-actions {
-  display: flex;
-  justify-content: flex-end;
-  gap: 8px;
-}
 </style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 9 - 3
工作空间/产研/权益/汇丰全行/需求文档/券码管理优化-20260622.md


+ 1 - 85
工作空间/产研/权益/汇丰全行/需求文档/权益介绍页移动端.md

@@ -88,15 +88,10 @@ flowchart TD
 编辑页由五部分组成:
 
 1.  顶部操作栏:页面状态、获取链接、保存草稿、发布上线。
-    
 2.  客户层级配置 Tab:卓越理财、卓越理财·尊尚、环球私人银行。
-    
 3.  Tab 名称配置。
-    
 4.  头部配置。
-    
 5.  按钮配置与权益列表配置。
-    
 
 客户层级 Tab:
 
@@ -118,11 +113,8 @@ flowchart TD
 保存范围:
 
 1.  后台保存或发布时提交 `premier`、`elite`、`global` 三档完整配置。
-    
 2.  不允许仅保存当前后台 Tab,避免其他等级配置被覆盖或丢失。
-    
 3.  任意字段修改后,如当前状态为已发布,需切换为“有未发布的修改”。
-    
 
 ### 2.3 获取页面链接
 
@@ -145,15 +137,10 @@ flowchart TD
 边界规则:
 
 1.  Campaign ID、Channel ID 清空时,不拼接对应参数。
-    
 2.  同时选择 Campaign ID、Channel ID 时,按 `CampaignId=xxx&Channelid=yyy` 拼接。
-    
 3.  草稿预览链接如同时存在投放参数,需保留 `draft=1`。
-    
 4.  复制链接成功后提示“链接已复制到剪贴板”。
-    
 5.  浏览器不支持剪贴板时,允许用户手动复制链接。
-    
 
 ### 2.4 Tab 名称配置
 
@@ -164,13 +151,9 @@ flowchart TD
 规则:
 
 1.  后台当前层级 Tab 的标题优先展示已配置的 Tab 名称。
-    
 2.  Tab 名称为空时,后台和移动端均使用默认名称兜底。
-    
 3.  Tab 名称过长时,移动端需单行展示并省略或自适应压缩,不得挤压其他 Tab。
-    
 4.  后台不支持新增、删除或调整三档 Tab 顺序。
-    
 
 ### 2.5 头部配置
 
@@ -185,24 +168,16 @@ flowchart TD
 移动端展示:
 
 1.  头图按移动端内容宽度铺满,保持视觉裁切,不得变形拉伸。
-    
 2.  头图为空或加载失败时,展示默认占位背景,不显示破图。
-    
 3.  主标题为空时不展示标题节点,副标题上移。
-    
 4.  副标题为空时不展示副标题节点。
-    
 5.  主标题、副标题过长时允许自然换行,但不得遮挡按钮或权益列表。
-    
 
 图片边界:
 
 1.  上传新图片后覆盖旧图片。
-    
 2.  图片超过后端限制大小时,上传失败并提示“图片大小超出限制,请重新上传”。
-    
 3.  图片格式限制以统一上传组件为准,建议支持 JPG、JPEG、PNG。
-    
 
 ### 2.6 按钮配置
 
@@ -225,15 +200,10 @@ flowchart TD
 交互规则:
 
 1.  已解锁按钮点击后,进入客户真实 MSC 对应的权益首页。
-    
 2.  已解锁按钮跳转目标不取当前查看 Tab;例如尊尚客户查看卓越 Tab,点击后仍进入尊尚客户权益首页。
-    
 3.  锁定按钮点击后,前端拦截跳转。
-    
 4.  如本期接入达标提示弹窗,点击锁定按钮后展示半屏达标提示弹窗;弹窗内容不由权益介绍页后台配置。
-    
 5.  连续点击按钮时需防重复跳转。
-    
 
 ### 2.7 权益列表配置
 
@@ -267,26 +237,17 @@ flowchart TD
 校验规则:
 
 1.  当某档“展示权益图片”开启时,该档所有权益项必须上传图片。
-    
 2.  任一权益缺图时,保存草稿和发布上线均阻断。
-    
 3.  缺图提示格式:“{Tab名称} 第 {序号} 个权益未上传图片,请补充后再保存。”
-    
 4.  缺图时后台自动切换到对应客户层级 Tab。
-    
 
 移动端展示边界:
 
 1.  标题为空且描述有值时,仅展示描述。
-    
 2.  描述为空且标题有值时,仅展示标题。
-    
 3.  标题和描述均为空时,该权益项不展示,避免出现空行。
-    
 4.  当前 Tab 无权益项时,移动端隐藏权益列表区域,保留头图、标题、副标题和按钮。
-    
 5.  权益图片加载失败时展示默认图标或圆点兜底,不显示破图。
-    
 
 ### 2.8 配置数据结构
 
@@ -322,13 +283,9 @@ flowchart TD
 数据规则:
 
 1.  `premier`、`elite`、`global` 为固定键,不允许运营新增或删除。
-    
 2.  `privilegeImageEnabled` 为当前层级列表级开关,不是单条权益字段。
-    
 3.  前台不应依赖后台返回客户等级权重,解锁关系需由前端或服务端统一维护。
-    
 4.  接口返回二期轮播字段时,本期前端忽略。
-    
 
 ## 三、移动端展示需求
 
@@ -358,19 +315,12 @@ flowchart TD
 页面结构:
 
 1.  页面导航栏:标题“权益介绍页”,左侧返回按钮。
-    
 2.  顶部固定入口:仅无三档等级客户展示。
-    
 3.  等级 Tab:卓越理财、卓越理财·尊尚、环球私人银行。
-    
 4.  当前 Tab 的头图。
-    
 5.  当前 Tab 的主标题、副标题。
-    
 6.  进入权益首页按钮。
-    
 7.  当前 Tab 的权益列表。
-    
 
 ### 3.3 客户等级与默认展示
 
@@ -388,13 +338,9 @@ flowchart TD
 边界规则:
 
 1.  客户等级读取失败或无法识别时,按无等级处理。
-    
 2.  无等级客户默认选中第一个 Tab,即卓越理财。
-    
 3.  切换 Tab 只改变当前查看内容,不改变客户真实等级。
-    
 4.  后台配置顺序不影响移动端展示顺序。
-    
 
 ### 3.4 顶部固定入口
 
@@ -416,35 +362,24 @@ flowchart TD
 交互规则:
 
 1.  点击后进入客户真实 MSC 对应的权益首页。
-    
 2.  对无三档等级客户,进入对应权益首页兜底版本。
-    
 3.  该入口不支持后台隐藏、改名或换图。
-    
 4.  跳转失败时提示“暂时无法查看,请稍后再试”。
-    
 
 ### 3.5 等级 Tab 与内容切换
 
 展示规则:
 
 1.  固定展示三档 Tab:卓越理财、卓越理财·尊尚、环球私人银行。
-    
 2.  Tab 名称读取后台 `tabName`,为空时使用默认名称。
-    
 3.  当前 Tab 需展示高亮文字和下划线。
-    
 
 交互规则:
 
 1.  支持点击 Tab 切换内容。
-    
 2.  如后续支持左右滑动切换,滑动结果需与点击 Tab 完全一致。
-    
 3.  切换后需刷新头图、主标题、副标题、按钮状态、权益列表。
-    
 4.  Tab 名称过长时需单行省略或自适应压缩。
-    
 
 ### 3.6 头部视觉区
 
@@ -459,13 +394,9 @@ flowchart TD
 边界规则:
 
 1.  头图为空或加载失败时展示默认占位。
-    
 2.  主标题为空时隐藏标题节点。
-    
 3.  副标题为空时隐藏副标题节点。
-    
 4.  文案过长时自然换行,不遮挡按钮和权益列表。
-    
 
 ### 3.7 按钮展示与跳转
 
@@ -481,22 +412,15 @@ flowchart TD
 展示规则:
 
 1.  已解锁状态展示后台 `buttons.unlocked`,为空时默认“进入权益专区”。
-    
 2.  锁定状态展示后台 `buttons.locked`,为空时默认“待解锁”。
-    
 3.  已解锁和锁定状态需有明显视觉区分。
-    
 
 点击规则:
 
 1.  已解锁按钮点击后,跳转至客户真实 MSC 对应的权益首页。
-    
 2.  当前正在查看的 Tab 不作为跳转目标。
-    
 3.  锁定按钮点击后不跳转,可展示达标提示弹窗。
-    
 4.  连续点击需防重复跳转。
-    
 
 ### 3.8 权益列表展示
 
@@ -512,17 +436,11 @@ flowchart TD
 展示规则:
 
 1.  权益列表按后台配置顺序展示。
-    
 2.  `privilegeImageEnabled=false` 时,展示圆点列表样式。
-    
 3.  `privilegeImageEnabled=true` 时,展示每条权益图片。
-    
 4.  图片加载失败时展示默认图标或圆点兜底。
-    
 5.  当前 Tab 无权益项时,隐藏权益列表区域。
-    
 6.  标题和描述均为空的权益项不展示。
-    
 
 ### 3.9 异常与兜底
 
@@ -575,6 +493,4 @@ flowchart TD
 | M12 | 空权益项 | 标题和描述均为空的权益项不展示 |
 | M13 | 空权益列表 | 当前 Tab 无权益项时隐藏列表区域 |
 | M14 | 二期轮播排除 | 页面不展示精选活动/活动轮播图 |
-| M15 | 异常提示 | 配置不存在、未发布、接口失败时展示错误或重试提示 |
-
-<br>
+| M15 | 异常提示 | 配置不存在、未发布、接口失败时展示错误或重试提示 |

+ 4 - 36
工作空间/产研/权益/汇丰全行/需求文档/汇丰借记卡移动端——权益.md

@@ -200,17 +200,13 @@
     
 -   **权益介绍:** 取后台"权益介绍"字段,非富文本,样式固定。图中卡片中权益名称下方内容
     
--   **权益有效期:** 取后台"权益有效期"字段
+-   **权益有效期:**
     
-    -   取发放计划字段:
+    -   权益平台兑换:【领取开始日期】-【领取结束日期】
         
-        -   固定日期:【开始日期】-【结束日期】
-            
-        -   相对日期:领取后 x 天内有效
-            
-    -   自定义:显示自定义输入的内容
+    -   非权益平台兑换:显示自定义输入的内容
         
--   **单客户权益数量(无领取限制则不显示):**x 次(整个周期)、月度 x 次、季度 x 次、年度 x 次
+-   **单客户权益数量:**x 次(整个周期)、月度 x 次、季度 x 次、年度 x 次
     
 -   **权益内容:** 取后台"权益内容"字段,富文本展示,图中卡片下方的内容。PDF链接需支持点击打开预览查看,不需要支持下载。
     
@@ -228,34 +224,6 @@
     
     <br>
     
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
-    <br>
-    
     ![|50%](../../../../../.resources/image-1776678923546.png)![|50%](../../../../../.resources/image-1776678942001.png)
 
 **(2)领取权益**

+ 1 - 31
工作空间/产研/权益/汇丰全行/需求文档/组装页移动端.md

@@ -7,13 +7,9 @@
 需求范围包括:
 
 -   移动端组装页:页面标题、微信分享、图片模块、图文模块、权益商品模块、按钮模块、底部弹窗、第三方跳转拦截提示。移动端运行环境包括 App 内 H5 和微信小程序内页面。
-    
 -   后台管理端:组装页列表、查询、分页、新建、编辑、保存草稿、发布上线、获取链接、模块新增/复制/删除/排序、微信分享卡片配置、各模块字段配置。
-    
 -   关联配置:借记卡/信用卡权益商品的“显示页面-组装页”选择,决定权益商品是否可在指定组装页中被动态展示。
-    
 -   接口范围:当前未提供正式接口说明,本文根据 PDF、PC Demo 与 H5 Demo 设计虚拟接口,均标记为【虚拟接口,待技术确认】。
-    
 
 ## 二、功能范围与模块结构
 
@@ -22,42 +18,26 @@
 组装页是可灵活配置的营销活动或线下沙龙引导落地页。页面内容由后台按模块顺序拼装,移动端在 App 内 H5 或微信小程序内按配置顺序自上而下展示。后台的链接配置项用于描述目标类型,选择 H5 时配置 H5 地址,选择小程序时配置 AppID 和页面路径;移动端再根据当前运行环境和端能力执行打开或跳转。
 
 -   顶部系统状态栏与导航栏:展示返回入口和页面标题。页面标题读取后台“组装页名称”,用于 Page Title 与导航栏标题回显。
-    
 -   微信分享能力:仅小程序环境生效。后台开启自定义微信分享后,前端注入分享标题、缩略图与当前页面链接;关闭时禁用分享菜单。
-    
 -   图片模块:作为页面头图或通栏广告图展示,移动端不支持点击。
-    
 -   图文模块:展示活动说明、正文、文字链接与外链图标。正文由后台富文本配置生成。后台可添加多个图文模块,移动端按排序多次出现时仍属于同一个功能模块,本文只描述一次完整规则。
-    
 -   权益商品模块:展示可配置大标题和当前客户有权限看到的权益商品列表。具体商品来自全局权益商品配置,并结合客户标签、发放资格和组装页展示配置动态筛选。
-    
 -   按钮模块:展示一个或多个操作按钮,支持红色主按钮和白色次按钮;点击动作支持直接跳转或先弹出底部提示弹窗。
-    
 -   底部提示弹窗:由按钮模块配置触发,展示弹窗标题、描述、取消按钮、确认按钮及确认按钮跳转配置。
-    
 -   第三方跳转拦截弹窗:当跳转目标识别为第三方页面时,在真正跳转前提示“您正在离开汇丰中国”,客户确认后才跳转。
-    
 
 ### 2.2 后台/管理端配置结构
 
 后台入口为“汇丰银行 / 页面管理 / 组装页”。后台通过列表和编辑页管理多个组装页。
 
 -   列表查询区:支持按页面 ID、页面名称筛选组装页。
-    
 -   列表区:展示操作、页面 ID、组装页名称、发布状态、创建时间、更新时间,并提供分页。发布状态包括“草稿”“待发布”“正式”。
-    
 -   新建/编辑页基础信息区:配置组装页名称,页面 ID 由系统保存后自动生成。
-    
 -   微信分享卡片配置区:控制是否启用自定义微信分享,并配置分享缩略图和分享标题。
-    
 -   模块配置区:支持图片模块、权益商品模块、图文模块、按钮模块的组合配置。
-    
 -   模块操作区:支持模块排序、复制、删除;权益商品模块每页仅允许一个,不支持复制。
-    
 -   按钮配置区:支持配置按钮文案、点击动作、按钮样式、跳转目标、弹窗内容和弹窗确认按钮跳转。后台配置的跳转目标可以是 H5 地址、小程序 AppID/页面路径,或固定目标“太古地产”“龙腾”。
-    
 -   获取链接弹窗:支持生成正式环境 H5 链接、小程序链接和草稿预览链接,并可选择 Campaign ID、Channel ID 拼接到链接参数。草稿状态仅生成草稿预览链接;待发布和正式状态可生成正式环境链接与草稿预览链接。
-    
 
 ### 2.3 前后台关系概览
 
@@ -404,21 +384,13 @@
 4.1.7 后台边界规则
 
 -   新建页面未保存时点击“获取链接”,提示“请先保存组装页再获取链接!”。
-    
 -   编辑页有未保存修改时点击返回或离开路由,弹出“当前有未保存修改,离开后修改内容将丢失,确定离开吗?”。
-    
 -   编辑页有未保存修改时点击“获取链接”,弹窗仍可打开,但提示“当前链接基于上一次保存内容,最新修改需保存后生效”。
-    
 -   草稿状态仅展示草稿预览链接;正式或已修改状态展示正式环境链接和草稿预览链接。
-    
 -   权益商品模块每页仅限一个;添加后底部“权益商品模块”新增按钮隐藏。
-    
 -   权益商品模块不支持复制。
-    
 -   模块排序、按钮排序、权益商品排序输入目标序号范围为 1 至当前列表长度,越界不移动。
-    
 -   删除模块需二次确认;删除按钮 Demo 未展示二次确认,建议补充确认规则。
-    
 
 ## 五、接口与字段说明
 
@@ -726,6 +698,4 @@ stateDiagram-v2
 | 9   | 草稿访问 | 草稿预览链接是否需要登录后台权限、是否可外部访问未明确。 | 预览安全、测试流程 | 产品、技术、安全 | 当前仅说明草稿预览用于内部测试 |
 | 10  | 发布确认 | PC Demo 发布上线未展示二次确认,但发布属于线上生效动作,是否需要确认弹窗未明确。 | 后台发布流程 | 产品  | 当前记录 Demo 行为,并建议补充确认 |
 | 11  | 删除能力 | PC Demo 中存在删除接口和方法,但列表操作列未展示删除按钮。是否需要组装页删除能力未明确。 | 后台列表操作、权限 | 产品  | 文档未作为列表显性按钮要求,仅在边界规则中保留 |
-| 12  | 移动端空页面 | 若组装页仅有权益商品模块且客户无可见权益,页面是否展示空白、失效页或其他兜底文案未明确。 | H5/小程序端体验 | 产品、设计 | 当前按“隐藏权益模块,其他模块正常展示”处理 |
-
-<br>
+| 12  | 移动端空页面 | 若组装页仅有权益商品模块且客户无可见权益,页面是否展示空白、失效页或其他兜底文案未明确。 | H5/小程序端体验 | 产品、设计 | 当前按“隐藏权益模块,其他模块正常展示”处理 |

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä