vue3-element-plus-app-chu 0.0.13 → 0.0.14
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 +4 -4
- package/package.json +3 -2
- package/src/layout/index.vue +21 -36
- package/src/router/index.ts +110 -0
- package/src/router/menus.ts +16 -0
- package/src/view/dcps/README.md +59 -0
- package/src/view/dcps/auth/components/AuthorizationDetailDialog.vue +2 -0
- package/src/view/dcps/auth/index.vue +4 -6
- package/src/view/dcps/balance/index.vue +4 -6
- package/src/view/dcps/common.css +7 -0
- package/src/view/dcps/estimation/api.ts +17 -142
- package/src/view/dcps/estimation/components/EstimationDetailDialog.vue +4 -1
- package/src/view/dcps/estimation/index.vue +7 -8
- package/src/view/dcps/estimation/utils.ts +113 -0
- package/src/view/dcps/forecast/api.ts +9 -201
- package/src/view/dcps/forecast/components/ForecastDetailView.vue +19 -9
- package/src/view/dcps/forecast/components/ForecastEditorView.vue +25 -14
- package/src/view/dcps/forecast/components/ForecastListView.vue +8 -11
- package/src/view/dcps/forecast/components/ForecastPageShell.vue +3 -7
- package/src/view/dcps/forecast/index.vue +22 -50
- package/src/view/dcps/forecast/list-refresh.ts +11 -0
- package/src/view/dcps/forecast/row-cache.ts +18 -0
- package/src/view/dcps/forecast/utils.ts +169 -0
- package/src/view/dcps/injection/components/InjectionDetailDialog.vue +2 -0
- package/src/view/dcps/injection/index.vue +4 -6
- package/src/view/dcps/write-off/api.ts +9 -164
- package/src/view/dcps/write-off/components/FlowWriteOffView.vue +4 -3
- package/src/view/dcps/write-off/components/ForecastWriteOffView.vue +4 -3
- package/src/view/dcps/write-off/index.vue +6 -7
- package/src/view/dcps/write-off/utils.ts +131 -0
package/README.md
CHANGED
|
@@ -14,18 +14,18 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
|
|
|
14
14
|
|
|
15
15
|
## 全局布局
|
|
16
16
|
|
|
17
|
-
应用外壳位于 `src/layout/index.vue`,由 `App.vue`
|
|
17
|
+
应用外壳位于 `src/layout/index.vue`,由 `App.vue` 通过 `RouterView` 挂载,统一承载侧边导航、折叠状态和业务页面内容。菜单配置在 `src/router/menus.ts`,路由表在 `src/router/index.ts`,新增业务页面时两处各追加一项即可。业务地址、浏览器前进后退和刷新都由 vue-router 驱动;头寸预报列表页由 `KeepAlive` 缓存,返回时保留筛选和分页状态。
|
|
18
18
|
|
|
19
19
|
## 数字人民币头寸预报
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
默认入口为数字人民币头寸预报(`/forecast`),侧边菜单可切换至其他数字人民币页面。
|
|
22
22
|
|
|
23
23
|
- 列表页只保留筛选卡片和表格卡片;筛选条件压在一行,窗口过窄时才换行。
|
|
24
|
-
-
|
|
24
|
+
- 新增、修改、详情不使用弹窗,分别对应 `/forecast/create`、`/forecast/:predRefNo/edit`、`/forecast/:predRefNo` 独立路由,页面顶部提供“返回列表”按钮;返回后列表的筛选和分页状态不变。
|
|
25
25
|
- 支持交易日期、客户、交易对手、机构、金额范围及业务代码筛选,分页、详情、新增、修改和批量删除。
|
|
26
26
|
- 金额、账号及流水号使用字符串处理。业务状态与类型代码保留接口原值,待服务端提供字典后再接入中文选项。
|
|
27
27
|
- 导入、导出按钮保留在列表工具栏,文件由后端接口生成和解析,页面不再在前端拼装 CSV。
|
|
28
|
-
- 模块目录与授权页面保持一致:`api.ts`
|
|
28
|
+
- 模块目录与授权页面保持一致:`api.ts` 只放接口定义,`utils.ts` 放日期、金额、表单校验等共用工具,`typs.ts` 类型,`runtime.ts` 默认交易日期,`index.vue` 为入口,`components/` 放页面外壳和列表、新增修改、详情页面。接口定义只维护在各自模块下,不再集中到 `src/api`。
|
|
29
29
|
- 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
|
|
30
30
|
|
|
31
31
|
本地后端的预报接口当前使用通用模拟响应,写入成功响应不代表真实持久化。
|
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.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"axios": "^1.20.0",
|
|
30
30
|
"dayjs": "^1.11.23",
|
|
31
31
|
"element-plus": "^2.14.7",
|
|
32
|
-
"vue": "^3.5.42"
|
|
32
|
+
"vue": "^3.5.42",
|
|
33
|
+
"vue-router": "^4.6.4"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@tsconfig/node24": "^24.0.5",
|
package/src/layout/index.vue
CHANGED
|
@@ -1,38 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onMounted, ref
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import DigitalRmbForecast from '@/view/dcps/forecast/index.vue'
|
|
6
|
-
import DigitalRmbInjection from '@/view/dcps/injection/index.vue'
|
|
7
|
-
import DigitalRmbWriteOff from '@/view/dcps/write-off/index.vue'
|
|
8
|
-
import DigitalRmbPositionEstimation from '@/view/dcps/estimation/index.vue'
|
|
9
|
-
|
|
10
|
-
interface LayoutMenu {
|
|
11
|
-
/** 菜单唯一标识,同时作为 KeepAlive 内的页面缓存键。 */
|
|
12
|
-
key: string
|
|
13
|
-
title: string
|
|
14
|
-
compactTitle: string
|
|
15
|
-
component: Component
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// 全局布局的菜单配置:新增业务页面时只需在这里追加一项。
|
|
19
|
-
const menus: LayoutMenu[] = [
|
|
20
|
-
{ key: 'forecast', title: '数字人民币头寸预报', compactTitle: '头寸预报', component: DigitalRmbForecast },
|
|
21
|
-
{ key: 'estimation', title: '数字人民币头寸匡算', compactTitle: '匡算', component: DigitalRmbPositionEstimation },
|
|
22
|
-
{ key: 'injection', title: '数字人民币注资明细查询', compactTitle: '注资明细', component: DigitalRmbInjection },
|
|
23
|
-
{ key: 'balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询', component: DigitalRmbAccountBalance },
|
|
24
|
-
{ key: 'auth', title: '数字人民币授权', compactTitle: '授权', component: DigitalRmbAuthorization },
|
|
25
|
-
{ key: 'write-off', title: '数字人民币核销', compactTitle: '核销', component: DigitalRmbWriteOff },
|
|
26
|
-
]
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
import { RouterView, useRoute, useRouter } from 'vue-router'
|
|
4
|
+
import { layoutMenus as menus } from '@/router/menus'
|
|
27
5
|
|
|
6
|
+
const route = useRoute()
|
|
7
|
+
const router = useRouter()
|
|
28
8
|
const firstMenu = menus[0]!
|
|
29
9
|
const collapsed = ref(false)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
10
|
+
// 子页面通过 meta.menu 指向所属一级菜单,进入新增、详情时侧边栏仍保持高亮。
|
|
11
|
+
const activeMenu = computed(() => route.meta.menu ?? String(route.name ?? firstMenu.name))
|
|
12
|
+
const selectMenu = (name: string) => {
|
|
13
|
+
if (name !== route.name) void router.push({ name })
|
|
14
|
+
}
|
|
36
15
|
|
|
37
16
|
onMounted(() => {
|
|
38
17
|
collapsed.value = window.innerWidth <= 760
|
|
@@ -54,9 +33,9 @@ onMounted(() => {
|
|
|
54
33
|
<el-menu
|
|
55
34
|
class="dcps-menu"
|
|
56
35
|
:default-active="activeMenu"
|
|
57
|
-
@select="
|
|
36
|
+
@select="selectMenu"
|
|
58
37
|
>
|
|
59
|
-
<el-menu-item v-for="menu in menus" :key="menu.
|
|
38
|
+
<el-menu-item v-for="menu in menus" :key="menu.name" :index="menu.name">
|
|
60
39
|
<span v-if="collapsed" class="menu-label menu-label--compact">{{ menu.compactTitle }}</span>
|
|
61
40
|
<span v-else class="menu-label">{{ menu.title }}</span>
|
|
62
41
|
</el-menu-item>
|
|
@@ -82,9 +61,15 @@ onMounted(() => {
|
|
|
82
61
|
</aside>
|
|
83
62
|
|
|
84
63
|
<div class="layout-content">
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
64
|
+
<!-- 头寸预报列表进入新增、修改或详情后保持挂载,返回时筛选条件和分页不变。 -->
|
|
65
|
+
<RouterView v-slot="{ Component, route: currentRoute }">
|
|
66
|
+
<KeepAlive include="DcpsForecastPage">
|
|
67
|
+
<component
|
|
68
|
+
:is="Component"
|
|
69
|
+
:key="currentRoute.name === 'forecast' ? 'forecast' : currentRoute.fullPath"
|
|
70
|
+
/>
|
|
71
|
+
</KeepAlive>
|
|
72
|
+
</RouterView>
|
|
88
73
|
</div>
|
|
89
74
|
</div>
|
|
90
75
|
</template>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
|
2
|
+
import AppLayout from '@/layout/index.vue'
|
|
3
|
+
import DigitalRmbAuthorization from '@/view/dcps/auth/index.vue'
|
|
4
|
+
import DigitalRmbAccountBalance from '@/view/dcps/balance/index.vue'
|
|
5
|
+
import DigitalRmbForecast from '@/view/dcps/forecast/index.vue'
|
|
6
|
+
import ForecastEditorView from '@/view/dcps/forecast/components/ForecastEditorView.vue'
|
|
7
|
+
import ForecastDetailView from '@/view/dcps/forecast/components/ForecastDetailView.vue'
|
|
8
|
+
import DigitalRmbInjection from '@/view/dcps/injection/index.vue'
|
|
9
|
+
import DigitalRmbWriteOff from '@/view/dcps/write-off/index.vue'
|
|
10
|
+
import DigitalRmbPositionEstimation from '@/view/dcps/estimation/index.vue'
|
|
11
|
+
import { recallForecastRow } from '@/view/dcps/forecast/row-cache'
|
|
12
|
+
|
|
13
|
+
declare module 'vue-router' {
|
|
14
|
+
interface RouteMeta {
|
|
15
|
+
/** 一级菜单路由名;子页面用它保持侧边菜单高亮。 */
|
|
16
|
+
menu?: string
|
|
17
|
+
/** 浏览器标题使用的页面名称。 */
|
|
18
|
+
title?: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const routes: RouteRecordRaw[] = [
|
|
23
|
+
{
|
|
24
|
+
path: '/',
|
|
25
|
+
component: AppLayout,
|
|
26
|
+
redirect: { name: 'forecast' },
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
path: 'forecast',
|
|
30
|
+
name: 'forecast',
|
|
31
|
+
component: DigitalRmbForecast,
|
|
32
|
+
meta: { menu: 'forecast', title: '数字人民币头寸预报' },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: 'forecast/create',
|
|
36
|
+
name: 'forecast-create',
|
|
37
|
+
component: ForecastEditorView,
|
|
38
|
+
meta: { menu: 'forecast', title: '新增数字人民币头寸预报' },
|
|
39
|
+
props: (route) => ({
|
|
40
|
+
mode: 'create',
|
|
41
|
+
tranDate: typeof route.query.tranDate === 'string' ? route.query.tranDate : undefined,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path: 'forecast/:predRefNo/edit',
|
|
46
|
+
name: 'forecast-edit',
|
|
47
|
+
component: ForecastEditorView,
|
|
48
|
+
meta: { menu: 'forecast', title: '修改数字人民币头寸预报' },
|
|
49
|
+
props: (route) => {
|
|
50
|
+
const predRefNo = String(route.params.predRefNo ?? '')
|
|
51
|
+
return { mode: 'edit', predRefNo, row: recallForecastRow(predRefNo) }
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
path: 'forecast/:predRefNo',
|
|
56
|
+
name: 'forecast-detail',
|
|
57
|
+
component: ForecastDetailView,
|
|
58
|
+
meta: { menu: 'forecast', title: '数字人民币头寸预报详情' },
|
|
59
|
+
props: (route) => {
|
|
60
|
+
const predRefNo = String(route.params.predRefNo ?? '')
|
|
61
|
+
return { predRefNo, row: recallForecastRow(predRefNo) }
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
path: 'estimation',
|
|
66
|
+
name: 'estimation',
|
|
67
|
+
component: DigitalRmbPositionEstimation,
|
|
68
|
+
meta: { menu: 'estimation', title: '数字人民币头寸匡算' },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
path: 'injection',
|
|
72
|
+
name: 'injection',
|
|
73
|
+
component: DigitalRmbInjection,
|
|
74
|
+
meta: { menu: 'injection', title: '数字人民币注资明细查询' },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
path: 'balance',
|
|
78
|
+
name: 'balance',
|
|
79
|
+
component: DigitalRmbAccountBalance,
|
|
80
|
+
meta: { menu: 'balance', title: '数字人民币账户行余额查询申请' },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
path: 'auth',
|
|
84
|
+
name: 'auth',
|
|
85
|
+
component: DigitalRmbAuthorization,
|
|
86
|
+
meta: { menu: 'auth', title: '数字人民币授权' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: 'write-off',
|
|
90
|
+
name: 'write-off',
|
|
91
|
+
component: DigitalRmbWriteOff,
|
|
92
|
+
meta: { menu: 'write-off', title: '数字人民币核销' },
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{ path: '/:pathMatch(.*)*', redirect: { name: 'forecast' } },
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
const router = createRouter({
|
|
100
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
101
|
+
routes,
|
|
102
|
+
scrollBehavior: () => ({ top: 0 }),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
router.afterEach((to) => {
|
|
106
|
+
const title = to.meta.title
|
|
107
|
+
document.title = title ? `${title} · DCPS` : 'DCPS 资金头寸管理'
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
export default router
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** 侧边菜单项;name 与对应的一级路由名保持一致。 */
|
|
2
|
+
export interface LayoutMenu {
|
|
3
|
+
name: string
|
|
4
|
+
title: string
|
|
5
|
+
compactTitle: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 侧边菜单配置:新增业务页面时在这里追加一项,并在 router 中添加对应路由。
|
|
9
|
+
export const layoutMenus: LayoutMenu[] = [
|
|
10
|
+
{ name: 'forecast', title: '数字人民币头寸预报', compactTitle: '头寸预报' },
|
|
11
|
+
{ name: 'estimation', title: '数字人民币头寸匡算', compactTitle: '匡算' },
|
|
12
|
+
{ name: 'injection', title: '数字人民币注资明细查询', compactTitle: '注资明细' },
|
|
13
|
+
{ name: 'balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询' },
|
|
14
|
+
{ name: 'auth', title: '数字人民币授权', compactTitle: '授权' },
|
|
15
|
+
{ name: 'write-off', title: '数字人民币核销', compactTitle: '核销' },
|
|
16
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# DCPS 页面组件与路由对应关系
|
|
2
|
+
|
|
3
|
+
本文档记录 `src/view/dcps` 下各组件的名称(`defineOptions({ name })`)与路由的对应关系。
|
|
4
|
+
新增或调整页面时,请同步更新此文档。
|
|
5
|
+
|
|
6
|
+
## 命名约定
|
|
7
|
+
|
|
8
|
+
- 所有 `src/view/dcps` 下的 Vue 组件,`name` 统一以 `Dcps` 开头(PascalCase)。
|
|
9
|
+
- 页面入口统一为各业务目录下的 `index.vue`,命名为 `Dcps<模块>Page`。
|
|
10
|
+
- 页面内部的组件按职责命名:`...List` / `...Editor` / `...Detail` / `...Dialog` / `...Shell`。
|
|
11
|
+
- 组件 `name` 与路由 `name` 是两套独立标识:路由名使用 kebab-case(如 `forecast-create`),组件名使用 PascalCase(如 `DcpsForecastEditor`)。
|
|
12
|
+
|
|
13
|
+
## 路由与页面组件
|
|
14
|
+
|
|
15
|
+
路由定义在 `src/router/index.ts`,所有业务页面都挂在 `AppLayout`(`src/layout/index.vue`)下。
|
|
16
|
+
|
|
17
|
+
| 路由路径 | 路由 name | 页面文件 | 组件 name(defineOptions) | 页面标题(meta.title) |
|
|
18
|
+
| --- | --- | --- | --- | --- |
|
|
19
|
+
| `/forecast` | `forecast` | `forecast/index.vue` | `DcpsForecastPage` | 数字人民币头寸预报 |
|
|
20
|
+
| `/forecast/create` | `forecast-create` | `forecast/components/ForecastEditorView.vue` | `DcpsForecastEditor` | 新增数字人民币头寸预报 |
|
|
21
|
+
| `/forecast/:predRefNo/edit` | `forecast-edit` | `forecast/components/ForecastEditorView.vue` | `DcpsForecastEditor` | 修改数字人民币头寸预报 |
|
|
22
|
+
| `/forecast/:predRefNo` | `forecast-detail` | `forecast/components/ForecastDetailView.vue` | `DcpsForecastDetail` | 数字人民币头寸预报详情 |
|
|
23
|
+
| `/estimation` | `estimation` | `estimation/index.vue` | `DcpsEstimationPage` | 数字人民币头寸匡算 |
|
|
24
|
+
| `/injection` | `injection` | `injection/index.vue` | `DcpsInjectionPage` | 数字人民币注资明细查询 |
|
|
25
|
+
| `/balance` | `balance` | `balance/index.vue` | `DcpsBalancePage` | 数字人民币账户行余额查询申请 |
|
|
26
|
+
| `/auth` | `auth` | `auth/index.vue` | `DcpsAuthPage` | 数字人民币授权 |
|
|
27
|
+
| `/write-off` | `write-off` | `write-off/index.vue` | `DcpsWriteOffPage` | 数字人民币核销 |
|
|
28
|
+
| `/` | — | 重定向到 `forecast` | — | — |
|
|
29
|
+
| `/:pathMatch(.*)*` | — | 重定向到 `forecast`(404 兜底) | — | — |
|
|
30
|
+
|
|
31
|
+
补充说明:
|
|
32
|
+
|
|
33
|
+
- `forecast-create` 与 `forecast-edit` 共用同一组件 `DcpsForecastEditor`,通过 `props.mode`(`create` / `edit`)区分模式;修改模式通过 `props.predRefNo` 接收路由参数。
|
|
34
|
+
- `forecast-detail` 通过 `props.predRefNo` 接收路由参数。
|
|
35
|
+
- 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应的一级路由 `name` 保持一致。
|
|
36
|
+
- `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报列表页,子页面通过 `meta.menu` 保持侧边菜单高亮。若其他页面也需要缓存,需要同时满足:页面组件定义 `name`,并把该名称加入 `include`。
|
|
37
|
+
|
|
38
|
+
## 非路由子组件
|
|
39
|
+
|
|
40
|
+
以下组件不直接对应路由,由页面组件在内部引用:
|
|
41
|
+
|
|
42
|
+
| 组件文件 | 组件 name | 使用位置 | 说明 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `forecast/components/ForecastPageShell.vue` | `DcpsForecastPageShell` | `DcpsForecastEditor`、`DcpsForecastDetail` | 新增 / 修改 / 详情页共用外壳,提供返回按钮和标题 |
|
|
45
|
+
| `forecast/components/ForecastListView.vue` | `DcpsForecastList` | `DcpsForecastPage` | 头寸预报列表,负责查询与展示,通过事件向外抛出新增 / 修改 / 详情 |
|
|
46
|
+
| `estimation/components/EstimationDetailDialog.vue` | `DcpsEstimationDetailDialog` | `DcpsEstimationPage` | 匡算单元格详情弹窗 |
|
|
47
|
+
| `injection/components/InjectionDetailDialog.vue` | `DcpsInjectionDetailDialog` | `DcpsInjectionPage` | 注资明细详情弹窗 |
|
|
48
|
+
| `auth/components/AuthorizationDetailDialog.vue` | `DcpsAuthDetailDialog` | `DcpsAuthPage` | 授权任务详情弹窗 |
|
|
49
|
+
| `write-off/components/ForecastWriteOffView.vue` | `DcpsForecastWriteOff` | `DcpsWriteOffPage` | 核销页上方预报信息表格 |
|
|
50
|
+
| `write-off/components/FlowWriteOffView.vue` | `DcpsFlowWriteOff` | `DcpsWriteOffPage` | 核销页下方流水信息表格 |
|
|
51
|
+
|
|
52
|
+
## 新增页面检查清单
|
|
53
|
+
|
|
54
|
+
1. 在 `src/view/dcps/<模块>/` 下新建 `index.vue`,并添加 `defineOptions({ name: 'Dcps<模块>Page' })`。
|
|
55
|
+
2. 同目录按需补充 `api.ts`、`typs.ts`、`utils.ts` 及 `components/` 子组件,子组件同样使用 `Dcps` 前缀命名。
|
|
56
|
+
3. 在 `src/router/index.ts` 的 `children` 中新增路由,配置 `path`、`name`、`component`、`meta.menu`、`meta.title`。
|
|
57
|
+
4. 如需出现在侧边菜单,在 `src/router/menus.ts` 中追加菜单项,`name` 与一级路由名一致。
|
|
58
|
+
5. 如需缓存页面,把组件 `name` 加入 `src/layout/index.vue` 中 `<KeepAlive>` 的 `include`。
|
|
59
|
+
6. 更新本文档的对应关系表。
|
|
@@ -7,6 +7,8 @@ import API from './api'
|
|
|
7
7
|
import { authorizationRuntime } from '../runtime'
|
|
8
8
|
import type { AuthorizationFilters, AuthorizationQueryParams, AuthorizationTask } from './typs'
|
|
9
9
|
|
|
10
|
+
defineOptions({ name: 'DcpsAuthPage' })
|
|
11
|
+
|
|
10
12
|
type Decision = 'approve' | 'reject'
|
|
11
13
|
type AmountSortOrder = 'ascending' | 'descending' | null
|
|
12
14
|
|
|
@@ -513,16 +515,12 @@ onBeforeUnmount(() => {
|
|
|
513
515
|
<style scoped>
|
|
514
516
|
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,让列表内部滚动并保留分页。 */
|
|
515
517
|
.dcps-page {
|
|
516
|
-
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
517
|
-
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
518
|
-
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
519
|
-
--host-layout-height: 0px;
|
|
520
518
|
box-sizing: border-box;
|
|
521
519
|
display: flex;
|
|
522
520
|
flex-direction: column;
|
|
523
521
|
gap: 5px;
|
|
524
|
-
height: calc(100vh - var(--host-layout-height));
|
|
525
|
-
height: calc(100dvh - var(--host-layout-height));
|
|
522
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
523
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
526
524
|
min-height: 0;
|
|
527
525
|
min-width: 0;
|
|
528
526
|
padding: 5px;
|
|
@@ -4,6 +4,8 @@ import { ElMessage } from 'element-plus'
|
|
|
4
4
|
import API from './api'
|
|
5
5
|
import type { AccountBalanceInfo, InjectionCreateParams } from './typs'
|
|
6
6
|
|
|
7
|
+
defineOptions({ name: 'DcpsBalancePage' })
|
|
8
|
+
|
|
7
9
|
// 余额信息、请求与加载状态。
|
|
8
10
|
const balance = ref<AccountBalanceInfo | null>(null) // 当前展示的账户行余额信息,未查询到时为 null。
|
|
9
11
|
const loading = ref(false) // 查询是否进行中,同时控制查询按钮状态。
|
|
@@ -335,16 +337,12 @@ onBeforeUnmount(() => {
|
|
|
335
337
|
<style scoped>
|
|
336
338
|
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,内容由滚动区承载。 */
|
|
337
339
|
.dcps-page {
|
|
338
|
-
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
339
|
-
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
340
|
-
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
341
|
-
--host-layout-height: 0px;
|
|
342
340
|
box-sizing: border-box;
|
|
343
341
|
display: flex;
|
|
344
342
|
flex-direction: column;
|
|
345
343
|
gap: 5px;
|
|
346
|
-
height: calc(100vh - var(--host-layout-height));
|
|
347
|
-
height: calc(100dvh - var(--host-layout-height));
|
|
344
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
345
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
348
346
|
min-height: 0;
|
|
349
347
|
min-width: 0;
|
|
350
348
|
padding: 5px;
|
|
@@ -1,159 +1,34 @@
|
|
|
1
1
|
import { postDigitalRmb } from '@/utils/digital-rmb-request'
|
|
2
|
-
import type { ApiResponse } from '@/utils/request'
|
|
3
2
|
import type {
|
|
4
|
-
|
|
5
|
-
Tam140503001InBody,
|
|
6
|
-
|
|
3
|
+
EstimationQueryParams,
|
|
4
|
+
Tam140503001InBody,
|
|
5
|
+
Tam140503001OutBody,
|
|
6
|
+
Tam140503002InBody,
|
|
7
|
+
Tam140503002OutBody,
|
|
8
|
+
Tam140503003InBody,
|
|
9
|
+
Tam140503003OutBody,
|
|
10
|
+
Tam140503004InBody,
|
|
11
|
+
Tam140503004OutBody,
|
|
7
12
|
} from './typs'
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 请求报文和接口类型均在本模块内维护。
|
|
15
|
+
* 数字人民币头寸匡算接口。
|
|
16
|
+
* 查询 140503001、导出 140503002、详情 140503003、历史预计调拨金额 140503004。
|
|
13
17
|
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 匡算查询(140503001)。
|
|
17
|
-
* @param params tranDate 为交易日期;predictAllotInAmt、predictAllotOutAmt
|
|
18
|
-
* 为今日预计调拨收入、支付金额。返回十个单元格金额,C11、D11 回显本次输入。
|
|
19
|
-
*/
|
|
18
|
+
export default {
|
|
19
|
+
/** 匡算查询;tranDate 为交易日期,返回十个单元格金额。 */
|
|
20
20
|
queryListApi: (params: EstimationQueryParams) =>
|
|
21
21
|
postDigitalRmb<Tam140503001InBody, Tam140503001OutBody>('/TAM/0001/140503001', params),
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* 匡算导出(140503002):入参与查询完全一致,响应只有 fileName。
|
|
25
|
-
* 下载协议待文件服务确认,页面导出按钮暂未接线。
|
|
26
|
-
*/
|
|
23
|
+
/** 匡算导出;入参与查询一致,响应只有 fileName。 */
|
|
27
24
|
exportApi: (params: EstimationQueryParams) =>
|
|
28
25
|
postDigitalRmb<Tam140503002InBody, Tam140503002OutBody>('/TAM/0001/140503002', params),
|
|
29
26
|
|
|
30
|
-
/**
|
|
31
|
-
* 匡算详情(140503003)。
|
|
32
|
-
* @param params position 为报表单元格位置编码,tranDate 为交易日期;
|
|
33
|
-
* 返回该单元格对应的预报列表、流水列表和总条数。
|
|
34
|
-
*/
|
|
27
|
+
/** 匡算详情;position 为单元格位置编码,tranDate 为交易日期。 */
|
|
35
28
|
getDetailApi: (params: Tam140503003InBody) =>
|
|
36
29
|
postDigitalRmb<Tam140503003InBody, Tam140503003OutBody>('/TAM/0001/140503003', params),
|
|
37
30
|
|
|
38
|
-
/**
|
|
39
|
-
* 历史预计调拨金额查询(140503004)。
|
|
40
|
-
* @param tranDate 交易日期。返回该日期的预计调拨收支金额,用于预填查询条件。
|
|
41
|
-
*/
|
|
31
|
+
/** 查询指定日期的历史预计调拨金额,用于预填查询条件。 */
|
|
42
32
|
queryHistoryApi: (tranDate: string) =>
|
|
43
|
-
postDigitalRmb<Tam140503004InBody, Tam140503004OutBody>('/TAM/0001/140503004', { tranDate })
|
|
33
|
+
postDigitalRmb<Tam140503004InBody, Tam140503004OutBody>('/TAM/0001/140503004', { tranDate })
|
|
44
34
|
}
|
|
45
|
-
|
|
46
|
-
/** 校验公共响应头;retCode 不是 00000 时按服务端提示或 fallback 抛错。 */
|
|
47
|
-
export function ensureSuccess<T>(response: ApiResponse<T>, fallback: string): T | null {
|
|
48
|
-
if (response?.head?.retCode !== '00000') {
|
|
49
|
-
throw new Error(response?.head?.retMsg?.trim() || fallback)
|
|
50
|
-
}
|
|
51
|
-
return response.body
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** 提取可展示的错误信息;无法识别时给出统一提示。 */
|
|
55
|
-
export function getErrorMessage(error: unknown): string {
|
|
56
|
-
if (error instanceof Error && error.message.trim()) return error.message
|
|
57
|
-
if (typeof error === 'string' && error.trim()) return error
|
|
58
|
-
return '操作失败,请稍后重试'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 业务日期始终按上海时区生成,不依赖浏览器所在时区,格式为 YYYYMMDD。 */
|
|
62
|
-
export function getToday(): string {
|
|
63
|
-
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
64
|
-
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
|
65
|
-
}).formatToParts(new Date())
|
|
66
|
-
const read = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value || ''
|
|
67
|
-
return read('year') + read('month') + read('day')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** 判断是否为合法的 YYYYMMDD 日期。 */
|
|
71
|
-
export function isValidDate(value: string): boolean {
|
|
72
|
-
if (!/^\d{8}$/.test(value)) return false
|
|
73
|
-
const year = Number(value.slice(0, 4))
|
|
74
|
-
const month = Number(value.slice(4, 6))
|
|
75
|
-
const day = Number(value.slice(6, 8))
|
|
76
|
-
if (year < 1 || month < 1 || month > 12 || day < 1) return false
|
|
77
|
-
const leap = year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
|
|
78
|
-
const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
79
|
-
return day <= (monthDays[month - 1] || 0)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** 只接受十进制文本和合法千分位;保留全部小数位,避免改变输入精度。 */
|
|
83
|
-
export function normalizeAmount(value: string): string {
|
|
84
|
-
let text = value.trim()
|
|
85
|
-
if (text.includes(',')) {
|
|
86
|
-
if (!/^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text)) return ''
|
|
87
|
-
text = text.replace(/,/g, '')
|
|
88
|
-
}
|
|
89
|
-
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(text)
|
|
90
|
-
if (!match || (!match[2] && !match[3])) return ''
|
|
91
|
-
const integer = (match[2] || '0').replace(/^0+(?=\d)/, '')
|
|
92
|
-
const fraction = match[3] || ''
|
|
93
|
-
const negative = match[1] === '-' && /[1-9]/.test(integer + fraction)
|
|
94
|
-
return `${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** 千分位展示至少两位小数,超过两位的小数原样保留,不做浮点数转换或舍入。 */
|
|
98
|
-
export function formatAmount(value?: string): string {
|
|
99
|
-
const original = value?.trim() || ''
|
|
100
|
-
if (!original) return '—'
|
|
101
|
-
const normalized = normalizeAmount(original)
|
|
102
|
-
if (!normalized) return original
|
|
103
|
-
const negative = normalized.startsWith('-')
|
|
104
|
-
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
105
|
-
const grouped = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
106
|
-
return `${negative ? '-' : ''}${grouped}.${fraction.padEnd(2, '0')}`
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 计算轧差(收入减支出);任一金额无法解析时返回空字符串。
|
|
111
|
-
* 两侧金额按小数位对齐后用 BigInt 相减,避免浮点误差;
|
|
112
|
-
* emptyAsZero 为 true 时空文本按 0 参与计算(用于输入框的实时轧差)。
|
|
113
|
-
*/
|
|
114
|
-
export function calcGapAmount(inAmt: string, outAmt: string, emptyAsZero = false): string {
|
|
115
|
-
const parse = (value: string) => {
|
|
116
|
-
if (!value?.trim() && emptyAsZero) value = '0'
|
|
117
|
-
const normalized = normalizeAmount(value)
|
|
118
|
-
if (!normalized) return null
|
|
119
|
-
const negative = normalized.startsWith('-')
|
|
120
|
-
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
121
|
-
return { negative, integer, fraction }
|
|
122
|
-
}
|
|
123
|
-
const inPart = parse(inAmt)
|
|
124
|
-
const outPart = parse(outAmt)
|
|
125
|
-
if (!inPart || !outPart) return ''
|
|
126
|
-
const scale = Math.max(inPart.fraction.length, outPart.fraction.length)
|
|
127
|
-
const toScaled = (part: { negative: boolean; integer: string; fraction: string }): bigint => {
|
|
128
|
-
const digits = `${part.integer}${part.fraction.padEnd(scale, '0')}`
|
|
129
|
-
return BigInt(digits) * (part.negative ? -1n : 1n)
|
|
130
|
-
}
|
|
131
|
-
const diff = toScaled(inPart) - toScaled(outPart)
|
|
132
|
-
const negative = diff < 0n
|
|
133
|
-
const digits = (negative ? -diff : diff).toString().padStart(scale + 1, '0')
|
|
134
|
-
const integer = digits.slice(0, digits.length - scale)
|
|
135
|
-
const fraction = scale ? digits.slice(digits.length - scale) : ''
|
|
136
|
-
return formatAmount(`${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** 收支方向字典:I 来账、O 往账。 */
|
|
140
|
-
export const payDirecLabels: Record<string, string> = { I: '来账', O: '往账' }
|
|
141
|
-
/** 支付渠道:预报与流水的数字人民币分别为 8、5;其余渠道保留原文。 */
|
|
142
|
-
export const payChannelLabels: Record<string, string> = { '5': '数字人民币', '8': '数字人民币' }
|
|
143
|
-
/** 核销状态字典:1 未核销、2 已核销。 */
|
|
144
|
-
export const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销' }
|
|
145
|
-
/** 业务类型字典:201 兑回、202 兑出、203 付现;与核销预报使用同一套枚举。 */
|
|
146
|
-
export const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
147
|
-
|
|
148
|
-
/** 读取字典文案;空值显示横线,未登记的代码保留原文展示。 */
|
|
149
|
-
export function dictLabel(labels: Record<string, string>, value?: string): string {
|
|
150
|
-
const text = value?.trim() || ''
|
|
151
|
-
return text ? labels[text] ?? text : '—'
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** 详情明细展示的统一空值处理;空值显示横线。 */
|
|
155
|
-
export function displayText(value?: string): string {
|
|
156
|
-
return value?.trim() || '—'
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export default API
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { onBeforeUnmount, ref } from 'vue'
|
|
3
3
|
import { ElMessage } from 'element-plus'
|
|
4
|
-
import API
|
|
4
|
+
import API from '../api'
|
|
5
|
+
import { busiTypeLabels, dictLabel, displayText, formatAmount, getErrorMessage, payChannelLabels, payDirecLabels, verifyStatusLabels } from '../utils'
|
|
5
6
|
import type { EstimationCell, EstimationPredRow, EstimationTranRow } from '../typs'
|
|
6
7
|
|
|
8
|
+
defineOptions({ name: 'DcpsEstimationDetailDialog' })
|
|
9
|
+
|
|
7
10
|
// 匡算详情弹窗:按 position、tranDate 查询单元格对应的预报与流水明细。
|
|
8
11
|
// 弹窗开启时由父页面挂载(destroy-on-close),挂载后立即请求详情。
|
|
9
12
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
3
|
import { ElMessage } from 'element-plus'
|
|
4
|
-
import API
|
|
4
|
+
import API from './api'
|
|
5
|
+
import {
|
|
5
6
|
calcGapAmount, ensureSuccess, formatAmount, getErrorMessage, getToday, isValidDate, normalizeAmount,
|
|
6
|
-
} from './
|
|
7
|
+
} from './utils'
|
|
7
8
|
import EstimationDetailDialog from './components/EstimationDetailDialog.vue'
|
|
8
9
|
import type { EstimationAmounts, EstimationCell, EstimationQueryParams } from './typs'
|
|
9
10
|
|
|
11
|
+
defineOptions({ name: 'DcpsEstimationPage' })
|
|
12
|
+
|
|
10
13
|
// 数字人民币头寸匡算页面:
|
|
11
14
|
// 查询条件为交易日期加上今日预计调拨收入、支出金额;交易日期变化后调用
|
|
12
15
|
// 历史预计调拨金额接口(140503004)预填金额,允许手工调整后重新匡算。
|
|
@@ -360,16 +363,12 @@ onBeforeUnmount(() => {
|
|
|
360
363
|
<style scoped>
|
|
361
364
|
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,内容由报表卡片承载。 */
|
|
362
365
|
.dcps-page {
|
|
363
|
-
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
364
|
-
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
365
|
-
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
366
|
-
--host-layout-height: 0px;
|
|
367
366
|
box-sizing: border-box;
|
|
368
367
|
display: flex;
|
|
369
368
|
flex-direction: column;
|
|
370
369
|
gap: 5px;
|
|
371
|
-
height: calc(100vh - var(--host-layout-height));
|
|
372
|
-
height: calc(100dvh - var(--host-layout-height));
|
|
370
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
371
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
373
372
|
min-height: 0;
|
|
374
373
|
min-width: 0;
|
|
375
374
|
padding: 5px;
|