Просмотр исходного кода

fix(mobile-oa-h5): align presign and freight tags with registry mirror

Use info for draft; full payMode codes; receptionSource/transportType colors.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 недель назад
Родитель
Сommit
2b5f2f939c

+ 6 - 18
mobile-oa-h5/src/market/components/PresignTransferCard.vue

@@ -75,7 +75,6 @@
           v-if="can('submit')"
           type="primary"
           size="small"
-          icon="passed"
           :loading="busyAction === 'submit-transfer'"
           :disabled="busy"
           @click="$emit('submit-transfer', modelValue)"
@@ -85,7 +84,6 @@
           type="warning"
           plain
           size="small"
-          icon="revoke"
           :loading="busyAction === 'withdraw-transfer'"
           :disabled="busy"
           @click="$emit('withdraw-transfer', modelValue)"
@@ -97,7 +95,6 @@
         type="danger"
         plain
         size="small"
-        icon="delete-o"
         :disabled="busy"
         @click="$emit('remove', modelValue)"
       >删除</van-button>
@@ -110,6 +107,7 @@ import { computed } from 'vue'
 import OaDatePicker from '../../oa/components/OaDatePicker.vue'
 import OaMoneyField from '../../oa/components/OaMoneyField.vue'
 import { removeMarketAttachmentAt } from '../attachment-upload-state.js'
+import { resolveStatusTag, payStatus } from '../status-tag-registry.js'
 import PresignAttachmentUploader from './PresignAttachmentUploader.vue'
 import PresignSelectField from './PresignSelectField.vue'
 
