ylwl-cpscoms 1.0.0
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 +15 -0
- package/es/SlAlert/index.vue.js +58 -0
- package/es/SlAlert/index.vue2.js +37 -0
- package/es/SlAlert/index.vue3.js +6 -0
- package/es/SlDescriptions/index.vue.js +55 -0
- package/es/SlDescriptions/index.vue2.js +57 -0
- package/es/SlDescriptions/index.vue3.js +6 -0
- package/es/SlDescriptions/renderOptions.vue.js +25 -0
- package/es/SlDescriptions/renderOptions.vue2.js +31 -0
- package/es/SlDialog/dialogPlus.js +159 -0
- package/es/SlDialog/index.js +202 -0
- package/es/SlDrawer/index.js +54 -0
- package/es/SlForm/index.vue.js +26 -0
- package/es/SlForm/index.vue2.js +433 -0
- package/es/SlForm/index.vue3.js +6 -0
- package/es/SlForm/mixinRender.js +239 -0
- package/es/SlForm/otherItem/titleItem.vue.js +39 -0
- package/es/SlForm/otherItem/titleItem.vue2.js +20 -0
- package/es/SlForm/otherItem/titleItem.vue3.js +6 -0
- package/es/SlGuide/index.vue.js +38 -0
- package/es/SlGuide/index.vue2.js +133 -0
- package/es/SlGuide/index.vue3.js +6 -0
- package/es/SlGuide/index.vue4.js +6 -0
- package/es/SlMessageBox/index.js +46 -0
- package/es/SlPage/index.vue.js +147 -0
- package/es/SlPage/index.vue2.js +312 -0
- package/es/SlPage/index.vue3.js +6 -0
- package/es/SlTable/components/colSetting.vue.js +94 -0
- package/es/SlTable/components/colSetting.vue2.js +66 -0
- package/es/SlTable/components/colSetting.vue3.js +6 -0
- package/es/SlTable/index.vue.js +171 -0
- package/es/SlTable/index.vue2.js +390 -0
- package/es/SlTable/index.vue3.js +6 -0
- package/es/SlTitle/index.vue.js +41 -0
- package/es/SlTitle/index.vue2.js +26 -0
- package/es/SlTitle/index.vue3.js +6 -0
- package/es/_virtual/_rollupPluginBabelHelpers.js +247 -0
- package/es/index.js +41 -0
- package/es/node_modules/shepherd.js/dist/css/shepherd.css.js +7 -0
- package/es/node_modules/style-inject/dist/style-inject.es.js +28 -0
- package/es/node_modules/vue-runtime-helpers/dist/normalize-component.js +76 -0
- package/es/utils/index.js +51 -0
- package/package.json +106 -0
- package/src/SlAlert/SlAlert.stories.js +108 -0
- package/src/SlAlert/index.vue +54 -0
- package/src/SlAlert/remark.md +16 -0
- package/src/SlDescriptions/SlDescriptions.stories.js +119 -0
- package/src/SlDescriptions/index.vue +60 -0
- package/src/SlDescriptions/renderOptions.vue +27 -0
- package/src/SlDialog/README-PLUS.md +74 -0
- package/src/SlDialog/README.md +114 -0
- package/src/SlDialog/dialogPlus.js +160 -0
- package/src/SlDialog/index.js +170 -0
- package/src/SlDrawer/SlDrawer.stories.js +154 -0
- package/src/SlDrawer/index.js +62 -0
- package/src/SlForm/SlForm.stories.js +120 -0
- package/src/SlForm/index.css +141 -0
- package/src/SlForm/index.vue +365 -0
- package/src/SlForm/mixinRender.js +228 -0
- package/src/SlForm/otherItem/titleItem.vue +31 -0
- package/src/SlForm/remark.md +607 -0
- package/src/SlGuide/SlGuide.stories.js +100 -0
- package/src/SlGuide/index.vue +166 -0
- package/src/SlGuide/remark.md +90 -0
- package/src/SlMessageBox/index.js +35 -0
- package/src/SlPage/README.md +515 -0
- package/src/SlPage/SlPage.stories.js +125 -0
- package/src/SlPage/index.css +38 -0
- package/src/SlPage/index.vue +266 -0
- package/src/SlPage/remark.md +283 -0
- package/src/SlTable/SlTable.stories.js +118 -0
- package/src/SlTable/components/colSetting.vue +86 -0
- package/src/SlTable/index.vue +541 -0
- package/src/SlTitle/SlTitle.stories.js +98 -0
- package/src/SlTitle/index.vue +49 -0
- package/src/global.css +5 -0
- package/src/index.js +47 -0
- package/src/store/index.js +20 -0
- package/src/utils/index.js +47 -0
- package/src/utils/tableConfig.js +33 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<div class="slMessage">
|
|
4
|
+
<div class="message-box">
|
|
5
|
+
<div class="message-title">
|
|
6
|
+
<div><i class="el-icon-warning"></i>
|
|
7
|
+
温馨提示
|
|
8
|
+
<el-button size="mini" type="text" class="toggle-button" @click="toggleMessage">
|
|
9
|
+
{{ isMessageVisible ? '收起' : '展开' }}
|
|
10
|
+
<i :class="isMessageVisible ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
|
|
11
|
+
</el-button>
|
|
12
|
+
</div>
|
|
13
|
+
<slot name="operate"></slot>
|
|
14
|
+
</div>
|
|
15
|
+
<div v-if="isMessageVisible" class="message-row" >
|
|
16
|
+
<slot name="message"></slot>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
<script>
|
|
22
|
+
import { isMobileDevice } from '../utils'
|
|
23
|
+
export default {
|
|
24
|
+
data() {
|
|
25
|
+
return {
|
|
26
|
+
isMessageVisible: !isMobileDevice()
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
methods: {
|
|
30
|
+
toggleMessage() {
|
|
31
|
+
this.isMessageVisible = !this.isMessageVisible
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
<style scoped>
|
|
37
|
+
/* .slMessage { */
|
|
38
|
+
/* padding: 10px 20px 0px 20px; */
|
|
39
|
+
/* } */
|
|
40
|
+
.message-box {
|
|
41
|
+
background: #fdf6ec;
|
|
42
|
+
padding: 5px 10px 10px;
|
|
43
|
+
}
|
|
44
|
+
.message-title {
|
|
45
|
+
color: #f59a23d8;
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
align-items: center;
|
|
49
|
+
}
|
|
50
|
+
.message-row {
|
|
51
|
+
font-size: 12px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<sl-alert>
|
|
4
|
+
<template slot="message">
|
|
5
|
+
1、删除后,该账号自动销号,此操作无法恢复'
|
|
6
|
+
</template>
|
|
7
|
+
</sl-alert>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
<script>
|
|
11
|
+
import SlAlert from '@/components/global/SlAlert'
|
|
12
|
+
export default {
|
|
13
|
+
components: {
|
|
14
|
+
SlAlert,
|
|
15
|
+
},
|
|
16
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import SlDescriptions from './index.vue'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/SlDescriptions',
|
|
5
|
+
component: SlDescriptions,
|
|
6
|
+
parameters: {
|
|
7
|
+
docs: {
|
|
8
|
+
description: {
|
|
9
|
+
component: '描述列表组件,基于 el-descriptions 封装。通过 renderData 传入模型和数据,自动渲染描述项。支持 format 格式化、template 自定义插槽、isShow 条件显示。'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const codeBlock = (code) => `
|
|
16
|
+
<div style="margin-top:24px;border-top:1px solid #eee;padding-top:16px">
|
|
17
|
+
<p style="font-size:14px;font-weight:600;color:#666;margin-bottom:8px;">使用代码:</p>
|
|
18
|
+
<pre style="background:#f5f5f5;padding:16px;border-radius:4px;overflow-x:auto;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-all"><code>${escapeHtml(code.trim())}</code></pre>
|
|
19
|
+
</div>`
|
|
20
|
+
|
|
21
|
+
function escapeHtml(str) {
|
|
22
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---- 基础用法 ----
|
|
26
|
+
const defaultCode = `<!-- 使用方式 -->
|
|
27
|
+
<sl-descriptions :render-data="renderData" />
|
|
28
|
+
|
|
29
|
+
<!-- renderData 结构 -->
|
|
30
|
+
renderData: {
|
|
31
|
+
data: { name: '张三', age: 25, status: 1 },
|
|
32
|
+
model: [
|
|
33
|
+
{ prop: 'name', label: '姓名' },
|
|
34
|
+
{ prop: 'age', label: '年龄' },
|
|
35
|
+
{ prop: 'status', label: '状态' }
|
|
36
|
+
]
|
|
37
|
+
}`
|
|
38
|
+
|
|
39
|
+
export const Default = () => ({
|
|
40
|
+
components: { SlDescriptions },
|
|
41
|
+
data() {
|
|
42
|
+
return {
|
|
43
|
+
renderData: {
|
|
44
|
+
data: { name: '张三', age: 25, status: '启用', createTime: '2024-01-15' },
|
|
45
|
+
model: [
|
|
46
|
+
{ prop: 'name', label: '姓名' },
|
|
47
|
+
{ prop: 'age', label: '年龄' },
|
|
48
|
+
{ prop: 'status', label: '状态' },
|
|
49
|
+
{ prop: 'createTime', label: '创建时间' }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
template: `
|
|
55
|
+
<div>
|
|
56
|
+
<sl-descriptions :render-data="renderData" />
|
|
57
|
+
${codeBlock(defaultCode)}
|
|
58
|
+
</div>`
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
Default.storyName = '基础用法'
|
|
62
|
+
|
|
63
|
+
// ---- 自定义列数 ----
|
|
64
|
+
const columnCode = `<!-- 自定义列数和尺寸 -->
|
|
65
|
+
<sl-descriptions :render-data="renderData" :el-table-props="{ column: 3, size: 'small' }" />`
|
|
66
|
+
|
|
67
|
+
export const CustomColumn = () => ({
|
|
68
|
+
components: { SlDescriptions },
|
|
69
|
+
data() {
|
|
70
|
+
return {
|
|
71
|
+
renderData: {
|
|
72
|
+
data: { name: '张三', age: 25, status: '启用', dept: '研发部', role: '前端开发', email: 'zhangsan@example.com' },
|
|
73
|
+
model: [
|
|
74
|
+
{ prop: 'name', label: '姓名' },
|
|
75
|
+
{ prop: 'age', label: '年龄' },
|
|
76
|
+
{ prop: 'status', label: '状态' },
|
|
77
|
+
{ prop: 'dept', label: '部门' },
|
|
78
|
+
{ prop: 'role', label: '角色' },
|
|
79
|
+
{ prop: 'email', label: '邮箱' }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
template: `
|
|
85
|
+
<div>
|
|
86
|
+
<sl-descriptions :render-data="renderData" :el-table-props="{ column: 3, size: 'small' }" />
|
|
87
|
+
${codeBlock(columnCode)}
|
|
88
|
+
</div>`
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
CustomColumn.storyName = '自定义列数'
|
|
92
|
+
|
|
93
|
+
// ---- 空值处理 ----
|
|
94
|
+
const emptyCode = `<!-- 空值自动显示为 "-" -->
|
|
95
|
+
<sl-descriptions :render-data="renderData" />`
|
|
96
|
+
|
|
97
|
+
export const EmptyValue = () => ({
|
|
98
|
+
components: { SlDescriptions },
|
|
99
|
+
data() {
|
|
100
|
+
return {
|
|
101
|
+
renderData: {
|
|
102
|
+
data: { name: '李四', age: 0, status: '', phone: null },
|
|
103
|
+
model: [
|
|
104
|
+
{ prop: 'name', label: '姓名' },
|
|
105
|
+
{ prop: 'age', label: '年龄(0 正常显示)' },
|
|
106
|
+
{ prop: 'status', label: '状态(空值显示 -)' },
|
|
107
|
+
{ prop: 'phone', label: '电话(null 显示 -)' }
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
template: `
|
|
113
|
+
<div>
|
|
114
|
+
<sl-descriptions :render-data="renderData" :el-table-props="{ size: 'small' }" />
|
|
115
|
+
${codeBlock(emptyCode)}
|
|
116
|
+
</div>`
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
EmptyValue.storyName = '空值处理'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<el-descriptions v-bind="elTableProps" size="small" border>
|
|
4
|
+
<el-descriptions-item v-for="item in renderData.model" v-if="isShow(item)" :key="item.name" >
|
|
5
|
+
<template slot="label">
|
|
6
|
+
{{ item.label }}
|
|
7
|
+
</template>
|
|
8
|
+
<span v-if="!item.format && !item.template"> {{ renderData.data[item.prop] || renderData.data[item.prop] === 0 ? renderData.data[item.prop] : '-' }}</span>
|
|
9
|
+
<renderOptions v-if="item.format && renderData.data" :render-options="item.format" :data="renderData.data" />
|
|
10
|
+
<slot v-if="item.template" :name="item.template" :data="renderData.data" />
|
|
11
|
+
</el-descriptions-item>
|
|
12
|
+
</el-descriptions>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
name: 'SlDescriptions',
|
|
20
|
+
components: {
|
|
21
|
+
renderOptions: () => import('./renderOptions.vue')
|
|
22
|
+
},
|
|
23
|
+
props: {
|
|
24
|
+
renderData: {
|
|
25
|
+
type: Object,
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
elTableProps: {
|
|
29
|
+
type: Object,
|
|
30
|
+
default: () => {
|
|
31
|
+
return {
|
|
32
|
+
size: 'small',
|
|
33
|
+
column: 2
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
data() {
|
|
39
|
+
return {}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
methods: {
|
|
43
|
+
isShow(item) {
|
|
44
|
+
if (item.isShow !== undefined) {
|
|
45
|
+
if (typeof item.isShow === 'function') {
|
|
46
|
+
return item.isShow(this.renderData.data)
|
|
47
|
+
} else {
|
|
48
|
+
return item.isShow
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<style lang="css" scoped>
|
|
59
|
+
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export default {
|
|
3
|
+
name: 'EcSystemsRenderOptions',
|
|
4
|
+
props: {
|
|
5
|
+
renderOptions: {
|
|
6
|
+
type: Function,
|
|
7
|
+
default: () => ({})
|
|
8
|
+
},
|
|
9
|
+
data: {
|
|
10
|
+
type: Object,
|
|
11
|
+
default: () => ({})
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
render(h) {
|
|
15
|
+
if (this._props.renderOptions) {
|
|
16
|
+
const callback = this._props.renderOptions(h, this._props.data)
|
|
17
|
+
if (typeof callback === 'string') {
|
|
18
|
+
return h('span', callback)
|
|
19
|
+
} else {
|
|
20
|
+
return callback
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# createCommandDialog_v5 使用说明(Portal 传送门版)
|
|
2
|
+
|
|
3
|
+
这是彻底解决“业务组件内聚”与“复用 el-dialog 原生插槽”冲突的最优雅方案。
|
|
4
|
+
|
|
5
|
+
我们在底层实现了一个类似于 Vue 3 `<Teleport>` (或 portal-vue) 的全局辅助组件 `<dialog-footer>`。你只需要在你的业务组件里用这个标签把按钮包起来,它就会自动“飞”到外层弹窗的右下角。
|
|
6
|
+
|
|
7
|
+
## 1. 快速开始
|
|
8
|
+
|
|
9
|
+
### 引入和调用
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
import { createCommandDialog } from './createCommandDialog_v5'
|
|
14
|
+
import MyCustomForm from './MyCustomForm.vue'
|
|
15
|
+
|
|
16
|
+
createCommandDialog({
|
|
17
|
+
title: '编辑用户信息',
|
|
18
|
+
contentComponent: MyCustomForm,
|
|
19
|
+
contentProps: { userId: 123 }
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. 业务组件怎么写? (`MyCustomForm.vue`)
|
|
24
|
+
|
|
25
|
+
在你的组件模板中,正常编写主体内容。在你想写底部按钮的地方,使用 `<dialog-footer>` 标签包裹它们即可!
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<template>
|
|
29
|
+
<div class="my-custom-form">
|
|
30
|
+
<!-- 这里是弹窗的主体内容 -->
|
|
31
|
+
<el-form label-width="80px">
|
|
32
|
+
<el-form-item label="用户名">
|
|
33
|
+
<el-input v-model="form.name"></el-input>
|
|
34
|
+
</el-form-item>
|
|
35
|
+
</el-form>
|
|
36
|
+
|
|
37
|
+
<!-- 🌟 只需要用这个标签包裹你的按钮 🌟 -->
|
|
38
|
+
<!-- 这些按钮会自动跑到 el-dialog 的原生 footer 区域 -->
|
|
39
|
+
<dialog-footer>
|
|
40
|
+
<el-button @click="handleCancel">取 消</el-button>
|
|
41
|
+
<el-button type="warning" @click="handleDraft">暂 存</el-button>
|
|
42
|
+
<el-button type="primary" :loading="loading" @click="handleSubmit">确 定</el-button>
|
|
43
|
+
</dialog-footer>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
export default {
|
|
49
|
+
data() {
|
|
50
|
+
return {
|
|
51
|
+
loading: false,
|
|
52
|
+
form: { name: '' }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
methods: {
|
|
56
|
+
handleCancel() {
|
|
57
|
+
this.$emit('close') // 关闭弹窗
|
|
58
|
+
},
|
|
59
|
+
handleDraft() {
|
|
60
|
+
// 执行暂存逻辑...
|
|
61
|
+
},
|
|
62
|
+
async handleSubmit() {
|
|
63
|
+
this.loading = true
|
|
64
|
+
// ... 发送请求 ...
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
this.loading = false
|
|
67
|
+
this.$emit('confirm') // 触发外层 onConfirm 并关闭
|
|
68
|
+
}, 1000)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
```
|
|
74
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# createCommandDialog 组件使用说明
|
|
2
|
+
|
|
3
|
+
`createCommandDialog` 是一个基于 Vue 2 和 Element UI 封装的函数式/编程式弹窗工具。它允许你通过函数调用的方式,在任意地方动态创建一个包含自定义内容的弹窗,无需在模板中预先声明 `<el-dialog>`。
|
|
4
|
+
|
|
5
|
+
## 1. 快速开始
|
|
6
|
+
|
|
7
|
+
### 引入方法
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { createCommandDialog } from './path/to/createCommandDialog' // 替换为实际路径
|
|
11
|
+
import MyCustomForm from './MyCustomForm.vue'
|
|
12
|
+
|
|
13
|
+
// 调用弹窗
|
|
14
|
+
createCommandDialog({
|
|
15
|
+
title: '编辑数据',
|
|
16
|
+
contentComponent: MyCustomForm,
|
|
17
|
+
contentProps: { id: 123 }, // 传递给 MyCustomForm 的 props
|
|
18
|
+
onConfirm: () => {
|
|
19
|
+
console.log('用户点击了确定且内部验证通过')
|
|
20
|
+
},
|
|
21
|
+
onCancel: () => {
|
|
22
|
+
console.log('用户点击了取消')
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 2. 配置参数 (Options)
|
|
28
|
+
|
|
29
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
30
|
+
| -------------------- | ------------ | -------- | ------------------------------------------------------------ |
|
|
31
|
+
| `title` | String | `'提示'` | 弹窗标题 |
|
|
32
|
+
| `width` | String | `'50%'` | 弹窗宽度 (移动端自动适应为 '90%') |
|
|
33
|
+
| `specialWidth` | String | `''` | 特殊宽度,若设置则优先使用此宽度 |
|
|
34
|
+
| `closeOnClickModal` | Boolean | `false` | 是否可以通过点击 modal 遮罩层关闭 Dialog |
|
|
35
|
+
| `contentComponent` | VueComponent | `null` | **必填**。弹窗主体区域要渲染的 Vue 自定义组件 |
|
|
36
|
+
| `contentProps` | Object | `{}` | 传递给 `contentComponent` 的属性 (props) |
|
|
37
|
+
| `footerBtns` | Array | `[]` | 自定义底部按钮。格式: `[{ type: 'primary', label: '按钮名', value: 'methodName' }]` |
|
|
38
|
+
| `cancelButtonText` | String | `'取消'` | 默认取消按钮的文本 |
|
|
39
|
+
| `confirmButtonText` | String | `'确定'` | 默认确定按钮的文本 |
|
|
40
|
+
| `isShowBtn` | Boolean | `true` | 是否显示底部按钮区域 |
|
|
41
|
+
| `showClose` | Boolean | `true` | 是否显示右上角的关闭图标 |
|
|
42
|
+
| `closeOnPressEscape` | Boolean | `false` | 是否可以通过按下 ESC 关闭 Dialog |
|
|
43
|
+
| `customClass` | String | `''` | Dialog 的自定义类名 |
|
|
44
|
+
| `onConfirm` | Function | - | 点击确定按钮且验证通过后的回调函数 |
|
|
45
|
+
| `onCancel` | Function | - | 点击取消按钮后的回调函数 |
|
|
46
|
+
| `onClose` | Function | - | 弹窗关闭后的回调函数 |
|
|
47
|
+
|
|
48
|
+
## 3. 子组件 (`contentComponent`) 编写规范
|
|
49
|
+
|
|
50
|
+
为了让弹窗的底部按钮与你的自定义组件产生交互,你的自定义组件(例如 `MyCustomForm.vue`)需要遵循以下约定:
|
|
51
|
+
|
|
52
|
+
### 3.1 拦截“确定”按钮操作
|
|
53
|
+
|
|
54
|
+
如果使用的是默认底部按钮,当用户点击“确定”时,组件会尝试调用你子组件内的 `confirm` 方法:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
export default {
|
|
58
|
+
props: ['id'], // 接收 contentProps 传入的数据
|
|
59
|
+
methods: {
|
|
60
|
+
// 方法名必须为 confirm
|
|
61
|
+
async confirm() {
|
|
62
|
+
// 1. 进行表单校验
|
|
63
|
+
const valid = await this.$refs.form.validate().catch(() => false)
|
|
64
|
+
if (!valid) {
|
|
65
|
+
// 返回 false,弹窗【不会】关闭
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. 提交数据或执行业务逻辑
|
|
70
|
+
await submitData({ id: this.id, ...this.formData })
|
|
71
|
+
|
|
72
|
+
// 不返回 false 或者返回 true,弹窗将会关闭,并触发外部的 onConfirm 回调
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3.2 自定义底部按钮 (`footerBtns`)
|
|
80
|
+
|
|
81
|
+
如果你通过 `footerBtns` 传入了自定义按钮,例如:
|
|
82
|
+
`[{ label: '暂存', value: 'handleSaveDraft', type: 'warning' }]`
|
|
83
|
+
|
|
84
|
+
当点击该按钮时,工具会尝试调用子组件的 `handleSaveDraft` 方法(即 `value` 对应的值):
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
export default {
|
|
88
|
+
methods: {
|
|
89
|
+
async handleSaveDraft() {
|
|
90
|
+
// 执行暂存逻辑
|
|
91
|
+
// 同样,如果 return false,则弹窗不会关闭;否则自动关闭
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3.3 主动控制弹窗
|
|
98
|
+
|
|
99
|
+
你的组件内部可以通过触发特定事件来主动控制外层弹窗:
|
|
100
|
+
|
|
101
|
+
- 主动触发确定并关闭:`this.$emit('onConfirm')`
|
|
102
|
+
- 主动触发关闭:`this.$emit('onClose')`
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## 优化建议:
|
|
107
|
+
|
|
108
|
+
### 1. 缺乏按钮 Loading 状态交互体验
|
|
109
|
+
|
|
110
|
+
问题描述 :由于 confirm() 是异步的(可能包含网络请求),点击“确定”后如果接口响应慢,按钮没有 loading 动画。 优化建议 :可以在 data() 中增加一个 confirmLoading: false 状态。在 handleConfirm 执行前将其设为 true ,执行完毕后 false 。并在 render 函数中把这个状态绑定给确定按钮的 loading 属性,防止用户重复点击提交。
|
|
111
|
+
|
|
112
|
+
**总结:不处理,统一由页面组件级做处理**
|
|
113
|
+
|
|
114
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import Vue from 'vue'
|
|
2
|
+
import { isMobileDevice } from '../utils'
|
|
3
|
+
|
|
4
|
+
// 🌟 核心魔法:注册一个全局的辅助组件,充当“传送门”(Portal)
|
|
5
|
+
if (!Vue.component('dialog-footer')) {
|
|
6
|
+
Vue.component('dialog-footer', {
|
|
7
|
+
inject: {
|
|
8
|
+
dialogCtx: { default: null }
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
mounted() {
|
|
12
|
+
if (this.dialogCtx) {
|
|
13
|
+
this.dialogCtx.setFooterVNodes(this.$slots.default)
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
updated() {
|
|
17
|
+
if (this.dialogCtx) {
|
|
18
|
+
this.dialogCtx.setFooterVNodes(this.$slots.default)
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
destroyed() {
|
|
22
|
+
if (this.dialogCtx) {
|
|
23
|
+
this.dialogCtx.setFooterVNodes(null)
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
render(h) {
|
|
27
|
+
return h('div', { style: { display: 'none' }, class: 'dialog-footer-portal' })
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createCommandDialogPlus(options) {
|
|
33
|
+
const defaultOptions = {
|
|
34
|
+
title: '提示',
|
|
35
|
+
width: '50%',
|
|
36
|
+
specialWidth: '',
|
|
37
|
+
closeOnClickModal: false,
|
|
38
|
+
contentComponent: null,
|
|
39
|
+
contentProps: {},
|
|
40
|
+
showClose: true,
|
|
41
|
+
closeOnPressEscape: false,
|
|
42
|
+
customClass: ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mergedOptions = { ...defaultOptions, ...options }
|
|
46
|
+
|
|
47
|
+
const Constructor = Vue.extend({
|
|
48
|
+
provide() {
|
|
49
|
+
return {
|
|
50
|
+
dialogCtx: this
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
visible: false,
|
|
56
|
+
footerVNodes: null
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
created() {
|
|
60
|
+
Object.assign(this, mergedOptions)
|
|
61
|
+
},
|
|
62
|
+
mounted() {
|
|
63
|
+
Vue.nextTick(() => {
|
|
64
|
+
this.visible = true
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
methods: {
|
|
68
|
+
handleBeforeClose(done) {
|
|
69
|
+
if (typeof done === 'function') {
|
|
70
|
+
done() // 允许 el-dialog 执行关闭动画
|
|
71
|
+
} else {
|
|
72
|
+
this.visible = false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
handleClose() {
|
|
76
|
+
if (this._hasClosed) return
|
|
77
|
+
this._hasClosed = true
|
|
78
|
+
console.log('你肯定会执行吗啊')
|
|
79
|
+
if (typeof this.onClose === 'function') {
|
|
80
|
+
this.onClose(this)
|
|
81
|
+
}
|
|
82
|
+
this.$once('closed', () => {
|
|
83
|
+
this.$destroy()
|
|
84
|
+
if (this.$el && this.$el.parentNode) {
|
|
85
|
+
this.$el.parentNode.removeChild(this.$el)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
// 暴露给业务组件的关闭方法
|
|
90
|
+
closeDialog() {
|
|
91
|
+
this.visible = false
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// 兼容以前的 confirm 逻辑(可选保留,为了向后兼容外层传入 onConfirm)
|
|
95
|
+
confirmDialog() {
|
|
96
|
+
if (typeof this.onConfirm === 'function') {
|
|
97
|
+
this.onConfirm()
|
|
98
|
+
}
|
|
99
|
+
this.visible = false // 同样只修改 visible
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// 供 <dialog-footer> 调用的方法
|
|
103
|
+
setFooterVNodes(vnodes) {
|
|
104
|
+
this.footerVNodes = vnodes
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
render(h) {
|
|
109
|
+
// 渲染主体业务组件
|
|
110
|
+
const dialogChildren = [
|
|
111
|
+
h(this.contentComponent, {
|
|
112
|
+
props: this.contentProps,
|
|
113
|
+
ref: 'contentComponent',
|
|
114
|
+
on: {
|
|
115
|
+
// 业务组件可以通过 this.$emit('close') 主动关闭弹窗
|
|
116
|
+
close: this.closeDialog,
|
|
117
|
+
// 业务组件可以通过 this.$emit('confirm') 触发外层回调并关闭弹窗
|
|
118
|
+
confirm: this.confirmDialog
|
|
119
|
+
// 【新增】:允许业务组件透传任意自定义事件到外层
|
|
120
|
+
// 如果你的业务组件需要向外抛出自定义事件(如 'save-draft'),
|
|
121
|
+
// 可以在调用 createCommandDialog 时通过 on 属性传入监听器。
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
if (this.footerVNodes) {
|
|
127
|
+
dialogChildren.push(h('template', { slot: 'footer' }, this.footerVNodes))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return h(
|
|
131
|
+
'el-dialog',
|
|
132
|
+
{
|
|
133
|
+
props: {
|
|
134
|
+
title: this.title,
|
|
135
|
+
visible: this.visible,
|
|
136
|
+
width: isMobileDevice() ? '90%' : this.specialWidth ? this.specialWidth : this.width === '30%' ? '30%' : '50%',
|
|
137
|
+
'close-on-click-modal': this.closeOnClickModal,
|
|
138
|
+
showClose: this.showClose,
|
|
139
|
+
'close-on-press-escape': this.closeOnPressEscape,
|
|
140
|
+
customClass: this.customClass
|
|
141
|
+
},
|
|
142
|
+
on: {
|
|
143
|
+
'update:visible': (val) => {
|
|
144
|
+
this.visible = val
|
|
145
|
+
},
|
|
146
|
+
'before-close': this.handleBeforeClose,
|
|
147
|
+
close: this.handleClose,
|
|
148
|
+
closed: () => {
|
|
149
|
+
this.$emit('closed')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
dialogChildren
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const instance = new Constructor().$mount()
|
|
159
|
+
document.body.appendChild(instance.$el)
|
|
160
|
+
}
|