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

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-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%)}.ecp-pro-descriptions[data-v-31a2b882]{width:100%;box-sizing:border-box}.ecp-pro-descriptions__header[data-v-31a2b882]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px}.ecp-pro-descriptions__title-wrap[data-v-31a2b882]{display:flex;align-items:center;gap:6px}.ecp-pro-descriptions__title[data-v-31a2b882]{font-size:16px;font-weight:600;color:#303133}.ecp-pro-descriptions__help[data-v-31a2b882],.ecp-pro-descriptions__toggle[data-v-31a2b882]{color:#909399}.ecp-pro-descriptions__toggle .el-icon-arrow-down[data-v-31a2b882]{margin-left:4px;transition:transform .2s ease}.ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded[data-v-31a2b882]{transform:rotate(180deg)}.ecp-pro-descriptions__body[data-v-31a2b882]{display:grid;border-top:1px solid #ebeef5;border-left:1px solid #ebeef5;overflow:hidden}.ecp-pro-descriptions__body.is-collapsed[data-v-31a2b882]{overflow:hidden}.ecp-pro-descriptions__body[data-v-31a2b882]:not(.is-bordered){border-top:0;border-left:0;gap:12px 16px}.ecp-pro-descriptions__item[data-v-31a2b882]{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-31a2b882]{border-right:0;border-bottom:0}.ecp-pro-descriptions__label[data-v-31a2b882],.ecp-pro-descriptions__content[data-v-31a2b882]{min-width:0;box-sizing:border-box;word-break:break-word}.ecp-pro-descriptions__label[data-v-31a2b882]{flex:0 0 120px;padding:12px 16px;color:#606266;background:#fafafa}.ecp-pro-descriptions__content[data-v-31a2b882]{flex:1;padding:12px 16px;color:#303133;background:#fff}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label[data-v-31a2b882]{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-31a2b882]{padding:0;background:transparent}.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label[data-v-31a2b882],.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content[data-v-31a2b882]{padding-top:8px;padding-bottom:8px;font-size:13px}@media (max-width: 767px){.ecp-pro-descriptions__item[data-v-31a2b882]{flex-direction:column}.ecp-pro-descriptions__label[data-v-31a2b882]{flex-basis:auto}}
@@ -141,3 +141,63 @@ export interface ProDescriptionsSchema {
141
141
  /** 栅格占位 */
142
142
  span?: number;
143
143
  }
144
+ /** Description 列数配置 */
145
+ export type DescriptionColumn = number | {
146
+ xxl?: number;
147
+ xl?: number;
148
+ lg?: number;
149
+ md?: number;
150
+ sm?: number;
151
+ xs?: number;
152
+ };
153
+ /** Description 描述项配置 */
154
+ export interface DescriptionSchema {
155
+ /** 标签 */
156
+ label: string;
157
+ /** 数据字段(兼容 Vben Description 的 field) */
158
+ field?: string;
159
+ /** 数据字段 */
160
+ dataIndex: string;
161
+ /** 栅格占位 */
162
+ span?: number;
163
+ /** 是否显示 */
164
+ show?: boolean | ((data: Record<string, unknown>) => boolean);
165
+ /** 标签最小宽度 */
166
+ labelMinWidth?: number;
167
+ /** 内容最小宽度 */
168
+ contentMinWidth?: number;
169
+ /** 标签样式 */
170
+ labelStyle?: Record<string, string | number>;
171
+ /** 内容样式 */
172
+ contentStyle?: Record<string, string | number>;
173
+ /** 自定义插槽名称 */
174
+ slot?: string;
175
+ /** 自定义渲染 */
176
+ render?: (value: unknown, record: Record<string, unknown>) => VNode | string | number | null | undefined;
177
+ }
178
+ /** Description Props */
179
+ export interface DescriptionProps {
180
+ title?: string;
181
+ helpMessage?: string | string[];
182
+ size?: 'medium' | 'small';
183
+ bordered?: boolean;
184
+ column?: DescriptionColumn;
185
+ schema?: DescriptionSchema[];
186
+ data?: Record<string, unknown>;
187
+ emptyText?: string;
188
+ useCollapse?: boolean;
189
+ collapseOptions?: {
190
+ canExpand?: boolean;
191
+ defaultExpand?: boolean;
192
+ expandButtonText?: string;
193
+ collapseButtonText?: string;
194
+ helpMessage?: string | string[];
195
+ visibleRows?: number;
196
+ };
197
+ }
198
+ /** Description 操作实例 */
199
+ export interface DescriptionActionType {
200
+ setProps: (props: Partial<DescriptionProps>) => Promise<void>;
201
+ setData: (data: Record<string, unknown>) => Promise<void>;
202
+ getData: () => Record<string, unknown>;
203
+ }
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.06",
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",
@@ -0,0 +1,437 @@
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 type {
76
+ DescriptionActionType,
77
+ DescriptionColumn,
78
+ DescriptionProps,
79
+ DescriptionSchema,
80
+ } from '../types'
81
+
82
+ interface NormalizedRow {
83
+ items: Array<DescriptionSchema & { _span: number }>
84
+ }
85
+
86
+ const props = withDefaults(
87
+ defineProps<{
88
+ title?: string
89
+ helpMessage?: string | string[]
90
+ size?: 'medium' | 'small'
91
+ bordered?: boolean
92
+ column?: DescriptionColumn
93
+ schema?: DescriptionSchema[]
94
+ data?: Record<string, unknown>
95
+ emptyText?: string
96
+ useCollapse?: boolean
97
+ collapseOptions?: {
98
+ canExpand?: boolean
99
+ defaultExpand?: boolean
100
+ expandButtonText?: string
101
+ collapseButtonText?: string
102
+ helpMessage?: string | string[]
103
+ visibleRows?: number
104
+ }
105
+ }>(),
106
+ {
107
+ size: 'medium',
108
+ bordered: true,
109
+ column: () => ({ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }),
110
+ schema: () => [],
111
+ data: () => ({}),
112
+ emptyText: '-',
113
+ useCollapse: false,
114
+ collapseOptions: () => ({ canExpand: false, defaultExpand: true, expandButtonText: '展开', collapseButtonText: '收起', visibleRows: 1 }),
115
+ }
116
+ )
117
+
118
+ const emit = defineEmits<{
119
+ (e: 'register', action: DescriptionActionType): void
120
+ }>()
121
+
122
+ const innerProps = ref<Partial<DescriptionProps>>({})
123
+ const innerData = ref<Record<string, unknown>>({})
124
+ const innerSchema = ref<DescriptionSchema[]>([])
125
+ const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920)
126
+ const expanded = ref(true)
127
+
128
+ const DescriptionValueRenderer = defineComponent({
129
+ name: 'EcpDescriptionValueRenderer',
130
+ props: {
131
+ schema: { type: Object, required: true },
132
+ value: { required: false },
133
+ record: { type: Object, required: true },
134
+ emptyText: { type: String, default: '-' },
135
+ },
136
+ setup(rendererProps) {
137
+ return () => {
138
+ const schema = rendererProps.schema as DescriptionSchema
139
+ const record = rendererProps.record as Record<string, unknown>
140
+ const value = rendererProps.value
141
+
142
+ if (schema.render) {
143
+ const rendered = schema.render(value, record)
144
+ if (rendered == null || rendered === '') return h('span', rendererProps.emptyText)
145
+ if (typeof rendered === 'string' || typeof rendered === 'number') return h('span', String(rendered))
146
+ return rendered as never
147
+ }
148
+
149
+ if (Array.isArray(value)) {
150
+ return h('span', value.length ? value.join(', ') : rendererProps.emptyText)
151
+ }
152
+
153
+ if (value === null || value === undefined || value === '') {
154
+ return h('span', rendererProps.emptyText)
155
+ }
156
+
157
+ if (typeof value === 'object') {
158
+ return h('span', JSON.stringify(value))
159
+ }
160
+
161
+ return h('span', String(value))
162
+ }
163
+ },
164
+ })
165
+
166
+ const { getSetting } = useComponentSetting()
167
+ const effectiveProps = computed(() => ({ ...getSetting('ProDescriptions'), ...props, ...innerProps.value }))
168
+
169
+ const breakpoints = { xxl: 1920, xl: 1200, lg: 992, md: 768, sm: 576 }
170
+
171
+ const resolveColumn = (column: DescriptionColumn | undefined, width: number) => {
172
+ if (typeof column === 'number') return Math.max(1, column)
173
+ const value = column ?? {}
174
+ if (width >= breakpoints.xxl) return value.xxl ?? value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
175
+ if (width >= breakpoints.xl) return value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
176
+ if (width >= breakpoints.lg) return value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
177
+ if (width >= breakpoints.md) return value.md ?? value.sm ?? value.xs ?? 3
178
+ if (width >= breakpoints.sm) return value.sm ?? value.xs ?? 2
179
+ return value.xs ?? 1
180
+ }
181
+
182
+ const currentColumn = computed(() => resolveColumn(effectiveProps.value.column, windowWidth.value))
183
+ const effectiveData = computed(() => effectiveProps.value.data ?? innerData.value ?? {})
184
+
185
+ const visibleSchema = computed(() => {
186
+ return (innerSchema.value.length ? innerSchema.value : effectiveProps.value.schema ?? []).filter((item) => {
187
+ const key = item.dataIndex || item.field
188
+ if (!key) return false
189
+ if (typeof item.show === 'function') {
190
+ return item.show(effectiveData.value)
191
+ }
192
+ return item.show !== false
193
+ }).map((item) => ({
194
+ ...item,
195
+ dataIndex: item.dataIndex || item.field || '',
196
+ }))
197
+ })
198
+
199
+ const normalizedRows = computed<NormalizedRow[]>(() => {
200
+ const rows: NormalizedRow[] = []
201
+ let currentRow: NormalizedRow = { items: [] }
202
+ let used = 0
203
+ const totalColumn = currentColumn.value
204
+ visibleSchema.value.forEach((item, index) => {
205
+ const remainingItems = visibleSchema.value.length - index
206
+ const requested = Math.max(1, Math.min(item.span ?? 1, totalColumn))
207
+ const span = remainingItems === 1 ? totalColumn - used || totalColumn : requested
208
+ if (used + span > totalColumn) {
209
+ rows.push(currentRow)
210
+ currentRow = { items: [] }
211
+ used = 0
212
+ }
213
+ const normalizedSpan = remainingItems === 1 && used < totalColumn ? Math.max(1, totalColumn - used) : span
214
+ currentRow.items.push({ ...item, _span: normalizedSpan })
215
+ used += normalizedSpan
216
+ if (used >= totalColumn) {
217
+ rows.push(currentRow)
218
+ currentRow = { items: [] }
219
+ used = 0
220
+ }
221
+ })
222
+ if (currentRow.items.length > 0) rows.push(currentRow)
223
+ return rows
224
+ })
225
+
226
+ const renderedRows = computed(() => {
227
+ if (!showCollapseButton.value || expanded.value) return normalizedRows.value
228
+ const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
229
+ return normalizedRows.value.slice(0, visibleRows)
230
+ })
231
+
232
+ const showCollapseButton = computed(() => {
233
+ const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
234
+ return !!effectiveProps.value.useCollapse && !!effectiveProps.value.collapseOptions?.canExpand && normalizedRows.value.length > visibleRows
235
+ })
236
+ const showHeader = computed(() => !!effectiveProps.value.title || !!effectiveProps.value.helpMessage || showCollapseButton.value)
237
+ const collapseButtonText = computed(() => ({
238
+ expand: effectiveProps.value.collapseOptions?.expandButtonText ?? '展开',
239
+ collapse: effectiveProps.value.collapseOptions?.collapseButtonText ?? '收起',
240
+ }))
241
+
242
+ const bodyStyle = computed(() => ({
243
+ gridTemplateColumns: `repeat(${currentColumn.value}, minmax(0, 1fr))`,
244
+ }))
245
+
246
+ const getItemStyle = (item: DescriptionSchema & { _span: number }) => ({
247
+ gridColumn: `span ${item._span}`,
248
+ })
249
+
250
+ const getLabelStyle = (item: DescriptionSchema) => ({
251
+ minWidth: item.labelMinWidth ? `${item.labelMinWidth}px` : undefined,
252
+ ...(item.labelStyle ?? {}),
253
+ })
254
+
255
+ const getContentStyle = (item: DescriptionSchema) => ({
256
+ minWidth: item.contentMinWidth ? `${item.contentMinWidth}px` : undefined,
257
+ ...(item.contentStyle ?? {}),
258
+ })
259
+
260
+ const getItemValue = (item: DescriptionSchema) => effectiveData.value[item.dataIndex]
261
+
262
+ const syncSchema = () => {
263
+ innerSchema.value = [...(effectiveProps.value.schema ?? [])]
264
+ }
265
+
266
+ const syncData = () => {
267
+ innerData.value = { ...(effectiveProps.value.data ?? {}) }
268
+ }
269
+
270
+ const setProps = async (descriptionProps: Partial<DescriptionProps>) => {
271
+ innerProps.value = { ...innerProps.value, ...descriptionProps }
272
+ if (descriptionProps.schema) innerSchema.value = [...descriptionProps.schema]
273
+ if (descriptionProps.data) innerData.value = { ...descriptionProps.data }
274
+ }
275
+
276
+ const setData = async (data: Record<string, unknown>) => {
277
+ innerData.value = { ...innerData.value, ...data }
278
+ innerProps.value = { ...innerProps.value, data: innerData.value }
279
+ }
280
+
281
+ const getData = () => ({ ...(effectiveProps.value.data ?? innerData.value ?? {}) })
282
+
283
+ const descriptionAction: DescriptionActionType = {
284
+ setProps,
285
+ setData,
286
+ getData,
287
+ }
288
+
289
+ defineExpose(descriptionAction)
290
+
291
+ const updateWindowWidth = () => {
292
+ if (typeof window !== 'undefined') windowWidth.value = window.innerWidth
293
+ }
294
+
295
+ onMounted(() => {
296
+ syncSchema()
297
+ syncData()
298
+ expanded.value = effectiveProps.value.collapseOptions?.defaultExpand !== false
299
+ emit('register', descriptionAction)
300
+ if (typeof window !== 'undefined') {
301
+ window.addEventListener('resize', updateWindowWidth)
302
+ }
303
+ })
304
+
305
+ onUnmounted(() => {
306
+ if (typeof window !== 'undefined') {
307
+ window.removeEventListener('resize', updateWindowWidth)
308
+ }
309
+ })
310
+
311
+ watch(() => effectiveProps.value.schema, syncSchema, { deep: true })
312
+ watch(() => effectiveProps.value.data, syncData, { deep: true })
313
+ watch(() => effectiveProps.value.collapseOptions?.defaultExpand, (value) => {
314
+ if (value !== undefined) expanded.value = value
315
+ })
316
+ </script>
317
+
318
+ <style scoped>
319
+ .ecp-pro-descriptions {
320
+ width: 100%;
321
+ box-sizing: border-box;
322
+ }
323
+
324
+ .ecp-pro-descriptions__header {
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: space-between;
328
+ margin-bottom: 12px;
329
+ gap: 12px;
330
+ }
331
+
332
+ .ecp-pro-descriptions__title-wrap {
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 6px;
336
+ }
337
+
338
+ .ecp-pro-descriptions__title {
339
+ font-size: 16px;
340
+ font-weight: 600;
341
+ color: #303133;
342
+ }
343
+
344
+ .ecp-pro-descriptions__help,
345
+ .ecp-pro-descriptions__toggle {
346
+ color: #909399;
347
+ }
348
+
349
+ .ecp-pro-descriptions__toggle .el-icon-arrow-down {
350
+ margin-left: 4px;
351
+ transition: transform 0.2s ease;
352
+ }
353
+
354
+ .ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded {
355
+ transform: rotate(180deg);
356
+ }
357
+
358
+ .ecp-pro-descriptions__body {
359
+ display: grid;
360
+ border-top: 1px solid #ebeef5;
361
+ border-left: 1px solid #ebeef5;
362
+ overflow: hidden;
363
+ }
364
+
365
+ .ecp-pro-descriptions__body.is-collapsed {
366
+ overflow: hidden;
367
+ }
368
+
369
+ .ecp-pro-descriptions__body:not(.is-bordered) {
370
+ border-top: 0;
371
+ border-left: 0;
372
+ gap: 12px 16px;
373
+ }
374
+
375
+ .ecp-pro-descriptions__item {
376
+ display: flex;
377
+ min-width: 0;
378
+ border-right: 1px solid #ebeef5;
379
+ border-bottom: 1px solid #ebeef5;
380
+ }
381
+
382
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__item {
383
+ border-right: 0;
384
+ border-bottom: 0;
385
+ }
386
+
387
+ .ecp-pro-descriptions__label,
388
+ .ecp-pro-descriptions__content {
389
+ min-width: 0;
390
+ box-sizing: border-box;
391
+ word-break: break-word;
392
+ }
393
+
394
+ .ecp-pro-descriptions__label {
395
+ flex: 0 0 120px;
396
+ padding: 12px 16px;
397
+ color: #606266;
398
+ background: #fafafa;
399
+ }
400
+
401
+ .ecp-pro-descriptions__content {
402
+ flex: 1;
403
+ padding: 12px 16px;
404
+ color: #303133;
405
+ background: #fff;
406
+ }
407
+
408
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label {
409
+ flex-basis: auto;
410
+ padding: 0;
411
+ margin-right: 8px;
412
+ background: transparent;
413
+ font-weight: 500;
414
+ }
415
+
416
+ .ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__content {
417
+ padding: 0;
418
+ background: transparent;
419
+ }
420
+
421
+ .ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label,
422
+ .ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content {
423
+ padding-top: 8px;
424
+ padding-bottom: 8px;
425
+ font-size: 13px;
426
+ }
427
+
428
+ @media (max-width: 767px) {
429
+ .ecp-pro-descriptions__item {
430
+ flex-direction: column;
431
+ }
432
+
433
+ .ecp-pro-descriptions__label {
434
+ flex-basis: auto;
435
+ }
436
+ }
437
+ </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
+ }
package/src/index.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import type { VueConstructor } from 'vue'
2
2
  import ProTable, { TableAction } from './ProTable'
