vue3-element-plus-app-chu 0.0.20 → 0.0.22
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/router/index.ts +7 -0
- package/src/router/menus.ts +1 -0
- package/src/view/dcps/README.md +4 -0
- package/src/view/dcps/auth/index.vue +1 -1
- package/src/view/dcps/estimation/index.vue +1 -1
- package/src/view/dcps/forecast/index.vue +1 -1
- package/src/view/dcps/funding/balance/api.ts +3 -3
- package/src/view/dcps/funding/balance/index.vue +2 -2
- package/src/view/dcps/parameters/api.ts +33 -0
- package/src/view/dcps/parameters/index.vue +559 -0
- package/src/view/dcps/parameters/typs.ts +33 -0
- package/src/view/dcps/runtime.ts +3 -3
- package/src/view/dcps/write-off/index.vue +1 -1
package/README.md
CHANGED
|
@@ -31,3 +31,7 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
|
|
|
31
31
|
- 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
|
|
32
32
|
|
|
33
33
|
本地后端的预报接口当前使用通用模拟响应,写入成功响应不代表真实持久化。
|
|
34
|
+
|
|
35
|
+
## 城银清头寸参数接口
|
|
36
|
+
|
|
37
|
+
新增、修改、删除、查询、详情五个接口的封装及类型位于 `src/view/dcps/city-clearing/parameters/`,本次仅提供接口层。城银清查询与详情使用 `moduleCode: '3'`。本地后端提供符合字段契约的模拟响应及参数校验,写操作不持久化;接口字段和示例见 [城银清头寸参数接口说明](docs/city-clearing-parameters-apis.md),Scalar 文档入口为后端 `/docs`。
|
package/package.json
CHANGED
package/src/router/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import DigitalRmbForecast from '@/view/dcps/forecast/index.vue'
|
|
|
6
6
|
import DigitalRmbInjection from '@/view/dcps/funding/injection/index.vue'
|
|
7
7
|
import DigitalRmbWriteOff from '@/view/dcps/write-off/index.vue'
|
|
8
8
|
import DigitalRmbPositionEstimation from '@/view/dcps/estimation/index.vue'
|
|
9
|
+
import DigitalRmbParameters from '@/view/dcps/parameters/index.vue'
|
|
9
10
|
|
|
10
11
|
declare module 'vue-router' {
|
|
11
12
|
interface RouteMeta {
|
|
@@ -98,6 +99,12 @@ const routes: RouteRecordRaw[] = [
|
|
|
98
99
|
component: DigitalRmbWriteOff,
|
|
99
100
|
meta: { menu: 'write-off', title: '数字人民币核销' },
|
|
100
101
|
},
|
|
102
|
+
{
|
|
103
|
+
path: 'parameters',
|
|
104
|
+
name: 'parameters',
|
|
105
|
+
component: DigitalRmbParameters,
|
|
106
|
+
meta: { menu: 'parameters', title: '数字人民币参数管理' },
|
|
107
|
+
},
|
|
101
108
|
],
|
|
102
109
|
},
|
|
103
110
|
{ path: '/:pathMatch(.*)*', redirect: { name: 'forecast' } },
|
package/src/router/menus.ts
CHANGED
package/src/view/dcps/README.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- 页面入口统一命名为 `Dcps<模块>Page`。
|
|
21
21
|
- 页面内部的弹窗、表格、表单不再单独拆文件,直接写在 `index.vue` 中,相关状态和方法使用页面前缀区分。
|
|
22
22
|
- 路由名使用 kebab-case(如 `forecast-create`)。
|
|
23
|
+
- DCPS 页面接口返回按 `any` 处理,接口封装可使用 `post<any>`,页面需要时可按 `any` 读取响应;业务字段类型定义在各页面本地 `typs.ts`。不要从 `@/utils/request` 导入公共 `ApiResponse`,也不要通过 `@chic/http` 的重导出导入该类型;仍按接口实际响应字段判断业务成功与失败。
|
|
23
24
|
|
|
24
25
|
## 路由与页面文件
|
|
25
26
|
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
| `/funding/balance` | `balance` | `funding/balance/index.vue` | 页面 | 数字人民币账户行余额查询申请 |
|
|
38
39
|
| `/auth` | `auth` | `auth/index.vue` | 页面 | 数字人民币授权 |
|
|
39
40
|
| `/write-off` | `write-off` | `write-off/index.vue` | 页面 | 数字人民币核销 |
|
|
41
|
+
| `/parameters` | `parameters` | `parameters/index.vue` | 列表、详情、新增和修改弹窗 | 数字人民币参数管理 |
|
|
40
42
|
| `/` | — | 重定向到 `forecast` | — | — |
|
|
41
43
|
| `/:pathMatch(.*)*` | — | 重定向到 `forecast`(404 兜底) | — | — |
|
|
42
44
|
|
|
@@ -46,6 +48,8 @@
|
|
|
46
48
|
- `forecast/index.vue` 顶部使用一个普通 `<script>` 块保存列表记录缓存,跳转修改或详情前暂存列表行,用于补齐详情接口未返回的字段。
|
|
47
49
|
- 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应路由的 `name` 保持一致;分组菜单通过 `children` 配置二级页面,页面的 `meta.menu` 指向自己的菜单项。
|
|
48
50
|
- “注资/预注资管理”对应 `funding` 分组,包含注资明细查询和账户行余额查询申请两个页面;访问子页面时自动展开父菜单。旧地址 `/injection`、`/balance` 保留为对应页面的别名。
|
|
51
|
+
- 数字人民币参数管理为末尾的独立菜单,页面文件为 `parameters/index.vue`、`parameters/api.ts`、`parameters/typs.ts`,接口与类型均独立定义在该目录,不再依赖或保留 `city-clearing/` 目录。页面复用“城银清算头寸管理-头寸参数”的五个接口,`api.ts` 参照授权页面直接向 `@chic/http` 平铺传参,由 Axios 公共封装拆分为服务端所需的 `body`、`head` 等字段,不手动包装信封。页面查询调用只传 `keywords`、数字类型的 `pageIndex` 和 `pageSize`,`moduleCode: '5'` 由 API 内部固定添加,分页数字由封装层转换为字符串;详情只传参数名称,新增传六个字符串表单字段,两者的模块代码也由 API 固定添加。本次用户确认以 `paraName` 定位修改和删除:修改时锁定原参数名称、参数标志只读;删除向 API 传名称数组,由 API 转为平铺的 `sysParaList`。页面类型使用 `DigitalRmbParameter` 等独立类型,原始 Swagger 契约及 `docs/city-clearing-parameters*` 文档文件名保留。当前本地后端仍返回模拟结果,不保存参数。
|
|
52
|
+
- 数字人民币参数管理以数字人民币授权页面为布局参考,统一筛选区、填满剩余高度的表格以及包含摘要和分组字段的详情弹窗样式。
|
|
49
53
|
- `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报页面;新增、修改、详情按路由 `key` 分别缓存,返回列表时自动重新查询。
|
|
50
54
|
|
|
51
55
|
## 新增页面检查清单
|
|
@@ -3,7 +3,7 @@ import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
|
3
3
|
import { ElConfigProvider, ElMessage } from 'element-plus'
|
|
4
4
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
5
5
|
import API from './api'
|
|
6
|
-
import dcpsRuntime from '../runtime'
|
|
6
|
+
import { dcpsRuntime } from '../runtime'
|
|
7
7
|
import type { AuthorizationFilters, AuthorizationQueryParams, AuthorizationTask } from './typs'
|
|
8
8
|
|
|
9
9
|
defineOptions({ name: 'DcpsAuthPage' })
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
3
|
import { ElConfigProvider, ElMessage } from 'element-plus'
|
|
4
4
|
import API from './api'
|
|
5
|
-
import dcpsRuntime from '../runtime'
|
|
5
|
+
import { dcpsRuntime } from '../runtime'
|
|
6
6
|
import type {
|
|
7
7
|
EstimationAmounts, EstimationCell, EstimationPredRow, EstimationQueryParams, EstimationTranRow,
|
|
8
8
|
} from './typs'
|
|
@@ -24,7 +24,7 @@ import { computed, onActivated, onBeforeUnmount, reactive, ref, watch } from 'vu
|
|
|
24
24
|
import { useRouter } from 'vue-router'
|
|
25
25
|
import { ElConfigProvider, ElMessage, ElMessageBox } from 'element-plus'
|
|
26
26
|
import API from './api'
|
|
27
|
-
import dcpsRuntime from '../runtime'
|
|
27
|
+
import { dcpsRuntime } from '../runtime'
|
|
28
28
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
29
29
|
import type {
|
|
30
30
|
ForecastCreateBody,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import baseHttpClient
|
|
1
|
+
import baseHttpClient from '@chic/http'
|
|
2
2
|
import type { AccountBalanceInfo, InjectionCreateParams } from './typs'
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
/** 查询单条账户行余额,无筛选参数。 */
|
|
6
6
|
queryBalanceApi: () =>
|
|
7
|
-
baseHttpClient.post<
|
|
7
|
+
baseHttpClient.post<AccountBalanceInfo>('/TAM/0001/140506001', {}),
|
|
8
8
|
/** 注资/预注资发起,响应未声明业务字段。 */
|
|
9
9
|
createApi: (params: InjectionCreateParams) =>
|
|
10
|
-
baseHttpClient.post
|
|
10
|
+
baseHttpClient.post('/TAM/0001/120506001', params),
|
|
11
11
|
}
|
|
@@ -72,7 +72,7 @@ const submitCreate = async () => {
|
|
|
72
72
|
|
|
73
73
|
pendingCreate.value = true
|
|
74
74
|
try {
|
|
75
|
-
const { head } = await API.createApi({ adjustType, tranAmt })
|
|
75
|
+
const { head } = await API.createApi({ adjustType, tranAmt }) as any
|
|
76
76
|
if (disposed) return
|
|
77
77
|
if (head?.retCode !== '00000') {
|
|
78
78
|
throw new Error(head?.retMsg || '提交失败')
|
|
@@ -169,7 +169,7 @@ const query = async () => {
|
|
|
169
169
|
const currentRequestId = ++requestId
|
|
170
170
|
loading.value = true
|
|
171
171
|
try {
|
|
172
|
-
const { head, body } = await API.queryBalanceApi()
|
|
172
|
+
const { head, body } = await API.queryBalanceApi() as any
|
|
173
173
|
if (currentRequestId !== requestId) return
|
|
174
174
|
if (head?.retCode !== '00000') {
|
|
175
175
|
throw new Error(head?.retMsg || '查询失败')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import baseHttpClient from '@chic/http'
|
|
2
|
+
import type {
|
|
3
|
+
ParameterEditBody,
|
|
4
|
+
ParameterFormValues,
|
|
5
|
+
ParameterQueryParams,
|
|
6
|
+
} from './typs'
|
|
7
|
+
|
|
8
|
+
const moduleCode = '5'
|
|
9
|
+
|
|
10
|
+
// 与授权页面一致,平铺传参,由 Axios 封装器拆分公共头、业务体并转换分页字段。
|
|
11
|
+
export default {
|
|
12
|
+
queryListApi: ({ keywords, pageIndex, pageSize }: ParameterQueryParams) =>
|
|
13
|
+
baseHttpClient.post<any>('/TAM/0001/140305001', {
|
|
14
|
+
moduleCode,
|
|
15
|
+
keywords,
|
|
16
|
+
pageIndex,
|
|
17
|
+
pageSize,
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
getDetailApi: (paraName: string) =>
|
|
21
|
+
baseHttpClient.post<any>('/TAM/0001/140305002', { moduleCode, paraName }),
|
|
22
|
+
|
|
23
|
+
addApi: (values: ParameterFormValues) =>
|
|
24
|
+
baseHttpClient.post<any>('/TAM/0001/120305002', { ...values, moduleCode }),
|
|
25
|
+
|
|
26
|
+
// 用户确认以原参数名称定位;原始修改契约不包含 moduleCode、paraMark。
|
|
27
|
+
editApi: (values: ParameterEditBody) =>
|
|
28
|
+
baseHttpClient.post<any>('/TAM/0001/120305003', values),
|
|
29
|
+
|
|
30
|
+
// 用户确认 sysParaList 的元素为参数名称;删除契约没有模块字段。
|
|
31
|
+
deleteApi: (paraNames: string[]) =>
|
|
32
|
+
baseHttpClient.post<any>('/TAM/0001/120305001', { sysParaList: [...paraNames] }),
|
|
33
|
+
}
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import { ElConfigProvider, ElMessage } from 'element-plus'
|
|
4
|
+
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
5
|
+
import API from './api'
|
|
6
|
+
import type { DigitalRmbParameter, ParameterFormValues, ParameterListBody } from './typs'
|
|
7
|
+
|
|
8
|
+
defineOptions({ name: 'DcpsParametersPage' })
|
|
9
|
+
|
|
10
|
+
type DialogMode = 'create' | 'edit' | 'detail'
|
|
11
|
+
type Mutation = 'create' | 'edit' | 'delete' | null
|
|
12
|
+
|
|
13
|
+
const parameterFields = [
|
|
14
|
+
{ key: 'paraName', label: '参数名称', multiline: false },
|
|
15
|
+
{ key: 'paraType', label: '参数类型', multiline: false },
|
|
16
|
+
{ key: 'paraMark', label: '参数标志', multiline: false },
|
|
17
|
+
{ key: 'paraValue', label: '参数值', multiline: true },
|
|
18
|
+
{ key: 'paraDesc', label: '参数描述', multiline: true },
|
|
19
|
+
{ key: 'remark', label: '备注', multiline: true },
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
const detailGroups = [
|
|
23
|
+
{ title: '参数配置', fields: parameterFields.filter(({ key }) => key === 'paraType' || key === 'paraMark' || key === 'paraValue') },
|
|
24
|
+
{ title: '说明信息', fields: parameterFields.filter(({ key }) => key === 'paraDesc' || key === 'remark') },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const createFormValues = (parameter: DigitalRmbParameter = {}): ParameterFormValues => ({
|
|
28
|
+
paraName: parameter.paraName ?? '',
|
|
29
|
+
paraType: parameter.paraType ?? '',
|
|
30
|
+
paraMark: parameter.paraMark ?? '',
|
|
31
|
+
paraValue: parameter.paraValue ?? '',
|
|
32
|
+
paraDesc: parameter.paraDesc ?? '',
|
|
33
|
+
remark: parameter.remark ?? '',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const errorMessage = (error: unknown): string =>
|
|
37
|
+
error instanceof Error && error.message.trim() ? error.message : '请求失败,请稍后重试'
|
|
38
|
+
|
|
39
|
+
const ensureSuccess = (response: any, fallback: string): any => {
|
|
40
|
+
if (response?.head?.retCode !== '00000') {
|
|
41
|
+
throw new Error(response?.head?.retMsg?.trim() || fallback)
|
|
42
|
+
}
|
|
43
|
+
return response.body
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 只读取契约中的字符串字段,参数值不转换成数字,也不去掉首尾空格。 */
|
|
47
|
+
const readParameter = (value: unknown): DigitalRmbParameter => {
|
|
48
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
49
|
+
throw new Error('服务端返回的参数格式异常')
|
|
50
|
+
}
|
|
51
|
+
const source = value as Record<string, unknown>
|
|
52
|
+
const result: DigitalRmbParameter = {}
|
|
53
|
+
for (const { key } of parameterFields) {
|
|
54
|
+
const field = source[key]
|
|
55
|
+
if (field === undefined) continue
|
|
56
|
+
if (typeof field !== 'string') throw new Error('服务端返回的参数字段格式异常')
|
|
57
|
+
result[key] = field
|
|
58
|
+
}
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const hasName = (parameter: DigitalRmbParameter): boolean => Boolean(parameter.paraName?.trim())
|
|
63
|
+
const displayValue = (value?: string): string => value === undefined || value === '' ? '—' : value
|
|
64
|
+
|
|
65
|
+
const keywords = ref('')
|
|
66
|
+
let appliedKeywords = ''
|
|
67
|
+
const rows = ref<DigitalRmbParameter[]>([])
|
|
68
|
+
const selectedRows = ref<DigitalRmbParameter[]>([])
|
|
69
|
+
const pagination = reactive({ current: 1, size: 10, total: 0 })
|
|
70
|
+
const paginationOptions: PaginationOptions = { align: 'right', pageSizes: [10, 20, 50], background: false }
|
|
71
|
+
const tableSettings = ref<TableSettings>({ size: 'small', stripe: true, border: true, headerBackground: true })
|
|
72
|
+
const tableRef = ref<InstanceType<typeof ArtDataTable> | null>(null)
|
|
73
|
+
const showSearchBar = ref(true)
|
|
74
|
+
const listLoading = ref(false)
|
|
75
|
+
const listError = ref('')
|
|
76
|
+
const pendingMutation = ref<Mutation>(null)
|
|
77
|
+
const writing = computed(() => pendingMutation.value !== null)
|
|
78
|
+
const listBusy = computed(() => listLoading.value || writing.value)
|
|
79
|
+
let listRequestId = 0
|
|
80
|
+
let detailRequestId = 0
|
|
81
|
+
let disposed = false
|
|
82
|
+
|
|
83
|
+
const textColumn = (prop: keyof DigitalRmbParameter, label: string, minWidth: number): ColumnOption<DigitalRmbParameter> => ({
|
|
84
|
+
prop, label, minWidth, showOverflowTooltip: true,
|
|
85
|
+
formatter: (row) => displayValue(row[prop]),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const tableColumns: ColumnOption<DigitalRmbParameter>[] = [
|
|
89
|
+
{ type: 'selection', width: 42, fixed: 'left', selectable: hasName },
|
|
90
|
+
{ type: 'globalIndex', label: '序号', width: 80, align: 'center' },
|
|
91
|
+
{ ...textColumn('paraName', '参数名称', 180), useSlot: true, slotName: 'name' },
|
|
92
|
+
textColumn('paraType', '参数类型', 110),
|
|
93
|
+
textColumn('paraMark', '参数标志', 110),
|
|
94
|
+
{ ...textColumn('paraValue', '参数值', 190), useSlot: true, slotName: 'value' },
|
|
95
|
+
textColumn('paraDesc', '参数描述', 220),
|
|
96
|
+
textColumn('remark', '备注', 180),
|
|
97
|
+
{ key: 'actions', label: '操作', width: 200, fixed: 'right', align: 'center', useSlot: true },
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
const clearSelection = () => {
|
|
101
|
+
selectedRows.value = []
|
|
102
|
+
tableRef.value?.getElTable()?.clearSelection()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 查询和翻页使用已提交的关键字,旧请求返回时不覆盖新结果。 */
|
|
106
|
+
const loadList = async () => {
|
|
107
|
+
if (disposed) return
|
|
108
|
+
const requestId = ++listRequestId
|
|
109
|
+
const params = { keywords: appliedKeywords || undefined, pageIndex: pagination.current, pageSize: pagination.size }
|
|
110
|
+
listLoading.value = true
|
|
111
|
+
listError.value = ''
|
|
112
|
+
clearSelection()
|
|
113
|
+
try {
|
|
114
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
115
|
+
const response = await API.queryListApi(params)
|
|
116
|
+
if (disposed || requestId !== listRequestId) return
|
|
117
|
+
const body: ParameterListBody | null = ensureSuccess(response, '参数查询失败')
|
|
118
|
+
if (!body || !Array.isArray(body.resultList)) throw new Error('查询结果缺少参数列表')
|
|
119
|
+
const totalText = body.totalCount ?? String(body.resultList.length)
|
|
120
|
+
const total = Number(totalText)
|
|
121
|
+
if (!/^\d+$/.test(totalText) || !Number.isSafeInteger(total) || total < 0) {
|
|
122
|
+
throw new Error('查询返回的总条数无效')
|
|
123
|
+
}
|
|
124
|
+
const lastPage = Math.max(1, Math.ceil(total / params.pageSize))
|
|
125
|
+
if (params.pageIndex > lastPage) {
|
|
126
|
+
pagination.current = lastPage
|
|
127
|
+
params.pageIndex = lastPage
|
|
128
|
+
if (attempt === 0) continue
|
|
129
|
+
throw new Error('参数列表已变化,请重新查询')
|
|
130
|
+
}
|
|
131
|
+
rows.value = body.resultList.map(readParameter)
|
|
132
|
+
pagination.total = total
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (disposed || requestId !== listRequestId) return
|
|
137
|
+
rows.value = []
|
|
138
|
+
Object.assign(pagination, { current: 1, total: 0 })
|
|
139
|
+
listError.value = errorMessage(error)
|
|
140
|
+
ElMessage.error(listError.value)
|
|
141
|
+
} finally {
|
|
142
|
+
if (!disposed && requestId === listRequestId) listLoading.value = false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const search = () => {
|
|
147
|
+
if (listBusy.value) return
|
|
148
|
+
appliedKeywords = keywords.value.trim()
|
|
149
|
+
pagination.current = 1
|
|
150
|
+
void loadList()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const resetFilters = () => {
|
|
154
|
+
if (listBusy.value) return
|
|
155
|
+
keywords.value = ''
|
|
156
|
+
search()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const refreshList = () => {
|
|
160
|
+
if (!listBusy.value) void loadList()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const changePage = (current: number, size = pagination.size) => {
|
|
164
|
+
if (listBusy.value || (current === pagination.current && size === pagination.size)) return
|
|
165
|
+
Object.assign(pagination, { current, size })
|
|
166
|
+
void loadList()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 新增、修改和详情共用弹窗;详情失败时禁用保存,避免提交未完整加载的数据。
|
|
170
|
+
const dialogVisible = ref(false)
|
|
171
|
+
const dialogMode = ref<DialogMode>('create')
|
|
172
|
+
const dialogLoading = ref(false)
|
|
173
|
+
const dialogReady = ref(false)
|
|
174
|
+
const dialogError = ref('')
|
|
175
|
+
const dialogSource = ref<DigitalRmbParameter | null>(null)
|
|
176
|
+
const form = ref(createFormValues())
|
|
177
|
+
const formNameError = ref('')
|
|
178
|
+
const dialogTitle = computed(() => ({ create: '新增参数', edit: '修改参数', detail: '数字人民币参数详情' })[dialogMode.value])
|
|
179
|
+
const dialogBusy = computed(() => dialogLoading.value || writing.value)
|
|
180
|
+
|
|
181
|
+
const openCreateDialog = () => {
|
|
182
|
+
if (listBusy.value) return
|
|
183
|
+
detailRequestId += 1
|
|
184
|
+
dialogMode.value = 'create'
|
|
185
|
+
dialogSource.value = null
|
|
186
|
+
form.value = createFormValues()
|
|
187
|
+
formNameError.value = ''
|
|
188
|
+
dialogError.value = ''
|
|
189
|
+
dialogLoading.value = false
|
|
190
|
+
dialogReady.value = true
|
|
191
|
+
dialogVisible.value = true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const loadDetail = async () => {
|
|
195
|
+
const source = dialogSource.value
|
|
196
|
+
if (!source || !hasName(source) || disposed || writing.value) return
|
|
197
|
+
const paraName = source.paraName!
|
|
198
|
+
const requestId = ++detailRequestId
|
|
199
|
+
dialogLoading.value = true
|
|
200
|
+
dialogReady.value = false
|
|
201
|
+
dialogError.value = ''
|
|
202
|
+
try {
|
|
203
|
+
const response = await API.getDetailApi(paraName)
|
|
204
|
+
if (disposed || requestId !== detailRequestId) return
|
|
205
|
+
const detail = readParameter(ensureSuccess(response, '读取参数详情失败'))
|
|
206
|
+
if (!Object.keys(detail).length) throw new Error('服务端未返回参数详情')
|
|
207
|
+
if (detail.paraName !== undefined && detail.paraName !== paraName) {
|
|
208
|
+
throw new Error('返回的参数名称与当前记录不一致')
|
|
209
|
+
}
|
|
210
|
+
form.value = createFormValues({ ...source, ...detail, paraName })
|
|
211
|
+
dialogReady.value = true
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (!disposed && requestId === detailRequestId) dialogError.value = errorMessage(error)
|
|
214
|
+
} finally {
|
|
215
|
+
if (!disposed && requestId === detailRequestId) dialogLoading.value = false
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const openParameterDialog = (parameter: DigitalRmbParameter, mode: 'edit' | 'detail' = 'detail') => {
|
|
220
|
+
if (listBusy.value || !hasName(parameter)) return
|
|
221
|
+
dialogMode.value = mode
|
|
222
|
+
dialogSource.value = { ...parameter }
|
|
223
|
+
form.value = createFormValues(parameter)
|
|
224
|
+
formNameError.value = ''
|
|
225
|
+
dialogError.value = ''
|
|
226
|
+
dialogVisible.value = true
|
|
227
|
+
void loadDetail()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const closeParameterDialog = () => {
|
|
231
|
+
// 关闭后即使请求继续返回,也不能更新下一次打开的弹窗。
|
|
232
|
+
detailRequestId += 1
|
|
233
|
+
dialogLoading.value = false
|
|
234
|
+
dialogReady.value = false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const submitParameter = async () => {
|
|
238
|
+
const mode = dialogMode.value
|
|
239
|
+
if (mode === 'detail' || dialogBusy.value || !dialogReady.value || !dialogVisible.value) return
|
|
240
|
+
const values = { ...form.value }
|
|
241
|
+
if (!values.paraName.trim()) {
|
|
242
|
+
formNameError.value = '请填写参数名称'
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
formNameError.value = ''
|
|
246
|
+
dialogError.value = ''
|
|
247
|
+
pendingMutation.value = mode
|
|
248
|
+
try {
|
|
249
|
+
const response = mode === 'create'
|
|
250
|
+
? await API.addApi({ ...values, paraName: values.paraName.trim() })
|
|
251
|
+
: await API.editApi({
|
|
252
|
+
paraName: dialogSource.value!.paraName!,
|
|
253
|
+
paraType: values.paraType,
|
|
254
|
+
paraValue: values.paraValue,
|
|
255
|
+
paraDesc: values.paraDesc,
|
|
256
|
+
remark: values.remark,
|
|
257
|
+
})
|
|
258
|
+
if (disposed) return
|
|
259
|
+
ensureSuccess(response, mode === 'create' ? '新增参数失败' : '修改参数失败')
|
|
260
|
+
ElMessage.success(response.head.retMsg?.trim() || (mode === 'create' ? '新增请求成功' : '修改请求成功'))
|
|
261
|
+
dialogVisible.value = false
|
|
262
|
+
// 写接口不返回记录;重新查询服务端,不在前端伪造保存后的列表。
|
|
263
|
+
await loadList()
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (!disposed) dialogError.value = errorMessage(error)
|
|
266
|
+
} finally {
|
|
267
|
+
if (!disposed) pendingMutation.value = null
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 删除名单取操作时的快照,确认后提交参数名称数组,支持单条和批量删除。
|
|
272
|
+
const deleteDialogVisible = ref(false)
|
|
273
|
+
const deleteNames = ref<string[]>([])
|
|
274
|
+
const deleteError = ref('')
|
|
275
|
+
const openDeleteDialog = (parameters: DigitalRmbParameter[]) => {
|
|
276
|
+
if (listBusy.value) return
|
|
277
|
+
const names = parameters.filter(hasName).map((parameter) => parameter.paraName!)
|
|
278
|
+
if (!names.length) return
|
|
279
|
+
deleteNames.value = [...new Set(names)]
|
|
280
|
+
deleteError.value = ''
|
|
281
|
+
deleteDialogVisible.value = true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const submitDelete = async () => {
|
|
285
|
+
if (writing.value || !deleteDialogVisible.value || !deleteNames.value.length) return
|
|
286
|
+
pendingMutation.value = 'delete'
|
|
287
|
+
deleteError.value = ''
|
|
288
|
+
try {
|
|
289
|
+
const response = await API.deleteApi(deleteNames.value)
|
|
290
|
+
if (disposed) return
|
|
291
|
+
ensureSuccess(response, '删除参数失败')
|
|
292
|
+
ElMessage.success(response.head.retMsg?.trim() || '删除请求成功')
|
|
293
|
+
deleteDialogVisible.value = false
|
|
294
|
+
await loadList()
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (!disposed) deleteError.value = errorMessage(error)
|
|
297
|
+
} finally {
|
|
298
|
+
if (!disposed) pendingMutation.value = null
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
onMounted(loadList)
|
|
303
|
+
onBeforeUnmount(() => {
|
|
304
|
+
disposed = true
|
|
305
|
+
listRequestId += 1
|
|
306
|
+
detailRequestId += 1
|
|
307
|
+
})
|
|
308
|
+
</script>
|
|
309
|
+
|
|
310
|
+
<template>
|
|
311
|
+
<ElConfigProvider :size="tableSettings.size">
|
|
312
|
+
<main class="dcps-page">
|
|
313
|
+
<!-- 与授权页统一:查询卡片整体显隐,筛选项上方显示标签,操作按钮靠右。 -->
|
|
314
|
+
<el-card v-show="showSearchBar" class="parameter-search" shadow="never" :body-style="{ padding: '10px' }">
|
|
315
|
+
<el-form
|
|
316
|
+
class="filter-form"
|
|
317
|
+
:size="tableSettings.size"
|
|
318
|
+
label-position="top"
|
|
319
|
+
:disabled="listBusy"
|
|
320
|
+
@submit.prevent="search()"
|
|
321
|
+
>
|
|
322
|
+
<el-form-item label="关键字">
|
|
323
|
+
<el-input v-model="keywords" placeholder="输入查询关键字" clearable />
|
|
324
|
+
</el-form-item>
|
|
325
|
+
<el-space class="filter-actions" :size="8">
|
|
326
|
+
<el-button @click="resetFilters">重置</el-button>
|
|
327
|
+
<el-button type="primary" native-type="submit" :loading="listLoading">查询</el-button>
|
|
328
|
+
</el-space>
|
|
329
|
+
</el-form>
|
|
330
|
+
</el-card>
|
|
331
|
+
|
|
332
|
+
<el-alert v-if="listError" :title="listError" type="error" show-icon :closable="false" />
|
|
333
|
+
|
|
334
|
+
<div class="parameter-table-region">
|
|
335
|
+
<ArtDataTable
|
|
336
|
+
ref="tableRef"
|
|
337
|
+
v-model:settings="tableSettings"
|
|
338
|
+
v-model:show-search-bar="showSearchBar"
|
|
339
|
+
style="padding: 10px"
|
|
340
|
+
fill
|
|
341
|
+
toolbar-layout="search,refresh,size,fullscreen,columns,settings"
|
|
342
|
+
:data="rows"
|
|
343
|
+
:columns="tableColumns"
|
|
344
|
+
:loading="listBusy"
|
|
345
|
+
:pagination="pagination"
|
|
346
|
+
:pagination-options="paginationOptions"
|
|
347
|
+
:empty-text="listError ? '查询失败,请重新查询' : '暂无符合条件的参数'"
|
|
348
|
+
@refresh="refreshList"
|
|
349
|
+
@pagination:current-change="changePage"
|
|
350
|
+
@pagination:size-change="(size: number) => changePage(1, size)"
|
|
351
|
+
@selection-change="(selection: DigitalRmbParameter[]) => selectedRows = selection"
|
|
352
|
+
@row-dblclick="(row: DigitalRmbParameter) => openParameterDialog(row)"
|
|
353
|
+
>
|
|
354
|
+
<template #toolbar-left>
|
|
355
|
+
<div class="table-actions">
|
|
356
|
+
<el-button type="primary" :disabled="listBusy" @click="openCreateDialog">新增参数</el-button>
|
|
357
|
+
<el-button type="danger" plain :disabled="listBusy || !selectedRows.length" @click="openDeleteDialog(selectedRows)">
|
|
358
|
+
批量删除
|
|
359
|
+
</el-button>
|
|
360
|
+
<span v-if="selectedRows.length" class="selection-count">已选 {{ selectedRows.length }} 项</span>
|
|
361
|
+
</div>
|
|
362
|
+
</template>
|
|
363
|
+
<template #name="{ row }">
|
|
364
|
+
<el-button v-if="hasName(row)" class="parameter-name" link type="primary" :disabled="listBusy" @click.stop="openParameterDialog(row)" @dblclick.stop>
|
|
365
|
+
{{ row.paraName }}
|
|
366
|
+
</el-button>
|
|
367
|
+
<span v-else>—</span>
|
|
368
|
+
</template>
|
|
369
|
+
<template #value="{ row }">
|
|
370
|
+
<span class="parameter-value">{{ displayValue(row.paraValue) }}</span>
|
|
371
|
+
</template>
|
|
372
|
+
<template #actions="{ row }">
|
|
373
|
+
<el-button text bg type="primary" :disabled="listBusy || !hasName(row)" @click.stop="openParameterDialog(row)" @dblclick.stop>详情</el-button>
|
|
374
|
+
<el-button text bg type="primary" :disabled="listBusy || !hasName(row)" @click.stop="openParameterDialog(row, 'edit')" @dblclick.stop>修改</el-button>
|
|
375
|
+
<el-button text bg type="danger" :disabled="listBusy || !hasName(row)" @click.stop="openDeleteDialog([row])" @dblclick.stop>删除</el-button>
|
|
376
|
+
</template>
|
|
377
|
+
</ArtDataTable>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<el-dialog
|
|
381
|
+
v-model="dialogVisible"
|
|
382
|
+
:title="dialogTitle"
|
|
383
|
+
:style="dialogMode === 'detail' ? { '--el-dialog-padding-primary': '10px', padding: '10px 16px', marginBottom: '16px' } : undefined"
|
|
384
|
+
:width="dialogMode === 'detail' ? 'min(1280px, calc(100vw - 32px))' : 'min(540px, calc(100vw - 32px))'"
|
|
385
|
+
:top="dialogMode === 'detail' ? '16px' : '15vh'"
|
|
386
|
+
append-to-body
|
|
387
|
+
destroy-on-close
|
|
388
|
+
:show-close="!writing"
|
|
389
|
+
:close-on-click-modal="false"
|
|
390
|
+
:close-on-press-escape="!writing"
|
|
391
|
+
@close="closeParameterDialog"
|
|
392
|
+
>
|
|
393
|
+
<el-alert v-if="dialogError" class="dialog-error" :title="dialogError" type="error" show-icon :closable="false" />
|
|
394
|
+
<el-scrollbar :class="{ 'detail-scrollbar': dialogMode === 'detail' }" :max-height="dialogMode === 'detail' ? undefined : 'min(62vh, 620px)'">
|
|
395
|
+
<div v-if="dialogLoading" class="detail-loading" role="status">正在读取参数详情…</div>
|
|
396
|
+
<div v-else-if="dialogMode === 'detail' && dialogReady" class="detail-content">
|
|
397
|
+
<header class="detail-summary">
|
|
398
|
+
<div class="detail-reference">
|
|
399
|
+
<span class="detail-label">参数名称</span>
|
|
400
|
+
<strong>{{ displayValue(form.paraName) }}</strong>
|
|
401
|
+
</div>
|
|
402
|
+
<div>
|
|
403
|
+
<span class="detail-label">参数类型</span>
|
|
404
|
+
<strong class="detail-status">{{ displayValue(form.paraType) }}</strong>
|
|
405
|
+
</div>
|
|
406
|
+
<div>
|
|
407
|
+
<span class="detail-label">参数标志</span>
|
|
408
|
+
<strong class="detail-status">{{ displayValue(form.paraMark) }}</strong>
|
|
409
|
+
</div>
|
|
410
|
+
</header>
|
|
411
|
+
<section v-for="group in detailGroups" :key="group.title" class="detail-group" :aria-label="group.title">
|
|
412
|
+
<h3>{{ group.title }}</h3>
|
|
413
|
+
<dl class="detail-fields">
|
|
414
|
+
<div v-for="field in group.fields" :key="field.key" class="detail-field" :class="{ 'detail-field--wide': field.multiline }">
|
|
415
|
+
<dt>{{ field.label }}</dt>
|
|
416
|
+
<dd :class="{ 'parameter-value': field.key === 'paraValue' }">{{ displayValue(form[field.key]) }}</dd>
|
|
417
|
+
</div>
|
|
418
|
+
</dl>
|
|
419
|
+
</section>
|
|
420
|
+
</div>
|
|
421
|
+
<el-form
|
|
422
|
+
v-else-if="dialogMode !== 'detail' && dialogReady"
|
|
423
|
+
id="parameter-editor-form"
|
|
424
|
+
class="parameter-form"
|
|
425
|
+
label-position="top"
|
|
426
|
+
:model="form"
|
|
427
|
+
:disabled="dialogBusy"
|
|
428
|
+
@submit.prevent="submitParameter()"
|
|
429
|
+
>
|
|
430
|
+
<el-form-item
|
|
431
|
+
v-for="field in parameterFields"
|
|
432
|
+
:key="field.key"
|
|
433
|
+
:label="field.label"
|
|
434
|
+
:required="field.key === 'paraName'"
|
|
435
|
+
:error="field.key === 'paraName' ? formNameError : undefined"
|
|
436
|
+
:class="{ 'form-item--wide': field.multiline }"
|
|
437
|
+
>
|
|
438
|
+
<el-input
|
|
439
|
+
v-model="form[field.key]"
|
|
440
|
+
:type="field.multiline ? 'textarea' : 'text'"
|
|
441
|
+
:autosize="field.multiline ? { minRows: 2, maxRows: 5 } : undefined"
|
|
442
|
+
:readonly="dialogMode === 'edit' && (field.key === 'paraName' || field.key === 'paraMark')"
|
|
443
|
+
:placeholder="`请输入${field.label}`"
|
|
444
|
+
@input="field.key === 'paraName' && (formNameError = '')"
|
|
445
|
+
/>
|
|
446
|
+
</el-form-item>
|
|
447
|
+
</el-form>
|
|
448
|
+
</el-scrollbar>
|
|
449
|
+
<template #footer>
|
|
450
|
+
<el-button v-if="dialogError && !dialogReady && dialogSource" :loading="dialogLoading" @click="loadDetail">重新读取</el-button>
|
|
451
|
+
<el-button :disabled="writing" @click="dialogVisible = false">{{ dialogMode === 'detail' ? '关闭' : '取消' }}</el-button>
|
|
452
|
+
<el-button
|
|
453
|
+
v-if="dialogMode !== 'detail'"
|
|
454
|
+
type="primary"
|
|
455
|
+
:loading="pendingMutation === 'create' || pendingMutation === 'edit'"
|
|
456
|
+
:disabled="dialogLoading || !dialogReady || writing"
|
|
457
|
+
@click="submitParameter"
|
|
458
|
+
>
|
|
459
|
+
{{ dialogMode === 'create' ? '新增' : '保存' }}
|
|
460
|
+
</el-button>
|
|
461
|
+
</template>
|
|
462
|
+
</el-dialog>
|
|
463
|
+
|
|
464
|
+
<el-dialog
|
|
465
|
+
v-model="deleteDialogVisible"
|
|
466
|
+
title="删除参数"
|
|
467
|
+
width="min(540px, calc(100vw - 32px))"
|
|
468
|
+
append-to-body
|
|
469
|
+
destroy-on-close
|
|
470
|
+
:show-close="!writing"
|
|
471
|
+
:close-on-click-modal="false"
|
|
472
|
+
:close-on-press-escape="!writing"
|
|
473
|
+
>
|
|
474
|
+
<p class="delete-description">确认删除以下 {{ deleteNames.length }} 项参数?</p>
|
|
475
|
+
<el-scrollbar max-height="240px">
|
|
476
|
+
<ul class="delete-list"><li v-for="name in deleteNames" :key="name">{{ name }}</li></ul>
|
|
477
|
+
</el-scrollbar>
|
|
478
|
+
<el-alert v-if="deleteError" class="dialog-error" :title="deleteError" type="error" show-icon :closable="false" />
|
|
479
|
+
<template #footer>
|
|
480
|
+
<el-button :disabled="writing" @click="deleteDialogVisible = false">取消</el-button>
|
|
481
|
+
<el-button type="danger" :loading="pendingMutation === 'delete'" :disabled="writing" @click="submitDelete">确认删除</el-button>
|
|
482
|
+
</template>
|
|
483
|
+
</el-dialog>
|
|
484
|
+
</main>
|
|
485
|
+
</ElConfigProvider>
|
|
486
|
+
</template>
|
|
487
|
+
|
|
488
|
+
<style scoped>
|
|
489
|
+
.dcps-page {
|
|
490
|
+
box-sizing: border-box;
|
|
491
|
+
display: flex;
|
|
492
|
+
flex-direction: column;
|
|
493
|
+
gap: 5px;
|
|
494
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
495
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
496
|
+
min-width: 0;
|
|
497
|
+
min-height: 0;
|
|
498
|
+
padding: 5px;
|
|
499
|
+
overflow: hidden;
|
|
500
|
+
background: var(--el-bg-color-page, #f7f8fa);
|
|
501
|
+
}
|
|
502
|
+
.parameter-search {
|
|
503
|
+
flex: 0 0 auto;
|
|
504
|
+
max-height: 42%;
|
|
505
|
+
max-height: min(42dvh, 360px);
|
|
506
|
+
min-height: 0;
|
|
507
|
+
overflow-y: auto;
|
|
508
|
+
}
|
|
509
|
+
.filter-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; }
|
|
510
|
+
.filter-form :deep(.el-form-item) { flex: 1 0 105px; min-width: 0; margin: 0; }
|
|
511
|
+
.filter-form :deep(.el-form-item__label) { margin-bottom: 4px; }
|
|
512
|
+
.filter-actions { flex-shrink: 0; margin-left: auto; }
|
|
513
|
+
.parameter-table-region { flex: 1; min-width: 0; min-height: 0; }
|
|
514
|
+
.table-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
515
|
+
.table-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
|
516
|
+
.selection-count { margin-left: 4px; color: var(--el-text-color-secondary); font-size: 12px; white-space: nowrap; }
|
|
517
|
+
.parameter-name { display: inline; max-width: 100%; overflow: hidden; text-overflow: ellipsis; text-align: left; vertical-align: middle; }
|
|
518
|
+
.parameter-value { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-variant-numeric: tabular-nums; white-space: pre-wrap; }
|
|
519
|
+
.parameter-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
|
|
520
|
+
.form-item--wide { grid-column: 1 / -1; }
|
|
521
|
+
.parameter-form :deep(.el-input__inner[readonly]) { color: var(--el-text-color-secondary); }
|
|
522
|
+
.dialog-error { margin-bottom: 14px; }
|
|
523
|
+
.detail-loading { padding: 40px 0; color: var(--el-text-color-secondary); text-align: center; }
|
|
524
|
+
.detail-scrollbar :deep(.el-scrollbar__wrap) {
|
|
525
|
+
max-height: calc(100vh - 120px);
|
|
526
|
+
max-height: calc(100dvh - 120px);
|
|
527
|
+
}
|
|
528
|
+
.detail-content { display: grid; gap: 8px; font-size: 13px; line-height: 1.4; }
|
|
529
|
+
.detail-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; padding: 14px; border-radius: 8px; background: var(--el-color-primary-light-9); overflow-wrap: anywhere; }
|
|
530
|
+
.detail-label,
|
|
531
|
+
.detail-field dt { color: var(--el-text-color-secondary); font-size: 12px; }
|
|
532
|
+
.detail-reference { display: grid; grid-column: span 2; align-content: start; gap: 4px; }
|
|
533
|
+
.detail-reference strong { color: var(--el-text-color-primary); font-size: 15px; }
|
|
534
|
+
.detail-status { display: block; margin-top: 6px; color: var(--el-text-color-primary); font-size: 15px; }
|
|
535
|
+
.detail-group { display: grid; grid-template-columns: 72px minmax(0, 1fr); align-items: start; gap: 8px; padding: 12px; border: 1px solid var(--el-border-color-lighter); border-radius: 8px; }
|
|
536
|
+
.detail-group h3 { margin: 4px 0 0; padding-left: 8px; border-left: 3px solid var(--el-color-primary); color: var(--el-text-color-primary); font-size: 14px; line-height: 20px; }
|
|
537
|
+
.detail-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 0; }
|
|
538
|
+
.detail-field { min-width: 0; padding: 8px 12px; border-radius: 6px; background: var(--el-fill-color-lighter); }
|
|
539
|
+
.detail-field--wide { grid-column: 1 / -1; }
|
|
540
|
+
.detail-field dd { margin: 2px 0 0; color: var(--el-text-color-primary); font-weight: 600; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
541
|
+
.delete-description { margin: 0 0 12px; line-height: 1.6; }
|
|
542
|
+
.delete-list { margin: 0 0 14px; padding: 12px 14px 12px 32px; border-radius: 8px; background: var(--el-fill-color-light); }
|
|
543
|
+
.delete-list li { padding: 3px 0; overflow-wrap: anywhere; }
|
|
544
|
+
@media (max-width: 1024px) {
|
|
545
|
+
.detail-group { grid-template-columns: minmax(0, 1fr); }
|
|
546
|
+
.detail-group h3 { margin-top: 0; }
|
|
547
|
+
.detail-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
548
|
+
}
|
|
549
|
+
@media (max-width: 600px) {
|
|
550
|
+
.detail-summary, .detail-fields { grid-template-columns: minmax(0, 1fr); }
|
|
551
|
+
.detail-reference { grid-column: auto; }
|
|
552
|
+
}
|
|
553
|
+
@media (max-width: 540px) {
|
|
554
|
+
.filter-form :deep(.el-form-item) { flex-basis: 100%; }
|
|
555
|
+
.filter-actions { margin-left: 0; }
|
|
556
|
+
.parameter-form { grid-template-columns: minmax(0, 1fr); }
|
|
557
|
+
.selection-count { width: 100%; margin-left: 0; }
|
|
558
|
+
}
|
|
559
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** 数字人民币头寸参数,字段及可选性对应原始接口契约。 */
|
|
2
|
+
export interface DigitalRmbParameter {
|
|
3
|
+
/** 参数描述 */
|
|
4
|
+
paraDesc?: string
|
|
5
|
+
/** 参数标志;接口未声明取值或枚举。 */
|
|
6
|
+
paraMark?: string
|
|
7
|
+
/** 参数名称;页面使用原名称定位详情、修改和删除。 */
|
|
8
|
+
paraName?: string
|
|
9
|
+
/** 参数类型;接口未声明取值或枚举。 */
|
|
10
|
+
paraType?: string
|
|
11
|
+
/** 参数值,保留原始字符串。 */
|
|
12
|
+
paraValue?: string
|
|
13
|
+
/** 备注 */
|
|
14
|
+
remark?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 列表响应,totalCount 按原始契约保留字符串。 */
|
|
18
|
+
export interface ParameterListBody {
|
|
19
|
+
resultList?: DigitalRmbParameter[]
|
|
20
|
+
totalCount?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 修改接口仅支持这五个参数字段,不包含模块代码和参数标志。 */
|
|
24
|
+
export type ParameterEditBody = Omit<DigitalRmbParameter, 'paraMark'>
|
|
25
|
+
|
|
26
|
+
export type ParameterFormValues = Required<DigitalRmbParameter>
|
|
27
|
+
|
|
28
|
+
/** 模块由页面接口固定为数币,调用处只提供关键字和公共分页。 */
|
|
29
|
+
export interface ParameterQueryParams {
|
|
30
|
+
keywords?: string
|
|
31
|
+
pageIndex: number
|
|
32
|
+
pageSize: number
|
|
33
|
+
}
|
package/src/view/dcps/runtime.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DCPS
|
|
3
|
-
* 页面通过 `import dcpsRuntime from '../runtime'` 引入;分组子页面使用 `../../runtime`。
|
|
2
|
+
* DCPS 各页面共用的运行时配置,统一通过具名对象 dcpsRuntime 导出。
|
|
3
|
+
* 页面通过 `import { dcpsRuntime } from '../runtime'` 引入;分组子页面使用 `../../runtime`。
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// 模块加载时按上海时区生成一次日期,所有页面共用,格式为 YYYYMMDD。
|
|
@@ -9,7 +9,7 @@ const dateParts = new Intl.DateTimeFormat('en-CA', {
|
|
|
9
9
|
}).formatToParts(new Date())
|
|
10
10
|
const readDatePart = (type: Intl.DateTimeFormatPartTypes) => dateParts.find((part) => part.type === type)?.value || ''
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export const dcpsRuntime = {
|
|
13
13
|
date: readDatePart('year') + readDatePart('month') + readDatePart('day'), // 共用业务日期。
|
|
14
14
|
approverId: '10092', // 工号。
|
|
15
15
|
approverName: '王干事', // 姓名。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
3
|
import { ElConfigProvider, ElMessage, ElMessageBox } from 'element-plus'
|
|
4
4
|
import API from './api'
|
|
5
|
-
import dcpsRuntime from '../runtime'
|
|
5
|
+
import { dcpsRuntime } from '../runtime'
|
|
6
6
|
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
7
7
|
import type {
|
|
8
8
|
CancelWriteOffPredItem, EditVerifyFormValues, FlowRow, ForecastRow, WriteOffFlowInput, WriteOffForecastItem, WriteOffItemInput,
|