Переглянути джерело

chore: add the mobile market contract dependency closure.

zlwu 1 місяць тому
батько
коміт
0927a514d2

+ 18 - 0
mobile-oa-h5/src/market/module-registry.js

@@ -0,0 +1,18 @@
+import freight from './modules/freight.js'
+import paymentChase from './modules/payment-chase.js'
+import payment from './modules/payment.js'
+import presign from './modules/presign.js'
+import protocol from './modules/protocol.js'
+import sample from './modules/sample.js'
+
+const MODULES = Object.freeze({ presign, protocol, 'payment-chase': paymentChase, payment, sample, freight })
+
+export function getMarketModule(module) {
+  const value = MODULES[module]
+  if (!value) throw new Error('市场页面配置无效')
+  return value
+}
+
+export function getMarketListParams(module, filters) {
+  return getMarketModule(module).listParams?.(filters) || {}
+}

+ 21 - 0
mobile-oa-h5/src/market/modules/freight.js

@@ -0,0 +1,21 @@
+export default {
+  key: 'freight',
+  resource: 'freights',
+  actions: {
+    specialist: ['view', 'create', 'edit', 'link', 'unlink'],
+    manager: ['view', 'create', 'edit', 'link', 'unlink']
+  },
+  listParams: ({ status } = {}) => ({ transportMode: status || '' }),
+  statusOptions: [{ text: '全部运输方式', value: '' }],
+  usesPagedSampleOptions(action) {
+    return ['create', 'link'].includes(action)
+  },
+  sampleOptions(action, detailItems, optionRows) {
+    if (action === 'unlink') return Array.isArray(detailItems) ? detailItems : []
+    return (Array.isArray(optionRows) ? optionRows : []).filter((item) => !item.freightId)
+  },
+  formSections: [{ key: 'base', title: '运费批次', fields: [
+    { key: 'transportMode', label: '运输方式', type: 'select', optionKey: 'transportModes', required: true },
+    { key: 'freightAmount', label: '运费金额', type: 'number', required: true }
+  ] }]
+}

+ 19 - 0
mobile-oa-h5/src/market/modules/payment-chase.js

@@ -0,0 +1,19 @@
+export default {
+  key: 'payment-chase',
+  resource: 'payment-chase',
+  readOnly: true,
+  actions: { specialist: ['view-payment'], manager: ['view-payment'] },
+  listParams: ({ status } = {}) => ({ chaseType: status || '' }),
+  statusOptions: [
+    { text: '全部催缴', value: '' },
+    { text: '已逾期', value: 'overdue' },
+    { text: '未来 30 天', value: 'upcoming' }
+  ],
+  detailTarget(item) {
+    return {
+      module: 'payment',
+      id: String(item?.samplePaymentId || ''),
+      query: item?.scheduleId ? { scheduleId: String(item.scheduleId) } : {}
+    }
+  }
+}

+ 28 - 0
mobile-oa-h5/src/market/modules/payment.js

@@ -0,0 +1,28 @@
+export default {
+  key: 'payment',
+  resource: 'payments',
+  actions: {
+    specialist: ['view', 'register-transfer', 'edit-transfer', 'submit', 'withdraw'],
+    manager: ['view', 'register-transfer', 'edit-transfer', 'submit', 'withdraw']
+  },
+  listParams: ({ status } = {}) => ({ payMode: status || '' }),
+  statusOptions: [{ text: '全部缴费模式', value: '' }],
+  initialForm(detail, target = {}) {
+    const transfer = (detail?.schedules || [])
+      .flatMap((row) => row.transfers || [])
+      .find((row) => String(row.id || '') === String(target.transferId || '')) || {}
+    const fields = ['transferAmount', 'transferTime', 'transferType', 'transferAttachments', 'remark']
+    return {
+      transfer: Object.fromEntries(fields
+        .filter((key) => Object.prototype.hasOwnProperty.call(transfer, key))
+        .map((key) => [key, transfer[key]]))
+    }
+  },
+  transferFields: [
+    { key: 'transferAmount', label: '转账金额', type: 'number', required: true },
+    { key: 'transferTime', label: '转账时间', type: 'datetime' },
+    { key: 'transferType', label: '转账方式', type: 'select', optionKey: 'transferTypes', required: true },
+    { key: 'transferAttachments', label: '转账凭证', type: 'upload' },
+    { key: 'remark', label: '备注', type: 'textarea' }
+  ]
+}