@@ -142,21 +140,11 @@ const canDelete = computed(() => !props.disabled && (newRow.value || actionSet.v
 const busy = computed(() => Boolean(props.busyAction) || props.uploading)
 const showActions = computed(() => canDelete.value || can('submit') || can('withdraw'))
 
-const statusLabel = computed(() => ({
-  dfj: '已付款',
-  jfqrz: '付款确认中',
-  yjf: '付款完成',
-  tfqrz: '退费确认中',
-  ytf: '已退费'
-}[String(props.modelValue.payStatus || 'dfj')] || '已付款'))
-
-const statusTone = computed(() => ({
-  dfj: 'default',
-  jfqrz: 'warning',
-  yjf: 'success',
-  tfqrz: 'warning',
-  ytf: 'default'
-}[String(props.modelValue.payStatus || 'dfj')] || 'default'))
+const statusTag = computed(() =>
+  resolveStatusTag(payStatus, props.modelValue.payStatus || 'dfj')
+)
+const statusLabel = computed(() => statusTag.value.label)
+const statusTone = computed(() => statusTag.value.tagType)
 
 function can(action) {
   return !props.disabled && actionSet.value.has(action)

+ 22 - 6
mobile-oa-h5/src/market/list-presenter.js

@@ -5,7 +5,9 @@ import {
   payMode,
   protocolStatus,
   donationStatus,
-  paymentChaseStatus
+  paymentChaseStatus,
+  receptionSource,
+  transportType
 } from './status-tag-registry.js'
 
 const SEARCH_PLACEHOLDERS = Object.freeze({
@@ -298,10 +300,12 @@ function freightSampleBlock(row, index) {
   const title = sampleCode
     ? `样本 ${index + 1} · ${sampleCode}`
     : `样本 ${index + 1}`
+  const source = resolveStatusTag(receptionSource, row?.sampleSource)
   return {
     key: String(row?.id || row?.sampleId || `${sampleCode || 'sample'}-${index}`),
     title,
-    status: label(SOURCE_LABELS, row?.sampleSource),
+    status: source.label,
+    statusTone: source.tagType,
     fields: [
       field('协议号', row?.protocolNo, { wide: true }),
       field('母亲姓名', row?.motherName),
@@ -312,18 +316,30 @@ function freightSampleBlock(row, index) {
   }
 }
 
+function freightTransportDisplay(item) {
+  const code = String(item?.transportMode || '').trim()
+  const provided = String(item?.transportModeLabel || '').trim()
+  if (provided && provided !== code) return provided
+  if (!code && !provided) return '—'
+  if (!code) return provided
+  return resolveStatusTag(transportType, code).label
+}
+
 function freightCard(role, item) {
   const rows = Array.isArray(item.items) ? item.items : []
   const first = rows[0] || {}
-  const transportMode = label({}, item.transportModeLabel || item.transportMode)
-  const source = label(SOURCE_LABELS, first.sampleSource || item.sourceLabel)
+  const transportCode = String(item.transportMode || '').trim()
+  const transportTag = resolveStatusTag(transportType, transportCode)
+  const transportMode = freightTransportDisplay(item)
+  const source = resolveStatusTag(receptionSource, first.sampleSource || item.sourceLabel)
   const sampleCount = Number(item.visibleItemCount ?? rows.length) || 0
   return {
     title: text(item.freightNo),
     subtitle: `${transportMode} · ${money(item.freightAmount)}`,
-    status: source,
+    status: source.label,
+    statusTone: source.tagType,
     fields: [
-      field('运输方式', transportMode, { tag: true }),
+      field('运输方式', transportMode, { tag: true, tagType: transportTag.tagType }),
       field('运费金额', money(item.freightAmount), { money: true }),
       field('关联数', String(sampleCount)),
       field('创建人', personName(item.creatorName || item.createByName, item.createBy)),

+ 49 - 29
mobile-oa-h5/src/market/modules/freight.js

@@ -1,10 +1,11 @@
-const SOURCE_LABELS = Object.freeze({ cell_storage: '细胞存储', research_donation: '科研捐赠' })
+import { resolveStatusTag, receptionSource, transportType } from '../status-tag-registry.js'
 
-function dictionaryLabel(map, code, providedLabel) {
-  const label = String(providedLabel || '').trim()
+function transportDisplay(code, providedLabel) {
   const key = String(code || '').trim()
+  const label = String(providedLabel || '').trim()
   if (label && label !== key) return label
-  return map[key] || (key ? '未知' : '—')
+  if (!key) return '—'
+  return resolveStatusTag(transportType, key).label
 }
 
 function person(name, id) {
@@ -24,29 +25,39 @@ export default {
   key: 'freight',
   resource: 'freights',
   actions: {
-    specialist: ['view', 'create', 'edit', 'link', 'unlink'],
-    manager: ['view', 'create', 'edit', 'link', 'unlink']
+    specialist: ['view', 'create', 'edit', 'delete', 'link', 'unlink'],
+    manager: ['view', 'create', 'edit', 'delete', 'link', 'unlink']
   },
   listParams: ({ status } = {}) => ({ transportMode: status || '', includeItems: true }),
   statusOptions: [{ text: '全部运输方式', value: '' }],
-  filterFields: [
-    { key: 'transportMode', label: '运输方式', type: 'select', optionKey: 'transportModes' },
+  // 运费列表用搜索下方 van-dropdown-menu(对齐预签约),不走筛选弹层
+  filterFields: [],
+  inlineDropdownFilters: [
+    { key: 'transportMode', allText: '全部运输方式', optionKey: 'transportModes' },
     {
-      key: 'sampleSource', label: '样本来源', type: 'select', options: [
+      key: 'sampleSource',
+      allText: '全部样本来源',
+      options: [
         { text: '细胞存储', value: 'cell_storage' },
         { text: '科研捐赠', value: 'research_donation' }
       ]
-    },
-    { key: 'createTimeRange', label: '创建时间', type: 'date-range' }
+    }
   ],
   detailSections(detail) {
+    const transportCode = String(detail?.transportMode || '').trim()
+    const transportTag = resolveStatusTag(transportType, transportCode)
     return [
       {
         key: 'base',
         title: '运费信息',
         fields: [
           { label: '批次号', value: detail?.freightNo || '—' },
-          { label: '运输方式', value: dictionaryLabel({}, detail?.transportMode, detail?.transportModeLabel), tag: true, tagType: 'primary' },
+          {
+            label: '运输方式',
+            value: transportDisplay(transportCode, detail?.transportModeLabel),
+            tag: true,
+            tagType: transportTag.tagType
+          },
           { label: '运费金额', value: money(detail?.freightAmount) },
           { label: '关联数', value: String(detail?.visibleItemCount ?? detail?.items?.length ?? 0) },
           { label: '创建人', value: person(detail?.creatorName || detail?.createByName, detail?.createBy) },
@@ -60,19 +71,23 @@ export default {
         key: 'items',
         title: '关联样本',
         fields: [],
-        cards: (detail?.items || []).map((item, index) => ({
-          key: String(item?.id || `item-${index}`),
-          title: item?.sampleCode || `样本 ${index + 1}`,
-          statusText: dictionaryLabel(SOURCE_LABELS, item?.sampleSource),
-          fields: [
-            { label: '来源', value: dictionaryLabel(SOURCE_LABELS, item?.sampleSource) },
-            { label: '协议单号', value: item?.protocolNo || '—' },
-            { label: '样本编码', value: item?.sampleCode || '—' },
-            { label: '样本类型', value: item?.sampleTypeName || '—' },
-            { label: '母亲姓名', value: item?.motherName || '—' },
-            { label: '子女姓名', value: item?.newbornName || '—' }
-          ]
-        }))
+        cards: (detail?.items || []).map((item, index) => {
+          const source = resolveStatusTag(receptionSource, item?.sampleSource)
+          return {
+            key: String(item?.id || `item-${index}`),
+            title: item?.sampleCode || `样本 ${index + 1}`,
+            statusText: source.label,
+            statusTone: source.tagType,
+            fields: [
+              { label: '来源', value: source.label, tag: true, tagType: source.tagType },
+              { label: '协议单号', value: item?.protocolNo || '—' },
+              { label: '样本编码', value: item?.sampleCode || '—' },
+              { label: '样本类型', value: item?.sampleTypeName || '—' },
+              { label: '母亲姓名', value: item?.motherName || '—' },
+              { label: '子女姓名', value: item?.newbornName || '—' }
+            ]
+          }
+        })
       }
     ]
   },
@@ -83,8 +98,13 @@ export default {
     if (action === 'unlink') return Array.isArray(detailItems) ? detailItems : []
     return Array.isArray(optionRows) ? optionRows : []
   },
-  formSections: [{ key: 'base', title: '运费批次', fields: [
-    { key: 'transportMode', label: '运输方式', type: 'select', optionKey: 'transportModes', required: true },
-    { key: 'freightAmount', label: '运费金额', type: 'number', required: true }
-  ] }]
+  formSections: [{
+    key: 'base',
+    title: '运费批次',
+    fieldsLayout: 'grid-2',
+    fields: [
+      { key: 'transportMode', label: '运输方式', type: 'select', optionKey: 'transportModes', required: true },
+      { key: 'freightAmount', label: '运费金额', type: 'number', required: true }
+    ]
+  }]
 }

+ 10 - 11
mobile-oa-h5/src/market/modules/presign.js

@@ -6,14 +6,8 @@ import {
   presignStatusLabel,
   presignStatusTone
 } from '../presign/presign-presenter.js'
+import { resolveStatusTag, payMode, payStatus } from '../status-tag-registry.js'
 
-const paymentStatus = Object.freeze({
-  dfj: { label: '已付款', tone: 'default' },
-  jfqrz: { label: '付款确认中', tone: 'warning' },
-  yjf: { label: '付款完成', tone: 'success' },
-  tfqrz: { label: '退费确认中', tone: 'warning' },
-  ytf: { label: '已退费', tone: 'default' }
-})
 const transferTypeLabels = Object.freeze({
   wxzz: '微信转账', wx: '微信转账', wechat: '微信转账',
   zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账',
@@ -51,6 +45,12 @@ function displayValue(value) {
   return value && value !== '-' ? value : '—'
 }
 
+function payModeLabel(value) {
+  const key = String(value || '').trim()
+  if (!key) return '—'
+  return resolveStatusTag(payMode, key).label
+}
+
 export default {
   key: 'presign',
   resource: 'presign-orders',
@@ -85,13 +85,12 @@ export default {
   detailSections(detail) {
     const status = String(detail?.orderStatus || '')
     const transfers = (detail?.transfers || []).map((transfer, index) => {
-      const paymentCode = String(transfer?.payStatus || 'dfj')
-      const payment = paymentStatus[paymentCode] || { label: '未知', tone: 'default' }
+      const payment = resolveStatusTag(payStatus, transfer?.payStatus || 'dfj')
       return {
         ...transfer,
         title: `意向金转账 ${index + 1}`,
         statusLabel: payment.label,
-        statusTone: payment.tone,
+        statusTone: payment.tagType,
         transferAmount: formatPresignMoney(transfer?.transferAmount),
         transferTime: formatPresignDateTime(transfer?.transferTime)
       }
@@ -110,7 +109,7 @@ export default {
           { label: '采集专员', value: person(detail?.collectorUserName, detail?.collectorUserId) },
           { label: '采集经理', value: person(detail?.regionManagerUserName, detail?.regionManagerUserId) },
           { label: '采集时间', value: displayValue(formatPresignDateTime(detail?.sampleCollectTime)) },
-          { label: '缴费方式', value: dictionaryLabel({ fq: '分期', dj: '趸交' }, detail?.payMode) },
+          { label: '缴费方式', value: payModeLabel(detail?.payMode) },
           {
             label: '样本类型',
             value: Array.isArray(detail?.sampleTypeNames) && detail.sampleTypeNames.length

+ 3 - 2
mobile-oa-h5/src/market/presign/presign-list-model.js

@@ -1,4 +1,5 @@
 import { intersectAllowedActions } from '../action-policy.js'
+import { resolveStatusTag, payMode } from '../status-tag-registry.js'
 import {
   formatPresignDate,
   formatPresignDateTime,
@@ -9,7 +10,6 @@ import {
 } from './presign-presenter.js'
 
 const CARD_ACTIONS = new Set(['edit', 'convert', 'delete'])
-const PAY_MODE_LABELS = Object.freeze({ dj: '趸交', fq: '分期', fgd: '分改趸', djfq: '趸交+分期' })
 
 function displayText(value) {
   return String(value ?? '').trim() || '-'
@@ -21,7 +21,8 @@ function actionCode(action) {
 
 function payModeLabel(value) {
   const key = String(value || '').trim()
-  return PAY_MODE_LABELS[key] || (key ? '未知' : '-')
+  if (!key) return '-'
+  return resolveStatusTag(payMode, key).label
 }
 
 export function buildPresignListParams({ keyword = '', orderStatus = '', page = 1, pageSize = 10 } = {}) {

+ 5 - 8
mobile-oa-h5/src/market/presign/presign-presenter.js

@@ -1,9 +1,4 @@
-const PRESIGN_STATUS = Object.freeze({
-  draft: Object.freeze({ label: '草稿', tone: 'default' }),
-  active: Object.freeze({ label: '进行中', tone: 'warning' }),
-  converted: Object.freeze({ label: '已转正式', tone: 'success' }),
-  void: Object.freeze({ label: '已作废', tone: 'danger' })
-})
+import { resolveStatusTag, presignOrderStatus } from '../status-tag-registry.js'
 
 function validDateParts(year, month, day) {
   const date = new Date(Date.UTC(year, month - 1, day))
@@ -83,11 +78,13 @@ export function formatPresignMoney(value) {
 export function presignStatusLabel(value) {
   const code = String(value ?? '').trim()
   if (!code) return '-'
-  return PRESIGN_STATUS[code]?.label || '未知'
+  return resolveStatusTag(presignOrderStatus, code).label
 }
 
 export function presignStatusTone(value) {
-  return PRESIGN_STATUS[String(value ?? '').trim()]?.tone || 'default'
+  const code = String(value ?? '').trim()
+  if (!code) return 'info'
+  return resolveStatusTag(presignOrderStatus, code).tagType
 }
 
 export function presignCardIndex(page, pageSize, position) {

+ 2 - 0
mobile-oa-h5/src/market/presign/presign-presenter.test.js

@@ -40,6 +40,8 @@ test('uses the Web presign status labels and mobile semantic tones', () => {
   assert.equal(presignStatusLabel('active'), '进行中')
   assert.equal(presignStatusLabel('converted'), '已转正式')
   assert.equal(presignStatusLabel('void'), '已作废')
+  assert.equal(presignStatusTone('draft'), 'info')
+  assert.notEqual(presignStatusTone('draft'), 'default')
   assert.equal(presignStatusTone('converted'), 'success')
   assert.equal(presignStatusTone('void'), 'danger')
   assert.equal(presignStatusLabel('custom'), '未知')