vue3-element-plus-app-chu 0.0.22 → 0.0.23

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 CHANGED
@@ -30,8 +30,8 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
30
30
  - 模块目录与授权页面保持一致:`api.ts` 只放接口定义,`utils.ts` 放日期、金额、表单校验等共用工具,`typs.ts` 类型,`runtime.ts` 默认交易日期,`index.vue` 为入口,`components/` 放页面外壳和列表、新增修改、详情页面。接口定义只维护在各自模块下,不再集中到 `src/api`。
31
31
  - 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
32
32
 
33
- 本地后端的预报接口当前使用通用模拟响应,写入成功响应不代表真实持久化。
33
+ 本地后端已接入 MongoDB 业务模拟,预报增删改查、核销、注资余额、匡算历史和参数均可持久化。JSON 响应统一为 `{ code, data, message }`,TAM 的 `head/body` 位于 `data` 中,前端公共请求层自动解包。服务端导入协议尚未实现时明确返回 `501`。完整运行说明见 [后端 README](backend/README.md)。
34
34
 
35
35
  ## 城银清头寸参数接口
36
36
 
37
- 新增、修改、删除、查询、详情五个接口的封装及类型位于 `src/view/dcps/city-clearing/parameters/`,本次仅提供接口层。城银清查询与详情使用 `moduleCode: '3'`。本地后端提供符合字段契约的模拟响应及参数校验,写操作不持久化;接口字段和示例见 [城银清头寸参数接口说明](docs/city-clearing-parameters-apis.md),Scalar 文档入口为后端 `/docs`。
37
+ 参数管理接口已支持持久化增删改查、关键词筛选和分页。数币页面位于 `src/view/dcps/parameters/`,使用 `moduleCode: '5'`;页面详情直接取表格行数据,不再请求详情接口。同一后端也支持城银清模块 `'3'`。参数名称全局唯一,修改和删除按名称定位;接口字段和示例见 [城银清头寸参数接口说明](docs/city-clearing-parameters-apis.md),Scalar 文档入口为后端 `/docs`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-element-plus-app-chu",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -30,13 +30,13 @@ export interface TamResponseHead {
30
30
  [field: string]: unknown
31
31
  }
32
32
 
33
- /** TAM 接口直接返回公共响应头和业务体,不再增加额外包装。 */
33
+ /** 页面使用的 TAM 数据;请求层从统一响应的 data 中读取。 */
34
34
  export interface ApiResponse<T = unknown> {
35
35
  head: TamResponseHead
36
36
  body: T | null
37
37
  }
38
38
 