+ 42 - 0
mobile-oa-h5/src/market/modules/presign.js

@@ -0,0 +1,42 @@
+import { commonFields } from './shared.js'
+
+export default {
+  key: 'presign',
+  resource: 'presign-orders',
+  actions: {
+    specialist: ['view', 'create', 'edit', 'convert'],
+    manager: ['view', 'create', 'edit', 'convert']
+  },
+  listParams: ({ status } = {}) => ({ orderStatus: status || '' }),
+  statusOptions: [
+    { text: '全部状态', value: '' },
+    { text: '预签约', value: 'yqy' },
+    { text: '已转正式', value: 'yzs' }
+  ],
+  formSections: [
+    {
+      key: 'base', title: '预签信息', fields: [
+        { key: 'orderDate', label: '预签日期', type: 'date' },
+        { key: 'protocolNo', label: '协议编号' },
+        { key: 'motherName', label: '客户姓名', required: true },
+        { key: 'expectedDeliveryDate', label: '预产期', type: 'date' },
+        { key: 'regionId', label: '采集地区', type: 'select', optionKey: 'regions' },
+        { key: 'hospitalId', label: '采集医院', type: 'select', optionKey: 'hospitals' },
+        { key: 'sampleCollectTime', label: '采集时间', type: 'datetime' },
+        { key: 'payMode', label: '缴费模式' },
+        { key: 'sampleTypeIds', label: '样本类型', type: 'multi-select', optionKey: 'sampleTypes' },
+        { key: 'contractAmount', label: '合同金额', type: 'number' },
+        { key: 'remark', ...commonFields.remark }
+      ]
+    },
+    {
+      key: 'transfers', title: '意向金', repeat: true, createOnly: true, fields: [
+        { key: 'transferAmount', label: '转账金额', type: 'number' },
+        { key: 'transferTime', label: '转账时间', type: 'datetime' },
+        { key: 'transferType', label: '转账方式' },
+        { key: 'transferAttachments', label: '转账附件', type: 'upload' },
+        { key: 'remark', ...commonFields.remark }
+      ]
+    }
+  ]
+}

+ 78 - 0
mobile-oa-h5/src/market/modules/protocol.js

