@aspire-ui/element-component-pro 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/CollapseContainer/CollapseContainer.vue.d.ts +101 -0
- package/dist/CollapseContainer/index.d.ts +4 -0
- package/dist/ProDescriptions/ProDescriptions.vue.d.ts +1 -1
- package/dist/ProForm/ApiSelect.vue.d.ts +1 -0
- package/dist/ProForm/ProForm.vue.d.ts +1 -4
- package/dist/ProForm/useForm.d.ts +3 -0
- package/dist/ProTable/ProTable.vue.d.ts +1 -1
- package/dist/element-component-pro.es.js +981 -884
- package/dist/element-component-pro.es.js.map +1 -1
- package/dist/element-component-pro.umd.js +2 -2
- package/dist/element-component-pro.umd.js.map +1 -1
- package/dist/index.d.ts +531 -102
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +38 -9
- package/package.json +1 -1
- package/src/CollapseContainer/CollapseContainer.vue +284 -0
- package/src/CollapseContainer/index.ts +4 -0
- package/src/ProForm/ApiSelect.vue +30 -4
- package/src/ProForm/ProForm.vue +8 -5
- package/src/ProForm/useForm.ts +21 -0
- package/src/index.ts +4 -0
- package/src/types/index.ts +41 -3
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-
|
|
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}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,12 +28,44 @@ export interface ScrollToFieldOptions {
|
|
|
28
28
|
/** 行内对齐 */
|
|
29
29
|
inline?: ScrollLogicalPosition;
|
|
30
30
|
}
|
|
31
|
+
export interface ProFormFieldOption {
|
|
32
|
+
label: string;
|
|
33
|
+
value: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface GetFieldOptions {
|
|
36
|
+
(field: string): ProFormFieldOption[];
|
|
37
|
+
(field: string, raw: false): ProFormFieldOption[];
|
|
38
|
+
(field: string, raw: true): unknown[];
|
|
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
|
+
}
|
|
31
65
|
/** ApiSelect 暴露的实例类型 */
|
|
32
66
|
export interface ApiSelectInstance {
|
|
33
|
-
options:
|
|
34
|
-
|
|
35
|
-
value: unknown;
|
|
36
|
-
}>;
|
|
67
|
+
options: ProFormFieldOption[];
|
|
68
|
+
rawOptions: unknown[];
|
|
37
69
|
loading: boolean;
|
|
38
70
|
fetchOptions: () => Promise<void>;
|
|
39
71
|
}
|
|
@@ -53,11 +85,8 @@ export interface FormActionType {
|
|
|
53
85
|
setProps: (props: Partial<ProFormProps>) => Promise<void>;
|
|
54
86
|
/** 获取指定字段的组件实例 */
|
|
55
87
|
getComponentInstance: (field: string) => ComponentPublicInstance | null;
|
|
56
|
-
/** 获取 api-select 字段的 options */
|
|
57
|
-
getFieldOptions:
|
|
58
|
-
label: string;
|
|
59
|
-
value: unknown;
|
|
60
|
-
}>;
|
|
88
|
+
/** 获取 api-select 字段的 options,raw=true 时返回接口原始列表数据 */
|
|
89
|
+
getFieldOptions: GetFieldOptions;
|
|
61
90
|
/** 获取 api-select 字段的加载状态 */
|
|
62
91
|
isFieldLoading: (field: string) => boolean;
|
|
63
92
|
}
|
package/package.json
CHANGED
|
@@ -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>
|
|
@@ -34,12 +34,32 @@ defineEmits<{ (e: 'input', value: unknown): void; (e: 'change', value: unknown):
|
|
|
34
34
|
|
|
35
35
|
const loading = ref(false)
|
|
36
36
|
const options = ref<Array<{ label: string; value: unknown }>>([])
|
|
37
|
-
const
|
|
37
|
+
const rawOptions = ref<unknown[]>([])
|
|
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))
|
|
38
58
|
|
|
39
59
|
const onVisibleChange = (visible: boolean) => {
|
|
40
60
|
if (props.lazy && visible) {
|
|
41
|
-
const paramsKey =
|
|
42
|
-
if (paramsKey !==
|
|
61
|
+
const paramsKey = sortStringify(props.params ?? null)
|
|
62
|
+
if (paramsKey !== lastFetchedParamsKey.value || options.value.length === 0) {
|
|
43
63
|
fetchOptions()
|
|
44
64
|
}
|
|
45
65
|
}
|
|
@@ -55,6 +75,7 @@ const fetchOptions = async () => {
|
|
|
55
75
|
: ((res as Record<string, unknown>)?.list as unknown[]) ??
|
|
56
76
|
((res as Record<string, unknown>)?.data as unknown[]) ??
|
|
57
77
|
[]
|
|
78
|
+
rawOptions.value = raw
|
|
58
79
|
const labelKey = props.labelField ?? 'label'
|
|
59
80
|
const valueKey = props.valueField ?? 'value'
|
|
60
81
|
options.value = raw.map((item: unknown) => {
|
|
@@ -64,7 +85,7 @@ const fetchOptions = async () => {
|
|
|
64
85
|
value: o[valueKey] ?? o.value,
|
|
65
86
|
}
|
|
66
87
|
})
|
|
67
|
-
|
|
88
|
+
lastFetchedParamsKey.value = sortStringify(props.params ?? null)
|
|
68
89
|
} finally {
|
|
69
90
|
loading.value = false
|
|
70
91
|
}
|
|
@@ -72,6 +93,7 @@ const fetchOptions = async () => {
|
|
|
72
93
|
|
|
73
94
|
defineExpose({
|
|
74
95
|
options,
|
|
96
|
+
rawOptions,
|
|
75
97
|
loading,
|
|
76
98
|
fetchOptions,
|
|
77
99
|
})
|
|
@@ -84,12 +106,16 @@ watch(() => props.api, () => {
|
|
|
84
106
|
fetchOptions()
|
|
85
107
|
} else {
|
|
86
108
|
options.value = []
|
|
109
|
+
rawOptions.value = []
|
|
87
110
|
}
|
|
88
111
|
}, { deep: true }
|
|
89
112
|
)
|
|
90
113
|
watch(() => props.params, () => {
|
|
114
|
+
const newKey = sortStringify(props.params ?? null)
|
|
115
|
+
if (newKey === lastFetchedParamsKey.value) return
|
|
91
116
|
if (props.lazy) {
|
|
92
117
|
options.value = []
|
|
118
|
+
rawOptions.value = []
|
|
93
119
|
} else {
|
|
94
120
|
fetchOptions()
|
|
95
121
|
}
|
package/src/ProForm/ProForm.vue
CHANGED
|
@@ -422,15 +422,18 @@ const getComponentInstance = (field: string): ComponentPublicInstance | null =>
|
|
|
422
422
|
return fieldInstanceMap.value.get(field) ?? null
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
/** 获取 api-select 字段的 options */
|
|
426
|
-
|
|
425
|
+
/** 获取 api-select 字段的 options,raw=true 时返回接口原始数据 */
|
|
426
|
+
function getFieldOptions(field: string): Array<{ label: string; value: unknown }>
|
|
427
|
+
function getFieldOptions(field: string, raw: false): Array<{ label: string; value: unknown }>
|
|
428
|
+
function getFieldOptions(field: string, raw: true): unknown[]
|
|
429
|
+
function getFieldOptions(field: string, raw = false): Array<{ label: string; value: unknown }> | unknown[] {
|
|
427
430
|
const instance = fieldInstanceMap.value.get(field)
|
|
428
431
|
if (!instance) return []
|
|
429
432
|
const apiSelectInstance = instance as unknown as ApiSelectInstance
|
|
430
|
-
if (
|
|
431
|
-
return apiSelectInstance
|
|
433
|
+
if (raw) {
|
|
434
|
+
return apiSelectInstance?.rawOptions ?? []
|
|
432
435
|
}
|
|
433
|
-
return []
|
|
436
|
+
return apiSelectInstance?.options ?? []
|
|
434
437
|
}
|
|
435
438
|
|
|
436
439
|
/** 获取 api-select 字段的加载状态 */
|
package/src/ProForm/useForm.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface UseFormReturn {
|
|
|
24
24
|
appendSchemaByField: (schema: ProFormSchema, prefixField?: string, first?: boolean) => Promise<void>
|
|
25
25
|
removeSchemaByField: (field: string | string[]) => Promise<void>
|
|
26
26
|
setProps: (props: Partial<ProFormProps>) => Promise<void>
|
|
27
|
+
getComponentInstance: FormActionType['getComponentInstance']
|
|
28
|
+
getFieldOptions: FormActionType['getFieldOptions']
|
|
29
|
+
isFieldLoading: FormActionType['isFieldLoading']
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export function useForm(props?: UseFormPropsReactive): [UseFormReturn['register'], Omit<UseFormReturn, 'register'>] {
|
|
@@ -92,6 +95,21 @@ export function useForm(props?: UseFormPropsReactive): [UseFormReturn['register'
|
|
|
92
95
|
await formAction.value?.setProps(formProps)
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
const getComponentInstance: FormActionType['getComponentInstance'] = (field) =>
|
|
99
|
+
formAction.value?.getComponentInstance(field) ?? null
|
|
100
|
+
|
|
101
|
+
function getFieldOptions(field: string): Array<{ label: string; value: unknown }>
|
|
102
|
+
function getFieldOptions(field: string, raw: false): Array<{ label: string; value: unknown }>
|
|
103
|
+
function getFieldOptions(field: string, raw: true): unknown[]
|
|
104
|
+
function getFieldOptions(field: string, raw = false): Array<{ label: string; value: unknown }> | unknown[] {
|
|
105
|
+
if (!formAction.value) return []
|
|
106
|
+
if (raw) return formAction.value.getFieldOptions(field, true)
|
|
107
|
+
return formAction.value.getFieldOptions(field)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const isFieldLoading: FormActionType['isFieldLoading'] = (field) =>
|
|
111
|
+
formAction.value?.isFieldLoading(field) ?? false
|
|
112
|
+
|
|
95
113
|
const result: UseFormReturn = {
|
|
96
114
|
register,
|
|
97
115
|
formAction,
|
|
@@ -107,6 +125,9 @@ export function useForm(props?: UseFormPropsReactive): [UseFormReturn['register'
|
|
|
107
125
|
appendSchemaByField,
|
|
108
126
|
removeSchemaByField,
|
|
109
127
|
setProps,
|
|
128
|
+
getComponentInstance,
|
|
129
|
+
getFieldOptions,
|
|
130
|
+
isFieldLoading,
|
|
110
131
|
}
|
|
111
132
|
|
|
112
133
|
return [register, result]
|
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,
|
package/src/types/index.ts
CHANGED
|
@@ -32,9 +32,47 @@ export interface ScrollToFieldOptions {
|
|
|
32
32
|
inline?: ScrollLogicalPosition
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface ProFormFieldOption {
|
|
36
|
+
label: string
|
|
37
|
+
value: unknown
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GetFieldOptions {
|
|
41
|
+
(field: string): ProFormFieldOption[]
|
|
42
|
+
(field: string, raw: false): ProFormFieldOption[]
|
|
43
|
+
(field: string, raw: true): unknown[]
|
|
44
|
+
}
|
|
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
|
+
|
|
35
72
|
/** ApiSelect 暴露的实例类型 */
|
|
36
73
|
export interface ApiSelectInstance {
|
|
37
|
-
options:
|
|
74
|
+
options: ProFormFieldOption[]
|
|
75
|
+
rawOptions: unknown[]
|
|
38
76
|
loading: boolean
|
|
39
77
|
fetchOptions: () => Promise<void>
|
|
40
78
|
}
|
|
@@ -55,8 +93,8 @@ export interface FormActionType {
|
|
|
55
93
|
setProps: (props: Partial<ProFormProps>) => Promise<void>
|
|
56
94
|
/** 获取指定字段的组件实例 */
|
|
57
95
|
getComponentInstance: (field: string) => ComponentPublicInstance | null
|
|
58
|
-
/** 获取 api-select 字段的 options */
|
|
59
|
-
getFieldOptions:
|
|
96
|
+
/** 获取 api-select 字段的 options,raw=true 时返回接口原始列表数据 */
|
|
97
|
+
getFieldOptions: GetFieldOptions
|
|
60
98
|
/** 获取 api-select 字段的加载状态 */
|
|
61
99
|
isFieldLoading: (field: string) => boolean
|
|
62
100
|
}
|