vue3-components-plus 3.0.10

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.
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div>
3
+ <el-button type="warning" @click="openDialog({ params: { test: '222' } })">弹出框组件</el-button>
4
+ <el-button type="primary" @click="updateDialogOption">更新弹窗选项</el-button>
5
+ <el-button type="success" @click="callDialogMethod">调用弹窗方法</el-button>
6
+ <!-- 为每个打开的弹窗生成一个关闭按钮 -->
7
+ <div v-for="(instance, index) in dialogInstances" :key="instance.id" style="margin-top: 10px;">
8
+ <el-button type="danger" @click="closeDialog(instance)">关闭弹窗 {{ index + 1 }}</el-button>
9
+ </div>
10
+
11
+ <!-- 全部关闭按钮 -->
12
+ <el-button v-if="dialogInstances.length > 0" type="danger" @click="closeAllDialogs" style="margin-top: 10px;">关闭所有弹窗</el-button>
13
+ </div>
14
+ </template>
15
+ <script setup lang="ts">
16
+ import VideoDemo from '@/views/VideoDemo.vue'
17
+ import { onMounted, onUnmounted, ref } from 'vue'
18
+
19
+ // 扩展Window接口
20
+ declare global {
21
+ interface Window {
22
+ __dialogInstances: any[];
23
+ NsDialog: any;
24
+ }
25
+ }
26
+
27
+ // 保存当前打开的弹窗实例数组
28
+ const dialogInstances = ref<any[]>([])
29
+ const openIndex = ref(0);
30
+ function openDialog(data = {}) {
31
+ openIndex.value++;
32
+ if(dialogInstances.value.length === 0) {
33
+ openIndex.value = 0;
34
+ }
35
+ window.NsDialog(
36
+ {
37
+ class: 'xxx',
38
+ title: '测试',
39
+ // 任何组件添加 $emit('close') 时,会触发关闭弹出框事件
40
+ dom: VideoDemo, // 也可以通过异步方式:import("@/views/VideoDemo.vue") 和 () => import("@/views/VideoDemo.vue")
41
+ domCompleted: (domRef: any)=>{
42
+ // dom加载完成或触发函数,domRef为dom实例可以执行defineExpose暴露出的函数
43
+ console.log('组件加载完成,domRef:', domRef)
44
+ domRef?.xxx?.()
45
+ },
46
+ option: {
47
+ // dom对应的自定义组件props属性
48
+ ...data,
49
+ },
50
+ events: {
51
+ // dom组件内部自定义事件emit('btnClick', xxx)
52
+ btnClick: () => {
53
+ console.log('点击中间区域内容')
54
+ },
55
+ },
56
+ width: '800px', // 宽度, 整个弹出框的高度,非内容高度
57
+ height: '450px', // 高度, 不配置则默认为内容高度
58
+ dialogPadding: [10, 20], // 弹窗内padding
59
+ // 弹窗绝对定位
60
+ x: 250 + openIndex.value * 20,
61
+ y: 100 + openIndex.value * 20,
62
+ // 设置函数时,则有放大和还原按钮,且按返回的对象设置弹出框。(会关闭拖动功能)
63
+ // maxSize: function () {
64
+ // return { width: '100%', height: '800px', x: 0, y: 100 }
65
+ // },
66
+ modal: false, // 模态框
67
+ modalColor: 'rgb(0 21 115 / 20%)', // 遮罩层颜色
68
+ showFooter: true, // 默认显示底部按钮
69
+ immediately: false, // true立即取消弹出框, false异步请求后取消弹出框,默认false
70
+ draggable: true, // 是否可拖拽,默认false
71
+ // 底部确认按钮回调事件
72
+ confirm: async (closeFn: any, componentRef: any, footerLoading: any) => {
73
+ // 2.componentRef可以调用内部函数,前提需要defineExpose
74
+ try {
75
+ const selectRows = componentRef?.value?.getSelectedRows()
76
+ console.log('点击确认,选择数据:', selectRows)
77
+ } catch (e) {
78
+ console.log(e)
79
+ await new Promise((resolve) => setTimeout(resolve, 1000))
80
+ }
81
+ // 3.footerLoading可以控制底部loading状态
82
+ if (footerLoading) {
83
+ footerLoading.value = false
84
+ }
85
+ // 1.请求数据,再关闭
86
+ if (closeFn) {
87
+ closeFn()
88
+ }
89
+ },
90
+ close: () => {
91
+ // 关闭弹出时立即出发
92
+ console.log('点击关闭')
93
+ // 更新dialogInstances数组
94
+ updateDialogInstances()
95
+ },
96
+ closed: () => {
97
+ // 弹窗销毁时触发
98
+ console.log('完成关闭')
99
+ // 更新dialogInstances数组
100
+ updateDialogInstances()
101
+ },
102
+ // 头部+底部自定义配置
103
+ /*
104
+ // 任何组件添加 $emit('close') 时,会触发关闭弹出框事件
105
+ headerDom: xxx,
106
+ headerOption: {},
107
+ headerEvents: {},
108
+ // 任何组件添加 $emit('close') 时,会触发关闭弹出框事件
109
+ footerDom: yyy,
110
+ footerOption: {},
111
+ footerEvents: {},
112
+ // 底部按钮名称
113
+ footerTitle: {
114
+ close: "取消",
115
+ confirm: "确定",
116
+ },
117
+ */
118
+ },
119
+ true,
120
+ '#app',
121
+ ) // true为是否遮罩(非必填), '#app'为挂载点(非必填)
122
+
123
+ // 更新dialogInstances数组
124
+ updateDialogInstances()
125
+
126
+ setTimeout(()=>{
127
+ const data = window.__dialogInstances;
128
+ console.log('当前所有弹窗实例:', data)
129
+ }, 333)
130
+ }
131
+
132
+ // 更新dialogInstances数组
133
+ function updateDialogInstances() {
134
+ // 使用setTimeout确保在DOM更新后执行
135
+ setTimeout(() => {
136
+ dialogInstances.value = [...window.__dialogInstances]
137
+ }, 0)
138
+ }
139
+
140
+ // 更新弹窗选项
141
+ function updateDialogOption() {
142
+ if (dialogInstances.value.length > 0) {
143
+ // 更新最后一个弹窗的选项
144
+ const lastInstance = dialogInstances.value[dialogInstances.value.length - 1]
145
+ // 现在可以直接更新弹窗标题了
146
+ lastInstance.updateOption({
147
+ title: '更新后的标题',
148
+ params: { test: '更新后的参数' }
149
+ })
150
+ console.log('已更新弹窗标题和选项')
151
+ } else {
152
+ console.warn('没有打开的弹窗实例')
153
+ }
154
+ }
155
+
156
+ // 调用弹窗内组件的方法
157
+ function callDialogMethod() {
158
+ if (dialogInstances.value.length > 0) {
159
+ // 调用最后一个弹窗内组件的方法
160
+ const lastInstance = dialogInstances.value[dialogInstances.value.length - 1]
161
+ if (lastInstance.domRef) {
162
+ // 使用新的callMethod方法调用组件方法
163
+ // 注意:这里需要组件内部通过defineExpose暴露方法
164
+ lastInstance.callMethod('defineExpose暴露的方法名', 'arg1', 'arg2')
165
+ console.log('已调用弹窗内组件方法')
166
+ } else {
167
+ console.warn('组件引用不存在')
168
+ }
169
+ } else {
170
+ console.warn('没有打开的弹窗实例')
171
+ }
172
+ }
173
+
174
+ // 关闭指定弹窗
175
+ async function closeDialog(instance: any) {
176
+ if (instance) {
177
+ // 使用新的close方法关闭弹窗
178
+ await instance.close()
179
+ console.log('已关闭指定弹窗')
180
+ } else {
181
+ console.warn('弹窗实例不存在')
182
+ }
183
+ }
184
+
185
+ // 关闭所有弹窗
186
+ function closeAllDialogs() {
187
+ // 关闭所有弹窗
188
+ dialogInstances.value.forEach(instance => {
189
+ instance.close()
190
+ })
191
+ openIndex.value = 0;
192
+ console.log('已关闭所有弹窗')
193
+ }
194
+
195
+ onMounted(() => {
196
+ setTimeout(() => {
197
+ openDialog({ params: { test: '1' } })
198
+ }, 300)
199
+
200
+ // 初始化dialogInstances数组
201
+ updateDialogInstances()
202
+ })
203
+
204
+ onUnmounted(()=>{
205
+ window.__dialogInstances.forEach(item=>{
206
+ item.close()
207
+ })
208
+ })
209
+ </script>
210
+ <style scoped></style>
@@ -0,0 +1,337 @@
1
+ <template>
2
+ <div class="cascade-async-demo">
3
+ <h1>级联表单示例 - 异步数据加载</h1>
4
+
5
+ <el-card class="demo-card">
6
+ <template #header>
7
+ <span>产品分类级联(模拟异步加载)</span>
8
+ </template>
9
+
10
+ <DynamicFormPlus
11
+ ref="formRef"
12
+ v-model="formData"
13
+ :config="formConfig"
14
+ :col-span="12"
15
+ :gutter="16"
16
+ label-width="120px"
17
+ @submit="handleSubmit"
18
+ />
19
+ </el-card>
20
+
21
+ <el-card class="demo-card" style="margin-top: 20px">
22
+ <template #header>
23
+ <span>表单数据</span>
24
+ </template>
25
+ <pre>{{ JSON.stringify(formData, null, 2) }}</pre>
26
+ </el-card>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { ref } from 'vue'
32
+ import { ElMessage } from 'element-plus'
33
+ import { DynamicFormPlus } from '../../packages/components/DynamicFormPlus'
34
+ import type { FormConfig } from '../../packages/components/DynamicFormPlus'
35
+
36
+ // 模拟异步 API 请求
37
+ const mockApiDelay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
38
+
39
+ // 模拟获取一级分类
40
+ async function fetchCategories() {
41
+ await mockApiDelay(500)
42
+ return [
43
+ { label: '电子产品', value: 'electronics' },
44
+ { label: '服装鞋帽', value: 'clothing' },
45
+ { label: '食品饮料', value: 'food' },
46
+ ]
47
+ }
48
+
49
+ // 模拟获取二级分类
50
+ async function fetchSubCategories(categoryId: string) {
51
+ await mockApiDelay(500)
52
+ const subCategoriesMap: Record<string, Array<{ label: string; value: string }>> = {
53
+ electronics: [
54
+ { label: '手机', value: 'phone' },
55
+ { label: '电脑', value: 'computer' },
56
+ { label: '相机', value: 'camera' },
57
+ ],
58
+ clothing: [
59
+ { label: '男装', value: 'mens' },
60
+ { label: '女装', value: 'womens' },
61
+ { label: '童装', value: 'kids' },
62
+ ],
63
+ food: [
64
+ { label: '零食', value: 'snacks' },
65
+ { label: '饮料', value: 'drinks' },
66
+ { label: '水果', value: 'fruits' },
67
+ ],
68
+ }
69
+ return subCategoriesMap[categoryId] || []
70
+ }
71
+
72
+ // 模拟获取品牌列表
73
+ async function fetchBrands(subCategoryId: string) {
74
+ await mockApiDelay(500)
75
+ const brandsMap: Record<string, Array<{ label: string; value: string }>> = {
76
+ phone: [
77
+ { label: '苹果', value: 'apple' },
78
+ { label: '华为', value: 'huawei' },
79
+ { label: '小米', value: 'xiaomi' },
80
+ ],
81
+ computer: [
82
+ { label: '联想', value: 'lenovo' },
83
+ { label: '戴尔', value: 'dell' },
84
+ { label: '惠普', value: 'hp' },
85
+ ],
86
+ camera: [
87
+ { label: '佳能', value: 'canon' },
88
+ { label: '尼康', value: 'nikon' },
89
+ { label: '索尼', value: 'sony' },
90
+ ],
91
+ mens: [
92
+ { label: '耐克', value: 'nike' },
93
+ { label: '阿迪达斯', value: 'adidas' },
94
+ { label: '优衣库', value: 'uniqlo' },
95
+ ],
96
+ womens: [
97
+ { label: 'ZARA', value: 'zara' },
98
+ { label: 'H&M', value: 'hm' },
99
+ { label: '优衣库', value: 'uniqlo' },
100
+ ],
101
+ kids: [
102
+ { label: '巴拉巴拉', value: 'balabala' },
103
+ { label: '安奈儿', value: 'annil' },
104
+ { label: '迪士尼', value: 'disney' },
105
+ ],
106
+ snacks: [
107
+ { label: '三只松鼠', value: 'squirrel' },
108
+ { label: '良品铺子', value: 'bestore' },
109
+ { label: '百草味', value: 'herb' },
110
+ ],
111
+ drinks: [
112
+ { label: '可口可乐', value: 'cocacola' },
113
+ { label: '百事可乐', value: 'pepsi' },
114
+ { label: '农夫山泉', value: 'nongfu' },
115
+ ],
116
+ fruits: [
117
+ { label: '佳沛', value: 'zespri' },
118
+ { label: '都乐', value: 'dole' },
119
+ { label: '新奇士', value: 'sunkist' },
120
+ ],
121
+ }
122
+ return brandsMap[subCategoryId] || []
123
+ }
124
+
125
+ // 表单数据
126
+ const formData = ref({
127
+ category: '',
128
+ subCategory: '',
129
+ brand: '',
130
+ productName: '',
131
+ price: 0,
132
+ })
133
+
134
+ // 表单配置
135
+ const formConfig: FormConfig = {
136
+ items: [
137
+ {
138
+ prop: 'category',
139
+ label: '一级分类',
140
+ component: 'el-select',
141
+ componentProps: {
142
+ placeholder: '请选择分类',
143
+ clearable: true,
144
+ loading: false,
145
+ },
146
+ rules: [{ required: true, message: '请选择一级分类', trigger: 'change' }],
147
+ span: 12,
148
+ },
149
+ {
150
+ prop: 'subCategory',
151
+ label: '二级分类',
152
+ component: 'el-select',
153
+ componentProps: {
154
+ placeholder: '请先选择一级分类',
155
+ clearable: true,
156
+ disabled: true,
157
+ loading: false,
158
+ },
159
+ rules: [{ required: true, message: '请选择二级分类', trigger: 'change' }],
160
+ span: 12,
161
+ // 级联配置:依赖一级分类
162
+ cascade: {
163
+ dependOn: 'category',
164
+ clearFields: ['subCategory', 'brand'], // 一级分类变化时清除二级分类和品牌
165
+ handler: async ({ dependValues, setComponentProps, clearValue }) => {
166
+ const category = dependValues.category
167
+
168
+ if (!category) {
169
+ setComponentProps({
170
+ placeholder: '请先选择一级分类',
171
+ disabled: true,
172
+ loading: false,
173
+ options: [],
174
+ })
175
+ clearValue()
176
+ return
177
+ }
178
+
179
+ // 显示加载状态
180
+ setComponentProps({
181
+ placeholder: '加载中...',
182
+ disabled: true,
183
+ loading: true,
184
+ options: [],
185
+ })
186
+
187
+ try {
188
+ // 异步加载二级分类数据
189
+ const subCategories = await fetchSubCategories(category)
190
+
191
+ setComponentProps({
192
+ placeholder: '请选择二级分类',
193
+ disabled: false,
194
+ loading: false,
195
+ options: subCategories,
196
+ })
197
+ } catch (error) {
198
+ console.error('加载二级分类失败:', error)
199
+ setComponentProps({
200
+ placeholder: '加载失败,请重试',
201
+ disabled: true,
202
+ loading: false,
203
+ options: [],
204
+ })
205
+ }
206
+ },
207
+ },
208
+ },
209
+ {
210
+ prop: 'brand',
211
+ label: '品牌',
212
+ component: 'el-select',
213
+ componentProps: {
214
+ placeholder: '请先选择二级分类',
215
+ clearable: true,
216
+ disabled: true,
217
+ loading: false,
218
+ },
219
+ rules: [{ required: true, message: '请选择品牌', trigger: 'change' }],
220
+ span: 12,
221
+ // 级联配置:依赖二级分类
222
+ cascade: {
223
+ dependOn: 'subCategory',
224
+ clearFields: ['brand'], // 二级分类变化时清除品牌
225
+ handler: async ({ dependValues, setComponentProps, clearValue }) => {
226
+ const subCategory = dependValues.subCategory
227
+
228
+ if (!subCategory) {
229
+ setComponentProps({
230
+ placeholder: '请先选择二级分类',
231
+ disabled: true,
232
+ loading: false,
233
+ options: [],
234
+ })
235
+ clearValue()
236
+ return
237
+ }
238
+
239
+ // 显示加载状态
240
+ setComponentProps({
241
+ placeholder: '加载中...',
242
+ disabled: true,
243
+ loading: true,
244
+ options: [],
245
+ })
246
+
247
+ try {
248
+ // 异步加载品牌数据
249
+ const brands = await fetchBrands(subCategory)
250
+
251
+ setComponentProps({
252
+ placeholder: '请选择品牌',
253
+ disabled: false,
254
+ loading: false,
255
+ options: brands,
256
+ })
257
+ } catch (error) {
258
+ console.error('加载品牌失败:', error)
259
+ setComponentProps({
260
+ placeholder: '加载失败,请重试',
261
+ disabled: true,
262
+ loading: false,
263
+ options: [],
264
+ })
265
+ }
266
+ },
267
+ },
268
+ },
269
+ {
270
+ prop: 'productName',
271
+ label: '产品名称',
272
+ component: 'el-input',
273
+ componentProps: {
274
+ placeholder: '请输入产品名称',
275
+ clearable: true,
276
+ },
277
+ rules: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
278
+ span: 12,
279
+ },
280
+ {
281
+ prop: 'price',
282
+ label: '价格',
283
+ component: 'el-input-number',
284
+ componentProps: {
285
+ min: 0,
286
+ precision: 2,
287
+ controlsPosition: 'right',
288
+ },
289
+ rules: [{ required: true, message: '请输入价格', trigger: 'blur' }],
290
+ span: 12,
291
+ },
292
+ ],
293
+ }
294
+
295
+ // 初始化时加载一级分类
296
+ ;(async () => {
297
+ try {
298
+ const categories = await fetchCategories()
299
+ formConfig.items[0].componentProps!.options = categories
300
+ } catch (error) {
301
+ console.error('加载一级分类失败:', error)
302
+ ElMessage.error('加载分类数据失败')
303
+ }
304
+ })()
305
+
306
+ // 表单引用
307
+ const formRef = ref()
308
+
309
+ // 处理提交
310
+ function handleSubmit(data: Record<string, any>) {
311
+ console.log('表单提交:', data)
312
+ ElMessage.success('表单提交成功!')
313
+ }
314
+ </script>
315
+
316
+ <style scoped lang="scss">
317
+ .cascade-async-demo {
318
+ padding: 20px;
319
+
320
+ h1 {
321
+ margin-bottom: 20px;
322
+ font-size: 24px;
323
+ font-weight: bold;
324
+ }
325
+
326
+ .demo-card {
327
+ margin-bottom: 20px;
328
+ }
329
+
330
+ pre {
331
+ background-color: #f5f5f5;
332
+ padding: 12px;
333
+ border-radius: 4px;
334
+ overflow-x: auto;
335
+ }
336
+ }
337
+ </style>