3
3
  import ProForm, { ProFormItem, FormActions } from './ProForm'
4
+ import ProDescriptions from './ProDescriptions'
4
5
  import { useForm } from './ProForm/useForm'
6
+ import { useDescription } from './ProDescriptions/useDescription'
5
7
  import { useProTable } from './ProTable/useProTable'
6
8
  import { useComponentSetting } from './useComponentSetting'
7
9
 
8
10
  export { ProForm, ProFormItem, FormActions, useForm }
9
11
  export { ProTable, useProTable, TableAction }
12
+ export { ProDescriptions, useDescription }
10
13
  export { useComponentSetting }
11
14
  export type { UseComponentSettingReturn } from './useComponentSetting'
12
15
  export type { UseProTableReturn, UseProTablePropsReactive } from './ProTable/useProTable'
16
+ export type { UseDescriptionReturn, UseDescriptionPropsReactive } from './ProDescriptions/useDescription'
13
17
  export * from './ProTable/types'
14
18
  export * from './types'
15
19
 
@@ -19,6 +23,7 @@ const components = [
19
23
  { name: 'ProForm', component: ProForm },
20
24
  { name: 'ProFormItem', component: ProFormItem },
21
25
  { name: 'FormActions', component: FormActions },
26
+ { name: 'ProDescriptions', component: ProDescriptions },
22
27
  ]
