vue3-element-plus-app-chu 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
1
  # Vue 3 + TypeScript + Element Plus
2
2
 
3
3
  基于 Vite 初始化的 Vue 3 项目,使用 TypeScript,并已全局注册 Element Plus。
4
+
5
+ ## 数字人民币头寸预报
6
+
7
+ 默认入口为数字人民币头寸预报,侧边菜单可切换至原有数字人民币授权页面;切换时保留各自列表状态。
8
+
9
+ - 列表页只保留筛选卡片和表格卡片;筛选条件压在一行,窗口过窄时才换行。
10
+ - 新增、修改、详情不使用弹窗,在当前路由内切换为独立页面,页面顶部提供“返回列表”按钮;返回后列表的筛选和分页状态不变。
11
+ - 支持交易日期、客户、交易对手、机构、金额范围及业务代码筛选,分页、详情、新增、修改和批量删除。
12
+ - 金额、账号及流水号使用字符串处理。业务状态与类型代码保留接口原值,待服务端提供字典后再接入中文选项。
13
+ - 导入、导出按钮保留在列表工具栏,文件由后端接口生成和解析,页面不再在前端拼装 CSV。
14
+ - 模块目录与授权页面保持一致:`api.ts` 接口与日期、金额、表单校验等共用工具,`typs.ts` 类型,`runtime.ts` 默认交易日期,`index.vue` 为入口,`components/` 放页面外壳和列表、新增修改、详情页面。
15
+ - 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
16
+
17
+ 本地后端的预报接口当前使用通用模拟响应,写入成功响应不代表真实持久化。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-element-plus-app-chu",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -1,5 +1,4 @@
1
- import { post, type ApiResponse } from '@chic/http'
2
- import type { AuthorizationTask } from './typs'
1
+ import baseHttpClient from "@chic/http"
3
2
 
4
3
  export default {
5
4
  /**
@@ -9,22 +8,12 @@ export default {
9
8
  * platfSeqNo 为平台流水号,taskSts 为任务状态(01 待审批、02 已通过、03 已拒绝)。
10
9
  * 日期范围:createTmBegin、createTmEnd 为创建日期起止值,格式为 YYYYMMDD。
11
10
  */
12
- queryListApi: (params: unknown) => post<ApiResponse<{
13
- // 当前页的授权任务记录。
14
- resultList: AuthorizationTask[]
15
- // 符合筛选条件的总记录数,接口可能返回字符串或数字。
16
- totalCount: string | number
17
- }>>('/TAM/0001/140507001', params),
11
+ queryListApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140507001", params),
18
12
  /**
19
13
  * 提交单笔数字人民币授权任务的审批结果。
20
14
  * @param params 审批参数:opType 固定为 authorize;platfSeqNo 为待审批任务的平台流水号。
21
15
  * 审批结果:approveFlag 为 02(通过)或 03(拒绝);rmk 为备注,拒绝时填写原因。
22
16
  * 审批身份:apprvrId、apprvrName 为审批人工号和姓名;apprUserId、userId 同样传审批人工号。
23
17
  */
24
- authorizeApi: (params: unknown) => post<ApiResponse<{
25
- // 业务处理状态,仅作返回字段;请求成功以 head.retCode 为准。
26
- bizSts: string
27
- // 审批结果状态,仅作返回字段,不参与请求成功判断。
28
- resultSts: string
29
- }>>('/TAM/0001/120507001', params),
18
+ authorizeApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/120507001", params)
30
19
  }
@@ -1,15 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
- import type { AuthorizationTask } from '../typs'
4
3
 
5
4
  // 父页面传入的弹窗状态、任务数据和操作权限。
6
5
  const props = defineProps<{
7
6
  // 是否显示详情弹窗。
8
7
  modelValue: boolean
9
8
  // 原始任务数据,用于判断审批状态和传递审批任务。
10
- task: AuthorizationTask | null
9
+ task: any
11
10
  // 父页面格式化后的金额、状态和时间,仅用于展示。
12
- displayValues: AuthorizationTask | null
11
+ displayValues: any
13
12
  // 页面正在查询或提交审批时,禁用详情中的审批入口。
14
13
  disabled?: boolean
15
14
  }>()
