1 介绍
下面的所有组件全部基于VUE3 + TS + element plus编写,其中部分组件可能涉及到其他技术栈,会进行单独说明。
2 基础组件
2.1 表格操作组件TableToolButton
此组件用于对表格进行增加、编辑、删除、导出操作。
2.1.1 组件属性
- addVisible:是否显示添加按钮,默认为true。
- editVisible:是否显示编辑按钮,默认为true。
- deleteVisible:是否显示删除按钮,默认为true。
- exportVisible:是否显示导出按钮,默认为true。
- selectedArray:传递的数组,用于对编辑和删除做预处理,默认为空数组。
2.1.2 组件事件
- toAdd:添加按钮触发事件,参数1:"新增";参数2:空对象
- toEdit:编辑按钮触发事件,会对selectedArray长度进行校验。不等于1时触发警告。参数1:"编辑";参数2:选中的对象
- toDelete:删除按钮触发事件,会对selectedArray数据进行处理,获取数组中所有的id属性值,然后使用','进行拼接。参数1:拼接后的字符串
- toExport:导出按钮触发事件。
2.1.3 组件展示
<script setup lang="ts">
// 表格按钮组件
import { Delete, Download, Edit, Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const props = withDefaults(defineProps<{
addVisible?: boolean,
editVisible?: boolean,
deleteVisible?: boolean,
exportVisible?: boolean,
selectedArray: any // 选中的数据
}>(), {
addVisible: true,
editVisible: true,
deleteVisible: true,
exportVisible: true
})
const emit = defineEmits(['toAdd', 'toEdit', 'toDelete', 'toExport'])
// 处理添加方法
const addFunction = () => {
emit('toAdd', '新增', {})
}
// 处理编辑方法
const editFunction = () => {
if (typeof props.selectedArray == 'undefined' || props.selectedArray.length !== 1) {
ElMessage({
message: '请选择一条数据进行编辑',
type: 'warning'
})
return
} // 将对象进行深拷贝
emit('toEdit', '编辑', JSON.parse(JSON.stringify(props.selectedArray[0])))
}
// 处理删除方法
const deleteFunction = () => {
if (props.selectedArray.length === 0) {
ElMessage({
message: '请选择一条数据进行删除',
type: 'warning'
})
return
}
ElMessageBox.confirm(
'确认要删除吗?',
'重要提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// 将对象中的id取出组成新的数组,然后使用join拼接
const ids = props.selectedArray.map((item: any) => item.id)
emit('toDelete', ids.join(','))
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
const exportFunction = () => {
emit('toExport')
}
</script>
<template>
<div class="table-tool-button">
<el-tooltip content="添加" v-if="addVisible" placement="top">
<el-button type="success" :icon="Plus" @click="addFunction" circle />
</el-tooltip>
<el-tooltip content="编辑" v-if="editVisible" placement="top">
<el-button type="primary" :icon="Edit" @click="editFunction" circle />
</el-tooltip>
<el-tooltip content="删除" v-if="deleteVisible" placement="top">
<el-button type="danger" :icon="Delete" @click="deleteFunction" circle />
</el-tooltip>
<el-tooltip content="导出" v-if="exportVisible" placement="top">
<el-button type="warning" :icon="Download" @click="exportFunction" circle />
</el-tooltip>
</div>
</template>
<style scoped>
</style>
2.2 上传图片组件UploadImage
此组件封装了上传图片功能,并在上传图片前对文件类型以及大小进行校验,并且在切换图片后触发服务端删除图片方法。
2.2.1 组件属性
- imageName:图片名称,用于回显图片。
2.2.2 组件事件
- success:上传图片后触发事件,参数1:上传的图片名。
2.2.3 组件展示
上传接口:
import api from '../axios'
// 下载图片地址
//export const downloadImageUrl = 'http://192.168.2.21:8080/image/downloadImage?name='
export const downloadImageUrl = '/api/image/downloadImage?name='
// 上传文件
export function uploadImageApi(file: File) {
const formData = new FormData()
formData.append('file', file)
return api.post('/image/uploadImage', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 下载图片
export function downloadImageApi(imageName: string) {
return api.get('/image/downloadImage', { params: { name: imageName }, responseType: 'blob' })
}
// 删除图片
export function deleteImageApi(imageName: string) {
return api.delete('/image/removeImage', { params: { name: imageName } })
}
组件代码:
<script setup lang="ts">
// 上传图片组件
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'
import { deleteImageApi, downloadImageApi, uploadImageApi } from '@/assets/api/file'
// 图片名
const props = defineProps<{
imageName: string
}>()
// 上传成功回调方法,返回图片地址
const emit = defineEmits(['success'])
// 图片名
const imageName = ref(props.imageName)
// 图片地址 img标签src的值
const imageUrl = ref()
// 上传图片
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
ElMessage.error('图片必须是jpg或png格式!')
return false
} else if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('图片大小不能超过2MB!')
return false
}
uploadImageApi(rawFile).then((res) => {
if (res.data.code === 200) {
ElMessage.success('上传成功')
imageName.value = res.data.data
echoImage()
emit('success', imageName.value)
}
})
return false // 返回false,阻止默认上传行为
}
// 回显图片
const echoImage = () => {
if (imageName.value != '') {
downloadImageApi(imageName.value).then((res) => {
imageUrl.value = URL.createObjectURL(res.data)
})
}
}
// 删除图片
const beforeRemove = () => {
if (imageName.value != '') {
deleteImageApi(imageName.value).then((res) => {
ElMessage.info(res.data.data)
})
}
}
onMounted(() => {
echoImage()
})
</script>
<template>
<!-- 上传图片 -->
<el-upload
class="avatar-uploader"
action="#"
accept="image/*"
:show-file-list="false"
:before-upload="beforeAvatarUpload"
:before-remove="beforeRemove"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</template>
<style scoped>
.avatar-uploader .avatar {
width: 250px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>
2.3 富文本编辑器WangEditor
此组件用于在页面中使用富文本编辑器,需要在项目中进行下载安装。官网地址:wangEditor。Vue3安装命令:
yarn add @wangeditor/editor-for-vue@next
# 或者 npm install @wangeditor/editor-for-vue@next --save
注意ts项目需要在env.d.ts文件里添加类型声明:
declare module "@wangeditor/editor-for-vue" {
const Editor: any;
const Toolbar: any;
type IEditorConfig = any;
}
2.3.1 组件属性
- valueHtml:编辑器里的内容,默认为空。
- size:编辑器中内容长度,此长度包含html标签,默认值2000。
- enableImg:是否开启向内容中插入图片功能,默认为false,注意如果开启请确保项目中已经引入了2.2.3章节中上传图片组件里的上传接口。
2.3.2 组件事件
- save:当编辑器内容发生改变时触发,对内容长度进行校验,如果开启了插入图片工具则会对插入的图片进行校验,触发服务器端删除图片功能。参数1:编辑器中的内容。
2.3.3 组件展示
<script setup lang="ts">
//富文本编辑组件
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { deleteImageApi, downloadImageUrl, uploadImageApi } from '@/assets/api/file'
const props = withDefaults(defineProps<{
valueHtml?: string // 内容
size?: number // 内容长度
enableImg?: boolean // 是否启用图片上传
}>(), {
valueHtml: '',
size: 2000,
enableImg: false
})
const emit = defineEmits<{
save: [valueHtml: string]
}>()
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref(props.valueHtml)
// 工具栏配置 默认屏蔽工具栏上的图片、视频按钮
const toolbarConfig = ref({
excludeKeys: ['group-image', 'insertVideo']
})
// 向服务器插入图片集合
const insertImage = ref<string[]>([])
// 编辑器配置
const editorConfig = {
placeholder: '输入内容...',
MENU_CONF: {
uploadImage: { // 上传图片的配置
// 自定义上传
async customUpload(file: File, insertFn: any) {
if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
ElMessage.info('图片必须是jpg或png格式!')
return
}
if (file.size > 1024 * 1024 * 2) {
ElMessage.info('图片大小不能超过2M!')
return
}
// 上传图片
uploadImageApi(file).then((res: any) => {
// 上传成功后,获取图片地址,并插入到编辑器中
if (res.data.code === 200) {
insertFn(downloadImageUrl + res.data.data, '无法显示')
}
})
}
},
insertImage: {
onInsertedImage(imageNode: any | null) {
if (imageNode == null) return
const { src } = imageNode
insertImage.value.push(src)
}
}
}
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
// 编辑器创建完毕的回调,记录 editor 实例,重要!
const handleCreated = (editor: any) => {
editorRef.value = editor
}
// 保存
const save = (editor: any) => {
if (valueHtml.value.length > props.size) {
ElMessageBox.alert('内容超出可存储范围,请删减内容!', '警告', {
confirmButtonText: 'OK'
})
} else {
// 获取编辑器中存在的图片
const imageTag = editor.getElemsByType('image')
// 获取删除的图片
let deleteImageList: any[]
if (imageTag.length > 0) {
// 获取编辑器中不存在的图片
const imageList = imageTag.map((item: any) => item.src)
deleteImageList = insertImage.value.filter(item => !imageList.includes(item))
} else {
// 如果编辑器中没有图片则删除所有插入的图片
deleteImageList = insertImage.value
insertImage.value = []
}
// 删除图片
if (deleteImageList.length > 0) {
let deleteImageListStr = ''
deleteImageList.forEach(item => {
// 获取图片名
deleteImageListStr += item.substring(item.indexOf('name=') + 5) + ','
})
deleteImageList.length = 0
deleteImageApi(deleteImageListStr).then((res: any) => {
if (res.data.code === 200) {
ElMessage.success('删除图片成功')
}
})
}
emit('save', valueHtml.value)
}
}
onMounted(() => {
if (props.enableImg) {
// 监听图片上传
toolbarConfig.value = {
excludeKeys: ['insertVideo']
}
}
})
</script>
<template>
<div style="border: 1px solid #ccc;z-index: 1000">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
mode="simple"
/>
<Editor
style="height: 400px; overflow-y: hidden;"
v-model="valueHtml"
:defaultConfig="editorConfig"
mode="simple"
@onCreated="handleCreated"
@onChange="save"
/>
</div>
</template>
<style scoped>
</style>
2.4 权限按钮组PermissionGroup
此组件通用性不高,只是在编辑角色时对系统中权限列表展示做分类处理。
2.4.1 组件属性
- permissionList:权限对象数组,必须。
- echoPermissions:需要回显的权限数组,必须。
2.4.2 组件事件
- update:permissions:按钮组选中状态发生改变时触发,返回选中的权限id数组。
2.4.3 组件展示
<script setup lang="ts">
// 权限分组组件,接受权限数据,根据对应菜单进行分组展示
import { computed, ref } from 'vue'
const props = defineProps<{
permissionList: any[], // 权限数据
echoPermissions: any[], // 回显选中的权限
}>()
// 定义事件,更新选中权限触发事件
const emits = defineEmits(['update:permissions'])
// 定义选中的权限
const selectedPermissions = ref(props.echoPermissions)
// 定义权限根据菜单分组展示
const permissionGroupByMenu = computed(() => {
let map: any = {}
let arr = props.permissionList
for (let i = 0; i < arr.length; i++) {
let ai = arr[i]
if (!map[ai.menuNames]) {
map[ai.menuNames] = [ai]
} else {
map[ai.menuNames].push(ai)
}
}
let res: any = []
Object.keys(map).forEach(key => {
res.push({
id: key,
data: map[key]
})
})
return res
})
</script>
<template>
<div class="permission-group">
<div style="margin-bottom: 10px" v-for="permissionGroup in permissionGroupByMenu">
<label class="group-name">{{ permissionGroup.id }}:</label>
<el-checkbox-group v-model="selectedPermissions" size="large">
<el-checkbox-button
@change="emits('update:permissions', selectedPermissions)"
style="margin: 1px"
v-for="permission in permissionGroup.data"
:key="permission.id"
:value="permission.id"
>
{{ permission.permissionDesc }}
</el-checkbox-button>
</el-checkbox-group>
</div>
</div>
</template>
<style scoped>
.permission-group {
height: 360px;
overflow-y: scroll;
}
.group-name {
font-size: 16px;
font-weight: bold;
}
</style>