@aspire-ui/element-component-pro 1.0.5 → 1.0.7

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/dist/style.css CHANGED
@@ -1 +1 @@
1
- .ecp-pro-table[data-v-c5638c20]{padding:16px;background:#fff;width:100%;box-sizing:border-box}.ecp-pro-table[data-v-c5638c20] .el-table{width:100%!important}.ecp-pro-table__header[data-v-c5638c20]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.ecp-pro-table__title-wrapper[data-v-c5638c20]{display:flex;align-items:center;gap:4px}.ecp-pro-table__title[data-v-c5638c20]{font-size:16px;font-weight:600}.ecp-pro-table__help[data-v-c5638c20]{color:#909399;cursor:help}.ecp-pro-table__toolbar[data-v-c5638c20]{display:flex;align-items:center;gap:8px}.ecp-pro-table__body[data-v-c5638c20]{width:100%}.ecp-pro-table__pagination[data-v-c5638c20]{margin-top:16px;display:flex;justify-content:flex-end}.ecp-pro-table__col-help[data-v-c5638c20]{margin-left:4px;color:#909399;cursor:help}.ecp-table-action[data-v-f319e73a],.ecp-table-action__item[data-v-f319e73a]{display:inline-flex;align-items:center;gap:4px}.ecp-table-action__icon[data-v-f319e73a]{margin-right:4px}.ecp-table-action__more[data-v-f319e73a]{display:inline-flex;align-items:center}.ecp-table-action__dropdown-item[data-v-f319e73a]{display:inline-flex;align-items:center;gap:4px}.ecp-tree-select[data-v-f30bba11]{position:relative;width:100%}.ecp-tree-select__filter-inner[data-v-f30bba11]{margin-bottom:8px}.ecp-tree-select__dropdown[data-v-f30bba11]{position:absolute;top:100%;left:0;right:0;max-height:280px;overflow:auto;background:#fff;border:1px solid #dcdfe6;border-radius:4px;margin-top:4px;z-index:1000;padding:8px}.ecp-tree-select__loading[data-v-f30bba11]{padding:24px;text-align:center;color:#909399;font-size:14px}.ecp-pro-form-item__help-icon[data-v-84880e9d]{margin-left:4px;color:#909399;cursor:help;font-size:14px}.ecp-pro-form-item__help-icon[data-v-84880e9d]:hover{color:#409eff}.ecp-pro-form-item__help-item[data-v-84880e9d]{margin-bottom:4px}.ecp-pro-form-item__help-item[data-v-84880e9d]:last-child{margin-bottom:0}.ecp-form-actions[data-v-489c88d2]{text-align:right}.ecp-form-actions__advance[data-v-489c88d2]{margin-right:8px}.el-icon-d-arrow-left.up[data-v-489c88d2]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-489c88d2]{transform:rotate(-90deg)}.ecp-pro-form[data-v-6171d80d]{padding:16px;position:relative}.ecp-pro-form__advance[data-v-6171d80d]{margin-bottom:16px}.ecp-pro-form_col[data-v-6171d80d]{position:relative;float:right}.el-icon-d-arrow-left.up[data-v-6171d80d]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-6171d80d]{transform:rotate(-90deg)}.ecp-form-actions__advance[data-v-6171d80d]{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%)}
1
+ .ecp-pro-table[data-v-c5638c20]{padding:16px;background:#fff;width:100%;box-sizing:border-box}.ecp-pro-table[data-v-c5638c20] .el-table{width:100%!important}.ecp-pro-table__header[data-v-c5638c20]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.ecp-pro-table__title-wrapper[data-v-c5638c20]{display:flex;align-items:center;gap:4px}.ecp-pro-table__title[data-v-c5638c20]{font-size:16px;font-weight:600}.ecp-pro-table__help[data-v-c5638c20]{color:#909399;cursor:help}.ecp-pro-table__toolbar[data-v-c5638c20]{display:flex;align-items:center;gap:8px}.ecp-pro-table__body[data-v-c5638c20]{width:100%}.ecp-pro-table__pagination[data-v-c5638c20]{margin-top:16px;display:flex;justify-content:flex-end}.ecp-pro-table__col-help[data-v-c5638c20]{margin-left:4px;color:#909399;cursor:help}.ecp-table-action[data-v-45a58e7c],.ecp-table-action__item[data-v-45a58e7c]{display:inline-flex;align-items:center;gap:4px}.ecp-table-action__icon[data-v-45a58e7c]{margin-right:4px}.ecp-table-action__more[data-v-45a58e7c]{display:inline-flex;align-items:center}.ecp-table-action__dropdown-item[data-v-45a58e7c]{display:inline-flex;align-items:center;gap:4px}.ecp-tree-select[data-v-f30bba11]{position:relative;width:100%}.ecp-tree-select__filter-inner[data-v-f30bba11]{margin-bottom:8px}.ecp-tree-select__dropdown[data-v-f30bba11]{position:absolute;top:100%;left:0;right:0;max-height:280px;overflow:auto;background:#fff;border:1px solid #dcdfe6;border-radius:4px;margin-top:4px;z-index:1000;padding:8px}.ecp-tree-select__loading[data-v-f30bba11]{padding:24px;text-align:center;color:#909399;font-size:14px}.ecp-pro-form-item__colon[data-v-9514f173]{margin-right:2px}.ecp-pro-form-item__help-icon[data-v-9514f173]{margin-left:4px;color:#909399;cursor:help;font-size:14px}.ecp-pro-form-item__help-icon[data-v-9514f173]:hover{color:#409eff}.ecp-pro-form-item__help-item[data-v-9514f173]{margin-bottom:4px}.ecp-pro-form-item__help-item[data-v-9514f173]:last-child{margin-bottom:0}.ecp-form-actions[data-v-489c88d2]{text-align:right}.ecp-form-actions__advance[data-v-489c88d2]{margin-right:8px}.el-icon-d-arrow-left.up[data-v-489c88d2]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-489c88d2]{transform:rotate(-90deg)}.ecp-pro-form[data-v-bf70afca]{padding:16px;position:relative}.ecp-pro-form__advance[data-v-bf70afca]{margin-bottom:16px}.ecp-pro-form_col[data-v-bf70afca]{position:relative;float:right}.el-icon-d-arrow-left.up[data-v-bf70afca]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-bf70afca]{transform:rotate(-90deg)}.ecp-form-actions__advance[data-v-bf70afca]{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%)}.ecp-pro-descriptions[data-v-7d6cd376]{width:100%;box-sizing:border-box}.ecp-pro-descriptions__header[data-v-7d6cd376]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px}.ecp-pro-descriptions__title-wrap[data-v-7d6cd376]{display:flex;align-items:center;gap:6px}.ecp-pro-descriptions__title[data-v-7d6cd376]{font-size:16px;font-weight:600;color:#303133}.ecp-pro-descriptions__help[data-v-7d6cd376],.ecp-pro-descriptions__toggle[data-v-7d6cd376]{color:#909399}.ecp-pro-descriptions__toggle .el-icon-arrow-down[data-v-7d6cd376]{margin-left:4px;transition:transform .2s ease}.ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded[data-v-7d6cd376]{transform:rotate(180deg)}.ecp-pro-descriptions__body[data-v-7d6cd376]{display:grid;border-top:1px solid #ebeef5;border-left:1px solid #ebeef5;overflow:hidden}.ecp-pro-descriptions__body.is-collapsed[data-v-7d6cd376]{overflow:hidden}.ecp-pro-descriptions__body[data-v-7d6cd376]:not(.is-bordered){border-top:0;border-left:0;gap:12px 16px}.ecp-pro-descriptions__item[data-v-7d6cd376]{display:flex;min-width:0;border-right:1px solid #ebeef5;border-bottom:1px solid #ebeef5}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__item[data-v-7d6cd376]{border-right:0;border-bottom:0}.ecp-pro-descriptions__label[data-v-7d6cd376],.ecp-pro-descriptions__content[data-v-7d6cd376]{min-width:0;box-sizing:border-box;word-break:break-word}.ecp-pro-descriptions__label[data-v-7d6cd376]{flex:0 0 120px;padding:12px 16px;color:#606266;background:#fafafa}.ecp-pro-descriptions__content[data-v-7d6cd376]{flex:1;padding:12px 16px;color:#303133;background:#fff}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label[data-v-7d6cd376]{flex-basis:auto;padding:0;margin-right:8px;background:transparent;font-weight:500}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__content[data-v-7d6cd376]{padding:0;background:transparent}.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label[data-v-7d6cd376],.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content[data-v-7d6cd376]{padding-top:8px;padding-bottom:8px;font-size:13px}@media (max-width: 767px){.ecp-pro-descriptions__item[data-v-7d6cd376]{flex-direction:column}.ecp-pro-descriptions__label[data-v-7d6cd376]{flex-basis:auto}}
@@ -54,6 +54,7 @@ export interface ProFormProps {
54
54
  initialValues?: Record<string, unknown>;
55
55
  labelWidth?: string;
56
56
  labelPosition?: 'left' | 'right' | 'top';
57
+ colon?: boolean;
57
58
  gutter?: number;
58
59
  size?: 'medium' | 'small' | 'large';
59
60
  disabled?: boolean;
@@ -88,6 +89,8 @@ export interface ProFormSchema {
88
89
  label: string;
89
90
  /** 单个表单项标签宽度,优先级高于 Form 的 labelWidth */
90
91
  labelWidth?: string;
92
+ /** 是否显示 label 冒号,优先级高于 ProForm 的 colon */
93
+ colon?: boolean;
91
94
  /**
92
95
  * 组件类型:
93
96
  * - 内置:'input' | 'select' | 'date-picker' | 'date-range' | 'input-number' | 'switch' | 'cascader' | 'checkbox' | 'radio'
@@ -129,6 +132,8 @@ export interface ProFormSchema {
129
132
  helpMessage?: string | string[];
130
133
  /** 温馨提示组件的 props */
131
134
  helpComponentProps?: Record<string, unknown>;
135
+ /** 表单项 value 的 tooltip,true 时默认展示当前值,支持函数动态返回 */
136
+ tooltip?: boolean | string | Record<string, unknown> | ((params: RenderCallbackParams) => boolean | string | Record<string, unknown>);
132
137
  }
133
138
  /** ProDescriptions 描述项配置 */
134
139
  export interface ProDescriptionsSchema {
@@ -141,3 +146,69 @@ export interface ProDescriptionsSchema {
141
146
  /** 栅格占位 */
142
147
  span?: number;
143
148
  }
149
+ /** Description 列数配置 */
150
+ export type DescriptionColumn = number | {
151
+ xxl?: number;
152
+ xl?: number;
153
+ lg?: number;
154
+ md?: number;
155
+ sm?: number;
156
+ xs?: number;
157
+ };
158
+ /** Description 描述项配置 */
159
+ export interface DescriptionSchema {
160
+ /** 标签 */
161
+ label: string;
162
+ /** 数据字段(兼容 Vben Description 的 field) */
163
+ field?: string;
164
+ /** 数据字段 */
165
+ dataIndex: string;
166
+ /** 栅格占位 */
167
+ span?: number;
168
+ /** 是否显示 */
169
+ show?: boolean | ((data: Record<string, unknown>) => boolean);
170
+ /** 标签最小宽度 */
171
+ labelMinWidth?: number;
172
+ /** 内容最小宽度 */
173
+ contentMinWidth?: number;
174
+ /** 标签样式 */
175
+ labelStyle?: Record<string, string | number>;
176
+ /** 内容样式 */
177
+ contentStyle?: Record<string, string | number>;
178
+ /** 自定义插槽名称 */
179
+ slot?: string;
180
+ /** 自定义渲染 */
181
+ render?: (value: unknown, record: Record<string, unknown>) => VNode | string | number | null | undefined;
182
+ /** 描述项 value 的 tooltip,true 时默认展示当前值,支持函数动态返回 */
183
+ tooltip?: boolean | string | Record<string, unknown> | ((params: {
184
+ value: unknown;
185
+ record: Record<string, unknown>;
186
+ schema: DescriptionSchema;
187
+ }) => boolean | string | Record<string, unknown>);
188
+ }
189
+ /** Description Props */
190
+ export interface DescriptionProps {
191
+ title?: string;
192
+ helpMessage?: string | string[];
193
+ size?: 'medium' | 'small';
194
+ bordered?: boolean;
195
+ column?: DescriptionColumn;
196
+ schema?: DescriptionSchema[];
197
+ data?: Record<string, unknown>;
198
+ emptyText?: string;
199
+ useCollapse?: boolean;
200
+ collapseOptions?: {
201
+ canExpand?: boolean;
202
+ defaultExpand?: boolean;
203
+ expandButtonText?: string;
204
+ collapseButtonText?: string;
205
+ helpMessage?: string | string[];
206
+ visibleRows?: number;
207
+ };
208
+ }
209
+ /** Description 操作实例 */
210
+ export interface DescriptionActionType {
211
+ setProps: (props: Partial<DescriptionProps>) => Promise<void>;
212
+ setData: (data: Record<string, unknown>) => Promise<void>;
213
+ getData: () => Record<string, unknown>;
214
+ }
@@ -0,0 +1,4 @@
1
+ export type TooltipValue = boolean | string | Record<string, unknown>;
2
+ export declare const hasTooltipContent: (value: unknown) => boolean;
3
+ export declare const getTooltipContent: (value: unknown) => string;
4
+ export declare const normalizeTooltipConfig: (tooltip: TooltipValue | null | undefined, fallbackValue?: unknown) => Record<string, unknown> | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspire-ui/element-component-pro",
3
- "version": "1.0.05",
3
+ "version": "1.0.07",
4
4
  "description": "Element UI 二次封装组件库,基于 Vue 2.7 + TypeScript + setup 语法糖,实现 VbenAdmin 风格的 Pro 组件",
5
5
  "type": "module",
6
6
  "main": "./dist/element-component-pro.umd.js",
@@ -32,6 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@vitejs/plugin-vue2": "^2.3.1",
34
34
  "element-ui": "^2.15.14",
35
+ "marked": "^9.1.6",
35
36
  "playwright": "^1.40.0",
36
37
  "typescript": "^5.3.0",
37
38
  "vite": "^4.5.0",
@@ -0,0 +1,455 @@
1
+ <template>
2
+ <div class="ecp-pro-descriptions">
3
+ <div v-if="showHeader" class="ecp-pro-descriptions__header">
4
+ <div class="ecp-pro-descriptions__title-wrap">
5
+ <span v-if="effectiveProps.title" class="ecp-pro-descriptions__title">{{ effectiveProps.title }}</span>
6
+ <el-tooltip v-if="effectiveProps.helpMessage" placement="top" effect="dark">
7
+ <template slot="content">
8
+ <span v-if="Array.isArray(effectiveProps.helpMessage)">
9
+ <div v-for="(msg, index) in effectiveProps.helpMessage" :key="index">{{ msg }}</div>
10
+ </span>
11
+ <span v-else>{{ effectiveProps.helpMessage }}</span>
12
+ </template>
13
+ <i class="el-icon-question ecp-pro-descriptions__help" />
14
+ </el-tooltip>
15
+ <el-tooltip v-if="effectiveProps.collapseOptions?.helpMessage" placement="top" effect="dark">
16
+ <template slot="content">
17
+ <span v-if="Array.isArray(effectiveProps.collapseOptions.helpMessage)">
18
+ <div v-for="(msg, index) in effectiveProps.collapseOptions.helpMessage" :key="`collapse-${index}`">{{ msg }}</div>
19
+ </span>
20
+ <span v-else>{{ effectiveProps.collapseOptions.helpMessage }}</span>
21
+ </template>
22
+ <i class="el-icon-info ecp-pro-descriptions__help" />
23
+ </el-tooltip>
24
+ </div>
25
+ <el-button
26
+ v-if="showCollapseButton"
27
+ type="text"
28
+ class="ecp-pro-descriptions__toggle"
29
+ @click="expanded = !expanded"
30
+ >
31
+ {{ expanded ? collapseButtonText.collapse : collapseButtonText.expand }}
32
+ <i class="el-icon-arrow-down" :class="expanded ? 'is-expanded' : ''" />
33
+ </el-button>
34
+ </div>
35
+
36
+ <div
37
+ class="ecp-pro-descriptions__body"
38
+ :class="[
39
+ `is-${effectiveProps.size}`,
40
+ { 'is-bordered': effectiveProps.bordered, 'is-collapsed': showCollapseButton && !expanded },
41
+ ]"
42
+ :style="bodyStyle"
43
+ v-bind="$attrs"
44
+ >
45
+ <template v-for="(row, rowIndex) in renderedRows">
46
+ <template v-for="item in row.items">
47
+ <div
48
+ :key="`${rowIndex}-${item.dataIndex || item.field}`"
49
+ class="ecp-pro-descriptions__item"
50
+ :style="getItemStyle(item)"
51
+ >
52
+ <div class="ecp-pro-descriptions__label" :style="getLabelStyle(item)">
53
+ {{ item.label }}
54
+ </div>
55
+ <div class="ecp-pro-descriptions__content" :style="getContentStyle(item)">
56
+ <slot
57
+ v-if="$scopedSlots[item.slot || item.dataIndex]"
58
+ :name="item.slot || item.dataIndex"
59
+ :value="getItemValue(item)"
60
+ :record="effectiveData"
61
+ :schema="item"
62
+ />
63
+ <DescriptionValueRenderer v-else :schema="item" :value="getItemValue(item)" :record="effectiveData" :empty-text="effectiveProps.emptyText || '-'" />
64
+ </div>
65
+ </div>
66
+ </template>
67
+ </template>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script setup lang="ts">
73
+ import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from 'vue'
74
+ import { useComponentSetting } from '../useComponentSetting'
75
+ import { normalizeTooltipConfig } from '../utils/tooltip'
76
+ import type {
77
+ DescriptionActionType,
78
+ DescriptionColumn,
79
+ DescriptionProps,
80
+ DescriptionSchema,
81
+ } from '../types'
82
+
83
+ interface NormalizedRow {
84
+ items: Array<DescriptionSchema & { _span: number }>
85
+ }
86
+
87
+ const props = withDefaults(
88
+ defineProps<{
89
+ title?: string
90
+ helpMessage?: string | string[]
91
+ size?: 'medium' | 'small'
92
+ bordered?: boolean
93
+ column?: DescriptionColumn
94
+ schema?: DescriptionSchema[]
95
+ data?: Record<string, unknown>
96
+ emptyText?: string
97
+ useCollapse?: boolean
98
+ collapseOptions?: {
99
+ canExpand?: boolean
100
+ defaultExpand?: boolean
101
+ expandButtonText?: string
102
+ collapseButtonText?: string
103
+ helpMessage?: string | string[]
104
+ visibleRows?: number
105
+ }
106
+ }>(),
107
+ {
108
+ size: 'medium',
109
+ bordered: true,
110
+ column: () => ({ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }),
111
+ schema: () => [],
112
+ data: () => ({}),
113
+ emptyText: '-',
114
+ useCollapse: false,
115
+ collapseOptions: () => ({ canExpand: false, defaultExpand: true, expandButtonText: '展开', collapseButtonText: '收起', visibleRows: 1 }),
116
+ }
117
+ )
118
+
119
+ const emit = defineEmits<{
120
+ (e: 'register', action: DescriptionActionType): void
121
+ }>()
122
+
123
+ const innerProps = ref<Partial<DescriptionProps>>({})
124
+ const innerData = ref<Record<string, unknown>>({})
125
+ const innerSchema = ref<DescriptionSchema[]>([])
126
+ const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920)
127
+ const expanded = ref(true)
128
+
129
+ const DescriptionValueRenderer = defineComponent({
130
+ name: 'EcpDescriptionValueRenderer',
131
+ props: {
132
+ schema: { type: Object, required: true },
133
+ value: { required: false },
134
+ record: { type: Object, required: true },
135
+ emptyText: { type: String, default: '-' },
136
+ },
137
+ setup(rendererProps) {
138
+ return () => {
139
+ const schema = rendererProps.schema as DescriptionSchema
140
+ const record = rendererProps.record as Record<string, unknown>
141
+ const value = rendererProps.value
142
+
143
+ const renderTextNode = (text: string) => {
144
+ const tooltipProps = normalizeTooltip(schema, text === rendererProps.emptyText ? value : text, record)
145
+ const contentNode = h('span', text)
146
+ if (!tooltipProps) return contentNode
147
+ return h('el-tooltip', { props: tooltipProps }, [contentNode])
148
+ }
149
+
150
+ if (schema.render) {
151
+ const rendered = schema.render(value, record)
152
+ if (rendered == null || rendered === '') return renderTextNode(rendererProps.emptyText)
153
+ if (typeof rendered === 'string' || typeof rendered === 'number') return renderTextNode(String(rendered))
154
+ return rendered as never
155
+ }
156
+
157
+ if (Array.isArray(value)) {
158
+ return renderTextNode(value.length ? value.join(', ') : rendererProps.emptyText)
159
+ }
160
+
161
+ if (value === null || value === undefined || value === '') {
162
+ return renderTextNode(rendererProps.emptyText)
163
+ }
164
+
165
+ if (typeof value === 'object') {
166
+ return renderTextNode(JSON.stringify(value))
167
+ }
168
+
169
+ return renderTextNode(String(value))
170
+ }
171
+ },
172
+ })
173
+
174
+ const normalizeTooltip = (schema: DescriptionSchema, value: unknown, record: Record<string, unknown>) => {
175
+ const tooltip = schema.tooltip
176
+ if (!tooltip) return null
177
+
178
+ const resolved = typeof tooltip === 'function'
179
+ ? tooltip({ value, record, schema })
180
+ : tooltip
181
+ return normalizeTooltipConfig(resolved, value)
182
+ }
183
+
184
+ const { getSetting } = useComponentSetting()
185
+ const effectiveProps = computed(() => ({ ...getSetting('ProDescriptions'), ...props, ...innerProps.value }))
186
+
187
+ const breakpoints = { xxl: 1920, xl: 1200, lg: 992, md: 768, sm: 576 }
188
+
189
+ const resolveColumn = (column: DescriptionColumn | undefined, width: number) => {
190
+ if (typeof column === 'number') return Math.max(1, column)
191
+ const value = column ?? {}
192
+ if (width >= breakpoints.xxl) return value.xxl ?? value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
193
+ if (width >= breakpoints.xl) return value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
194
+ if (width >= breakpoints.lg) return value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
195
+ if (width >= breakpoints.md) return value.md ?? value.sm ?? value.xs ?? 3
196
+ if (width >= breakpoints.sm) return value.sm ?? value.xs ?? 2
197
+ return value.xs ?? 1
198
+ }
199
+
200
+ const currentColumn = computed(() => resolveColumn(effectiveProps.value.column, windowWidth.value))
201
+ const effectiveData = computed(() => effectiveProps.value.data ?? innerData.value ?? {})
202
+
203
+ const visibleSchema = computed(() => {
204
+ return (innerSchema.value.length ? innerSchema.value : effectiveProps.value.schema ?? []).filter((item) => {
205
+ const key = item.dataIndex || item.field
206
+ if (!key) return false
207
+ if (typeof item.show === 'function') {
208
+ return item.show(effectiveData.value)
209
+ }
210
+ return item.show !== false
211
+ }).map((item) => ({
212
+ ...item,
213
+ dataIndex: item.dataIndex || item.field || '',
214
+ }))
215
+ })
216
+
217
+ const normalizedRows = computed<NormalizedRow[]>(() => {
218
+ const rows: NormalizedRow[] = []
219
+ let currentRow: NormalizedRow = { items: [] }
220
+ let used = 0
221
+ const totalColumn = currentColumn.value
222
+ visibleSchema.value.forEach((item, index) => {
223
+ const remainingItems = visibleSchema.value.length - index
224
+ const requested = Math.max(1, Math.min(item.span ?? 1, totalColumn))
225
+ const span = remainingItems === 1 ? totalColumn - used || totalColumn : requested
226
+ if (used + span > totalColumn) {
227
+ rows.push(currentRow)
228
+ currentRow = { items: [] }
229
+ used = 0
230
+ }
231
+ const normalizedSpan = remainingItems === 1 && used < totalColumn ? Math.max(1, totalColumn - used) : span
232
+ currentRow.items.push({ ...item, _span: normalizedSpan })
233
+ used += normalizedSpan
234
+ if (used >= totalColumn) {
235
+ rows.push(currentRow)
236
+ currentRow = { items: [] }
237
+ used = 0
238
+ }
239
+ })
240
+ if (currentRow.items.length > 0) rows.push(currentRow)
241
+ return rows
242
+ })
243
+
244
+ const renderedRows = computed(() => {
245
+ if (!showCollapseButton.value || expanded.value) return normalizedRows.value
246
+ const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
247
+ return normalizedRows.value.slice(0, visibleRows)
248
+ })
249
+
250
+ const showCollapseButton = computed(() => {
251
+ const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
252
+ return !!effectiveProps.value.useCollapse && !!effectiveProps.value.collapseOptions?.canExpand && normalizedRows.value.length > visibleRows
253
+ })
254
+ const showHeader = computed(() => !!effectiveProps.value.title || !!effectiveProps.value.helpMessage || showCollapseButton.value)
255
+ const collapseButtonText = computed(() => ({
256
+ expand: effectiveProps.value.collapseOptions?.expandButtonText ?? '展开',
257
+ collapse: effectiveProps.value.collapseOptions?.collapseButtonText ?? '收起',
258
+ }))
259
+
260
+ const bodyStyle = computed(() => ({
261
+ gridTemplateColumns: `repeat(${currentColumn.value}, minmax(0, 1fr))`,
262
+ }))
263
+
264
+ const getItemStyle = (item: DescriptionSchema & { _span: number }) => ({
265
+ gridColumn: `span ${item._span}`,
266
+ })
267
+
268
+ const getLabelStyle = (item: DescriptionSchema) => ({
269
+ minWidth: item.labelMinWidth ? `${item.labelMinWidth}px` : undefined,
270
+ ...(item.labelStyle ?? {}),
271
+ })
272
+
273
+ const getContentStyle = (item: DescriptionSchema) => ({
274
+ minWidth: item.contentMinWidth ? `${item.contentMinWidth}px` : undefined,
275
+ ...(item.contentStyle ?? {}),
276
+ })
277
+
278
+ const getItemValue = (item: DescriptionSchema) => effectiveData.value[item.dataIndex]
279
+
280
+ const syncSchema = () => {
281
+ innerSchema.value = [...(effectiveProps.value.schema ?? [])]
282
+ }
283
+
284
+ const syncData = () => {
285
+ innerData.value = { ...(effectiveProps.value.data ?? {}) }
286
+ }
287
+
288
+ const setProps = async (descriptionProps: Partial<DescriptionProps>) => {
289
+ innerProps.value = { ...innerProps.value, ...descriptionProps }
290
+ if (descriptionProps.schema) innerSchema.value = [...descriptionProps.schema]
291
+ if (descriptionProps.data) innerData.value = { ...descriptionProps.data }
292
+ }
293
+
294
+ const setData = async (data: Record<string, unknown>) => {
295
+ innerData.value = { ...innerData.value, ...data }
296
+ innerProps.value = { ...innerProps.value, data: innerData.value }
297
+ }
298
+
299
+ const getData = () => ({ ...(effectiveProps.value.data ?? innerData.value ?? {}) })
300
+
301
+ const descriptionAction: DescriptionActionType = {
302
+ setProps,
303
+ setData,
304
+ getData,
305
+ }
306
+
307
+ defineExpose(descriptionAction)
308
+
309
+ const updateWindowWidth = () => {
310
+ if (typeof window !== 'undefined') windowWidth.value = window.innerWidth
311
+ }
312
+
313
+ onMounted(() => {
314
+ syncSchema()
315
+ syncData()
316
+ expanded.value = effectiveProps.value.collapseOptions?.defaultExpand !== false
317
+ emit('register', descriptionAction)
318
+ if (typeof window !== 'undefined') {
319
+ window.addEventListener('resize', updateWindowWidth)
320
+ }
321
+ })
322
+
323
+ onUnmounted(() => {
324
+ if (typeof window !== 'undefined') {
325
+ window.removeEventListener('resize', updateWindowWidth)
326
+ }
327
+ })
328
+
329
+ watch(() => effectiveProps.value.schema, syncSchema, { deep: true })
330
+ watch(() => effectiveProps.value.data, syncData, { deep: true })
331
+ watch(() => effectiveProps.value.collapseOptions?.defaultExpand, (value) => {
332
+ if (value !== undefined) expanded.value = value
333
+ })
334
+ </script>
335
+
336
+ <style scoped>
337
+ .ecp-pro-descriptions {
338
+ width: 100%;
339
+ box-sizing: border-box;
340
+ }
341
+
342
+ .ecp-pro-descriptions__header {
343
+ display: flex;
344
+ align-items: center;
345
+ justify-content: space-between;
346
+ margin-bottom: 12px;
347
+ gap: 12px;
348
+ }
349
+
350
+ .ecp-pro-descriptions__title-wrap {
351
+ display: flex;
352
+ align-items: center;
353
+ gap: 6px;
354
+ }
355
+
356
+ .ecp-pro-descriptions__title {
357
+ font-size: 16px;
358
+ font-weight: 600;
359
+ color: #303133;
360
+ }
361
+
362
+ .ecp-pro-descriptions__help,
363
+ .ecp-pro-descriptions__toggle {
364
+ color: #909399;
365
+ }
366
+
367
+ .ecp-pro-descriptions__toggle .el-icon-arrow-down {
368
+ margin-left: 4px;
369
+ transition: transform 0.2s ease;
370
+ }
371
+
372
+ .ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded {
373
+ transform: rotate(180deg);
374
+ }
375
+
376
+ .ecp-pro-descriptions__body {
377
+ display: grid;
378
+ border-top: 1px solid #ebeef5;
379
+ border-left: 1px solid #ebeef5;
380
+ overflow: hidden;
381
+ }
382
+
383
+ .ecp-pro-descriptions__body.is-collapsed {
384
+ overflow: hidden;
385
+ }
386
+
387
+ .ecp-pro-descriptions__body:not(.is-bordered) {
388
+ border-top: 0;
389
+ border-left: 0;
390
+ gap: 12px 16px;
391
+ }
392
+
393
+ .ecp-pro-descriptions__item {
394
+ display: flex;
395
+ min-width: 0;
396
+ border-right: 1px solid #ebeef5;
397
+ border-bottom: 1px solid #ebeef5;
398
+ }
399
+
400
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__item {
401
+ border-right: 0;
402
+ border-bottom: 0;
403
+ }
404
+
405
+ .ecp-pro-descriptions__label,
406
+ .ecp-pro-descriptions__content {
407
+ min-width: 0;
408
+ box-sizing: border-box;
409
+ word-break: break-word;
410
+ }
411
+
412
+ .ecp-pro-descriptions__label {
413
+ flex: 0 0 120px;
414
+ padding: 12px 16px;
415
+ color: #606266;
416
+ background: #fafafa;
417
+ }
418
+
419
+ .ecp-pro-descriptions__content {
420
+ flex: 1;
421
+ padding: 12px 16px;
422
+ color: #303133;
423
+ background: #fff;
424
+ }
425
+
426
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label {
427
+ flex-basis: auto;
428
+ padding: 0;
429
+ margin-right: 8px;
430
+ background: transparent;
431
+ font-weight: 500;
432
+ }
433
+
434
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__content {
435
+ padding: 0;
436
+ background: transparent;
437
+ }
438
+
439
+ .ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label,
440
+ .ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content {
441
+ padding-top: 8px;
442
+ padding-bottom: 8px;
443
+ font-size: 13px;
444
+ }
445
+
446
+ @media (max-width: 767px) {
447
+ .ecp-pro-descriptions__item {
448
+ flex-direction: column;
449
+ }
450
+
451
+ .ecp-pro-descriptions__label {
452
+ flex-basis: auto;
453
+ }
454
+ }
455
+ </style>
@@ -0,0 +1,5 @@
1
+ import ProDescriptions from './ProDescriptions.vue'
2
+ import { useDescription } from './useDescription'
3
+
4
+ export { ProDescriptions, useDescription }
5
+ export default ProDescriptions
@@ -0,0 +1,52 @@
1
+ import { ref, unref, watch, type Ref } from 'vue'
2
+ import type { DescriptionActionType, DescriptionProps } from '../types'
3
+
4
+ export type UseDescriptionPropsReactive = DescriptionProps | Ref<DescriptionProps | undefined>
5
+
6
+ export type UseDescriptionReturn = [
7
+ (instance: DescriptionActionType) => void,
8
+ DescriptionActionType,
9
+ ]
10
+
11
+ export function useDescription(props?: UseDescriptionPropsReactive): UseDescriptionReturn {
12
+ const descriptionActionRef = ref<DescriptionActionType | null>(null)
13
+
14
+ const getDescriptionProps = (): DescriptionProps | undefined =>
15
+ (props ? unref(props as Ref<DescriptionProps | undefined>) : undefined) as DescriptionProps | undefined
16
+
17
+ const getDescriptionAction = (): DescriptionActionType => {
18
+ const action = unref(descriptionActionRef)
19
+ if (!action) {
20
+ throw new Error('ProDescriptions instance has not been registered')
21
+ }
22
+ return action
23
+ }
24
+
25
+ const register = (instance: DescriptionActionType) => {
26
+ descriptionActionRef.value = instance
27
+ const descriptionProps = getDescriptionProps()
28
+ if (descriptionProps && Object.keys(descriptionProps).length > 0) {
29
+ instance.setProps(descriptionProps)
30
+ }
31
+ }
32
+
33
+ if (props) {
34
+ watch(
35
+ () => getDescriptionProps(),
36
+ (descriptionProps) => {
37
+ if (descriptionProps && descriptionActionRef.value) {
38
+ descriptionActionRef.value.setProps(descriptionProps)
39
+ }
40
+ },
41
+ { deep: true }
42
+ )
43
+ }
44
+
45
+ const descriptionActions: DescriptionActionType = {
46
+ setProps: (descriptionProps) => getDescriptionAction().setProps(descriptionProps),
47
+ setData: (data) => getDescriptionAction().setData(data),
48
+ getData: () => getDescriptionAction().getData(),
49
+ }
50
+
51
+ return [register, descriptionActions]
52
+ }
@@ -9,7 +9,7 @@
9
9
  <el-col v-if="shouldShow(schema)" :key="schema.field" v-bind="getColProps(schema)"
10
10
  :offset="schema.colProps?.offset ?? effectiveProps.baseColProps?.offset ?? 0" :data-field="schema.field">
11
11
  <ProFormItem :schema="schema" :form-model="currentFormModel" :form-disabled="effectiveProps.disabled"
12
- :auto-placeholder="effectiveProps.autoSetPlaceholder" :form-action-type="formActionRef"
12
+ :auto-placeholder="effectiveProps.autoSetPlaceholder" :form-action-type="formActionRef" :colon="effectiveProps.colon"
13
13
  :custom-components="formCustomComponents" :on-field-change="handleFieldChange">
14
14
  <template v-if="slots[getSlotName(schema)]">
15
15
  <slot :name="getSlotName(schema)" :model="currentFormModel" :schema="schema" :field="schema.field"
@@ -69,6 +69,7 @@ const props = withDefaults(
69
69
  initialValues?: Record<string, unknown>
70
70
  labelWidth?: string
71
71
  labelPosition?: 'left' | 'right' | 'top'
72
+ colon?: boolean
72
73
  gutter?: number
73
74
  size?: 'medium' | 'small' | 'large'
74
75
  disabled?: boolean
@@ -96,6 +97,7 @@ const props = withDefaults(
96
97
  {
97
98
  labelWidth: '120px',
98
99
  labelPosition: 'right',
100
+ colon: true,
99
101
  gutter: 24,
100
102
  size: 'medium',
101
103
  autoSetPlaceholder: true,