配置search
page-search.vue
1.Form 表单
Form 属性
- model:表单数据对象
- label-width:标签的长度, 作为 Form 直接子元素的 form-item 会继承该值
- size:用于控制该表单内组件的尺寸’large’ | ‘default’ | ‘small’
Form Item 属性
- label:标签文本
- prop:model 的键名。 在定义了 validate、resetFields 的方法时,该属性是必填的
2.Layout 布局
默认使用 Flex 布局,通过 row 和 col 组件,并通过 col 组件的 span 属性我们就可以自由地组合布局,默认 24 分栏
Row 属性:行
- gutter:栅格间隔
Col 属性:列
- span:栅格占据的列数
3.Input 输入框
Input 属性
- v-model:绑定值,使标签可以输入
- placeholder:输入框占位文本
4.DatePicker 日期选择器
- v-model:选中项绑定值
- type:显示类型year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange
- range-separator:选择范围时的分隔符
- start-placeholder:范围选择时开始日期的占位内容
- end-placeholder:范围选择时结束日期的占位内容
5.Select 选择器
Select 属性
- v-model:选中项绑定值
Option 属性
- label:选项的标签,若不设置则默认与value相同
- value:选项的值
//components/page-search/page-search.vue
<template>
<div class="search" v-if="isQuery">
<!-- 1.输入搜索关键字的表单 -->
<el-form
:model="searchForm"
ref="formRef"
:label-width="searchConfig.labelWidth ?? '80px'"
size="large"
>
<el-row :gutter="20">
<template v-for="item in searchConfig.formItems" :key="item.prop">
<el-col :span="8">
<el-form-item :label="item.label" :prop="item.prop">
<template v-if="item.type === 'input'">
<el-input
v-model="searchForm[item.prop]"
:placeholder="item.placeholder"
/>
</template>
<template v-if="item.type === 'date-picker'">
<el-date-picker
v-model="searchForm[item.prop]"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</template>
<template v-if="item.type === 'select'">
<el-select
v-model="searchForm[item.prop]"
:placeholder="item.placeholder"
style="width: 100%"
>
<template v-for="option in item.options" :key="option.value">
<el-option :label="option.label" :value="option.value" />
</template>
</el-select>
</template>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 2.重置和搜索的按钮 -->
<div class="btns">
<el-button icon="Refresh" @click="handleResetClick">重置</el-button>
<el-button icon="Search" type="primary" @click="handleQueryClick"
>查询</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'
import usePermissions from '@/hooks/usePermissions'
// 定义自定义事件/接收的属性
interface IProps {
searchConfig: {
pageName: string
labelWidth?: string
formItems: any[]
}
}
const emit = defineEmits(['queryClick', 'resetClick'])
const props = defineProps<IProps>()
//const props = defineProps({
//searchConfig:{
//type:Object,
//default
//}
//})
// 获取权限
const isQuery = usePermissions(`${props.searchConfig.pageName}:query`)
// 定义form的数据
const initialForm: any = {}
for (const item of props.searchConfig.formItems) {
initialForm[item.prop] = item.initialValue ?? ''//初始化数据
}
const searchForm = reactive(initialForm)
//const searchForm=reactive({
//name:'',
//leader:''.
//createAt:''
//})
// 重置操作
const formRef = ref<InstanceType<typeof ElForm>>()
function handleResetClick() {
// 1.form中的数据全部重置
formRef.value?.resetFields()
// 2.将事件出去, content内部重新发送网络请求
emit('resetClick')
}
function handleQueryClick() {
emit('queryClick', searchForm)
}
</script>
<style lang="less" scoped>
.search {
background-color: #fff;
padding: 20px;
.el-form-item {
padding: 20px 30px;
margin-bottom: 0;
}
.btns {
text-align: right;
padding: 0 50px 10px 0;
.el-button {
height: 36px;
}
}
}
</style>
date picker日期选择器 转换为中文
//App.vue
<div class="app">
<el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</div>
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
// .mjs文件声明
//env.d.ts
declare module '*.mjs'
serch.config.ts
// views/main/system/departent/config/search.config.ts
const searchConfig = {
pageName: 'department',
formItems: [
{
type: 'input',
prop: 'name',
label: '部门名称',
placeholder: '请输入查询的部门名称',
initialValue: 'bbb'
},
{
type: 'input',
prop: 'leader',
label: '部门领导',
placeholder: '请输入查询的领导名称'
},
{
type: 'date-picker',
prop: 'createAt',
label: '创建时间'
},{
type: 'select',
prop: 'enable',
label: '状态',
placeholder:"请选择查询的状态",
options:[
{label:'启动',value:1},
{label:'禁用',value:0}
]
}
]
}
export default searchConfig
department.vue
<div class="department">
<page-search
:search-config="searchConfig"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
</div>
import searchConfig from './config/search.config'
import PageSearch from '@/components/page-search/page-search.vue'
配置content
page-content.vue
1.Table 表格
Table 属性
- data:显示的数据
- border:是否带有纵向边框
Table-column 属性
- align:对齐方式
- type:对应列的类型。 如果设置了selection则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮
- label:column 的 key
- prop:字段名称 对应列内容的字段名
Table-column 插槽
- default:自定义列的内容
- header:自定义表头的内容
2.Pagination 分页
属性
- page-size:每页显示条目个数,支持 v-model 双向绑定
- current-page:当前页数,支持 v-model 双向绑定
- total:总条目数
- layout:组件布局,子组件名用逗号分隔total/sizes / prev / pager / next / jumper /
事件
- @size-change: pageSize 改变时触发
- @current-change:current-change 改变时触发
//components/page-content/page-content.vue
<template>
<div class="content">
//头部
<div class="header">
<h3 class="title">{{ contentConfig?.header?.title ?? '数据列表' }}</h3>
<el-button v-if="isCreate" type="primary" @click="handleNewUserClick">
{{ contentConfig?.header?.btnTitle ?? '新建数据' }}
</el-button>
</div>
//表格
<div class="table">
<el-table
:data="pageList"
border
style="width: 100%"
v-bind="contentConfig.childrenTree"
>
<template v-for="item in contentConfig.propsList" :key="item.prop">
<template v-if="item.type === 'timer'">
//v-bind="item"等价于 :label="item.label" :prop="item.prop" :width="item.width"
<el-table-column align="center" v-bind="item">
<template #default="scope">
{{ formatUTC(scope.row[item.prop]) }}
</template>
</el-table-column>
</template>
<template v-else-if="item.type === 'handler'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<el-button
v-if="isUpdate"
size="small"
icon="Edit"
type="primary"
text
@click="handleEditBtnClick(scope.row)"
>
编辑
</el-button>
<el-button
v-if="isDelete"
size="small"
icon="Delete"
type="danger"
text
@click="handleDeleteBtnClick(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</template>
//自定义类型
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<slot
:name="item.slotName"//具名插槽
v-bind="scope"
:prop="item.prop"
hName="why"
></slot>
//-{{scope.row[item.prop]}}-
</template>
</el-table-column>
</template>
<template v-else>
<el-table-column align="center" v-bind="item" />
</template>
//状态
// <el-table-column
// align="center"
// label="状态"
// prop="enable"
// width="100px"
// >
// <!-- 作用域插槽 -->
// <template #default="scope">
//plain镂空效果
// <el-button
// size="small"
// :type="scope.row.enable ? 'primary' : 'danger'"
// plain
// >
// {{ scope.row.enable ? '启用' : '禁用' }}
// </el-button>
// </template>
// </el-table-column>
</template>
</el-table>
</div>
//分页器
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
:total="pageTotalCount"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import useSystemStore from '@/store/main/system/system'
import { formatUTC } from '@/utils/format'
import usePermissions from '@/hooks/usePermissions'
interface IProps {
contentConfig: {
pageName: string
header?: {
title?: string
btnTitle?: string
}
propsList: any[]
childrenTree?: any
}
}
const props = defineProps<IProps>()
// 定义事件
const emit = defineEmits(['newClick', 'editClick'])
// 0.获取是否有对应的增删改查的权限
const isCreate = usePermissions(`${props.contentConfig.pageName}:create`)
const isDelete = usePermissions(`${props.contentConfig.pageName}:delete`)
const isUpdate = usePermissions(`${props.contentConfig.pageName}:update`)
const isQuery = usePermissions(`${props.contentConfig.pageName}:query`)
// 1.发起action,请求usersList的数据
const systemStore = useSystemStore()
const currentPage = ref(1)
const pageSize = ref(10)
//新建,编辑,删除都将分页器页数设置为第一页,监听store中的action事件
systemStore.$onAction(({ name, after }) => {
after(() => {
if (
name === 'deletePageByIdAction' ||
name === 'editPageDataAction' ||
name === 'newPageDataAction'
) {
currentPage.value = 1
}
})
})
fetchPageListData()
// 2.获取usersList数据,进行展示
const { pageList, pageTotalCount } = storeToRefs(systemStore)
// 3.页码相关的逻辑
function handleSizeChange() {
fetchPageListData()
}
function handleCurrentChange() {
fetchPageListData()
}
// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
if (!isQuery) return
// 1.获取offset/size
const size = pageSize.value
const offset = (currentPage.value - 1) * size
const pageInfo = { size, offset }
// 2.发起网络请求
const queryInfo = { ...pageInfo, ...formData }
systemStore.postPageListAction(props.contentConfig.pageName, queryInfo)
}
// 5.删除/新建/编辑的操作
function handleDeleteBtnClick(id: number) {
systemStore.deletePageByIdAction(props.contentConfig.pageName, id)
}
function handleNewUserClick() {
emit('newClick')
}
function handleEditBtnClick(itemData: any) {
emit('editClick', itemData)
}
// 6.监听systemStore中的actions被执行
defineExpose({ fetchPageListData })
</script>
<style lang="less" scoped>
.content {
margin-top: 20px;
padding: 20px;
background-color: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
.title {
font-size: 22px;
}
}
.table {
:deep(.el-table__cell) {
padding: 12px 0;
}
.el-button {
margin-left: 0;
padding: 5px 8px;
}
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>
封装API
// service/main/system/system.ts
import hyRequest from '@/service'
/** 用户列表API */
export function postUsersListData(queryInfo: any) {
return hyRequest.post({
url: '/users/list',
data: queryInfo
//data:{
//offset:0,
//size:10
//}
})
}
export function deleteUserById(id: number) {
return hyRequest.delete({
url: `/users/${id}`
})
}
export function newUserData(userInfo: any) {
return hyRequest.post({
url: '/users',
data: userInfo
})
}
export function editUserData(id: number, userInfo: any) {
return hyRequest.patch({
url: `/users/${id}`,
data: userInfo
})
}
/** 针对页面的网络请求: 增删改查 */
export function postPageListData(pageName: string, queryInfo: any) {
return hyRequest.post({
url: `/${pageName}/list`,
data: queryInfo
})
}
export function deletePageById(pageName: string, id: number) {
return hyRequest.delete({
url: `/${pageName}/${id}`
})
}
export function newPageData(pageName: string, pageInfo: any) {
return hyRequest.post({
url: `/${pageName}`,
data: pageInfo
})
}
export function editPageData(pageName: string, id: number, pageInfo: any) {
return hyRequest.patch({
url: `/${pageName}/${id}`,
data: pageInfo
})
}
发送请求
//store/main/system/system.ts
import {
deletePageById,
deleteUserById,
editPageData,
editUserData,
newPageData,
newUserData,
postPageListData,
postUsersListData
} from '@/service/main/system/system'
import { defineStore } from 'pinia'
import useMainStore from '../main'
import type { ISystemState } from './type'
//export interface IUser {
// id: number
// name: string
// realname: string
// cellphone: number
// enable: number
// departmentId: number
// roleId: number
// createAt: string
// updateAt: string
//}
//export interface ISystemState {
// usersList: IUser[]
// usersTotalCount: number
// pageList: any[]
// pageTotalCount: number
//}
const useSystemStore = defineStore('system', {
state: (): ISystemState => ({
usersList: [],//用户列表数据
usersTotalCount: 0,
pageList: [],
pageTotalCount: 0
}),
actions: {
//获取用户列表数据
async postUsersListAction(queryInfo: any) {
const usersListResult = await postUsersListData(queryInfo)
const { totalCount, list } = usersListResult.data
this.usersTotalCount = totalCount
this.usersList = list
},
//
async deleteUserByIdAction(id: number) {
// 1.删除数据操作
const deleteResult = await deleteUserById(id)
console.log(deleteResult)
// 2.重新请求新的数据
this.postUsersListAction({ offset: 0, size: 10 })
},
async newUserDataAction(userInfo: any) {
// 1.创建新的用户
const newResult = await newUserData(userInfo)
console.log(newResult)
// 2.重新请求新的数据
this.postUsersListAction({ offset: 0, size: 10 })
},
async editUserDataAction(id: number, userInfo: any) {
// 1.更新用户的数据
const editResult = await editUserData(id, userInfo)
console.log(editResult)
// 2.重新请求新的数据
this.postUsersListAction({ offset: 0, size: 10 })
},
/** 针对页面的数据: 增删改查 */
async postPageListAction(pageName: string, queryInfo: any) {
const pageListResult = await postPageListData(pageName, queryInfo)
const { totalCount, list } = pageListResult.data
this.pageList = list
this.pageTotalCount = totalCount
},
async deletePageByIdAction(pageName: string, id: number) {
const deleteResult = await deletePageById(pageName, id)
console.log(deleteResult)
this.postPageListAction(pageName, { offset: 0, size: 10 })
// 获取完整的数据
const mainStore = useMainStore()
mainStore.fetchEntireDataAction()
},
async newPageDataAction(pageName: string, pageInfo: any) {
const newResult = await newPageData(pageName, pageInfo)
console.log(newResult)
this.postPageListAction(pageName, { offset: 0, size: 10 })
// 获取完整的数据
const mainStore = useMainStore()
mainStore.fetchEntireDataAction()
},
async editPageDataAction(pageName: string, id: number, pageInfo: any) {
const editResult = await editPageData(pageName, id, pageInfo)
console.log(editResult)
this.postPageListAction(pageName, { offset: 0, size: 10 })
// 获取完整的数据
const mainStore = useMainStore()
mainStore.fetchEntireDataAction()
}
}
})
export default useSystemStore
UTC时间格式化
npm install dayjs
// utils/format.ts
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
dayjs.extend(utc)
export function formatUTC(
utcString: string,
format: string = 'YYYY/MM/DD HH:mm:ss'
) {
const resultTime = dayjs.utc(utcString).utcOffset(8).format(format)//+8小时:零时区转东八区时间
return resultTime
}
content.config.ts
const contentConfig = {
pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新建部门'
},
propsList: [
// 1.selection 2.index
{ type: 'selection', label: '选择', width: '80px' },
{ type: 'index', label: '序号', width: '80px' },
{ type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
{ type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
{ type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
//自定义
// {
// type: 'custom',
// label: '部门领导',
// prop: 'leader',
// width: '150px',
// slotName: 'leader'
// },
// {
// type: 'custom',
// label: '上级部门',
// prop: 'parentId',
// width: '150px',
// slotName: 'parent'
// },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '150px' }
],
childrenTree: {
rowKey: 'id', //子菜单,存在时不能用type
treeProps: {
children: 'children'
}
}
}
export default contentConfig
department.vue
<page-content
:content-config="contentConfig"
ref="contentRef"
@new-click="handleNewClick"
@edit-click="handleEditClick"
>
<template #leader="scope">
<span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
</template>
<template #parent="scope">
<span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
</template>
</page-content>
配置modal
page-modal.vue
1.Dialog 对话框
属性
v-model:是否显示 Dialog
title:Dialog 对话框 的标题
插槽
header:对话框标题的内容;会替换标题部分,但不会移除关闭按钮。
footer:Dialog 按钮操作区的内容
2.Form 表单
Form 属性
- model:表单数据对象
- label-width:标签的长度, 作为 Form 直接子元素的 form-item 会继承该值
- size:用于控制该表单内组件的尺寸’large’ | ‘default’ | ‘small’
Form Item 属性
- label:标签文本
- prop:model 的键名。 在定义了 validate、resetFields 的方法时,该属性是必填的
3.DatePicker 日期选择器
- v-model:选中项绑定值
- type:显示类型year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange
- range-separator:选择范围时的分隔符
- start-placeholder:范围选择时开始日期的占位内容
- end-placeholder:范围选择时结束日期的占位内容
4.Select 选择器
Select 属性
- v-model:选中项绑定值
Option 属性
- label:选项的标签,若不设置则默认与value相同
- value:选项的值
<template>
<div class="modal">
<el-dialog
v-model="dialogVisible"
:title="
isNewRef ? modalConfig.header.newTitle : modalConfig.header.editTitle
"
width="30%"
center
>
<div class="form">
<el-form :model="formData" label-width="80px" size="large">
<template v-for="item in modalConfig.formItems" :key="item.prop">
<el-form-item :label="item.label" :prop="item.prop">
<template v-if="item.type === 'input'">
<el-input
v-model="formData[item.prop]"
:placeholder="item.placeholder"
/>
</template>
<template v-if="item.type === 'date-picker'">
<el-date-picker
v-model="formData[item.prop]"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</template>
<template v-if="item.type === 'select'">
<el-select
v-model="formData[item.prop]"
:placeholder="item.placeholder"
style="width: 100%"
>
<template v-for="option in item.options" :key="option.value">
<el-option :label="option.label" :value="option.value" />
</template>
</el-select>
</template>
<template v-if="item.type === 'custom'">
<slot :name="item.slotName"></slot>
</template>
</el-form-item>
</template>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirmClick">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import useSystemStore from '@/store/main/system/system'
interface IModalProps {
modalConfig: {
pageName: string
header: {
newTitle: string
editTitle: string
}
formItems: any[]
}
otherInfo?: any//树结构数据
}
// 0.定义props
const props = defineProps<IModalProps>()
// 1.定义内部的属性
const dialogVisible = ref(false)
const initialData: any = {}
for (const item of props.modalConfig.formItems) {
initialData[item.prop] = item.initialValue ?? ''
}
const formData = reactive<any>(initialData)
const isNewRef = ref(true)
const editData = ref()
// 2.获取roles/departments数据
const systemStore = useSystemStore()
// 2.定义设置dialogVisible方法
function setModalVisible(isNew: boolean = true, itemData?: any) {
dialogVisible.value = true
isNewRef.value = isNew
if (!isNew && itemData) {
// 编辑数据
for (const key in formData) {
formData[key] = itemData[key]
}
editData.value = itemData//用于记录状态
} else {
// 新建数据
for (const key in formData) {
const item = props.modalConfig.formItems.find((item) => item.prop === key)//初始化值
formData[key] = item ? item.initialValue : ''
}
editData.value = null
}
}
// 3.点击了确定的逻辑
function handleConfirmClick() {
dialogVisible.value = false
let infoData = formData
if (props.otherInfo) {//合并树结构menulist数据
infoData = { ...infoData, ...props.otherInfo }
}
if (!isNewRef.value && editData.value) {
// 编辑用户的数据
systemStore.editPageDataAction(
props.modalConfig.pageName,
editData.value.id,
infoData
)
} else {
// 创建新的部门
systemStore.newPageDataAction(props.modalConfig.pageName, infoData)
}
}
// 暴露的属性和方法
defineExpose({ setModalVisible })
</script>
<style lang="less" scoped>
.form {
padding: 0 20px;
}
</style>
modal.config.ts
import type { IModalConfig } from '@/components/page-modal/type'
//interface IModalConfig {
// pageName: string
// header: {
// newTitle: string
// editTitle: string
// }
// formItems: any[]
// }
// otherInfo?: any
// }
const modalConfig: IModalConfig = {
pageName: 'department',
header: {
newTitle: '新建部门',
editTitle: '编辑部门'
},
formItems: [
{
type: 'input',
label: '部门名称',
prop: 'name',
placeholder: '请输入部门名称'
},
{
type: 'input',
label: '部门领导',
prop: 'leader',
placeholder: '请输入部门领导'
},
{
type: 'select',
label: '上级部门',
prop: 'parentId',
placeholder: '请选择上级部门',
options: []
}.
{
type: 'custom',
slotName:'menulist'
}
]
}
export default modalConfig
department.vue
<page-modal :modal-config="modalConfigRef" ref="modalRef" />
department.vue
Tree 树形控件
属性
data:展示数据
node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
show-checkbox:节点是否可被选择
props:配置选项
- label:指定节点标签为节点对象的某个属性值
- children:指定子树为节点对象的某个属性值
@check:点击节点复选框之后触发
setCheckedKeys:设置目前选中的节点,使用此方法必须有node-key属性
<template>
<div class="department">
<page-search
:search-config="searchConfig"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
<page-content
:content-config="contentConfig"
ref="contentRef"
@new-click="handleNewClick"
@edit-click="handleEditClick"
>
<template #leader="scope">//scope获取插槽中的数据
<span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
<span>{{scope.hName}}</span>
</template>
<template #parent="scope">
<span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
</template>
</page-content>
<page-modal :modal-config="modalConfigRef" ref="modalRef" >
<template #menulist>
<el-tree
ref="treeRef"
:data="entireMenus"
show-checkbox
node-key="id"
:props="{ children: 'children', label: 'name' }"
@check="handleElTreeCheck"
/>
</template>
</page-modal>
</div>
</template>
<script setup lang="ts" name="department">
import { computed } from 'vue'
import useMainStore from '@/store/main/main'
import PageSearch from '@/components/page-search/page-search.vue'
import PageContent from '@/components/page-content/page-content.vue'
import PageModal from '@/components/page-modal/page-modal.vue'
import searchConfig from './config/search.config'
import contentConfig from './config/content.config'
import modalConfig from './config/modal.config'
import usePageContent from '@/hooks/usePageContent'
import usePageModal from '@/hooks/usePageModal'
// 对modalConfig进行操作
//获取整个部门数据
const modalConfigRef = computed(() => {
const mainStore = useMainStore()
const departments = mainStore.entireDepartments.map((item) => {
return { label: item.name, value: item.id }
})
modalConfig.formItems.forEach((item) => {
if (item.prop === 'parentId') {
item.options.push(...departments)
}
})
return modalConfig
})
// setup相同的逻辑的抽取: hooks
// 点击search, content的操作
const { contentRef, handleQueryClick, handleResetClick } = usePageContent()
// 点击content, modal的操作
const { modalRef, handleNewClick, handleEditClick } = usePageModal()
// 获取完整的菜单(role.vue中需要)
const mainStore = useMainStore()
const { entireMenus } = storeToRefs(mainStore)
const otherInfo = ref({})//树结构的数据,未配置
function handleElTreeCheck(data1: any, data2: any) {
const menuList = [...data2.checkedKeys, ...data2.halfCheckedKeys]//子节点id,父节点id
console.log(data2.checkedKeys)
otherInfo.value = { menuList }
}
const treeRef = ref<InstanceType<typeof ElTree>>()
//针对树结构数据的回调
function newCallback() {
nextTick(() => {
treeRef.value?.setCheckedKeys([])
})
}
function editCallback(itemData: any) {
nextTick(() => {
const menuIds = mapMenuListToIds(itemData.menuList)
treeRef.value?.setCheckedKeys(menuIds)
})
}
</script>
<style scoped>
.leader {
color: red;
}
.parent {
color: blue;
}
</style>
mapMenuListToIds方法封装
//utils/map-menus.ts
export function mapMenuListToIds(menuList: any[]) {
const ids: number[] = []
function recurseGetId(menus: any[]) {
for (const item of menus) {
if (item.children) {
recurseGetId(item.children)//递归
} else {
ids.push(item.id)
}
}
}
recurseGetId(menuList)
return ids
}
获取选择器整个数据
选择器数据API封装
import hyRequest from '..'
export function getEntireRoles() {
return hyRequest.post({
url: '/role/list'
})
}
export function getEntireDepartments() {
return hyRequest.post({
url: '/department/list'
})
}
export function getEntireMenus() {
return hyRequest.post({
url: '/menu/list'
})
}
调用
//store/main/main.ts
import {
getEntireDepartments,
getEntireMenus,
getEntireRoles
} from '@/service/main/main'
import { defineStore } from 'pinia'
interface IMainState {
entireRoles: any[]
entireDepartments: any[]
entireMenus: any[]
}
const useMainStore = defineStore('main', {
state: (): IMainState => ({
entireRoles: [],
entireDepartments: [],
entireMenus: []
}),
actions: {
async fetchEntireDataAction() {
const rolesResult = await getEntireRoles()
const departmentsResult = await getEntireDepartments()
const menuResult = await getEntireMenus()
// 保存数据
this.entireRoles = rolesResult.data.list
this.entireDepartments = departmentsResult.data.list
this.entireMenus = menuResult.data.list
}
}
})
export default useMainStore
页面相关逻辑
usePageContent.ts
// hooks/usePageConten.ts
import { ref } from 'vue'
import type PageContent from '@/components/page-content/page-content.vue'
function usePageContent() {
const contentRef = ref<InstanceType<typeof PageContent>>()
function handleQueryClick(queryInfo: any) {
contentRef.value?.fetchPageListData(queryInfo)
}
function handleResetClick() {
contentRef.value?.fetchPageListData()
}
return {
contentRef,
handleQueryClick,
handleResetClick
}
}
export default usePageContent
usePageModal.ts
import { ref } from 'vue'
import type PageModal from '@/components/page-modal/page-modal.vue'
type CallbackFnType = (data?: any) => void
function usePageModal(
newCallback?: CallbackFnType,
editCallback?: CallbackFnType
) {
const modalRef = ref<InstanceType<typeof PageModal>>()
function handleNewClick() {
modalRef.value?.setModalVisible()
if (newCallback) newCallback()
}
function handleEditClick(itemData: any) {
// 1.让modal显示出来
modalRef.value?.setModalVisible(false, itemData)
// 2.编辑的回调
if (editCallback) editCallback(itemData)
}
return { modalRef, handleNewClick, handleEditClick }
}
export default usePageModal
按钮权限管理
封装映射方法
// utils/map-menus.ts
/**
* 从菜单映射到按钮的权限
* @param menuList 菜单的列表
* @returns 权限的数组(字符串数组)
*/
export function mapMenusToPermissions(menuList: any[]) {
const permissions: string[] = []
function recurseGetPermission(menus: any[]) {
for (const item of menus) {
if (item.type === 3) {
//permission :"system:users:create"
permissions.push(item.permission)
} else {
recurseGetPermission(item.children ?? [])
}
}
}
recurseGetPermission(menuList)
return permissions
}
使用方法
// store/login.ts
async loginAccountAction(account: IAccount) {
// 重要: 获取登录用户的所有按钮的权限
const permissions = mapMenusToPermissions(userMenus)
this.permissions = permissions
}
loadLocalCacheAction() {
// 2.获取按钮的权限
const permissions = mapMenusToPermissions(userMenus)
this.permissions = permissions
}
根据权限判断按钮是否展示
// hookes/usePermissions.ts
import useLoginStore from '@/store/login/login'
//permissionID 'development:create'
function usePermissions(permissionID: string) {
const loginStore = useLoginStore()
const { permissions } = loginStore
//转换为布尔类型
return !!permissions.find((item) => item.includes(permissionID))
}
export default usePermissions
// page-content.vue
V-if="iscreate"
import usePermissions from '@/hooks/usePermissions'
// 0.获取是否有对应的增删改查的权限
const isCreate = usePermissions(`${props.contentConfig.pageName}:create`)
const isDelete = usePermissions(`${props.contentConfig.pageName}:delete`)
const isUpdate = usePermissions(`${props.contentConfig.pageName}:update`)
const isQuery = usePermissions(`${props.contentConfig.pageName}:query`)
// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
if (!isQuery) return
...
}