vue3-element-plus-app-chu 0.0.13 → 0.0.15
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 +107 -0
- package/src/router/menus.ts +16 -0
- package/src/utils/request.ts +3 -1
- package/src/view/dcps/README.md +52 -0
- package/src/view/dcps/auth/index.vue +151 -14
- package/src/view/dcps/balance/index.vue +4 -6
- package/src/view/dcps/common.css +7 -0
- package/src/view/dcps/estimation/api.ts +18 -148
- package/src/view/dcps/estimation/index.vue +312 -13
- package/src/view/dcps/forecast/api.ts +21 -218
- package/src/view/dcps/forecast/index.vue +996 -50
- package/src/view/dcps/injection/index.vue +154 -11
- package/src/view/dcps/write-off/api.ts +25 -180
- package/src/view/dcps/write-off/index.vue +776 -39
- package/src/view/dcps/auth/components/.gitkeep +0 -0
- package/src/view/dcps/auth/components/AuthorizationDetailDialog.vue +0 -171
- package/src/view/dcps/estimation/components/EstimationDetailDialog.vue +0 -222
- package/src/view/dcps/forecast/components/ForecastDetailView.vue +0 -145
- package/src/view/dcps/forecast/components/ForecastEditorView.vue +0 -320
- package/src/view/dcps/forecast/components/ForecastListView.vue +0 -322
- package/src/view/dcps/forecast/components/ForecastPageShell.vue +0 -56
- package/src/view/dcps/injection/components/InjectionDetailDialog.vue +0 -171
- package/src/view/dcps/runtime.ts +0 -38
- package/src/view/dcps/write-off/components/FlowWriteOffView.vue +0 -299
- package/src/view/dcps/write-off/components/ForecastWriteOffView.vue +0 -437
|
@@ -1,159 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { ApiResponse } from '@/utils/request'
|
|
3
|
-
import type {
|
|
4
|
-
EstimationAmounts, EstimationHistory, EstimationPredRow, EstimationQueryParams, EstimationTranRow,
|
|
5
|
-
Tam140503001InBody, Tam140503001OutBody, Tam140503002InBody, Tam140503002OutBody,
|
|
6
|
-
Tam140503003InBody, Tam140503003OutBody, Tam140503004InBody, Tam140503004OutBody,
|
|
7
|
-
} from './typs'
|
|
1
|
+
import baseHttpClient from "@chic/http"
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
* 数字人民币头寸匡算页面接口与共用工具。
|
|
11
|
-
* 接口地址:查询 140503001、导出 140503002、详情 140503003、历史预计调拨金额 140503004;
|
|
12
|
-
* 请求报文和接口类型均在本模块内维护。
|
|
13
|
-
*/
|
|
14
|
-
const API = {
|
|
3
|
+
export default {
|
|
15
4
|
/**
|
|
16
|
-
*
|
|
17
|
-
* @param params tranDate
|
|
18
|
-
*
|
|
5
|
+
* 查询数字人民币头寸匡算结果。
|
|
6
|
+
* @param params 查询参数:tranDate 为交易日期(YYYYMMDD),
|
|
7
|
+
* predictAllotInAmt、predictAllotOutAmt 为今日预计调拨收入、支出金额。
|
|
8
|
+
* 返回 B15、C3、C6、C7、C8、C11、D6、D7、D8、D11 十个单元格金额。
|
|
19
9
|
*/
|
|
20
|
-
queryListApi: (params:
|
|
21
|
-
postDigitalRmb<Tam140503001InBody, Tam140503001OutBody>('/TAM/0001/140503001', params),
|
|
22
|
-
|
|
10
|
+
queryListApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140503001", params),
|
|
23
11
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
12
|
+
* 导出数字人民币头寸匡算结果。
|
|
13
|
+
* @param params 导出参数与查询一致:tranDate、predictAllotInAmt、predictAllotOutAmt;
|
|
14
|
+
* 响应只返回导出文件名 fileName。
|
|
26
15
|
*/
|
|
27
|
-
exportApi: (params:
|
|
28
|
-
postDigitalRmb<Tam140503002InBody, Tam140503002OutBody>('/TAM/0001/140503002', params),
|
|
29
|
-
|
|
16
|
+
exportApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140503002", params),
|
|
30
17
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @param params position
|
|
33
|
-
*
|
|
18
|
+
* 查询匡算单元格明细。
|
|
19
|
+
* @param params 查询参数:position 为单元格位置编码,tranDate 为交易日期(YYYYMMDD)。
|
|
20
|
+
* 响应包含预报明细 predList、流水明细 tranList 和总条数 totalCount。
|
|
34
21
|
*/
|
|
35
|
-
getDetailApi: (params:
|
|
36
|
-
postDigitalRmb<Tam140503003InBody, Tam140503003OutBody>('/TAM/0001/140503003', params),
|
|
37
|
-
|
|
22
|
+
getDetailApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140503003", params),
|
|
38
23
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @param tranDate
|
|
24
|
+
* 查询指定日期的历史预计调拨金额,用于预填查询条件。
|
|
25
|
+
* @param tranDate 交易日期,格式为 YYYYMMDD。
|
|
41
26
|
*/
|
|
42
27
|
queryHistoryApi: (tranDate: string) =>
|
|
43
|
-
|
|
44
|
-
}
|
|
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')
|
|
28
|
+
baseHttpClient.post<any>("/TAM/0001/140503004", { tranDate })
|
|
68
29
|
}
|
|
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,11 +1,12 @@
|
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import API from './api'
|
|
5
|
+
import type {
|
|
6
|
+
EstimationAmounts, EstimationCell, EstimationPredRow, EstimationQueryParams, EstimationTranRow,
|
|
7
|
+
} from './typs'
|
|
8
|
+
|
|
9
|
+
defineOptions({ name: 'DcpsEstimationPage' })
|
|
9
10
|
|
|
10
11
|
// 数字人民币头寸匡算页面:
|
|
11
12
|
// 查询条件为交易日期加上今日预计调拨收入、支出金额;交易日期变化后调用
|
|
@@ -30,6 +31,112 @@ const cellLabels: Record<string, string> = {
|
|
|
30
31
|
B15: '当日实时余额',
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// ---------- 页面内工具函数 ----------
|
|
35
|
+
|
|
36
|
+
/** 提取可展示的错误信息;无法识别时给出统一提示。 */
|
|
37
|
+
const getErrorMessage = (error: unknown): string => {
|
|
38
|
+
if (error instanceof Error && error.message.trim()) return error.message
|
|
39
|
+
if (typeof error === 'string' && error.trim()) return error
|
|
40
|
+
return '操作失败,请稍后重试'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 业务日期始终按上海时区生成,不依赖浏览器所在时区,格式为 YYYYMMDD。 */
|
|
44
|
+
const getToday = (): string => {
|
|
45
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
46
|
+
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
|
47
|
+
}).formatToParts(new Date())
|
|
48
|
+
const read = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value || ''
|
|
49
|
+
return read('year') + read('month') + read('day')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 判断是否为合法的 YYYYMMDD 日期。 */
|
|
53
|
+
const isValidDate = (value: string): boolean => {
|
|
54
|
+
if (!/^\d{8}$/.test(value)) return false
|
|
55
|
+
const year = Number(value.slice(0, 4))
|
|
56
|
+
const month = Number(value.slice(4, 6))
|
|
57
|
+
const day = Number(value.slice(6, 8))
|
|
58
|
+
if (year < 1 || month < 1 || month > 12 || day < 1) return false
|
|
59
|
+
const leap = year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
|
|
60
|
+
const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
61
|
+
return day <= (monthDays[month - 1] || 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 只接受十进制文本和合法千分位;保留全部小数位,避免改变输入精度。 */
|
|
65
|
+
const normalizeAmount = (value: string): string => {
|
|
66
|
+
let text = value.trim()
|
|
67
|
+
if (text.includes(',')) {
|
|
68
|
+
if (!/^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text)) return ''
|
|
69
|
+
text = text.replace(/,/g, '')
|
|
70
|
+
}
|
|
71
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(text)
|
|
72
|
+
if (!match || (!match[2] && !match[3])) return ''
|
|
73
|
+
const integer = (match[2] || '0').replace(/^0+(?=\d)/, '')
|
|
74
|
+
const fraction = match[3] || ''
|
|
75
|
+
const negative = match[1] === '-' && /[1-9]/.test(integer + fraction)
|
|
76
|
+
return `${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 千分位展示至少两位小数,超过两位的小数原样保留,不做浮点数转换或舍入。 */
|
|
80
|
+
const formatAmount = (value?: string): string => {
|
|
81
|
+
const original = value?.trim() || ''
|
|
82
|
+
if (!original) return '—'
|
|
83
|
+
const normalized = normalizeAmount(original)
|
|
84
|
+
if (!normalized) return original
|
|
85
|
+
const negative = normalized.startsWith('-')
|
|
86
|
+
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
87
|
+
const grouped = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
88
|
+
return `${negative ? '-' : ''}${grouped}.${fraction.padEnd(2, '0')}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 计算轧差(收入减支出);任一金额无法解析时返回空字符串。
|
|
93
|
+
* emptyAsZero 为 true 时空文本按 0 参与计算(用于输入框的实时轧差)。
|
|
94
|
+
*/
|
|
95
|
+
const calcGapAmount = (inAmt: string, outAmt: string, emptyAsZero = false): string => {
|
|
96
|
+
const parse = (value: string) => {
|
|
97
|
+
if (!value?.trim() && emptyAsZero) value = '0'
|
|
98
|
+
const normalized = normalizeAmount(value)
|
|
99
|
+
if (!normalized) return null
|
|
100
|
+
const negative = normalized.startsWith('-')
|
|
101
|
+
const [integer = '0', fraction = ''] = (negative ? normalized.slice(1) : normalized).split('.')
|
|
102
|
+
return { negative, integer, fraction }
|
|
103
|
+
}
|
|
104
|
+
const inPart = parse(inAmt)
|
|
105
|
+
const outPart = parse(outAmt)
|
|
106
|
+
if (!inPart || !outPart) return ''
|
|
107
|
+
const scale = Math.max(inPart.fraction.length, outPart.fraction.length)
|
|
108
|
+
const toScaled = (part: { negative: boolean; integer: string; fraction: string }): bigint => {
|
|
109
|
+
const digits = `${part.integer}${part.fraction.padEnd(scale, '0')}`
|
|
110
|
+
return BigInt(digits) * (part.negative ? -1n : 1n)
|
|
111
|
+
}
|
|
112
|
+
const diff = toScaled(inPart) - toScaled(outPart)
|
|
113
|
+
const negative = diff < 0n
|
|
114
|
+
const digits = (negative ? -diff : diff).toString().padStart(scale + 1, '0')
|
|
115
|
+
const integer = digits.slice(0, digits.length - scale)
|
|
116
|
+
const fraction = scale ? digits.slice(digits.length - scale) : ''
|
|
117
|
+
return formatAmount(`${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 收支方向字典:I 来账、O 往账。 */
|
|
121
|
+
const payDirecLabels: Record<string, string> = { I: '来账', O: '往账' }
|
|
122
|
+
/** 支付渠道:预报与流水的数字人民币分别为 8、5;其余渠道保留原文。 */
|
|
123
|
+
const payChannelLabels: Record<string, string> = { '5': '数字人民币', '8': '数字人民币' }
|
|
124
|
+
/** 核销状态字典:1 未核销、2 已核销。 */
|
|
125
|
+
const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销' }
|
|
126
|
+
/** 业务类型字典:201 兑回、202 兑出、203 付现;与核销预报使用同一套枚举。 */
|
|
127
|
+
const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
128
|
+
|
|
129
|
+
/** 读取字典文案;空值显示横线,未登记的代码保留原文展示。 */
|
|
130
|
+
const dictLabel = (labels: Record<string, string>, value?: string): string => {
|
|
131
|
+
const text = value?.trim() || ''
|
|
132
|
+
return text ? labels[text] ?? text : '—'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 详情明细展示的统一空值处理;空值显示横线。 */
|
|
136
|
+
const displayText = (value?: string): string => value?.trim() || '—'
|
|
137
|
+
|
|
138
|
+
// ---------- 匡算报表 ----------
|
|
139
|
+
|
|
33
140
|
// 读取接口金额文本;未返回的编码保持空字符串,展示为空白。
|
|
34
141
|
const amountOf = (position: string): string => {
|
|
35
142
|
const source = amounts.value as unknown as Record<string, unknown> | null
|
|
@@ -165,9 +272,57 @@ const submitQuery = async () => {
|
|
|
165
272
|
}
|
|
166
273
|
}
|
|
167
274
|
|
|
168
|
-
//
|
|
275
|
+
// ---------- 单元格明细弹窗 ----------
|
|
276
|
+
|
|
277
|
+
// 弹窗状态:cell 为被单击的报表单元格;明细请求在弹窗打开时发起。
|
|
169
278
|
const detailVisible = ref(false)
|
|
170
279
|
const detailCell = ref<EstimationCell | null>(null)
|
|
280
|
+
const detailPredList = ref<EstimationPredRow[]>([])
|
|
281
|
+
const detailTranList = ref<EstimationTranRow[]>([])
|
|
282
|
+
const detailTotalCount = ref('')
|
|
283
|
+
const detailLoading = ref(false)
|
|
284
|
+
const detailHasError = ref(false)
|
|
285
|
+
let detailRequestId = 0
|
|
286
|
+
|
|
287
|
+
/** 查询单元格明细;cell 或交易日期缺失时不发起请求,直接呈现空态。 */
|
|
288
|
+
const loadDetailInfo = async () => {
|
|
289
|
+
const position = detailCell.value?.position?.trim()
|
|
290
|
+
const date = queriedTranDate.value.trim()
|
|
291
|
+
if (!position || !date) return
|
|
292
|
+
const currentRequestId = ++detailRequestId
|
|
293
|
+
detailLoading.value = true
|
|
294
|
+
try {
|
|
295
|
+
const { head, body } = await API.getDetailApi({ position, tranDate: date }) as any
|
|
296
|
+
if (currentRequestId !== detailRequestId || disposed) return
|
|
297
|
+
if (head?.retCode !== '00000') {
|
|
298
|
+
throw new Error(head?.retMsg || '查询失败')
|
|
299
|
+
}
|
|
300
|
+
detailPredList.value = Array.isArray(body?.predList) ? body.predList : []
|
|
301
|
+
detailTranList.value = Array.isArray(body?.tranList) ? body.tranList : []
|
|
302
|
+
detailTotalCount.value = String(body?.totalCount ?? '').trim()
|
|
303
|
+
detailHasError.value = false
|
|
304
|
+
} catch (error) { // error 为详情请求或响应数据校验异常。
|
|
305
|
+
if (currentRequestId !== detailRequestId || disposed) return
|
|
306
|
+
detailPredList.value = []
|
|
307
|
+
detailTranList.value = []
|
|
308
|
+
detailTotalCount.value = ''
|
|
309
|
+
detailHasError.value = true
|
|
310
|
+
ElMessage.error(getErrorMessage(error))
|
|
311
|
+
} finally {
|
|
312
|
+
if (currentRequestId === detailRequestId) detailLoading.value = false
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** 关闭弹窗:重置明细并让未完成的请求失效。 */
|
|
317
|
+
const closeDetail = () => {
|
|
318
|
+
detailVisible.value = false
|
|
319
|
+
detailRequestId += 1
|
|
320
|
+
detailPredList.value = []
|
|
321
|
+
detailTranList.value = []
|
|
322
|
+
detailTotalCount.value = ''
|
|
323
|
+
detailHasError.value = false
|
|
324
|
+
detailLoading.value = false
|
|
325
|
+
}
|
|
171
326
|
|
|
172
327
|
/** 打开单元格明细弹窗;结果就绪前其余单元格为占位横线,不发起详情查询。 */
|
|
173
328
|
const openDetail = (cell?: EstimationCell) => {
|
|
@@ -176,12 +331,19 @@ const openDetail = (cell?: EstimationCell) => {
|
|
|
176
331
|
detailVisible.value = true
|
|
177
332
|
}
|
|
178
333
|
|
|
334
|
+
/** 合并预报、流水列表得到明细总条数;接口 totalCount 缺失时按列表长度兜底展示。 */
|
|
335
|
+
const resolveTotal = () => {
|
|
336
|
+
if (/^\d+$/.test(detailTotalCount.value)) return detailTotalCount.value
|
|
337
|
+
return String(detailPredList.value.length + detailTranList.value.length)
|
|
338
|
+
}
|
|
339
|
+
|
|
179
340
|
// 页面首次显示时按默认日期预填预计调拨收支金额。
|
|
180
341
|
onMounted(prefillHistory)
|
|
181
342
|
// 页面离开后,未完成的预填请求不再更新表单或弹出消息。
|
|
182
343
|
onBeforeUnmount(() => {
|
|
183
344
|
disposed = true
|
|
184
345
|
prefillRequestId += 1
|
|
346
|
+
detailRequestId += 1
|
|
185
347
|
})
|
|
186
348
|
</script>
|
|
187
349
|
|
|
@@ -353,23 +515,132 @@ onBeforeUnmount(() => {
|
|
|
353
515
|
</el-card>
|
|
354
516
|
|
|
355
517
|
<!-- 单元格明细弹窗:按位置编码查询预报与流水列表。 -->
|
|
356
|
-
<
|
|
518
|
+
<el-dialog
|
|
519
|
+
v-model="detailVisible"
|
|
520
|
+
:title="detailCell ? `匡算明细 - ${detailCell.label}` : '匡算明细'"
|
|
521
|
+
width="min(1080px, calc(100vw - 32px))"
|
|
522
|
+
top="16px"
|
|
523
|
+
append-to-body
|
|
524
|
+
destroy-on-close
|
|
525
|
+
@open="loadDetailInfo"
|
|
526
|
+
@closed="closeDetail"
|
|
527
|
+
>
|
|
528
|
+
<el-scrollbar class="detail-scrollbar">
|
|
529
|
+
<div class="detail-content">
|
|
530
|
+
<!-- 上下文摘要:被点击单元格的位置、金额与详情查询的交易日期。 -->
|
|
531
|
+
<header class="detail-summary">
|
|
532
|
+
<div>
|
|
533
|
+
<span class="detail-label">位置编码</span>
|
|
534
|
+
<strong class="detail-position">{{ detailCell?.position || '—' }}</strong>
|
|
535
|
+
</div>
|
|
536
|
+
<div>
|
|
537
|
+
<span class="detail-label">单元格金额(元)</span>
|
|
538
|
+
<strong class="detail-amount">{{ formatAmount(detailCell?.amount) }}</strong>
|
|
539
|
+
</div>
|
|
540
|
+
<div>
|
|
541
|
+
<span class="detail-label">交易日期</span>
|
|
542
|
+
<strong>{{ queriedTranDate }}</strong>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="detail-reference">
|
|
545
|
+
<span class="detail-label">明细总条数</span>
|
|
546
|
+
<strong>{{ resolveTotal() }}</strong>
|
|
547
|
+
</div>
|
|
548
|
+
</header>
|
|
549
|
+
|
|
550
|
+
<div v-loading="detailLoading" class="detail-tables">
|
|
551
|
+
<!-- 预报信息明细;列表为空时展示统一空态。 -->
|
|
552
|
+
<div class="dialog-section-title">预报信息</div>
|
|
553
|
+
<el-table :data="detailPredList" size="small" border max-height="240px" empty-text="暂无预报信息">
|
|
554
|
+
<el-table-column label="预报流水号" min-width="150" show-overflow-tooltip>
|
|
555
|
+
<template #default="{ row }">{{ displayText(row.predRefNo) }}</template>
|
|
556
|
+
</el-table-column>
|
|
557
|
+
<el-table-column label="业务类型" width="90" align="center">
|
|
558
|
+
<template #default="{ row }">{{ dictLabel(busiTypeLabels, row.busiType) }}</template>
|
|
559
|
+
</el-table-column>
|
|
560
|
+
<el-table-column label="客户名称" min-width="140" show-overflow-tooltip>
|
|
561
|
+
<template #default="{ row }">{{ displayText(row.clientName) }}</template>
|
|
562
|
+
</el-table-column>
|
|
563
|
+
<el-table-column label="交易对手名称" min-width="140" show-overflow-tooltip>
|
|
564
|
+
<template #default="{ row }">{{ displayText(row.competName) }}</template>
|
|
565
|
+
</el-table-column>
|
|
566
|
+
<el-table-column label="收支方向" width="80" align="center">
|
|
567
|
+
<template #default="{ row }">{{ dictLabel(payDirecLabels, row.payDirec) }}</template>
|
|
568
|
+
</el-table-column>
|
|
569
|
+
<el-table-column label="支付渠道" width="110" align="center">
|
|
570
|
+
<template #default="{ row }">{{ dictLabel(payChannelLabels, row.payChannel) }}</template>
|
|
571
|
+
</el-table-column>
|
|
572
|
+
<el-table-column label="预报金额(元)" width="140" align="right">
|
|
573
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.predAmt) }}</span></template>
|
|
574
|
+
</el-table-column>
|
|
575
|
+
<el-table-column label="已核销金额(元)" width="140" align="right">
|
|
576
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.verifyAmt) }}</span></template>
|
|
577
|
+
</el-table-column>
|
|
578
|
+
<el-table-column label="核销状态" width="90" align="center">
|
|
579
|
+
<template #default="{ row }">{{ dictLabel(verifyStatusLabels, row.verifyStatus) }}</template>
|
|
580
|
+
</el-table-column>
|
|
581
|
+
<el-table-column label="落地支行" min-width="130" show-overflow-tooltip>
|
|
582
|
+
<template #default="{ row }">{{ displayText(row.groundBranch) }}</template>
|
|
583
|
+
</el-table-column>
|
|
584
|
+
<el-table-column label="上级单位" min-width="130" show-overflow-tooltip>
|
|
585
|
+
<template #default="{ row }">{{ displayText(row.upOrg) }}</template>
|
|
586
|
+
</el-table-column>
|
|
587
|
+
</el-table>
|
|
588
|
+
|
|
589
|
+
<!-- 流水信息明细;失败的详情可重新打开弹窗重试。 -->
|
|
590
|
+
<div class="dialog-section-title">流水信息</div>
|
|
591
|
+
<el-table :data="detailTranList" size="small" border max-height="240px" empty-text="暂无流水信息">
|
|
592
|
+
<el-table-column label="交易参考号" min-width="150" show-overflow-tooltip>
|
|
593
|
+
<template #default="{ row }">{{ displayText(row.refNo) }}</template>
|
|
594
|
+
</el-table-column>
|
|
595
|
+
<el-table-column label="业务类型" width="90" align="center">
|
|
596
|
+
<template #default="{ row }">{{ dictLabel(busiTypeLabels, row.busiType) }}</template>
|
|
597
|
+
</el-table-column>
|
|
598
|
+
<el-table-column label="付款人名称" min-width="140" show-overflow-tooltip>
|
|
599
|
+
<template #default="{ row }">{{ displayText(row.payerName) }}</template>
|
|
600
|
+
</el-table-column>
|
|
601
|
+
<el-table-column label="收款人名称" min-width="140" show-overflow-tooltip>
|
|
602
|
+
<template #default="{ row }">{{ displayText(row.payeeName) }}</template>
|
|
603
|
+
</el-table-column>
|
|
604
|
+
<el-table-column label="收支方向" width="80" align="center">
|
|
605
|
+
<template #default="{ row }">{{ dictLabel(payDirecLabels, row.payDirec) }}</template>
|
|
606
|
+
</el-table-column>
|
|
607
|
+
<el-table-column label="支付渠道" width="110" align="center">
|
|
608
|
+
<template #default="{ row }">{{ dictLabel(payChannelLabels, row.payChannel) }}</template>
|
|
609
|
+
</el-table-column>
|
|
610
|
+
<el-table-column label="金额(元)" width="140" align="right">
|
|
611
|
+
<template #default="{ row }"><span class="amount-cell">{{ formatAmount(row.amt) }}</span></template>
|
|
612
|
+
</el-table-column>
|
|
613
|
+
<el-table-column label="核销状态" width="90" align="center">
|
|
614
|
+
<template #default="{ row }">{{ dictLabel(verifyStatusLabels, row.verifyStatus) }}</template>
|
|
615
|
+
</el-table-column>
|
|
616
|
+
<el-table-column label="落地支行" min-width="130" show-overflow-tooltip>
|
|
617
|
+
<template #default="{ row }">{{ displayText(row.groundBranch) }}</template>
|
|
618
|
+
</el-table-column>
|
|
619
|
+
<el-table-column label="上级单位" min-width="130" show-overflow-tooltip>
|
|
620
|
+
<template #default="{ row }">{{ displayText(row.upOrg) }}</template>
|
|
621
|
+
</el-table-column>
|
|
622
|
+
</el-table>
|
|
623
|
+
|
|
624
|
+
<div v-if="detailHasError" class="detail-retry">详情查询失败,关闭弹窗后重新打开可重试。</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</el-scrollbar>
|
|
628
|
+
<template #footer>
|
|
629
|
+
<el-button @click="closeDetail">关闭</el-button>
|
|
630
|
+
</template>
|
|
631
|
+
</el-dialog>
|
|
357
632
|
</main>
|
|
358
633
|
</template>
|
|
359
634
|
|
|
360
635
|
<style scoped>
|
|
361
636
|
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,内容由报表卡片承载。 */
|
|
362
637
|
.dcps-page {
|
|
363
|
-
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
364
|
-
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
365
|
-
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
366
|
-
--host-layout-height: 0px;
|
|
367
638
|
box-sizing: border-box;
|
|
368
639
|
display: flex;
|
|
369
640
|
flex-direction: column;
|
|
370
641
|
gap: 5px;
|
|
371
|
-
height: calc(100vh - var(--host-layout-height));
|
|
372
|
-
height: calc(100dvh - var(--host-layout-height));
|
|
642
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
643
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
373
644
|
min-height: 0;
|
|
374
645
|
min-width: 0;
|
|
375
646
|
padding: 5px;
|
|
@@ -481,4 +752,32 @@ td.report-data--input { padding: 5px 8px; }
|
|
|
481
752
|
font-size: 10px;
|
|
482
753
|
letter-spacing: 0.02em;
|
|
483
754
|
}
|
|
755
|
+
|
|
756
|
+
/* ---------- 单元格明细弹窗 ---------- */
|
|
757
|
+
.detail-scrollbar { max-height: calc(100vh - 240px); }
|
|
758
|
+
.detail-content { padding: 2px 4px 4px; }
|
|
759
|
+
|
|
760
|
+
/* 摘要区:位置编码、金额、日期放在一行,便于确认明细上下文。 */
|
|
761
|
+
.detail-summary {
|
|
762
|
+
display: flex;
|
|
763
|
+
flex-wrap: wrap;
|
|
764
|
+
align-items: center;
|
|
765
|
+
gap: 12px 36px;
|
|
766
|
+
margin-bottom: 12px;
|
|
767
|
+
padding: 12px 14px;
|
|
768
|
+
border-radius: 10px;
|
|
769
|
+
background: var(--el-fill-color-light);
|
|
770
|
+
}
|
|
771
|
+
.detail-label { display: block; margin-bottom: 5px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
772
|
+
.detail-summary strong { overflow-wrap: anywhere; font-size: 13px; }
|
|
773
|
+
.detail-amount { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; }
|
|
774
|
+
.detail-reference { margin-left: auto; }
|
|
775
|
+
.detail-reference strong { font-weight: 600; }
|
|
776
|
+
|
|
777
|
+
.dialog-section-title { margin: 12px 0 6px; color: var(--el-text-color-regular); font-size: 13px; font-weight: 600; }
|
|
778
|
+
.detail-tables .el-table + .el-table { margin-top: 2px; }
|
|
779
|
+
.detail-tables { min-height: 160px; }
|
|
780
|
+
.detail-tables .dialog-section-title:first-child { margin-top: 0; }
|
|
781
|
+
.amount-cell { color: var(--el-text-color-primary); font-variant-numeric: tabular-nums; }
|
|
782
|
+
.detail-retry { margin-top: 10px; color: var(--el-color-danger); font-size: 12px; text-align: center; }
|
|
484
783
|
</style>
|