39
- /** 非 TAM 接口沿用原有响应格式。 */
39
+ /** 后端统一的 JSON 外层响应格式。 */
40
40
  export interface StandardApiResponse<T = unknown> {
41
41
  code: number
42
42
  data: T | null
@@ -124,7 +124,7 @@ http.interceptors.response.use(
124
124
 
125
125
  type RequestConfig = Omit<HttpRequestConfig, 'url' | 'method'>
126
126
 
127
- // 泛型 T 表示完整响应结构;直接透传服务端 JSON,保留 head、body 和公共头字段。
127
+ // TAM 接口将统一响应中的 data 解包,页面继续使用 head/body。
128
128
  export const request = async <T = ApiResponse>(
129
129
  config: HttpRequestConfig,
130
130
  ): Promise<T> => {
@@ -132,6 +132,12 @@ export const request = async <T = ApiResponse>(
132
132
  ...config,
133
133
  data: createTamEnvelope(config.data),
134
134
  })
135
+ const payload: unknown = response.data
136
+ if (/^\/TAM(?:\/|$)/i.test(config.url ?? '')
137
+ && isPlainObject(payload) && typeof payload.code === 'number') {
138
+ if (payload.code !== 0) throw new Error(typeof payload.message === 'string' ? payload.message : '请求失败')
139
+ return payload.data as T
140
+ }
135
141
  return response.data
136
142
  }
137
143
 
@@ -48,7 +48,7 @@
48
48
  - `forecast/index.vue` 顶部使用一个普通 `<script>` 块保存列表记录缓存,跳转修改或详情前暂存列表行,用于补齐详情接口未返回的字段。
49
49
  - 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应路由的 `name` 保持一致;分组菜单通过 `children` 配置二级页面,页面的 `meta.menu` 指向自己的菜单项。
50
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*` 文档文件名保留。当前本地后端仍返回模拟结果,不保存参数。
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
52
  - 数字人民币参数管理以数字人民币授权页面为布局参考,统一筛选区、填满剩余高度的表格以及包含摘要和分组字段的详情弹窗样式。
53
53
  - `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报页面;新增、修改、详情按路由 `key` 分别缓存,返回列表时自动重新查询。
54
54
 
@@ -615,6 +615,8 @@ onBeforeUnmount(() => {
615
615
  .dcps-page {
616
616
  box-sizing: border-box;
617
617
  display: flex;
618
+ width: 100%;
619
+ max-width: 100%;
618
620
  flex-direction: column;
619
621
  gap: 5px;
620
622
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -16,22 +16,23 @@ const tableSize = 'small'
16
16
  // 历史预计调拨金额接口(140503004)预填金额,允许手工调整后重新匡算。
17
17
  // 匡算结果(140503001)以合并单元格的头寸匡算表展示,样式与业务原型一致;
18
18
  // 「今日预计调拨金额」行的收入、支付单元格就是查询条件输入框,随历史记录预填;
19
- // 导出按钮暂未接线(下载协议待确认);单击其余金额单元格按位置编码查询详情(140503003)。
19
+ // 导出按钮暂未接线(下载协议待确认);单击可查看明细的金额单元格按位置编码查询详情(140503003)。
20
20
 
21
21
  // 接口字段编码与图中报表位置的对应关系:
22
22
  // C3 城商行支付清算账户前一日余额;C6/D6 报备金额;C7/D7 未核销金额;
23
23
  // C8/D8 未核销业务流水;C11/D11 今日预计调拨金额(查询条件输入框,不查详情);
24
- // B15 当日实时余额。轧差列由前端按「收入减支出」计算:结果行随查询结果更新,
24
+ // B15 当日实时余额。C3、B15 仅展示余额,不提供明细弹窗。
25
+ // 轧差列由前端按「收入减支出」计算:结果行随查询结果更新,
25
26
  // 调拨行随输入实时更新;日初可用资金、预留金额及两条空行暂无接口字段,先按原型占位留空。
27
+
28
+ // 仅登记可查看明细的单元格;C3、B15 为余额展示,不参与详情查询。
26
29
  const cellLabels: Record<string, string> = {
27
- C3: '城商行支付清算账户前一日余额',
28
30
  C6: '报备金额(收入)',
29
31
  D6: '报备金额(支付)',
30
32
  C7: '未核销金额(收入)',
31
33
  D7: '未核销金额(支付)',
32
34
  C8: '未核销业务流水(收入)',
33
35
  D8: '未核销业务流水(支付)',
34
- B15: '当日实时余额',
35
36
  }
36
37
 
37
38
  // ---------- 页面内工具函数 ----------
@@ -380,10 +381,10 @@ onBeforeUnmount(() => {
380
381
  <tr>
381
382
  <td colspan="2" class="report-label">城商行支付清算账户前一日余额</td>
382
383
  <td colspan="2" class="report-data">
383
- <button type="button" class="report-value" @click="openDetail(cellOf('C3'))">
384
+ <div class="report-value report-value--static">
384
385
  <span class="report-amount">{{ amountText('C3') }}</span>
385
386
  <span class="report-tag">C3</span>
386
- </button>
387
+ </div>
387
388
  </td>
388
389
  <td class="report-label report-label--unit">单位:元</td>
389
390
  </tr>
@@ -498,10 +499,10 @@ onBeforeUnmount(() => {
498
499
  <tr>
499
500
  <td colspan="2" class="report-label">当日实时余额</td>
500
501
  <td colspan="3" class="report-data">
501
- <button type="button" class="report-value" @click="openDetail(cellOf('B15'))">
502
+ <div class="report-value report-value--static">
502
503
  <span class="report-amount">{{ amountText('B15') }}</span>
503
504
  <span class="report-tag">B15</span>
504
- </button>
505
+ </div>
505
506
  </td>
506
507
  </tr>
507
508
  </tbody>
@@ -633,6 +634,8 @@ onBeforeUnmount(() => {
633
634
  .dcps-page {
634
635
  box-sizing: border-box;
635
636
  display: flex;
637
+ width: 100%;
638
+ max-width: 100%;
636
639
  flex-direction: column;
637
640
  gap: 5px;
638
641
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -737,6 +740,11 @@ td.report-data--input { padding: 5px 8px; }
737
740
  }
738
741
  .report-value:hover { background: var(--el-fill-color-light); }
739
742
  .report-value:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: -2px; }
743
+ /* 无明细的余额单元格(C3、B15):保留金额与位置标注的对齐,但不响应点击。 */
744
+ .report-value--static,
745
+ .report-table:not(.is-ready) .report-value--static { cursor: default; }
746
+ .report-value--static:hover,
747
+ .report-table:not(.is-ready) .report-value--static:hover { background: transparent; }
740
748
  .report-amount { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; font-weight: 600; }
741
749
  .report-tag {
742
750
  flex: 0 0 auto;
@@ -889,6 +889,8 @@ onActivated(() => {
889
889
  .dcps-page {
890
890
  box-sizing: border-box;
891
891
  display: flex;
892
+ width: 100%;
893
+ max-width: 100%;
892
894
  flex-direction: column;
893
895
  gap: 5px;
894
896
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -199,15 +199,14 @@ onBeforeUnmount(() => {
199
199
 
200
200
  <template>
201
201
  <main class="dcps-page">
202
- <!-- 标题区:页面名称、最近查询时间、发起入口与查询按钮。 -->
202
+ <!-- 标题区:最近查询时间、发起入口与账户行余额查询按钮。 -->
203
203
  <el-card class="balance-header" shadow="never" :body-style="{ padding: '10px 14px' }">
204
204
  <div class="balance-heading">
205
- <h1 class="balance-title">数字人民币账户行余额查询申请</h1>
206
- <span v-if="lastQueryTime" class="balance-updated">最近查询:{{ lastQueryTime }}</span>
207
205
  <span class="balance-actions">
208
206
  <el-button @click="openCreateDialog">注资/预注资发起</el-button>
209
- <el-button type="primary" :loading="loading" @click="query()">查询</el-button>
207
+ <el-button type="primary" :loading="loading" @click="query()">账户行余额查询</el-button>
210
208
  </span>
209
+ <span v-if="lastQueryTime" class="balance-updated">最近查询:{{ lastQueryTime }}</span>
211
210
  </div>
212
211
  </el-card>
213
212
 
@@ -297,6 +296,8 @@ onBeforeUnmount(() => {
297
296
  .dcps-page {
298
297
  box-sizing: border-box;
299
298
  display: flex;
299
+ width: 100%;
300
+ max-width: 100%;
300
301
  flex-direction: column;
301
302
  gap: 5px;
302
303
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -308,10 +309,9 @@ onBeforeUnmount(() => {
308
309
  background: var(--el-bg-color-page, #f7f8fa);
309
310
  }
310
311
  .balance-header { flex: 0 0 auto; }
311
- .balance-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px; }
312
- .balance-title { margin: 0; color: var(--el-text-color-primary); font-size: 16px; font-weight: 600; line-height: 22px; }
312
+ .balance-heading { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; gap: 6px 14px; }
313
313
  .balance-updated { color: var(--el-text-color-secondary); font-size: 12px; }
314
- .balance-actions { margin-left: auto; display: flex; }
314
+ .balance-actions { display: flex; }
315
315
  .balance-actions :deep(.el-button + .el-button) { margin-left: 8px; }
316
316
 
317
317
  .balance-scroll { flex: 1; min-height: 0; min-width: 0; }
@@ -424,6 +424,8 @@ onBeforeUnmount(() => {
424
424
  .dcps-page {
425
425
  box-sizing: border-box;
426
426
  display: flex;
427
+ width: 100%;
428
+ max-width: 100%;
427
429
  flex-direction: column;
428
430
  gap: 5px;
429
431
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -17,9 +17,6 @@ export default {
17
17
  pageSize,
18
18
  }),
19
19
 
20
- getDetailApi: (paraName: string) =>
21
- baseHttpClient.post<any>('/TAM/0001/140305002', { moduleCode, paraName }),
22
-
23
20
  addApi: (values: ParameterFormValues) =>
24
21
  baseHttpClient.post<any>('/TAM/0001/120305002', { ...values, moduleCode }),
25
22
 
@@ -77,7 +77,6 @@ const pendingMutation = ref<Mutation>(null)
77
77
  const writing = computed(() => pendingMutation.value !== null)
78
78
  const listBusy = computed(() => listLoading.value || writing.value)
79
79
  let listRequestId = 0
80
- let detailRequestId = 0
81
80
  let disposed = false
82
81
 
83
82
  const textColumn = (prop: keyof DigitalRmbParameter, label: string, minWidth: number): ColumnOption<DigitalRmbParameter> => ({
@@ -166,56 +165,25 @@ const changePage = (current: number, size = pagination.size) => {
166
165
  void loadList()
167
166
  }
168
167
 
169
- // 新增、修改和详情共用弹窗;详情失败时禁用保存,避免提交未完整加载的数据。
168
+ // 新增、修改和详情共用弹窗;详情直接使用表格行数据,不再单独请求。
170
169
  const dialogVisible = ref(false)
171
170
  const dialogMode = ref<DialogMode>('create')
172
- const dialogLoading = ref(false)
173
- const dialogReady = ref(false)
174
171
  const dialogError = ref('')
175
172
  const dialogSource = ref<DigitalRmbParameter | null>(null)
176
173
  const form = ref(createFormValues())
177
174
  const formNameError = ref('')
178
175
  const dialogTitle = computed(() => ({ create: '新增参数', edit: '修改参数', detail: '数字人民币参数详情' })[dialogMode.value])
179
- const dialogBusy = computed(() => dialogLoading.value || writing.value)
180
176
 
181
177
  const openCreateDialog = () => {
182
178
  if (listBusy.value) return
183
- detailRequestId += 1
184
179
  dialogMode.value = 'create'
185
180
  dialogSource.value = null
186
181
  form.value = createFormValues()
187
182
  formNameError.value = ''
188
183
  dialogError.value = ''
189
- dialogLoading.value = false
190
- dialogReady.value = true
191
184
  dialogVisible.value = true
192
185
  }
193
186
 
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
187
  const openParameterDialog = (parameter: DigitalRmbParameter, mode: 'edit' | 'detail' = 'detail') => {
220
188
  if (listBusy.value || !hasName(parameter)) return
221
189
  dialogMode.value = mode
@@ -224,19 +192,11 @@ const openParameterDialog = (parameter: DigitalRmbParameter, mode: 'edit' | 'det
224
192
  formNameError.value = ''
225
193
  dialogError.value = ''
226
194
  dialogVisible.value = true
227
- void loadDetail()
228
- }
229
-
230
- const closeParameterDialog = () => {
231
- // 关闭后即使请求继续返回,也不能更新下一次打开的弹窗。
232
- detailRequestId += 1
233
- dialogLoading.value = false
234
- dialogReady.value = false
235
195
  }
236
196
 
237
197
  const submitParameter = async () => {
238
198
  const mode = dialogMode.value
239
- if (mode === 'detail' || dialogBusy.value || !dialogReady.value || !dialogVisible.value) return
199
+ if (mode === 'detail' || writing.value || !dialogVisible.value) return
240
200
  const values = { ...form.value }
241
201
  if (!values.paraName.trim()) {
242
202
  formNameError.value = '请填写参数名称'
@@ -303,7 +263,6 @@ onMounted(loadList)
303
263
  onBeforeUnmount(() => {
304
264
  disposed = true
305
265
  listRequestId += 1
306
- detailRequestId += 1
307
266
  })
308
267
  </script>
309
268
 
@@ -319,7 +278,7 @@ onBeforeUnmount(() => {
319
278
  :disabled="listBusy"
320
279
  @submit.prevent="search()"
321
280
  >
322
- <el-form-item label="关键字">
281
+ <el-form-item class="filter-keywords" label="关键字">
323
282
  <el-input v-model="keywords" placeholder="输入查询关键字" clearable />
324
283
  </el-form-item>
325
284
  <el-space class="filter-actions" :size="8">
@@ -388,12 +347,10 @@ onBeforeUnmount(() => {
388
347
  :show-close="!writing"
389
348
  :close-on-click-modal="false"
390
349
  :close-on-press-escape="!writing"
391
- @close="closeParameterDialog"
392
350
  >
393
351
  <el-alert v-if="dialogError" class="dialog-error" :title="dialogError" type="error" show-icon :closable="false" />
394
352
  <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">
353
+ <div v-if="dialogMode === 'detail'" class="detail-content">
397
354
  <header class="detail-summary">
398
355
  <div class="detail-reference">
399
356
  <span class="detail-label">参数名称</span>
@@ -419,12 +376,12 @@ onBeforeUnmount(() => {
419
376
  </section>
420
377
  </div>
421
378
  <el-form
422
- v-else-if="dialogMode !== 'detail' && dialogReady"
379
+ v-else
423
380
  id="parameter-editor-form"
424
381
  class="parameter-form"
425
382
  label-position="top"
426
383
  :model="form"
427
- :disabled="dialogBusy"
384
+ :disabled="writing"
428
385
  @submit.prevent="submitParameter()"
429
386
  >
430
387
  <el-form-item
@@ -447,13 +404,12 @@ onBeforeUnmount(() => {
447
404
  </el-form>
448
405
  </el-scrollbar>
449
406
  <template #footer>
450
- <el-button v-if="dialogError && !dialogReady && dialogSource" :loading="dialogLoading" @click="loadDetail">重新读取</el-button>
451
407
  <el-button :disabled="writing" @click="dialogVisible = false">{{ dialogMode === 'detail' ? '关闭' : '取消' }}</el-button>
452
408
  <el-button
453
409
  v-if="dialogMode !== 'detail'"
454
410
  type="primary"
455
411
  :loading="pendingMutation === 'create' || pendingMutation === 'edit'"
456
- :disabled="dialogLoading || !dialogReady || writing"
412
+ :disabled="writing"
457
413
  @click="submitParameter"
458
414
  >
459
415
  {{ dialogMode === 'create' ? '新增' : '保存' }}
@@ -489,6 +445,8 @@ onBeforeUnmount(() => {
489
445
  .dcps-page {
490
446
  box-sizing: border-box;
491
447
  display: flex;
448
+ width: 100%;
449
+ max-width: 100%;
492
450
  flex-direction: column;
493
451
  gap: 5px;
494
452
  height: calc(100vh - var(--dpcs-host-layout-height));
@@ -507,9 +465,10 @@ onBeforeUnmount(() => {
507
465
  overflow-y: auto;
508
466
  }
509
467
  .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; }
468
+ .filter-form :deep(.el-form-item) { flex: 0 0 auto; min-width: 0; margin: 0; }
469
+ .filter-form :deep(.filter-keywords) { flex: 0 1 240px; width: 240px; max-width: 100%; }
511
470
  .filter-form :deep(.el-form-item__label) { margin-bottom: 4px; }
512
- .filter-actions { flex-shrink: 0; margin-left: auto; }
471
+ .filter-actions { flex-shrink: 0; margin-left: 0; }
513
472
  .parameter-table-region { flex: 1; min-width: 0; min-height: 0; }
514
473
  .table-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
515
474
  .table-actions :deep(.el-button + .el-button) { margin-left: 0; }
@@ -520,7 +479,6 @@ onBeforeUnmount(() => {
520
479
  .form-item--wide { grid-column: 1 / -1; }
521
480
  .parameter-form :deep(.el-input__inner[readonly]) { color: var(--el-text-color-secondary); }
522
481
  .dialog-error { margin-bottom: 14px; }
523
- .detail-loading { padding: 40px 0; color: var(--el-text-color-secondary); text-align: center; }
524
482
  .detail-scrollbar :deep(.el-scrollbar__wrap) {
525
483
  max-height: calc(100vh - 120px);
526
484
  max-height: calc(100dvh - 120px);
@@ -551,7 +509,7 @@ onBeforeUnmount(() => {
551
509
  .detail-reference { grid-column: auto; }
552
510
  }
553
511
  @media (max-width: 540px) {
554
- .filter-form :deep(.el-form-item) { flex-basis: 100%; }
512
+ .filter-form :deep(.filter-keywords) { flex: 1 1 100%; width: auto; }
555
513
  .filter-actions { margin-left: 0; }
556
514
  .parameter-form { grid-template-columns: minmax(0, 1fr); }
557
515
  .selection-count { width: 100%; margin-left: 0; }
@@ -932,6 +932,8 @@ onBeforeUnmount(() => {
932
932
  .dcps-page {
933
933
  box-sizing: border-box;
934
934
  display: flex;
935
+ width: 100%;
936
+ max-width: 100%;
935
937
  flex-direction: column;
936
938
  gap: 5px;
937
939
  height: calc(100vh - var(--dpcs-host-layout-height));