@vue-ui-kit/ant 2.4.5 → 2.5.0

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/package.json CHANGED
@@ -1,82 +1,84 @@
1
- {
2
- "name": "@vue-ui-kit/ant",
3
- "version": "2.4.5",
4
- "description": "Vue3 UI Kit based on Ant Design",
5
- "type": "module",
6
- "main": "./dist/cjs/index.js",
7
- "module": "./dist/es/index.js",
8
- "types": "./dist/index.d.ts",
9
- "style": "./dist/style.css",
10
- "sass": "./dist/style.scss",
11
- "exports": {
12
- ".": {
13
- "require": "./dist/cjs/index.js",
14
- "import": "./dist/es/index.js",
15
- "types": "./dist/index.d.ts"
16
- },
17
- "./style.css": "./dist/style.css",
18
- "./style.scss": "./src/packages/styles/index.scss",
19
- "./dist/style.scss": "./dist/style.scss",
20
- "./dist/style.css": "./dist/style.css"
21
- },
22
- "files": [
23
- "dist",
24
- "src",
25
- "README.md",
26
- "README.zh.md",
27
- "tsconfig.json"
28
- ],
29
- "scripts": {
30
- "dev": "vue-tsc --noEmit --skipLibCheck & vite",
31
- "build": "ts-patch install && vite build && sass src/packages/styles/index.scss dist/style.css --no-source-map",
32
- "prepack": "rimraf dist && pnpm build",
33
- "prepare": "husky",
34
- "lint:lint-staged": "lint-staged",
35
- "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
36
- },
37
- "keywords": [
38
- "vue3",
39
- "ui kit",
40
- "ant design",
41
- "ant",
42
- "antdv"
43
- ],
44
- "author": {
45
- "name": "adoin",
46
- "email": "adoin@qq.com"
47
- },
48
- "dependencies": {
49
- "e-virt-table": "^1.3.17",
50
- "uuid": "^10.0.0",
51
- "xe-utils": "^3.7.6"
52
- },
53
- "peerDependencies": {
54
- "@ant-design/icons-vue": ">=7.0.0",
55
- "ant-design-vue": ">=4.0.0",
56
- "vue": ">=3.2.0"
57
- },
58
- "license": "GPL",
59
- "engines": {
60
- "node": "^20.19.0 || >=22.12.0"
61
- },
62
- "devDependencies": {
63
- "@ant-design/icons-vue": ">=7.0.0",
64
- "@commitlint/cli": "^19.4.1",
65
- "@commitlint/config-conventional": "^19.4.1",
66
- "@types/uuid": "^10.0.0",
67
- "@vitejs/plugin-vue": "^6.0.5",
68
- "@vitejs/plugin-vue-jsx": "^5.1.5",
69
- "ant-design-vue": "^4.2.3",
70
- "esbuild": "^0.28.0",
71
- "husky": "^9.1.5",
72
- "lint-staged": "^15.2.9",
73
- "prettier": "^3.3.3",
74
- "rimraf": "^6.0.1",
75
- "sass": "^1.77.8",
76
- "ts-patch": "^3.2.1",
77
- "typescript-transform-paths": "^3.5.0",
78
- "vite": "^8.0.8",
79
- "vite-plugin-dts": "^4.5.4",
80
- "vue": "^3.4.38"
81
- }
82
- }
1
+ {
2
+ "name": "@vue-ui-kit/ant",
3
+ "version": "2.5.0",
4
+ "description": "Vue3 UI Kit based on Ant Design",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/es/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "style": "./dist/style.css",
10
+ "sass": "./dist/style.scss",
11
+ "exports": {
12
+ ".": {
13
+ "require": "./dist/cjs/index.js",
14
+ "import": "./dist/es/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./style.css": "./dist/style.css",
18
+ "./style.scss": "./src/packages/styles/index.scss",
19
+ "./dist/style.scss": "./dist/style.scss",
20
+ "./dist/style.css": "./dist/style.css"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "README.md",
26
+ "README.zh.md",
27
+ "tsconfig.json"
28
+ ],
29
+ "scripts": {
30
+ "dev": "vue-tsc --noEmit --skipLibCheck & vite",
31
+ "build": "ts-patch install && vite build && sass src/packages/styles/index.scss dist/style.css --no-source-map",
32
+ "prepack": "rimraf dist && pnpm build",
33
+ "prepare": "husky",
34
+ "lint:lint-staged": "lint-staged",
35
+ "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
36
+ },
37
+ "keywords": [
38
+ "vue3",
39
+ "ui kit",
40
+ "ant design",
41
+ "ant",
42
+ "antdv"
43
+ ],
44
+ "author": {
45
+ "name": "adoin",
46
+ "email": "adoin@qq.com"
47
+ },
48
+ "dependencies": {
49
+ "e-virt-table": "^1.3.25",
50
+ "uuid": "^10.0.0",
51
+ "xe-utils": "^3.7.6"
52
+ },
53
+ "peerDependencies": {
54
+ "@ant-design/icons-vue": ">=7.0.0",
55
+ "ant-design-vue": ">=4.0.0",
56
+ "vue": ">=3.2.0"
57
+ },
58
+ "license": "GPL",
59
+ "engines": {
60
+ "node": "^20.19.0 || >=22.12.0"
61
+ },
62
+ "devDependencies": {
63
+ "@ant-design/icons-vue": ">=7.0.0",
64
+ "@commitlint/cli": "^21.0.0",
65
+ "@commitlint/config-conventional": "^21.0.0",
66
+ "@types/uuid": "^10.0.0",
67
+ "@vitejs/plugin-vue": "^6.0.5",
68
+ "@vitejs/plugin-vue-jsx": "^5.1.5",
69
+ "ant-design-vue": "^4.2.3",
70
+ "esbuild": "^0.28.0",
71
+ "husky": "^9.1.5",
72
+ "lint-staged": "^15.2.9",
73
+ "prettier": "^3.3.3",
74
+ "rimraf": "^6.0.1",
75
+ "sass": "^1.77.8",
76
+ "scroll-into-view-if-needed": "^2.2.31",
77
+ "ts-patch": "^3.2.1",
78
+ "typescript-transform-paths": "^3.5.0",
79
+ "vite": "^8.0.8",
80
+ "vite-plugin-dts": "^4.5.4",
81
+ "vue": "^3.5.33",
82
+ "vue-types": "^3.0.2"
83
+ }
84
+ }
@@ -80,6 +80,38 @@ export interface PFormProps<F extends Recordable = Recordable> extends FormProps
80
80
  customReset?: () => void;
81
81
  }