@@ -0,0 +1,78 @@
+import { section } from './shared.js'
+
+const labels = {
+  protocolNo: '协议编号', donationNo: '捐赠编号', registerDate: '登记日期',
+  donationDate: '捐赠日期', motherName: '母亲姓名', motherPhone: '联系电话',
+  regionName: '采集地区', hospitalName: '采集医院', sampleCode: '样本编码',
+  payMode: '缴费模式', protocolAttachments: '协议附件'
+}
+
+export default {
+  key: 'protocol',
+  resource: 'protocols',
+  actions: { specialist: ['view', 'create', 'edit'], manager: ['view', 'create', 'edit'] },
+  listParams: ({ status } = {}) => ({ source: status || '' }),
+  statusOptions: [
+    { text: '全部来源', value: '' },
+    { text: '细胞存储', value: 'cell_storage' },
+    { text: '科研捐赠', value: 'research_donation' }
+  ],
+  detailSections(detail) {
+    if (Array.isArray(detail?.sections)) return detail.sections
+    const values = [
+      section('protocol', '协议信息', detail?.protocol || detail?.basic || detail, labels),
+      section('collection', '采集信息', detail?.collection, labels),
+      section('customer', '客户信息', detail?.customer, labels),
+      section('samples', '样本信息', { samples: detail?.samples }, labels)
+    ]
+    if (detail?.source === 'cell_storage' || detail?.payment) {
+      values.push(section('payment', '缴费信息', detail?.payment, labels))
+    }
+    values.push(section('attachments', '附件信息', detail?.attachments, labels))
+    return values
+  },
+  formSections: [
+    { key: 'protocol', title: '协议信息', fields: [
+      { key: 'source', label: '业务来源', type: 'tabs', required: true, createOnly: true, options: [
+        { text: '细胞存储', value: 'cell_storage' }, { text: '科研捐赠', value: 'research_donation' }
+      ] },
+      { key: 'protocolNo', label: '协议编号', source: 'cell_storage' },
+      { key: 'donationNo', label: '捐赠编号', source: 'research_donation' },
+      { key: 'registerDate', label: '登记日期', type: 'date', source: 'cell_storage' },
+      { key: 'donationDate', label: '捐赠日期', type: 'date', source: 'research_donation' },
+      { key: 'protocolTotalAmount', label: '协议总金额', type: 'number', source: 'cell_storage' },
+      { key: 'remark', label: '备注', type: 'textarea' }
+    ] },
+    { key: 'collection', title: '采集信息', nested: true, fields: [
+      { key: 'regionId', label: '采集地区', type: 'select', optionKey: 'regions' },
+      { key: 'hospitalId', label: '采集医院', type: 'select', optionKey: 'hospitals' },
+      { key: 'sampleCollectTime', label: '采集时间', type: 'datetime' }
+    ] },
+    { key: 'customer', title: '客户信息', nested: true, fields: [
+      { key: 'motherName', label: '母亲姓名', required: true },
+      { key: 'motherPhone', label: '联系电话', type: 'tel' },
+      { key: 'motherIdCard', label: '证件号码' },
+      { key: 'expectedDeliveryDate', label: '预产期', type: 'date' }
+    ] },
+    { key: 'samples', title: '样本信息', repeat: true, fields: [
+      { key: 'sampleTypeId', label: '样本类型', type: 'select', optionKey: 'sampleTypes' },
+      { key: 'sampleCode', label: '样本编码' },
+      { key: 'storageYears', label: '存储年限', type: 'number', source: 'cell_storage' }
+    ] },
+    { key: 'payment', title: '缴费信息(只读)', nested: true, readOnly: true, editOnly: true, source: 'cell_storage', fields: [
+      { key: 'payMode', label: '缴费模式', readOnly: true },
+      { key: 'contractAmount', label: '合同金额', readOnly: true },
+      { key: 'remark', label: '缴费备注', readOnly: true }
+    ] },
+    { key: 'attachments', title: '附件与确认', nested: true, fields: [
+      { key: 'protocolAttachments', label: '协议附件', type: 'upload', source: 'cell_storage' },
+      { key: 'consentFormUrls', label: '知情同意书', type: 'upload' },
+      { key: 'healthSurveyFormUrls', label: '健康调查表', type: 'upload', source: 'cell_storage' },
+      { key: 'attachmentUrls', label: '其他附件', type: 'upload', source: 'research_donation' },
+      { key: 'infectiousDiseaseReportUrls', label: '传染病报告', type: 'upload', source: 'research_donation' }
+    ] },
+    { key: 'confirm', title: '提交确认', nested: true, uiOnly: true, fields: [
+      { key: 'confirmed', label: '我已核对以上信息', type: 'checkbox', required: true }
+    ] }
+  ]
+}

+ 30 - 0
mobile-oa-h5/src/market/modules/sample.js

@@ -0,0 +1,30 @@
+export default {
+  key: 'sample',
+  resource: 'samples',
+  readOnlySources: ['research_donation'],
+  actions: { specialist: ['view', 'storage-cert', 'insurance'], manager: ['view', 'storage-cert', 'insurance'] },
+  listParams: ({ status } = {}) => ({ status: status || '' }),
+  statusOptions: [{ text: '全部状态', value: '' }],
+  formOptionsParams(sampleId) {
+    return { sampleId: String(sampleId || '').trim() }
+  },
+  initialForm(detail, action) {
+    const handles = (detail?.sections || []).find((section) => section.key === 'market-handles') || {}
+    const handle = action === 'insurance' ? handles.insurance || {} : handles.storageCert || {}
+    const fields = ['handleTime', 'handlerUserId', 'attachments', 'remark']
+    return {
+      handle: Object.fromEntries(fields
+        .filter((key) => Object.prototype.hasOwnProperty.call(handle, key))
+        .map((key) => [key, handle[key]]))
+    }
+  },
+  detailSections(detail) {
+    return Array.isArray(detail?.sections) ? detail.sections : []
+  },
+  handleFields: [
+    { key: 'handleTime', label: '办理时间', type: 'datetime' },
+    { key: 'handlerUserId', label: '办理人员', type: 'select', optionKey: 'users' },
+    { key: 'attachments', label: '附件', type: 'upload' },
+    { key: 'remark', label: '备注', type: 'textarea' }
+  ]
+}

