@aspire-ui/element-component-pro 1.0.16 → 1.0.18

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-f3d27813]{padding:16px;background:#fff;width:100%;box-sizing:border-box}.ecp-pro-table[data-v-f3d27813] .el-table{width:100%!important}.ecp-pro-table__header[data-v-f3d27813]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.ecp-pro-table__title-wrapper[data-v-f3d27813]{display:flex;align-items:center;gap:4px}.ecp-pro-table__title[data-v-f3d27813]{font-size:16px;font-weight:600}.ecp-pro-table__help[data-v-f3d27813]{color:#909399;cursor:help}.ecp-pro-table__toolbar[data-v-f3d27813]{display:flex;align-items:center;gap:8px}.ecp-pro-table__body[data-v-f3d27813]{width:100%}.ecp-pro-table__pagination[data-v-f3d27813]{margin-top:16px;display:flex;justify-content:flex-end}.ecp-pro-table__col-help[data-v-f3d27813]{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-48d7960b]{margin-right:2px}.ecp-pro-form-item__help-icon[data-v-48d7960b]{margin-left:4px;color:#909399;cursor:help;font-size:14px}.ecp-pro-form-item__help-icon[data-v-48d7960b]:hover{color:#409eff}.ecp-pro-form-item__help-item[data-v-48d7960b]{margin-bottom:4px}.ecp-pro-form-item__help-item[data-v-48d7960b]: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-80bac8be]{padding:16px;position:relative}.ecp-pro-form__advance[data-v-80bac8be]{margin-bottom:16px}.ecp-pro-form_col[data-v-80bac8be]{position:relative;float:right}.el-icon-d-arrow-left.up[data-v-80bac8be]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-80bac8be]{transform:rotate(-90deg)}.ecp-form-actions__advance[data-v-80bac8be]{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%)}.ecp-pro-descriptions[data-v-656036f6]{width:100%;box-sizing:border-box}.ecp-pro-descriptions__header[data-v-656036f6]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px}.ecp-pro-descriptions__title-wrap[data-v-656036f6]{display:flex;align-items:center;gap:6px}.ecp-pro-descriptions__title[data-v-656036f6]{font-size:16px;font-weight:600;color:#303133}.ecp-pro-descriptions__help[data-v-656036f6],.ecp-pro-descriptions__toggle[data-v-656036f6]{color:#909399}.ecp-pro-descriptions__toggle .el-icon-arrow-down[data-v-656036f6]{margin-left:4px;transition:transform .2s ease}.ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded[data-v-656036f6]{transform:rotate(180deg)}.ecp-pro-descriptions__body[data-v-656036f6]{display:grid;border-top:1px solid #ebeef5;border-left:1px solid #ebeef5;overflow:hidden}.ecp-pro-descriptions__body.is-collapsed[data-v-656036f6]{overflow:hidden}.ecp-pro-descriptions__body[data-v-656036f6]:not(.is-bordered){border-top:0;border-left:0;gap:12px 16px}.ecp-pro-descriptions__item[data-v-656036f6]{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-656036f6]{border-right:0;border-bottom:0}.ecp-pro-descriptions__label[data-v-656036f6],.ecp-pro-descriptions__content[data-v-656036f6]{min-width:0;box-sizing:border-box;word-break:break-word}.ecp-pro-descriptions__label[data-v-656036f6]{flex:0 0 120px;padding:12px 16px;color:#606266;background:#fafafa}.ecp-pro-descriptions__content[data-v-656036f6]{flex:1;padding:12px 16px;color:#303133;background:#fff}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label[data-v-656036f6]{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-656036f6]{padding:0;background:transparent}.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label[data-v-656036f6],.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content[data-v-656036f6]{padding-top:8px;padding-bottom:8px;font-size:13px}@media (max-width: 767px){.ecp-pro-descriptions__item[data-v-656036f6]{flex-direction:column}.ecp-pro-descriptions__label[data-v-656036f6]{flex-basis:auto}}.ecp-pro-table-form__form[data-v-44aa7592] .el-form-item{margin-bottom:0}.ecp-pro-table-form__cell-item[data-v-44aa7592]{width:100%}.ecp-pro-table-form__cell-item[data-v-44aa7592] .el-form-item__content{margin-left:0!important;line-height:normal}.ecp-pro-table-form__fixed-label[data-v-44aa7592]{color:#303133;font-size:14px}.ecp-pro-table-form__req[data-v-44aa7592]{color:#f56c6c;margin-right:2px}.ecp-pro-table-form__th-text[data-v-44aa7592]{font-weight:500;color:#606266}.ecp-pro-table-form__action-title[data-v-44aa7592]{margin-right:8px;font-size:13px;color:#606266}.ecp-pro-table-form__add-btn[data-v-44aa7592]{padding:0;font-size:14px}.ecp-pro-table-form__del-btn[data-v-44aa7592]{padding:0;color:#909399}.ecp-pro-table-form__del-btn[data-v-44aa7592]:not(:disabled){color:#409eff}.ecp-pro-table-form .ecp-pro-table-form__header-cell{background:#f5f7fa!important}
1
+ .ecp-pro-table[data-v-f3d27813]{padding:16px;background:#fff;width:100%;box-sizing:border-box}.ecp-pro-table[data-v-f3d27813] .el-table{width:100%!important}.ecp-pro-table__header[data-v-f3d27813]{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.ecp-pro-table__title-wrapper[data-v-f3d27813]{display:flex;align-items:center;gap:4px}.ecp-pro-table__title[data-v-f3d27813]{font-size:16px;font-weight:600}.ecp-pro-table__help[data-v-f3d27813]{color:#909399;cursor:help}.ecp-pro-table__toolbar[data-v-f3d27813]{display:flex;align-items:center;gap:8px}.ecp-pro-table__body[data-v-f3d27813]{width:100%}.ecp-pro-table__pagination[data-v-f3d27813]{margin-top:16px;display:flex;justify-content:flex-end}.ecp-pro-table__col-help[data-v-f3d27813]{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-48d7960b]{margin-right:2px}.ecp-pro-form-item__help-icon[data-v-48d7960b]{margin-left:4px;color:#909399;cursor:help;font-size:14px}.ecp-pro-form-item__help-icon[data-v-48d7960b]:hover{color:#409eff}.ecp-pro-form-item__help-item[data-v-48d7960b]{margin-bottom:4px}.ecp-pro-form-item__help-item[data-v-48d7960b]: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-80bac8be]{padding:16px;position:relative}.ecp-pro-form__advance[data-v-80bac8be]{margin-bottom:16px}.ecp-pro-form_col[data-v-80bac8be]{position:relative;float:right}.el-icon-d-arrow-left.up[data-v-80bac8be]{transform:rotate(90deg)}.el-icon-d-arrow-left.down[data-v-80bac8be]{transform:rotate(-90deg)}.ecp-form-actions__advance[data-v-80bac8be]{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%)}.ecp-pro-descriptions[data-v-656036f6]{width:100%;box-sizing:border-box}.ecp-pro-descriptions__header[data-v-656036f6]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px}.ecp-pro-descriptions__title-wrap[data-v-656036f6]{display:flex;align-items:center;gap:6px}.ecp-pro-descriptions__title[data-v-656036f6]{font-size:16px;font-weight:600;color:#303133}.ecp-pro-descriptions__help[data-v-656036f6],.ecp-pro-descriptions__toggle[data-v-656036f6]{color:#909399}.ecp-pro-descriptions__toggle .el-icon-arrow-down[data-v-656036f6]{margin-left:4px;transition:transform .2s ease}.ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded[data-v-656036f6]{transform:rotate(180deg)}.ecp-pro-descriptions__body[data-v-656036f6]{display:grid;border-top:1px solid #ebeef5;border-left:1px solid #ebeef5;overflow:hidden}.ecp-pro-descriptions__body.is-collapsed[data-v-656036f6]{overflow:hidden}.ecp-pro-descriptions__body[data-v-656036f6]:not(.is-bordered){border-top:0;border-left:0;gap:12px 16px}.ecp-pro-descriptions__item[data-v-656036f6]{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-656036f6]{border-right:0;border-bottom:0}.ecp-pro-descriptions__label[data-v-656036f6],.ecp-pro-descriptions__content[data-v-656036f6]{min-width:0;box-sizing:border-box;word-break:break-word}.ecp-pro-descriptions__label[data-v-656036f6]{flex:0 0 120px;padding:12px 16px;color:#606266;background:#fafafa}.ecp-pro-descriptions__content[data-v-656036f6]{flex:1;padding:12px 16px;color:#303133;background:#fff}.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label[data-v-656036f6]{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-656036f6]{padding:0;background:transparent}.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label[data-v-656036f6],.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content[data-v-656036f6]{padding-top:8px;padding-bottom:8px;font-size:13px}@media (max-width: 767px){.ecp-pro-descriptions__item[data-v-656036f6]{flex-direction:column}.ecp-pro-descriptions__label[data-v-656036f6]{flex-basis:auto}}.ecp-collapse-container[data-v-087dba3a]{background:#fff;border-radius:14px;border:1px solid #e8eef8;box-shadow:0 10px 24px #0f2d5e0f;overflow:hidden}.ecp-collapse-container.is-ghost[data-v-087dba3a]{border-color:transparent;box-shadow:none;background:transparent}.ecp-collapse-container__header[data-v-087dba3a]{min-height:56px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid #eef3fb}.ecp-collapse-container.is-ghost .ecp-collapse-container__header[data-v-087dba3a]{padding-left:0;padding-right:0}.ecp-collapse-container__header-main[data-v-087dba3a]{min-width:0;flex:1;display:flex;align-items:center}.ecp-collapse-container__title-wrap[data-v-087dba3a]{min-width:0;display:inline-flex;align-items:center;gap:8px}.ecp-collapse-container__title[data-v-087dba3a]{color:#1f2d3d;font-size:16px;font-weight:600;line-height:1.4}.ecp-collapse-container__help[data-v-087dba3a]{color:#91a3b7;font-size:14px}.ecp-collapse-container__header-extra[data-v-087dba3a]{display:inline-flex;align-items:center;justify-content:flex-end;gap:12px;flex-shrink:0}.ecp-collapse-container__toggle[data-v-087dba3a]{padding:0;color:#2f6fd3}.ecp-collapse-container__toggle i[data-v-087dba3a]{margin-left:6px;transition:transform .2s ease}.ecp-collapse-container__toggle i.is-expanded[data-v-087dba3a]{transform:rotate(180deg)}.ecp-collapse-container__body[data-v-087dba3a]{padding:20px;box-sizing:border-box}.ecp-collapse-container.is-ghost .ecp-collapse-container__body[data-v-087dba3a]{padding-left:0;padding-right:0}.ecp-collapse-container.is-header-clickable .ecp-collapse-container__header-main[data-v-087dba3a]{cursor:pointer}@media (max-width: 768px){.ecp-collapse-container__header[data-v-087dba3a]{padding:14px 16px;align-items:flex-start;flex-direction:column}.ecp-collapse-container__header-extra[data-v-087dba3a]{width:100%;justify-content:space-between}.ecp-collapse-container__body[data-v-087dba3a]{padding:16px}}.ecp-pro-table-form__form[data-v-44aa7592] .el-form-item{margin-bottom:0}.ecp-pro-table-form__cell-item[data-v-44aa7592]{width:100%}.ecp-pro-table-form__cell-item[data-v-44aa7592] .el-form-item__content{margin-left:0!important;line-height:normal}.ecp-pro-table-form__fixed-label[data-v-44aa7592]{color:#303133;font-size:14px}.ecp-pro-table-form__req[data-v-44aa7592]{color:#f56c6c;margin-right:2px}.ecp-pro-table-form__th-text[data-v-44aa7592]{font-weight:500;color:#606266}.ecp-pro-table-form__action-title[data-v-44aa7592]{margin-right:8px;font-size:13px;color:#606266}.ecp-pro-table-form__add-btn[data-v-44aa7592]{padding:0;font-size:14px}.ecp-pro-table-form__del-btn[data-v-44aa7592]{padding:0;color:#909399}.ecp-pro-table-form__del-btn[data-v-44aa7592]:not(:disabled){color:#409eff}.ecp-pro-table-form .ecp-pro-table-form__header-cell{background:#f5f7fa!important}
@@ -37,6 +37,31 @@ export interface GetFieldOptions {
37
37
  (field: string, raw: false): ProFormFieldOption[];
38
38
  (field: string, raw: true): unknown[];
39
39
  }
40
+ export interface CollapseContainerProps {
41
+ title?: string;
42
+ loading?: boolean;
43
+ /** 与 Vben Admin 保持一致,沿用 canExpan 命名 */
44
+ canExpan?: boolean;
45
+ /** canExpan 的别名,便于业务侧按语义使用 */
46
+ canExpand?: boolean;
47
+ helpMessage?: string | string[];
48
+ triggerWindowResize?: boolean;
49
+ expanded?: boolean;
50
+ defaultExpand?: boolean;
51
+ ghost?: boolean;
52
+ expandButtonText?: string;
53
+ collapseButtonText?: string;
54
+ /** 根容器透传属性,支持 style/class/id/data-* 等 */
55
+ wrapperProps?: Record<string, unknown>;
56
+ /** 头部额外 class */
57
+ headerClass?: string;
58
+ /** 头部额外样式 */
59
+ headerStyle?: Record<string, string | number>;
60
+ /** 内容区额外 class */
61
+ contentClass?: string;
62
+ /** 内容区额外样式 */
63
+ contentStyle?: Record<string, string | number>;
64
+ }
40
65
  /** ApiSelect 暴露的实例类型 */
41
66
  export interface ApiSelectInstance {
42
67
  options: ProFormFieldOption[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspire-ui/element-component-pro",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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,284 @@
1
+ <template>
2
+ <div
3
+ v-loading="effectiveProps.loading"
4
+ class="ecp-collapse-container"
5
+ :class="{
6
+ 'is-expanded': mergedExpanded,
7
+ 'is-ghost': effectiveProps.ghost,
8
+ 'is-header-clickable': canToggleByHeader,
9
+ }"
10
+ v-bind="wrapperProps"
11
+ >
12
+ <div v-if="showHeader" class="ecp-collapse-container__header" :class="effectiveProps.headerClass" :style="effectiveProps.headerStyle">
13
+ <div class="ecp-collapse-container__header-main" @click="handleHeaderClick">
14
+ <div v-if="$slots.title || effectiveProps.title" class="ecp-collapse-container__title-wrap">
15
+ <slot name="title">
16
+ <span class="ecp-collapse-container__title">{{ effectiveProps.title }}</span>
17
+ </slot>
18
+ <el-tooltip v-if="effectiveProps.helpMessage" placement="top" effect="dark">
19
+ <template slot="content">
20
+ <span v-if="Array.isArray(effectiveProps.helpMessage)">
21
+ <div v-for="(msg, index) in effectiveProps.helpMessage" :key="index">{{ msg }}</div>
22
+ </span>
23
+ <span v-else>{{ effectiveProps.helpMessage }}</span>
24
+ </template>
25
+ <i class="el-icon-question ecp-collapse-container__help" />
26
+ </el-tooltip>
27
+ </div>
28
+ </div>
29
+
30
+ <div v-if="$slots.action || resolvedCanExpan" class="ecp-collapse-container__header-extra" @click.stop>
31
+ <slot name="action" />
32
+ <el-button
33
+ v-if="resolvedCanExpan"
34
+ type="text"
35
+ class="ecp-collapse-container__toggle"
36
+ @click.stop="toggleExpand"
37
+ >
38
+ {{ mergedExpanded ? effectiveProps.collapseButtonText : effectiveProps.expandButtonText }}
39
+ <i class="el-icon-arrow-down" :class="{ 'is-expanded': mergedExpanded }" />
40
+ </el-button>
41
+ </div>
42
+ </div>
43
+
44
+ <el-collapse-transition>
45
+ <div
46
+ v-show="mergedExpanded"
47
+ class="ecp-collapse-container__body"
48
+ :class="effectiveProps.contentClass"
49
+ :style="effectiveProps.contentStyle"
50
+ >
51
+ <slot />
52
+ </div>
53
+ </el-collapse-transition>
54
+ </div>
55
+ </template>
56
+
57
+ <script setup lang="ts">
58
+ import { computed, ref, watch, useSlots } from 'vue'
59
+ import { useComponentSetting } from '../useComponentSetting'
60
+
61
+ interface CollapseContainerProps {
62
+ title?: string
63
+ loading?: boolean
64
+ canExpan?: boolean
65
+ canExpand?: boolean
66
+ helpMessage?: string | string[]
67
+ triggerWindowResize?: boolean
68
+ expanded?: boolean
69
+ defaultExpand?: boolean
70
+ ghost?: boolean
71
+ expandButtonText?: string
72
+ collapseButtonText?: string
73
+ wrapperProps?: Record<string, unknown>
74
+ headerClass?: string
75
+ headerStyle?: Record<string, string | number>
76
+ contentClass?: string
77
+ contentStyle?: Record<string, string | number>
78
+ }
79
+
80
+ const props = withDefaults(defineProps<CollapseContainerProps>(), {
81
+ loading: false,
82
+ canExpan: true,
83
+ canExpand: undefined,
84
+ triggerWindowResize: true,
85
+ expanded: undefined,
86
+ defaultExpand: true,
87
+ ghost: false,
88
+ expandButtonText: '展开',
89
+ collapseButtonText: '收起',
90
+ wrapperProps: () => ({}),
91
+ headerClass: '',
92
+ headerStyle: () => ({}),
93
+ contentClass: '',
94
+ contentStyle: () => ({}),
95
+ })
96
+
97
+ const emit = defineEmits<{
98
+ (e: 'update:expanded', value: boolean): void
99
+ (e: 'change', value: boolean): void
100
+ (e: 'collapse'): void
101
+ (e: 'expand'): void
102
+ }>()
103
+
104
+ const slots = useSlots()
105
+ const { mergeSettings } = useComponentSetting()
106
+
107
+ const innerExpanded = ref(props.expanded ?? props.defaultExpand)
108
+
109
+ const effectiveProps = computed(() => mergeSettings('CollapseContainer', { ...props }) as CollapseContainerProps)
110
+ const resolvedCanExpan = computed(() => effectiveProps.value.canExpand ?? effectiveProps.value.canExpan ?? true)
111
+ const isControlled = computed(() => effectiveProps.value.expanded !== undefined)
112
+ const mergedExpanded = computed(() => isControlled.value ? !!effectiveProps.value.expanded : innerExpanded.value)
113
+ const showHeader = computed(() => !!slots.title || !!slots.action || !!effectiveProps.value.title || !!effectiveProps.value.helpMessage || resolvedCanExpan.value)
114
+ const canToggleByHeader = computed(() => resolvedCanExpan.value)
115
+ const wrapperProps = computed(() => {
116
+ const incoming = effectiveProps.value.wrapperProps ?? {}
117
+ const wrapperClass = incoming.class
118
+ const wrapperStyle = incoming.style
119
+ const rest = { ...incoming }
120
+ delete rest.class
121
+ delete rest.style
122
+
123
+ return {
124
+ ...rest,
125
+ class: wrapperClass,
126
+ style: wrapperStyle,
127
+ }
128
+ })
129
+
130
+ const triggerWindowResize = () => {
131
+ if (!effectiveProps.value.triggerWindowResize || typeof window === 'undefined') return
132
+ window.setTimeout(() => {
133
+ window.dispatchEvent(new Event('resize'))
134
+ }, 220)
135
+ }
136
+
137
+ const setExpanded = (value: boolean) => {
138
+ if (!isControlled.value) {
139
+ innerExpanded.value = value
140
+ }
141
+ emit('update:expanded', value)
142
+ emit('change', value)
143
+ if (value) emit('expand')
144
+ else emit('collapse')
145
+ triggerWindowResize()
146
+ }
147
+
148
+ const toggleExpand = () => {
149
+ if (!resolvedCanExpan.value) return
150
+ setExpanded(!mergedExpanded.value)
151
+ }
152
+
153
+ const handleHeaderClick = () => {
154
+ if (!canToggleByHeader.value) return
155
+ toggleExpand()
156
+ }
157
+
158
+ watch(() => props.expanded, (value) => {
159
+ if (value !== undefined) {
160
+ innerExpanded.value = value
161
+ }
162
+ })
163
+
164
+ watch(() => props.defaultExpand, (value) => {
165
+ if (props.expanded === undefined) {
166
+ innerExpanded.value = value
167
+ }
168
+ })
169
+
170
+ defineExpose({
171
+ setExpanded,
172
+ toggleExpand,
173
+ })
174
+ </script>
175
+
176
+ <style scoped>
177
+ .ecp-collapse-container {
178
+ background: #fff;
179
+ border-radius: 14px;
180
+ border: 1px solid #e8eef8;
181
+ box-shadow: 0 10px 24px rgba(15, 45, 94, 0.06);
182
+ overflow: hidden;
183
+ }
184
+
185
+ .ecp-collapse-container.is-ghost {
186
+ border-color: transparent;
187
+ box-shadow: none;
188
+ background: transparent;
189
+ }
190
+
191
+ .ecp-collapse-container__header {
192
+ min-height: 56px;
193
+ padding: 0 20px;
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: space-between;
197
+ gap: 16px;
198
+ border-bottom: 1px solid #eef3fb;
199
+ }
200
+
201
+ .ecp-collapse-container.is-ghost .ecp-collapse-container__header {
202
+ padding-left: 0;
203
+ padding-right: 0;
204
+ }
205
+
206
+ .ecp-collapse-container__header-main {
207
+ min-width: 0;
208
+ flex: 1;
209
+ display: flex;
210
+ align-items: center;
211
+ }
212
+
213
+ .ecp-collapse-container__title-wrap {
214
+ min-width: 0;
215
+ display: inline-flex;
216
+ align-items: center;
217
+ gap: 8px;
218
+ }
219
+
220
+ .ecp-collapse-container__title {
221
+ color: #1f2d3d;
222
+ font-size: 16px;
223
+ font-weight: 600;
224
+ line-height: 1.4;
225
+ }
226
+
227
+ .ecp-collapse-container__help {
228
+ color: #91a3b7;
229
+ font-size: 14px;
230
+ }
231
+
232
+ .ecp-collapse-container__header-extra {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ justify-content: flex-end;
236
+ gap: 12px;
237
+ flex-shrink: 0;
238
+ }
239
+
240
+ .ecp-collapse-container__toggle {
241
+ padding: 0;
242
+ color: #2f6fd3;
243
+ }
244
+
245
+ .ecp-collapse-container__toggle i {
246
+ margin-left: 6px;
247
+ transition: transform 0.2s ease;
248
+ }
249
+
250
+ .ecp-collapse-container__toggle i.is-expanded {
251
+ transform: rotate(180deg);
252
+ }
253
+
254
+ .ecp-collapse-container__body {
255
+ padding: 20px;
256
+ box-sizing: border-box;
257
+ }
258
+
259
+ .ecp-collapse-container.is-ghost .ecp-collapse-container__body {
260
+ padding-left: 0;
261
+ padding-right: 0;
262
+ }
263
+
264
+ .ecp-collapse-container.is-header-clickable .ecp-collapse-container__header-main {
265
+ cursor: pointer;
266
+ }
267
+
268
+ @media (max-width: 768px) {
269
+ .ecp-collapse-container__header {
270
+ padding: 14px 16px;
271
+ align-items: flex-start;
272
+ flex-direction: column;
273
+ }
274
+
275
+ .ecp-collapse-container__header-extra {
276
+ width: 100%;
277
+ justify-content: space-between;
278
+ }
279
+
280
+ .ecp-collapse-container__body {
281
+ padding: 16px;
282
+ }
283
+ }
284
+ </style>
@@ -0,0 +1,4 @@
1
+ import CollapseContainer from './CollapseContainer.vue'
2
+
3
+ export { CollapseContainer }
4
+ export default CollapseContainer
@@ -35,12 +35,31 @@ defineEmits<{ (e: 'input', value: unknown): void; (e: 'change', value: unknown):
35
35
  const loading = ref(false)
36
36
  const options = ref<Array<{ label: string; value: unknown }>>([])
37
37
  const rawOptions = ref<unknown[]>([])
38
- const cachedParams = ref<string>('')
38
+
39
+ const sortStringify = (obj: unknown): string => {
40
+ if (obj === null || obj === undefined) return 'null'
41
+ return JSON.stringify(sortKeys(obj))
42
+ }
43
+
44
+ const sortKeys = (obj: unknown): unknown => {
45
+ if (Array.isArray(obj)) return obj.map(sortKeys)
46
+ if (obj !== null && typeof obj === 'object') {
47
+ return Object.keys(obj as Record<string, unknown>)
48
+ .sort()
49
+ .reduce<Record<string, unknown>>((acc, key) => {
50
+ acc[key] = sortKeys((obj as Record<string, unknown>)[key])
51
+ return acc
52
+ }, {})
53
+ }
54
+ return obj
55
+ }
56
+
57
+ const lastFetchedParamsKey = ref<string>(sortStringify(props.params ?? null))
39
58
 
40
59
  const onVisibleChange = (visible: boolean) => {
41
60
  if (props.lazy && visible) {
42
- const paramsKey = JSON.stringify(props.params ?? {})
43
- if (paramsKey !== cachedParams.value || options.value.length === 0) {
61
+ const paramsKey = sortStringify(props.params ?? null)
62
+ if (paramsKey !== lastFetchedParamsKey.value || options.value.length === 0) {
44
63
  fetchOptions()
45
64
  }
46
65
  }
@@ -66,7 +85,7 @@ const fetchOptions = async () => {
66
85
  value: o[valueKey] ?? o.value,
67
86
  }
68
87
  })
69
- cachedParams.value = JSON.stringify(props.params ?? {})
88
+ lastFetchedParamsKey.value = sortStringify(props.params ?? null)
70
89
  } finally {
71
90
  loading.value = false
72
91
  }
@@ -92,6 +111,8 @@ watch(() => props.api, () => {
92
111
  }, { deep: true }
93
112
  )
94
113
  watch(() => props.params, () => {
114
+ const newKey = sortStringify(props.params ?? null)
115
+ if (newKey === lastFetchedParamsKey.value) return
95
116
  if (props.lazy) {
96
117
  options.value = []
97
118
  rawOptions.value = []
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import type { VueConstructor } from 'vue'
2
2
  import ProTable, { TableAction } from './ProTable'
3
3
  import ProForm, { ProFormItem, FormActions, FormattedNumberInput } from './ProForm'
4
4
  import ProDescriptions from './ProDescriptions'
5
+ import CollapseContainer from './CollapseContainer'
5
6
  import { ProTableForm } from './ProTableForm'
6
7
  import { useForm } from './ProForm/useForm'
7
8
  import { useDescription } from './ProDescriptions/useDescription'
@@ -11,6 +12,7 @@ import { useComponentSetting } from './useComponentSetting'
11
12
  export { ProForm, ProFormItem, FormActions, FormattedNumberInput, useForm }
12
13
  export { ProTable, useProTable, TableAction }
13
14
  export { ProDescriptions, useDescription }
15
+ export { CollapseContainer }
14
16
  export { ProTableForm }
15
17
  export type {
16
18
  ProTableFormColumn,
@@ -34,6 +36,7 @@ const components = [
34
36
  { name: 'FormActions', component: FormActions },
35
37
  { name: 'FormattedNumberInput', component: FormattedNumberInput },
36
38
  { name: 'ProDescriptions', component: ProDescriptions },
39
+ { name: 'CollapseContainer', component: CollapseContainer },
37
40
  { name: 'ProTableForm', component: ProTableForm },
38
41
  ]
39
42
 
@@ -48,6 +51,7 @@ export default {
48
51
  ProTable,
49
52
  ProForm,
50
53
  ProDescriptions,
54
+ CollapseContainer,
51
55
  TableAction,
52
56
  FormattedNumberInput,
53
57
  ProTableForm,
@@ -43,6 +43,32 @@ export interface GetFieldOptions {
43
43
  (field: string, raw: true): unknown[]
44
44
  }
45
45
 
46
+ export interface CollapseContainerProps {
47
+ title?: string
48
+ loading?: boolean
49
+ /** 与 Vben Admin 保持一致,沿用 canExpan 命名 */
50
+ canExpan?: boolean
51
+ /** canExpan 的别名,便于业务侧按语义使用 */
52
+ canExpand?: boolean
53
+ helpMessage?: string | string[]
54
+ triggerWindowResize?: boolean
55
+ expanded?: boolean
56
+ defaultExpand?: boolean
57
+ ghost?: boolean
58
+ expandButtonText?: string
59
+ collapseButtonText?: string
60
+ /** 根容器透传属性,支持 style/class/id/data-* 等 */
61
+ wrapperProps?: Record<string, unknown>
62
+ /** 头部额外 class */
63
+ headerClass?: string
64
+ /** 头部额外样式 */
65
+ headerStyle?: Record<string, string | number>
66
+ /** 内容区额外 class */
67
+ contentClass?: string
68
+ /** 内容区额外样式 */
69
+ contentStyle?: Record<string, string | number>
70
+ }
71
+
46
72
  /** ApiSelect 暴露的实例类型 */
47
73
  export interface ApiSelectInstance {
48
74
  options: ProFormFieldOption[]