82
82
 
83
+ export interface PDescriptionItemProps<D extends Recordable = Recordable> {
84
+ field?: string;
85
+ label?: string;
86
+ span?: number;
87
+ labelStyle?: Recordable;
88
+ contentStyle?: Recordable;
89
+ /** 只读展示渲染器,派发到 renderStore.renderDefault */
90
+ readonlyRender?: ItemRender;
91
+ slots?: {
92
+ label?: () => any;
93
+ default?: ({ data, field }: { data: D; field?: string }) => any;
94
+ };
95
+ }
96
+
97
+ export interface PDescriptionProps<D extends Recordable = Recordable> {
98
+ /** 数据源 */
99
+ data: D;
100
+ items: PDescriptionItemProps<D>[];
101
+ /** 透传 a-descriptions */
102
+ title?: string;
103
+ extra?: string;
104
+ bordered?: boolean;
105
+ size?: 'default' | 'small' | 'middle';
106
+ column?: number | Partial<Record<'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', number>>;
107
+ layout?: 'horizontal' | 'vertical';
108
+ colon?: boolean;
109
+ labelStyle?: Recordable;
110
+ contentStyle?: Recordable;
111
+ /** 缺省占位,row[field] 为空时展示 */
112
+ emptyText?: string;
113
+ }
114
+
83
115
  export interface PBlockProps<F extends Recordable = Recordable> {
84
116
  name?: string;
85
117
  getFormSetting: (data: Partial<F>) => PFormProps<Partial<F>>;
@@ -314,6 +346,12 @@ export interface RenderFormParams<F extends Recordable = Recordable> {
314
346
  field?: string;
315
347
  }
316
348
 
349
+ export interface RenderReadonlyParams<D extends Recordable = Recordable> {
350
+ data: D;
351
+ field?: string;
352
+ title?: string;
353
+ }
354
+
317
355
  export interface RenderTableParams<D extends Recordable = Recordable> {
318
356
  data?: D[];
319
357
  row: D;
@@ -389,6 +427,16 @@ export interface PCanvasGridProps<
389
427
  * 适应展示区到页面顶部的高度,通常和容器最上的y值有关(正比)
390
428
  */
391
429
  fitHeight?: number;
430
+ /**
431
+ * 开启后组件根节点按视口从其左上角铺满宽高,与 PGrid 的 autoBoxSize 行为一致。
432
+ * 外层未声明固定高度、需组件自身占满可视区域时使用。
433
+ */
434
+ autoBoxSize?: boolean;
435
+ /**
436
+ * `autoBoxSize` 时距视口右侧/底部的预留像素;数字或 `"12px"` 同时作用于两侧;对象可分别指定 `right` / `bottom`。
437
+ * 未传时使用 `setUIKitConfig({ grid: { autoBoxSizeOffset } })` 的全局值。
438
+ */
439
+ autoBoxSizeOffset?: number | string | { right?: number; bottom?: number };
392
440
  }
393
441
  export interface PGridInstance<
394
442
  D extends Recordable = Recordable,
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { App } from 'vue';
2
+ import type { Component } from 'vue';
2
3
  import PForm from '@/components/PForm.vue';
3
4
  import PGrid from '@/components/PGrid.vue';
4
5
  import PCanvasTable from '@/components/PCanvasTable.vue';
@@ -6,6 +7,7 @@ import PCanvasGrid from '@/components/PCanvasGrid.vue';
6
7
  import PFormGroup from '@/components/PFormGroup.vue';
7
8
  import PGroupBlock from '@/components/PGroupBlock.vue';
8
9
  import PromisePicker from '@/components/PromisePicker.vue';
10
+ import PDescription from '@/components/PDescription.vue';
9
11
  import { addFormatter } from '@/utils/AFormatters';
10
12
  import { addRender } from '@/store/renderStore';
11
13
  import {
@@ -29,9 +31,10 @@ export default {
29
31
  app.component('PGrid', PGrid);
30
32
  app.component('PCanvasTable', PCanvasTable);
31
33
  app.component('PCanvasGrid', PCanvasGrid);
32
- app.component('PFormGroup', PFormGroup);
34
+ app.component('PFormGroup', PFormGroup as unknown as Component);
33
35
  app.component('PGroupBlock', PGroupBlock);
34
36
  app.component('PromisePicker', PromisePicker);
37
+ app.component('PDescription', PDescription);
35
38
  },
36
39
  addFormatter,
37
40
  addRender,
@@ -62,6 +65,7 @@ export {
62
65
  PFormGroup,
63
66
  PGroupBlock,
64
67
  PromisePicker,
68
+ PDescription,
65
69
  addFormatter,
66
70
  addRender,
67
71
  };
@@ -12,6 +12,7 @@
12
12
  Ref,
13
13
  reactive,
14
14
  onMounted,
15
+ nextTick,
15
16
  toRefs,
16
17
  onBeforeUnmount,
17
18
  watch,
@@ -39,10 +40,17 @@
39
40
  } from 'ant-design-vue';
40
41
  import { DownOutlined } from '@ant-design/icons-vue';
41
42
  import { getCanvasTableDefaults, getGridDefaults } from '@/utils/config';
43
+ import {
44
+ createAutoViewportBoxController,
45
+ parseAutoViewportBoxOffset,
46
+ type AutoViewportBoxController,
47
+ type AutoViewportBoxOffsetInput,
48
+ } from '@/utils/autoViewportBox';
42
49
  import { eachTree } from '@/utils/treeHelper';
43
50
  const props = withDefaults(defineProps<PCanvasGridProps<D, F>>(), {
44
51
  lazyReset: () => getGridDefaults().lazyReset ?? false,
45
- fitHeight: () => getGridDefaults().fitCanvasHeight ?? 100,
52
+ fitHeight: () => getGridDefaults().fitCanvasHeight ?? 0,
53
+ autoBoxSize: false,
46
54
  });
47
55
  const attrs = useAttrs();
48
56
  const emit = defineEmits<{
@@ -61,6 +69,15 @@
61
69
  const { formConfig, pageConfig, columns, toolbarConfig, proxyConfig, config, staticConfig } =
62
70
  toRefs(props);
63
71
  const canvasTableDefaults = getCanvasTableDefaults();
72
+
73
+ /** 局部 `autoBoxSizeOffset` 优先,否则用 `getGridDefaults().autoBoxSizeOffset`(`setUIKitConfig`) */
74
+ const resolvedAutoBoxSizeOffset = computed<AutoViewportBoxOffsetInput>(() => {
75
+ const local = props.autoBoxSizeOffset;
76
+ if (local !== undefined && local !== null) {
77
+ return local;
78
+ }
79
+ return getGridDefaults().autoBoxSizeOffset as AutoViewportBoxOffsetInput;
80
+ });
64
81
  const renderFormKey = ref(uuid_v4());
65
82
  const queryFormData = ref<Partial<F>>({}) as Ref<Partial<F>>;
66
83
 
@@ -110,6 +127,8 @@
110
127
  const boxEl = ref<HTMLDivElement>();
111
128
  const pFormWrapper = ref<HTMLDivElement>();
112
129
  const tableWrapperEl = ref<HTMLDivElement>();
130
+ const tableBodyEl = ref<HTMLDivElement>();
131
+ const tableFooterEl = ref<HTMLDivElement>();
113
132
  const renderHeight = ref(500);
114
133
  /* 分页时缓存的跨页选择数据,风险:数据可能会更新导致串页,读取计算的时候要去重 */
115
134
  const pageSelections = ref<{
@@ -376,14 +395,59 @@
376
395
  pageSelections.value = {};
377
396
  return resetQueryFormData();
378
397
  };
398
+
399
+ const renderContent = (content: string | (() => any)) => {
400
+ if (isFunction(content)) {
401
+ return content();
402
+ }
403
+ return content;
404
+ };
405
+ const isStringContent = (content: any) => isString(content);
406
+
407
+ let autoViewportBoxCtrl: AutoViewportBoxController | null = null;
408
+ const clearAutoViewportBox = () => {
409
+ autoViewportBoxCtrl?.destroy();
410
+ autoViewportBoxCtrl = null;
411
+ };
412
+ const syncAutoViewportBox = () => {
413
+ clearAutoViewportBox();
414
+ if (!props.autoBoxSize || !boxEl.value) return;
415
+ autoViewportBoxCtrl = createAutoViewportBoxController(
416
+ () => boxEl.value ?? undefined,
417
+ () => parseAutoViewportBoxOffset(resolvedAutoBoxSizeOffset.value),
418
+ {
419
+ onLayout: () => {
420
+ nextTick(() => resizeTable());
421
+ },
422
+ },
423
+ );
424
+ autoViewportBoxCtrl.attach();
425
+ };
426
+
427
+ watch(
428
+ () => props.autoBoxSize,
429
+ (on) => {
430
+ if (!on) clearAutoViewportBox();
431
+ else nextTick(() => syncAutoViewportBox());
432
+ },
433
+ );
434
+ watch(
435
+ resolvedAutoBoxSizeOffset,
436
+ () => {
437
+ if (props.autoBoxSize) autoViewportBoxCtrl?.update();
438
+ },
439
+ { deep: true },
440
+ );
441
+
379
442
  let resizeRaf: number | null = null;
380
443
  const resizeTable = () => {
381
444
  if (resizeRaf) cancelAnimationFrame(resizeRaf);
382
445
  resizeRaf = requestAnimationFrame(() => {
383
446
  resizeRaf = null;
384
- if (!tableWrapperEl.value) return;
385
- const rect = tableWrapperEl.value.getBoundingClientRect();
386
- renderHeight.value = Math.max(window.innerHeight - rect.top - props.fitHeight, 100);
447
+ if (!tableBodyEl.value) return;
448
+ const bodyH = tableBodyEl.value.clientHeight;
449
+ const reserve = props.fitHeight ?? 0;
450
+ renderHeight.value = Math.max(bodyH - reserve, 100);
387
451
  });
388
452
  };
389
453
 
@@ -401,8 +465,12 @@
401
465
  { immediate: true },
402
466
  );
403
467
 
468
+ // 分页/已选区域高度变化时重算
469
+ watch(mode, () => nextTick(() => resizeTable()));
470
+
404
471
  let observer: MutationObserver;
405
472
  let boxElResizeObserver: ResizeObserver | null = null;
473
+ let tableBodyResizeObserver: ResizeObserver | null = null;
406
474
  onMounted(() => {
407
475
  resizeTable();
408
476
  window.addEventListener('resize', resizeTable);
@@ -428,7 +496,16 @@
428
496
  ancestor = ancestor.parentElement;
429
497
  }
430
498
  }
499
+ // 直接观察实际承载 canvas 的内层盒子,避免 padding/footer 干扰
500
+ tableBodyResizeObserver = new ResizeObserver(() => resizeTable());
501
+ if (tableBodyEl.value) {
502
+ tableBodyResizeObserver.observe(tableBodyEl.value);
503
+ }
431
504
  resetQueryFormData(props.manualFetch);
505
+ nextTick(() => {
506
+ if (props.autoBoxSize) syncAutoViewportBox();
507
+ resizeTable();
508
+ });
432
509
  });