@@ -18,7 +17,7 @@ const emit = defineEmits<{
18
17
  // visible 为更新后的弹窗显隐状态。
19
18
  'update:modelValue': [visible: boolean]
20
19
  // task 为用户选择审批的原始任务。
21
- approve: [task: AuthorizationTask]
20
+ approve: [task: any]
22
21
  }>()
23
22
 
24
23
  // 详情分组配置;title 为分组标题,fields 每项为 [任务字段名, 展示名称]。
@@ -71,7 +70,7 @@ const canApprove = computed(() =>
71
70
  && !!props.task.platfSeqNo?.trim(),
72
71
  )
73
72
  /** 读取详情展示值,空值显示占位符;field 为任务字段名。 */
74
- const displayValue = (field: keyof AuthorizationTask) => props.displayValues?.[field]?.trim() || '—'
73
+ const displayValue = (field: string) => props.displayValues?.[field]?.trim() || '—'
75
74
 
76
75
  // 通知父页面打开审批弹窗,由父页面统一提交审批。
77
76
  const approveTask = () => {
@@ -83,13 +82,14 @@ const approveTask = () => {
83
82
  <el-dialog
84
83
  :model-value="modelValue"
85
84
  title="数字人民币授权详情"
85
+ style="--el-dialog-padding-primary: 10px; padding: 10px 16px; margin-bottom: 16px"
86
86
  width="min(1280px, calc(100vw - 32px))"
87
- top="5vh"
87
+ top="16px"
88
88
  append-to-body
89
89
  destroy-on-close
90
90
  @update:model-value="emit('update:modelValue', $event)"
91
91
  >
92
- <el-scrollbar max-height="68vh">
92
+ <el-scrollbar class="detail-scrollbar">
93
93
  <div v-if="task" class="detail-content">
94
94
  <!-- 交易摘要 -->
95
95
  <header class="detail-summary">
@@ -116,6 +116,7 @@ const approveTask = () => {
116
116
  v-for="[field, label] in group.fields"
117
117
  :key="field"
118
118
  class="detail-field"
119
+ :class="{ 'detail-field--remark': field === 'rmk' }"
119
120
  >
120
121
  <dt>{{ label }}</dt>
121
122
  <dd>{{ displayValue(field) }}</dd>
@@ -136,20 +137,28 @@ const approveTask = () => {
136
137
  </template>
137
138
 
138
139
  <style scoped>
139
- .detail-content { display: grid; gap: 14px; font-size: 13px; }
140
- .detail-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; padding: 18px; border-radius: 8px; background: var(--el-color-primary-light-9); overflow-wrap: anywhere; }
140
+ /* 为弹窗边距、标题和底部按钮预留空间,其余高度用于完整展示详情。 */
141
+ .detail-scrollbar :deep(.el-scrollbar__wrap) {
142
+ max-height: calc(100vh - 120px);
143
+ max-height: calc(100dvh - 120px);
144
+ }
145
+ .detail-content { display: grid; gap: 8px; font-size: 13px; line-height: 1.4; }
146
+ .detail-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; padding: 14px; border-radius: 8px; background: var(--el-color-primary-light-9); overflow-wrap: anywhere; }
141
147
  .detail-label,
142
148
  .detail-field dt { color: var(--el-text-color-secondary); font-size: 12px; }
143
149
  .detail-amount { display: block; margin-top: 4px; color: var(--el-color-primary-dark-2); font-size: 24px; font-variant-numeric: tabular-nums; line-height: 1.3; }
144
150
  .detail-status { display: block; margin-top: 6px; color: var(--el-text-color-primary); font-size: 15px; }
145
151
  .detail-reference { display: grid; grid-column: span 2; align-content: start; gap: 4px; }
146
- .detail-group { padding: 16px; border: 1px solid var(--el-border-color-lighter); border-radius: 8px; }
147
- .detail-group h3 { margin: 0 0 14px; padding-left: 8px; border-left: 3px solid var(--el-color-primary); color: var(--el-text-color-primary); font-size: 14px; line-height: 20px; }
148
- .detail-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin: 0; }
149
- .detail-field { min-width: 0; padding: 12px; border-radius: 6px; background: var(--el-fill-color-lighter); }
150
- .detail-field dd { margin: 6px 0 0; color: var(--el-text-color-primary); font-weight: 600; overflow-wrap: anywhere; white-space: pre-wrap; }
152
+ .detail-group { display: grid; grid-template-columns: 72px minmax(0, 1fr); align-items: start; gap: 8px; padding: 12px; border: 1px solid var(--el-border-color-lighter); border-radius: 8px; }
153
+ .detail-group h3 { margin: 4px 0 0; padding-left: 8px; border-left: 3px solid var(--el-color-primary); color: var(--el-text-color-primary); font-size: 14px; line-height: 20px; }
154
+ .detail-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin: 0; }
155
+ .detail-field { min-width: 0; padding: 8px 12px; border-radius: 6px; background: var(--el-fill-color-lighter); }
156
+ .detail-field--remark { grid-column: 1 / -1; }
157
+ .detail-field dd { margin: 2px 0 0; color: var(--el-text-color-primary); font-weight: 600; overflow-wrap: anywhere; white-space: pre-wrap; }
151
158
 
