SunlightDrawer
基于 Element Plus Drawer 封装的增强版抽屉组件,支持自定义宽度、灵活的底部按钮配置、内容区域滚动等功能。
组件特性
- ✨ 支持自定义宽度和方向
- 📏 支持自适应内容高度
- 🎨 灵活的底部按钮配置
- 🔄 支持双向绑定显示/隐藏
- 📌 支持自定义标题和底部插槽
- 🖱️ 支持全屏切换功能
基本使用
基础抽屉
最简单的抽屉使用方式,只需要提供标题和内容。
vue
<template>
<div>
<el-button type="primary" @click="drawerVisible = true">打开抽屉</el-button>
<el-button type="success" @click="drawerVisibleLtr = true">从左侧打开</el-button>
<el-button type="warning" @click="drawerVisibleTtb = true">从上侧打开</el-button>
<!-- 右侧抽屉 -->
<SunlightDrawer
v-model:visible="drawerVisible"
drawer-title="基础抽屉"
content-height="600"
@on-confirm="handleConfirm"
@on-cancel="handleCancel"
>
<div class="drawer-content">
<p>这是一个基础的 SunlightDrawer 抽屉示例。</p>
<p>该组件基于 Element Plus Drawer 封装,提供了更多增强功能:</p>
<ul>
<li>支持自定义宽度和方向</li>
<li>支持自适应内容高度</li>
<li>灵活的底部按钮配置</li>
<li>支持全屏切换功能</li>
</ul>
<p>您可以通过点击右上角的全屏图标切换全屏模式。</p>
</div>
</SunlightDrawer>
<!-- 左侧抽屉 -->
<SunlightDrawer
v-model:visible="drawerVisibleLtr"
drawer-title="左侧抽屉"
drawer-direction="ltr"
drawer-size="40%"
content-height="500"
@on-confirm="handleConfirmLtr"
@on-cancel="handleCancel"
>
<div class="drawer-content">
<p>这是一个从左侧打开的抽屉示例。</p>
<p>抽屉方向可以通过 drawerDirection 属性设置,支持以下值:</p>
<ul>
<li>rtl: 从右侧打开(默认)</li>
<li>ltr: 从左侧打开</li>
<li>ttb: 从上侧打开</li>
<li>btt: 从下侧打开</li>
</ul>
</div>
</SunlightDrawer>
<!-- 上侧抽屉 -->
<SunlightDrawer
v-model:visible="drawerVisibleTtb"
drawer-title="上侧抽屉"
drawer-direction="ttb"
drawer-size="50%"
content-height="300"
@on-confirm="handleConfirmTtb"
@on-cancel="handleCancel"
>
<div class="drawer-content">
<p>这是一个从上侧打开的抽屉示例。</p>
<p>您可以通过 drawerSize 属性设置抽屉的大小,支持百分比和像素值。</p>
</div>
</SunlightDrawer>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { SunlightDrawer } from 'sunlight-ui';
import { ElMessage } from 'element-plus';
// 抽屉显示状态
const drawerVisible = ref(false);
const drawerVisibleLtr = ref(false);
const drawerVisibleTtb = ref(false);
// 处理右侧抽屉确认按钮点击
const handleConfirm = () => {
ElMessage.success('右侧抽屉 - 点击了确认按钮');
drawerVisible.value = false;
};
// 处理左侧抽屉确认按钮点击
const handleConfirmLtr = () => {
ElMessage.success('左侧抽屉 - 点击了确认按钮');
drawerVisibleLtr.value = false;
};
// 处理上侧抽屉确认按钮点击
const handleConfirmTtb = () => {
ElMessage.success('上侧抽屉 - 点击了确认按钮');
drawerVisibleTtb.value = false;
};
// 处理取消按钮点击
const handleCancel = () => {
ElMessage.info('点击了取消按钮');
};
</script>
<style scoped>
.drawer-content {
padding: 20px;
line-height: 1.8;
}
.drawer-content ul {
margin: 10px 0;
padding-left: 20px;
}
.drawer-content li {
margin-bottom: 5px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model:visible | 抽屉显示状态 | boolean | false |
| drawerTitle | 抽屉标题 | string | '抽屉' |
| drawerDirection | 抽屉方向 | 'rtl' | 'ltr' | 'ttb' | 'btt' | 'rtl' |
| drawerSize | 抽屉大小 | string | number | '30%' |
| contentHeight | 内容区域高度 | number | string | 'calc(100vh - 150px)' |
| withFooter | 是否显示底部按钮 | boolean | true |
| confirmBtnText | 确认按钮文本 | string | '确认' |
| cancelBtnText | 取消按钮文本 | string | '取消' |
自定义表单组件
在抽屉中集成复杂的表单组件,实现数据的录入和编辑功能。
vue
<template>
<div>
<el-button type="primary" @click="drawerVisible = true">打开带表单的抽屉</el-button>
<SunlightDrawer
v-model:visible="drawerVisible"
drawer-title="自定义表单组件"
drawer-size="50%"
content-height="600"
@on-confirm="handleConfirm"
@on-cancel="handleCancel"
>
<el-form :model="formData" label-width="100px" :rules="rules" ref="formRef">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number v-model="formData.age" :min="1" :max="120" placeholder="请输入年龄" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="formData.gender">
<el-radio label="male">男</el-radio>
<el-radio label="female">女</el-radio>
<el-radio label="other">其他</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" type="email" />
</el-form-item>
<el-form-item label="部门" prop="department">
<el-select v-model="formData.department" placeholder="请选择部门">
<el-option label="技术部" value="tech" />
<el-option label="市场部" value="market" />
<el-option label="销售部" value="sales" />
<el-option label="人事部" value="hr" />
</el-select>
</el-form-item>
<el-form-item label="职位" prop="position">
<el-select v-model="formData.position" placeholder="请选择职位">
<el-option label="员工" value="staff" />
<el-option label="主管" value="supervisor" />
<el-option label="经理" value="manager" />
<el-option label="总监" value="director" />
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" type="textarea" :rows="3" placeholder="请输入详细地址" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注信息" />
</el-form-item>
</el-form>
</SunlightDrawer>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { SunlightDrawer } from 'sunlight-ui';
import { ElMessage } from 'element-plus';
// 抽屉显示状态
const drawerVisible = ref(false);
// 表单引用
const formRef = ref();
// 表单数据
const formData = reactive({
name: '',
age: null,
gender: 'male',
email: '',
department: '',
position: '',
phone: '',
address: '',
remark: ''
});
// 表单验证规则
const rules = reactive({
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 10, message: '姓名长度在 2 到 10 个字符', trigger: 'blur' }
],
age: [
{ required: true, message: '请输入年龄', trigger: 'blur' },
{ type: 'number', message: '年龄必须为数字值', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
department: [
{ required: true, message: '请选择部门', trigger: 'change' }
],
position: [
{ required: true, message: '请选择职位', trigger: 'change' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
address: [
{ required: true, message: '请输入详细地址', trigger: 'blur' },
{ min: 10, message: '地址长度不能少于 10 个字符', trigger: 'blur' }
]
});
// 处理确认按钮点击
const handleConfirm = async () => {
if (!formRef.value) return;
try {
await formRef.value.validate();
ElMessage.success('表单验证通过,点击了确认按钮');
console.log('表单数据:', formData);
drawerVisible.value = false;
} catch (error) {
ElMessage.error('表单验证失败,请检查输入');
}
};
// 处理取消按钮点击
const handleCancel = () => {
ElMessage.info('点击了取消按钮');
// 重置表单
if (formRef.value) {
formRef.value.resetFields();
}
};
</script>
<style scoped>
/* 可以在这里添加自定义样式 */
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| formOptions | 表单配置项 | Record<string, any> | {} |
| rules | 表单验证规则 | Record<string, any[]> | {} |
自定义表单 Label
通过插槽自定义表单标签的样式、内容和布局。
vue
<template>
<div>
<el-button type="primary" @click="drawerVisible = true">打开自定义标签抽屉</el-button>
<SunlightDrawer
v-model:visible="drawerVisible"
drawer-title="自定义表单 Label"
drawer-size="50%"
content-height="500"
@on-confirm="handleConfirm"
@on-cancel="handleCancel"
>
<el-form :model="formData" label-width="120px" class="custom-label-form">
<!-- 自定义样式标签 -->
<el-form-item label="姓名" prop="name" class="custom-label-item">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
<!-- 带图标标签 -->
<el-form-item prop="email">
<template #label>
<span class="label-with-icon">
<el-icon><Message /></el-icon>
<span>邮箱</span>
</span>
</template>
<el-input v-model="formData.email" placeholder="请输入邮箱" type="email" />
</el-form-item>
<!-- 带必填标记的自定义标签 -->
<el-form-item prop="phone">
<template #label>
<span class="custom-required-label">
手机号
<span class="required-mark">*</span>
</span>
</template>
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<!-- 多行标签 -->
<el-form-item prop="address">
<template #label>
<div class="multi-line-label">
<span>详细地址</span>
<small>(请填写完整地址,包括省市区街道)</small>
</div>
</template>
<el-input v-model="formData.address" type="textarea" :rows="3" placeholder="请输入详细地址" />
</el-form-item>
<!-- 自定义颜色标签 -->
<el-form-item prop="status">
<template #label>
<span class="color-label">状态</span>
</template>
<el-select v-model="formData.status" placeholder="请选择状态">
<el-option label="启用" value="active" />
<el-option label="禁用" value="disabled" />
</el-select>
</el-form-item>
</el-form>
</SunlightDrawer>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { SunlightDrawer } from 'sunlight-ui';
import { ElMessage } from 'element-plus';
import { Message } from '@element-plus/icons-vue';
// 抽屉显示状态
const drawerVisible = ref(false);
// 表单数据
const formData = reactive({
name: '',
email: '',
phone: '',
address: '',
status: 'active'
});
// 处理确认按钮点击
const handleConfirm = () => {
ElMessage.success('点击了确认按钮');
console.log('表单数据:', formData);
drawerVisible.value = false;
};
// 处理取消按钮点击
const handleCancel = () => {
ElMessage.info('点击了取消按钮');
};
</script>
<style scoped>
.custom-label-form {
padding: 10px 0;
}
/* 自定义样式标签 */
.custom-label-item :deep(.el-form-item__label) {
font-weight: bold;
color: #67C23A;
font-size: 15px;
}
/* 带图标标签 */
.label-with-icon {
display: flex;
align-items: center;
gap: 6px;
font-weight: 500;
}
.label-with-icon .el-icon {
color: #409EFF;
}
/* 带必填标记的自定义标签 */
.custom-required-label {
display: flex;
align-items: center;
gap: 4px;
}
.required-mark {
color: #F56C6C;
font-size: 14px;
margin-right: 4px;
}
/* 多行标签 */
.multi-line-label {
display: flex;
flex-direction: column;
gap: 2px;
}
.multi-line-label small {
font-size: 11px;
color: #909399;
font-weight: normal;
}
/* 自定义颜色标签 */
.color-label {
color: #E6A23C;
font-weight: bold;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| labelWidth | 标签宽度 | string | number | '80px' |
| labelPosition | 标签位置 | 'left' | 'right' | 'top' | 'right' |
自定义底部
通过插槽自定义抽屉的底部按钮区域,支持多按钮布局和自定义样式。
vue
<template>
<div>
<el-button type="primary" @click="drawerVisible = true">打开自定义底部抽屉</el-button>
<SunlightDrawer
v-model:visible="drawerVisible"
drawer-title="自定义底部"
drawer-size="50%"
content-height="400"
@on-confirm="handleConfirm"
@on-cancel="handleCancel"
>
<div class="drawer-content">
<p>这是一个自定义底部的抽屉示例,您可以通过插槽自定义底部的按钮和布局。</p>
<p>底部区域可以包含多个按钮、不同样式的按钮,或者其他自定义内容。</p>
</div>
<!-- 底部上方的额外内容 -->
<template #footer-top>
<div class="footer-top-content">
<el-divider />
<div class="additional-info">
<el-icon><InfoFilled /></el-icon>
<span>点击"确认"将保存当前设置,点击"取消"将关闭抽屉。</span>
</div>
</div>
</template>
<!-- 自定义底部按钮 -->
<template #footer="{ handleConfirmClick, handleCancelClick }">
<div class="custom-footer">
<!-- 左侧按钮 -->
<div class="footer-left">
<el-button type="info" @click="handleReset">重置</el-button>
<el-button type="warning" @click="handlePreview">预览</el-button>
</div>
<!-- 右侧按钮 -->
<div class="footer-right">
<el-button @click="handleCancelClick">取消</el-button>
<el-button type="primary" :loading="isLoading" @click="handleConfirmClick">
<el-icon v-if="isLoading"><Loading /></el-icon>
<span>确认</span>
</el-button>
<el-button type="success" @click="handleSaveAndContinue">保存并继续</el-button>
</div>
</div>
</template>
</SunlightDrawer>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { SunlightDrawer } from 'sunlight-ui';
import { ElMessage } from 'element-plus';
import { InfoFilled, Loading } from '@element-plus/icons-vue';
// 抽屉显示状态
const drawerVisible = ref(false);
// 加载状态
const isLoading = ref(false);
// 处理确认按钮点击
const handleConfirm = () => {
isLoading.value = true;
// 模拟异步操作
setTimeout(() => {
isLoading.value = false;
ElMessage.success('点击了确认按钮');
drawerVisible.value = false;
}, 1500);
};
// 处理取消按钮点击
const handleCancel = () => {
ElMessage.info('点击了取消按钮');
};
// 处理重置按钮点击
const handleReset = () => {
ElMessage.warning('点击了重置按钮');
};
// 处理预览按钮点击
const handlePreview = () => {
ElMessage.info('点击了预览按钮');
};
// 处理保存并继续按钮点击
const handleSaveAndContinue = () => {
ElMessage.success('点击了保存并继续按钮');
};
</script>
<style scoped>
.drawer-content {
padding: 20px;
line-height: 1.8;
}
/* 底部上方内容 */
.footer-top-content {
margin: 10px 0;
}
.additional-info {
display: flex;
align-items: center;
gap: 8px;
color: #909399;
font-size: 14px;
margin-top: 10px;
}
/* 自定义底部样式 */
.custom-footer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 10px 0;
}
.footer-left,
.footer-right {
display: flex;
gap: 10px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
隐藏源代码
配置说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| footerButtonAlign | 底部按钮对齐方式 | 'left' | 'center' | 'right' | 'right' |
事件说明
| 事件名 | 说明 | 参数 |
|---|---|---|
| update:visible | 抽屉显示状态变化事件 | value: boolean |
| toggle-fullscreen | 全屏状态切换事件 | value: boolean |
| on-confirm | 确认按钮点击事件 | - |
| on-cancel | 取消按钮点击事件 | - |
插槽说明
| 插槽名 | 说明 | 参数 |
|---|---|---|
| default | 抽屉内容区域 | - |
| header | 抽屉头部区域 | headerScope |
| header-title | 抽屉标题区域 | - |
| fullscreen-toggle | 全屏切换图标区域 | { isFullscreenMode, handleFullscreenToggle } |
| footer | 抽屉底部按钮区域 | { handleConfirmClick, handleCancelClick } |
| footer-top | 底部按钮上方的插槽 | - |
方法说明
| 方法名 | 说明 | 参数 |
|---|---|---|
| showDrawer | 显示抽屉 | - |
| hideDrawer | 隐藏抽屉 | - |
| handleConfirmClick | 触发确认事件 | - |
| handleCancelClick | 触发取消事件 | - |
| handleFullscreenToggle | 切换全屏状态 | - |