433
510
  onBeforeUnmount(() => {
434
511
  if (resizeRaf) cancelAnimationFrame(resizeRaf);
@@ -436,6 +513,8 @@
436
513
  observer.disconnect();
437
514
  formWrapperResizeObserver?.disconnect();
438
515
  boxElResizeObserver?.disconnect();
516
+ tableBodyResizeObserver?.disconnect();
517
+ clearAutoViewportBox();
439
518
  });
440
519
  defineExpose({
441
520
  commitProxy: {
@@ -456,7 +535,14 @@
456
535
  });
457
536
  </script>
458
537
  <template>
459
- <div ref="boxEl" class="h-full p-wrapper flex flex-col gap-8px overflow-y-auto" v-bind="attrs">
538
+ <div
539
+ ref="boxEl"
540
+ :class="[
541
+ 'p-wrapper flex flex-col gap-8px overflow-y-auto',
542
+ autoBoxSize ? 'min-h-0 min-w-0 flex-1 w-full' : 'h-full',
543
+ ]"
544
+ v-bind="attrs"
545
+ >
460
546
  <div v-if="mode === 'bad'">请检查配置</div>
461
547
  <template v-else>
462
548
  <div
@@ -509,9 +595,14 @@
509
595
  :ghost="btn.ghost"
510
596
  :block="btn.block"
