vue3-element-plus-app-chu 0.0.10 → 0.0.11
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 -0
- package/package.json +1 -1
- package/src/{view/dcps/layout → layout}/index.vue +38 -38
- package/src/view/dcps/digital-rmb-authorization/index.vue +9 -9
- package/src/view/dcps/digital-rmb-forecast/components/ForecastEditorView.vue +2 -2
- package/src/view/dcps/digital-rmb-forecast/components/ForecastListView.vue +2 -2
- package/src/view/dcps/digital-rmb-position-estimation/api.ts +154 -0
- package/src/view/dcps/digital-rmb-position-estimation/components/EstimationDetailDialog.vue +222 -0
- package/src/view/dcps/digital-rmb-position-estimation/index.vue +484 -0
- package/src/view/dcps/digital-rmb-position-estimation/typs.ts +29 -0
- package/src/view/dcps/runtime.ts +38 -0
- package/src/view/dcps/digital-rmb-authorization/runtime.ts +0 -16
- package/src/view/dcps/digital-rmb-forecast/runtime.ts +0 -8
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
|
|
|
12
12
|
|
|
13
13
|
一键启动命令通过 `npm-run-all2` 并行执行两个子命令,输出带 `[dev:frontend]` / `[dev:backend]` 前缀。
|
|
14
14
|
|
|
15
|
+
## 全局布局
|
|
16
|
+
|
|
17
|
+
应用外壳位于 `src/layout/index.vue`,由 `App.vue` 挂载,统一承载侧边导航、折叠状态和业务页面内容。菜单与页面的对应关系集中在文件顶部的 `menus` 配置中,新增业务页面时追加一项即可;页面切换由 `KeepAlive` 保留各自状态。
|
|
18
|
+
|
|
15
19
|
## 数字人民币头寸预报
|
|
16
20
|
|
|
17
21
|
默认入口为数字人民币头寸预报,侧边菜单可切换至原有数字人民币授权页面;切换时保留各自列表状态。
|
package/package.json
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { onMounted, ref } from 'vue'
|
|
3
|
-
import DigitalRmbAuthorization from '
|
|
4
|
-
import DigitalRmbAccountBalance from '
|
|
5
|
-
import DigitalRmbForecast from '
|
|
6
|
-
import DigitalRmbInjection from '
|
|
7
|
-
import DigitalRmbWriteOff from '
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
2
|
+
import { computed, onMounted, ref, type Component } from 'vue'
|
|
3
|
+
import DigitalRmbAuthorization from '@/view/dcps/digital-rmb-authorization/index.vue'
|
|
4
|
+
import DigitalRmbAccountBalance from '@/view/dcps/digital-rmb-account-balance/index.vue'
|
|
5
|
+
import DigitalRmbForecast from '@/view/dcps/digital-rmb-forecast/index.vue'
|
|
6
|
+
import DigitalRmbInjection from '@/view/dcps/digital-rmb-injection/index.vue'
|
|
7
|
+
import DigitalRmbWriteOff from '@/view/dcps/digital-rmb-write-off/index.vue'
|
|
8
|
+
import DigitalRmbPositionEstimation from '@/view/dcps/digital-rmb-position-estimation/index.vue'
|
|
9
|
+
|
|
10
|
+
interface LayoutMenu {
|
|
11
|
+
/** 菜单唯一标识,同时作为 KeepAlive 内的页面缓存键。 */
|
|
12
|
+
key: string
|
|
13
|
+
title: string
|
|
14
|
+
compactTitle: string
|
|
15
|
+
component: Component
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
+
// 全局布局的菜单配置:新增业务页面时只需在这里追加一项。
|
|
19
|
+
const menus: LayoutMenu[] = [
|
|
20
|
+
{ key: 'digital-rmb-forecast', title: '数字人民币头寸预报', compactTitle: '头寸预报', component: DigitalRmbForecast },
|
|
21
|
+
{ key: 'digital-rmb-position-estimation', title: '数字人民币头寸匡算', compactTitle: '匡算', component: DigitalRmbPositionEstimation },
|
|
22
|
+
{ key: 'digital-rmb-injection', title: '数字人民币注资明细查询', compactTitle: '注资明细', component: DigitalRmbInjection },
|
|
23
|
+
{ key: 'digital-rmb-account-balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询', component: DigitalRmbAccountBalance },
|
|
24
|
+
{ key: 'digital-rmb-authorization', title: '数字人民币授权', compactTitle: '授权', component: DigitalRmbAuthorization },
|
|
25
|
+
{ key: 'digital-rmb-write-off', title: '数字人民币核销', compactTitle: '核销', component: DigitalRmbWriteOff },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const firstMenu = menus[0]!
|
|
18
29
|
const collapsed = ref(false)
|
|
19
|
-
const activeMenu = ref(
|
|
30
|
+
const activeMenu = ref(firstMenu.key)
|
|
31
|
+
|
|
32
|
+
// 当前菜单对应的页面组件;切换时由 KeepAlive 保留各自状态。
|
|
33
|
+
const activeComponent = computed(
|
|
34
|
+
() => menus.find((menu) => menu.key === activeMenu.value)?.component ?? firstMenu.component,
|
|
35
|
+
)
|
|
20
36
|
|
|
21
37
|
onMounted(() => {
|
|
22
38
|
collapsed.value = window.innerWidth <= 760
|
|
@@ -24,7 +40,7 @@ onMounted(() => {
|
|
|
24
40
|
</script>
|
|
25
41
|
|
|
26
42
|
<template>
|
|
27
|
-
<div class="
|
|
43
|
+
<div class="app-layout" :class="{ 'is-collapsed': collapsed }">
|
|
28
44
|
<aside class="side-rail">
|
|
29
45
|
<div class="rail-brand">
|
|
30
46
|
<div class="brand-symbol">¥</div>
|
|
@@ -40,25 +56,9 @@ onMounted(() => {
|
|
|
40
56
|
:default-active="activeMenu"
|
|
41
57
|
@select="(index: string) => activeMenu = index"
|
|
42
58
|
>
|
|
43
|
-
<el-menu-item index="
|
|
44
|
-
<span v-if="collapsed" class="menu-label menu-label--compact"
|
|
45
|
-
<span v-else class="menu-label"
|
|
46
|
-
</el-menu-item>
|
|
47
|
-
<el-menu-item index="digital-rmb-injection">
|
|
48
|
-
<span v-if="collapsed" class="menu-label menu-label--compact">注资明细</span>
|
|
49
|
-
<span v-else class="menu-label">数字人民币注资明细查询</span>
|
|
50
|
-
</el-menu-item>
|
|
51
|
-
<el-menu-item index="digital-rmb-account-balance">
|
|
52
|
-
<span v-if="collapsed" class="menu-label menu-label--compact">余额查询</span>
|
|
53
|
-
<span v-else class="menu-label">数字人民币账户行余额查询申请</span>
|
|
54
|
-
</el-menu-item>
|
|
55
|
-
<el-menu-item index="digital-rmb-authorization">
|
|
56
|
-
<span v-if="collapsed" class="menu-label menu-label--compact">授权</span>
|
|
57
|
-
<span v-else class="menu-label">数字人民币授权</span>
|
|
58
|
-
</el-menu-item>
|
|
59
|
-
<el-menu-item index="digital-rmb-write-off">
|
|
60
|
-
<span v-if="collapsed" class="menu-label menu-label--compact">核销</span>
|
|
61
|
-
<span v-else class="menu-label">数字人民币核销</span>
|
|
59
|
+
<el-menu-item v-for="menu in menus" :key="menu.key" :index="menu.key">
|
|
60
|
+
<span v-if="collapsed" class="menu-label menu-label--compact">{{ menu.compactTitle }}</span>
|
|
61
|
+
<span v-else class="menu-label">{{ menu.title }}</span>
|
|
62
62
|
</el-menu-item>
|
|
63
63
|
</el-menu>
|
|
64
64
|
|
|
@@ -83,14 +83,14 @@ onMounted(() => {
|
|
|
83
83
|
|
|
84
84
|
<div class="layout-content">
|
|
85
85
|
<KeepAlive>
|
|
86
|
-
<component :is="
|
|
86
|
+
<component :is="activeComponent" />
|
|
87
87
|
</KeepAlive>
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
90
90
|
</template>
|
|
91
91
|
|
|
92
92
|
<style scoped>
|
|
93
|
-
.
|
|
93
|
+
.app-layout {
|
|
94
94
|
display: flex;
|
|
95
95
|
min-height: 100vh;
|
|
96
96
|
background: #f7f8fa;
|
|
@@ -4,7 +4,7 @@ import { ElMessage } from 'element-plus'
|
|
|
4
4
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
5
5
|
import AuthorizationDetailDialog from './components/AuthorizationDetailDialog.vue'
|
|
6
6
|
import API from './api'
|
|
7
|
-
import
|
|
7
|
+
import { authorizationRuntime } from '../runtime'
|
|
8
8
|
import type { AuthorizationFilters, AuthorizationQueryParams, AuthorizationTask } from './typs'
|
|
9
9
|
|
|
10
10
|
type Decision = 'approve' | 'reject'
|
|
@@ -20,8 +20,8 @@ const defaultFilters: AuthorizationFilters = {
|
|
|
20
20
|
}
|
|
21
21
|
// 默认创建日期范围:[开始日期, 结束日期],格式为 YYYYMMDD,查询重置时使用。
|
|
22
22
|
const defaultDates: [string, string] = [
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
authorizationRuntime.createTmBegin,
|
|
24
|
+
authorizationRuntime.createTmEnd,
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
// 重复输入项共用一份配置,依次为字段、名称和长度限制。
|
|
@@ -223,8 +223,8 @@ const getQueryFilters = () => ({
|
|
|
223
223
|
payeeName: filters.value.payeeName.trim(), // 收款人名称。
|
|
224
224
|
platfSeqNo: filters.value.platfSeqNo.trim(), // 平台流水号。
|
|
225
225
|
taskSts: filters.value.taskSts, // 任务状态,空值表示全部。
|
|
226
|
-
createTmBegin: dateRange.value?.[0] ||
|
|
227
|
-
createTmEnd: dateRange.value?.[1] || dateRange.value?.[0] ||
|
|
226
|
+
createTmBegin: dateRange.value?.[0] || authorizationRuntime.createTmBegin, // 创建开始日期,格式为 YYYYMMDD,未选择时取默认值。
|
|
227
|
+
createTmEnd: dateRange.value?.[1] || dateRange.value?.[0] || authorizationRuntime.createTmEnd, // 创建结束日期,格式为 YYYYMMDD,缺失时回退到开始日期或默认值。
|
|
228
228
|
})
|
|
229
229
|
// 仅查询、重置时更新;翻页和刷新沿用这份条件。
|
|
230
230
|
let appliedQuery = getQueryFilters()
|
|
@@ -308,8 +308,8 @@ const openDecisionDialog = (task: any) => {
|
|
|
308
308
|
const submitDecision = async (decision: Decision) => {
|
|
309
309
|
const platfSeqNo = selectedTask.value?.platfSeqNo?.trim() // 本次审批任务的平台流水号,用于唯一定位任务。
|
|
310
310
|
if (!canApprove(selectedTask.value) || !platfSeqNo || pendingDecision.value || loading.value) return
|
|
311
|
-
const approverId =
|
|
312
|
-
const approverName =
|
|
311
|
+
const approverId = authorizationRuntime.approverId.trim() // 本次提交的审批人工号,同时作为公共用户标识。
|
|
312
|
+
const approverName = authorizationRuntime.approverName.trim() // 本次提交的审批人姓名。
|
|
313
313
|
const remarkText = remark.value.trim() // 去除首尾空白后的备注或拒绝原因。
|
|
314
314
|
if (!approverId || !approverName) {
|
|
315
315
|
ElMessage.warning('审批人信息未设置')
|
|
@@ -471,12 +471,12 @@ onBeforeUnmount(() => {
|
|
|
471
471
|
<el-row :gutter="14">
|
|
472
472
|
<el-col :xs="24" :sm="12">
|
|
473
473
|
<el-form-item label="审批人工号">
|
|
474
|
-
<el-input :model-value="
|
|
474
|
+
<el-input :model-value="authorizationRuntime.approverId" disabled />
|
|
475
475
|
</el-form-item>
|
|
476
476
|
</el-col>
|
|
477
477
|
<el-col :xs="24" :sm="12">
|
|
478
478
|
<el-form-item label="审批人姓名">
|
|
479
|
-
<el-input :model-value="
|
|
479
|
+
<el-input :model-value="authorizationRuntime.approverName" readonly />
|
|
480
480
|
</el-form-item>
|
|
481
481
|
</el-col>
|
|
482
482
|
</el-row>
|
|
@@ -5,7 +5,7 @@ import ForecastPageShell from './ForecastPageShell.vue'
|
|
|
5
5
|
import API, {
|
|
6
6
|
createFormValues, ensureSuccess, getErrorMessage, getToday, normalizeAmount, validateForm,
|
|
7
7
|
} from '../api'
|
|
8
|
-
import
|
|
8
|
+
import { forecastRuntime } from '../../runtime'
|
|
9
9
|
import type {
|
|
10
10
|
ForecastCreateBody,
|
|
11
11
|
ForecastDetail,
|
|
@@ -115,7 +115,7 @@ const fieldPlaceholder = (field: EditorField) => {
|
|
|
115
115
|
const initializeForm = () => {
|
|
116
116
|
const nextValues = createFormValues()
|
|
117
117
|
if (props.mode === 'create') {
|
|
118
|
-
nextValues.tranDate = props.tranDate ||
|
|
118
|
+
nextValues.tranDate = props.tranDate || forecastRuntime.tranDate
|
|
119
119
|
nextValues.predDate = getToday()
|
|
120
120
|
} else {
|
|
121
121
|
for (const key of Object.keys(nextValues) as FieldKey[]) {
|
|
@@ -4,7 +4,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
4
4
|
import API, {
|
|
5
5
|
compareAmounts, ensureSuccess, formatAmount, formatDate, getErrorMessage, normalizeAmount,
|
|
6
6
|
} from '../api'
|
|
7
|
-
import
|
|
7
|
+
import { forecastRuntime } from '../../runtime'
|
|
8
8
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
9
9
|
import type { ForecastFilters, ForecastRow } from '../typs'
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ const emit = defineEmits<{
|
|
|
19
19
|
|
|
20
20
|
type ListMutation = 'delete' | null
|
|
21
21
|
const createFilters = (): ForecastFilters => ({
|
|
22
|
-
tranDate:
|
|
22
|
+
tranDate: forecastRuntime.tranDate, clientName: '', competName: '', payDirec: '', busiType: '', payChannel: '',
|
|
23
23
|
groundBranch: '', predStatus: '', verifyStatus: '', startTransAmt: '', endTransAmt: '', sortRule: '',
|
|
24
24
|
})
|
|
25
25
|
const filters = ref(createFilters())
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import sharedEstimationApi from '@/api/modules/digital-rmb/position-estimation'
|
|
2
|
+
import type { ApiResponse } from '@/utils/request'
|
|
3
|
+
import type {
|
|
4
|
+
EstimationAmounts, EstimationHistory, EstimationPredRow, EstimationQueryParams, EstimationTranRow,
|
|
5
|
+
} from './typs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 数字人民币头寸匡算页面接口与共用工具。
|
|
9
|
+
* 接口地址:查询 140503001、导出 140503002、详情 140503003、历史预计调拨金额 140503004;
|
|
10
|
+
* 请求报文沿用 @/api 中的数币接口定义。
|
|
11
|
+
*/
|
|
12
|
+
const API = {
|
|
13
|
+
/**
|
|
14
|
+
* 匡算查询(140503001)。
|
|
15
|
+
* @param params tranDate 为交易日期;predictAllotInAmt、predictAllotOutAmt
|
|
16
|
+
* 为今日预计调拨收入、支付金额。返回十个单元格金额,C11、D11 回显本次输入。
|
|
17
|
+
*/
|
|
18
|
+
queryListApi: (params: EstimationQueryParams) => sharedEstimationApi.queryListApi(params),
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 匡算导出(140503002):入参与查询完全一致,响应只有 fileName。
|
|
22
|
+
* 下载协议待文件服务确认,页面导出按钮暂未接线。
|
|
23
|
+
*/
|
|
24
|
+
exportApi: (params: EstimationQueryParams) => sharedEstimationApi.exportApi(params),
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 匡算详情(140503003)。
|
|
28
|
+
* @param params position 为报表单元格位置编码,tranDate 为交易日期;
|
|
29
|
+
* 返回该单元格对应的预报列表、流水列表和总条数。
|
|
30
|
+
*/
|
|
31
|
+
getDetailApi: (params: { position: string; tranDate: string }) =>
|
|
32
|
+
sharedEstimationApi.getDetailApi(params),
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 历史预计调拨金额查询(140503004)。
|
|
36
|
+
* @param tranDate 交易日期。返回该日期的预计调拨收支金额,用于预填查询条件。
|
|
37
|
+
*/
|
|
38
|
+
queryHistoryApi: (tranDate: string) => sharedEstimationApi.queryHistoryApi({ tranDate }),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 校验公共响应头;retCode 不是 00000 时按服务端提示或 fallback 抛错。 */
|
|
42
|
+
export function ensureSuccess<T>(response: ApiResponse<T>, fallback: string): T | null {
|
|
43
|
+
if (response?.head?.retCode !== '00000') {
|
|
44
|
+
throw new Error(response?.head?.retMsg?.trim() || fallback)
|
|
45
|
+
}
|
|
46
|
+
return response.body
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 提取可展示的错误信息;无法识别时给出统一提示。 */
|
|
50
|
+
export function getErrorMessage(error: unknown): string {
|
|
51
|
+
if (error instanceof Error && error.message.trim()) return error.message
|
|
52
|
+
if (typeof error === 'string' && error.trim()) return error
|
|
53
|
+
return '操作失败,请稍后重试'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 业务日期始终按上海时区生成,不依赖浏览器所在时区,格式为 YYYYMMDD。 */
|
|
57
|
+
export function getToday(): string {
|
|
58
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
59
|
+
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
|
60
|
+
}).formatToParts(new Date())
|
|
61
|
+
const read = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value || ''
|
|
62
|
+
return read('year') + read('month') + read('day')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 判断是否为合法的 YYYYMMDD 日期。 */
|
|
66
|
+
export function isValidDate(value: string): boolean {
|
|
67
|
+
if (!/^\d{8}$/.test(value)) return false
|
|
68
|
+
const year = Number(value.slice(0, 4))
|
|
69
|
+
const month = Number(value.slice(4, 6))
|
|
70
|
+
const day = Number(value.slice(6, 8))
|
|
71
|
+
if (year < 1 || month < 1 || month > 12 || day < 1) return false
|
|
72
|
+
const leap = year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
|
|
73
|
+
const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
74
|
+
return day <= (monthDays[month - 1] || 0)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 只接受十进制文本和合法千分位;保留全部小数位,避免改变输入精度。 */
|
|
78
|
+
export function normalizeAmount(value: string): string {
|
|
79
|
+
let text = value.trim()
|
|
80
|
+
if (text.includes(',')) {
|
|
81
|
+
if (!/^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text)) return ''
|
|
82
|
+
text = text.replace(/,/g, '')
|
|
83
|
+
}
|
|
84
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(text)
|
|
85
|
+
if (!match || (!match[2] && !match[3])) return ''
|
|
86
|
+
const integer = (match[2] || '0').replace(/^0+(?=\d)/, '')
|
|
87
|
+
const fraction = match[3] || ''
|
|
88
|
+
const negative = match[1] === '-' && /[1-9]/.test(integer + fraction)
|
|
89
|
+
return `${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 千分位展示至少两位小数,超过两位的小数原样保留,不做浮点数转换或舍入。 */
|
|
93
|
+
export function formatAmount(value?: string): string {
|
|
94
|
+
const original = value?.trim() || ''
|
|
95
|
+
if (!original) return '—'
|
|
96
|
+
const normalized = normalizeAmount(original)
|
|
97
|
+
if (!normalized) return original
|
|
98
|
+
const negative = normalized.startsWith('-')
|
|
99
|
+
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
100
|
+
const grouped = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
101
|
+
return `${negative ? '-' : ''}${grouped}.${fraction.padEnd(2, '0')}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 计算轧差(收入减支出);任一金额无法解析时返回空字符串。
|
|
106
|
+
* 两侧金额按小数位对齐后用 BigInt 相减,避免浮点误差;
|
|
107
|
+
* emptyAsZero 为 true 时空文本按 0 参与计算(用于输入框的实时轧差)。
|
|
108
|
+
*/
|
|
109
|
+
export function calcGapAmount(inAmt: string, outAmt: string, emptyAsZero = false): string {
|
|
110
|
+
const parse = (value: string) => {
|
|
111
|
+
if (!value?.trim() && emptyAsZero) value = '0'
|
|
112
|
+
const normalized = normalizeAmount(value)
|
|
113
|
+
if (!normalized) return null
|
|
114
|
+
const negative = normalized.startsWith('-')
|
|
115
|
+
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
116
|
+
return { negative, integer, fraction }
|
|
117
|
+
}
|
|
118
|
+
const inPart = parse(inAmt)
|
|
119
|
+
const outPart = parse(outAmt)
|
|
120
|
+
if (!inPart || !outPart) return ''
|
|
121
|
+
const scale = Math.max(inPart.fraction.length, outPart.fraction.length)
|
|
122
|
+
const toScaled = (part: { negative: boolean; integer: string; fraction: string }): bigint => {
|
|
123
|
+
const digits = `${part.integer}${part.fraction.padEnd(scale, '0')}`
|
|
124
|
+
return BigInt(digits) * (part.negative ? -1n : 1n)
|
|
125
|
+
}
|
|
126
|
+
const diff = toScaled(inPart) - toScaled(outPart)
|
|
127
|
+
const negative = diff < 0n
|
|
128
|
+
const digits = (negative ? -diff : diff).toString().padStart(scale + 1, '0')
|
|
129
|
+
const integer = digits.slice(0, digits.length - scale)
|
|
130
|
+
const fraction = scale ? digits.slice(digits.length - scale) : ''
|
|
131
|
+
return formatAmount(`${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 收支方向字典:I 来账、O 往账。 */
|
|
135
|
+
export const payDirecLabels: Record<string, string> = { I: '来账', O: '往账' }
|
|
136
|
+
/** 支付渠道:预报与流水的数字人民币分别为 8、5;其余渠道保留原文。 */
|
|
137
|
+
export const payChannelLabels: Record<string, string> = { '5': '数字人民币', '8': '数字人民币' }
|
|
138
|
+
/** 核销状态字典:1 未核销、2 已核销。 */
|
|
139
|
+
export const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销' }
|
|
140
|
+
/** 业务类型字典:201 兑回、202 兑出、203 付现;与核销预报使用同一套枚举。 */
|
|
141
|
+
export const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
142
|
+
|
|
143
|
+
/** 读取字典文案;空值显示横线,未登记的代码保留原文展示。 */
|
|
144
|
+
export function dictLabel(labels: Record<string, string>, value?: string): string {
|
|
145
|
+
const text = value?.trim() || ''
|
|
146
|
+
return text ? labels[text] ?? text : '—'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 详情明细展示的统一空值处理;空值显示横线。 */
|
|
150
|
+
export function displayText(value?: string): string {
|
|
151
|
+
return value?.trim() || '—'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default API
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount, ref } from 'vue'
|
|
3
|
+
import { ElMessage } from 'element-plus'
|
|
4
|
+
import API, { busiTypeLabels, dictLabel, displayText, formatAmount, getErrorMessage, payChannelLabels, payDirecLabels, verifyStatusLabels } from '../api'
|
|
5
|
+
import type { EstimationCell, EstimationPredRow, EstimationTranRow } from '../typs'
|
|
6
|
+
|
|
7
|
+
// 匡算详情弹窗:按 position、tranDate 查询单元格对应的预报与流水明细。
|
|
8
|
+
// 弹窗开启时由父页面挂载(destroy-on-close),挂载后立即请求详情。
|
|
9
|
+
|
|
10
|
+
// 父页面传入的弹窗显隐状态、单击的报表单元格和本次匡算的交易日期。
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
modelValue: boolean // 弹窗是否显示。
|
|
13
|
+
cell: EstimationCell | null // 单击的单元格,含位置编码、展示名称和金额文本。
|
|
14
|
+
tranDate: string // 详情查询的交易日期,取自最近一次匡算查询。
|
|
15
|
+
}>()
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
'update:modelValue': [visible: boolean] // 向父页面同步弹窗显隐状态。
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
// 详情数据与加载状态。
|
|
21
|
+
const predList = ref<EstimationPredRow[]>([]) // 详情返回的预报明细列表。
|
|
22
|
+
const tranList = ref<EstimationTranRow[]>([]) // 详情返回的流水明细列表。
|
|
23
|
+
const totalCount = ref('') // 详情返回的明细总条数。
|
|
24
|
+
const loading = ref(false) // 详情请求是否进行中。
|
|
25
|
+
const hasError = ref(false) // 详情请求是否失败,失败时提示重新打开再试。
|
|
26
|
+
let requestId = 0 // 最新请求的序号,用于忽略过期响应。
|
|
27
|
+
let disposed = false // 弹窗是否已卸载,避免未完成请求继续更新界面。
|
|
28
|
+
|
|
29
|
+
/** 查询单元格明细;cell 或交易日期缺失时不发起请求,直接呈现空态。 */
|
|
30
|
+
const query = async () => {
|
|
31
|
+
const position = props.cell?.position?.trim()
|
|
32
|
+
const date = props.tranDate.trim()
|
|
33
|
+
if (!position || !date) return
|
|
34
|
+
const currentRequestId = ++requestId
|
|
35
|
+
loading.value = true
|
|
36
|
+
try {
|
|
37
|
+
const { head, body } = await API.getDetailApi({ position, tranDate: date }) as any
|
|
38
|
+
if (currentRequestId !== requestId || disposed) return
|
|
39
|
+
if (head?.retCode !== '00000') {
|
|
40
|
+
throw new Error(head?.retMsg || '查询失败')
|
|
41
|
+
}
|
|
42
|
+
predList.value = Array.isArray(body?.predList) ? body.predList : []
|
|
43
|
+
tranList.value = Array.isArray(body?.tranList) ? body.tranList : []
|
|
44
|
+
totalCount.value = String(body?.totalCount ?? '').trim()
|
|
45
|
+
hasError.value = false
|
|
46
|
+
} catch (error) { // error 为详情请求或响应数据校验异常。
|
|
47
|
+
if (currentRequestId !== requestId || disposed) return
|
|
48
|
+
predList.value = []
|
|
49
|
+
tranList.value = []
|
|
50
|
+
totalCount.value = ''
|
|
51
|
+
hasError.value = true
|
|
52
|
+
ElMessage.error(getErrorMessage(error))
|
|
53
|
+
} finally {
|
|
54
|
+
if (currentRequestId === requestId) loading.value = false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 弹窗内容配合 destroy-on-close 每次打开重新挂载表格;明细请求由 el-dialog 的 open 事件触发,
|
|
59
|
+
// 组件实例常驻不重复挂载,onMounted 只会执行一次而不能承担每次打开的查询。
|
|
60
|
+
// 弹窗关闭后,未完成的请求不再更新界面或弹出消息。
|
|
61
|
+
onBeforeUnmount(() => {
|
|
62
|
+
disposed = true
|
|
63
|
+
requestId += 1
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/** 合并预报、流水列表得到明细总条数;接口 totalCount 缺失时按列表长度兜底展示。 */
|
|
67
|
+
const resolveTotal = () => {
|
|
68
|
+
if (/^\d+$/.test(totalCount.value)) return totalCount.value
|
|
69
|
+
return String(predList.value.length + tranList.value.length)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 预报信息表格的行键:预报流水号可能缺失或重复,固定追加下标保证唯一。
|
|
73
|
+
const predRowKey = (row: EstimationPredRow, index: number) => `${row.predRefNo?.trim() || 'pred'}-${index}`
|
|
74
|
+
// 流水信息表格的行键:交易参考号同理追加下标保证唯一。
|
|
75
|
+
const tranRowKey = (row: EstimationTranRow, index: number) => `${row.refNo?.trim() || 'tran'}-${index}`
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<el-dialog
|
|
80
|
+
:model-value="modelValue"
|
|
81
|
+
:title="cell ? `匡算明细 - ${cell.label}` : '匡算明细'"
|
|
82
|
+
width="min(1080px, calc(100vw - 32px))"
|
|
83
|
+
top="16px"
|
|
84
|
+
append-to-body
|
|
85
|
+
destroy-on-close
|
|
86
|
+
@open="query"
|
|
87
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
88
|
+
>
|
|
89
|
+
<el-scrollbar class="detail-scrollbar">
|
|
90
|
+
<div class="detail-content">
|
|
91
|
+
<!-- 上下文摘要:被点击单元格的位置、金额与详情查询的交易日期。 -->
|
|
92
|
+
<header class="detail-summary">
|
|
93
|
+
<div>
|
|
94
|
+
<span class="detail-label">位置编码</span>
|
|
95
|
+
<strong class="detail-position">{{ cell?.position || '—' }}</strong>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<span class="detail-label">单元格金额(元)</span>
|
|
99
|
+
<strong class="detail-amount">{{ formatAmount(cell?.amount) }}</strong>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<span class="detail-label">交易日期</span>
|
|
103
|
+
<strong>{{ tranDate }}</strong>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="detail-reference">
|
|
106
|
+
<span class="detail-label">明细总条数</span>
|
|
107
|
+
<strong>{{ resolveTotal() }}</strong>
|
|
108
|
+
</div>
|
|
109
|
+
</header>
|
|
110
|
+
|
|
111
|
+
<div v-loading="loading" class="detail-tables">
|
|
112
|
+
<!-- 预报信息明细;列表为空时展示统一空态。 -->
|
|
113
|
+
<div class="dialog-section-title">预报信息</div>
|
|
114
|
+
<el-table :data="predList" size="small" border max-height="240px" empty-text="暂无预报信息">
|
|
115
|
+
<el-table-column label="预报流水号" min-width="150" show-overflow-tooltip>
|
|
116
|
+
<template #default="{ row }">{{ displayText(row.predRefNo) }}</template>
|
|
117
|
+
</el-table-column>
|
|
118
|
+
<el-table-column label="业务类型" width="90" align="center">
|
|
119
|
+
<template #default="{ row }">{{ dictLabel(busiTypeLabels, row.busiType) }}</template>
|
|
120
|
+
</el-table-column>
|
|
121
|
+
<el-table-column label="客户名称" min-width="140" show-overflow-tooltip>
|
|
122
|
+
<template #default="{ row }">{{ displayText(row.clientName) }}</template>
|
|
123
|
+
</el-table-column>
|
|
124
|
+
<el-table-column label="交易对手名称" min-width="140" show-overflow-tooltip>
|
|
125
|
+
<template #default="{ row }">{{ displayText(row.competName) }}</template>
|
|
126
|
+
</el-table-column>
|
|
127
|
+
<el-table-column label="收支方向" width="80" align="center">
|
|
128
|
+
<template #default="{ row }">{{ dictLabel(payDirecLabels, row.payDirec) }}</template>
|
|
129
|
+
</el-table-column>
|
|
130
|
+
<el-table-column label="支付渠道" width="110" align="center">
|
|
131
|
+
<template #default="{ row }">{{ dictLabel(payChannelLabels, row.payChannel) }}</template>
|
|
132
|
+
</el-table-column>
|
|
133
|
+
<el-table-column label="预报金额(元)" width="140" align="right">
|
|
134
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.predAmt) }}</span></template>
|
|
135
|
+
</el-table-column>
|
|
136
|
+
<el-table-column label="已核销金额(元)" width="140" align="right">
|
|
137
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.verifyAmt) }}</span></template>
|
|
138
|
+
</el-table-column>
|
|
139
|
+
<el-table-column label="核销状态" width="90" align="center">
|
|
140
|
+
<template #default="{ row }">{{ dictLabel(verifyStatusLabels, row.verifyStatus) }}</template>
|
|
141
|
+
</el-table-column>
|
|
142
|
+
<el-table-column label="落地支行" min-width="130" show-overflow-tooltip>
|
|
143
|
+
<template #default="{ row }">{{ displayText(row.groundBranch) }}</template>
|
|
144
|
+
</el-table-column>
|
|
145
|
+
<el-table-column label="上级单位" min-width="130" show-overflow-tooltip>
|
|
146
|
+
<template #default="{ row }">{{ displayText(row.upOrg) }}</template>
|
|
147
|
+
</el-table-column>
|
|
148
|
+
</el-table>
|
|
149
|
+
|
|
150
|
+
<!-- 流水信息明细;失败的详情可重新打开弹窗重试。 -->
|
|
151
|
+
<div class="dialog-section-title">流水信息</div>
|
|
152
|
+
<el-table :data="tranList" size="small" border max-height="240px" empty-text="暂无流水信息">
|
|
153
|
+
<el-table-column label="交易参考号" min-width="150" show-overflow-tooltip>
|
|
154
|
+
<template #default="{ row }">{{ displayText(row.refNo) }}</template>
|
|
155
|
+
</el-table-column>
|
|
156
|
+
<el-table-column label="业务类型" width="90" align="center">
|
|
157
|
+
<template #default="{ row }">{{ dictLabel(busiTypeLabels, row.busiType) }}</template>
|
|
158
|
+
</el-table-column>
|
|
159
|
+
<el-table-column label="付款人名称" min-width="140" show-overflow-tooltip>
|
|
160
|
+
<template #default="{ row }">{{ displayText(row.payerName) }}</template>
|
|
161
|
+
</el-table-column>
|
|
162
|
+
<el-table-column label="收款人名称" min-width="140" show-overflow-tooltip>
|
|
163
|
+
<template #default="{ row }">{{ displayText(row.payeeName) }}</template>
|
|
164
|
+
</el-table-column>
|
|
165
|
+
<el-table-column label="收支方向" width="80" align="center">
|
|
166
|
+
<template #default="{ row }">{{ dictLabel(payDirecLabels, row.payDirec) }}</template>
|
|
167
|
+
</el-table-column>
|
|
168
|
+
<el-table-column label="支付渠道" width="110" align="center">
|
|
169
|
+
<template #default="{ row }">{{ dictLabel(payChannelLabels, row.payChannel) }}</template>
|
|
170
|
+
</el-table-column>
|
|
171
|
+
<el-table-column label="金额(元)" width="140" align="right">
|
|
172
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.amt) }}</span></template>
|
|
173
|
+
</el-table-column>
|
|
174
|
+
<el-table-column label="核销状态" width="90" align="center">
|
|
175
|
+
<template #default="{ row }">{{ dictLabel(verifyStatusLabels, row.verifyStatus) }}</template>
|
|
176
|
+
</el-table-column>
|
|
177
|
+
<el-table-column label="落地支行" min-width="130" show-overflow-tooltip>
|
|
178
|
+
<template #default="{ row }">{{ displayText(row.groundBranch) }}</template>
|
|
179
|
+
</el-table-column>
|
|
180
|
+
<el-table-column label="上级单位" min-width="130" show-overflow-tooltip>
|
|
181
|
+
<template #default="{ row }">{{ displayText(row.upOrg) }}</template>
|
|
182
|
+
</el-table-column>
|
|
183
|
+
</el-table>
|
|
184
|
+
|
|
185
|
+
<div v-if="hasError" class="detail-retry">详情查询失败,关闭弹窗后重新打开可重试。</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</el-scrollbar>
|
|
189
|
+
<template #footer>
|
|
190
|
+
<el-button @click="emit('update:modelValue', false)">关闭</el-button>
|
|
191
|
+
</template>
|
|
192
|
+
</el-dialog>
|
|
193
|
+
</template>
|
|
194
|
+
|
|
195
|
+
<style scoped>
|
|
196
|
+
.detail-scrollbar { max-height: calc(100vh - 240px); }
|
|
197
|
+
.detail-content { padding: 2px 4px 4px; }
|
|
198
|
+
|
|
199
|
+
/* 摘要区:位置编码、金额、日期放在一行,便于确认明细上下文。 */
|
|
200
|
+
.detail-summary {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-wrap: wrap;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 12px 36px;
|
|
205
|
+
margin-bottom: 12px;
|
|
206
|
+
padding: 12px 14px;
|
|
207
|
+
border-radius: 10px;
|
|
208
|
+
background: var(--el-fill-color-light);
|
|
209
|
+
}
|
|
210
|
+
.detail-label { display: block; margin-bottom: 5px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
211
|
+
.detail-summary strong { overflow-wrap: anywhere; font-size: 13px; }
|
|
212
|
+
.detail-amount { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; }
|
|
213
|
+
.detail-reference { margin-left: auto; }
|
|
214
|
+
.detail-reference strong { font-weight: 600; }
|
|
215
|
+
|
|
216
|
+
.dialog-section-title { margin: 12px 0 6px; color: var(--el-text-color-regular); font-size: 13px; font-weight: 600; }
|
|
217
|
+
.detail-tables .el-table + .el-table { margin-top: 2px; }
|
|
218
|
+
.detail-tables { min-height: 160px; }
|
|
219
|
+
.detail-tables .dialog-section-title:first-child { margin-top: 0; }
|
|
220
|
+
.amount-cell { color: var(--el-text-color-primary); font-variant-numeric: tabular-nums; }
|
|
221
|
+
.detail-retry { margin-top: 10px; color: var(--el-color-danger); font-size: 12px; text-align: center; }
|
|
222
|
+
</style>
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import { ElMessage } from 'element-plus'
|
|
4
|
+
import API, {
|
|
5
|
+
calcGapAmount, ensureSuccess, formatAmount, getErrorMessage, getToday, isValidDate, normalizeAmount,
|
|
6
|
+
} from './api'
|
|
7
|
+
import EstimationDetailDialog from './components/EstimationDetailDialog.vue'
|
|
8
|
+
import type { EstimationAmounts, EstimationCell, EstimationQueryParams } from './typs'
|
|
9
|
+
|
|
10
|
+
// 数字人民币头寸匡算页面:
|
|
11
|
+
// 查询条件为交易日期加上今日预计调拨收入、支出金额;交易日期变化后调用
|
|
12
|
+
// 历史预计调拨金额接口(140503004)预填金额,允许手工调整后重新匡算。
|
|
13
|
+
// 匡算结果(140503001)以合并单元格的头寸匡算表展示,样式与业务原型一致;
|
|
14
|
+
// 「今日预计调拨金额」行的收入、支付单元格就是查询条件输入框,随历史记录预填;
|
|
15
|
+
// 导出按钮暂未接线(下载协议待确认);单击其余金额单元格按位置编码查询详情(140503003)。
|
|
16
|
+
|
|
17
|
+
// 接口字段编码与图中报表位置的对应关系:
|
|
18
|
+
// C3 城商行支付清算账户前一日余额;C6/D6 报备金额;C7/D7 未核销金额;
|
|
19
|
+
// C8/D8 未核销业务流水;C11/D11 今日预计调拨金额(查询条件输入框,不查详情);
|
|
20
|
+
// B15 当日实时余额。轧差列由前端按「收入减支出」计算:结果行随查询结果更新,
|
|
21
|
+
// 调拨行随输入实时更新;日初可用资金、预留金额及两条空行暂无接口字段,先按原型占位留空。
|
|
22
|
+
const cellLabels: Record<string, string> = {
|
|
23
|
+
C3: '城商行支付清算账户前一日余额',
|
|
24
|
+
C6: '报备金额(收入)',
|
|
25
|
+
D6: '报备金额(支付)',
|
|
26
|
+
C7: '未核销金额(收入)',
|
|
27
|
+
D7: '未核销金额(支付)',
|
|
28
|
+
C8: '未核销业务流水(收入)',
|
|
29
|
+
D8: '未核销业务流水(支付)',
|
|
30
|
+
B15: '当日实时余额',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 读取接口金额文本;未返回的编码保持空字符串,展示为空白。
|
|
34
|
+
const amountOf = (position: string): string => {
|
|
35
|
+
const source = amounts.value as unknown as Record<string, unknown> | null
|
|
36
|
+
const value = source?.[position]
|
|
37
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 结果单元格展示文本;金额缺失时显示空白(按要求不显示占位横线)。 */
|
|
41
|
+
const amountText = (position: string): string => {
|
|
42
|
+
const value = amountOf(position)
|
|
43
|
+
return value ? formatAmount(value) : ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 组装单元格对象供详情弹窗使用:位置编码、展示名称与当前金额一起带入。 */
|
|
47
|
+
const cellOf = (position: string): EstimationCell => ({
|
|
48
|
+
position,
|
|
49
|
+
label: cellLabels[position] ?? position,
|
|
50
|
+
amount: amountOf(position),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// 查询条件:日期默认当天;预计调拨收支金额由历史接口预填。
|
|
54
|
+
const queryForm = reactive({ tranDate: getToday(), predictAllotInAmt: '', predictAllotOutAmt: '' })
|
|
55
|
+
// 各金额输入的校验提示,空字符串表示没有错误。
|
|
56
|
+
const formError = reactive({ predictAllotInAmt: '', predictAllotOutAmt: '' })
|
|
57
|
+
|
|
58
|
+
// 查询与预填可能并行;各自独立控制按钮加载状态。
|
|
59
|
+
const querying = ref(false) // 匡算查询是否进行中。
|
|
60
|
+
const prefilling = ref(false) // 历史预计调拨金额预填是否进行中。
|
|
61
|
+
let prefillRequestId = 0 // 最新预填请求的序号,用于忽略过期响应。
|
|
62
|
+
let disposed = false // 页面是否已卸载,避免未完成请求继续更新界面。
|
|
63
|
+
|
|
64
|
+
// 最近一次查询的结果与提交参数;未查询时为 null。
|
|
65
|
+
const amounts = ref<EstimationAmounts | null>(null)
|
|
66
|
+
const lastQueryParams = ref<EstimationQueryParams | null>(null)
|
|
67
|
+
|
|
68
|
+
/** 匡算结果是否就绪:就绪前其余单元格显示占位横线且不可点击。 */
|
|
69
|
+
const resultReady = computed(() => amounts.value !== null)
|
|
70
|
+
|
|
71
|
+
/** 详情查询的交易日期:优先取生成当前报表的查询日期,兜底当前选择的日期。 */
|
|
72
|
+
const queriedTranDate = computed(() => lastQueryParams.value?.tranDate || queryForm.tranDate)
|
|
73
|
+
|
|
74
|
+
/** 结果行的轧差文本(收入减支出);结果未就绪或金额缺失时显示空白。 */
|
|
75
|
+
const gapText = (inPosition: string, outPosition: string): string => {
|
|
76
|
+
if (!resultReady.value) return ''
|
|
77
|
+
return calcGapAmount(amountOf(inPosition), amountOf(outPosition))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 今日预计调拨金额行的实时轧差:随输入即时计算,空输入按 0 参与计算。 */
|
|
81
|
+
const inputGapText = computed(() =>
|
|
82
|
+
calcGapAmount(queryForm.predictAllotInAmt, queryForm.predictAllotOutAmt, true))
|
|
83
|
+
|
|
84
|
+
/** 校验金额文本:须为不小于 0、最多两位小数的十进制金额;返回错误提示文本。 */
|
|
85
|
+
const checkAmount = (value: string): string => {
|
|
86
|
+
const normalized = normalizeAmount(value)
|
|
87
|
+
if (!normalized || normalized.startsWith('-') || (normalized.split('.')[1]?.length || 0) > 2) {
|
|
88
|
+
return '须为不小于 0 的金额,最多保留两位小数'
|
|
89
|
+
}
|
|
90
|
+
return ''
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 汇总查询参数;校验不通过时写入字段错误并返回 null,查询与导出共用。 */
|
|
94
|
+
const collectQueryParams = (): EstimationQueryParams | null => {
|
|
95
|
+
const tranDate = queryForm.tranDate.trim()
|
|
96
|
+
if (!isValidDate(tranDate)) {
|
|
97
|
+
ElMessage.warning('请选择有效的交易日期')
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
formError.predictAllotInAmt = checkAmount(queryForm.predictAllotInAmt)
|
|
101
|
+
formError.predictAllotOutAmt = checkAmount(queryForm.predictAllotOutAmt)
|
|
102
|
+
if (formError.predictAllotInAmt || formError.predictAllotOutAmt) return null
|
|
103
|
+
return {
|
|
104
|
+
tranDate,
|
|
105
|
+
predictAllotInAmt: normalizeAmount(queryForm.predictAllotInAmt),
|
|
106
|
+
predictAllotOutAmt: normalizeAmount(queryForm.predictAllotOutAmt),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 按交易日期预填预计调拨收支金额;仅在预填请求为最新请求时允许更新表单。 */
|
|
111
|
+
const prefillHistory = async () => {
|
|
112
|
+
const tranDate = queryForm.tranDate.trim()
|
|
113
|
+
if (!isValidDate(tranDate)) return
|
|
114
|
+
const currentRequestId = ++prefillRequestId
|
|
115
|
+
prefilling.value = true
|
|
116
|
+
try {
|
|
117
|
+
const { head, body } = await API.queryHistoryApi(tranDate) as any
|
|
118
|
+
if (currentRequestId !== prefillRequestId || disposed) return
|
|
119
|
+
if (head?.retCode !== '00000') {
|
|
120
|
+
throw new Error(head?.retMsg || '查询失败')
|
|
121
|
+
}
|
|
122
|
+
queryForm.predictAllotInAmt = String(body?.predictAllotInAmt ?? '').trim()
|
|
123
|
+
queryForm.predictAllotOutAmt = String(body?.predictAllotOutAmt ?? '').trim()
|
|
124
|
+
formError.predictAllotInAmt = ''
|
|
125
|
+
formError.predictAllotOutAmt = ''
|
|
126
|
+
} catch (error) { // error 为历史接口请求或响应数据校验异常。
|
|
127
|
+
if (currentRequestId !== prefillRequestId || disposed) return
|
|
128
|
+
ElMessage.error(getErrorMessage(error))
|
|
129
|
+
} finally {
|
|
130
|
+
if (currentRequestId === prefillRequestId) prefilling.value = false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 交易日期变化:旧报表基于旧日期不再有效,清空结果并按新日期重新预填。 */
|
|
135
|
+
const onDateChange = () => {
|
|
136
|
+
amounts.value = null
|
|
137
|
+
lastQueryParams.value = null
|
|
138
|
+
formError.predictAllotInAmt = ''
|
|
139
|
+
formError.predictAllotOutAmt = ''
|
|
140
|
+
void prefillHistory()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 匡算查询:成功后展示头寸匡算表,失败时清空结果并提示错误。 */
|
|
144
|
+
const submitQuery = async () => {
|
|
145
|
+
if (querying.value) return
|
|
146
|
+
const params = collectQueryParams()
|
|
147
|
+
if (!params) return
|
|
148
|
+
querying.value = true
|
|
149
|
+
try {
|
|
150
|
+
const { head, body } = await API.queryListApi(params) as any
|
|
151
|
+
if (head?.retCode !== '00000') {
|
|
152
|
+
throw new Error(head?.retMsg || '匡算查询失败')
|
|
153
|
+
}
|
|
154
|
+
if (!body || typeof body !== 'object') {
|
|
155
|
+
throw new Error('服务端未返回匡算结果')
|
|
156
|
+
}
|
|
157
|
+
amounts.value = body as EstimationAmounts
|
|
158
|
+
lastQueryParams.value = params
|
|
159
|
+
} catch (error) { // error 为查询请求或响应数据校验异常。
|
|
160
|
+
amounts.value = null
|
|
161
|
+
lastQueryParams.value = null
|
|
162
|
+
ElMessage.error(getErrorMessage(error))
|
|
163
|
+
} finally {
|
|
164
|
+
querying.value = false
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 详情弹窗状态:cell 为被单击的报表单元格。
|
|
169
|
+
const detailVisible = ref(false)
|
|
170
|
+
const detailCell = ref<EstimationCell | null>(null)
|
|
171
|
+
|
|
172
|
+
/** 打开单元格明细弹窗;结果就绪前其余单元格为占位横线,不发起详情查询。 */
|
|
173
|
+
const openDetail = (cell?: EstimationCell) => {
|
|
174
|
+
if (!cell || !resultReady.value) return
|
|
175
|
+
detailCell.value = cell
|
|
176
|
+
detailVisible.value = true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 页面首次显示时按默认日期预填预计调拨收支金额。
|
|
180
|
+
onMounted(prefillHistory)
|
|
181
|
+
// 页面离开后,未完成的预填请求不再更新表单或弹出消息。
|
|
182
|
+
onBeforeUnmount(() => {
|
|
183
|
+
disposed = true
|
|
184
|
+
prefillRequestId += 1
|
|
185
|
+
})
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<template>
|
|
189
|
+
<main class="dcps-page">
|
|
190
|
+
<!-- 顶部操作区:交易日期选择与导出、查询入口(收支金额在下方表格内填写)。 -->
|
|
191
|
+
<el-card class="estimation-header" shadow="never" :body-style="{ padding: '10px 14px' }">
|
|
192
|
+
<div class="estimation-heading">
|
|
193
|
+
<div class="filter-field">
|
|
194
|
+
<label>交易日期</label>
|
|
195
|
+
<el-date-picker
|
|
196
|
+
v-model="queryForm.tranDate"
|
|
197
|
+
class="filter-date"
|
|
198
|
+
type="date"
|
|
199
|
+
value-format="YYYYMMDD"
|
|
200
|
+
:clearable="false"
|
|
201
|
+
@change="onDateChange"
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
<el-button type="primary" :loading="querying" @click="submitQuery">查询</el-button>
|
|
205
|
+
<el-button>导出</el-button>
|
|
206
|
+
</div>
|
|
207
|
+
</el-card>
|
|
208
|
+
|
|
209
|
+
<!-- 匡算报表:合并单元格的头寸匡算表,与业务原型一致;表格常显,结果就绪前金额显示占位。 -->
|
|
210
|
+
<el-card class="estimation-report" shadow="never" :body-style="{ padding: '12px 14px' }">
|
|
211
|
+
<div class="report-scroll">
|
|
212
|
+
<table class="report-table" :class="{ 'is-ready': resultReady }">
|
|
213
|
+
<!-- 列:两个左侧标签列、收入、支付、轧差。 -->
|
|
214
|
+
<colgroup>
|
|
215
|
+
<col class="col-l1"><col class="col-l2"><col class="col-in"><col class="col-out"><col class="col-net">
|
|
216
|
+
</colgroup>
|
|
217
|
+
<tbody>
|
|
218
|
+
<!-- 表头标题:头寸匡算表。 -->
|
|
219
|
+
<tr>
|
|
220
|
+
<td colspan="5" class="report-title">头寸匡算表</td>
|
|
221
|
+
</tr>
|
|
222
|
+
<!-- 前一日余额:C3 横跨收入、支付两列,右侧标注单位。 -->
|
|
223
|
+
<tr>
|
|
224
|
+
<td colspan="2" class="report-label">城商行支付清算账户前一日余额</td>
|
|
225
|
+
<td colspan="2" class="report-data">
|
|
226
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('C3'))">
|
|
227
|
+
<span class="report-amount">{{ amountText('C3') }}</span>
|
|
228
|
+
<span class="report-tag">C3</span>
|
|
229
|
+
</button>
|
|
230
|
+
</td>
|
|
231
|
+
<td class="report-label report-label--unit">单位:元</td>
|
|
232
|
+
</tr>
|
|
233
|
+
<!-- 收支表头:收入、支付、轧差。 -->
|
|
234
|
+
<tr>
|
|
235
|
+
<td colspan="2" class="report-label"></td>
|
|
236
|
+
<td class="report-label report-label--center">收入</td>
|
|
237
|
+
<td class="report-label report-label--center">支付</td>
|
|
238
|
+
<td class="report-label report-label--center">轧差</td>
|
|
239
|
+
</tr>
|
|
240
|
+
<!-- 报备情况组:报备金额(C6/D6)、未核销金额(C7/D7),轧差暂空。 -->
|
|
241
|
+
<tr>
|
|
242
|
+
<td rowspan="2" class="report-label">今日大额收付款报备情况</td>
|
|
243
|
+
<td class="report-label">报备金额</td>
|
|
244
|
+
<td class="report-data">
|
|
245
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('C6'))">
|
|
246
|
+
<span class="report-amount">{{ amountText('C6') }}</span>
|
|
247
|
+
<span class="report-tag">C6</span>
|
|
248
|
+
</button>
|
|
249
|
+
</td>
|
|
250
|
+
<td class="report-data">
|
|
251
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('D6'))">
|
|
252
|
+
<span class="report-amount">{{ amountText('D6') }}</span>
|
|
253
|
+
<span class="report-tag">D6</span>
|
|
254
|
+
</button>
|
|
255
|
+
</td>
|
|
256
|
+
<td class="report-gap" title="轧差 = 收入 − 支出(前端计算)">{{ gapText('C6', 'D6') }}</td>
|
|
257
|
+
</tr>
|
|
258
|
+
<tr>
|
|
259
|
+
<td class="report-label">未核销金额</td>
|
|
260
|
+
<td class="report-data">
|
|
261
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('C7'))">
|
|
262
|
+
<span class="report-amount">{{ amountText('C7') }}</span>
|
|
263
|
+
<span class="report-tag">C7</span>
|
|
264
|
+
</button>
|
|
265
|
+
</td>
|
|
266
|
+
<td class="report-data">
|
|
267
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('D7'))">
|
|
268
|
+
<span class="report-amount">{{ amountText('D7') }}</span>
|
|
269
|
+
<span class="report-tag">D7</span>
|
|
270
|
+
</button>
|
|
271
|
+
</td>
|
|
272
|
+
<td class="report-gap" title="轧差 = 收入 − 支出(前端计算)">{{ gapText('C7', 'D7') }}</td>
|
|
273
|
+
</tr>
|
|
274
|
+
<!-- 未核销业务流水:C8/D8,轧差暂空。 -->
|
|
275
|
+
<tr>
|
|
276
|
+
<td colspan="2" class="report-label">未核销业务流水</td>
|
|
277
|
+
<td class="report-data">
|
|
278
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('C8'))">
|
|
279
|
+
<span class="report-amount">{{ amountText('C8') }}</span>
|
|
280
|
+
<span class="report-tag">C8</span>
|
|
281
|
+
</button>
|
|
282
|
+
</td>
|
|
283
|
+
<td class="report-data">
|
|
284
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('D8'))">
|
|
285
|
+
<span class="report-amount">{{ amountText('D8') }}</span>
|
|
286
|
+
<span class="report-tag">D8</span>
|
|
287
|
+
</button>
|
|
288
|
+
</td>
|
|
289
|
+
<td class="report-gap" title="轧差 = 收入 − 支出(前端计算)">{{ gapText('C8', 'D8') }}</td>
|
|
290
|
+
</tr>
|
|
291
|
+
<!-- 日初可用资金:暂无接口字段,按原型留空。 -->
|
|
292
|
+
<tr>
|
|
293
|
+
<td colspan="2" class="report-label">日初可用资金</td>
|
|
294
|
+
<td colspan="3" class="report-blank"></td>
|
|
295
|
+
</tr>
|
|
296
|
+
<!-- 占位空行;与原型保持一致,后续接入新字段时在此填充。 -->
|
|
297
|
+
<tr>
|
|
298
|
+
<td colspan="5" class="report-blank report-blank--tall"></td>
|
|
299
|
+
</tr>
|
|
300
|
+
<!-- 今日预计调拨金额:C11/D11 为查询条件输入框,随历史记录预填,底色高亮。 -->
|
|
301
|
+
<tr>
|
|
302
|
+
<td colspan="2" class="report-label">今日预计调拨金额</td>
|
|
303
|
+
<td class="report-data report-data--high report-data--input">
|
|
304
|
+
<el-input
|
|
305
|
+
v-model="queryForm.predictAllotInAmt"
|
|
306
|
+
class="report-input"
|
|
307
|
+
inputmode="decimal"
|
|
308
|
+
placeholder="输入调拨收入金额"
|
|
309
|
+
:maxlength="40"
|
|
310
|
+
@input="formError.predictAllotInAmt = ''"
|
|
311
|
+
/>
|
|
312
|
+
<div v-if="formError.predictAllotInAmt" class="report-error">{{ formError.predictAllotInAmt }}</div>
|
|
313
|
+
</td>
|
|
314
|
+
<td class="report-data report-data--high report-data--input">
|
|
315
|
+
<el-input
|
|
316
|
+
v-model="queryForm.predictAllotOutAmt"
|
|
317
|
+
class="report-input"
|
|
318
|
+
inputmode="decimal"
|
|
319
|
+
placeholder="输入调拨支出金额"
|
|
320
|
+
:maxlength="40"
|
|
321
|
+
@input="formError.predictAllotOutAmt = ''"
|
|
322
|
+
/>
|
|
323
|
+
<div v-if="formError.predictAllotOutAmt" class="report-error">{{ formError.predictAllotOutAmt }}</div>
|
|
324
|
+
</td>
|
|
325
|
+
<td class="report-gap" title="轧差 = 收入 − 支出(前端计算)">{{ inputGapText }}</td>
|
|
326
|
+
</tr>
|
|
327
|
+
<!-- 清算账户预留金额:暂无接口字段,按原型留空。 -->
|
|
328
|
+
<tr>
|
|
329
|
+
<td colspan="2" class="report-label">城商行支付清算账户预留金额</td>
|
|
330
|
+
<td colspan="3" class="report-blank"></td>
|
|
331
|
+
</tr>
|
|
332
|
+
<!-- 占位空行。 -->
|
|
333
|
+
<tr>
|
|
334
|
+
<td colspan="5" class="report-blank report-blank--tall"></td>
|
|
335
|
+
</tr>
|
|
336
|
+
<!-- 分节标题:实时余额。 -->
|
|
337
|
+
<tr>
|
|
338
|
+
<td colspan="5" class="report-section">实时余额</td>
|
|
339
|
+
</tr>
|
|
340
|
+
<!-- 当日实时余额:B15 横跨收入、支付、轧差三列。 -->
|
|
341
|
+
<tr>
|
|
342
|
+
<td colspan="2" class="report-label">当日实时余额</td>
|
|
343
|
+
<td colspan="3" class="report-data">
|
|
344
|
+
<button type="button" class="report-value" @click="openDetail(cellOf('B15'))">
|
|
345
|
+
<span class="report-amount">{{ amountText('B15') }}</span>
|
|
346
|
+
<span class="report-tag">B15</span>
|
|
347
|
+
</button>
|
|
348
|
+
</td>
|
|
349
|
+
</tr>
|
|
350
|
+
</tbody>
|
|
351
|
+
</table>
|
|
352
|
+
</div>
|
|
353
|
+
</el-card>
|
|
354
|
+
|
|
355
|
+
<!-- 单元格明细弹窗:按位置编码查询预报与流水列表。 -->
|
|
356
|
+
<EstimationDetailDialog v-model="detailVisible" :cell="detailCell" :tran-date="queriedTranDate" />
|
|
357
|
+
</main>
|
|
358
|
+
</template>
|
|
359
|
+
|
|
360
|
+
<style scoped>
|
|
361
|
+
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,内容由报表卡片承载。 */
|
|
362
|
+
.dcps-page {
|
|
363
|
+
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
364
|
+
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
365
|
+
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
366
|
+
--host-layout-height: 0px;
|
|
367
|
+
box-sizing: border-box;
|
|
368
|
+
display: flex;
|
|
369
|
+
flex-direction: column;
|
|
370
|
+
gap: 5px;
|
|
371
|
+
height: calc(100vh - var(--host-layout-height));
|
|
372
|
+
height: calc(100dvh - var(--host-layout-height));
|
|
373
|
+
min-height: 0;
|
|
374
|
+
min-width: 0;
|
|
375
|
+
padding: 5px;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
background: var(--el-bg-color-page, #f7f8fa);
|
|
378
|
+
}
|
|
379
|
+
.estimation-header { flex: 0 0 auto; }
|
|
380
|
+
.estimation-heading { display: flex; align-items: flex-end; gap: 6px 14px; }
|
|
381
|
+
|
|
382
|
+
.filter-field label { display: block; margin-bottom: 5px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
383
|
+
/* 交易日期设定宽度:el-date-editor 默认宽度更高,其根节点不带本页
|
|
384
|
+
scoped 属性,需要经 :deep 从宿主命中。 */
|
|
385
|
+
.estimation-heading :deep(.filter-date) { width: 140px; }
|
|
386
|
+
|
|
387
|
+
.estimation-report { flex: 1; min-height: 0; min-width: 0; overflow: hidden; }
|
|
388
|
+
|
|
389
|
+
/* 头寸匡算表:合并单元格布局,深色细边框模拟原型报表。 */
|
|
390
|
+
.report-scroll { max-height: 100%; overflow: auto; }
|
|
391
|
+
.report-table {
|
|
392
|
+
/* 列宽参照原型:左标签区约三分之一,收入、支付列等宽,轧差列最窄。 */
|
|
393
|
+
--report-line: var(--el-text-color-primary, #303133);
|
|
394
|
+
width: 100%;
|
|
395
|
+
min-width: 760px;
|
|
396
|
+
border-collapse: collapse;
|
|
397
|
+
table-layout: fixed;
|
|
398
|
+
color: var(--el-text-color-primary);
|
|
399
|
+
}
|
|
400
|
+
.col-l1 { width: 20%; }
|
|
401
|
+
.col-l2 { width: 12%; }
|
|
402
|
+
.col-in { width: 29.5%; }
|
|
403
|
+
.col-out { width: 29.5%; }
|
|
404
|
+
.col-net { width: 9%; }
|
|
405
|
+
|
|
406
|
+
.report-table td {
|
|
407
|
+
border: 1px solid var(--report-line);
|
|
408
|
+
padding: 9px 12px;
|
|
409
|
+
font-size: 12px;
|
|
410
|
+
vertical-align: middle;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* 标题与分节:红色居中,与原型一致。 */
|
|
414
|
+
.report-title { height: 40px; font-size: 17px; font-weight: 700; text-align: center; }
|
|
415
|
+
.report-section { height: 52px; font-size: 15px; font-weight: 700; text-align: center; }
|
|
416
|
+
.report-title,
|
|
417
|
+
.report-section { color: var(--el-color-danger); }
|
|
418
|
+
|
|
419
|
+
/* 标签单元格:左侧自然对齐,表头行与单位列不强调。 */
|
|
420
|
+
.report-label { color: var(--el-text-color-primary); }
|
|
421
|
+
.report-label--center,
|
|
422
|
+
.report-label--unit { text-align: center; }
|
|
423
|
+
.report-label--unit { white-space: nowrap; }
|
|
424
|
+
|
|
425
|
+
/* 占位空单元格:日初可用资金与预留金额两行,整体稍矮、空行更高。 */
|
|
426
|
+
.report-blank { min-width: 0; }
|
|
427
|
+
.report-blank--tall { height: 34px; }
|
|
428
|
+
|
|
429
|
+
/* 轧差单元格:前端计算的净值(收入减支出),结果行随查询结果、调拨行随输入实时更新。 */
|
|
430
|
+
.report-gap { padding: 5px 12px; color: var(--el-text-color-regular); font-variant-numeric: tabular-nums; }
|
|
431
|
+
|
|
432
|
+
/* 高亮单元格:今日预计调拨金额按原型使用浅蓝底色。 */
|
|
433
|
+
.report-data--high { background: var(--el-color-primary-light-8); }
|
|
434
|
+
|
|
435
|
+
/* 输入单元格:收入、支付为查询条件,输入框与高亮底色融为一体。
|
|
436
|
+
使用 td 前缀提高优先级,避免被后方 .report-data 的 padding: 0 覆盖。 */
|
|
437
|
+
td.report-data--input { padding: 5px 8px; }
|
|
438
|
+
.report-input {
|
|
439
|
+
--el-input-text-color: var(--el-text-color-primary);
|
|
440
|
+
}
|
|
441
|
+
.report-input :deep(.el-input__wrapper) {
|
|
442
|
+
background: transparent;
|
|
443
|
+
box-shadow: none;
|
|
444
|
+
padding: 0;
|
|
445
|
+
}
|
|
446
|
+
.report-input :deep(.el-input__inner) { height: 26px; line-height: 26px; }
|
|
447
|
+
.report-input :deep(.el-input__wrapper.is-focus) { box-shadow: inset 0 0 0 1px var(--el-color-primary); border-radius: 4px; }
|
|
448
|
+
.report-input :deep(.el-input__inner::placeholder) { color: var(--el-text-color-placeholder); font-weight: 400; }
|
|
449
|
+
.report-error { margin-top: 2px; color: var(--el-color-danger); font-size: 10px; line-height: 1.3; }
|
|
450
|
+
|
|
451
|
+
/* 结果就绪前:单元格不可交互,仅展示占位横线。 */
|
|
452
|
+
.report-table:not(.is-ready) .report-value { cursor: default; }
|
|
453
|
+
.report-table:not(.is-ready) .report-value:hover { background: transparent; }
|
|
454
|
+
|
|
455
|
+
/* 金额单元格按钮:重置按钮样式,提示可点击查看明细。 */
|
|
456
|
+
.report-data { padding: 0; }
|
|
457
|
+
.report-value {
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
width: 100%;
|
|
462
|
+
min-height: 35px;
|
|
463
|
+
padding: 4px 12px;
|
|
464
|
+
border: 0;
|
|
465
|
+
background: transparent;
|
|
466
|
+
color: inherit;
|
|
467
|
+
font: inherit;
|
|
468
|
+
text-align: left;
|
|
469
|
+
cursor: pointer;
|
|
470
|
+
}
|
|
471
|
+
.report-value:hover { background: var(--el-fill-color-light); }
|
|
472
|
+
.report-value:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: -2px; }
|
|
473
|
+
.report-amount { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
474
|
+
.report-tag {
|
|
475
|
+
flex: 0 0 auto;
|
|
476
|
+
padding: 0 6px;
|
|
477
|
+
border-radius: 4px;
|
|
478
|
+
color: var(--el-text-color-secondary);
|
|
479
|
+
background: var(--el-fill-color-light);
|
|
480
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
481
|
+
font-size: 10px;
|
|
482
|
+
letter-spacing: 0.02em;
|
|
483
|
+
}
|
|
484
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Tam140503001InBody,
|
|
3
|
+
Tam140503001OutBody,
|
|
4
|
+
Tam140503003OutBodyPredList,
|
|
5
|
+
Tam140503003OutBodyTranList,
|
|
6
|
+
Tam140503004OutBody,
|
|
7
|
+
} from '@/api'
|
|
8
|
+
|
|
9
|
+
/** 匡算查询与导出(140503001、140503002)的业务参数:交易日期与预计调拨收支金额均为必填。 */
|
|
10
|
+
export type EstimationQueryParams = Required<Tam140503001InBody>
|
|
11
|
+
|
|
12
|
+
/** 匡算查询(140503001)返回的十个报表单元格金额;字段名即单元格位置编码。 */
|
|
13
|
+
export type EstimationAmounts = Tam140503001OutBody
|
|
14
|
+
|
|
15
|
+
/** 历史预计调拨金额查询(140503004)返回,用于预填查询条件。 */
|
|
16
|
+
export type EstimationHistory = Required<Tam140503004OutBody>
|
|
17
|
+
|
|
18
|
+
/** 匡算详情(140503003)返回的单条预报明细。 */
|
|
19
|
+
export type EstimationPredRow = Tam140503003OutBodyPredList
|
|
20
|
+
|
|
21
|
+
/** 匡算详情(140503003)返回的单条流水明细。 */
|
|
22
|
+
export type EstimationTranRow = Tam140503003OutBodyTranList
|
|
23
|
+
|
|
24
|
+
/** 匡算报表单元格:位置为接口字段编码,label 为中文展示名称,amount 为接口金额文本。 */
|
|
25
|
+
export interface EstimationCell {
|
|
26
|
+
position: string // 单元格位置编码,同接口字段名;详情接口按此提交 position。
|
|
27
|
+
label: string // 单元格展示名称,如 报备金额(收入)。
|
|
28
|
+
amount?: string // 接口返回的原始金额文本;未查询时可为空。
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dcps 各模块的运行时配置;原先分散在各模块目录的 runtime.ts 统一汇总到本文件,
|
|
3
|
+
* 页面通过 `../../runtime` 按需引入对应模块的配置。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 业务日期始终按上海时区生成,不依赖浏览器所在时区,格式为 YYYYMMDD。
|
|
8
|
+
* 与各模块 api.ts 中的 getToday 逻辑保持一致,供运行时配置计算默认日期。
|
|
9
|
+
*/
|
|
10
|
+
const getToday = (): string => {
|
|
11
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
12
|
+
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
|
13
|
+
}).formatToParts(new Date())
|
|
14
|
+
const read = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value || ''
|
|
15
|
+
return read('year') + read('month') + read('day')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 预报页面的默认查询日期;日期格式为 YYYYMMDD。 */
|
|
19
|
+
export const forecastRuntime = {
|
|
20
|
+
tranDate: getToday(), // 默认交易日期,查询和重置时使用。
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 授权页面的默认创建日期,格式为 YYYYMMDD;当前使用固定日期。 */
|
|
24
|
+
const authorizationToday = '20261012'
|
|
25
|
+
|
|
26
|
+
/** 授权页面的审批人和默认创建日期;日期格式为 YYYYMMDD。 */
|
|
27
|
+
export const authorizationRuntime = {
|
|
28
|
+
approverId: '10092', // 审批人工号。
|
|
29
|
+
approverName: '王干事', // 审批人姓名。
|
|
30
|
+
createTmBegin: authorizationToday, // 默认创建开始日期,格式为 YYYYMMDD。
|
|
31
|
+
createTmEnd: authorizationToday, // 默认创建结束日期,格式为 YYYYMMDD。
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 按模块名聚合的运行时配置;也可从本文件直接引入单个模块的配置。 */
|
|
35
|
+
export default {
|
|
36
|
+
forecast: forecastRuntime,
|
|
37
|
+
authorization: authorizationRuntime,
|
|
38
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// 返回配置的默认查询日期,格式为 YYYYMMDD;当前使用固定日期。
|
|
2
|
-
const getLocalDate = () => {
|
|
3
|
-
return `20261012`
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const today = getLocalDate() // 默认查询日期,取自上方函数中的固定配置。
|
|
7
|
-
|
|
8
|
-
/** 授权页面的审批人和默认创建日期;日期格式为 YYYYMMDD。 */
|
|
9
|
-
const runtime = {
|
|
10
|
-
approverId: '10092', // 审批人工号。
|
|
11
|
-
approverName: '王干事', // 审批人姓名。
|
|
12
|
-
createTmBegin: today, // 默认创建开始日期,格式为 YYYYMMDD。
|
|
13
|
-
createTmEnd: today, // 默认创建结束日期,格式为 YYYYMMDD。
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default runtime
|