+ 13 - 0
mobile-oa-h5/src/market/modules/shared.js

@@ -0,0 +1,13 @@
+export function objectFields(source, labels = {}) {
+  return Object.entries(source || {})
+    .filter(([, value]) => value != null && typeof value !== 'object')
+    .map(([key, value]) => ({ label: labels[key] || key, value }))
+}
+
+export function section(key, title, value, labels) {
+  return { key, title, fields: objectFields(value, labels), raw: value }
+}
+
+export const commonFields = {
+  remark: { label: '备注', type: 'textarea' }
+}

+ 108 - 0
mobile-oa-h5/src/market/routes.js

@@ -0,0 +1,108 @@
+export const MARKET_MODULES = [
+  'presign',
+  'protocol',
+  'payment-chase',
+  'payment',
+  'sample',
+  'freight'
+]
+
+export const MARKET_ROLES = ['specialist', 'manager']
+
+export const MARKET_MODULE_TITLES = Object.freeze({
+  presign: '预签约订单',
+  protocol: '协议单',
+  'payment-chase': '催缴信息',
+  payment: '缴费信息',
+  sample: '样本信息',
+  freight: '运费信息'
+})
+
+export const MARKET_ROLE_TITLES = Object.freeze({
+  specialist: '市场专员',
+  manager: '地区经理'
+})
+
+function isMarketRole(value) {
+  return MARKET_ROLES.includes(value)
+}
+
+function isMarketModule(value) {
+  return MARKET_MODULES.includes(value)
+}
+
+export function getMarketRouteContext(route) {
+  const marketRole = route?.meta?.marketRole
+  const module = route?.meta?.module
+  if (!isMarketRole(marketRole) || !isMarketModule(module)) {
+    throw new Error('市场页面配置无效')
+  }
+  return { marketRole, module }
+}
+
+export function shouldLoadMarketListAfterDetail({ previousDetailId, detailId }) {
+  return Boolean(String(previousDetailId || '').trim()) && !String(detailId || '').trim()
+}
+
+export function pickMarketNavigationQuery(query) {
+  const returnPath = String(query?.returnPath || '').trim()
+  return returnPath.startsWith('/pages/') && !returnPath.startsWith('//')
+    ? { returnPath }
+    : {}
+}
+
+export function marketRecordNavigationQuery(module, item, query) {
+  const result = pickMarketNavigationQuery(query)
+  const source = String(item?.source || '').trim()
+  if (module === 'protocol' && ['cell_storage', 'research_donation'].includes(source)) {
+    result.source = source
+  }
+  return result
+}
+
+export function resolveMarketSavedDetailId(module, currentId, result, action) {
+  const current = String(currentId || '').trim()
+  if (module === 'freight' && action === 'unlink' && result?.disappeared === true) return ''
+  if (module === 'payment' && current) return current
+  return String(result?.id || current).trim()
+}
+
+const listComponent = () => import('../views/market/MarketModuleList.vue')
+const detailComponent = () => import('../views/market/MarketModuleDetail.vue')
+const formComponent = () => import('../views/market/MarketModuleForm.vue')
+
+export const marketRoutes = MARKET_ROLES.flatMap((marketRole) =>
+  MARKET_MODULES.map((module) => {
+    const meta = {
+      marketRole,
+      module,
+      title: MARKET_MODULE_TITLES[module]
+    }
+    return {
+      path: `/market/${marketRole}/${module}`,
+      name: `market-${marketRole}-${module}`,
+      component: listComponent,
+      meta,
+      children: [
+        {
+          path: 'new',
+          name: `market-${marketRole}-${module}-new`,
+          component: formComponent,
+          meta: { ...meta, formMode: 'create' }
+        },
+        {
+          path: ':id/edit',
+          name: `market-${marketRole}-${module}-edit`,
+          component: formComponent,
+          meta: { ...meta, formMode: 'edit' }
+        },
+        {
+          path: ':id',
+          name: `market-${marketRole}-${module}-detail`,
+          component: detailComponent,
+          meta: { ...meta, detail: true }
+        }
+      ]
+    }
+  })
+)