511
597
  >
512
- <Icon v-if="btn.icon" :icon="btn.icon" />
513
- {{ btn.content }}
514
- <DownOutlined />
598
+ <div class="flex items-center gap-4px">
599
+ <Icon v-if="btn.icon" :icon="btn.icon" />
600
+ <template v-if="btn.content && isStringContent(renderContent(btn.content))">
601
+ {{ renderContent(btn.content) }}
602
+ </template>
603
+ <component v-else-if="btn.content" :is="renderContent(btn.content)" />
604
+ <DownOutlined />
605
+ </div>
515
606
  </a-button>
516
607
  </a-dropdown>
517
608
  <a-button
@@ -526,8 +617,13 @@
526
617
  :block="btn.block"
527
618
  @click="debounceToolBtnClick(btn.code)"
528
619
  >
529
- <Icon v-if="btn.icon" :icon="btn.icon" />
530
- {{ btn.content }}
620
+ <div class="flex items-center gap-4px">
621
+ <Icon v-if="btn.icon" :icon="btn.icon" />
622
+ <template v-if="btn.content && isStringContent(renderContent(btn.content))">
623
+ {{ renderContent(btn.content) }}
624
+ </template>
625
+ <component v-else-if="btn.content" :is="renderContent(btn.content)" />
626
+ </div>
531
627
  </a-button>