152
159
  @media (max-width: 1024px) {
160
+ .detail-group { grid-template-columns: minmax(0, 1fr); }
161
+ .detail-group h3 { margin-top: 0; }
153
162
  .detail-summary,
154
163
  .detail-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
155
164
  }
@@ -5,7 +5,10 @@ import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSett
5
5
  import AuthorizationDetailDialog from './components/AuthorizationDetailDialog.vue'
6
6
  import API from './api'
7
7
  import runtime from './runtime'
8
- import type { AuthorizationDateRange, AuthorizationFilters, AuthorizationTask, Decision } from './typs'
8
+ import type { AuthorizationFilters, AuthorizationQueryParams, AuthorizationTask } from './typs'
9
+
10
+ type Decision = 'approve' | 'reject'
11
+ type AmountSortOrder = 'ascending' | 'descending' | null
9
12
 
10
13
  // 初始查询条件;空字符串表示不限制该条件,重置时恢复此配置。
11
14
  const defaultFilters: AuthorizationFilters = {
@@ -16,7 +19,7 @@ const defaultFilters: AuthorizationFilters = {
16
19
  taskSts: '', // 任务状态;空值查询全部状态。
17
20
  }
18
21
  // 默认创建日期范围:[开始日期, 结束日期],格式为 YYYYMMDD,查询重置时使用。
19
- const defaultDates: AuthorizationDateRange = [
22
+ const defaultDates: [string, string] = [
20
23
  runtime.createTmBegin,
21
24
  runtime.createTmEnd,
22
25
  ]
@@ -38,9 +41,10 @@ const decisionActions = [
38
41
 
39
42
  // 查询表单、列表和分页状态。
40
43
  const filters = ref({ ...defaultFilters }) // 正在编辑的查询条件,点击查询后才生效。
41
- const dateRange = ref<AuthorizationDateRange | null>([...defaultDates]) // 当前选择的创建日期范围,格式为 YYYYMMDD,允许为空。
44
+ const dateRange = ref<[string, string] | null>([...defaultDates]) // 当前选择的创建日期范围,格式为 YYYYMMDD,允许为空。
42
45
  const showSearchBar = ref(true) // 筛选区是否显示,由表格工具栏切换。
43
46
  const rows = ref<AuthorizationTask[]>([]) // 当前页的原始授权任务列表。
47
+ const amountSortOrder = ref<AmountSortOrder>(null) // 当前页金额的排序方向,null 保留接口返回顺序。
44
48
  const pagination = reactive({ current: 1, size: 10, total: 0 }) // 当前页码、每页条数、符合查询条件的总条数。
45
49
  const loading = ref(false) // 列表请求是否进行中,同时控制表格和查询按钮的加载状态。
46
50
  let requestId = 0 // 最新列表请求的序号,用于忽略过期响应。
@@ -48,11 +52,11 @@ let disposed = false // 页面是否已卸载,避免未完成请求继续更
48
52
 
49
53
  // 详情保留原始任务,格式化数据单独用于展示。
50
54
  const detailDialogVisible = ref(false) // 详情弹窗是否显示。
51
- const detailTask = ref<AuthorizationTask | null>(null) // 当前查看详情的原始任务,未选择时为 null。
55
+ const detailTask = ref<any>(null) // 当前查看详情的原始任务,未选择时为 null。
52
56
 
53
57
  // 审批弹窗状态。
54
58
  const dialogVisible = ref(false) // 审批弹窗是否显示。
55
- const selectedTask = ref<AuthorizationTask | null>(null) // 本次待处理的任务,提交时读取平台流水号。
59
+ const selectedTask = ref<any>(null) // 本次待处理的任务,提交时读取平台流水号。
56
60
  const remark = ref('') // 用户输入的审批备注,通过时选填,拒绝时必填。
57
61
  const remarkError = ref('') // 拒绝原因的校验提示,空字符串表示没有错误。
58
62
  const pendingDecision = ref<Decision | null>(null) // 正在提交的操作:approve 通过、reject 拒绝;null 表示空闲。
@@ -64,7 +68,7 @@ const paginationOptions: PaginationOptions = { align: 'right', background: false
64
68
  // 提取异常提示;error 为捕获到的请求或处理异常。
65
69
  const getErrorMessage = (error: unknown) => error instanceof Error ? error.message : '请求失败,请稍后重试'
66
70
  // 判断 task 是否可审批:必须处于待审批状态,且平台流水号有效。
67
- const canApprove = (task?: AuthorizationTask | null) => task?.taskSts === '01' && !!task.platfSeqNo?.trim()
71
+ const canApprove = (task?: any) => task?.taskSts === '01' && !!task.platfSeqNo?.trim()
68
72
  // 将状态码 status 转为中文名称,未识别的状态保留原值。
69
73
  const formatStatus = (status?: string) => statusLabels[status || ''] || status || '未知'
70
74
  // 将接口时间 value 转为浏览器本地时间,空值显示横线,无效时间保留原文。
@@ -76,7 +80,10 @@ const formatDateTime = (value?: string) => {
76
80
 
77
81
  // 将金额字符串 value 拆成符号、整数和小数,避免转为浮点数后丢失精度。
78
82
  const parseAmount = (value?: string) => {
79
- const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(value?.trim() || '') // 匹配结果:1 为符号,2 为整数,3 为小数。
83
+ const text = String(value ?? '').trim()
84
+ // 仅去掉合法千分位分隔符,避免把格式错误的 1,23 当成正常金额。
85
+ const normalized = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text) ? text.replace(/,/g, '') : text
86
+ const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(normalized) // 匹配结果:1 为符号,2 为整数,3 为小数。
80
87
  if (!match || (!match[2] && !match[3])) return null
81
88
 
82
89
  return {
@@ -89,7 +96,7 @@ const parseAmount = (value?: string) => {
89
96
  // 格式化金额 value:先换算为整数分并四舍五入,再添加千分位,统一显示两位小数。
90
97
  const formatAmount = (value?: string) => {
91
98
  const amount = parseAmount(value) // 解析后的十进制金额,无法识别时为 null。
92
- if (!amount) return value?.trim() || '—'
99
+ if (!amount) return String(value ?? '').trim() || '—'
93
100
 
94
101
  const fraction = amount.fraction.padEnd(3, '0') // 至少保留三位小数,第三位用于判断进位。
95
102
  let cents = BigInt(amount.integer + fraction.slice(0, 2)) // 金额绝对值对应的整数分,使用 BigInt 保证精度。
@@ -101,11 +108,11 @@ const formatAmount = (value?: string) => {
101
108
  return `${sign}${integer}.${digits.slice(-2)}`
102
109
  }
103
110
 
104
- // 比较左侧金额 left 和右侧金额 right:补齐小数后按整数比较;无效金额在升序时排在最后。
105
- const compareAmounts = (left?: string, right?: string) => {
111
+ // 补齐小数后按精确整数比较;空值和无效金额在升序、降序中均排在最后。
112
+ const compareAmounts = (left: string | undefined, right: string | undefined, order: Exclude<AmountSortOrder, null>) => {
106
113
  const leftAmount = parseAmount(left) // 左侧金额的解析结果。
107
114
  const rightAmount = parseAmount(right) // 右侧金额的解析结果。
108
- if (!leftAmount && !rightAmount) return (left || '').localeCompare(right || '', 'zh-CN')
115
+ if (!leftAmount && !rightAmount) return 0
109
116
  if (!leftAmount) return 1
110
117
  if (!rightAmount) return -1
111
118
 
@@ -114,11 +121,21 @@ const compareAmounts = (left?: string, right?: string) => {
114
121
  const rightDigits = rightAmount.integer + rightAmount.fraction.padEnd(precision, '0') // 右侧金额按统一精度放大后的绝对值。
115
122
  const leftValue = BigInt(leftDigits) * (leftAmount.negative ? -1n : 1n) // 左侧带符号的精确整数。
116
123
  const rightValue = BigInt(rightDigits) * (rightAmount.negative ? -1n : 1n) // 右侧带符号的精确整数。
117
- if (leftValue < rightValue) return -1
118
- if (leftValue > rightValue) return 1
124
+ const direction = order === 'ascending' ? 1 : -1
125
+ if (leftValue < rightValue) return -direction
126
+ if (leftValue > rightValue) return direction
119
127
  return 0
120
128
  }
121
129
 
130
+ // 只排序当前页的副本,详情和审批继续使用原始任务;取消排序时恢复接口顺序。
131
+ const tableRows = computed(() => {
132
+ const order = amountSortOrder.value
133
+ return order ? [...rows.value].sort((left, right) => compareAmounts(left.txnAmt, right.txnAmt, order)) : rows.value
134
+ })
135
+ const changeSort = ({ prop, order }: { prop: string; order: AmountSortOrder }) => {
136
+ amountSortOrder.value = prop === 'txnAmt' ? order : null
137
+ }
138
+
122
139
  /**
123
140
  * 生成普通文本列,默认将空值显示为横线。
124
141
  * @param prop 接口字段名。
@@ -127,16 +144,33 @@ const compareAmounts = (left?: string, right?: string) => {
127
144
  * @param format 字段格式化函数,参数 value 为当前单元格原值。
128
145
  */
129
146
  const textColumn = (
130
- prop: keyof AuthorizationTask,
147
+ prop: string,
131
148
  label: string,
132
149
  width: number,
133
150
  format = (value?: string) => value?.trim() || '—',
134
- ): ColumnOption<AuthorizationTask> => ({
151
+ ): ColumnOption => ({
135
152
  // row 为当前行任务,按 prop 读取单元格值后交给 format 处理。
136
153
  prop, label, width, showOverflowTooltip: true, formatter: (row) => format(row[prop]),
137
154
  })
155
+
156
+ // 日期列按原始时间戳对当前页排序,空值和无效日期在升序时排在最后。
157
+ const dateColumn = (
158
+ prop: string,
159
+ label: string,
160
+ ): ColumnOption => ({
161
+ ...textColumn(prop, label, 180, formatDateTime),
162
+ sortable: true,
163
+ sortMethod: (left, right) => {
164
+ const leftTime = Date.parse(left[prop]?.trim() || '')
165
+ const rightTime = Date.parse(right[prop]?.trim() || '')
166
+ if (Number.isNaN(leftTime)) return Number.isNaN(rightTime) ? 0 : 1
167
+ if (Number.isNaN(rightTime)) return -1
168
+ return leftTime - rightTime
169
+ },
170
+ })
171
+
138
172
  // 表格完整列配置;字段顺序即展示顺序,任务状态和操作列固定在右侧。
139
- const tableColumns: ColumnOption<AuthorizationTask>[] = [
173
+ const tableColumns: ColumnOption[] = [
140
174
  { key: 'sequence', type: 'globalIndex', label: '序号', width: 80, align: 'center' },
141
175
  textColumn('platfSeqNo', '平台流水号', 190),
142
176
  textColumn('id', '任务ID', 180),
@@ -145,9 +179,7 @@ const tableColumns: ColumnOption<AuthorizationTask>[] = [
145
179
  textColumn('payeeName', '收款人名称', 160),
146
180
  {
147
181
  prop: 'txnAmt', label: '交易金额(元)', width: 180, align: 'right',
148
- useSlot: true, slotName: 'amount', sortable: true,
149
- // left、right 为参与排序的两条任务;只比较当前页金额。
150
- sortMethod: (left, right) => compareAmounts(left.txnAmt, right.txnAmt),
182
+ useSlot: true, slotName: 'amount', sortable: 'custom',
151
183
  },
152
184
  textColumn('payWltId', '付款钱包ID', 190),
153
185
  textColumn('authoOrgName', '授权机构名称', 180),
@@ -158,11 +190,11 @@ const tableColumns: ColumnOption<AuthorizationTask>[] = [
158
190
  textColumn('collSideAcctBeloOrgNo', '收款方账户所属机构编号', 210),
159
191
  textColumn('subBrchName', '落地支行名称', 180),
160
192
  textColumn('subBrchBankNo', '落地支行行号', 160),
161
- textColumn('txnTm', '交易发起时间', 180, formatDateTime),
162
- textColumn('createTm', '创建时间', 180, formatDateTime),
193
+ dateColumn('txnTm', '交易发起时间'),
194
+ dateColumn('createTm', '创建时间'),
163
195
  textColumn('apprvrId', '审批人ID', 130),
164
196
  textColumn('apprvrName', '审批人名称', 130),
165
- textColumn('approveTm', '审批时间', 180, formatDateTime),
197
+ dateColumn('approveTm', '审批时间'),
166
198
  textColumn('rmk', '审批备注', 220),
167
199
  { ...textColumn('taskSts', '任务状态', 100, formatStatus), fixed: 'right', align: 'center' },
168
200
  { key: 'actions', label: '操作', width: 80, fixed: 'right', align: 'center', useSlot: true },
@@ -185,7 +217,7 @@ const detailValues = computed(() => {
185
217
 
186
218
  // 读取表单并组装请求参数,接口日期格式为 YYYYMMDD。
187
219
  const getQueryFilters = () => ({
188
- opType: 'query', // 查询授权任务。
220
+ opType: 'query' as const, // 查询授权任务。
189
221
  authoOrgNo: filters.value.authoOrgNo.trim(), // 归属机构编号。
190
222
  collePayerName: filters.value.collePayerName.trim(), // 付款人名称。
191
223
  payeeName: filters.value.payeeName.trim(), // 收款人名称。
@@ -201,12 +233,12 @@ let appliedQuery = getQueryFilters()
201
233
  const loadList = async () => {
202
234
  if (disposed) return
203
235
  const currentRequestId = ++requestId // 本次请求序号,仅与最新序号相等时允许更新界面。
204
- const params = { ...appliedQuery, pageIndex: pagination.current, pageSize: pagination.size } // 本次查询条件和分页快照。
236
+ const params: AuthorizationQueryParams = { ...appliedQuery, pageIndex: pagination.current, pageSize: pagination.size } // 本次查询条件和分页快照。
205
237
  loading.value = true
206
238
  try {
207
239
  for (let attempt = 0; attempt < 2; attempt += 1) { // attempt:0 为首次查询,1 为页码修正后的补查。
208
240
  // head 为公共响应头,body 为包含列表及总条数的业务数据。
209
- const { head, body } = await API.queryListApi(params)
241
+ const { head, body } = await API.queryListApi(params) as any
210
242
  if (currentRequestId !== requestId) return
211
243
  if (head?.retCode !== '00000') {
212
244
  throw new Error(head?.retMsg || '查询失败')
@@ -257,13 +289,13 @@ const changePage = (current: number, size = pagination.size) => {
257
289
  }
258
290
 
259
291
  // 双击行查看详情;task 为当前行的原始授权任务。
260
- const openDetailDialog = (task: AuthorizationTask) => {
292
+ const openDetailDialog = (task: any) => {
261
293
  if (loading.value || pendingDecision.value) return
262
294
  detailTask.value = task
263
295
  detailDialogVisible.value = true
264
296
  }
265
297
  // 打开审批窗口;task 为列表或详情选中的任务,只允许处理待审批状态。
266
- const openDecisionDialog = (task: AuthorizationTask) => {
298
+ const openDecisionDialog = (task: any) => {
267
299
  if (!canApprove(task) || loading.value || pendingDecision.value) return
268
300
  detailDialogVisible.value = false
269
301
  selectedTask.value = task
@@ -301,7 +333,7 @@ const submitDecision = async (decision: Decision) => {
301
333
  rmk: remarkText, // 审批备注,拒绝时必须提供原因。
302
334
  apprUserId: approverId, // 公共审批用户标识,与审批人工号一致。
303
335
  userId: approverId, // 公共操作用户标识,与审批人工号一致。
304
- })
336
+ }) as any
305
337
  if (disposed) return
306
338
  // retCode 为 00000 即表示请求成功。
307
339
  if (head?.retCode !== '00000') {
@@ -373,7 +405,7 @@ onBeforeUnmount(() => {
373
405
  v-model:show-search-bar="showSearchBar"
374
406
  fill
375
407
  toolbar-layout="search,refresh,size,fullscreen,columns,settings"
376
- :data="rows"
408
+ :data="tableRows"
377
409
  :columns="tableColumns"
378
410
  :loading="loading"
379
411
  :pagination="pagination"
@@ -382,6 +414,7 @@ onBeforeUnmount(() => {
382
414
  empty-text="暂无符合条件的授权任务"
383
415
  @pagination:current-change="changePage"
384
416
  @pagination:size-change="(size) => changePage(1, size)"
417
+ @sort-change="changeSort"
385
418
  @refresh="loadList"
386
419
  @row-dblclick="openDetailDialog"
387
420
  >
@@ -478,14 +511,18 @@ onBeforeUnmount(() => {
478
511
  </template>
479
512
 
480
513
  <style scoped>
481
- /* 页面使用 flex 分配剩余高度,让列表内部滚动并保留分页。 */
514
+ /* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,让列表内部滚动并保留分页。 */
482
515
  .dcps-page {
516
+ /* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
517
+ 独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
518
+ 不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
519
+ --host-layout-height: 0px;
483
520
  box-sizing: border-box;
484
521
  display: flex;
485
522
  flex-direction: column;
486
- gap: 14px;
487
- height: 100vh;
488
- height: 100dvh;
523
+ gap: 5px;
524
+ height: calc(100vh - var(--host-layout-height));
525
+ height: calc(100dvh - var(--host-layout-height));
489
526
  min-height: 0;
490
527
  min-width: 0;
491
528
  padding: 5px;
@@ -1,5 +1,3 @@
1
- import type { AuthorizationRuntime } from './typs'
2
-
3
1
  // 返回配置的默认查询日期,格式为 YYYYMMDD;当前使用固定日期。
4
2
  const getLocalDate = () => {
5
3
  return `20261012`
@@ -8,7 +6,7 @@ const getLocalDate = () => {
8
6
  const today = getLocalDate() // 默认查询日期,取自上方函数中的固定配置。
9
7
 
10
8
  /** 授权页面的审批人和默认创建日期;日期格式为 YYYYMMDD。 */
11
- const runtime: AuthorizationRuntime = {
9
+ const runtime = {
12
10
  approverId: '10092', // 审批人工号。
13
11
  approverName: '王干事', // 审批人姓名。
14
12
  createTmBegin: today, // 默认创建开始日期,格式为 YYYYMMDD。
@@ -1,4 +1,22 @@
1
- /** 数字人民币授权任务。 */
1
+ /** 页面查询条件;空字符串表示不限制该条件。 */
2
+ export interface AuthorizationFilters {
3
+ authoOrgNo: string // 归属行(报备机构)编号。
4
+ collePayerName: string // 付款人名称。
5
+ payeeName: string // 收款人名称。
6
+ platfSeqNo: string // 平台流水号。
7
+ taskSts: string // 任务状态:01 待审批、02 已通过、03 已拒绝。
8
+ }
9
+
10
+ /** 列表查询参数;日期格式为 YYYYMMDD,分页从第 1 页开始。 */
11
+ export interface AuthorizationQueryParams extends AuthorizationFilters {
12
+ opType: 'query' // 查询操作标识。
13
+ createTmBegin: string // 创建开始日期。
14
+ createTmEnd: string // 创建结束日期。
15
+ pageIndex: number // 当前页码。
16
+ pageSize: number // 每页条数。
17
+ }
18
+
19
+ /** 接口返回的数字人民币授权任务字段。 */
2
20
  export interface AuthorizationTask {
3
21
  id?: string // 任务 ID。
4
22
  platfSeqNo?: string // 平台流水号,用于定位审批任务。
@@ -8,7 +26,7 @@ export interface AuthorizationTask {
8
26
  collePayerName?: string // 付款人名称。
9
27
  payeeName?: string // 收款人名称。
10
28
  payWltId?: string // 付款钱包 ID。
11
- txnAmt?: string // 交易金额,单位为元。
29
+ txnAmt?: string // 交易金额,单位为元,保留字符串精度。
12
30
  txnTm?: string // 交易发起时间。
13
31
  createTm?: string // 任务创建时间。
14
32
  taskSts?: string // 任务状态:01 待审批、02 已通过、03 已拒绝。
@@ -24,36 +42,9 @@ export interface AuthorizationTask {
24
42
  collSideAcctBeloOrgNo?: string // 收款方账户所属机构编号。
25
43
  }
26
44
 
27
- /** 页面查询条件;空字符串表示不限制该条件。 */
28
- export interface AuthorizationFilters {
29
- authoOrgNo: string // 归属行(报备机构)编号。
30
- collePayerName: string // 付款人名称。
31
- payeeName: string // 收款人名称。
32
- platfSeqNo: string // 平台流水号。
33
- taskSts: string // 任务状态:01 待审批、02 已通过、03 已拒绝。
45
+ /** 列表查询返回的业务数据。 */
46
+ export interface AuthorizationQueryResult {
47
+ resultList: AuthorizationTask[] // 当前页的授权任务记录。
48
+ totalCount: string | number // 符合查询条件的总记录数。
49
+ totalPage?: string | number // 总页数。
34
50
  }
35
-
36
- /** 创建日期范围:[开始日期, 结束日期],格式为 YYYYMMDD。 */
37
- export type AuthorizationDateRange = [string, string]
38
-
39
- /** 页面审批操作:approve 通过,reject 拒绝。 */
40
- export type Decision = 'approve' | 'reject'
41
-
42
- /** 当前审批人信息,供授权请求使用。 */
43
- export interface ApproverInfo {
44
- approverId: string // 审批人工号。
45
- approverName: string // 审批人姓名。
46
- }
47
-
48
- /** 授权页面运行配置,集中定义审批人和默认创建日期。 */
49
- export interface AuthorizationRuntime extends ApproverInfo {
50
- createTmBegin: string // 默认创建开始日期,格式为 YYYYMMDD。
51
- createTmEnd: string // 默认创建结束日期,格式为 YYYYMMDD。
52
- }
53
-
54
- /** 通过和拒绝共用的审批表单;姓名只读,由 runtime 提供。 */
55
- export interface ApprovalForm {
56
- approverId: string // 本次审批使用的工号,取自 runtime,页面禁用修改。
57
- remark: string // 通过时为可选备注,拒绝时为必填原因。
58
- }
59
-