权限模块功能基本改写完成
This commit is contained in:
@@ -338,4 +338,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
upload: {
|
||||||
|
url: `system/file/upload`,
|
||||||
|
name: '文件上传',
|
||||||
|
post: async function (params = {}) {
|
||||||
|
return await request.post(this.url, params)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,9 +74,91 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
.search-box {
|
|
||||||
padding: 10px;
|
.tool-bar {
|
||||||
background-color: #f5f5f5;
|
padding: 12px 16px;
|
||||||
border-radius: 10px;
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
:deep(.ant-form) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item-label) {
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮组样式
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索输入框样式
|
||||||
|
:deep(.ant-input),
|
||||||
|
:deep(.ant-select-selector) {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮样式优化
|
||||||
|
:deep(.ant-btn) {
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主按钮特殊样式
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 危险按钮样式
|
||||||
|
:deep(.ant-btn-dangerous) {
|
||||||
|
&:hover {
|
||||||
|
background: #ff4d4f;
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { upload } from "@/api/system";
|
import systemApi from "@/api/system";
|
||||||
|
|
||||||
//上传配置
|
//上传配置
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
apiObj: upload, //上传请求API对象
|
apiObj: systemApi.upload.post, //上传请求API对象
|
||||||
filename: "file", //form请求时文件的key
|
filename: "file", //form请求时文件的key
|
||||||
successCode: 1, //请求完成代码
|
successCode: 1, //请求完成代码
|
||||||
maxSize: 10, //最大文件大小 默认10MB
|
maxSize: 10, //最大文件大小 默认10MB
|
||||||
@@ -15,6 +15,6 @@ export default {
|
|||||||
msg: res.message //分析描述字段结构
|
msg: res.message //分析描述字段结构
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apiObjFile: upload, //附件上传请求API对象
|
apiObjFile: systemApi.upload.post, //附件上传请求API对象
|
||||||
maxSizeFile: 10 //最大文件大小 默认10MB
|
maxSizeFile: 10 //最大文件大小 默认10MB
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,155 +1,283 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<div class="pages department-page">
|
||||||
<el-header>
|
<div class="tool-bar">
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
<a-form layout="inline" :model="searchForm">
|
||||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
<a-form-item>
|
||||||
|
<a-input v-model:value="searchForm.keyword" placeholder="请输入部门名称" allow-clear style="width: 200px" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><search-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleReset">
|
||||||
|
<template #icon><redo-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="right-panel-search">
|
<a-button type="primary" @click="handleAdd">
|
||||||
<el-input v-model="search.keyword" placeholder="部门名称" clearable></el-input>
|
<template #icon><plus-outlined /></template>
|
||||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
</a-button>
|
||||||
|
<a-button danger :disabled="selection.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
<div class="table-content">
|
||||||
<el-main class="nopadding">
|
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading" :pagination="false"
|
||||||
<scTable ref="table" :apiObj="apiObj" row-key="id" :params="search" @selection-change="selectionChange" hidePagination>
|
:row-key="rowKey" :row-selection="rowSelection" @refresh="loadData" @select="handleSelectChange"
|
||||||
<el-table-column type="selection" width="50"></el-table-column>
|
@selectAll="handleSelectAll">
|
||||||
<el-table-column label="#" type="index" width="50"></el-table-column>
|
<template #action="{ record }">
|
||||||
<el-table-column label="部门名称" prop="title"></el-table-column>
|
<a-space>
|
||||||
<el-table-column label="部门标识" prop="name" width="150"></el-table-column>
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
<el-table-column label="排序" prop="sort" width="150"></el-table-column>
|
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||||
<el-table-column label="操作" fixed="right" align="right" width="220">
|
<a-popconfirm title="确定删除该部门吗?" @confirm="handleDelete(record)">
|
||||||
<template #default="scope">
|
<a-button type="link" size="small" danger>删除</a-button>
|
||||||
<el-button-group>
|
</a-popconfirm>
|
||||||
<el-button type="success" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
</a-space>
|
||||||
<el-divider direction="vertical"></el-divider>
|
|
||||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
|
||||||
<el-divider direction="vertical"></el-divider>
|
|
||||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
|
||||||
<template #reference>
|
|
||||||
<el-button type="danger">删除</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-popconfirm>
|
|
||||||
</el-button-group>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</scTable>
|
</scTable>
|
||||||
</el-main>
|
</div>
|
||||||
</el-container>
|
</div>
|
||||||
|
|
||||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
|
|
||||||
|
|
||||||
|
<!-- 新增/编辑部门弹窗 -->
|
||||||
|
<save-dialog v-if="dialog.save" ref="saveDialogRef" @success="handleSaveSuccess" @closed="dialog.save = false" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import saveDialog from './save'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { message, Modal } from 'ant-design-vue'
|
||||||
|
import scTable from '@/components/scTable/index.vue'
|
||||||
|
import saveDialog from './save.vue'
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
export default {
|
defineOptions({
|
||||||
name: 'auth.department',
|
name: 'authDepartment'
|
||||||
components: {
|
})
|
||||||
saveDialog
|
|
||||||
},
|
// 表格引用
|
||||||
data() {
|
const tableRef = ref(null)
|
||||||
return {
|
|
||||||
dialog: {
|
// 对话框状态
|
||||||
save: false,
|
const dialog = reactive({
|
||||||
permission: false
|
save: false
|
||||||
},
|
})
|
||||||
apiObj: this.$API.auth.department.list,
|
|
||||||
selection: [],
|
// 弹窗引用
|
||||||
search: {
|
const saveDialogRef = ref(null)
|
||||||
|
|
||||||
|
// 选中的行
|
||||||
|
const selection = ref([])
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
keyword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 行key
|
||||||
|
const rowKey = 'id'
|
||||||
|
|
||||||
|
// 行选择配置
|
||||||
|
const rowSelection = computed(() => ({
|
||||||
|
selectedRowKeys: selection.value.map(item => item.id),
|
||||||
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
selection.value = selectedRows
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{ title: '#', dataIndex: '_index', key: '_index', width: 60, align: 'center' },
|
||||||
|
{ title: '部门名称', dataIndex: 'title', key: 'title', width: 300 },
|
||||||
|
{ title: '部门标识', dataIndex: 'name', key: 'name', width: 200 },
|
||||||
|
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 100, align: 'center' },
|
||||||
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 220, align: 'center', slot: 'action', fixed: 'right' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 加载部门列表数据
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
is_tree: 1,
|
is_tree: 1,
|
||||||
keyword: null
|
...searchForm
|
||||||
}
|
}
|
||||||
|
const res = await authApi.department.list.get(params)
|
||||||
|
if (res.code === 1) {
|
||||||
|
tableData.value = res.data || []
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '加载数据失败')
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
methods: {
|
console.error('加载部门列表失败:', error)
|
||||||
//添加
|
message.error('加载数据失败')
|
||||||
add(){
|
} finally {
|
||||||
this.dialog.save = true
|
loading.value = false
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//编辑
|
|
||||||
table_edit(row){
|
|
||||||
this.dialog.save = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open('edit').setData(row)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//查看
|
|
||||||
table_show(row){
|
|
||||||
this.dialog.save = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open('show').setData(row)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//删除
|
|
||||||
async table_del(row){
|
|
||||||
var reqData = {id: row.id}
|
|
||||||
var res = await this.$API.auth.department.delete.post(reqData);
|
|
||||||
if(res.code == 1){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
this.$message.success("删除成功")
|
|
||||||
}else{
|
|
||||||
this.$alert(res.message, "提示", {type: 'error'})
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
//批量删除
|
|
||||||
async batch_del(){
|
|
||||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
|
|
||||||
type: 'warning'
|
|
||||||
}).then(async () => {
|
|
||||||
const loading = this.$loading();
|
|
||||||
let ids = this.selection.map(item => item.id)
|
|
||||||
var reqData = {ids: ids}
|
|
||||||
var res = await this.$API.auth.department.delete.post(reqData);
|
|
||||||
if(res.code == 1){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
this.$message.success("删除成功")
|
|
||||||
loading.close();
|
|
||||||
}else{
|
|
||||||
this.$message.error(res.message)
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
|
|
||||||
})
|
// 选择变化处理
|
||||||
},
|
const handleSelectChange = (record, selected, selectedRows) => {
|
||||||
//表格选择后回调事件
|
if (selected) {
|
||||||
selectionChange(selection){
|
selection.value.push(record)
|
||||||
this.selection = selection;
|
} else {
|
||||||
},
|
const index = selection.value.findIndex(item => item.id === record.id)
|
||||||
//搜索
|
if (index > -1) {
|
||||||
upsearch(){
|
selection.value.splice(index, 1)
|
||||||
this.$refs.table.upData(this.search)
|
|
||||||
},
|
|
||||||
//根据ID获取树结构
|
|
||||||
filterTree(id){
|
|
||||||
var target = null;
|
|
||||||
function filter(tree){
|
|
||||||
tree.forEach(item => {
|
|
||||||
if(item.id == id){
|
|
||||||
target = item
|
|
||||||
}
|
|
||||||
if(item.children){
|
|
||||||
filter(item.children)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
filter(this.$refs.table.tableData)
|
|
||||||
return target
|
|
||||||
},
|
|
||||||
//本地更新数据
|
|
||||||
handleSaveSuccess(data, mode){
|
|
||||||
if(mode=='add'){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
}else if(mode=='edit'){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全选/取消全选处理
|
||||||
|
const handleSelectAll = (selected, selectedRows, changeRows) => {
|
||||||
|
if (selected) {
|
||||||
|
changeRows.forEach(record => {
|
||||||
|
if (!selection.value.find(item => item.id === record.id)) {
|
||||||
|
selection.value.push(record)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
changeRows.forEach(record => {
|
||||||
|
const index = selection.value.findIndex(item => item.id === record.id)
|
||||||
|
if (index > -1) {
|
||||||
|
selection.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.keyword = ''
|
||||||
|
selection.value = []
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增部门
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('add')
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看部门
|
||||||
|
const handleView = (record) => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('show').setData(record)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑部门
|
||||||
|
const handleEdit = (record) => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('edit').setData(record)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除部门
|
||||||
|
const handleDelete = async (record) => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.department.delete.post({ id: record.id })
|
||||||
|
if (res.code === 1) {
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除部门失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selection.value.length === 0) {
|
||||||
|
message.warning('请选择要删除的部门')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定删除选中的 ${selection.value.length} 个部门吗?如果删除项中含有子集将会被一并删除`,
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const ids = selection.value.map(item => item.id)
|
||||||
|
const res = await authApi.department.delete.post({ ids })
|
||||||
|
if (res.code === 1) {
|
||||||
|
message.success('删除成功')
|
||||||
|
selection.value = []
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除部门失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取树节点
|
||||||
|
const filterTree = (id) => {
|
||||||
|
let target = null
|
||||||
|
function filter(tree) {
|
||||||
|
for (const item of tree) {
|
||||||
|
if (item.id === id) {
|
||||||
|
target = item
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
filter(item.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filter(tableData.value)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存成功回调
|
||||||
|
const handleSaveSuccess = (data, mode) => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.department-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.table-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,103 +1,156 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')">
|
<a-modal :title="titleMap[mode]" :open="visible" :width="500" :destroy-on-close="true" :footer="null"
|
||||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
@cancel="handleCancel">
|
||||||
<el-form-item label="上级部门" prop="parent_id">
|
<a-form :model="form" :rules="rules" :disabled="mode === 'show'" ref="dialogForm" :label-col="{ span: 5 }"
|
||||||
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
|
:wrapper-col="{ span: 18 }">
|
||||||
</el-form-item>
|
<a-form-item label="上级部门" name="parent_id">
|
||||||
<el-form-item label="部门名称" prop="title">
|
<a-tree-select v-model:value="form.parent_id" :tree-data="departments"
|
||||||
<el-input v-model="form.title" clearable></el-input>
|
:field-names="departmentFieldNames" :tree-default-expand-all="false" placeholder="请选择上级部门"
|
||||||
</el-form-item>
|
allow-clear tree-node-filter-prop="title"
|
||||||
<el-form-item label="部门别名" prop="name">
|
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" />
|
||||||
<el-input v-model="form.name" clearable></el-input>
|
</a-form-item>
|
||||||
</el-form-item>
|
<a-form-item label="部门名称" name="title">
|
||||||
<el-form-item label="排序" prop="sort">
|
<a-input v-model:value="form.title" placeholder="请输入部门名称" allow-clear></a-input>
|
||||||
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
|
</a-form-item>
|
||||||
</el-form-item>
|
<a-form-item label="部门别名" name="name">
|
||||||
</el-form>
|
<a-input v-model:value="form.name" placeholder="请输入部门别名" allow-clear></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="排序" name="sort">
|
||||||
|
<a-input-number v-model:value="form.sort" :min="1" :step="1" style="width: 100%" placeholder="请输入排序" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="visible=false" >取 消</el-button>
|
<a-button @click="handleCancel">取 消</a-button>
|
||||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
<a-button v-if="mode !== 'show'" type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive } from 'vue'
|
||||||
emits: ['success', 'closed'],
|
import { message } from 'ant-design-vue'
|
||||||
data() {
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'closed'])
|
||||||
|
|
||||||
|
const mode = ref('add')
|
||||||
|
const titleMap = {
|
||||||
|
add: '新增部门',
|
||||||
|
edit: '编辑部门',
|
||||||
|
show: '查看部门'
|
||||||
|
}
|
||||||
|
const visible = ref(false)
|
||||||
|
const isSaving = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
sort: 1,
|
||||||
|
parent_id: null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const dialogForm = ref()
|
||||||
|
|
||||||
|
// 验证规则
|
||||||
|
const rules = {
|
||||||
|
title: [{ required: true, message: '请输入部门名称', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '请输入部门别名', trigger: 'blur' }],
|
||||||
|
sort: [
|
||||||
|
{ required: true, message: '请输入排序', trigger: 'change' },
|
||||||
|
{ type: 'number', message: '排序必须为数字', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门数据
|
||||||
|
const departments = ref([])
|
||||||
|
const departmentFieldNames = {
|
||||||
|
title: 'title',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示对话框
|
||||||
|
const open = (openMode = 'add') => {
|
||||||
|
mode.value = openMode
|
||||||
|
visible.value = true
|
||||||
return {
|
return {
|
||||||
mode: "add",
|
setData,
|
||||||
titleMap: {
|
open,
|
||||||
add: '新增',
|
close
|
||||||
edit: '编辑',
|
|
||||||
show: '查看'
|
|
||||||
},
|
|
||||||
visible: false,
|
|
||||||
isSaveing: false,
|
|
||||||
//表单数据
|
|
||||||
form: {id:"",title: "",name: "",sort: 1,parent_id: ""},
|
|
||||||
//验证规则
|
|
||||||
rules: {
|
|
||||||
sort: [{required: true, message: '请输入排序', trigger: 'change'}],
|
|
||||||
title: [{required: true, message: '请输入角色名称'}],
|
|
||||||
name: [{required: true, message: '请输入角色别名'}]
|
|
||||||
},
|
|
||||||
//所需数据选项
|
|
||||||
groups: [],
|
|
||||||
groupsProps: {
|
|
||||||
value: "id",
|
|
||||||
label: "title",
|
|
||||||
emitPath: false,
|
|
||||||
checkStrictly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getGroup()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//显示
|
|
||||||
open(mode='add'){
|
|
||||||
this.mode = mode;
|
|
||||||
this.visible = true;
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
//加载树数据
|
|
||||||
async getGroup(){
|
|
||||||
var res = await this.$API.auth.department.list.get({is_tree: 1});
|
|
||||||
this.groups = res.data;
|
|
||||||
},
|
|
||||||
//表单提交方法
|
|
||||||
submit(){
|
|
||||||
this.$refs.dialogForm.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
this.isSaveing = true;
|
|
||||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
|
||||||
var res = {}
|
|
||||||
if(this.mode == 'add'){
|
|
||||||
res = await this.$API.auth.department.add.post(this.form);
|
|
||||||
}else{
|
|
||||||
res = await this.$API.auth.department.edit.post(this.form);
|
|
||||||
}
|
|
||||||
this.isSaveing = false;
|
|
||||||
if(res.code == 1){
|
|
||||||
this.$emit('success', this.form, this.mode)
|
|
||||||
this.visible = false;
|
|
||||||
this.$message.success("操作成功")
|
|
||||||
}else{
|
|
||||||
this.$message.error(res.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//表单注入数据
|
|
||||||
setData(data){
|
|
||||||
this.form.id = data.id
|
|
||||||
this.form.title = data.title
|
|
||||||
this.form.name = data.name
|
|
||||||
this.form.sort = data.sort
|
|
||||||
this.form.parent_id = data.parent_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('closed')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单提交方法
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
await dialogForm.value.validate()
|
||||||
|
isSaving.value = true
|
||||||
|
let res = {}
|
||||||
|
form.parent_id = form.parent_id || 0
|
||||||
|
|
||||||
|
if (mode.value === 'add') {
|
||||||
|
res = await authApi.department.add.post(form)
|
||||||
|
} else {
|
||||||
|
res = await authApi.department.edit.post(form)
|
||||||
|
}
|
||||||
|
|
||||||
|
isSaving.value = false
|
||||||
|
if (res.code === 1) {
|
||||||
|
emit('success', form, mode.value)
|
||||||
|
visible.value = false
|
||||||
|
message.success('操作成功')
|
||||||
|
} else {
|
||||||
|
message.error(res.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败', error)
|
||||||
|
isSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载部门树数据
|
||||||
|
const loadDepartments = async () => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.department.list.get({ is_tree: 1 })
|
||||||
|
departments.value = res.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载部门树失败:', error)
|
||||||
|
message.error('加载部门树失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单注入数据
|
||||||
|
const setData = (data) => {
|
||||||
|
form.id = data.id
|
||||||
|
form.title = data.title
|
||||||
|
form.name = data.name
|
||||||
|
form.sort = data.sort
|
||||||
|
form.parent_id = data.parent_id || null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
loadDepartments()
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
|
|||||||
@@ -1,170 +1,330 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<div class="pages permission-page">
|
||||||
<el-aside width="300px" v-loading="menuloading">
|
<div class="left-box">
|
||||||
<el-container>
|
<div class="header">
|
||||||
<el-header>
|
<a-input v-model:value="menuFilterText" placeholder="搜索菜单..." allow-clear @change="handleMenuSearch">
|
||||||
<el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
|
<template #prefix>
|
||||||
</el-header>
|
<search-outlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||||
<el-main class="nopadding">
|
|
||||||
<el-tree ref="menu" class="menu" node-key="id" :data="menuList" :props="menuProps" draggable highlight-current :expand-on-click-node="false" check-strictly show-checkbox :filter-node-method="menuFilterNode" @node-click="menuClick" @node-drop="nodeDrop">
|
|
||||||
|
|
||||||
<template #default="{node, data}">
|
|
||||||
<span class="custom-tree-node el-tree-node__label">
|
|
||||||
<span class="label">
|
|
||||||
{{ node.label }}
|
|
||||||
</span>
|
|
||||||
<span class="do">
|
|
||||||
<el-icon @click.stop="add(node, data)"><el-icon-plus /></el-icon>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
</a-input>
|
||||||
</el-tree>
|
</div>
|
||||||
</el-main>
|
<div class="body">
|
||||||
<el-footer style="height:51px;">
|
<a-tree v-model:selectedKeys="selectedMenuKeys" v-model:checkedKeys="checkedMenuKeys"
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="add()"></el-button>
|
:tree-data="filteredMenuTree" :field-names="{ title: 'title', key: 'id', children: 'children' }"
|
||||||
<el-button type="danger" plain icon="el-icon-delete" @click="delMenu"></el-button>
|
showLine checkable :check-strictly="true" :expand-on-click-node="false"
|
||||||
</el-footer>
|
@select="onMenuSelect" @check="onMenuCheck">
|
||||||
</el-container>
|
<template #icon="{ dataRef }">
|
||||||
</el-aside>
|
<folder-outlined v-if="dataRef.children" />
|
||||||
<el-container>
|
<file-outlined v-else />
|
||||||
<el-main class="nopadding" style="padding:20px;" ref="main">
|
</template>
|
||||||
<save ref="save" :menu="menuList"></save>
|
<template #title="{ dataRef }">
|
||||||
</el-main>
|
<span class="tree-node-title">{{ dataRef.title }}</span>
|
||||||
</el-container>
|
<plus-outlined class="tree-node-add" @click.stop="handleAdd(dataRef)" />
|
||||||
</el-container>
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleAdd(null)">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
新增
|
||||||
|
</a-button>
|
||||||
|
<a-button danger @click="handleDeleteBatch">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-box">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">{{ selectedMenu?.title || '请选择菜单' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<save-form v-if="selectedMenu" :menu="menuTree" :menu-id="selectedMenu.id" :parent-id="parentId"
|
||||||
|
@success="handleSaveSuccess" />
|
||||||
|
<a-empty v-else description="请选择左侧菜单后操作" :image-size="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
let newMenuIndex = 1;
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import save from './save'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
|
import saveForm from './save.vue'
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
export default {
|
defineOptions({
|
||||||
name: "auth.permission",
|
name: 'authPermission'
|
||||||
components: {
|
})
|
||||||
save
|
|
||||||
},
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
menuloading: false,
|
|
||||||
menuList: [],
|
|
||||||
menuProps: {
|
|
||||||
label: (data)=>{
|
|
||||||
return data.title
|
|
||||||
}
|
|
||||||
},
|
|
||||||
menuFilterText: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
menuFilterText(val){
|
|
||||||
this.$refs.menu.filter(val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getMenu();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//加载树数据
|
|
||||||
async getMenu(){
|
|
||||||
this.menuloading = true
|
|
||||||
var res = await this.$API.auth.menu.list.get({is_tree: 1});
|
|
||||||
if(res.code == 1){
|
|
||||||
this.menuloading = false
|
|
||||||
this.menuList = res.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//树点击
|
|
||||||
menuClick(data, node){
|
|
||||||
var pid = node.level==1?undefined:node.parent.data.id;
|
|
||||||
this.$refs.save.setData(data, pid)
|
|
||||||
this.$refs.main.$el.scrollTop = 0
|
|
||||||
},
|
|
||||||
//树过滤
|
|
||||||
menuFilterNode(value, data){
|
|
||||||
if (!value) return true;
|
|
||||||
var targetText = data.title;
|
|
||||||
return targetText.indexOf(value) !== -1;
|
|
||||||
},
|
|
||||||
//树拖拽
|
|
||||||
nodeDrop(draggingNode, dropNode, dropType){
|
|
||||||
if(dropType == 'before'){
|
|
||||||
|
|
||||||
}else if(dropType == 'after'){
|
// 菜单树数据
|
||||||
|
const menuTree = ref([])
|
||||||
|
const filteredMenuTree = ref([])
|
||||||
|
const selectedMenuKeys = ref([])
|
||||||
|
const checkedMenuKeys = ref([])
|
||||||
|
const menuFilterText = ref('')
|
||||||
|
|
||||||
}else if(dropType == 'inner'){
|
// 当前选中的菜单
|
||||||
|
const selectedMenu = ref(null)
|
||||||
|
const parentId = ref(null)
|
||||||
|
|
||||||
|
// 加载菜单树
|
||||||
|
const loadMenuTree = async () => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.menu.list.get({ is_tree: 1 })
|
||||||
|
if (res.code === 1) {
|
||||||
|
menuTree.value = res.data || []
|
||||||
|
filteredMenuTree.value = res.data || []
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
//增加
|
console.error('加载菜单树失败:', error)
|
||||||
async add(node, data){
|
}
|
||||||
var newMenuName = "新菜单" + newMenuIndex++;
|
}
|
||||||
var newMenuData = {
|
|
||||||
parent_id: data ? data.id : 0,
|
// 菜单搜索
|
||||||
name: newMenuName,
|
const handleMenuSearch = (e) => {
|
||||||
path: "",
|
const keyword = e.target?.value || ''
|
||||||
component: "",
|
menuFilterText.value = keyword
|
||||||
title: newMenuName,
|
if (!keyword) {
|
||||||
type: "menu",
|
filteredMenuTree.value = menuTree.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归过滤菜单树
|
||||||
|
const filterTree = (nodes) => {
|
||||||
|
return nodes.reduce((acc, node) => {
|
||||||
|
const isMatch = node.title && node.title.toLowerCase().includes(keyword.toLowerCase())
|
||||||
|
const filteredChildren = node.children ? filterTree(node.children) : []
|
||||||
|
|
||||||
|
if (isMatch || filteredChildren.length > 0) {
|
||||||
|
acc.push({
|
||||||
|
...node,
|
||||||
|
children: filteredChildren.length > 0 ? filteredChildren : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredMenuTree.value = filterTree(menuTree.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找菜单节点
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找父节点ID
|
||||||
|
const findParentId = (tree, id) => {
|
||||||
|
for (const node of tree) {
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
const child = node.children.find(child => child.id === id)
|
||||||
|
if (child) {
|
||||||
|
return node.id
|
||||||
|
}
|
||||||
|
const found = findParentId(node.children, id)
|
||||||
|
if (found !== null) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单选择事件
|
||||||
|
const onMenuSelect = (selectedKeys, { selected, node }) => {
|
||||||
|
if (selected) {
|
||||||
|
const menuId = selectedKeys[0]
|
||||||
|
const menuNode = findMenuNode(menuTree.value, menuId)
|
||||||
|
selectedMenu.value = menuNode
|
||||||
|
parentId.value = findParentId(menuTree.value, menuId)
|
||||||
|
} else {
|
||||||
|
selectedMenu.value = null
|
||||||
|
parentId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单勾选事件
|
||||||
|
const onMenuCheck = (checkedKeys, info) => {
|
||||||
|
console.log('checkedKeys:', checkedKeys, 'info:', info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增菜单
|
||||||
|
const handleAdd = async (parentNode) => {
|
||||||
|
try {
|
||||||
|
let newMenuData = {
|
||||||
|
parent_id: parentNode ? parentNode.id : 0,
|
||||||
|
name: '新菜单',
|
||||||
|
path: '',
|
||||||
|
component: '',
|
||||||
|
title: '新菜单',
|
||||||
|
type: 'menu',
|
||||||
sort: 0
|
sort: 0
|
||||||
}
|
}
|
||||||
this.menuloading = true
|
|
||||||
var res = await this.$API.auth.menu.add.post(newMenuData)
|
const res = await authApi.menu.add.post(newMenuData)
|
||||||
this.menuloading = false
|
if (res.code === 1) {
|
||||||
newMenuData.id = res.data.id
|
newMenuData.id = res.data.id
|
||||||
|
message.success('添加成功')
|
||||||
|
await loadMenuTree()
|
||||||
|
|
||||||
this.$refs.menu.append(newMenuData, node)
|
// 选中新增的菜单
|
||||||
this.$refs.menu.setCurrentKey(newMenuData.id)
|
selectedMenuKeys.value = [newMenuData.id]
|
||||||
var pid = node ? node.data.id : ""
|
const menuNode = findMenuNode(menuTree.value, newMenuData.id)
|
||||||
this.$refs.save.setData(newMenuData, pid)
|
selectedMenu.value = menuNode
|
||||||
},
|
parentId.value = parentNode ? parentNode.id : null
|
||||||
//删除菜单
|
} else {
|
||||||
async delMenu(){
|
message.error(res.message || '添加失败')
|
||||||
var CheckedNodes = this.$refs.menu.getCheckedNodes()
|
}
|
||||||
if(CheckedNodes.length == 0){
|
} catch (error) {
|
||||||
this.$message.warning("请选择需要删除的项")
|
console.error('添加菜单失败:', error)
|
||||||
return false;
|
message.error('添加失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除菜单
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
if (checkedMenuKeys.value.length === 0) {
|
||||||
|
message.warning('请选择需要删除的菜单')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var confirm = await this.$confirm('确认删除已选择的菜单吗?','提示', {
|
Modal.confirm({
|
||||||
type: 'warning',
|
title: '确认删除',
|
||||||
confirmButtonText: '删除',
|
content: `确定删除已选择的 ${checkedMenuKeys.value.length} 个菜单吗?`,
|
||||||
confirmButtonClass: 'el-button--danger'
|
okText: '删除',
|
||||||
}).catch(() => {})
|
okType: 'danger',
|
||||||
if(confirm != 'confirm'){
|
cancelText: '取消',
|
||||||
return false
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.menu.delete.post({ ids: checkedMenuKeys.value })
|
||||||
|
if (res.code === 1) {
|
||||||
|
message.success('删除成功')
|
||||||
|
|
||||||
|
// 如果当前选中的菜单被删除了,清空选择
|
||||||
|
if (selectedMenu.value && checkedMenuKeys.value.includes(selectedMenu.value.id)) {
|
||||||
|
selectedMenu.value = null
|
||||||
|
selectedMenuKeys.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
this.menuloading = true
|
checkedMenuKeys.value = []
|
||||||
var reqData = {
|
await loadMenuTree()
|
||||||
ids: CheckedNodes.map(item => item.id)
|
} else {
|
||||||
|
message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除菜单失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
}
|
}
|
||||||
var res = await this.$API.auth.menu.delete.post(reqData)
|
|
||||||
this.menuloading = false
|
|
||||||
|
|
||||||
if(res.code == 1){
|
|
||||||
CheckedNodes.forEach(item => {
|
|
||||||
var node = this.$refs.menu.getNode(item)
|
|
||||||
if(node.isCurrent){
|
|
||||||
this.$refs.save.setData({})
|
|
||||||
}
|
}
|
||||||
this.$refs.menu.remove(item)
|
|
||||||
})
|
})
|
||||||
}else{
|
}
|
||||||
this.$message.warning(res.msg)
|
|
||||||
|
// 保存成功回调
|
||||||
|
const handleSaveSuccess = async () => {
|
||||||
|
await loadMenuTree()
|
||||||
|
// 重新设置当前选中的菜单
|
||||||
|
if (selectedMenu.value) {
|
||||||
|
const menuNode = findMenuNode(menuTree.value, selectedMenu.value.id)
|
||||||
|
selectedMenu.value = menuNode
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadMenuTree()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
|
|
||||||
.custom-tree-node .label {display: flex;align-items: center;;height: 100%;}
|
|
||||||
.custom-tree-node .label .el-tag {margin-left: 5px;}
|
|
||||||
.custom-tree-node .do {display: none;}
|
|
||||||
.custom-tree-node .do i {margin-left:5px;color: #999;}
|
|
||||||
.custom-tree-node .do i:hover {color: #333;}
|
|
||||||
|
|
||||||
.custom-tree-node:hover .do {display: inline-block;}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,209 +1,305 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="40">
|
<div class="save-form">
|
||||||
<el-col v-if="!form.id">
|
<a-row :gutter="24">
|
||||||
<el-empty description="请选择左侧菜单后操作" :image-size="100"></el-empty>
|
<a-col :span="12">
|
||||||
</el-col>
|
<a-card title="基本信息" :bordered="false" size="small">
|
||||||
<template v-else>
|
<a-form :model="form" :rules="rules" ref="dialogForm" :label-col="{ span: 6 }"
|
||||||
<el-col :lg="12">
|
:wrapper-col="{ span: 16 }">
|
||||||
<h2>{{form.title || "新增菜单"}}</h2>
|
<a-form-item label="显示名称" name="title">
|
||||||
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
|
<a-input v-model:value="form.title" placeholder="菜单显示名字" allow-clear />
|
||||||
<el-form-item label="显示名称" prop="title">
|
</a-form-item>
|
||||||
<el-input v-model="form.title" clearable placeholder="菜单显示名字"></el-input>
|
<a-form-item label="上级菜单" name="parent_id">
|
||||||
</el-form-item>
|
<a-tree-select v-model:value="form.parent_id" :tree-data="menuOptions"
|
||||||
<el-form-item label="上级菜单" prop="parentId">
|
:field-names="menuFieldNames" :tree-default-expand-all="false" show-icon
|
||||||
<el-cascader v-model="form.parentId" :options="menuOptions" :props="menuProps" :show-all-levels="false" placeholder="顶级菜单" clearable disabled></el-cascader>
|
placeholder="顶级菜单" allow-clear tree-node-filter-prop="title"
|
||||||
</el-form-item>
|
:disabled="!!menuId" />
|
||||||
<el-form-item label="类型" prop="type">
|
</a-form-item>
|
||||||
<el-radio-group v-model="form.type">
|
<a-form-item label="类型" name="type">
|
||||||
<el-radio-button value="menu">菜单</el-radio-button>
|
<a-radio-group v-model:value="form.type" button-style="solid">
|
||||||
<el-radio-button value="iframe">Iframe</el-radio-button>
|
<a-radio-button value="menu">菜单</a-radio-button>
|
||||||
<el-radio-button value="link">外链</el-radio-button>
|
<a-radio-button value="iframe">Iframe</a-radio-button>
|
||||||
<el-radio-button value="button">按钮</el-radio-button>
|
<a-radio-button value="link">外链</a-radio-button>
|
||||||
</el-radio-group>
|
<a-radio-button value="button">按钮</a-radio-button>
|
||||||
</el-form-item>
|
</a-radio-group>
|
||||||
<el-form-item label="别名" prop="name">
|
</a-form-item>
|
||||||
<el-input v-model="form.name" clearable placeholder="菜单别名"></el-input>
|
<a-form-item label="别名" name="name">
|
||||||
<div class="el-form-item-msg">系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏</div>
|
<a-input v-model:value="form.name" placeholder="菜单别名" allow-clear />
|
||||||
</el-form-item>
|
<div class="form-item-msg">系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏</div>
|
||||||
<el-form-item label="菜单图标" prop="icon">
|
</a-form-item>
|
||||||
<sc-icon-select v-model="form.icon" clearable></sc-icon-select>
|
<a-form-item label="菜单图标" name="icon">
|
||||||
</el-form-item>
|
<a-input v-model:value="form.icon" placeholder="请输入图标类名" allow-clear />
|
||||||
<el-form-item label="路由地址" prop="path">
|
</a-form-item>
|
||||||
<el-input v-model="form.path" clearable placeholder=""></el-input>
|
<a-form-item label="路由地址" name="path">
|
||||||
</el-form-item>
|
<a-input v-model:value="form.path" placeholder="" allow-clear />
|
||||||
<el-form-item label="重定向" prop="redirect">
|
</a-form-item>
|
||||||
<el-input v-model="form.redirect" clearable placeholder=""></el-input>
|
<a-form-item label="重定向" name="redirect">
|
||||||
</el-form-item>
|
<a-input v-model:value="form.redirect" placeholder="" allow-clear />
|
||||||
<el-form-item label="排序" prop="sort">
|
</a-form-item>
|
||||||
<el-input type="number" v-model="form.sort" class="mx-4" :min="0" :max="100" controls-position="right" clearable />
|
<a-form-item label="排序" name="sort">
|
||||||
</el-form-item>
|
<a-input-number v-model:value="form.sort" :min="0" :max="100" style="width: 100%" />
|
||||||
<el-form-item label="菜单高亮" prop="active">
|
</a-form-item>
|
||||||
<el-input v-model="form.active" clearable placeholder=""></el-input>
|
<a-form-item label="菜单高亮" name="active">
|
||||||
<div class="el-form-item-msg">子节点或详情页需要高亮的上级菜单路由地址</div>
|
<a-input v-model:value="form.active" placeholder="" allow-clear />
|
||||||
</el-form-item>
|
<div class="form-item-msg">子节点或详情页需要高亮的上级菜单路由地址</div>
|
||||||
<el-form-item label="视图" prop="component">
|
</a-form-item>
|
||||||
<el-input v-model="form.component" clearable placeholder="">
|
<a-form-item label="视图" name="component">
|
||||||
<template #prepend>pages/</template>
|
<a-input v-model:value="form.component" placeholder="" allow-clear>
|
||||||
</el-input>
|
<template #addonBefore>pages/</template>
|
||||||
<div class="el-form-item-msg">如父节点、链接或Iframe等没有视图的菜单不需要填写</div>
|
</a-input>
|
||||||
</el-form-item>
|
<div class="form-item-msg">如父节点、链接或Iframe等没有视图的菜单不需要填写</div>
|
||||||
<el-form-item label="颜色" prop="color">
|
</a-form-item>
|
||||||
<el-color-picker v-model="form.color" :predefine="predefineColors"></el-color-picker>
|
<a-form-item label="颜色" name="color">
|
||||||
</el-form-item>
|
<a-input v-model:value="form.color" placeholder="请输入颜色值" allow-clear />
|
||||||
<el-form-item label="是否隐藏" prop="hidden">
|
</a-form-item>
|
||||||
<el-checkbox v-model="form.hidden">隐藏菜单</el-checkbox>
|
<a-form-item label="是否隐藏" name="hidden">
|
||||||
<el-checkbox v-model="form.hiddenBreadcrumb">隐藏面包屑</el-checkbox>
|
<a-checkbox v-model:checked="form.hidden">隐藏菜单</a-checkbox>
|
||||||
<div class="el-form-item-msg">菜单不显示在导航中,但用户依然可以访问,例如详情页</div>
|
<a-checkbox v-model:checked="form.hiddenBreadcrumb">隐藏面包屑</a-checkbox>
|
||||||
</el-form-item>
|
<div class="form-item-msg">菜单不显示在导航中,但用户依然可以访问,例如详情页</div>
|
||||||
<el-form-item label="是否固定" prop="affix">
|
</a-form-item>
|
||||||
<el-switch v-model="form.affix" />
|
<a-form-item label="是否固定" name="affix">
|
||||||
<div class="el-form-item-msg">是否固定,类似首页控制台在标签中是没有关闭按钮的</div>
|
<a-switch v-model:checked="form.affix" />
|
||||||
</el-form-item>
|
<div class="form-item-msg">是否固定,类似首页控制台在标签中是没有关闭按钮的</div>
|
||||||
<el-form-item label="是否全屏" prop="fullpage">
|
</a-form-item>
|
||||||
<el-switch v-model="form.fullpage" />
|
<a-form-item label="是否全屏" name="fullpage">
|
||||||
<div class="el-form-item-msg">是否全屏</div>
|
<a-switch v-model:checked="form.fullpage" />
|
||||||
</el-form-item>
|
<div class="form-item-msg">是否全屏</div>
|
||||||
<el-form-item>
|
</a-form-item>
|
||||||
<el-button type="primary" @click="save" :loading="loading">保 存</el-button>
|
<a-form-item :wrapper-col="{ span: 16, offset: 6 }">
|
||||||
</el-form-item>
|
<a-button type="primary" @click="handleSave" :loading="loading">保存</a-button>
|
||||||
</el-form>
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
</el-col>
|
</a-card>
|
||||||
<el-col :lg="12" class="apilist">
|
</a-col>
|
||||||
<h2>接口权限</h2>
|
<a-col :span="12">
|
||||||
<sc-form-table v-model="form.apiList" :addTemplate="apiListAddTemplate" placeholder="暂无匹配接口权限">
|
<a-card title="接口权限" :bordered="false" size="small">
|
||||||
<el-table-column prop="code" label="标识" width="150">
|
<a-button type="dashed" block @click="addApiRow">
|
||||||
<template #default="scope">
|
<template #icon><plus-outlined /></template>
|
||||||
<el-input v-model="scope.row.code" placeholder="请输入内容"></el-input>
|
添加接口权限
|
||||||
|
</a-button>
|
||||||
|
<a-list :data-source="form.apiList" item-layout="horizontal" style="margin-top: 16px">
|
||||||
|
<template #renderItem="{ item, index }">
|
||||||
|
<a-list-item>
|
||||||
|
<a-row :gutter="8" style="width: 100%">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-input v-model:value="item.code" placeholder="标识" allow-clear />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="14">
|
||||||
|
<a-input v-model:value="item.url" placeholder="Api url" allow-clear />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="2">
|
||||||
|
<a-button type="text" danger @click="removeApiRow(index)">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</a-list>
|
||||||
<el-table-column prop="url" label="Api url">
|
<a-empty v-if="form.apiList.length === 0" description="暂无接口权限" style="margin-top: 16px" />
|
||||||
<template #default="scope">
|
</a-card>
|
||||||
<el-input v-model="scope.row.url" placeholder="请输入内容"></el-input>
|
</a-col>
|
||||||
</template>
|
</a-row>
|
||||||
</el-table-column>
|
</div>
|
||||||
</sc-form-table>
|
|
||||||
</el-col>
|
|
||||||
</template>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import scIconSelect from '@/components/scIconSelect'
|
import { ref, reactive, watch, onMounted } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
components: {
|
menu: { type: Object, default: () => [] },
|
||||||
scIconSelect
|
menuId: { type: [Number, String], default: null },
|
||||||
},
|
parentId: { type: [Number, String], default: null }
|
||||||
props: {
|
})
|
||||||
menu: { type: Object, default: () => {} },
|
|
||||||
},
|
const emit = defineEmits(['success'])
|
||||||
data(){
|
|
||||||
return {
|
// 表单数据
|
||||||
form: {
|
const form = reactive({
|
||||||
id: "",
|
id: '',
|
||||||
parentId: "",
|
parent_id: 0,
|
||||||
name: "",
|
name: '',
|
||||||
path: "",
|
path: '',
|
||||||
component: "",
|
component: '',
|
||||||
redirect: "",
|
redirect: '',
|
||||||
sort: 0,
|
sort: 0,
|
||||||
title: "",
|
title: '',
|
||||||
icon: "",
|
icon: '',
|
||||||
active: "",
|
active: '',
|
||||||
color: "",
|
color: '',
|
||||||
type: "menu",
|
type: 'menu',
|
||||||
affix: false,
|
affix: false,
|
||||||
|
hidden: false,
|
||||||
|
hiddenBreadcrumb: false,
|
||||||
|
fullpage: false,
|
||||||
apiList: []
|
apiList: []
|
||||||
},
|
})
|
||||||
menuOptions: [],
|
|
||||||
menuProps: {
|
// 表单引用
|
||||||
|
const dialogForm = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 验证规则
|
||||||
|
const rules = {
|
||||||
|
title: [{ required: true, message: '请输入显示名称', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '请输入别名', trigger: 'blur' }],
|
||||||
|
type: [{ required: true, message: '请选择类型', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单选项
|
||||||
|
const menuOptions = ref([])
|
||||||
|
const menuFieldNames = {
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'title',
|
label: 'title',
|
||||||
checkStrictly: true
|
children: 'children'
|
||||||
},
|
}
|
||||||
predefineColors: [
|
|
||||||
'#ff4500',
|
|
||||||
'#ff8c00',
|
|
||||||
'#ffd700',
|
|
||||||
'#67C23A',
|
|
||||||
'#00ced1',
|
|
||||||
'#409EFF',
|
|
||||||
'#c71585'
|
|
||||||
],
|
|
||||||
rules: [],
|
|
||||||
apiListAddTemplate: {
|
|
||||||
code: "",
|
|
||||||
url: ""
|
|
||||||
},
|
|
||||||
loading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
menu: {
|
|
||||||
handler(){
|
|
||||||
this.menuOptions = this.treeToMap(this.menu)
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
|
|
||||||
},
|
// 简单化菜单
|
||||||
methods: {
|
const treeToMap = (tree) => {
|
||||||
//简单化菜单
|
|
||||||
treeToMap(tree){
|
|
||||||
const map = []
|
const map = []
|
||||||
tree.forEach(item => {
|
tree.forEach(item => {
|
||||||
var obj = {
|
const obj = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
parentId: item.parentId,
|
parent_id: item.parent_id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
children: item.children&&item.children.length>0 ? this.treeToMap(item.children) : null
|
children: item.children && item.children.length > 0 ? treeToMap(item.children) : null
|
||||||
}
|
}
|
||||||
map.push(obj)
|
map.push(obj)
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听菜单树变化
|
||||||
|
watch(
|
||||||
|
() => props.menu,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
menuOptions.value = treeToMap(newVal)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
//保存
|
{ deep: true, immediate: true }
|
||||||
async save(){
|
)
|
||||||
this.loading = true
|
|
||||||
let res = {};
|
// 添加接口权限行
|
||||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
const addApiRow = () => {
|
||||||
if(this.form.id){
|
form.apiList.push({
|
||||||
res = await this.$API.auth.menu.edit.post(this.form)
|
code: '',
|
||||||
}else{
|
url: ''
|
||||||
res = await this.$API.auth.menu.add.post(this.form)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除接口权限行
|
||||||
|
const removeApiRow = (index) => {
|
||||||
|
form.apiList.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载菜单详情
|
||||||
|
const loadMenuDetail = async (id) => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.menu.list.get({ id })
|
||||||
|
if (res.code === 1 && res.data) {
|
||||||
|
setData(res.data, props.parentId)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载菜单详情失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
await dialogForm.value.validate()
|
||||||
|
loading.value = true
|
||||||
|
let res = {}
|
||||||
|
form.parent_id = form.parent_id || 0
|
||||||
|
|
||||||
|
if (form.id) {
|
||||||
|
res = await authApi.menu.edit.post(form)
|
||||||
|
} else {
|
||||||
|
res = await authApi.menu.add.post(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = false
|
loading.value = false
|
||||||
if(res.code == 1){
|
if (res.code === 1) {
|
||||||
this.$message.success("保存成功")
|
message.success('保存成功')
|
||||||
// this.$TOOL.data.set("MENU", res.data)
|
emit('success')
|
||||||
}else{
|
} else {
|
||||||
this.$message.error(res.message)
|
message.error(res.message || '保存失败')
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
//表单注入数据
|
console.error('表单验证失败', error)
|
||||||
setData(data, pid){
|
loading.value = false
|
||||||
this.form = data
|
}
|
||||||
this.form.hidden = this.form.hidden == 1 ? true : false;
|
}
|
||||||
this.form.hiddenBreadcrumb = this.form.hiddenBreadcrumb == 1 ? true : false;
|
|
||||||
this.form.affix = this.form.affix == 1 ? true : false;
|
// 表单注入数据
|
||||||
this.form.fullpage = this.form.fullpage == 1 ? true : false;
|
const setData = (data, pid) => {
|
||||||
this.form.apiList = data.apiList || []
|
form.id = data.id
|
||||||
this.form.sort = data.sort + ""
|
form.parent_id = data.parent_id || pid || 0
|
||||||
this.form.parent_id = data.parent_id || pid
|
form.name = data.name || ''
|
||||||
|
form.path = data.path || ''
|
||||||
|
form.component = data.component || ''
|
||||||
|
form.redirect = data.redirect || ''
|
||||||
|
form.sort = data.sort || 0
|
||||||
|
form.title = data.title || ''
|
||||||
|
form.icon = data.icon || ''
|
||||||
|
form.active = data.active || ''
|
||||||
|
form.color = data.color || ''
|
||||||
|
form.type = data.type || 'menu'
|
||||||
|
form.affix = data.affix == 1
|
||||||
|
form.hidden = data.hidden == 1
|
||||||
|
form.hiddenBreadcrumb = data.hiddenBreadcrumb == 1
|
||||||
|
form.fullpage = data.fullpage == 1
|
||||||
|
form.apiList = data.apiList || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
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 {
|
||||||
|
.form-item-msg {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card) {
|
||||||
|
.ant-card-head {
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.ant-card-head-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-list-item) {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h2 {font-size: 17px;color: #3c4a54;padding:0 0 30px 0;}
|
|
||||||
.apilist {border-left: 1px solid #eee;}
|
|
||||||
|
|
||||||
[data-theme="dark"] h2 {color: #fff;}
|
|
||||||
[data-theme="dark"] .apilist {border-color: #434343;}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,173 +1,323 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<div class="pages role-page">
|
||||||
<el-header>
|
<div class="tool-bar">
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
<a-form layout="inline" :model="searchForm">
|
||||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
<a-form-item>
|
||||||
<el-button type="primary" plain :disabled="selection.length!=1" @click="permission">权限设置</el-button>
|
<a-input v-model:value="searchForm.keyword" placeholder="请输入角色名称" allow-clear
|
||||||
|
style="width: 200px" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><search-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleReset">
|
||||||
|
<template #icon><redo-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="right-panel-search">
|
<a-button type="primary" @click="handleAdd">
|
||||||
<el-input v-model="search.keyword" placeholder="角色名称" clearable></el-input>
|
<template #icon><plus-outlined /></template>
|
||||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
</a-button>
|
||||||
|
<a-button danger :disabled="selection.length === 0" @click="handleBatchDelete">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<a-button :disabled="selection.length !== 1" @click="handlePermission">
|
||||||
|
<template #icon><key-outlined /></template>
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
<div class="table-content">
|
||||||
<el-main class="nopadding">
|
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
|
||||||
<scTable ref="table" :apiObj="apiObj" row-key="id" @selection-change="selectionChange" :params="search">
|
:pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="loadData"
|
||||||
<el-table-column type="selection" width="50"></el-table-column>
|
@paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll">
|
||||||
<el-table-column label="ID" prop="id" width="50"></el-table-column>
|
<template #status="{ record }">
|
||||||
<el-table-column label="角色名称" prop="title"></el-table-column>
|
<a-tag :color="record.status === 1 ? 'success' : 'error'">
|
||||||
<el-table-column label="别名" prop="name" width="150"></el-table-column>
|
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||||
<el-table-column label="状态" prop="status" width="150">
|
</a-tag>
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="scope.row.status == 1 ? 'success' : 'danger'">{{ scope.row.status == 1 ? '正常' : '禁用' }}</el-tag>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
<template #action="{ record }">
|
||||||
<el-table-column label="操作" fixed="right" align="right" width="180">
|
<a-space>
|
||||||
<template #default="scope">
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
<el-button-group>
|
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||||
<el-button type="success" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
<a-popconfirm title="确定删除该角色吗?" @confirm="handleDelete(record)">
|
||||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
<a-button type="link" size="small" danger>删除</a-button>
|
||||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
</a-popconfirm>
|
||||||
<template #reference>
|
</a-space>
|
||||||
<el-button type="danger">删除</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-popconfirm>
|
|
||||||
</el-button-group>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</scTable>
|
</scTable>
|
||||||
</el-main>
|
</div>
|
||||||
</el-container>
|
</div>
|
||||||
|
|
||||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
|
<!-- 新增/编辑角色弹窗 -->
|
||||||
|
<save-dialog v-if="dialog.save" ref="saveDialogRef" @success="handleSaveSuccess" @closed="dialog.save = false" />
|
||||||
<permission-dialog v-if="dialog.permission" ref="permissionDialog" @closed="dialog.permission=false" @success="permissionSuccess"></permission-dialog>
|
|
||||||
|
|
||||||
|
<!-- 权限设置弹窗 -->
|
||||||
|
<permission-dialog v-if="dialog.permission" ref="permissionDialogRef" @success="permissionSuccess"
|
||||||
|
@closed="dialog.permission = false" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import saveDialog from './save'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import permissionDialog from './permission'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
|
import scTable from '@/components/scTable/index.vue'
|
||||||
|
import saveDialog from './save.vue'
|
||||||
|
import permissionDialog from './permission.vue'
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
export default {
|
defineOptions({
|
||||||
name: 'auth.role',
|
name: 'authRole'
|
||||||
components: {
|
})
|
||||||
saveDialog,
|
|
||||||
permissionDialog
|
// 表格引用
|
||||||
},
|
const tableRef = ref(null)
|
||||||
data() {
|
|
||||||
return {
|
// 对话框状态
|
||||||
dialog: {
|
const dialog = reactive({
|
||||||
save: false,
|
save: false,
|
||||||
permission: false
|
permission: false
|
||||||
},
|
})
|
||||||
apiObj: this.$API.auth.role.list,
|
|
||||||
selection: [],
|
|
||||||
search: {keyword: null}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//添加
|
|
||||||
add(){
|
|
||||||
this.dialog.save = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//编辑
|
|
||||||
table_edit(row){
|
|
||||||
this.dialog.save = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open('edit').setData(row)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//查看
|
|
||||||
table_show(row){
|
|
||||||
this.dialog.save = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.saveDialog.open('show').setData(row)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//权限设置
|
|
||||||
permission(){
|
|
||||||
if(this.selection.length != 1){
|
|
||||||
this.$message.error("请选择要设置的角色")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.dialog.permission = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.permissionDialog.open().setData(this.selection[0])
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//删除
|
|
||||||
async table_del(row){
|
|
||||||
var reqData = {id: row.id}
|
|
||||||
var res = await this.$API.auth.role.delete.post(reqData);
|
|
||||||
if(res.code == 1){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
this.$message.success("删除成功")
|
|
||||||
}else{
|
|
||||||
this.$message.error(res.message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//批量删除
|
|
||||||
async batch_del(){
|
|
||||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
|
|
||||||
type: 'warning'
|
|
||||||
}).then(async () => {
|
|
||||||
var ids = this.selection.map(item => item.id)
|
|
||||||
var reqData = {ids: ids}
|
|
||||||
var res = await this.$API.auth.role.delete.post(reqData);
|
|
||||||
if(res.code == 1){
|
|
||||||
const loading = this.$loading();
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
loading.close();
|
|
||||||
this.$message.success("操作成功")
|
|
||||||
}else{
|
|
||||||
this.$message.error(res.message)
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
|
|
||||||
})
|
// 弹窗引用
|
||||||
},
|
const saveDialogRef = ref(null)
|
||||||
//表格选择后回调事件
|
const permissionDialogRef = ref(null)
|
||||||
selectionChange(selection){
|
|
||||||
this.selection = selection;
|
// 选中的行
|
||||||
},
|
const selection = ref([])
|
||||||
//搜索
|
|
||||||
upsearch(){
|
// 搜索表单
|
||||||
this.$refs.table.upData(this.search)
|
const searchForm = reactive({
|
||||||
},
|
keyword: ''
|
||||||
//根据ID获取树结构
|
})
|
||||||
filterTree(id){
|
|
||||||
var target = null;
|
// 表格数据
|
||||||
function filter(tree){
|
const tableData = ref([])
|
||||||
tree.forEach(item => {
|
const loading = ref(false)
|
||||||
if(item.id == id){
|
|
||||||
target = item
|
// 分页配置
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
pageSizeOptions: ['20', '50', '100', '200']
|
||||||
|
})
|
||||||
|
|
||||||
|
// 行key
|
||||||
|
const rowKey = 'id'
|
||||||
|
|
||||||
|
// 行选择配置
|
||||||
|
const rowSelection = computed(() => ({
|
||||||
|
selectedRowKeys: selection.value.map(item => item.id),
|
||||||
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
selection.value = selectedRows
|
||||||
}
|
}
|
||||||
if(item.children){
|
}))
|
||||||
filter(item.children)
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80, align: 'center' },
|
||||||
|
{ title: '角色名称', dataIndex: 'title', key: 'title', width: 200 },
|
||||||
|
{ title: '别名', dataIndex: 'name', key: 'name', width: 200 },
|
||||||
|
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 100, align: 'center' },
|
||||||
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', slot: 'status' },
|
||||||
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 200, align: 'center', slot: 'action', fixed: 'right' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 分页变化处理
|
||||||
|
const handlePaginationChange = ({ page, pageSize }) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载角色列表数据
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: pagination.current,
|
||||||
|
limit: pagination.pageSize,
|
||||||
|
...searchForm
|
||||||
}
|
}
|
||||||
})
|
const res = await authApi.role.list.get(params)
|
||||||
|
if (res.code === 1) {
|
||||||
|
tableData.value = res.data?.data || []
|
||||||
|
pagination.total = res.data?.total || 0
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '加载数据失败')
|
||||||
}
|
}
|
||||||
filter(this.$refs.table.tableData)
|
} catch (error) {
|
||||||
return target
|
console.error('加载角色列表失败:', error)
|
||||||
},
|
message.error('加载数据失败')
|
||||||
//本地更新数据
|
} finally {
|
||||||
handleSaveSuccess(data, mode){
|
loading.value = false
|
||||||
if(mode=='add'){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
}else if(mode=='edit'){
|
|
||||||
this.$refs.table.refresh()
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
permissionSuccess(){
|
|
||||||
this.$refs.table.refresh()
|
// 选择变化处理
|
||||||
|
const handleSelectChange = (record, selected, selectedRows) => {
|
||||||
|
if (selected) {
|
||||||
|
selection.value.push(record)
|
||||||
|
} else {
|
||||||
|
const index = selection.value.findIndex(item => item.id === record.id)
|
||||||
|
if (index > -1) {
|
||||||
|
selection.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全选/取消全选处理
|
||||||
|
const handleSelectAll = (selected, selectedRows, changeRows) => {
|
||||||
|
if (selected) {
|
||||||
|
changeRows.forEach(record => {
|
||||||
|
if (!selection.value.find(item => item.id === record.id)) {
|
||||||
|
selection.value.push(record)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
changeRows.forEach(record => {
|
||||||
|
const index = selection.value.findIndex(item => item.id === record.id)
|
||||||
|
if (index > -1) {
|
||||||
|
selection.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.keyword = ''
|
||||||
|
selection.value = []
|
||||||
|
pagination.current = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增角色
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('add')
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看角色
|
||||||
|
const handleView = (record) => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('show').setData(record)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑角色
|
||||||
|
const handleEdit = (record) => {
|
||||||
|
dialog.save = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saveDialogRef.value?.open('edit').setData(record)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除角色
|
||||||
|
const handleDelete = async (record) => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.role.delete.post({ id: record.id })
|
||||||
|
if (res.code === 1) {
|
||||||
|
message.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除角色失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selection.value.length === 0) {
|
||||||
|
message.warning('请选择要删除的角色')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定删除选中的 ${selection.value.length} 个角色吗?`,
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const ids = selection.value.map(item => item.id)
|
||||||
|
const res = await authApi.role.delete.post({ ids })
|
||||||
|
if (res.code === 1) {
|
||||||
|
message.success('删除成功')
|
||||||
|
selection.value = []
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除角色失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限设置
|
||||||
|
const handlePermission = () => {
|
||||||
|
if (selection.value.length !== 1) {
|
||||||
|
message.error('请选择一个角色进行权限设置')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dialog.permission = true
|
||||||
|
setTimeout(() => {
|
||||||
|
permissionDialogRef.value?.open().setData(selection.value[0])
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存成功回调
|
||||||
|
const handleSaveSuccess = (data, mode) => {
|
||||||
|
if (mode === 'add') {
|
||||||
|
loadData()
|
||||||
|
} else if (mode === 'edit') {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限设置成功回调
|
||||||
|
const permissionSuccess = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.role-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.table-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,129 +1,181 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog title="角色权限设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
<a-modal title="角色权限设置" :open="visible" :width="600" :destroy-on-close="true" :footer="null" @cancel="handleCancel">
|
||||||
<el-tabs tab-position="top">
|
<a-tabs tab-position="top">
|
||||||
<el-tab-pane label="菜单权限">
|
<a-tab-pane key="menu" tab="菜单权限">
|
||||||
<div class="treeMain">
|
<div class="treeMain">
|
||||||
<el-tree ref="menu" node-key="id" :data="menu.list" :default-checked-keys="menu.checked" :props="menu.props" check-strictly show-checkbox></el-tree>
|
<a-tree ref="menuTreeRef" v-model:checkedKeys="menu.checked" :tree-data="menu.list"
|
||||||
</div>
|
:field-names="menu.fieldNames" :checkable="true" :default-expand-all="true"
|
||||||
</el-tab-pane>
|
:check-strictly="true" :selectable="false">
|
||||||
<el-tab-pane label="数据权限">
|
<template #title="{ title }">
|
||||||
<el-form label-width="100px" label-position="left">
|
{{ title }}
|
||||||
<el-form-item label="数据权限">
|
|
||||||
<sc-select v-model="form.data_range" dic="data_auth" style="width: 80%;"></sc-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="控制台">
|
|
||||||
<el-form label-width="100px" label-position="left">
|
|
||||||
<el-form-item label="控制台视图">
|
|
||||||
<el-select v-model="form.dashboard" placeholder="请选择">
|
|
||||||
<el-option v-for="item in dashboardOptions" :key="item.value" :label="item.label" :value="item.value">
|
|
||||||
<span style="float: left">{{ item.label }}</span>
|
|
||||||
<span style="float: right; color: #8492a6; font-size: 12px">{{ item.views }}</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
<div class="el-form-item-msg">用于控制角色登录后控制台的视图</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="visible=false" >取 消</el-button>
|
|
||||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</a-tree>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="data" tab="数据权限">
|
||||||
|
<a-form :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
|
||||||
|
<a-form-item label="数据权限">
|
||||||
|
<a-select v-model:value="form.data_range" placeholder="请选择数据权限" style="width: 100%">
|
||||||
|
<a-select-option value="0">全部数据权限</a-select-option>
|
||||||
|
<a-select-option value="1">本部门及以下数据</a-select-option>
|
||||||
|
<a-select-option value="2">本部门数据权限</a-select-option>
|
||||||
|
<a-select-option value="3">仅本人数据权限</a-select-option>
|
||||||
|
<a-select-option value="4">自定义数据权限</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="dashboard" tab="控制台">
|
||||||
|
<a-form :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
|
||||||
|
<a-form-item label="控制台视图">
|
||||||
|
<a-select v-model:value="form.dashboard" placeholder="请选择" style="width: 100%">
|
||||||
|
<a-select-option value="0">
|
||||||
|
<div style="display: flex; justify-content: space-between">
|
||||||
|
<span>数据统计</span>
|
||||||
|
<span style="color: #8492a6; font-size: 12px">stats</span>
|
||||||
|
</div>
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="1">
|
||||||
|
<div style="display: flex; justify-content: space-between">
|
||||||
|
<span>工作台</span>
|
||||||
|
<span style="color: #8492a6; font-size: 12px">work</span>
|
||||||
|
</div>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<div class="ant-form-item-explain">用于控制角色登录后控制台的视图</div>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="handleCancel">取 消</a-button>
|
||||||
|
<a-button type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive } from 'vue'
|
||||||
emits: ['success', 'closed'],
|
import { message } from 'ant-design-vue'
|
||||||
data() {
|
import authApi from '@/api/auth'
|
||||||
return {
|
|
||||||
visible: false,
|
|
||||||
isSaveing: false,
|
|
||||||
menu: {
|
|
||||||
list: [],
|
|
||||||
checked: [],
|
|
||||||
props: {
|
|
||||||
label: (data)=>{
|
|
||||||
return data.title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
group: {
|
|
||||||
list: [],
|
|
||||||
checked: [],
|
|
||||||
props: {}
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
list: [],
|
|
||||||
checked: [],
|
|
||||||
props: {}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
role_id: 0,
|
|
||||||
auth: [],
|
|
||||||
data_range: "",
|
|
||||||
dashboard: "work",
|
|
||||||
},
|
|
||||||
dashboardOptions: [
|
|
||||||
{
|
|
||||||
value: '0',
|
|
||||||
label: '数据统计',
|
|
||||||
views: 'stats'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '1',
|
|
||||||
label: '工作台',
|
|
||||||
views: 'work'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getMenu();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
open(){
|
|
||||||
this.visible = true;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
async submit(){
|
|
||||||
let authSelect = this.$refs.menu.getCheckedNodes();
|
|
||||||
this.form.permissions = [];
|
|
||||||
authSelect.map(item => {
|
|
||||||
this.form.permissions.push(item.id);
|
|
||||||
})
|
|
||||||
this.isSaveing = true;
|
|
||||||
var res = await this.$API.auth.role.auth.post(this.form);
|
|
||||||
|
|
||||||
this.isSaveing = false;
|
const emit = defineEmits(['success', 'closed'])
|
||||||
if(res.code == 1){
|
|
||||||
this.$emit('success', this.form)
|
const visible = ref(false)
|
||||||
this.visible = false;
|
const isSaveing = ref(false)
|
||||||
this.$message.success("操作成功")
|
const menuTreeRef = ref()
|
||||||
}else{
|
|
||||||
this.$alert(res.message, "提示", {type: 'error'})
|
const menu = reactive({
|
||||||
}
|
list: [],
|
||||||
},
|
checked: [],
|
||||||
async getMenu(){
|
fieldNames: {
|
||||||
var res = await this.$API.auth.menu.list.get({is_tree: 1});
|
title: 'title',
|
||||||
this.menu.list = res.data;
|
key: 'id',
|
||||||
},
|
children: 'children'
|
||||||
//表单注入数据
|
|
||||||
setData(data){
|
|
||||||
this.form.id = data.id;
|
|
||||||
this.form.data_range = data.data_range;
|
|
||||||
this.form.dashboard = data.dashboard;
|
|
||||||
// this.form.mobile_module = data.mobile_module;
|
|
||||||
data.permissions.map(item => {
|
|
||||||
this.menu.checked.push(item.id);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
role_id: 0,
|
||||||
|
permissions: [],
|
||||||
|
data_range: '',
|
||||||
|
dashboard: '1'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 打开对话框
|
||||||
|
const open = () => {
|
||||||
|
visible.value = true
|
||||||
|
return {
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('closed')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交保存
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
isSaveing.value = true
|
||||||
|
|
||||||
|
// 获取选中的菜单权限 ID
|
||||||
|
form.permissions = menu.checked || []
|
||||||
|
form.role_id = form.id
|
||||||
|
|
||||||
|
const res = await authApi.role.auth.post(form)
|
||||||
|
|
||||||
|
isSaveing.value = false
|
||||||
|
if (res.code === 1) {
|
||||||
|
emit('success', form)
|
||||||
|
visible.value = false
|
||||||
|
message.success('操作成功')
|
||||||
|
} else {
|
||||||
|
message.error(res.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存权限失败:', error)
|
||||||
|
isSaveing.value = false
|
||||||
|
message.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取菜单列表
|
||||||
|
const getMenu = async () => {
|
||||||
|
try {
|
||||||
|
const res = await authApi.menu.list.get({ is_tree: 1 })
|
||||||
|
menu.list = res.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取菜单列表失败:', error)
|
||||||
|
message.error('获取菜单列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
const setData = (data) => {
|
||||||
|
form.id = data.id
|
||||||
|
form.data_range = data.data_range || ''
|
||||||
|
form.dashboard = data.dashboard || '1'
|
||||||
|
|
||||||
|
// 设置选中的菜单权限
|
||||||
|
if (data.permissions && data.permissions.length > 0) {
|
||||||
|
menu.checked = data.permissions.map(item => item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
getMenu()
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
|
.treeMain {
|
||||||
|
height: 280px;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-explain {
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,121 +1,124 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
<a-modal :title="titleMap[mode]" :open="visible" :width="500" :destroy-on-close="true" :footer="null"
|
||||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
@cancel="handleCancel">
|
||||||
<el-form-item label="上级角色" prop="parent_id" v-if="false">
|
<a-form :model="form" :rules="rules" :disabled="mode === 'show'" ref="dialogForm" :label-col="{ span: 5 }"
|
||||||
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
|
:wrapper-col="{ span: 18 }">
|
||||||
</el-form-item>
|
<a-form-item label="角色名称" name="title">
|
||||||
<el-form-item label="角色名称" prop="title">
|
<a-input v-model:value="form.title" placeholder="请输入角色名称" allow-clear></a-input>
|
||||||
<el-input v-model="form.title" clearable></el-input>
|
</a-form-item>
|
||||||
</el-form-item>
|
<a-form-item label="角色别名" name="name">
|
||||||
<el-form-item label="角色别名" prop="name">
|
<a-input v-model:value="form.name" placeholder="请输入角色别名" allow-clear></a-input>
|
||||||
<el-input v-model="form.name" clearable></el-input>
|
</a-form-item>
|
||||||
</el-form-item>
|
<a-form-item label="排序" name="sort">
|
||||||
<el-form-item label="排序" prop="sort">
|
<a-input-number v-model:value="form.sort" :min="1" :step="1" style="width: 100%" placeholder="请输入排序" />
|
||||||
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
|
</a-form-item>
|
||||||
</el-form-item>
|
</a-form>
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="visible=false" >取 消</el-button>
|
<a-button @click="handleCancel">取 消</a-button>
|
||||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
<a-button v-if="mode !== 'show'" type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive } from 'vue'
|
||||||
emits: ['success', 'closed'],
|
import { message } from 'ant-design-vue'
|
||||||
data() {
|
import authApi from '@/api/auth'
|
||||||
return {
|
|
||||||
mode: "add",
|
|
||||||
titleMap: {
|
|
||||||
add: '新增',
|
|
||||||
edit: '编辑',
|
|
||||||
show: '查看'
|
|
||||||
},
|
|
||||||
visible: false,
|
|
||||||
isSaveing: false,
|
|
||||||
//表单数据
|
|
||||||
form: {
|
|
||||||
id:"",
|
|
||||||
title: "",
|
|
||||||
name: "",
|
|
||||||
sort: 1,
|
|
||||||
parent_id: 0
|
|
||||||
},
|
|
||||||
//验证规则
|
|
||||||
rules: {
|
|
||||||
sort: [
|
|
||||||
{required: true, message: '请输入排序', trigger: 'change'}
|
|
||||||
],
|
|
||||||
label: [
|
|
||||||
{required: true, message: '请输入角色名称'}
|
|
||||||
],
|
|
||||||
alias: [
|
|
||||||
{required: true, message: '请输入角色别名'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
//所需数据选项
|
|
||||||
groups: [],
|
|
||||||
groupsProps: {
|
|
||||||
value: "id",
|
|
||||||
label: "title",
|
|
||||||
emitPath: false,
|
|
||||||
checkStrictly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getGroup()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//显示
|
|
||||||
open(mode='add'){
|
|
||||||
this.mode = mode;
|
|
||||||
this.visible = true;
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
//加载树数据
|
|
||||||
async getGroup(){
|
|
||||||
var res = await this.$API.auth.role.list.get({is_tree: 1});
|
|
||||||
this.groups = res.data;
|
|
||||||
},
|
|
||||||
//表单提交方法
|
|
||||||
submit(){
|
|
||||||
this.$refs.dialogForm.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
this.isSaveing = true;
|
|
||||||
var res = {}
|
|
||||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
|
||||||
if(this.mode == 'add'){
|
|
||||||
res = await this.$API.auth.role.add.post(this.form);
|
|
||||||
}else{
|
|
||||||
res = await this.$API.auth.role.edit.post(this.form);
|
|
||||||
}
|
|
||||||
this.isSaveing = false;
|
|
||||||
if(res.code == 1){
|
|
||||||
this.$emit('success', this.form, this.mode)
|
|
||||||
this.visible = false;
|
|
||||||
this.$message.success("操作成功")
|
|
||||||
}else{
|
|
||||||
this.$message.error(res.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//表单注入数据
|
|
||||||
setData(data){
|
|
||||||
this.form.id = data.id
|
|
||||||
this.form.title = data.title
|
|
||||||
this.form.name = data.name
|
|
||||||
this.form.sort = data.sort
|
|
||||||
// this.form.parent_id = data.parent_id
|
|
||||||
|
|
||||||
//可以和上面一样单个注入,也可以像下面一样直接合并进去
|
const emit = defineEmits(['success', 'closed'])
|
||||||
//Object.assign(this.form, data)
|
|
||||||
|
const mode = ref('add')
|
||||||
|
const titleMap = {
|
||||||
|
add: '新增角色',
|
||||||
|
edit: '编辑角色',
|
||||||
|
show: '查看角色'
|
||||||
|
}
|
||||||
|
const visible = ref(false)
|
||||||
|
const isSaveing = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
sort: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const dialogForm = ref()
|
||||||
|
|
||||||
|
// 验证规则
|
||||||
|
const rules = {
|
||||||
|
title: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '请输入角色别名', trigger: 'blur' }],
|
||||||
|
sort: [
|
||||||
|
{ required: true, message: '请输入排序', trigger: 'change' },
|
||||||
|
{ type: 'number', message: '排序必须为数字', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示对话框
|
||||||
|
const open = (openMode = 'add') => {
|
||||||
|
mode.value = openMode
|
||||||
|
visible.value = true
|
||||||
|
return {
|
||||||
|
setData,
|
||||||
|
open,
|
||||||
|
close
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('closed')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单提交方法
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
await dialogForm.value.validate()
|
||||||
|
isSaveing.value = true
|
||||||
|
let res = {}
|
||||||
|
if (mode.value === 'add') {
|
||||||
|
res = await authApi.role.add.post(form)
|
||||||
|
} else {
|
||||||
|
res = await authApi.role.edit.post(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSaveing.value = false
|
||||||
|
if (res.code === 1) {
|
||||||
|
emit('success', form, mode.value)
|
||||||
|
visible.value = false
|
||||||
|
message.success('操作成功')
|
||||||
|
} else {
|
||||||
|
message.error(res.message)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败', error)
|
||||||
|
isSaveing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单注入数据
|
||||||
|
const setData = (data) => {
|
||||||
|
form.id = data.id
|
||||||
|
form.title = data.title
|
||||||
|
form.name = data.name
|
||||||
|
form.sort = data.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
<div class="pages user-page">
|
<div class="pages 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">
|
||||||
|
<template #prefix>
|
||||||
|
<search-outlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="departmentTree"
|
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree"
|
||||||
:field-names="{ title: 'title', key: 'id', children: 'children' }" show-icon @select="onDeptSelect">
|
:field-names="{ title: 'title', key: 'id', children: 'children' }" showLine @select="onDeptSelect">
|
||||||
<template #icon="{ dataRef }">
|
<template #icon="{ dataRef }">
|
||||||
<folder-outlined v-if="dataRef.children" />
|
<folder-outlined v-if="dataRef.children" />
|
||||||
<file-outlined v-else />
|
<file-outlined v-else />
|
||||||
@@ -15,37 +19,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-box">
|
<div class="right-box">
|
||||||
<div class="search-bar">
|
<div class="tool-bar">
|
||||||
|
<div class="left-panel">
|
||||||
<a-form layout="inline" :model="searchForm">
|
<a-form layout="inline" :model="searchForm">
|
||||||
<a-form-item label="用户名">
|
<a-form-item>
|
||||||
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear />
|
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear
|
||||||
|
style="width: 160px" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="姓名">
|
<a-form-item>
|
||||||
<a-input v-model:value="searchForm.nickname" placeholder="请输入姓名" allow-clear />
|
<a-input v-model:value="searchForm.nickname" placeholder="请输入姓名" allow-clear
|
||||||
|
style="width: 160px" />
|
||||||
</a-form-item>
|
</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><search-outlined /></template>
|
||||||
搜索
|
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button @click="handleReset">
|
<a-button @click="handleReset">
|
||||||
<template #icon><redo-outlined /></template>
|
<template #icon><redo-outlined /></template>
|
||||||
重置
|
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-content">
|
<div class="right-panel">
|
||||||
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
|
|
||||||
:pagination="pagination" :row-key="rowKey" @change="handleTableChange" @refresh="loadData">
|
|
||||||
<template #toolLeft>
|
|
||||||
<a-button type="primary" @click="handleAdd">
|
<a-button type="primary" @click="handleAdd">
|
||||||
<template #icon><plus-outlined /></template>
|
<template #icon><plus-outlined /></template>
|
||||||
新增用户
|
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
<a-button @click="handleExport">
|
||||||
|
<template #icon><export-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-content">
|
||||||
|
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
|
||||||
|
:pagination="pagination" :row-key="rowKey" @refresh="loadData"
|
||||||
|
@paginationChange="handlePaginationChange">
|
||||||
<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><user-outlined /></template>
|
||||||
@@ -56,12 +65,15 @@
|
|||||||
{{ record.status === 1 ? '正常' : '禁用' }}
|
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<template #department_title="{ record }">
|
||||||
|
{{ record.department?.title }}
|
||||||
|
</template>
|
||||||
<template #roles="{ record }">
|
<template #roles="{ record }">
|
||||||
<a-tag v-for="role in record.roles" :key="role.id" color="blue">
|
<a-tag v-for="role in record.roles" :key="role.id" color="blue">
|
||||||
{{ role.title }}
|
{{ role.title }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template #_action="{ record }">
|
<template #action="{ record }">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||||
@@ -86,14 +98,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import {
|
|
||||||
FolderOutlined,
|
|
||||||
FileOutlined,
|
|
||||||
SearchOutlined,
|
|
||||||
RedoOutlined,
|
|
||||||
PlusOutlined,
|
|
||||||
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 './save.vue'
|
||||||
import roleDialog from './role.vue'
|
import roleDialog from './role.vue'
|
||||||
@@ -118,7 +122,9 @@ const roleDialogRef = ref(null)
|
|||||||
|
|
||||||
// 部门树数据
|
// 部门树数据
|
||||||
const departmentTree = ref([])
|
const departmentTree = ref([])
|
||||||
|
const filteredDepartmentTree = ref([])
|
||||||
const selectedDeptKeys = ref([])
|
const selectedDeptKeys = ref([])
|
||||||
|
const departmentKeyword = ref('')
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
@@ -144,64 +150,23 @@ const pagination = reactive({
|
|||||||
// 行key
|
// 行key
|
||||||
const rowKey = 'id'
|
const rowKey = 'id'
|
||||||
|
|
||||||
|
// 分页变化处理
|
||||||
|
const handlePaginationChange = ({ page, pageSize }) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{ title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' },
|
||||||
title: '头像',
|
{ title: '用户名', dataIndex: 'username', key: 'username', width: 150 },
|
||||||
dataIndex: 'avatar',
|
{ title: '姓名', dataIndex: 'nickname', key: 'nickname', width: 150 },
|
||||||
key: 'avatar',
|
{ title: '部门', dataIndex: 'department_title', key: 'department_title', slot: 'department_title', width: 150 },
|
||||||
width: 80,
|
{ title: '角色', dataIndex: 'roles', key: 'roles', width: 200, slot: 'roles' },
|
||||||
align: 'center',
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', slot: 'status' },
|
||||||
slot: 'avatar'
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 180 },
|
||||||
},
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 200, align: 'center', slot: 'action', fixed: 'right' }
|
||||||
{
|
|
||||||
title: '用户名',
|
|
||||||
dataIndex: 'username',
|
|
||||||
key: 'username',
|
|
||||||
width: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '姓名',
|
|
||||||
dataIndex: 'nickname',
|
|
||||||
key: 'nickname',
|
|
||||||
width: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '部门',
|
|
||||||
dataIndex: 'department_title',
|
|
||||||
key: 'department_title',
|
|
||||||
width: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '角色',
|
|
||||||
dataIndex: 'roles',
|
|
||||||
key: 'roles',
|
|
||||||
width: 200,
|
|
||||||
slot: 'roles'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
width: 100,
|
|
||||||
align: 'center',
|
|
||||||
slot: 'status'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
dataIndex: 'created_at',
|
|
||||||
key: 'created_at',
|
|
||||||
width: 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: '_action',
|
|
||||||
key: '_action',
|
|
||||||
width: 200,
|
|
||||||
align: 'center',
|
|
||||||
slot: '_action',
|
|
||||||
fixed: 'right'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// 加载部门树
|
// 加载部门树
|
||||||
@@ -210,12 +175,41 @@ const loadDepartmentTree = async () => {
|
|||||||
const res = await authApi.department.list.get({ is_tree: 1 })
|
const res = await authApi.department.list.get({ is_tree: 1 })
|
||||||
if (res.code === 1) {
|
if (res.code === 1) {
|
||||||
departmentTree.value = res.data || []
|
departmentTree.value = res.data || []
|
||||||
|
filteredDepartmentTree.value = res.data || []
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载部门树失败:', error)
|
console.error('加载部门树失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 部门搜索
|
||||||
|
const handleDeptSearch = (e) => {
|
||||||
|
const keyword = e.target?.value || ''
|
||||||
|
departmentKeyword.value = keyword
|
||||||
|
if (!keyword) {
|
||||||
|
filteredDepartmentTree.value = departmentTree.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归过滤部门树
|
||||||
|
const filterTree = (nodes) => {
|
||||||
|
return nodes.reduce((acc, node) => {
|
||||||
|
const isMatch = node.title && node.title.toLowerCase().includes(keyword.toLowerCase())
|
||||||
|
const filteredChildren = node.children ? filterTree(node.children) : []
|
||||||
|
|
||||||
|
if (isMatch || filteredChildren.length > 0) {
|
||||||
|
acc.push({
|
||||||
|
...node,
|
||||||
|
children: filteredChildren.length > 0 ? filteredChildren : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredDepartmentTree.value = filterTree(departmentTree.value)
|
||||||
|
}
|
||||||
|
|
||||||
// 加载用户列表数据
|
// 加载用户列表数据
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -240,9 +234,15 @@ const loadData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 表格变化处理(排序、筛选)
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
// 如果需要处理排序或筛选,可以在这里添加逻辑
|
||||||
|
console.log('表格变化:', { pagination, filters, sorter })
|
||||||
|
}
|
||||||
|
|
||||||
// 部门选择事件
|
// 部门选择事件
|
||||||
const onDeptSelect = (selectedKeys) => {
|
const onDeptSelect = (selectedKeys) => {
|
||||||
if (selectedKeys.length > 0) {
|
if (selectedKeys && selectedKeys.length > 0) {
|
||||||
searchForm.department_id = selectedKeys[0]
|
searchForm.department_id = selectedKeys[0]
|
||||||
} else {
|
} else {
|
||||||
searchForm.department_id = null
|
searchForm.department_id = null
|
||||||
@@ -263,17 +263,25 @@ const handleReset = () => {
|
|||||||
searchForm.nickname = ''
|
searchForm.nickname = ''
|
||||||
searchForm.department_id = null
|
searchForm.department_id = null
|
||||||
selectedDeptKeys.value = []
|
selectedDeptKeys.value = []
|
||||||
|
departmentKeyword.value = ''
|
||||||
|
filteredDepartmentTree.value = departmentTree.value
|
||||||
pagination.current = 1
|
pagination.current = 1
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格变化事件
|
// 刷新表格
|
||||||
const handleTableChange = (pag) => {
|
const refreshTable = () => {
|
||||||
pagination.current = pag.current
|
|
||||||
pagination.pageSize = pag.pageSize
|
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出数据
|
||||||
|
const handleExport = () => {
|
||||||
|
message.info('导出功能开发中...')
|
||||||
|
// TODO: 实现导出功能
|
||||||
|
// const params = { ...searchForm }
|
||||||
|
// 调用导出API
|
||||||
|
}
|
||||||
|
|
||||||
// 新增用户
|
// 新增用户
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
dialog.save = true
|
dialog.save = true
|
||||||
@@ -309,7 +317,7 @@ const handleRole = (record) => {
|
|||||||
// 删除用户
|
// 删除用户
|
||||||
const handleDelete = async (record) => {
|
const handleDelete = async (record) => {
|
||||||
try {
|
try {
|
||||||
const res = await window.$API.auth.users.delete.post({ id: record.id })
|
const res = await authApi.users.delete.post({ id: record.id })
|
||||||
if (res.code === 1) {
|
if (res.code === 1) {
|
||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
loadData()
|
loadData()
|
||||||
@@ -358,12 +366,11 @@ onMounted(() => {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
height: 50px;
|
padding: 12px 16px;
|
||||||
line-height: 50px;
|
|
||||||
padding: 0 16px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
@@ -379,23 +386,6 @@ onMounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
height: 50px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: #fff;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
:deep(.ant-form) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-form-item) {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-content {
|
.table-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,78 +1,130 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog title="角色设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
<a-modal title="角色设置" :open="visible" :width="500" :destroy-on-close="true" :footer="null" @cancel="handleCancel">
|
||||||
<el-tabs tab-position="top">
|
<a-tabs tab-position="top">
|
||||||
<el-tab-pane label="角色选择">
|
<a-tab-pane key="role" tab="角色选择">
|
||||||
<div class="treeMain">
|
<div class="treeMain">
|
||||||
<el-tree ref="role" node-key="id" :data="role.list" :default-checked-keys="role.checked" :props="role.props" check-strictly default-expand-all show-checkbox></el-tree>
|
<a-tree ref="roleTreeRef" v-model:checkedKeys="role.checked" :tree-data="role.list"
|
||||||
</div>
|
:field-names="role.fieldNames" :checkable="true" :default-expand-all="true"
|
||||||
</el-tab-pane>
|
:check-strictly="true" :selectable="false">
|
||||||
</el-tabs>
|
<template #title="{ title }">
|
||||||
<template #footer>
|
{{ title }}
|
||||||
<el-button @click="visible=false" >取 消</el-button>
|
|
||||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</a-tree>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="handleCancel">取 消</a-button>
|
||||||
|
<a-button type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive } from 'vue'
|
||||||
data(){
|
import { message } from 'ant-design-vue'
|
||||||
return {
|
import authAPI from '@/api/auth'
|
||||||
visible: false,
|
|
||||||
isSaveing: false,
|
const emit = defineEmits(['success', 'closed'])
|
||||||
role: {
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const isSaveing = ref(false)
|
||||||
|
const roleTreeRef = ref()
|
||||||
|
|
||||||
|
const role = reactive({
|
||||||
list: [],
|
list: [],
|
||||||
checked: [],
|
checked: [],
|
||||||
props: {
|
fieldNames: {
|
||||||
label: (data)=>{
|
title: 'title',
|
||||||
return data.title
|
key: 'id',
|
||||||
|
children: 'children'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
},
|
|
||||||
form: {uid: '',roles: []}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
this.getRole();
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
open(){
|
|
||||||
this.visible = true;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
async submit(){
|
|
||||||
this.isSaveing = true;
|
|
||||||
let roles = this.$refs.role.getCheckedNodes();
|
|
||||||
this.form.roles = [];
|
|
||||||
roles.map(item => {
|
|
||||||
this.form.roles.push(item.id);
|
|
||||||
})
|
|
||||||
this.isSaveing = true;
|
|
||||||
var res = await this.$API.auth.users.uprole.post(this.form);
|
|
||||||
|
|
||||||
this.isSaveing = false;
|
const form = reactive({
|
||||||
if(res.code == 1){
|
uid: '',
|
||||||
this.$emit('success', this.form)
|
roles: []
|
||||||
this.visible = false;
|
})
|
||||||
this.$message.success("操作成功")
|
|
||||||
}else{
|
// 打开对话框
|
||||||
this.$message.error(res.message)
|
const open = () => {
|
||||||
}
|
visible.value = true
|
||||||
},
|
return {
|
||||||
async getRole(){
|
open,
|
||||||
let res = await this.$API.auth.role.list.get({is_tree: 1});
|
setData,
|
||||||
this.role.list = res.data;
|
close
|
||||||
},
|
|
||||||
setData(data){
|
|
||||||
data.roles.map(item => {
|
|
||||||
this.role.checked.push(item.id)
|
|
||||||
})
|
|
||||||
this.form.uid = data.uid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('closed')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交保存
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
isSaveing.value = true
|
||||||
|
|
||||||
|
// 获取选中的角色 ID
|
||||||
|
form.roles = role.checked || []
|
||||||
|
|
||||||
|
const res = await authAPI.users.uprole.post(form)
|
||||||
|
|
||||||
|
isSaveing.value = false
|
||||||
|
if (res.code === 1) {
|
||||||
|
emit('success', form)
|
||||||
|
visible.value = false
|
||||||
|
message.success('操作成功')
|
||||||
|
} else {
|
||||||
|
message.error(res.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存角色失败:', error)
|
||||||
|
isSaveing.value = false
|
||||||
|
message.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取角色列表
|
||||||
|
const getRole = async () => {
|
||||||
|
try {
|
||||||
|
const res = await authAPI.role.list.get({ is_tree: 1 })
|
||||||
|
role.list = res.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取角色列表失败:', error)
|
||||||
|
message.error('获取角色列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
const setData = (data) => {
|
||||||
|
role.checked = data.roles ? data.roles.map(item => item.id) : []
|
||||||
|
form.uid = data.uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
getRole()
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
|
.treeMain {
|
||||||
|
height: 280px;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,156 +1,194 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')">
|
<a-modal :title="titleMap[mode]" :open="visible" :width="500" :destroy-on-close="true" :mask-closable="false"
|
||||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
:footer="null" @cancel="handleCancel">
|
||||||
<el-form-item label="头像" prop="avatar">
|
<a-form :model="form" :rules="rules" :disabled="mode === 'show'" ref="dialogForm" :label-col="{ span: 5 }"
|
||||||
|
:wrapper-col="{ span: 18 }">
|
||||||
|
<a-form-item label="头像" name="avatar">
|
||||||
<sc-upload v-model="form.avatar" :cropper="true" :aspectRatio="1" title="上传头像"></sc-upload>
|
<sc-upload v-model="form.avatar" :cropper="true" :aspectRatio="1" title="上传头像"></sc-upload>
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
<el-form-item label="登录账号" prop="username">
|
<a-form-item label="登录账号" name="username">
|
||||||
<el-input v-model="form.username" placeholder="用于登录系统" clearable></el-input>
|
<a-input v-model:value="form.username" placeholder="用于登录系统" allow-clear />
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
<el-form-item label="姓名" prop="nickname">
|
<a-form-item label="姓名" name="nickname">
|
||||||
<el-input v-model="form.nickname" placeholder="请输入完整的真实姓名" clearable></el-input>
|
<a-input v-model:value="form.nickname" placeholder="请输入完整的真实姓名" allow-clear />
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
<template v-if="mode=='add'">
|
<template v-if="mode === 'add'">
|
||||||
<el-form-item label="登录密码" prop="password">
|
<a-form-item label="登录密码" name="password">
|
||||||
<el-input type="password" v-model="form.password" clearable show-password></el-input>
|
<a-input-password v-model:value="form.password" allow-clear />
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
<el-form-item label="确认密码" prop="password2">
|
<a-form-item label="确认密码" name="password2">
|
||||||
<el-input type="password" v-model="form.password2" clearable show-password></el-input>
|
<a-input-password v-model:value="form.password2" allow-clear />
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<el-form-item label="所属部门" prop="group">
|
<a-form-item label="所属部门" name="department_id">
|
||||||
<el-tree-select v-model="form.department_id" :data="department" :props="departmentProps" placeholder="请选择部门" />
|
<a-tree-select v-model:value="form.department_id" :tree-data="department"
|
||||||
</el-form-item>
|
:field-names="departmentFieldNames" :tree-default-expand-all="false" show-icon placeholder="请选择部门" allow-clear
|
||||||
<el-form-item label="所属角色" prop="roles">
|
tree-node-filter-prop="title" />
|
||||||
<el-tree-select v-model="form.roles" :data="groups" :props="groupsProps" multiple placeholder="请选择角色" />
|
</a-form-item>
|
||||||
</el-form-item>
|
<a-form-item label="所属角色" name="roles">
|
||||||
</el-form>
|
<a-tree-select v-model:value="form.roles" :tree-data="groups" :field-names="groupsFieldNames"
|
||||||
|
:tree-default-expand-all="false" :tree-checkable="true" placeholder="请选择角色" allow-clear
|
||||||
|
tree-node-filter-prop="title" multiple />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="visible=false" >取 消</el-button>
|
<a-button @click="handleCancel">取 消</a-button>
|
||||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
<a-button v-if="mode !== 'show'" type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive } from 'vue'
|
||||||
emits: ['success', 'closed'],
|
import { message } from 'ant-design-vue'
|
||||||
data() {
|
import scUpload from '@/components/scUpload/index.vue'
|
||||||
return {
|
import authApi from '@/api/auth'
|
||||||
mode: "add",
|
|
||||||
titleMap: {
|
const emit = defineEmits(['success', 'closed'])
|
||||||
|
|
||||||
|
const mode = ref('add')
|
||||||
|
const titleMap = {
|
||||||
add: '新增用户',
|
add: '新增用户',
|
||||||
edit: '编辑用户',
|
edit: '编辑用户',
|
||||||
show: '查看'
|
show: '查看'
|
||||||
},
|
}
|
||||||
visible: false,
|
const visible = ref(false)
|
||||||
isSaveing: false,
|
const isSaveing = ref(false)
|
||||||
//表单数据
|
|
||||||
form: { username: "", avatar: "", department_id: 0, roles: []},
|
// 表单数据
|
||||||
//验证规则
|
const form = reactive({
|
||||||
rules: {
|
username: '',
|
||||||
// avatar:[
|
avatar: '',
|
||||||
// {required: true, message: '请上传头像'}
|
department_id: 0,
|
||||||
// ],
|
roles: []
|
||||||
username: [{required: true, message: '请输入登录账号'}],
|
})
|
||||||
nickname: [{required: true, message: '请输入真实姓名'}],
|
|
||||||
|
// 表单引用
|
||||||
|
const dialogForm = ref()
|
||||||
|
|
||||||
|
// 验证规则
|
||||||
|
const rules = {
|
||||||
|
username: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
|
||||||
|
nickname: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
|
||||||
password: [
|
password: [
|
||||||
{required: true, message: '请输入登录密码'},
|
{ required: true, message: '请输入登录密码', trigger: 'blur' },
|
||||||
{validator: (rule, value, callback) => {
|
{
|
||||||
if (this.form.password2 !== '') {
|
validator: (rule, value) => {
|
||||||
this.$refs.dialogForm.validateField('password2');
|
if (form.password2 !== '') {
|
||||||
|
dialogForm.value?.validateFields('password2')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
trigger: 'change'
|
||||||
}
|
}
|
||||||
callback();
|
|
||||||
}}
|
|
||||||
],
|
],
|
||||||
password2: [
|
password2: [
|
||||||
{required: true, message: '请再次输入密码'},
|
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||||
{validator: (rule, value, callback) => {
|
{
|
||||||
if (value !== this.form.password) {
|
validator: (rule, value) => {
|
||||||
callback(new Error('两次输入密码不一致!'));
|
if (value !== form.password) {
|
||||||
}else{
|
return Promise.reject(new Error('两次输入密码不一致!'))
|
||||||
callback();
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
//所需数据选项
|
|
||||||
department: [],
|
// 所需数据选项
|
||||||
departmentProps: {
|
const department = ref([])
|
||||||
value: "id",
|
const departmentFieldNames = {
|
||||||
label: "title",
|
value: 'id',
|
||||||
multiple: false,
|
label: 'title',
|
||||||
checkStrictly: true
|
children: 'children'
|
||||||
},
|
}
|
||||||
groups: [],
|
|
||||||
groupsProps: {
|
const groups = ref([])
|
||||||
value: "id",
|
const groupsFieldNames = {
|
||||||
label: "title",
|
value: 'id',
|
||||||
multiple: false,
|
label: 'title',
|
||||||
checkStrictly: true
|
children: 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示对话框
|
||||||
|
const open = (openMode = 'add') => {
|
||||||
|
mode.value = openMode
|
||||||
|
visible.value = true
|
||||||
|
return {
|
||||||
|
setData,
|
||||||
|
open,
|
||||||
|
close
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
mounted() {
|
// 关闭对话框
|
||||||
this.getGroup()
|
const close = () => {
|
||||||
this.getDepartment()
|
visible.value = false
|
||||||
},
|
}
|
||||||
methods: {
|
|
||||||
//显示
|
// 处理取消
|
||||||
open(mode='add'){
|
const handleCancel = () => {
|
||||||
this.mode = mode;
|
emit('closed')
|
||||||
this.visible = true;
|
visible.value = false
|
||||||
return this
|
}
|
||||||
},
|
|
||||||
//加载树数据
|
// 加载树数据
|
||||||
async getGroup(){
|
const getGroup = async () => {
|
||||||
var res = await this.$API.auth.role.list.get({is_tree: 1});
|
const res = await authApi.role.list.get({ is_tree: 1 })
|
||||||
this.groups = res.data;
|
groups.value = res.data
|
||||||
},
|
}
|
||||||
async getDepartment(){
|
|
||||||
var res = await this.$API.auth.department.list.get({is_tree: 1});
|
const getDepartment = async () => {
|
||||||
this.department = res.data;
|
const res = await authApi.department.list.get({ is_tree: 1 })
|
||||||
},
|
department.value = res.data
|
||||||
//表单提交方法
|
}
|
||||||
submit(){
|
|
||||||
this.$refs.dialogForm.validate(async (valid) => {
|
// 表单提交方法
|
||||||
if (valid) {
|
const submit = async () => {
|
||||||
this.isSaveing = true;
|
try {
|
||||||
var res = {};
|
await dialogForm.value.validate()
|
||||||
if(this.mode == 'add'){
|
isSaveing.value = true
|
||||||
res = await this.$API.auth.users.add.post(this.form);
|
let res = {}
|
||||||
}else{
|
if (mode.value === 'add') {
|
||||||
res = await this.$API.auth.users.edit.post(this.form);
|
res = await authApi.users.add.post(form)
|
||||||
|
} else {
|
||||||
|
res = await authApi.users.edit.post(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isSaveing = false;
|
isSaveing.value = false
|
||||||
if(res.code == 1){
|
if (res.code === 1) {
|
||||||
this.$emit('success', this.form, this.mode)
|
emit('success', form, mode.value)
|
||||||
this.visible = false;
|
visible.value = false
|
||||||
this.$message.success("操作成功")
|
message.success('操作成功')
|
||||||
}else{
|
} else {
|
||||||
this.$alert(res.message, "提示", {type: 'error'})
|
message.error(res.message)
|
||||||
}
|
}
|
||||||
}else{
|
} catch (error) {
|
||||||
return false;
|
console.error('表单验证失败', error)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
},
|
|
||||||
//表单注入数据
|
|
||||||
setData(data){
|
|
||||||
this.form.uid = data.uid
|
|
||||||
this.form.username = data.username
|
|
||||||
this.form.avatar = data.avatar
|
|
||||||
this.form.nickname = data.nickname
|
|
||||||
this.form.department_id = data.department_id
|
|
||||||
|
|
||||||
data.roles.map(item => {
|
// 表单注入数据
|
||||||
this.form.roles.push(item.id)
|
const setData = (data) => {
|
||||||
})
|
form.uid = data.uid
|
||||||
}
|
form.username = data.username
|
||||||
}
|
form.avatar = data.avatar
|
||||||
}
|
form.nickname = data.nickname
|
||||||
|
form.department_id = data.department_id
|
||||||
|
form.roles = data.roles.map(item => item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
getGroup()
|
||||||
|
getDepartment()
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
setData,
|
||||||
|
close
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user