532
628
  <div v-else></div>
533
629
  </template>
@@ -548,43 +644,52 @@
548
644
  @click="debounceToolToolClick(tool.code)"
549
645
  :loading="loading.toolbar || (!!tool.code && codeLoadings[tool.code])"
550
646
  >
551
- <Icon v-if="tool.icon" :icon="tool.icon" />
552
- {{ tool.content }}
647
+ <div class="flex items-center gap-4px">
648
+ <Icon v-if="tool.icon" :icon="tool.icon" />
649
+ <template v-if="tool.content && isStringContent(renderContent(tool.content))">
650
+ {{ renderContent(tool.content) }}
651
+ </template>
652
+ <component v-else-if="tool.content" :is="renderContent(tool.content)" />
653
+ </div>
553
654
  </a-button>
554
655
  </template>
555
656
  </span>
556
657
  </div>
557
- <div ref="tableWrapperEl" :class="`p-pane flex-1 h-0 p-inner-scroll`">
558
- <div
559
- v-if="staticConfig?.selectable && staticConfig.showCount"
560
- class="w-full text-slate-5 pl-4"
561
- >
562
- 已选:{{ selectedRowKeys.length }}
658
+ <div ref="tableWrapperEl" class="p-pane flex-1 h-0 min-h-0 flex flex-col p-inner-scroll">
659
+ <div ref="tableBodyEl" class="flex-1 min-h-0 overflow-hidden">
660
+ <p-canvas-table
661
+ ref="canvasTableRef"
662
+ :columns="finalColumns"
663
+ :config="{
664
+ ...propsWithDefaults.config,
665
+ HEIGHT: renderHeight,
666
+ }"
667
+ :data="tableData"
668
+ :loading="loading.table"
669
+ @selection-change="handleSelectionChange"
670
+ >
671
+ <template v-for="(_, name) in $slots" #[name]="slotProps">
672
+ <slot :name="name" v-bind="slotProps"></slot>
673
+ </template>
674
+ </p-canvas-table>
675
+ </div>
676
+ <div ref="tableFooterEl" class="flex-shrink-0">
677
+ <div
678
+ v-if="staticConfig?.selectable && staticConfig.showCount"
679
+ class="w-full text-slate-5 pl-4"
680
+ >
681
+ 已选:{{ selectedRowKeys.length }}
682
+ </div>
683
+ <a-pagination
684
+ class="p-canvas-pagination"
685
+ v-if="mode === 'pagination'"
686
+ size="small"
687
+ :current="pagination.page"
688
+ :page-size="pagination.size"
689
+ :total="totalCount"
690
+ @change="handleTableChange"
691
+ />
563
692
  </div>
