feat: 增加菜单管理
This commit is contained in:
@@ -33,3 +33,11 @@ export const updateOrder = (data) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**重试订单流程 */
|
||||||
|
export const retryOrderHook = (data) => {
|
||||||
|
return http2.post('/api/v1/admin/order/retry_hook', data,{
|
||||||
|
headers:{
|
||||||
|
'Content-Type':'multipart/form-data'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { http2 } from "@/utils/request.js"
|
||||||
|
|
||||||
|
// ========== 后台菜单管理 ==========
|
||||||
|
|
||||||
|
/** 获取后台菜单列表 */
|
||||||
|
export const getWebRoutsList = (params) => {
|
||||||
|
return http2.get('/api/v1/admin/server/web_routs/list', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增后台菜单 */
|
||||||
|
export const addWebRouts = (data) => {
|
||||||
|
return http2.post('/api/v1/admin/server/web_routs/add', data, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改后台菜单 */
|
||||||
|
export const updateWebRouts = (data) => {
|
||||||
|
return http2.post('/api/v1/admin/server/web_routs/update', data, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除后台菜单 */
|
||||||
|
export const deleteWebRouts = (data) => {
|
||||||
|
return http2.delete('/api/v1/admin/server/web_routs/delete', {
|
||||||
|
data,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 后台菜单权限管理 ==========
|
||||||
|
|
||||||
|
/** 获取后台菜单权限列表 */
|
||||||
|
export const getWebRoutsPermissionList = (params) => {
|
||||||
|
return http2.get('/api/v1/admin/server/web_routs/permission/list', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增后台菜单权限 */
|
||||||
|
export const addWebRoutsPermission = (data) => {
|
||||||
|
return http2.post('/api/v1/admin/server/web_routs/permission/add', data, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改后台菜单权限 */
|
||||||
|
export const updateWebRoutsPermission = (data) => {
|
||||||
|
return http2.post('/api/v1/admin/server/web_routs/permission/update', data, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除后台菜单权限 */
|
||||||
|
export const deleteWebRoutsPermission = (data) => {
|
||||||
|
return http2.delete('/api/v1/admin/server/web_routs/permission/delete', {
|
||||||
|
data,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取当前用户的后台菜单权限树 */
|
||||||
|
export const getMyWebRoutsPermission = () => {
|
||||||
|
return http2.get('/api/v1/admin/server/web_routs/my')
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<div class="icon-selector">
|
||||||
|
<el-input
|
||||||
|
:model-value="modelValue"
|
||||||
|
placeholder="点击选择图标"
|
||||||
|
readonly
|
||||||
|
@click="popoverVisible = true"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon v-if="modelValue" :size="18">
|
||||||
|
<component :is="modelValue" />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<el-icon v-if="modelValue" class="clear-btn" @click.stop="handleClear"><CircleClose /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-dialog v-model="popoverVisible" title="选择图标" width="680px" append-to-body>
|
||||||
|
<el-input v-model="searchKey" placeholder="搜索图标名称" clearable class="icon-search">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="icon-grid">
|
||||||
|
<div
|
||||||
|
v-for="name in filteredIcons"
|
||||||
|
:key="name"
|
||||||
|
class="icon-item"
|
||||||
|
:class="{ active: modelValue === name }"
|
||||||
|
@click="handleSelect(name)"
|
||||||
|
>
|
||||||
|
<el-icon :size="22"><component :is="name" /></el-icon>
|
||||||
|
<span class="icon-name">{{ name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="filteredIcons.length === 0" class="icon-empty">
|
||||||
|
未找到匹配的图标
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
import { Search, CircleClose } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: String, default: '' }
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const popoverVisible = ref(false)
|
||||||
|
const searchKey = ref('')
|
||||||
|
|
||||||
|
const allIcons = Object.keys(ElementPlusIconsVue).sort()
|
||||||
|
|
||||||
|
const filteredIcons = computed(() => {
|
||||||
|
if (!searchKey.value) return allIcons
|
||||||
|
const key = searchKey.value.toLowerCase()
|
||||||
|
return allIcons.filter(name => name.toLowerCase().includes(key))
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelect = (name) => {
|
||||||
|
emit('update:modelValue', name)
|
||||||
|
popoverVisible.value = false
|
||||||
|
searchKey.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-selector { width: 100%; }
|
||||||
|
.icon-search { margin-bottom: 12px; }
|
||||||
|
.icon-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.icon-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 10px 4px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.icon-item:hover {
|
||||||
|
border-color: #409eff;
|
||||||
|
background: #ecf5ff;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
.icon-item.active {
|
||||||
|
border-color: #409eff;
|
||||||
|
background: #409eff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.icon-name {
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.icon-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.clear-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #c0c4cc;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.clear-btn:hover { color: #f56c6c; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div class="menu-path-selector">
|
||||||
|
<el-input
|
||||||
|
:model-value="modelValue"
|
||||||
|
placeholder="点击从菜单中选择路径,或手动输入"
|
||||||
|
clearable
|
||||||
|
@input="$emit('update:modelValue', $event)"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="dialogVisible = true">
|
||||||
|
<el-icon><FolderOpened /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible" title="选择菜单路径" width="550px" append-to-body>
|
||||||
|
<el-input v-model="searchKey" placeholder="搜索菜单名称或路径" clearable class="path-search">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="menu-tree">
|
||||||
|
<el-tree
|
||||||
|
:data="filteredMenuTree"
|
||||||
|
:props="{ label: 'label', children: 'children' }"
|
||||||
|
node-key="path"
|
||||||
|
:default-expand-all="!!searchKey"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
highlight-current
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<div class="tree-node" :class="{ 'is-selected': modelValue === data.path, 'no-path': !data.path }">
|
||||||
|
<el-icon v-if="data.icon" :size="16" style="margin-right: 6px; flex-shrink: 0;">
|
||||||
|
<component :is="data.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span class="node-title">{{ data.title }}</span>
|
||||||
|
<el-tag v-if="data.path" size="small" type="info" class="node-path">{{ data.path }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</div>
|
||||||
|
<div v-if="filteredMenuTree.length === 0" class="tree-empty">
|
||||||
|
未找到匹配的菜单
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { Search, FolderOpened } from '@element-plus/icons-vue'
|
||||||
|
import { menus } from '@/config/menus'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: String, default: '' }
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const searchKey = ref('')
|
||||||
|
|
||||||
|
const buildTreeData = (menuList) => {
|
||||||
|
return menuList.map(item => {
|
||||||
|
const node = {
|
||||||
|
path: item.path || '',
|
||||||
|
title: item.title,
|
||||||
|
icon: item.icon || '',
|
||||||
|
label: item.title
|
||||||
|
}
|
||||||
|
if (item.children?.length) {
|
||||||
|
node.children = buildTreeData(item.children)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuTree = computed(() => buildTreeData(menus))
|
||||||
|
|
||||||
|
const filterTree = (nodes, keyword) => {
|
||||||
|
const key = keyword.toLowerCase()
|
||||||
|
const result = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
const titleMatch = node.title?.toLowerCase().includes(key)
|
||||||
|
const pathMatch = node.path?.toLowerCase().includes(key)
|
||||||
|
let filteredChildren = []
|
||||||
|
if (node.children?.length) {
|
||||||
|
filteredChildren = filterTree(node.children, keyword)
|
||||||
|
}
|
||||||
|
if (titleMatch || pathMatch || filteredChildren.length > 0) {
|
||||||
|
result.push({
|
||||||
|
...node,
|
||||||
|
children: filteredChildren.length > 0 ? filteredChildren : node.children
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredMenuTree = computed(() => {
|
||||||
|
if (!searchKey.value) return menuTree.value
|
||||||
|
return filterTree(menuTree.value, searchKey.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNodeClick = (data) => {
|
||||||
|
if (!data.path) return
|
||||||
|
emit('update:modelValue', data.path)
|
||||||
|
dialogVisible.value = false
|
||||||
|
searchKey.value = ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-path-selector { width: 100%; }
|
||||||
|
.path-search { margin-bottom: 12px; }
|
||||||
|
.menu-tree {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 4px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.tree-node.is-selected {
|
||||||
|
background: #ecf5ff;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
.tree-node.no-path {
|
||||||
|
color: #909399;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.node-title { margin-right: 8px; font-size: 13px; }
|
||||||
|
.node-path { flex-shrink: 0; }
|
||||||
|
.tree-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
:deep(.el-tree-node__content) { height: 36px; }
|
||||||
|
:deep(.el-tree-node__content:hover) { background-color: #f5f7fa; }
|
||||||
|
</style>
|
||||||
@@ -185,6 +185,14 @@ export const menus = [
|
|||||||
{
|
{
|
||||||
path: '/system/setting-manage',
|
path: '/system/setting-manage',
|
||||||
title: '配置管理'
|
title: '配置管理'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/menu',
|
||||||
|
title: '菜单管理',
|
||||||
|
children: [
|
||||||
|
{ path: '/system/menu-manage', title: '菜单列表' },
|
||||||
|
{ path: '/system/menu-permission', title: '菜单权限' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,6 +407,18 @@ const routes = [
|
|||||||
name: 'SettingManage',
|
name: 'SettingManage',
|
||||||
component: () => import('../views/system/SettingManage.vue'),
|
component: () => import('../views/system/SettingManage.vue'),
|
||||||
meta: { title: '配置管理' }
|
meta: { title: '配置管理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu-manage',
|
||||||
|
name: 'MenuManage',
|
||||||
|
component: () => import('../views/system/MenuManage.vue'),
|
||||||
|
meta: { title: '菜单管理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu-permission',
|
||||||
|
name: 'MenuPermission',
|
||||||
|
component: () => import('../views/system/MenuPermission.vue'),
|
||||||
|
meta: { title: '菜单权限' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,6 +22,12 @@
|
|||||||
<el-option label="已失效" value="2" />
|
<el-option label="已失效" value="2" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="错误信息">
|
||||||
|
<el-select v-model="queryParams.error" placeholder="全部" clearable style="width: 140px">
|
||||||
|
<el-option label="有错误的订单" :value="true" />
|
||||||
|
<el-option label="无错误的订单" :value="false" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleQuery">
|
<el-button type="primary" @click="handleQuery">
|
||||||
<el-icon><Search /></el-icon>搜索
|
<el-icon><Search /></el-icon>搜索
|
||||||
@@ -89,11 +95,22 @@
|
|||||||
<span>{{ row.payNum }}</span>
|
<span>{{ row.payNum }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="订单状态" width="100">
|
<el-table-column label="订单状态" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getStatusType(row.state)">
|
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||||||
{{ getStatusText(row.state) }}
|
<el-tag :type="getStatusType(row.state)">
|
||||||
</el-tag>
|
{{ getStatusText(row.state) }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="row.error" type="danger" size="small">异常</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="错误信息" min-width="250">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip v-if="row.error" :content="row.error" placement="top" :show-after="300">
|
||||||
|
<span class="error-text">{{ row.error }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="支付方式" width="100">
|
<el-table-column label="支付方式" width="100">
|
||||||
@@ -111,11 +128,12 @@
|
|||||||
<span>{{ formatDate(row.CreatedAt) }}</span>
|
<span>{{ formatDate(row.CreatedAt) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
<el-table-column label="操作" width="250" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button v-if="row.error" type="danger" link @click="handleRetryOrder(row)">重试流程</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -162,6 +180,10 @@
|
|||||||
<el-descriptions-item label="创建时间">{{ formatDate(orderDetail.CreatedAt) }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatDate(orderDetail.CreatedAt) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">{{ formatDate(orderDetail.UpdatedAt) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ formatDate(orderDetail.UpdatedAt) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="参数信息">{{ orderDetail.args || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="参数信息">{{ orderDetail.args || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item v-if="orderDetail.error" label="错误信息" :span="2">
|
||||||
|
<el-tag type="danger" size="small" style="margin-right: 6px;">异常</el-tag>
|
||||||
|
<span style="color: #f56c6c;">{{ orderDetail.error }}</span>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="备注" :span="2">{{ orderDetail.note || '无' }}</el-descriptions-item>
|
<el-descriptions-item label="备注" :span="2">{{ orderDetail.note || '无' }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -389,7 +411,7 @@
|
|||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Delete, Search, Download, Refresh, User, ShoppingCart, Ticket, Money, Close } from '@element-plus/icons-vue'
|
import { Plus, Delete, Search, Download, Refresh, User, ShoppingCart, Ticket, Money, Close } from '@element-plus/icons-vue'
|
||||||
import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder } from '@/api/admin/order'
|
import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder, retryOrderHook } from '@/api/admin/order'
|
||||||
import UserListSelector from '@/components/admin/UserListSelector.vue'
|
import UserListSelector from '@/components/admin/UserListSelector.vue'
|
||||||
import ProductSelector from '@/components/admin/ProductSelector.vue'
|
import ProductSelector from '@/components/admin/ProductSelector.vue'
|
||||||
import DiscountCodeSelector from '@/components/admin/DiscountCodeSelector.vue'
|
import DiscountCodeSelector from '@/components/admin/DiscountCodeSelector.vue'
|
||||||
@@ -403,7 +425,8 @@ const queryParams = reactive({
|
|||||||
key: '',
|
key: '',
|
||||||
state: '',
|
state: '',
|
||||||
user_id: '',
|
user_id: '',
|
||||||
user_key: ''
|
user_key: '',
|
||||||
|
error: null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 订单表单
|
// 订单表单
|
||||||
@@ -544,6 +567,7 @@ const resetQuery = () => {
|
|||||||
queryParams.state = ''
|
queryParams.state = ''
|
||||||
queryParams.user_id = ''
|
queryParams.user_id = ''
|
||||||
queryParams.user_key = ''
|
queryParams.user_key = ''
|
||||||
|
queryParams.error = null
|
||||||
queryParams.page = 1
|
queryParams.page = 1
|
||||||
fetchOrderList()
|
fetchOrderList()
|
||||||
}
|
}
|
||||||
@@ -644,6 +668,32 @@ const handleEdit = (row) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重试订单流程
|
||||||
|
const handleRetryOrder = (row) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认对订单「${row.name}」(ID: ${row.id}) 重试流程吗?`,
|
||||||
|
'重试订单流程',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认重试',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await retryOrderHook({ order_id: row.id })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success('重试流程已触发')
|
||||||
|
fetchOrderList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '重试失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重试订单流程失败:', error)
|
||||||
|
ElMessage.error(error.response?.data?.message || '重试订单流程失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
// 删除订单
|
// 删除订单
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm(`确认删除订单 ${row.name} 吗?`, '警告', {
|
ElMessageBox.confirm(`确认删除订单 ${row.name} 吗?`, '警告', {
|
||||||
@@ -966,4 +1016,21 @@ onMounted(() => {
|
|||||||
|
|
||||||
.unit-input-row { display: flex; align-items: center; gap: 6px; width: 100%; }
|
.unit-input-row { display: flex; align-items: center; gap: 6px; width: 100%; }
|
||||||
.unit-text { font-size: 13px; color: #606266; flex-shrink: 0; white-space: nowrap; }
|
.unit-text { font-size: 13px; color: #606266; flex-shrink: 0; white-space: nowrap; }
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 12px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
<template>
|
||||||
|
<div class="menu-manage-container">
|
||||||
|
<el-card class="main-container" shadow="never">
|
||||||
|
<div class="filter-section">
|
||||||
|
<div class="filter-content">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||||||
|
<el-form-item label="关键词">
|
||||||
|
<el-input v-model="queryParams.key" placeholder="菜单名称/路径" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="父级菜单">
|
||||||
|
<el-select v-model="queryParams.parent_id" placeholder="全部" clearable style="width: 160px">
|
||||||
|
<el-option v-for="m in parentMenuOptions" :key="m.id" :label="m.title" :value="m.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">
|
||||||
|
<el-icon><Search /></el-icon>搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="action-bar">
|
||||||
|
<el-radio-group v-model="viewMode" size="default" @change="handleViewModeChange">
|
||||||
|
<el-radio-button value="list">
|
||||||
|
<el-icon style="vertical-align: -2px;"><Grid /></el-icon> 列表视图
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button value="tree">
|
||||||
|
<el-icon style="vertical-align: -2px;"><Connection /></el-icon> 树状视图
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-button v-if="viewMode === 'list'" type="primary" @click="handleAdd(null)">
|
||||||
|
<el-icon><Plus /></el-icon>新增顶级菜单
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" @click="handleRefresh">
|
||||||
|
<el-icon><Refresh /></el-icon>刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表视图 -->
|
||||||
|
<div v-if="viewMode === 'list'" class="table-section">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="menuList"
|
||||||
|
style="width: 100%"
|
||||||
|
row-key="id"
|
||||||
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
|
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||||
|
>
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column prop="title" label="菜单名称" min-width="180" />
|
||||||
|
<el-table-column prop="path" label="路径" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" type="info">{{ row.path || '-' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="icon" label="图标" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.icon" style="display: flex; align-items: center; gap: 6px;">
|
||||||
|
<el-icon><component :is="row.icon" /></el-icon>
|
||||||
|
<span>{{ row.icon }}</span>
|
||||||
|
</div>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="parentId" label="父级ID" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.parentId ?? '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" width="170">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.CreatedAt) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="250" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<el-button type="primary" link @click="handleAdd(row)">添加子菜单</el-button>
|
||||||
|
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无菜单数据" :image-size="80" />
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.page"
|
||||||
|
v-model:page-size="queryParams.count"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:total="total"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
background
|
||||||
|
class="pagination"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 树状视图 -->
|
||||||
|
<div v-if="viewMode === 'tree'" v-loading="myPermLoading" class="tree-section">
|
||||||
|
<el-tree
|
||||||
|
v-if="myPermTree.length > 0"
|
||||||
|
:data="myPermTree"
|
||||||
|
node-key="id"
|
||||||
|
default-expand-all
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<div class="perm-tree-node">
|
||||||
|
<el-icon v-if="data.icon" style="margin-right: 6px; flex-shrink: 0;"><component :is="data.icon" /></el-icon>
|
||||||
|
<span class="perm-tree-title">{{ data.title }}</span>
|
||||||
|
<el-tag size="small" type="info" style="margin-left: 8px;">{{ data.path || '-' }}</el-tag>
|
||||||
|
<el-tag :type="data.enable ? 'success' : 'danger'" size="small" style="margin-left: 6px;">
|
||||||
|
{{ data.enable ? '启用' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<el-empty v-if="!myPermLoading && myPermTree.length === 0" description="暂无菜单权限数据" :image-size="80" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 菜单表单对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogType === 'add' ? '新增菜单' : '编辑菜单'"
|
||||||
|
width="550px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-form ref="formRef" :model="menuForm" :rules="menuRules" label-width="100px">
|
||||||
|
<el-form-item label="菜单名称" prop="title">
|
||||||
|
<el-input v-model="menuForm.title" placeholder="请输入菜单名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="菜单路径" prop="path">
|
||||||
|
<MenuPathSelector v-model="menuForm.path" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="菜单图标" prop="icon">
|
||||||
|
<IconSelector v-model="menuForm.icon" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="父级菜单">
|
||||||
|
<el-select v-model="menuForm.parent_id" placeholder="无(顶级菜单)" clearable style="width: 100%">
|
||||||
|
<el-option label="无(顶级菜单)" :value="0" />
|
||||||
|
<el-option v-for="m in parentMenuOptions" :key="m.id" :label="m.title" :value="m.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Search, Plus, Refresh, Grid, Connection } from '@element-plus/icons-vue'
|
||||||
|
import { getWebRoutsList, addWebRouts, updateWebRouts, deleteWebRouts, getMyWebRoutsPermission } from '@/api/admin/webRouts'
|
||||||
|
import { formatDate as formatDateTool } from '@/utils/tool'
|
||||||
|
import IconSelector from '@/components/admin/IconSelector.vue'
|
||||||
|
import MenuPathSelector from '@/components/admin/MenuPathSelector.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const menuList = ref([])
|
||||||
|
const parentMenuOptions = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogType = ref('add')
|
||||||
|
const formRef = ref(null)
|
||||||
|
const viewMode = ref('list')
|
||||||
|
|
||||||
|
const queryParams = reactive({
|
||||||
|
page: 1,
|
||||||
|
count: 10,
|
||||||
|
key: '',
|
||||||
|
parent_id: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuForm = reactive({
|
||||||
|
id: undefined,
|
||||||
|
title: '',
|
||||||
|
path: '',
|
||||||
|
icon: '',
|
||||||
|
parent_id: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuRules = {
|
||||||
|
title: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
|
||||||
|
path: [{ required: true, message: '请输入菜单路径', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateStr) => formatDateTool(dateStr)
|
||||||
|
|
||||||
|
const flattenForParent = (list) => {
|
||||||
|
const result = []
|
||||||
|
for (const item of list) {
|
||||||
|
result.push(item)
|
||||||
|
if (item.children?.length) {
|
||||||
|
result.push(...flattenForParent(item.children))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMenuList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {}
|
||||||
|
Object.keys(queryParams).forEach(key => {
|
||||||
|
if (queryParams[key] !== '' && queryParams[key] !== null && queryParams[key] !== undefined) {
|
||||||
|
params[key] = queryParams[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const res = await getWebRoutsList(params)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
menuList.value = res.data.data?.list || []
|
||||||
|
total.value = res.data.data?.all_count || 0
|
||||||
|
parentMenuOptions.value = flattenForParent(menuList.value)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取菜单列表失败:', error)
|
||||||
|
ElMessage.error('获取菜单列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.page = 1
|
||||||
|
fetchMenuList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.key = ''
|
||||||
|
queryParams.parent_id = null
|
||||||
|
queryParams.page = 1
|
||||||
|
fetchMenuList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
queryParams.count = size
|
||||||
|
fetchMenuList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
queryParams.page = page
|
||||||
|
fetchMenuList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdd = (parentRow) => {
|
||||||
|
dialogType.value = 'add'
|
||||||
|
dialogVisible.value = true
|
||||||
|
Object.assign(menuForm, {
|
||||||
|
id: undefined,
|
||||||
|
title: '',
|
||||||
|
path: '',
|
||||||
|
icon: '',
|
||||||
|
parent_id: parentRow?.id || 0
|
||||||
|
})
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogType.value = 'edit'
|
||||||
|
dialogVisible.value = true
|
||||||
|
Object.assign(menuForm, {
|
||||||
|
id: row.id,
|
||||||
|
title: row.title,
|
||||||
|
path: row.path,
|
||||||
|
icon: row.icon,
|
||||||
|
parent_id: row.parentId || 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确认删除菜单「${row.title}」吗?删除后其子菜单也将受到影响。`, '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteWebRouts({ id: row.id })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
fetchMenuList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitForm = () => {
|
||||||
|
formRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
try {
|
||||||
|
const submitData = {
|
||||||
|
title: menuForm.title,
|
||||||
|
path: menuForm.path,
|
||||||
|
icon: menuForm.icon
|
||||||
|
}
|
||||||
|
if (menuForm.parent_id) {
|
||||||
|
submitData.parent_id = menuForm.parent_id
|
||||||
|
}
|
||||||
|
let res
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
res = await addWebRouts(submitData)
|
||||||
|
} else {
|
||||||
|
submitData.id = menuForm.id
|
||||||
|
res = await updateWebRouts(submitData)
|
||||||
|
}
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
fetchMenuList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '操作失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const myPermLoading = ref(false)
|
||||||
|
const myPermTree = ref([])
|
||||||
|
|
||||||
|
const fetchMyPermission = async () => {
|
||||||
|
myPermLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getMyWebRoutsPermission()
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
myPermTree.value = res.data.data || []
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '获取失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取我的菜单权限失败:', error)
|
||||||
|
ElMessage.error('获取我的菜单权限失败')
|
||||||
|
} finally {
|
||||||
|
myPermLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleViewModeChange = (mode) => {
|
||||||
|
if (mode === 'list') {
|
||||||
|
fetchMenuList()
|
||||||
|
} else {
|
||||||
|
fetchMyPermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
if (viewMode.value === 'list') {
|
||||||
|
fetchMenuList()
|
||||||
|
} else {
|
||||||
|
fetchMyPermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchMenuList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-manage-container { padding: 0; }
|
||||||
|
.main-container { border: 1px solid #e1e8ed; background: #ffffff; }
|
||||||
|
.filter-section { padding: 0; border-bottom: 1px solid #e1e8ed; background: #fafbfc; }
|
||||||
|
.filter-content { display: flex; justify-content: space-between; align-items: flex-start; padding: 16px 20px; gap: 20px; flex-wrap: wrap; }
|
||||||
|
.filter-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||||
|
.filter-form :deep(.el-form-item) { margin-bottom: 0; margin-right: 8px; }
|
||||||
|
.filter-form :deep(.el-form-item__label) { font-size: 13px; }
|
||||||
|
.action-bar { display: flex; gap: 12px; flex-shrink: 0; }
|
||||||
|
.table-section { padding: 0; }
|
||||||
|
.action-buttons { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.text-muted { color: #c0c4cc; }
|
||||||
|
.pagination { margin-top: 20px; padding: 16px 20px; border-top: 1px solid #e1e8ed; background: #fafbfc; justify-content: flex-end; }
|
||||||
|
.dialog-footer { display: flex; justify-content: flex-end; gap: 12px; }
|
||||||
|
:deep(.el-card__body) { padding: 0; }
|
||||||
|
:deep(.el-table th) { background: #f8f9fa !important; border-bottom: 2px solid #e1e8ed; color: #2c3e50; font-weight: 600; font-size: 13px; }
|
||||||
|
:deep(.el-table td) { border-bottom: 1px solid #f0f2f5; color: #34495e; }
|
||||||
|
:deep(.el-table tr:hover > td) { background-color: #f8f9fa !important; }
|
||||||
|
.tree-section { padding: 16px 20px; min-height: 300px; }
|
||||||
|
.perm-tree-node { display: flex; align-items: center; padding: 2px 0; width: 100%; }
|
||||||
|
.perm-tree-title { font-size: 13px; font-weight: 500; }
|
||||||
|
.tree-section :deep(.el-tree-node__content) { height: 38px; }
|
||||||
|
.tree-section :deep(.el-tree-node__content:hover) { background-color: #f5f7fa; }
|
||||||
|
.action-bar { display: flex; gap: 12px; flex-shrink: 0; align-items: center; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,610 @@
|
|||||||
|
<template>
|
||||||
|
<div class="menu-permission-container">
|
||||||
|
<el-card class="main-container" shadow="never">
|
||||||
|
<div class="filter-section">
|
||||||
|
<div class="filter-content">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||||||
|
<el-form-item label="类型">
|
||||||
|
<el-select v-model="queryParams.owner_type" placeholder="请选择类型" clearable style="width: 130px" @change="handleOwnerTypeChange">
|
||||||
|
<el-option label="用户" value="user" />
|
||||||
|
<el-option label="管理员组" value="group" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户" v-if="queryParams.owner_type === 'user'">
|
||||||
|
<div class="selector-inline">
|
||||||
|
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
|
||||||
|
{{ queryUserName || `用户 #${queryParams.user_id}` }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button type="primary" plain @click="openUserSelector('query')" size="default">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="管理员组" v-if="queryParams.owner_type === 'group'">
|
||||||
|
<div class="selector-inline">
|
||||||
|
<el-tag v-if="queryParams.admin_group_id" type="success" closable @close="clearQueryGroup" style="margin-right: 8px;">
|
||||||
|
{{ queryGroupName || `组 #${queryParams.admin_group_id}` }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button type="success" plain @click="openGroupSelector('query')" size="default">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
{{ queryParams.admin_group_id ? '重新选择' : '选择管理员组' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">
|
||||||
|
<el-icon><Search /></el-icon>查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="action-bar">
|
||||||
|
<el-button type="primary" @click="handleAdd">
|
||||||
|
<el-icon><Plus /></el-icon>分配权限
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" @click="handleRefresh">
|
||||||
|
<el-icon><Refresh /></el-icon>刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-section">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="permissionList"
|
||||||
|
style="width: 100%"
|
||||||
|
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||||
|
>
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column label="所属类型" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getOwnerType(row) === 'user' ? 'primary' : 'success'" size="small">
|
||||||
|
{{ getOwnerType(row) === 'user' ? '用户' : '管理员组' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="所属对象" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.userId">用户 #{{ row.userId }}</span>
|
||||||
|
<span v-else-if="row.adminGroupId">管理员组 #{{ row.adminGroupId }}</span>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="菜单" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.webRouts">
|
||||||
|
<span style="font-weight: 500;">{{ row.webRouts.title }}</span>
|
||||||
|
<el-tag size="small" type="info" style="margin-left: 8px;">{{ row.webRouts.path }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<span v-else class="text-muted">菜单ID: {{ row.webRoutsId }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.enable ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.enable ? '启用' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" width="170">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.CreatedAt) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<el-button type="primary" link @click="handleToggleEnable(row)">
|
||||||
|
{{ row.enable ? '禁用' : '启用' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无权限数据" :image-size="80" />
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 用户选择弹窗 -->
|
||||||
|
<UserListSelector
|
||||||
|
v-model="userSelectorVisible"
|
||||||
|
:current-user-id="selectorTarget === 'query' ? queryParams.user_id : permForm.user_id"
|
||||||
|
@confirm="handleUserConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 管理员组选择弹窗 -->
|
||||||
|
<UserGroupSelector
|
||||||
|
v-model="groupSelectorVisible"
|
||||||
|
:current-group-id="selectorTarget === 'query' ? queryParams.admin_group_id : permForm.admin_group_id"
|
||||||
|
admin-group
|
||||||
|
@confirm="handleGroupConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 菜单选择弹窗 -->
|
||||||
|
<el-dialog v-model="menuSelectorVisible" title="选择菜单" width="700px" append-to-body @open="openMenuSelector">
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
||||||
|
<el-input v-model="menuSearchKey" placeholder="搜索菜单名称或路径" clearable style="flex: 1;" @keyup.enter="fetchMenuSelectorList">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button type="primary" @click="fetchMenuSelectorList">搜索</el-button>
|
||||||
|
<el-button type="success" @click="fetchMenuSelectorList">
|
||||||
|
<el-icon><Refresh /></el-icon>刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-loading="menuSelectorLoading"
|
||||||
|
:data="menuSelectorFlatList"
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="handleMenuCurrentChange"
|
||||||
|
:height="400"
|
||||||
|
style="width: 100%"
|
||||||
|
row-key="id"
|
||||||
|
>
|
||||||
|
<el-table-column prop="id" label="ID" width="70" />
|
||||||
|
<el-table-column prop="title" label="菜单名称" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :style="{ paddingLeft: (row._level || 0) * 20 + 'px' }">
|
||||||
|
<span v-if="row._level" style="color: #c0c4cc; margin-right: 4px;">└</span>
|
||||||
|
{{ row.title }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="path" label="路径" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" type="info">{{ row.path || '-' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="icon" label="图标" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-icon v-if="row.icon"><component :is="row.icon" /></el-icon>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="menuSelectorVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :disabled="!menuSelectorTemp" @click="confirmMenuSelect">确定选择</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 分配/编辑权限对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogType === 'add' ? '分配菜单权限' : '编辑菜单权限'"
|
||||||
|
width="600px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-form ref="formRef" :model="permForm" :rules="formRules" label-width="120px">
|
||||||
|
<el-form-item label="所属类型" prop="owner_type">
|
||||||
|
<el-select v-model="permForm.owner_type" placeholder="请选择" style="width: 100%" :disabled="dialogType === 'edit'" @change="handleFormOwnerTypeChange">
|
||||||
|
<el-option label="用户" value="user" />
|
||||||
|
<el-option label="管理员组" value="group" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户" prop="user_id" v-if="permForm.owner_type === 'user'">
|
||||||
|
<div class="selector-inline" style="width: 100%;">
|
||||||
|
<el-tag v-if="permForm.user_id" type="primary" closable @close="permForm.user_id = null" style="margin-right: 8px;">
|
||||||
|
{{ formUserName || `用户 #${permForm.user_id}` }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button type="primary" plain @click="openUserSelector('form')">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
{{ permForm.user_id ? '重新选择' : '选择用户' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="管理员组" prop="admin_group_id" v-if="permForm.owner_type === 'group'">
|
||||||
|
<div class="selector-inline" style="width: 100%;">
|
||||||
|
<el-tag v-if="permForm.admin_group_id" type="success" closable @close="permForm.admin_group_id = null" style="margin-right: 8px;">
|
||||||
|
{{ formGroupName || `组 #${permForm.admin_group_id}` }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button type="success" plain @click="openGroupSelector('form')">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
{{ permForm.admin_group_id ? '重新选择' : '选择管理员组' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="菜单" prop="web_routs_id">
|
||||||
|
<div class="selector-inline" style="width: 100%;">
|
||||||
|
<el-tag v-if="permForm.web_routs_id" closable @close="clearFormMenu" style="margin-right: 8px;">
|
||||||
|
{{ formMenuName || `菜单 #${permForm.web_routs_id}` }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button plain @click="menuSelectorVisible = true">
|
||||||
|
<el-icon><Menu /></el-icon>
|
||||||
|
{{ permForm.web_routs_id ? '重新选择' : '选择菜单' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否启用">
|
||||||
|
<el-switch v-model="permForm.enable" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Search, Plus, Refresh, User, Menu } from '@element-plus/icons-vue'
|
||||||
|
import UserListSelector from '@/components/admin/UserListSelector.vue'
|
||||||
|
import UserGroupSelector from '@/components/admin/UserGroupSelector.vue'
|
||||||
|
import {
|
||||||
|
getWebRoutsList,
|
||||||
|
getWebRoutsPermissionList,
|
||||||
|
addWebRoutsPermission,
|
||||||
|
updateWebRoutsPermission,
|
||||||
|
deleteWebRoutsPermission
|
||||||
|
} from '@/api/admin/webRouts'
|
||||||
|
import { formatDate as formatDateTool } from '@/utils/tool'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const permissionList = ref([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogType = ref('add')
|
||||||
|
const formRef = ref(null)
|
||||||
|
|
||||||
|
const userSelectorVisible = ref(false)
|
||||||
|
const groupSelectorVisible = ref(false)
|
||||||
|
const menuSelectorVisible = ref(false)
|
||||||
|
const menuSelectorLoading = ref(false)
|
||||||
|
const menuSelectorTemp = ref(null)
|
||||||
|
const menuSearchKey = ref('')
|
||||||
|
const selectorTarget = ref('query')
|
||||||
|
|
||||||
|
const queryUserName = ref('')
|
||||||
|
const queryGroupName = ref('')
|
||||||
|
const formUserName = ref('')
|
||||||
|
const formGroupName = ref('')
|
||||||
|
const formMenuName = ref('')
|
||||||
|
|
||||||
|
const queryParams = reactive({
|
||||||
|
owner_type: 'group',
|
||||||
|
user_id: null,
|
||||||
|
admin_group_id: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const permForm = reactive({
|
||||||
|
id: undefined,
|
||||||
|
web_routs_id: null,
|
||||||
|
enable: true,
|
||||||
|
owner_type: 'group',
|
||||||
|
admin_group_id: null,
|
||||||
|
user_id: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRules = {
|
||||||
|
owner_type: [{ required: true, message: '请选择所属类型', trigger: 'change' }],
|
||||||
|
web_routs_id: [{ required: true, message: '请选择菜单', trigger: 'change' }],
|
||||||
|
user_id: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
||||||
|
admin_group_id: [{ required: true, message: '请选择管理员组', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateStr) => formatDateTool(dateStr)
|
||||||
|
|
||||||
|
const getOwnerType = (row) => {
|
||||||
|
if (row.userId) return 'user'
|
||||||
|
if (row.adminGroupId) return 'group'
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPermissionList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {}
|
||||||
|
if (queryParams.owner_type) params.owner_type = queryParams.owner_type
|
||||||
|
if (queryParams.owner_type === 'user' && queryParams.user_id) params.user_id = queryParams.user_id
|
||||||
|
if (queryParams.owner_type === 'group' && queryParams.admin_group_id) params.admin_group_id = queryParams.admin_group_id
|
||||||
|
const res = await getWebRoutsPermissionList(params)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
permissionList.value = res.data.data || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取权限列表失败:', error)
|
||||||
|
ElMessage.error('获取权限列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flattenMenuTree = (list, level = 0) => {
|
||||||
|
const result = []
|
||||||
|
for (const item of list) {
|
||||||
|
result.push({ ...item, _level: level, children: undefined })
|
||||||
|
if (item.children?.length) {
|
||||||
|
result.push(...flattenMenuTree(item.children, level + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuSelectorFlatList = ref([])
|
||||||
|
|
||||||
|
const openMenuSelector = () => {
|
||||||
|
menuSelectorTemp.value = null
|
||||||
|
menuSearchKey.value = ''
|
||||||
|
fetchMenuSelectorList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMenuSelectorList = async () => {
|
||||||
|
menuSelectorLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = { page: 1, count: 10 }
|
||||||
|
if (menuSearchKey.value) params.key = menuSearchKey.value
|
||||||
|
const res = await getWebRoutsList(params)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
const treeList = res.data.data?.list || []
|
||||||
|
menuSelectorFlatList.value = flattenMenuTree(treeList)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取菜单列表失败:', error)
|
||||||
|
} finally {
|
||||||
|
menuSelectorLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMenuCurrentChange = (row) => {
|
||||||
|
menuSelectorTemp.value = row
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmMenuSelect = () => {
|
||||||
|
if (!menuSelectorTemp.value) return
|
||||||
|
permForm.web_routs_id = menuSelectorTemp.value.id
|
||||||
|
formMenuName.value = `${menuSelectorTemp.value.title} (${menuSelectorTemp.value.path})`
|
||||||
|
menuSelectorVisible.value = false
|
||||||
|
menuSelectorTemp.value = null
|
||||||
|
menuSearchKey.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFormMenu = () => {
|
||||||
|
permForm.web_routs_id = null
|
||||||
|
formMenuName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOwnerTypeChange = () => {
|
||||||
|
queryParams.user_id = null
|
||||||
|
queryParams.admin_group_id = null
|
||||||
|
queryUserName.value = ''
|
||||||
|
queryGroupName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFormOwnerTypeChange = () => {
|
||||||
|
permForm.user_id = null
|
||||||
|
permForm.admin_group_id = null
|
||||||
|
formUserName.value = ''
|
||||||
|
formGroupName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const canQuery = () => {
|
||||||
|
if (queryParams.owner_type === 'user' && !queryParams.user_id) {
|
||||||
|
ElMessage.warning('请先选择用户')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (queryParams.owner_type === 'group' && !queryParams.admin_group_id) {
|
||||||
|
ElMessage.warning('请先选择管理员组')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!queryParams.owner_type) {
|
||||||
|
ElMessage.warning('请先选择类型')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
if (!canQuery()) return
|
||||||
|
fetchPermissionList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
if (!canQuery()) return
|
||||||
|
fetchPermissionList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.owner_type = 'group'
|
||||||
|
queryParams.user_id = null
|
||||||
|
queryParams.admin_group_id = null
|
||||||
|
queryUserName.value = ''
|
||||||
|
queryGroupName.value = ''
|
||||||
|
permissionList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearQueryUser = () => {
|
||||||
|
queryParams.user_id = null
|
||||||
|
queryUserName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearQueryGroup = () => {
|
||||||
|
queryParams.admin_group_id = null
|
||||||
|
queryGroupName.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUserSelector = (target) => {
|
||||||
|
selectorTarget.value = target
|
||||||
|
userSelectorVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openGroupSelector = (target) => {
|
||||||
|
selectorTarget.value = target
|
||||||
|
groupSelectorVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUserConfirm = (user) => {
|
||||||
|
const id = user.user_id || user.UserId || user.userId
|
||||||
|
const name = user.user_name || user.UserName || user.userName || `用户 #${id}`
|
||||||
|
if (selectorTarget.value === 'query') {
|
||||||
|
queryParams.user_id = id
|
||||||
|
queryUserName.value = name
|
||||||
|
} else {
|
||||||
|
permForm.user_id = id
|
||||||
|
formUserName.value = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGroupConfirm = (group) => {
|
||||||
|
const name = group.name || group.groupName || `组 #${group.id}`
|
||||||
|
const id = group.id
|
||||||
|
if (selectorTarget.value === 'query') {
|
||||||
|
queryParams.admin_group_id = id
|
||||||
|
queryGroupName.value = name
|
||||||
|
} else {
|
||||||
|
permForm.admin_group_id = id
|
||||||
|
formGroupName.value = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogType.value = 'add'
|
||||||
|
dialogVisible.value = true
|
||||||
|
Object.assign(permForm, {
|
||||||
|
id: undefined,
|
||||||
|
web_routs_id: null,
|
||||||
|
enable: true,
|
||||||
|
owner_type: 'group',
|
||||||
|
admin_group_id: null,
|
||||||
|
user_id: null
|
||||||
|
})
|
||||||
|
formUserName.value = ''
|
||||||
|
formGroupName.value = ''
|
||||||
|
formMenuName.value = ''
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogType.value = 'edit'
|
||||||
|
dialogVisible.value = true
|
||||||
|
const ownerType = row.userId ? 'user' : 'group'
|
||||||
|
Object.assign(permForm, {
|
||||||
|
id: row.id,
|
||||||
|
web_routs_id: row.webRoutsId,
|
||||||
|
enable: row.enable,
|
||||||
|
owner_type: ownerType,
|
||||||
|
admin_group_id: row.adminGroupId || null,
|
||||||
|
user_id: row.userId || null
|
||||||
|
})
|
||||||
|
formUserName.value = row.userId ? `用户 #${row.userId}` : ''
|
||||||
|
formGroupName.value = row.adminGroupId ? `组 #${row.adminGroupId}` : ''
|
||||||
|
if (row.webRouts) {
|
||||||
|
formMenuName.value = `${row.webRouts.title} (${row.webRouts.path})`
|
||||||
|
} else {
|
||||||
|
formMenuName.value = row.webRoutsId ? `菜单 #${row.webRoutsId}` : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggleEnable = async (row) => {
|
||||||
|
const newEnable = !row.enable
|
||||||
|
const action = newEnable ? '启用' : '禁用'
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确认${action}该菜单权限吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
const res = await updateWebRoutsPermission({
|
||||||
|
id: row.id,
|
||||||
|
enable: newEnable
|
||||||
|
})
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success(`${action}成功`)
|
||||||
|
fetchPermissionList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || `${action}失败`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error(error.response?.data?.message || `${action}失败`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm('确认删除该菜单权限吗?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteWebRoutsPermission({ id: row.id })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
fetchPermissionList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitForm = () => {
|
||||||
|
formRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
try {
|
||||||
|
const submitData = {
|
||||||
|
web_routs_id: permForm.web_routs_id,
|
||||||
|
enable: permForm.enable,
|
||||||
|
owner_type: permForm.owner_type
|
||||||
|
}
|
||||||
|
if (permForm.owner_type === 'user') {
|
||||||
|
submitData.user_id = permForm.user_id
|
||||||
|
} else {
|
||||||
|
submitData.admin_group_id = permForm.admin_group_id
|
||||||
|
}
|
||||||
|
|
||||||
|
let res
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
res = await addWebRoutsPermission(submitData)
|
||||||
|
} else {
|
||||||
|
submitData.id = permForm.id
|
||||||
|
res = await updateWebRoutsPermission(submitData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success(dialogType.value === 'add' ? '分配成功' : '修改成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
fetchPermissionList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.message || '操作失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-permission-container { padding: 0; }
|
||||||
|
.main-container { border: 1px solid #e1e8ed; background: #ffffff; }
|
||||||
|
.filter-section { padding: 0; border-bottom: 1px solid #e1e8ed; background: #fafbfc; }
|
||||||
|
.filter-content { display: flex; justify-content: space-between; align-items: flex-start; padding: 16px 20px; gap: 20px; flex-wrap: wrap; }
|
||||||
|
.filter-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||||
|
.filter-form :deep(.el-form-item) { margin-bottom: 0; margin-right: 8px; }
|
||||||
|
.filter-form :deep(.el-form-item__label) { font-size: 13px; }
|
||||||
|
.action-bar { display: flex; gap: 12px; flex-shrink: 0; }
|
||||||
|
.selector-inline { display: flex; align-items: center; }
|
||||||
|
.table-section { padding: 0; }
|
||||||
|
.action-buttons { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.text-muted { color: #c0c4cc; }
|
||||||
|
.dialog-footer { display: flex; justify-content: flex-end; gap: 12px; }
|
||||||
|
:deep(.el-card__body) { padding: 0; }
|
||||||
|
:deep(.el-table th) { background: #f8f9fa !important; border-bottom: 2px solid #e1e8ed; color: #2c3e50; font-weight: 600; font-size: 13px; }
|
||||||
|
:deep(.el-table td) { border-bottom: 1px solid #f0f2f5; color: #34495e; }
|
||||||
|
:deep(.el-table tr:hover > td) { background-color: #f8f9fa !important; }
|
||||||
|
</style>
|
||||||
+550
-193
@@ -7,20 +7,54 @@
|
|||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
"name": "VNC指令管理"
|
"name": "后台菜单权限管理"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "后台菜单管理"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/v1/admin/server/vnc_command/group/list": {
|
"/api/v1/admin/server/web_routs/permission/list": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取指令分组列表(含指令项)",
|
"summary": "获取后台菜单权限列表",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"operationId": "VncCommandGroupList",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"VNC指令管理"
|
"后台菜单权限管理"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "owner_type",
|
||||||
|
"in": "query",
|
||||||
|
"description": "所属类型",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"user",
|
||||||
|
"group"
|
||||||
|
],
|
||||||
|
"default": "group"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "用户ID(owner_type为user时必填)",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin_group_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "管理员组ID(owner_type为group时必填)",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
@@ -34,12 +68,14 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "查询成功",
|
"description": "成功",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"properties": {}
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRoutsPermission"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -49,14 +85,13 @@
|
|||||||
"security": []
|
"security": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/server/vnc_command/group/create": {
|
"/api/v1/admin/server/web_routs/permission/add": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "创建指令分组",
|
"summary": "新增后台菜单权限",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"operationId": "VncCommandGroupCreate",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"VNC指令管理"
|
"后台菜单权限管理"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -76,53 +111,69 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"web_routs_id": {
|
||||||
"type": "string",
|
|
||||||
"description": "分组名称",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"sort": {
|
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "排序",
|
"description": "菜单ID",
|
||||||
"example": 0
|
"example": 0
|
||||||
},
|
},
|
||||||
"default_icon": {
|
"enable": {
|
||||||
"description": "分组文本图标(如 📚),当无文件图标时使用",
|
"type": "boolean",
|
||||||
"example": "",
|
"default": true,
|
||||||
"type": "string"
|
"description": "是否启用",
|
||||||
|
"example": "true"
|
||||||
},
|
},
|
||||||
"icon_file_id": {
|
"owner_type": {
|
||||||
"description": "分组图标文件ID",
|
"type": "string",
|
||||||
"example": "",
|
"enum": [
|
||||||
"type": "string"
|
"user",
|
||||||
|
"group"
|
||||||
|
],
|
||||||
|
"description": "所属类型",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"admin_group_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "管理员组ID(owner_type为group时使用)",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "用户ID(owner_type为user时使用)",
|
||||||
|
"example": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"name"
|
"web_routs_id",
|
||||||
|
"owner_type"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"examples": {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "创建成功",
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRoutsPermission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"headers": {}
|
"headers": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": []
|
"security": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/server/vnc_command/group/update": {
|
"/api/v1/admin/server/web_routs/permission/update": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "修改指令分组",
|
"summary": "修改后台菜单权限",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"operationId": "VncCommandGroupUpdate",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"VNC指令管理"
|
"后台菜单权限管理"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -144,143 +195,42 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "分组ID",
|
"description": "权限记录ID",
|
||||||
"example": 0
|
"example": 0
|
||||||
},
|
},
|
||||||
"name": {
|
"web_routs_id": {
|
||||||
"type": "string",
|
"type": "integer",
|
||||||
|
"description": "菜单ID",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "是否启用",
|
||||||
"example": ""
|
"example": ""
|
||||||
},
|
},
|
||||||
"sort": {
|
"admin_group_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"description": "管理员组ID",
|
||||||
"example": 0
|
"example": 0
|
||||||
},
|
},
|
||||||
"default_icon": {
|
"user_id": {
|
||||||
"description": "分组文本图标(如 📚),当无文件图标时使用",
|
|
||||||
"example": "",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"icon_file_id": {
|
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "分组图标文件ID",
|
"description": "用户ID",
|
||||||
"example": 0
|
"example": 0
|
||||||
|
},
|
||||||
|
"owner_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"user",
|
||||||
|
"group"
|
||||||
|
],
|
||||||
|
"description": "所属类型",
|
||||||
|
"example": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"examples": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "修改成功",
|
|
||||||
"headers": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/admin/server/vnc_command/group/delete": {
|
|
||||||
"delete": {
|
|
||||||
"summary": "删除指令分组(级联删除指令项)",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "",
|
|
||||||
"operationId": "VncCommandGroupDelete",
|
|
||||||
"tags": [
|
|
||||||
"VNC指令管理"
|
|
||||||
],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"in": "query",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer {{token}}",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer {{token}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "删除成功",
|
|
||||||
"headers": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/admin/server/vnc_command/item/create": {
|
|
||||||
"post": {
|
|
||||||
"summary": "创建指令项",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "",
|
|
||||||
"operationId": "VncCommandItemCreate",
|
|
||||||
"tags": [
|
|
||||||
"VNC指令管理"
|
|
||||||
],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer {{token}}",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer {{token}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"multipart/form-data": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"group_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "所属分组ID",
|
|
||||||
"example": 0
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "指令名称",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"cmd": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "指令内容(支持 %var% 变量占位符)",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"vars": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "变量列表 JSON,格式 [{\"k\":\"path\",\"p\":\"目录路径\"}]",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"sort": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "排序",
|
|
||||||
"example": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"group_id",
|
|
||||||
"label",
|
|
||||||
"cmd"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -288,21 +238,27 @@
|
|||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "创建成功",
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRoutsPermission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"headers": {}
|
"headers": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": []
|
"security": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/server/vnc_command/item/update": {
|
"/api/v1/admin/server/web_routs/permission/delete": {
|
||||||
"post": {
|
"delete": {
|
||||||
"summary": "修改指令项",
|
"summary": "删除后台菜单权限",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"operationId": "VncCommandItemUpdate",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"VNC指令管理"
|
"后台菜单权限管理"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -324,23 +280,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "指令项ID",
|
"description": "权限记录ID",
|
||||||
"example": 0
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"type": "string",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"cmd": {
|
|
||||||
"type": "string",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"vars": {
|
|
||||||
"type": "string",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"sort": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 0
|
"example": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -354,28 +294,114 @@
|
|||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "修改成功",
|
"description": "成功",
|
||||||
"headers": {}
|
"headers": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": []
|
"security": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/server/vnc_command/item/delete": {
|
"/api/v1/admin/server/web_routs/my": {
|
||||||
"delete": {
|
"get": {
|
||||||
"summary": "删除指令项",
|
"summary": "获取当前用户的后台菜单权限树",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "自动读取当前用户的 user_id 与 AdminGroupId,合并用户级与管理员组级菜单权限,返回完整的菜单树形结构。每个菜单节点包含 enable 状态,用户级权限优先于管理员组级权限。",
|
||||||
"operationId": "VncCommandItemDelete",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"VNC指令管理"
|
"后台菜单权限管理"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "Authorization",
|
||||||
"in": "query",
|
"in": "header",
|
||||||
"description": "",
|
"description": "",
|
||||||
"required": true,
|
"example": "Bearer {{token}}",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/WebRoutsWithPermission"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 1,
|
||||||
|
"path": "/dashboard",
|
||||||
|
"title": "仪表盘",
|
||||||
|
"icon": "dashboard",
|
||||||
|
"parentId": null,
|
||||||
|
"enable": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"path": "/dashboard/overview",
|
||||||
|
"title": "概览",
|
||||||
|
"icon": "overview",
|
||||||
|
"parentId": 1,
|
||||||
|
"enable": true,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headers": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/server/web_routs/list": {
|
||||||
|
"get": {
|
||||||
|
"summary": "获取后台菜单列表",
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "",
|
||||||
|
"tags": [
|
||||||
|
"后台菜单管理"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"description": "页码",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "count",
|
||||||
|
"in": "query",
|
||||||
|
"description": "每页数量",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"in": "query",
|
||||||
|
"description": "搜索关键字(匹配标题或路径)",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parent_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "父级菜单ID,不传则获取顶级菜单",
|
||||||
|
"required": false,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
@@ -393,7 +419,223 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "删除成功",
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRouts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"all_count": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headers": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/server/web_routs/add": {
|
||||||
|
"post": {
|
||||||
|
"summary": "新增后台菜单",
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "",
|
||||||
|
"tags": [
|
||||||
|
"后台菜单管理"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "",
|
||||||
|
"example": "Bearer {{token}}",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单路径",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单名称",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单图标",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"parent_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "父级菜单ID",
|
||||||
|
"example": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"title",
|
||||||
|
"icon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRouts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headers": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/server/web_routs/update": {
|
||||||
|
"post": {
|
||||||
|
"summary": "修改后台菜单",
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "",
|
||||||
|
"tags": [
|
||||||
|
"后台菜单管理"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "",
|
||||||
|
"example": "Bearer {{token}}",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "菜单ID",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单路径",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单名称",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单图标",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"parent_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "父级菜单ID",
|
||||||
|
"example": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRouts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headers": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/server/web_routs/delete": {
|
||||||
|
"delete": {
|
||||||
|
"summary": "删除后台菜单",
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "",
|
||||||
|
"tags": [
|
||||||
|
"后台菜单管理"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "",
|
||||||
|
"example": "Bearer {{token}}",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "菜单ID",
|
||||||
|
"example": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
"headers": {}
|
"headers": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -402,7 +644,122 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {},
|
"schemas": {
|
||||||
|
"AdminWebRouts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单路径"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单名称"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单图标"
|
||||||
|
},
|
||||||
|
"parentId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "父级菜单ID",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRouts"
|
||||||
|
},
|
||||||
|
"description": "子菜单列表"
|
||||||
|
},
|
||||||
|
"CreatedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"UpdatedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WebRoutsWithPermission": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "菜单ID"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单路径"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单名称"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "菜单图标"
|
||||||
|
},
|
||||||
|
"parentId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "父级菜单ID",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "是否启用(合并用户级与管理员组级权限后的结果,用户级优先)"
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/WebRoutsWithPermission"
|
||||||
|
},
|
||||||
|
"description": "子菜单列表"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AdminWebRoutsPermission": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"webRoutsId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "菜单ID"
|
||||||
|
},
|
||||||
|
"webRouts": {
|
||||||
|
"$ref": "#/components/schemas/AdminWebRouts"
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "是否启用"
|
||||||
|
},
|
||||||
|
"adminGroupId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "管理员组ID",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "用户ID",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"CreatedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"UpdatedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {},
|
"responses": {},
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user