23
28
 
24
29
  export function install(Vue: VueConstructor) {
@@ -31,5 +36,6 @@ export default {
31
36
  install,
32
37
  ProTable,
33
38
  ProForm,
39
+ ProDescriptions,
34
40
  TableAction,
35
41
  }
@@ -160,3 +160,67 @@ export interface ProDescriptionsSchema {
160
160
  /** 栅格占位 */
161
161
  span?: number
162
162
  }
163
+
164
+ /** Description 列数配置 */
165
+ export type DescriptionColumn = number | {
166
+ xxl?: number
167
+ xl?: number
168
+ lg?: number
169
+ md?: number
170
+ sm?: number
171
+ xs?: number
172
+ }
173
+
174
+ /** Description 描述项配置 */
175
+ export interface DescriptionSchema {
176
+ /** 标签 */
177
+ label: string
178
+ /** 数据字段(兼容 Vben Description 的 field) */
179
+ field?: string
180
+ /** 数据字段 */
181
+ dataIndex: string
182
+ /** 栅格占位 */
183
+ span?: number
184
+ /** 是否显示 */
185
+ show?: boolean | ((data: Record<string, unknown>) => boolean)
186
+ /** 标签最小宽度 */
187
+ labelMinWidth?: number
188
+ /** 内容最小宽度 */
189
+ contentMinWidth?: number
190
+ /** 标签样式 */
191
+ labelStyle?: Record<string, string | number>
192
+ /** 内容样式 */
193
+ contentStyle?: Record<string, string | number>
194
+ /** 自定义插槽名称 */
195
+ slot?: string
196
+ /** 自定义渲染 */
197
+ render?: (value: unknown, record: Record<string, unknown>) => VNode | string | number | null | undefined
198
+ }
199
+
200
+ /** Description Props */
201
+ export interface DescriptionProps {
202
+ title?: string
203
+ helpMessage?: string | string[]
204
+ size?: 'medium' | 'small'
205
+ bordered?: boolean
206
+ column?: DescriptionColumn
207
+ schema?: DescriptionSchema[]
208
+ data?: Record<string, unknown>
209
+ emptyText?: string
210
+ useCollapse?: boolean
211
+ collapseOptions?: {
212
+ canExpand?: boolean
213
+ defaultExpand?: boolean
214
+ expandButtonText?: string
215
+ collapseButtonText?: string
216
+ helpMessage?: string | string[]
217
+ visibleRows?: number
218
+ }
219
+ }
220
+
221
+ /** Description 操作实例 */
222
+ export interface DescriptionActionType {
223
+ setProps: (props: Partial<DescriptionProps>) => Promise<void>
224
+ setData: (data: Record<string, unknown>) => Promise<void>
225
+ getData: () => Record<string, unknown>
226
+ }