更新代码
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<a-form :model="form" :rules="rules" ref="dialogForm" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
|
||||
<!-- 第一行:权限名称和类型 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="权限名称" name="title">
|
||||
<a-input v-model:value="form.title" placeholder="权限名称" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="type" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-radio-group v-model:value="form.type" button-style="solid">
|
||||
<a-radio-button value="menu">菜单</a-radio-button>
|
||||
<a-radio-button value="api">接口</a-radio-button>
|
||||
<a-radio-button value="button">按钮</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 第二行:上级权限和权限编码 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="上级权限" name="parent_id">
|
||||
<a-tree-select v-model:value="form.parent_id" :tree-data="menuOptions"
|
||||
:field-names="menuFieldNames" :tree-default-expand-all="false" show-icon placeholder="顶级权限"
|
||||
allow-clear tree-node-filter-prop="title" :disabled="!!menuId" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="权限编码" name="name">
|
||||
<a-input v-model:value="form.name" placeholder="如: system.user.list" allow-clear />
|
||||
<div class="form-tip">格式:模块.功能.操作,系统唯一标识</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 第三行:路由地址和组件路径 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="路由地址" name="path">
|
||||
<a-input v-model:value="form.path" placeholder="/system/users" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="组件路径" name="component">
|
||||
<a-input v-model:value="form.component" placeholder="system/users/index" allow-clear>
|
||||
<template #addonBefore>pages/</template>
|
||||
</a-input>
|
||||
<div class="form-tip">顶级菜单或有子菜单的父节点不需要填写</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 第四行:菜单图标和排序 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="菜单图标" name="icon">
|
||||
<sc-icon-picker v-model:value="form.icon" placeholder="请选择图标" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number v-model:value="form.sort" :min="0" :max="10000" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<a-divider />
|
||||
|
||||
<!-- 选项设置区域 -->
|
||||
<div class="options-section">
|
||||
<h4>选项设置</h4>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-form-item label="是否隐藏" name="hidden" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
|
||||
<a-checkbox v-model:checked="form.hidden">隐藏菜单</a-checkbox>
|
||||
<a-checkbox v-model:checked="form.hiddenBreadcrumb">隐藏面包屑</a-checkbox>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item label="是否缓存" name="keepAlive" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
|
||||
<a-switch v-model:checked="form.keepAlive" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item label="是否固定" name="affix" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
|
||||
<a-switch v-model:checked="form.affix" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item label="外链" name="target">
|
||||
<a-radio-group v-model:value="form.target" button-style="solid" size="small">
|
||||
<a-radio-button value="null">当前窗口</a-radio-button>
|
||||
<a-radio-button value="_blank">新窗口</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<a-divider />
|
||||
|
||||
<!-- 状态 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-switch v-model:checked="statusChecked" checked-children="启用" un-checked-children="禁用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<a-form-item :wrapper-col="{ span: 18, offset: 4 }" style="margin-top: 32px">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSave" :loading="loading" size="large">保存</a-button>
|
||||
<a-button @click="$emit('cancel')" size="large">取消</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import authApi from '@/api/auth'
|
||||
import scIconPicker from '@/components/scIconPicker/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'PermissionSaveForm'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
menu: { type: [Object, Array], default: () => [] },
|
||||
menuId: { type: [Number, String], default: null },
|
||||
parentId: { type: [Number, String], default: null }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['success', 'cancel'])
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
parent_id: 0,
|
||||
name: '',
|
||||
title: '',
|
||||
path: '',
|
||||
component: '',
|
||||
icon: '',
|
||||
sort: 0,
|
||||
type: 'menu',
|
||||
status: 1,
|
||||
target: null,
|
||||
// meta 字段内容
|
||||
hidden: false,
|
||||
hiddenBreadcrumb: false,
|
||||
keepAlive: false,
|
||||
affix: false
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
const dialogForm = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
// 验证规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入权限编码', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 菜单选项
|
||||
const menuOptions = ref([])
|
||||
const menuFieldNames = {
|
||||
value: 'id',
|
||||
label: 'title',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 状态开关计算属性
|
||||
const statusChecked = computed({
|
||||
get: () => form.status === 1,
|
||||
set: (val) => {
|
||||
form.status = val ? 1 : 0
|
||||
}
|
||||
})
|
||||
|
||||
// 筛单化菜单树,排除自己和子节点
|
||||
const treeToMap = (tree, excludeId = null) => {
|
||||
const map = []
|
||||
tree.forEach(item => {
|
||||
if (item.id === excludeId) return // 排除自己
|
||||
const obj = {
|
||||
id: item.id,
|
||||
parent_id: item.parent_id,
|
||||
title: item.title,
|
||||
children: item.children && item.children.length > 0 ? treeToMap(item.children, excludeId) : null
|
||||
}
|
||||
map.push(obj)
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
// 查找权限节点
|
||||
const findMenuNode = (tree, id) => {
|
||||
for (const node of tree) {
|
||||
if (node.id === id) {
|
||||
return node
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const found = findMenuNode(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 监听菜单树变化
|
||||
watch(
|
||||
() => props.menu,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
// 排除当前编辑的节点,避免选择自己作为父节点
|
||||
menuOptions.value = treeToMap(newVal, props.menuId)
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
// 监听 menuId 变化,从菜单树中查找并赋值
|
||||
watch(
|
||||
() => props.menuId,
|
||||
(newVal) => {
|
||||
if (newVal && props.menu && props.menu.length > 0) {
|
||||
const menuNode = findMenuNode(props.menu, newVal)
|
||||
if (menuNode) {
|
||||
setData(menuNode, props.parentId)
|
||||
}
|
||||
} else if (!newVal) {
|
||||
// 清空表单
|
||||
setData({
|
||||
id: '',
|
||||
parent_id: props.parentId || 0,
|
||||
name: '',
|
||||
title: '',
|
||||
path: '',
|
||||
component: '',
|
||||
icon: '',
|
||||
sort: 0,
|
||||
type: 'menu',
|
||||
status: 1,
|
||||
target: null,
|
||||
hidden: false,
|
||||
hiddenBreadcrumb: false,
|
||||
keepAlive: false,
|
||||
affix: false
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 加载权限详情
|
||||
const loadMenuDetail = async (id) => {
|
||||
try {
|
||||
const res = await authApi.permissions.detail.get(id)
|
||||
if (res.code === 200 && res.data) {
|
||||
setData(res.data, props.parentId)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载权限详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await dialogForm.value.validate()
|
||||
loading.value = true
|
||||
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
id: form.id || undefined,
|
||||
parent_id: form.parent_id || 0,
|
||||
name: form.name,
|
||||
title: form.title,
|
||||
path: form.path,
|
||||
component: form.component,
|
||||
icon: form.icon,
|
||||
sort: form.sort,
|
||||
type: form.type,
|
||||
status: form.status,
|
||||
target: form.target,
|
||||
meta: {
|
||||
hidden: form.hidden,
|
||||
hiddenBreadcrumb: form.hiddenBreadcrumb,
|
||||
keepAlive: form.keepAlive,
|
||||
affix: form.affix
|
||||
}
|
||||
}
|
||||
|
||||
let res = {}
|
||||
if (form.id) {
|
||||
res = await authApi.permissions.edit.put(form.id, submitData)
|
||||
} else {
|
||||
res = await authApi.permissions.add.post(submitData)
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
if (res.code === 200) {
|
||||
message.success('保存成功')
|
||||
emit('success')
|
||||
} else {
|
||||
message.error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('表单验证失败', error)
|
||||
loading.value = false
|
||||
message.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 表单注入数据
|
||||
const setData = (data, pid) => {
|
||||
form.id = data.id || ''
|
||||
form.parent_id = data.parent_id !== undefined ? data.parent_id : (pid || 0)
|
||||
form.name = data.name || ''
|
||||
form.title = data.title || ''
|
||||
form.path = data.path || ''
|
||||
form.component = data.component || ''
|
||||
form.icon = data.icon || ''
|
||||
form.sort = data.sort || 0
|
||||
form.type = data.type || 'menu'
|
||||
form.status = data.status !== undefined ? data.status : 1
|
||||
form.target = data.target || '_blank'
|
||||
|
||||
// 解析 meta 字段
|
||||
const meta = data.meta && typeof data.meta === 'string' ? JSON.parse(data.meta) : (data.meta || {})
|
||||
form.hidden = meta.hidden || false
|
||||
form.hiddenBreadcrumb = meta.hiddenBreadcrumb || false
|
||||
form.keepAlive = meta.keepAlive || false
|
||||
form.affix = meta.affix || false
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (props.menuId) {
|
||||
loadMenuDetail(props.menuId)
|
||||
} else if (props.parentId) {
|
||||
form.parent_id = props.parentId
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
setData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.save-form {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #8c8c8;
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.options-section {
|
||||
margin-top: 16px;
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-divider {
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,27 @@
|
||||
<template>
|
||||
<div class="pages permission-page">
|
||||
<div class="pages-sidebar-layout permission-page">
|
||||
<div class="left-box">
|
||||
<div class="header">
|
||||
<a-input v-model:value="menuFilterText" placeholder="搜索菜单..." allow-clear @change="handleMenuSearch">
|
||||
<a-input v-model:value="menuFilterText" placeholder="搜索权限..." allow-clear @change="handleMenuSearch">
|
||||
<template #prefix>
|
||||
<SearchOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="body">
|
||||
<a-tree v-model:selectedKeys="selectedMenuKeys" v-model:checkedKeys="checkedMenuKeys"
|
||||
:tree-data="filteredMenuTree" :field-names="{ title: 'title', key: 'id', children: 'children' }"
|
||||
showLine checkable :check-strictly="true" :expand-on-click-node="false" @select="onMenuSelect"
|
||||
<a-tree
|
||||
v-model:selectedKeys="selectedMenuKeys"
|
||||
v-model:checkedKeys="checkedMenuKeys"
|
||||
:tree-data="filteredMenuTree"
|
||||
:field-names="{ title: 'title', key: 'id', children: 'children' }"
|
||||
show-line
|
||||
checkable
|
||||
:check-strictly="false"
|
||||
:expand-on-click-node="false"
|
||||
@select="onMenuSelect"
|
||||
@check="onMenuCheck">
|
||||
<template #icon="{ dataRef }">
|
||||
<FolderOutlined v-if="dataRef.children" />
|
||||
<ApartmentOutlined v-if="dataRef.children" />
|
||||
<FileOutlined v-else />
|
||||
</template>
|
||||
<template #title="{ dataRef }">
|
||||
@@ -23,22 +30,14 @@
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd(null)">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
<a-button danger @click="handleDeleteBatch">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-box">
|
||||
<div class="header">
|
||||
<div class="title">{{ selectedMenu?.title || '请选择权限节点' }}</div>
|
||||
<a-button type="link" size="small" @click="handleRefresh">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="body">
|
||||
<save-form v-if="selectedMenu" :menu="menuTree" :menu-id="selectedMenu.id" :parent-id="parentId"
|
||||
@@ -52,8 +51,14 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { SearchOutlined, FolderOutlined, FileOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
import saveForm from './save.vue'
|
||||
import {
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
ApartmentOutlined,
|
||||
FileOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import saveForm from './components/SaveForm.vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
defineOptions({
|
||||
@@ -84,7 +89,19 @@ const loadMenuTree = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 权限搜索
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
loadMenuTree()
|
||||
if (selectedMenu.value) {
|
||||
// 重新获取当前选中的权限详情
|
||||
const menuNode = findMenuNode(menuTree.value, selectedMenu.value.id)
|
||||
if (menuNode) {
|
||||
selectedMenu.value = menuNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索权限
|
||||
const handleMenuSearch = (e) => {
|
||||
const keyword = e.target?.value || ''
|
||||
menuFilterText.value = keyword
|
||||
@@ -141,7 +158,7 @@ const findParentId = (tree, id) => {
|
||||
return null
|
||||
}
|
||||
|
||||
// 权限选择事件
|
||||
// 限制选择事件
|
||||
const onMenuSelect = (selectedKeys, { selected }) => {
|
||||
if (selected) {
|
||||
const menuId = selectedKeys[0]
|
||||
@@ -154,7 +171,7 @@ const onMenuSelect = (selectedKeys, { selected }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 权限勾选事件
|
||||
// 限制勾选事件
|
||||
const onMenuCheck = (checkedKeys, info) => {
|
||||
console.log('checkedKeys:', checkedKeys, 'info:', info)
|
||||
}
|
||||
@@ -249,86 +266,6 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.permission-page {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.left-box {
|
||||
width: 300px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
|
||||
.header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
|
||||
.tree-node-title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tree-node-add {
|
||||
margin-left: 8px;
|
||||
color: #999;
|
||||
display: none;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tree-node-content-wrapper) {
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
.tree-node-add {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
}
|
||||
}
|
||||
|
||||
.right-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.header {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #fff;
|
||||
height: 56px;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
@extend .pages-sidebar-layout;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="pages role-page">
|
||||
<div class="pages-base-layout role-page">
|
||||
<div class="tool-bar">
|
||||
<div class="left-panel">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
@@ -7,20 +7,14 @@
|
||||
<a-input v-model:value="searchForm.keyword" placeholder="请输入角色名称" allow-clear
|
||||
style="width: 180px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="请选择状态" allow-clear style="width: 100px">
|
||||
<a-select-option :value="1">正常</a-select-option>
|
||||
<a-select-option :value="0">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><search-outlined /></template>
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon><redo-outlined /></template>
|
||||
<a-button @click="handleUserReset">
|
||||
<template #icon><RedoOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
@@ -31,25 +25,25 @@
|
||||
<a-dropdown :disabled="selectedRows.length === 0">
|
||||
<a-button :disabled="selectedRows.length === 0">
|
||||
批量操作
|
||||
<down-outlined />
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleBatchStatus">
|
||||
<check-circle-outlined />批量启用/禁用
|
||||
<CheckCircleOutlined />批量启用/禁用
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleBatchCopy">
|
||||
<copy-outlined />批量复制
|
||||
<CopyOutlined />批量复制
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="handleBatchDelete" danger>
|
||||
<delete-outlined />批量删除
|
||||
<DeleteOutlined />批量删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><plus-outlined /></template>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
</div>
|
||||
@@ -89,9 +83,18 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
SearchOutlined,
|
||||
RedoOutlined,
|
||||
PlusOutlined,
|
||||
DownOutlined,
|
||||
CheckCircleOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import scTable from '@/components/scTable/index.vue'
|
||||
import saveDialog from './save.vue'
|
||||
import permissionDialog from './permission.vue'
|
||||
import saveDialog from './components/SaveDialog.vue'
|
||||
import permissionDialog from './components/PermissionDialog.vue'
|
||||
import authApi from '@/api/auth'
|
||||
import { useTable } from '@/hooks/useTable'
|
||||
|
||||
@@ -283,6 +286,13 @@ const handlePermission = (record) => {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleUserReset = () => {
|
||||
searchForm.keyword = ''
|
||||
searchForm.status = null
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
// 保存成功回调
|
||||
const handleSaveSuccess = () => {
|
||||
refreshTable()
|
||||
@@ -296,14 +306,6 @@ const permissionSuccess = () => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.role-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.table-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
@extend .pages-base-layout;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="pages user-page">
|
||||
<div class="pages-sidebar-layout user-page">
|
||||
<div class="left-box">
|
||||
<div class="header">
|
||||
<a-input v-model:value="departmentKeyword" placeholder="搜索部门..." allow-clear @change="handleDeptSearch">
|
||||
<template #prefix>
|
||||
<search-outlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
<SearchOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
@@ -12,8 +12,8 @@
|
||||
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line @select="onDeptSelect">
|
||||
<template #icon="{ dataRef }">
|
||||
<apartment-outlined v-if="dataRef.children && dataRef.children.length > 0" />
|
||||
<user-outlined v-else />
|
||||
<ApartmentOutlined v-if="dataRef.children && dataRef.children.length > 0" />
|
||||
<UserOutlined v-else />
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
@@ -26,28 +26,14 @@
|
||||
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear
|
||||
style="width: 140px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名">
|
||||
<a-input v-model:value="searchForm.real_name" placeholder="请输入姓名" allow-clear
|
||||
style="width: 140px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱">
|
||||
<a-input v-model:value="searchForm.email" placeholder="请输入邮箱" allow-clear
|
||||
style="width: 160px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="请选择状态" allow-clear style="width: 100px">
|
||||
<a-select-option :value="1">正常</a-select-option>
|
||||
<a-select-option :value="0">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><search-outlined /></template>
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleUserReset">
|
||||
<template #icon><redo-outlined /></template>
|
||||
<template #icon><RedoOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
@@ -58,22 +44,22 @@
|
||||
<a-dropdown :disabled="selectedRows.length === 0">
|
||||
<a-button :disabled="selectedRows.length === 0">
|
||||
批量操作
|
||||
<down-outlined />
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleBatchStatus">
|
||||
<check-circle-outlined />批量启用/禁用
|
||||
<CheckCircleOutlined />批量启用/禁用
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleBatchDepartment">
|
||||
<apartment-outlined />批量分配部门
|
||||
<ApartmentOutlined />批量分配部门
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleBatchRoles">
|
||||
<team-outlined />批量分配角色
|
||||
<TeamOutlined />批量分配角色
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="handleBatchDelete" danger>
|
||||
<delete-outlined />批量删除
|
||||
<DeleteOutlined />批量删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
@@ -81,35 +67,35 @@
|
||||
<a-dropdown>
|
||||
<a-button>
|
||||
更多
|
||||
<down-outlined />
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleImport">
|
||||
<import-outlined />导入用户
|
||||
<ImportOutlined />导入用户
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleExport">
|
||||
<export-outlined />导出用户
|
||||
<ExportOutlined />导出用户
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleDownloadTemplate">
|
||||
<download-outlined />下载模板
|
||||
<DownloadOutlined />下载模板
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><plus-outlined /></template>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
|
||||
:pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable"
|
||||
@paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll">
|
||||
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
|
||||
:pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable"
|
||||
@paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll">
|
||||
<template #avatar="{ record }">
|
||||
<a-avatar :src="record.avatar" :size="32">
|
||||
<template #icon><user-outlined /></template>
|
||||
<template #icon><UserOutlined /></template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
@@ -151,9 +137,23 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
SearchOutlined,
|
||||
RedoOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
CheckCircleOutlined,
|
||||
ApartmentOutlined,
|
||||
TeamOutlined,
|
||||
DownOutlined,
|
||||
ImportOutlined,
|
||||
ExportOutlined,
|
||||
DownloadOutlined,
|
||||
UserOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import scTable from '@/components/scTable/index.vue'
|
||||
import saveDialog from './save.vue'
|
||||
import roleDialog from './role.vue'
|
||||
import saveDialog from './components/SaveDialog.vue'
|
||||
import roleDialog from './components/RoleDialog.vue'
|
||||
import authApi from '@/api/auth'
|
||||
import { useTable } from '@/hooks/useTable'
|
||||
|
||||
@@ -194,14 +194,12 @@ const {
|
||||
// 对话框状态
|
||||
const dialog = reactive({
|
||||
save: false,
|
||||
role: false,
|
||||
department: false
|
||||
role: false
|
||||
})
|
||||
|
||||
// 弹窗引用
|
||||
const saveDialogRef = ref(null)
|
||||
const roleDialogRef = ref(null)
|
||||
const departmentDialogRef = ref(null)
|
||||
|
||||
// 部门树数据
|
||||
const departmentTree = ref([])
|
||||
@@ -217,10 +215,10 @@ const columns = [
|
||||
{ title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' },
|
||||
{ title: '用户名', dataIndex: 'username', key: 'username', width: 150 },
|
||||
{ title: '姓名', dataIndex: 'real_name', key: 'real_name', width: 150 },
|
||||
{ title: '邮箱', dataIndex: 'email', key: 'email', width: 180 },
|
||||
{ title: '邮箱', dataIndex: 'email', key: 'email', width: 180, ellipsis: true },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 },
|
||||
{ title: '部门', dataIndex: 'department', key: 'department', slot: 'department', width: 150 },
|
||||
{ title: '角色', dataIndex: 'roles', key: 'roles', width: 200, slot: 'roles' },
|
||||
{ title: '部门', dataIndex: 'department', key: 'department', slot: 'department', width: 150, ellipsis: true },
|
||||
{ title: '角色', dataIndex: 'roles', key: 'roles', width: 200, slot: 'roles', ellipsis: true },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', slot: 'status' },
|
||||
{ title: '最后登录', dataIndex: 'last_login_at', key: 'last_login_at', width: 180 },
|
||||
{ title: '操作', dataIndex: 'action', key: 'action', width: 280, align: 'center', slot: 'action', fixed: 'right' }
|
||||
@@ -278,7 +276,7 @@ const handleUserReset = () => {
|
||||
selectedDeptKeys.value = []
|
||||
departmentKeyword.value = ''
|
||||
filteredDepartmentTree.value = departmentTree.value
|
||||
handleReset()
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
// 部门选择事件
|
||||
@@ -508,44 +506,6 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-page {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.left-box {
|
||||
width: 260px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
|
||||
.header {
|
||||
padding: 12px 16px;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.table-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
@extend .pages-sidebar-layout;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user