564
- <p-canvas-table
565
- ref="canvasTableRef"
566
- :columns="finalColumns"
567
- :config="{
568
- ...propsWithDefaults.config,
569
- HEIGHT: renderHeight,
570
- }"
571
- :data="tableData"
572
- :loading="loading.table"
573
- @selection-change="handleSelectionChange"
574
- >
575
- <template v-for="(_, name) in $slots" #[name]="slotProps">
576
- <slot :name="name" v-bind="slotProps"></slot>
577
- </template>
578
- </p-canvas-table>
579
- <a-pagination
580
- class="p-canvas-pagination"
581
- v-if="mode === 'pagination'"
582
- size="small"
583
- :current="pagination.page"
584
- :page-size="pagination.size"
585
- :total="totalCount"
586
- @change="handleTableChange"
587
- />
588
693
  </div>
589
694
  </template>
590
695
  </div>
@@ -0,0 +1,50 @@
1
+ <script lang="ts" setup generic="D extends Recordable = Recordable" name="PDescription">
2
+ import { computed } from 'vue';
3
+ import type { PDescriptionProps } from '#/antProxy';
4
+ import PDescriptionContent from '@/components/PDescriptionContent';
5
+
6
+ const props = withDefaults(defineProps<PDescriptionProps<D>>(), {
7
+ bordered: false,
8
+ column: 3,
9
+ size: 'default',
10
+ layout: 'horizontal',
11
+ colon: true,
12
+ emptyText: '-',
13
+ });
14
+
15
+ const descAttrs = computed(() => ({
16
+ title: props.title,
17
+ extra: props.extra,
18
+ bordered: props.bordered,
19
+ size: props.size,
20
+ column: props.column,
21
+ layout: props.layout,
22
+ colon: props.colon,
23
+ labelStyle: props.labelStyle,
24
+ contentStyle: props.contentStyle,
25
+ }));
26
+ </script>
27
+ <template>
28
+ <a-descriptions v-bind="descAttrs" class="p-description">
29
+ <template v-if="$slots.title || title" #title>
30
+ <slot name="title">{{ title }}</slot>
31
+ </template>
32
+ <template v-if="$slots.extra || extra" #extra>
33
+ <slot name="extra">{{ extra }}</slot>
34
+ </template>
35
+
36
+ <a-descriptions-item
37
+ v-for="(item, idx) in items"
38
+ :key="`_desc_${item.field ?? idx}`"
39
+ :label="item.label"
40
+ :span="item.span"
41
+ :label-style="item.labelStyle"
42
+ :content-style="item.contentStyle"
43
+ >
44
+ <template v-if="item.slots?.label" #label>
45
+ <component :is="item.slots.label" />
46
+ </template>
47
+ <p-description-content :item="item" :data="data" :empty-text="emptyText || '-'" />
48
+ </a-descriptions-item>
49
+ </a-descriptions>
50
+ </template>
@@ -0,0 +1,46 @@
1
+ import { defineComponent } from 'vue';
2
+ import type { PDescriptionItemProps } from '#/antProxy';
3
+ import renderStore from '@/store/renderStore';
4
+ import { isGoodValue } from '@/utils/is';
5
+
6
+ export default defineComponent(
7
+ <D extends Recordable = Recordable>(props: {
8
+ item: PDescriptionItemProps<D>;
9
+ data: D;
10
+ emptyText: string;
11
+ }) => {
12
+ return () => {
13
+ const { item, data, emptyText } = props;
14
+
15
+ // 1. 插槽优先
16
+ if (item.slots?.default) {
17
+ return item.slots.default({ data, field: item.field });
18
+ }
19
+
20
+ // 2. readonlyRender 派发到 renderReadonly(专为只读语境设计的渲染维度)
21
+ if (item.readonlyRender?.name) {
22
+ const r = item.readonlyRender;
23
+ const node =
24
+ renderStore.renders[r.name]?.renderReadonly?.(
25
+ r,
26
+ {
27
+ data,
28
+ field: item.field,
29
+ title: item.label,
30
+ },
31
+ {},
32
+ ) ?? null;
33
+ if (node !== null) return node;
34
+ }
35
+
36
+ // 3. 原值;空值用 emptyText
37
+ if (!item.field) return emptyText;
38
+ const v = (data as Recordable)?.[item.field];
39
+ return isGoodValue(v) ? v : emptyText;
40
+ };
41
+ },
42
+ {
43
+ name: 'PDescriptionContent',
44
+ props: ['item', 'data', 'emptyText'],
45
+ },
46
+ );