更新代码

This commit is contained in:
2026-02-11 14:35:58 +08:00
parent 9339cefae0
commit 2720de7f44
6 changed files with 507 additions and 212 deletions

View File

@@ -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>

View File

@@ -1,20 +1,27 @@
<template> <template>
<div class="pages permission-page"> <div class="pages-sidebar-layout permission-page">
<div class="left-box"> <div class="left-box">
<div class="header"> <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> <template #prefix>
<SearchOutlined style="color: rgba(0, 0, 0, 0.45)" /> <SearchOutlined style="color: rgba(0, 0, 0, 0.45)" />
</template> </template>
</a-input> </a-input>
</div> </div>
<div class="body"> <div class="body">
<a-tree v-model:selectedKeys="selectedMenuKeys" v-model:checkedKeys="checkedMenuKeys" <a-tree
:tree-data="filteredMenuTree" :field-names="{ title: 'title', key: 'id', children: 'children' }" v-model:selectedKeys="selectedMenuKeys"
showLine checkable :check-strictly="true" :expand-on-click-node="false" @select="onMenuSelect" 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"> @check="onMenuCheck">
<template #icon="{ dataRef }"> <template #icon="{ dataRef }">
<FolderOutlined v-if="dataRef.children" /> <ApartmentOutlined v-if="dataRef.children" />
<FileOutlined v-else /> <FileOutlined v-else />
</template> </template>
<template #title="{ dataRef }"> <template #title="{ dataRef }">
@@ -23,22 +30,14 @@
</template> </template>
</a-tree> </a-tree>
</div> </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>
<div class="right-box"> <div class="right-box">
<div class="header"> <div class="header">
<div class="title">{{ selectedMenu?.title || '请选择权限节点' }}</div> <div class="title">{{ selectedMenu?.title || '请选择权限节点' }}</div>
<a-button type="link" size="small" @click="handleRefresh">
<template #icon><ReloadOutlined /></template>
刷新
</a-button>
</div> </div>
<div class="body"> <div class="body">
<save-form v-if="selectedMenu" :menu="menuTree" :menu-id="selectedMenu.id" :parent-id="parentId" <save-form v-if="selectedMenu" :menu="menuTree" :menu-id="selectedMenu.id" :parent-id="parentId"
@@ -52,8 +51,14 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import { SearchOutlined, FolderOutlined, FileOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue' import {
import saveForm from './save.vue' SearchOutlined,
ReloadOutlined,
ApartmentOutlined,
FileOutlined,
PlusOutlined
} from '@ant-design/icons-vue'
import saveForm from './components/SaveForm.vue'
import authApi from '@/api/auth' import authApi from '@/api/auth'
defineOptions({ 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 handleMenuSearch = (e) => {
const keyword = e.target?.value || '' const keyword = e.target?.value || ''
menuFilterText.value = keyword menuFilterText.value = keyword
@@ -141,7 +158,7 @@ const findParentId = (tree, id) => {
return null return null
} }
// 限选择事件 // 限选择事件
const onMenuSelect = (selectedKeys, { selected }) => { const onMenuSelect = (selectedKeys, { selected }) => {
if (selected) { if (selected) {
const menuId = selectedKeys[0] const menuId = selectedKeys[0]
@@ -154,7 +171,7 @@ const onMenuSelect = (selectedKeys, { selected }) => {
} }
} }
// 限勾选事件 // 限勾选事件
const onMenuCheck = (checkedKeys, info) => { const onMenuCheck = (checkedKeys, info) => {
console.log('checkedKeys:', checkedKeys, 'info:', info) console.log('checkedKeys:', checkedKeys, 'info:', info)
} }
@@ -249,86 +266,6 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.permission-page { .permission-page {
display: flex; @extend .pages-sidebar-layout;
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;
}
}
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="pages role-page"> <div class="pages-base-layout role-page">
<div class="tool-bar"> <div class="tool-bar">
<div class="left-panel"> <div class="left-panel">
<a-form layout="inline" :model="searchForm"> <a-form layout="inline" :model="searchForm">
@@ -7,20 +7,14 @@
<a-input v-model:value="searchForm.keyword" placeholder="请输入角色名称" allow-clear <a-input v-model:value="searchForm.keyword" placeholder="请输入角色名称" allow-clear
style="width: 180px" /> style="width: 180px" />
</a-form-item> </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-form-item>
<a-space> <a-space>
<a-button type="primary" @click="handleSearch"> <a-button type="primary" @click="handleSearch">
<template #icon><search-outlined /></template> <template #icon><SearchOutlined /></template>
搜索 搜索
</a-button> </a-button>
<a-button @click="handleReset"> <a-button @click="handleUserReset">
<template #icon><redo-outlined /></template> <template #icon><RedoOutlined /></template>
重置 重置
</a-button> </a-button>
</a-space> </a-space>
@@ -31,25 +25,25 @@
<a-dropdown :disabled="selectedRows.length === 0"> <a-dropdown :disabled="selectedRows.length === 0">
<a-button :disabled="selectedRows.length === 0"> <a-button :disabled="selectedRows.length === 0">
批量操作 批量操作
<down-outlined /> <DownOutlined />
</a-button> </a-button>
<template #overlay> <template #overlay>
<a-menu> <a-menu>
<a-menu-item @click="handleBatchStatus"> <a-menu-item @click="handleBatchStatus">
<check-circle-outlined />批量启用/禁用 <CheckCircleOutlined />批量启用/禁用
</a-menu-item> </a-menu-item>
<a-menu-item @click="handleBatchCopy"> <a-menu-item @click="handleBatchCopy">
<copy-outlined />批量复制 <CopyOutlined />批量复制
</a-menu-item> </a-menu-item>
<a-menu-divider /> <a-menu-divider />
<a-menu-item @click="handleBatchDelete" danger> <a-menu-item @click="handleBatchDelete" danger>
<delete-outlined />批量删除 <DeleteOutlined />批量删除
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
<a-button type="primary" @click="handleAdd"> <a-button type="primary" @click="handleAdd">
<template #icon><plus-outlined /></template> <template #icon><PlusOutlined /></template>
新增 新增
</a-button> </a-button>
</div> </div>
@@ -89,9 +83,18 @@
<script setup> <script setup>
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { message, Modal } from 'ant-design-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 scTable from '@/components/scTable/index.vue'
import saveDialog from './save.vue' import saveDialog from './components/SaveDialog.vue'
import permissionDialog from './permission.vue' import permissionDialog from './components/PermissionDialog.vue'
import authApi from '@/api/auth' import authApi from '@/api/auth'
import { useTable } from '@/hooks/useTable' import { useTable } from '@/hooks/useTable'
@@ -283,6 +286,13 @@ const handlePermission = (record) => {
}, 0) }, 0)
} }
// 重置
const handleUserReset = () => {
searchForm.keyword = ''
searchForm.status = null
handleSearch()
}
// 保存成功回调 // 保存成功回调
const handleSaveSuccess = () => { const handleSaveSuccess = () => {
refreshTable() refreshTable()
@@ -296,14 +306,6 @@ const permissionSuccess = () => {
<style scoped lang="scss"> <style scoped lang="scss">
.role-page { .role-page {
display: flex; @extend .pages-base-layout;
flex-direction: column;
height: 100%;
padding: 0;
.table-content {
flex: 1;
overflow: hidden;
}
} }
</style> </style>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="pages user-page"> <div class="pages-sidebar-layout user-page">
<div class="left-box"> <div class="left-box">
<div class="header"> <div class="header">
<a-input v-model:value="departmentKeyword" placeholder="搜索部门..." allow-clear @change="handleDeptSearch"> <a-input v-model:value="departmentKeyword" placeholder="搜索部门..." allow-clear @change="handleDeptSearch">
<template #prefix> <template #prefix>
<search-outlined style="color: rgba(0, 0, 0, 0.45)" /> <SearchOutlined style="color: rgba(0, 0, 0, 0.45)" />
</template> </template>
</a-input> </a-input>
</div> </div>
@@ -12,8 +12,8 @@
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree" <a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree"
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line @select="onDeptSelect"> :field-names="{ title: 'name', key: 'id', children: 'children' }" show-line @select="onDeptSelect">
<template #icon="{ dataRef }"> <template #icon="{ dataRef }">
<apartment-outlined v-if="dataRef.children && dataRef.children.length > 0" /> <ApartmentOutlined v-if="dataRef.children && dataRef.children.length > 0" />
<user-outlined v-else /> <UserOutlined v-else />
</template> </template>
</a-tree> </a-tree>
</div> </div>
@@ -26,28 +26,14 @@
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear <a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear
style="width: 140px" /> style="width: 140px" />
</a-form-item> </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-form-item>
<a-space> <a-space>
<a-button type="primary" @click="handleSearch"> <a-button type="primary" @click="handleSearch">
<template #icon><search-outlined /></template> <template #icon><SearchOutlined /></template>
搜索 搜索
</a-button> </a-button>
<a-button @click="handleUserReset"> <a-button @click="handleUserReset">
<template #icon><redo-outlined /></template> <template #icon><RedoOutlined /></template>
重置 重置
</a-button> </a-button>
</a-space> </a-space>
@@ -58,22 +44,22 @@
<a-dropdown :disabled="selectedRows.length === 0"> <a-dropdown :disabled="selectedRows.length === 0">
<a-button :disabled="selectedRows.length === 0"> <a-button :disabled="selectedRows.length === 0">
批量操作 批量操作
<down-outlined /> <DownOutlined />
</a-button> </a-button>
<template #overlay> <template #overlay>
<a-menu> <a-menu>
<a-menu-item @click="handleBatchStatus"> <a-menu-item @click="handleBatchStatus">
<check-circle-outlined />批量启用/禁用 <CheckCircleOutlined />批量启用/禁用
</a-menu-item> </a-menu-item>
<a-menu-item @click="handleBatchDepartment"> <a-menu-item @click="handleBatchDepartment">
<apartment-outlined />批量分配部门 <ApartmentOutlined />批量分配部门
</a-menu-item> </a-menu-item>
<a-menu-item @click="handleBatchRoles"> <a-menu-item @click="handleBatchRoles">
<team-outlined />批量分配角色 <TeamOutlined />批量分配角色
</a-menu-item> </a-menu-item>
<a-menu-divider /> <a-menu-divider />
<a-menu-item @click="handleBatchDelete" danger> <a-menu-item @click="handleBatchDelete" danger>
<delete-outlined />批量删除 <DeleteOutlined />批量删除
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>
@@ -81,35 +67,35 @@
<a-dropdown> <a-dropdown>
<a-button> <a-button>
更多 更多
<down-outlined /> <DownOutlined />
</a-button> </a-button>
<template #overlay> <template #overlay>
<a-menu> <a-menu>
<a-menu-item @click="handleImport"> <a-menu-item @click="handleImport">
<import-outlined />导入用户 <ImportOutlined />导入用户
</a-menu-item> </a-menu-item>
<a-menu-item @click="handleExport"> <a-menu-item @click="handleExport">
<export-outlined />导出用户 <ExportOutlined />导出用户
</a-menu-item> </a-menu-item>
<a-menu-item @click="handleDownloadTemplate"> <a-menu-item @click="handleDownloadTemplate">
<download-outlined />下载模板 <DownloadOutlined />下载模板
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
<a-button type="primary" @click="handleAdd"> <a-button type="primary" @click="handleAdd">
<template #icon><plus-outlined /></template> <template #icon><PlusOutlined /></template>
新增 新增
</a-button> </a-button>
</div> </div>
</div> </div>
<div class="table-content"> <div class="table-content">
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading" <scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
:pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable" :pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable"
@paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll"> @paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll">
<template #avatar="{ record }"> <template #avatar="{ record }">
<a-avatar :src="record.avatar" :size="32"> <a-avatar :src="record.avatar" :size="32">
<template #icon><user-outlined /></template> <template #icon><UserOutlined /></template>
</a-avatar> </a-avatar>
</template> </template>
<template #status="{ record }"> <template #status="{ record }">
@@ -151,9 +137,23 @@
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-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 scTable from '@/components/scTable/index.vue'
import saveDialog from './save.vue' import saveDialog from './components/SaveDialog.vue'
import roleDialog from './role.vue' import roleDialog from './components/RoleDialog.vue'
import authApi from '@/api/auth' import authApi from '@/api/auth'
import { useTable } from '@/hooks/useTable' import { useTable } from '@/hooks/useTable'
@@ -194,14 +194,12 @@ const {
// 对话框状态 // 对话框状态
const dialog = reactive({ const dialog = reactive({
save: false, save: false,
role: false, role: false
department: false
}) })
// 弹窗引用 // 弹窗引用
const saveDialogRef = ref(null) const saveDialogRef = ref(null)
const roleDialogRef = ref(null) const roleDialogRef = ref(null)
const departmentDialogRef = ref(null)
// 部门树数据 // 部门树数据
const departmentTree = ref([]) const departmentTree = ref([])
@@ -217,10 +215,10 @@ const columns = [
{ title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' }, { title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' },
{ title: '用户名', dataIndex: 'username', key: 'username', width: 150 }, { title: '用户名', dataIndex: 'username', key: 'username', width: 150 },
{ title: '姓名', dataIndex: 'real_name', key: 'real_name', 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: 'phone', key: 'phone', width: 130 },
{ title: '部门', dataIndex: 'department', key: 'department', slot: 'department', width: 150 }, { title: '部门', dataIndex: 'department', key: 'department', slot: 'department', width: 150, ellipsis: true },
{ title: '角色', dataIndex: 'roles', key: 'roles', width: 200, slot: 'roles' }, { title: '角色', dataIndex: 'roles', key: 'roles', width: 200, slot: 'roles', ellipsis: true },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', slot: 'status' }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', slot: 'status' },
{ title: '最后登录', dataIndex: 'last_login_at', key: 'last_login_at', width: 180 }, { title: '最后登录', dataIndex: 'last_login_at', key: 'last_login_at', width: 180 },
{ title: '操作', dataIndex: 'action', key: 'action', width: 280, align: 'center', slot: 'action', fixed: 'right' } { title: '操作', dataIndex: 'action', key: 'action', width: 280, align: 'center', slot: 'action', fixed: 'right' }
@@ -278,7 +276,7 @@ const handleUserReset = () => {
selectedDeptKeys.value = [] selectedDeptKeys.value = []
departmentKeyword.value = '' departmentKeyword.value = ''
filteredDepartmentTree.value = departmentTree.value filteredDepartmentTree.value = departmentTree.value
handleReset() handleSearch()
} }
// 部门选择事件 // 部门选择事件
@@ -508,44 +506,6 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.user-page { .user-page {
display: flex; @extend .pages-sidebar-layout;
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;
}
}
} }
</style> </style>