vue3-element-plus-app-chu 0.0.29 → 0.0.31
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 +13 -7
- package/package.json +2 -2
- package/src/components/table/ArtDataTable.vue +5 -1
- package/src/components/table/ArtTable.vue +2 -2
- package/src/components/table/types.ts +6 -5
- package/src/components/table/utils/columns.ts +7 -6
- package/src/utils/request.ts +5 -4
- package/src/utils/table-sort.ts +74 -0
- package/src/view/dcps/README.md +12 -5
- package/src/view/dcps/auth/index.vue +5 -9
- package/src/view/dcps/auth/typs.ts +1 -0
- package/src/view/dcps/dictionaries.ts +46 -0
- package/src/view/dcps/estimation/index.vue +61 -25
- package/src/view/dcps/estimation/typs.ts +1 -1
- package/src/view/dcps/flow-query/api.ts +7 -5
- package/src/view/dcps/flow-query/index.vue +109 -115
- package/src/view/dcps/flow-query/typs.ts +12 -59
- package/src/view/dcps/forecast/api.ts +64 -33
- package/src/view/dcps/forecast/index.vue +118 -82
- package/src/view/dcps/forecast/typs.ts +17 -28
- package/src/view/dcps/funding/balance/index.vue +8 -5
- package/src/view/dcps/funding/balance/typs.ts +2 -2
- package/src/view/dcps/funding/injection/api.ts +7 -2
- package/src/view/dcps/funding/injection/index.vue +64 -47
- package/src/view/dcps/funding/injection/typs.ts +15 -9
- package/src/view/dcps/parameters/index.vue +4 -4
- package/src/view/dcps/write-off/api.ts +51 -6
- package/src/view/dcps/write-off/index.vue +216 -84
- package/src/view/dcps/write-off/typs.ts +16 -20
package/README.md
CHANGED
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
## 启动
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm dev # 同时启动前端(5173
|
|
8
|
+
pnpm dev # 同时启动前端(5173)和指定后端(3011)
|
|
9
9
|
pnpm dev:frontend # 只启动前端
|
|
10
|
-
pnpm dev:backend #
|
|
10
|
+
pnpm dev:backend # 只启动 verce-vue-test/backend 后端
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
一键启动命令通过 `npm-run-all2` 并行执行两个子命令,输出带 `[dev:frontend]` / `[dev:backend]` 前缀。
|
|
14
14
|
|
|
15
|
+
后端项目统一使用 `/Users/bombingpig/WebstormProjects/verce-vue-test/backend`,本项目不再保留 `backend/` 代码。已有 3011 后端服务运行时,使用 `pnpm dev:frontend` 即可复用该服务。前端开发代理将 `/TAM`、`/api`、`/nacos` 请求转发到 `http://localhost:3011/oms`,例如 `/TAM/0001/140501001` 对应后端 `/oms/TAM/0001/140501001`。
|
|
16
|
+
|
|
17
|
+
接口以 [Scalar 文档](http://localhost:3011/oms/docs) 和 [OpenAPI JSON](http://localhost:3011/oms/openapi.json) 为准,统一服务前缀为 `/oms`。仓库内的接口摘录用于查看字段和页面对应关系,不能替代运行中后端的文档。
|
|
18
|
+
|
|
15
19
|
## 全局布局
|
|
16
20
|
|
|
17
21
|
应用外壳位于 `src/layout/index.vue`,由 `App.vue` 通过 `RouterView` 挂载,统一承载侧边导航、折叠状态和业务页面内容。菜单配置在 `src/router/menus.ts`,路由表在 `src/router/index.ts`,新增业务页面时两处各追加一项即可。业务地址、浏览器前进后退和刷新都由 vue-router 驱动;头寸预报列表页由 `KeepAlive` 缓存,返回时保留筛选和分页状态。
|
|
@@ -26,16 +30,18 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
|
|
|
26
30
|
- 新增、修改、详情与列表共用 `/forecast` 入口,以当前页面状态切换完整视图,页面顶部提供“返回列表”按钮;返回后列表的筛选和分页状态不变。
|
|
27
31
|
- 支持交易日期、客户、交易对手、机构、金额范围及业务代码筛选,分页、详情、新增、修改和批量删除。
|
|
28
32
|
- 金额、账号及流水号使用字符串处理。业务类型使用文档明确的中文选项,其余未提供字典的代码保留接口原值。
|
|
29
|
-
-
|
|
33
|
+
- 导出复用查询接口,提交公共头 `fileFlag: 'Y'` 并下载后端返回的 CSV data URI;导入接口当前返回 `501`,页面移除导入入口。
|
|
34
|
+
- 新增仅要求业务类型、预报金额、交易日期和收支方向,客户、机构等字段选填;支付渠道未传时由后端默认 `8`,预报日期未传时由后端默认当天。新增、修改不展示或提交接口未声明的票号、出票日。
|
|
35
|
+
- 详情只展示当前详情接口声明的 15 个字段;编辑仅提交定位流水号和实际变更的可写字段,避免将详情未返回的信息覆盖为空。查询排序按日期降序、日期升序、金额降序、金额升序分别提交 `1/2/3/4`。
|
|
30
36
|
- 模块目录与授权页面保持一致:`api.ts` 放接口定义,`typs.ts` 放类型,`index.vue` 包含列表、表单、详情和页面内工具函数。默认交易日期通过共用的 `src/view/dcps/runtime.ts` 获取,接口定义只维护在各自模块下,不再集中到 `src/api`。
|
|
31
37
|
- 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
页面通过公共请求层读取 TAM 的 `head/body`,统一响应的解包与兼容处理由 `src/utils/request.ts` 负责。指定后端用于替代前端 mock;数字人民币核销、修改和撤销核销按顺序直接更新数据库,单机 MongoDB 即可使用。`head.consumerSeqNo` 可选,传入时支持相同请求重试;原金额和状态快照校验仍保留。注资发起只保存 `PENDING` / `NOT_SENT` 待处理记录,不调整账户余额;预报导入当前返回 `501`。业务校验、数据保存和导入导出均以指定后端的实时文档及实现为准。
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
数字人民币页面的业务类型使用 `201/202/203` 字典,新增预报不提交 `predName`;核销预报方向使用 `1/2`,待核销查询包含状态 `1/3`,展示交易对手账号;流水可按业务类型筛选并读取 `tranStatus`;注资明细支持起止交易日期;授权页展示业务类型中文名称。匡算明细展示部分核销状态。字段及页面接口映射见 [数字人民币接口摘录](docs/digital-rmb-apis.md),当前接口约束以实时文档为准。
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
独立页面 `/flow-query` 的普通查询、初次加载、分页和刷新与核销页面的流水信息共用 `/TAM/0001/140502002`,参数保持一致:交易日期 `tranDate` 必填且默认取共用业务日期,可按业务类型、我方机构、付款人、收款人、收支方向、金额范围和核销状态筛选,未填写的可选条件不提交。列表读取 `resultList` 和字符串类型的 `totalCount`,字段沿用核销流水模型。「查询」「实时查询流水」与「重置」按钮并排放在查询条件区末尾。「实时查询流水」单独调用 `/TAM/0001/140504002`,只携带公共分页参数 `pageIndex: 1`、`pageSize: 1`,业务请求体为空;成功或失败通过弹窗提示,不更新列表。
|
|
38
44
|
|
|
39
45
|
## 城银清头寸参数接口
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
数币参数页面位于 `src/view/dcps/parameters/`,查询和新增使用 `moduleCode: '5'`;详情直接取表格行数据,修改和删除按参数名称定位。指定后端当前通过 `swaggerStub` 返回参数接口的静态兼容响应,不持久化增删改。接口字段和调用方式见 [城银清头寸参数接口说明](docs/city-clearing-parameters-apis.md),当前服务能力以 [Scalar 文档](http://localhost:3011/oms/docs) 为准。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue3-element-plus-app-chu",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"dev": "run-p --print-label dev:frontend dev:backend",
|
|
15
15
|
"dev:frontend": "vite",
|
|
16
|
-
"dev:backend": "pnpm -
|
|
16
|
+
"dev:backend": "pnpm --dir /Users/bombingpig/WebstormProjects/verce-vue-test/backend dev",
|
|
17
17
|
"build": "run-s type-check build-only",
|
|
18
18
|
"build:fast": "vite build",
|
|
19
19
|
"preview": "vite preview",
|
|
@@ -52,6 +52,9 @@
|
|
|
52
52
|
</template>
|
|
53
53
|
</ArtTable>
|
|
54
54
|
</div>
|
|
55
|
+
<div v-if="slots.footer" class="art-data-table__footer">
|
|
56
|
+
<slot name="footer" />
|
|
57
|
+
</div>
|
|
55
58
|
</section>
|
|
56
59
|
</Teleport>
|
|
57
60
|
</template>
|
|
@@ -140,7 +143,7 @@ function getTableAttrs() {
|
|
|
140
143
|
return tableAttrs
|
|
141
144
|
}
|
|
142
145
|
function tableSlotNames() {
|
|
143
|
-
return Object.keys(slots).filter((name) => !['toolbar-left', 'toolbar-right', 'search'].includes(name))
|
|
146
|
+
return Object.keys(slots).filter((name) => !['toolbar-left', 'toolbar-right', 'search', 'footer'].includes(name))
|
|
144
147
|
}
|
|
145
148
|
async function setFullscreen(value: boolean) {
|
|
146
149
|
if (value === isFullscreen.value || typeof document === 'undefined') return
|
|
@@ -181,6 +184,7 @@ defineExpose({
|
|
|
181
184
|
}
|
|
182
185
|
.art-data-table__search { padding: 0 0 16px; }
|
|
183
186
|
.art-data-table__content { min-width: 0; }
|
|
187
|
+
.art-data-table__footer { flex-shrink: 0; min-width: 0; }
|
|
184
188
|
.art-data-table--fill, .art-data-table--fullscreen { display: flex; flex-direction: column; min-height: 0; }
|
|
185
189
|
.art-data-table--fill { height: 100%; }
|
|
186
190
|
.art-data-table--fill .art-data-table__content,
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
<script setup lang="ts">
|
|
46
46
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, useAttrs, useSlots, type CSSProperties } from 'vue'
|
|
47
|
-
import { ElEmpty, ElPagination, ElTable, vLoading } from 'element-plus'
|
|
47
|
+
import { ElEmpty, ElPagination, ElTable, vLoading, type TableInstance } from 'element-plus'
|
|
48
48
|
import ArtTableColumn from './ArtTableColumn'
|
|
49
49
|
import type { ColumnOption, HeaderCellStyle, PaginationConfig, PaginationOptions, TableSize } from './types'
|
|
50
50
|
import { getColumnKey, normalizeColumns } from './utils/columns'
|
|
@@ -82,7 +82,7 @@ const emit = defineEmits<{
|
|
|
82
82
|
|
|
83
83
|
const attrs = useAttrs()
|
|
84
84
|
const slots = useSlots()
|
|
85
|
-
const elTableRef = ref<
|
|
85
|
+
const elTableRef = ref<TableInstance | null>(null)
|
|
86
86
|
const containerRef = ref<HTMLElement | null>(null)
|
|
87
87
|
const bodyRef = ref<HTMLElement | null>(null)
|
|
88
88
|
const containerWidth = ref(1280)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, VNodeChild } from 'vue'
|
|
2
2
|
import type { TableColumnCtx } from 'element-plus'
|
|
3
|
+
import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
|
|
3
4
|
|
|
4
5
|
export type TableSize = 'small' | 'default' | 'large'
|
|
5
6
|
|
|
@@ -28,7 +29,7 @@ export interface PaginationOptions {
|
|
|
28
29
|
[key: string]: unknown
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export interface CellScope<T = any> {
|
|
32
|
+
export interface CellScope<T extends DefaultRow = any> {
|
|
32
33
|
row: T
|
|
33
34
|
column: TableColumnCtx<T>
|
|
34
35
|
$index: number
|
|
@@ -36,7 +37,7 @@ export interface CellScope<T = any> {
|
|
|
36
37
|
value?: unknown
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
export interface HeaderScope<T = any> {
|
|
40
|
+
export interface HeaderScope<T extends DefaultRow = any> {
|
|
40
41
|
column: TableColumnCtx<T>
|
|
41
42
|
$index: number
|
|
42
43
|
prop?: string
|
|
@@ -44,7 +45,7 @@ export interface HeaderScope<T = any> {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/** 兼容 Element Plus 列属性及原项目的 useSlot / formatter 写法。 */
|
|
47
|
-
export interface ColumnOption<T = any>
|
|
48
|
+
export interface ColumnOption<T extends DefaultRow = any>
|
|
48
49
|
extends Partial<Omit<TableColumnCtx<T>, 'type' | 'children' | 'formatter' | 'renderHeader' | 'width' | 'minWidth'>> {
|
|
49
50
|
key?: string
|
|
50
51
|
type?: 'default' | 'selection' | 'index' | 'expand' | 'globalIndex'
|
|
@@ -66,12 +67,12 @@ export interface ColumnOption<T = any>
|
|
|
66
67
|
[key: string]: any
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
export interface ColumnCheck<T = any> extends ColumnOption<T> {
|
|
70
|
+
export interface ColumnCheck<T extends DefaultRow = any> extends ColumnOption<T> {
|
|
70
71
|
key: string
|
|
71
72
|
checked: boolean
|
|
72
73
|
visible: boolean
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
export type HeaderCellStyle<T = any> =
|
|
76
|
+
export type HeaderCellStyle<T extends DefaultRow = any> =
|
|
76
77
|
| CSSProperties
|
|
77
78
|
| ((scope: { row: TableColumnCtx<T>[]; column: TableColumnCtx<T>; rowIndex: number; columnIndex: number }) => CSSProperties)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ColumnCheck, ColumnOption } from '../types'
|
|
2
|
+
import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
|
|
2
3
|
|
|
3
4
|
const specialLabels: Record<string, string> = {
|
|
4
5
|
selection: '勾选列',
|
|
@@ -8,31 +9,31 @@ const specialLabels: Record<string, string> = {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/** 推荐为无 prop 的自定义列提供 key;索引仅作为最后的回退。 */
|
|
11
|
-
export function getColumnKey<T>(column: ColumnOption<T>, index = 0): string {
|
|
12
|
+
export function getColumnKey<T extends DefaultRow>(column: ColumnOption<T>, index = 0): string {
|
|
12
13
|
if (column.key) return column.key
|
|
13
14
|
if (column.type && column.type !== 'default') return `__${column.type}__`
|
|
14
15
|
if (column.prop) return String(column.prop)
|
|
15
16
|
return `__column_${index}__`
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function getColumnVisibility<T>(column: ColumnOption<T>): boolean {
|
|
19
|
+
export function getColumnVisibility<T extends DefaultRow>(column: ColumnOption<T>): boolean {
|
|
19
20
|
return column.visible ?? column.checked ?? true
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/** 固定列和功能列保持自己的位置,其余列可排序。 */
|
|
23
|
-
export function isColumnPinned<T>(column: ColumnOption<T>): boolean {
|
|
24
|
+
export function isColumnPinned<T extends DefaultRow>(column: ColumnOption<T>): boolean {
|
|
24
25
|
return Boolean(column.fixed || (column.type && column.type !== 'default'))
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/** 浅复制每一级配置,保留 formatter、render 等函数和 VNode。 */
|
|
28
|
-
export function cloneColumn<T>(column: ColumnOption<T>): ColumnOption<T> {
|
|
29
|
+
export function cloneColumn<T extends DefaultRow>(column: ColumnOption<T>): ColumnOption<T> {
|
|
29
30
|
return {
|
|
30
31
|
...column,
|
|
31
32
|
...(column.children ? { children: column.children.map(cloneColumn) } : {})
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export function normalizeColumns<T>(columns: ColumnOption<T>[]): ColumnOption<T>[] {
|
|
36
|
+
export function normalizeColumns<T extends DefaultRow>(columns: ColumnOption<T>[]): ColumnOption<T>[] {
|
|
36
37
|
const usedKeys = new Set<string>()
|
|
37
38
|
return columns.map((column, index) => {
|
|
38
39
|
const baseKey = getColumnKey(column, index)
|
|
@@ -48,7 +49,7 @@ export function normalizeColumns<T>(columns: ColumnOption<T>[]): ColumnOption<T>
|
|
|
48
49
|
})
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function getColumnChecks<T>(columns: ColumnOption<T>[]): ColumnCheck<T>[] {
|
|
52
|
+
export function getColumnChecks<T extends DefaultRow>(columns: ColumnOption<T>[]): ColumnCheck<T>[] {
|
|
52
53
|
return normalizeColumns(columns).map((column) => {
|
|
53
54
|
const visible = getColumnVisibility(column)
|
|
54
55
|
return {
|
package/src/utils/request.ts
CHANGED
|
@@ -30,17 +30,19 @@ export interface TamResponseHead {
|
|
|
30
30
|
[field: string]: unknown
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** 页面使用的 TAM
|
|
33
|
+
/** 页面使用的 TAM 数据;3011 后端直接在响应外层提供 head/body。 */
|
|
34
34
|
export interface ApiResponse<T = unknown> {
|
|
35
35
|
head: TamResponseHead
|
|
36
36
|
body: T | null
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/** 后端统一响应;data 是业务数据,TAM 接口同时提供公共 head 和业务 body。 */
|
|
40
40
|
export interface StandardApiResponse<T = unknown> {
|
|
41
41
|
code: number
|
|
42
42
|
data: T | null
|
|
43
43
|
message: string
|
|
44
|
+
head?: TamResponseHead
|
|
45
|
+
body?: T | null
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export type HttpRequestConfig = AxiosRequestConfig
|
|
@@ -124,7 +126,7 @@ http.interceptors.response.use(
|
|
|
124
126
|
|
|
125
127
|
type RequestConfig = Omit<HttpRequestConfig, 'url' | 'method'>
|
|
126
128
|
|
|
127
|
-
// TAM
|
|
129
|
+
// TAM 接口保留外层 head/body;data 是业务对象,不能单独解包而丢失公共响应头。
|
|
128
130
|
export const request = async <T = ApiResponse>(
|
|
129
131
|
config: HttpRequestConfig,
|
|
130
132
|
): Promise<T> => {
|
|
@@ -136,7 +138,6 @@ export const request = async <T = ApiResponse>(
|
|
|
136
138
|
if (/^\/TAM(?:\/|$)/i.test(config.url ?? '')
|
|
137
139
|
&& isPlainObject(payload) && typeof payload.code === 'number') {
|
|
138
140
|
if (payload.code !== 0) throw new Error(typeof payload.message === 'string' ? payload.message : '请求失败')
|
|
139
|
-
return payload.data as T
|
|
140
141
|
}
|
|
141
142
|
return response.data
|
|
142
143
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
type LocalSortKind = 'amount' | 'date' | 'time'
|
|
2
|
+
|
|
3
|
+
/** 金额保留十进制原始精度,兼容合法千分位,不通过浮点数比较。 */
|
|
4
|
+
function parseAmount(value: unknown) {
|
|
5
|
+
const text = String(value ?? '').trim()
|
|
6
|
+
const normalized = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text)
|
|
7
|
+
? text.replace(/,/g, '') : text
|
|
8
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(normalized)
|
|
9
|
+
if (!match || (!match[2] && !match[3])) return null
|
|
10
|
+
return { sign: match[1] === '-' ? -1n : 1n, integer: match[2] || '0', fraction: match[3] || '' }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function compareAmounts(left: unknown, right: unknown): number {
|
|
14
|
+
const a = parseAmount(left)
|
|
15
|
+
const b = parseAmount(right)
|
|
16
|
+
if (!a || !b) return a ? -1 : b ? 1 : 0
|
|
17
|
+
const precision = Math.max(a.fraction.length, b.fraction.length)
|
|
18
|
+
const aValue = BigInt(a.integer + a.fraction.padEnd(precision, '0')) * a.sign
|
|
19
|
+
const bValue = BigInt(b.integer + b.fraction.padEnd(precision, '0')) * b.sign
|
|
20
|
+
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 兼容 YYYYMMDD、YYYYMMDDHHmmss、日期分隔符与带时区的 ISO 时间。 */
|
|
24
|
+
function parseDate(value: unknown): number | null {
|
|
25
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.getTime()
|
|
26
|
+
const text = String(value ?? '').trim().replace(
|
|
27
|
+
/^(\d{4})(\d{2})(\d{2})(?:(\d{2})(\d{2})(\d{2})(\.\d+)?)?$/,
|
|
28
|
+
(_, year, month, day, hour, minute, second, fraction) =>
|
|
29
|
+
`${year}-${month}-${day}${hour ? `T${hour}:${minute}:${second}${fraction || ''}` : ''}`,
|
|
30
|
+
)
|
|
31
|
+
const match = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[T\s]+(\d{1,2}):(\d{2})(?::(\d{2})(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/i.exec(text)
|
|
32
|
+
if (!match) return null
|
|
33
|
+
const [, year = '', month = '', day = '', hour = '00', minute = '00', second = '00', fraction = '', zone = ''] = match
|
|
34
|
+
const y = Number(year), m = Number(month), d = Number(day)
|
|
35
|
+
const h = Number(hour), min = Number(minute), s = Number(second)
|
|
36
|
+
const daysInMonth = [31, y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
37
|
+
if (m < 1 || m > 12 || d < 1 || d > daysInMonth[m - 1]! || h > 23 || min > 59 || s > 59) return null
|
|
38
|
+
// 无时区的日期统一按本地时间解析,避免紧凑日期与带横线日期相差时区。
|
|
39
|
+
const normalized = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T${hour.padStart(2, '0')}:${minute}:${second}${fraction}${zone}`
|
|
40
|
+
const timestamp = Date.parse(normalized)
|
|
41
|
+
return Number.isNaN(timestamp) ? null : timestamp
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 纯时间支持 HHmmss、HH:mm[:ss],完整日期时间沿用日期比较。 */
|
|
45
|
+
function parseTime(value: unknown): number | null {
|
|
46
|
+
const text = String(value ?? '').trim()
|
|
47
|
+
const match = /^(\d{2})(\d{2})(\d{2})(\.\d+)?$/.exec(text)
|
|
48
|
+
|| /^(\d{1,2}):(\d{2})(?::(\d{2})(\.\d+)?)?$/.exec(text)
|
|
49
|
+
if (!match) return parseDate(value)
|
|
50
|
+
const [, hour = '', minute = '', second = '00', fraction = ''] = match
|
|
51
|
+
const h = Number(hour), min = Number(minute), s = Number(second)
|
|
52
|
+
if (h > 23 || min > 59 || s > 59) return null
|
|
53
|
+
return ((h * 60 + min) * 60 + s + Number(fraction || 0)) * 1000
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 交由 Element Plus 对当前数据副本排序;取消排序时恢复传入顺序。 */
|
|
57
|
+
export function localSort<T = any>(prop: string | ((row: T) => unknown), kind: LocalSortKind) {
|
|
58
|
+
const readValue = typeof prop === 'function' ? prop : (row: T): unknown =>
|
|
59
|
+
prop.replace(/\[(\d+)\]/g, '.$1').split('.').reduce<unknown>((value, key) =>
|
|
60
|
+
value == null ? undefined : (value as Record<string, unknown>)[key], row)
|
|
61
|
+
return {
|
|
62
|
+
sortable: true as const,
|
|
63
|
+
sortMethod: (left: T, right: T): number => {
|
|
64
|
+
const a = readValue(left)
|
|
65
|
+
const b = readValue(right)
|
|
66
|
+
if (kind === 'amount') return compareAmounts(a, b)
|
|
67
|
+
const parse = kind === 'time' ? parseTime : parseDate
|
|
68
|
+
const aTime = parse(a)
|
|
69
|
+
const bTime = parse(b)
|
|
70
|
+
if (aTime === null || bTime === null) return aTime === null ? bTime === null ? 0 : 1 : -1
|
|
71
|
+
return aTime - bTime
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/view/dcps/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
`src/view/dcps` 下每个业务页面统一保持三个文件,不再拆分子组件、工具文件:
|
|
4
4
|
|
|
5
|
-
跨页面共用的运行时配置统一放在 `src/view/dcps/runtime.ts`,通过具名对象 `dcpsRuntime` 导出,包含 `date`(共用业务日期,格式为 `YYYYMMDD`)、`approverId`(工号)和 `approverName
|
|
5
|
+
跨页面共用的运行时配置统一放在 `src/view/dcps/runtime.ts`,通过具名对象 `dcpsRuntime` 导出,包含 `date`(共用业务日期,格式为 `YYYYMMDD`)、`approverId`(工号)和 `approverName`(姓名)。日期在模块加载时按上海时区生成一次,预报、匡算、核销、流水查询及授权查询的默认日期均使用同一个 `date`;授权日期范围的开始和结束默认都取该值。页面通过 `import { dcpsRuntime } from '../runtime'`(分组子页面为 `../../runtime`)引入,不再单独导出日期工具或模块配置。
|
|
6
|
+
|
|
7
|
+
跨页面共用的状态和注资方式字典统一放在 `src/view/dcps/dictionaries.ts`,包含结算钱包状态 `sttlmWlltSts`、注资预注资状态 `ciSts`、注资方式 `ciMode`、交易处理状态 `txnProceSts` 和人行状态 `pbocSts`。表格、详情和摘要通过 `formatDcpsStatus` 展示中文,空值显示横线,未知代码保留原文。流水查询和核销的 `tranStatus` 复用同一份 `transactionStatusLabels`;注资明细的交易处理状态筛选使用 `transactionStatusOptions`,显示中文并提交原始代码。
|
|
6
8
|
|
|
7
9
|
一级菜单为分组时,页面放在该分组目录下。例如“注资/预注资管理”的两个页面分别位于 `funding/injection/` 和 `funding/balance/`,各自保留下面三个文件。
|
|
8
10
|
|
|
@@ -16,9 +18,13 @@
|
|
|
16
18
|
|
|
17
19
|
## 当前接口契约
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
接口统一使用 `/Users/bombingpig/WebstormProjects/verce-vue-test/backend`,以 [Scalar 文档](http://localhost:3011/oms/docs) 和 [OpenAPI JSON](http://localhost:3011/oms/openapi.json) 为准。服务前缀为 `/oms`,前端开发代理将 `/TAM`、`/api`、`/nacos` 转发到 `http://localhost:3011/oms`。本项目已移除原 `backend/`;仓库内 `docs/digital-rmb.openapi.json` 为字段摘录。
|
|
22
|
+
|
|
23
|
+
预报新增必填 `busiType`、`predAmt`、`tranDate`、`payDirec`,业务类型字典为 `201` 兑回、`202` 兑出、`203` 付现;新增和修改不提交 `predName`、`billNo`、`billDate`。客户和机构等字段选填,`payChannel` 缺省为 `8`,`predDate` 缺省为后端当天日期。核销预报查询使用方向 `1/2`,待核销传 `verifyStatusList: ['1', '3']`,已核销传 `['2']`;预报管理和流水方向保持各自原协议。核销页流水查询使用 `busiType`,读取 `tranStatus`(`S/P/F`),展示核销状态 `3` 部分核销。注资明细提供 `txnDtStart`、`txnDtEnd` 筛选。字段摘录及页面接口映射见 `docs/digital-rmb-apis.md`,业务校验与保存行为由指定后端负责。
|
|
24
|
+
|
|
25
|
+
当前核销预报 API 将页面 `1/2` 方向与后端实现的 `I/O` 双向转换;核销写操作提交公共 `consumerSeqNo`、原状态和原金额快照。后端作为替代前端 mock 的本地测试服务,核销、修改和撤销核销按顺序直接更新数据库,各种 MongoDB 部署模式均不启动事务。`consumerSeqNo` 可选,缺省时后端生成,传入时支持相同请求重试;快照校验和关联撤回逻辑仍保留。注资明细使用服务端分页;发起注资只保存 `PENDING` / `NOT_SENT` 记录,不改变余额。参数接口当前由 `swaggerStub` 返回静态兼容数据,不持久化参数增删改。
|
|
20
26
|
|
|
21
|
-
独立流水查询页面 `/flow-query`
|
|
27
|
+
独立流水查询页面 `/flow-query` 的普通查询、初次加载、分页和刷新复用核销页的 `queryFlowApi`(`140502002`)与流水模型,读取 `resultList` 和字符串类型的 `totalCount`。查询参数与核销页流水信息保持一致:`tranDate` 必填,默认取 `dcpsRuntime.date`;可选条件为 `busiType`、`orgName`、`payerName`、`payeeName`、`payDirec`(I/O)、`startTransAmt`、`endTransAmt` 和 `verifyStatus`(1/2),空条件不发送;分页放在公共请求头。「查询」「实时查询流水」与「重置」按钮并排放在查询条件区末尾。「实时查询流水」通过独立的 `queryRealtimeFlowApi` 调用 `140504002`,只传公共分页 `pageIndex: 1`、`pageSize: 1`,业务请求体为空;结果仅弹窗提示,不改变表格和分页。
|
|
22
28
|
|
|
23
29
|
## 命名约定
|
|
24
30
|
|
|
@@ -49,10 +55,11 @@
|
|
|
49
55
|
补充说明:
|
|
50
56
|
|
|
51
57
|
- 头寸预报的列表、新增、修改、详情共用 `/forecast` 与 `forecast/index.vue`,通过当前组件的视图状态切换;修改和详情使用所选记录的 `predRefNo` 请求数据,返回列表保留筛选和分页。
|
|
52
|
-
- `
|
|
58
|
+
- 预报详情只展示当前 `Tam140501002Out.Body` 声明的 15 个字段,不用列表行补充接口未声明的详情字段。编辑可以使用详情及可用列表值初始化,但保存只发送 `predRefSeq` 与实际变更的可写字段;`api.ts` 按各接口字段白名单组装参数,不直接提交整条列表记录。
|
|
59
|
+
- 预报导出复用 `/140501001`,公共头传 `fileFlag: 'Y'`,不使用 `appHead.opType`。页面下载后端返回的 `fileName`、`filePath`(CSV data URI),不在前端生成 CSV。导入接口当前返回 `501`,不提供页面入口。排序 `sortRule` 的 `1/2/3/4` 分别对应日期降序、日期升序、金额降序、金额升序。
|
|
53
60
|
- 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应路由的 `name` 保持一致;分组菜单通过 `children` 配置二级页面,页面的 `meta.menu` 指向自己的菜单项。
|
|
54
61
|
- “注资/预注资管理”对应 `funding` 分组,包含注资明细查询和账户行余额查询申请两个页面;访问子页面时自动展开父菜单。旧地址 `/injection`、`/balance` 保留为对应页面的别名。
|
|
55
|
-
- 数字人民币参数管理为末尾的独立菜单,页面文件为 `parameters/index.vue`、`parameters/api.ts`、`parameters/typs.ts`,接口与类型均独立定义在该目录,不再依赖或保留 `city-clearing/` 目录。页面复用“城银清算头寸管理-头寸参数”的列表、新增、修改、删除四个接口,详情直接取表格行数据,不再请求详情接口;`api.ts` 参照授权页面直接向 `@chic/http` 平铺传参,由 Axios 公共封装拆分为服务端所需的 `body`、`head` 等字段,不手动包装信封。页面查询调用只传 `keywords`、数字类型的 `pageIndex` 和 `pageSize`,`moduleCode: '5'` 由 API 内部固定添加,分页数字由封装层转换为字符串;新增传六个字符串表单字段,模块代码也由 API 固定添加。本次用户确认以 `paraName` 定位修改和删除:修改时锁定原参数名称、参数标志只读;删除向 API 传名称数组,由 API 转为平铺的 `sysParaList`。页面类型使用 `DigitalRmbParameter` 等独立类型,原始 Swagger 契约及 `docs/city-clearing-parameters*`
|
|
62
|
+
- 数字人民币参数管理为末尾的独立菜单,页面文件为 `parameters/index.vue`、`parameters/api.ts`、`parameters/typs.ts`,接口与类型均独立定义在该目录,不再依赖或保留 `city-clearing/` 目录。页面复用“城银清算头寸管理-头寸参数”的列表、新增、修改、删除四个接口,详情直接取表格行数据,不再请求详情接口;`api.ts` 参照授权页面直接向 `@chic/http` 平铺传参,由 Axios 公共封装拆分为服务端所需的 `body`、`head` 等字段,不手动包装信封。页面查询调用只传 `keywords`、数字类型的 `pageIndex` 和 `pageSize`,`moduleCode: '5'` 由 API 内部固定添加,分页数字由封装层转换为字符串;新增传六个字符串表单字段,模块代码也由 API 固定添加。本次用户确认以 `paraName` 定位修改和删除:修改时锁定原参数名称、参数标志只读;删除向 API 传名称数组,由 API 转为平铺的 `sysParaList`。页面类型使用 `DigitalRmbParameter` 等独立类型,原始 Swagger 契约及 `docs/city-clearing-parameters*` 文档文件名保留。参数接口当前返回静态兼容响应,不持久化新增、修改或删除;后续实现范围以指定后端的实时文档为准。
|
|
56
63
|
- 数字人民币参数管理以数字人民币授权页面为布局参考,统一筛选区、填满剩余高度的表格以及包含摘要和分组字段的详情弹窗样式。
|
|
57
64
|
- `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报页面;新增、修改、详情在同一实例内切换,返回列表时自动重新查询。
|
|
58
65
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
3
|
import { ElConfigProvider, ElMessage } from 'element-plus'
|
|
4
4
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
5
|
+
import { localSort } from '@/utils/table-sort'
|
|
5
6
|
import API from './api'
|
|
6
7
|
import { dcpsRuntime } from '../runtime'
|
|
7
8
|
import type { AuthorizationFilters, AuthorizationQueryParams, AuthorizationTask } from './typs'
|
|
@@ -78,6 +79,7 @@ const detailGroups = [
|
|
|
78
79
|
['collePayerName', '付款人名称'],
|
|
79
80
|
['payeeName', '收款人名称'],
|
|
80
81
|
['payWltId', '付款钱包 ID'],
|
|
82
|
+
['collWltId', '收款钱包ID'],
|
|
81
83
|
],
|
|
82
84
|
},
|
|
83
85
|
{
|
|
@@ -206,20 +208,13 @@ const textColumn = (
|
|
|
206
208
|
prop, label, width, showOverflowTooltip: true, formatter: (row) => format(row[prop]),
|
|
207
209
|
})
|
|
208
210
|
|
|
209
|
-
//
|
|
211
|
+
// 时间列兼容紧凑格式、纯时间和完整日期时间,对当前页做本地排序。
|
|
210
212
|
const dateColumn = (
|
|
211
213
|
prop: string,
|
|
212
214
|
label: string,
|
|
213
215
|
): ColumnOption => ({
|
|
214
216
|
...textColumn(prop, label, 180, formatDateTime),
|
|
215
|
-
|
|
216
|
-
sortMethod: (left, right) => {
|
|
217
|
-
const leftTime = Date.parse(left[prop]?.trim() || '')
|
|
218
|
-
const rightTime = Date.parse(right[prop]?.trim() || '')
|
|
219
|
-
if (Number.isNaN(leftTime)) return Number.isNaN(rightTime) ? 0 : 1
|
|
220
|
-
if (Number.isNaN(rightTime)) return -1
|
|
221
|
-
return leftTime - rightTime
|
|
222
|
-
},
|
|
217
|
+
...localSort(prop, 'time'),
|
|
223
218
|
})
|
|
224
219
|
|
|
225
220
|
// 表格完整列配置;字段顺序即展示顺序,任务状态和操作列固定在右侧。
|
|
@@ -235,6 +230,7 @@ const tableColumns: ColumnOption[] = [
|
|
|
235
230
|
useSlot: true, slotName: 'amount', sortable: 'custom',
|
|
236
231
|
},
|
|
237
232
|
textColumn('payWltId', '付款钱包ID', 190),
|
|
233
|
+
textColumn('collWltId', '收款钱包ID', 190),
|
|
238
234
|
textColumn('authoOrgName', '授权机构名称', 180),
|
|
239
235
|
textColumn('authoOrgNo', '授权机构编号', 150),
|
|
240
236
|
textColumn('paySideAcctBeloOrgName', '付款方账户所属机构名称', 210),
|
|
@@ -26,6 +26,7 @@ export interface AuthorizationTask {
|
|
|
26
26
|
collePayerName?: string // 付款人名称。
|
|
27
27
|
payeeName?: string // 收款人名称。
|
|
28
28
|
payWltId?: string // 付款钱包 ID。
|
|
29
|
+
collWltId?: string // 收款钱包 ID。
|
|
29
30
|
txnAmt?: string // 交易金额,单位为元,保留字符串精度。
|
|
30
31
|
txnTm?: string // 交易发起时间。
|
|
31
32
|
createTm?: string // 任务创建时间。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** 注资明细、流水查询和核销共用的交易处理状态。 */
|
|
2
|
+
export const transactionStatusLabels: Record<string, string> = {
|
|
3
|
+
S: '成功',
|
|
4
|
+
F: '失败',
|
|
5
|
+
P: '处理中',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** 查询下拉展示中文,提交时继续使用接口代码。 */
|
|
9
|
+
export const transactionStatusOptions = Object.entries(transactionStatusLabels)
|
|
10
|
+
.map(([value, label]) => ({ value, label }))
|
|
11
|
+
|
|
12
|
+
/** 数字人民币页面共用的状态和注资方式字典。 */
|
|
13
|
+
export const dcpsStatusLabels = {
|
|
14
|
+
sttlmWlltSts: {
|
|
15
|
+
SWS01: '正常',
|
|
16
|
+
SWS02: '冻结',
|
|
17
|
+
SWS03: '注销',
|
|
18
|
+
},
|
|
19
|
+
ciSts: {
|
|
20
|
+
FDRC: '注资成功',
|
|
21
|
+
FDWT: '待注资',
|
|
22
|
+
},
|
|
23
|
+
ciMode: {
|
|
24
|
+
'01': '系统自动',
|
|
25
|
+
'02': '手工',
|
|
26
|
+
'03': '缺款自动',
|
|
27
|
+
},
|
|
28
|
+
txnProceSts: transactionStatusLabels,
|
|
29
|
+
pbocSts: {
|
|
30
|
+
PR01: '失败',
|
|
31
|
+
PR06: '已受理',
|
|
32
|
+
PR10: '成功',
|
|
33
|
+
PR99: '已退汇',
|
|
34
|
+
'-1': '其他',
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type DcpsStatusField = keyof typeof dcpsStatusLabels
|
|
39
|
+
|
|
40
|
+
/** 空值显示横线,未知代码保留原文;兼容人行状态返回数字 -1。 */
|
|
41
|
+
export const formatDcpsStatus = (field: DcpsStatusField, value?: string | number | null): string => {
|
|
42
|
+
const text = String(value ?? '').trim()
|
|
43
|
+
if (!text) return '—'
|
|
44
|
+
const labels: Readonly<Record<string, string>> = dcpsStatusLabels[field]
|
|
45
|
+
return Object.hasOwn(labels, text) ? labels[text] ?? text : text
|
|
46
|
+
}
|