优化更新

This commit is contained in:
2026-01-23 22:05:09 +08:00
parent 8283555457
commit 0608f0febb
14 changed files with 667 additions and 622 deletions

View File

@@ -16,6 +16,7 @@
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@ckeditor/ckeditor5-vue": "^7.3.0",
"@element-plus/icons-vue": "^2.3.2",
"ant-design-vue": "^4.2.6",
"axios": "^1.13.2",
"ckeditor5": "^47.4.0",

View File

@@ -2,171 +2,148 @@ import request from '@/utils/request'
export default {
login: {
url: `auth/login`,
name: '用户登录',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('auth/login', params)
},
},
logout: {
url: `auth/logout`,
name: '用户登出',
get: async function () {
return await request.get(this.url)
return await request.get('auth/logout')
},
},
user: {
url: `auth/user`,
name: '获取用户信息',
get: async function () {
return await request.get(this.url)
return await request.get('auth/user')
},
},
users: {
list: {
url: `auth/users/index`,
name: "获得用户列表",
get: async function (params) {
return await request.get(this.url, { params });
return await request.get('auth/users/index', { params });
},
},
add: {
url: `auth/users/add`,
name: "添加用户",
post: async function (params) {
return await request.post(this.url, params);
return await request.post('auth/users/add', params);
},
},
edit: {
url: `auth/users/edit`,
name: "编辑用户",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/users/edit', params);
},
},
uppasswd: {
url: `auth/users/passwd`,
name: "修改密码",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/users/passwd', params);
},
},
uprole: {
url: `auth/users/uprole`,
name: "设置角色",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/users/uprole', params);
},
},
delete: {
url: `auth/users/delete`,
name: "删除用户",
post: async function (params) {
return await request.delete(this.url, params);
return await request.delete('auth/users/delete', params);
},
},
},
role: {
list: {
url: `auth/role/index`,
name: "获得角色列表",
get: async function (params) {
return await request.get(this.url, { params });
return await request.get('auth/role/index', { params });
},
},
add: {
url: `auth/role/add`,
name: "添加角色",
post: async function (params) {
return await request.post(this.url, params);
return await request.post('auth/role/add', params);
},
},
edit: {
url: `auth/role/edit`,
name: "编辑角色",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/role/edit', params);
},
},
auth: {
url: `auth/role/auth`,
name: "角色授权",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/role/auth', params);
},
},
delete: {
url: `auth/role/delete`,
name: "删除角色",
post: async function (params) {
return await request.delete(this.url, params);
return await request.delete('auth/role/delete', params);
},
},
},
department: {
list: {
url: `auth/department/index`,
name: "获得部门列表",
get: async function (params) {
return await request.get(this.url, { params });
return await request.get('auth/department/index', { params });
},
},
add: {
url: `auth/department/add`,
name: "添加部门",
post: async function (params) {
return await request.post(this.url, params);
return await request.post('auth/department/add', params);
},
},
edit: {
url: `auth/department/edit`,
name: "编辑部门",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/department/edit', params);
},
},
delete: {
url: `auth/department/delete`,
name: "删除部门",
post: async function (params) {
return await request.delete(this.url, params);
return await request.delete('auth/department/delete', params);
},
},
},
menu: {
my: {
url: `auth/menu/my`,
name: '获取我的菜单',
get: async function () {
return await request.get(this.url)
return await request.get('auth/menu/my')
},
},
list: {
url: `auth/menu/index`,
name: "获取菜单",
get: async function (params) {
return await request.get(this.url, { params });
return await request.get('auth/menu/index', { params });
},
},
add: {
url: `auth/menu/add`,
name: "添加菜单",
post: async function (params) {
return await request.post(this.url, params);
return await request.post('auth/menu/add', params);
},
},
edit: {
url: `auth/menu/edit`,
name: "编辑菜单",
post: async function (params) {
return await request.put(this.url, params);
return await request.put('auth/menu/edit', params);
},
},
delete: {
url: `auth/menu/delete`,
name: "删除菜单",
post: async function (params) {
return await request.delete(this.url, params);
return await request.delete('auth/menu/delete', params);
},
},
},

View File

@@ -2,347 +2,301 @@ import request from '@/utils/request'
export default {
version: {
url: `system/index/version`,
name: '获取最新版本号',
get: async function () {
return await request.get(this.url)
return await request.get('system/index/version')
},
},
clearcache: {
url: `system/index/clearcache`,
name: '清除缓存',
post: async function () {
return await request.post(this.url)
return await request.post('system/index/clearcache')
},
},
info: {
url: `system/index/info`,
name: '系统信息',
get: function (params) {
return request.get(this.url, { params })
return request.get('system/index/info', { params })
},
},
setting: {
list: {
url: `system/setting/index`,
name: '获取配置信息',
get: function (params) {
return request.get(this.url, { params })
return request.get('system/setting/index', { params })
},
},
fields: {
url: `system/setting/fields`,
name: '获取配置字段',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/setting/fields', { params })
},
},
add: {
url: `system/setting/add`,
name: '保存配置信息',
post: function (data) {
return request.post(this.url, data)
return request.post('system/setting/add', data)
},
},
edit: {
url: `system/setting/edit`,
name: '编辑配置信息',
post: function (data) {
return request.put(this.url, data)
return request.put('system/setting/edit', data)
},
},
save: {
url: `system/setting/save`,
name: '保存配置信息',
post: function (data) {
return request.put(this.url, data)
return request.put('system/setting/save', data)
},
},
},
dictionary: {
category: {
url: `system/dict/category`,
name: '获取字典树',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/dict/category', { params })
},
},
editcate: {
url: `system/dict/editcate`,
name: '编辑字典树',
post: async function (data = {}) {
return await request.put(this.url, data)
return await request.put('system/dict/editcate', data)
},
},
addcate: {
url: `system/dict/addcate`,
name: '添加字典树',
post: async function (data = {}) {
return await request.post(this.url, data)
return await request.post('system/dict/addcate', data)
},
},
delCate: {
url: `system/dict/deletecate`,
name: '删除字典树',
post: async function (data = {}) {
return await request.delete(this.url, data)
return await request.delete('system/dict/deletecate', data)
},
},
list: {
url: `system/dict/lists`,
name: '字典明细',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/dict/lists', { params })
},
},
get: {
url: `system/dict/detail`,
name: '获取字典数据',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/dict/detail', { params })
},
},
edit: {
url: `system/dict/edit`,
name: '编辑字典明细',
post: async function (data = {}) {
return await request.put(this.url, data)
return await request.put('system/dict/edit', data)
},
},
add: {
url: `system/dict/add`,
name: '添加字典明细',
post: async function (data = {}) {
return await request.post(this.url, data)
return await request.post('system/dict/add', data)
},
},
delete: {
url: `system/dict/delete`,
name: '删除字典明细',
post: async function (data = {}) {
return await request.delete(this.url, data)
return await request.delete('system/dict/delete', data)
},
},
detail: {
url: `system/dict/detail`,
name: '字典明细',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/dict/detail', { params })
},
},
alldic: {
url: `system/dict/all`,
name: '全部字典',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/dict/all', { params })
},
},
},
area: {
list: {
url: `system/area/index`,
name: '地区列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/area/index', { params })
},
},
add: {
url: `system/area/add`,
name: '地区添加',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('system/area/add', params)
},
},
edit: {
url: `system/area/edit`,
name: '地区编辑',
post: async function (params) {
return await request.put(this.url, params)
return await request.put('system/area/edit', params)
},
},
},
app: {
list: {
url: `system/app/list`,
name: '应用列表',
get: async function () {
return await request.get(this.url)
return await request.get('system/app/list')
},
},
},
client: {
list: {
url: `system/client/index`,
name: '客户端列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/client/index', { params })
},
},
add: {
url: `system/client/add`,
name: '客户端添加',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('system/client/add', params)
},
},
edit: {
url: `system/client/edit`,
name: '客户端编辑',
post: async function (params) {
return await request.put(this.url, params)
return await request.put('system/client/edit', params)
},
},
delete: {
url: `system/client/delete`,
name: '客户端删除',
post: async function (params) {
return await request.delete(this.url, params)
return await request.delete('system/client/delete', params)
},
},
menu: {
list: {
url: `system/menu/index`,
name: '客户端菜单列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/menu/index', { params })
},
},
add: {
url: `system/menu/add`,
name: '客户端菜单添加',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('system/menu/add', params)
},
},
edit: {
url: `system/menu/edit`,
name: '客户端菜单编辑',
post: async function (params) {
return await request.put(this.url, params)
return await request.put('system/menu/edit', params)
},
},
delete: {
url: `system/menu/delete`,
name: '客户端菜单删除',
post: async function (params) {
return await request.delete(this.url, params)
return await request.delete('system/menu/delete', params)
},
},
},
},
log: {
list: {
url: `system/log/index`,
name: '日志列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/log/index', { params })
},
},
my: {
url: `system/log/my`,
name: '我的日志',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/log/my', { params })
},
},
delete: {
url: `system/log/delete`,
name: '日志删除',
post: async function (params) {
return await request.delete(this.url, params)
return await request.delete('system/log/delete', params)
},
},
},
tasks: {
list: {
url: `system/tasks/index`,
name: '任务列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/tasks/index', { params })
},
},
delete: {
url: `system/tasks/delete`,
name: '任务删除',
post: async function (params) {
return await request.delete(this.url, params)
return await request.delete('system/tasks/delete', params)
},
},
},
crontab: {
list: {
url: `system/crontab/index`,
name: '定时任务列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/crontab/index', { params })
},
},
add: {
url: `system/crontab/add`,
name: '定时任务添加',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('system/crontab/add', params)
},
},
edit: {
url: `system/crontab/edit`,
name: '定时任务编辑',
post: async function (params) {
return await request.put(this.url, params)
return await request.put('system/crontab/edit', params)
},
},
delete: {
url: `system/crontab/delete`,
name: '定时任务删除',
post: async function (params) {
return await request.delete(this.url, params)
return await request.delete('system/crontab/delete', params)
},
},
log: {
url: `system/crontab/log`,
name: '定时任务日志',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/crontab/log', { params })
},
},
reload: {
url: `system/crontab/reload`,
name: '定时任务重载',
post: async function (params) {
return await request.put(this.url, params)
return await request.put('system/crontab/reload', params)
},
},
},
modules: {
list: {
url: `system/modules/index`,
name: '模块列表',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/modules/index', { params })
},
},
update: {
url: `system/modules/update`,
name: '更新模块',
post: async function (params) {
return await request.post(this.url, params)
return await request.post('system/modules/update', params)
},
},
},
sms: {
count: {
url: `system/sms/count`,
name: '短信发送统计',
get: async function (params) {
return await request.get(this.url, { params })
return await request.get('system/sms/count', { params })
},
},
},
upload: {
url: `system/file/upload`,
name: '文件上传',
post: async function (params = {}) {
return await request.post(this.url, params)
return await request.post('system/file/upload', params)
},
},
}

View File

@@ -1,4 +1,5 @@
import * as AIcons from '@ant-design/icons-vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
export default {
install(app) {
@@ -6,5 +7,9 @@ export default {
for (let icon in AIcons) {
app.component(`${icon}`, AIcons[icon])
}
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(`El${key}`, component)
}
}
}

239
src/hooks/useTable.js Normal file
View File

@@ -0,0 +1,239 @@
import { ref, reactive, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
/**
* 表格通用hooks
* @param {Object} options 配置选项
* @param {Function} options.api 获取列表数据的API函数必须返回包含data和total的响应
* @param {Object} options.searchForm 搜索表单的初始值
* @param {Array} options.columns 表格列配置
* @param {String} options.rowKey 行的唯一标识,默认为'id'
* @param {Boolean} options.needPagination 是否需要分页默认为true
* @param {Object} options.paginationConfig 分页配置,可选
* @param {Boolean} options.needSelection 是否需要行选择默认为false
* @param {Boolean} options.immediateLoad 是否在组件挂载时自动加载数据默认为true
* @returns {Object} 返回表格相关的状态和方法
*/
export function useTable(options = {}) {
const {
api,
searchForm: initialSearchForm = {},
columns = [],
rowKey = 'id',
needPagination = true,
paginationConfig = {},
needSelection = false,
immediateLoad = true
} = options
// 表格引用
const tableRef = ref(null)
// 搜索表单
const searchForm = reactive({ ...initialSearchForm })
// 表格数据
const tableData = ref([])
// 加载状态
const loading = ref(false)
// 选中的行数据
const selectedRows = ref([])
// 选中的行keys
const selectedRowKeys = computed(() => selectedRows.value.map(item => item[rowKey]))
// 分页配置
const defaultPaginationConfig = {
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['20', '50', '100', '200']
}
const pagination = reactive({
...defaultPaginationConfig,
...paginationConfig
})
// 行选择配置
const rowSelection = computed(() => {
if (!needSelection) return null
return {
selectedRowKeys: selectedRowKeys.value,
onChange: (keys, rows) => {
selectedRows.value = rows
}
}
})
// 行选择事件处理用于scTable的@select事件
const handleSelectChange = (record, selected, selectedRows) => {
if (!needSelection) return
if (selected) {
selectedRows.value.push(record)
} else {
const index = selectedRows.value.findIndex(item => item[rowKey] === record[rowKey])
if (index > -1) {
selectedRows.value.splice(index, 1)
}
}
}
// 全选/取消全选处理用于scTable的@selectAll事件
const handleSelectAll = (selected, selectedRows, changeRows) => {
if (!needSelection) return
if (selected) {
changeRows.forEach(record => {
if (!selectedRows.value.find(item => item[rowKey] === record[rowKey])) {
selectedRows.value.push(record)
}
})
} else {
changeRows.forEach(record => {
const index = selectedRows.value.findIndex(item => item[rowKey] === record[rowKey])
if (index > -1) {
selectedRows.value.splice(index, 1)
}
})
}
}
// 加载数据
const loadData = async (params = {}) => {
if (!api) {
console.warn('useTable: 未提供api函数无法加载数据')
return
}
loading.value = true
try {
const requestParams = {
...searchForm,
...params
}
// 如果需要分页,添加分页参数
if (needPagination) {
requestParams.page = pagination.current
requestParams.limit = pagination.pageSize
}
// 调用API函数确保this上下文正确
const res = await api(requestParams)
if (res.code === 1) {
// 如果是分页数据
if (needPagination) {
tableData.value = res.data?.data || []
pagination.total = res.data?.total || 0
} else {
// 非分页数据(如树形数据)
tableData.value = res.data || []
}
} else {
message.error(res.message || '加载数据失败')
}
} catch (error) {
console.error('加载数据失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// 分页变化处理
const handlePaginationChange = ({ page, pageSize }) => {
if (!needPagination) return
pagination.current = page
pagination.pageSize = pageSize
loadData()
}
// 搜索
const handleSearch = () => {
if (needPagination) {
pagination.current = 1
}
loadData()
}
// 重置
const handleReset = () => {
// 重置搜索表单为初始值
Object.keys(searchForm).forEach(key => {
searchForm[key] = initialSearchForm[key]
})
// 清空选择
selectedRows.value = []
// 重置分页
if (needPagination) {
pagination.current = 1
}
// 重新加载数据
loadData()
}
// 刷新表格
const refreshTable = () => {
loadData()
}
// 清空选择
const clearSelection = () => {
selectedRows.value = []
}
// 设置选中行
const setSelectedRows = (rows) => {
selectedRows.value = rows
}
// 更新搜索表单
const setSearchForm = (data) => {
Object.assign(searchForm, data)
}
// 直接设置表格数据(用于特殊场景)
const setTableData = (data) => {
tableData.value = data
}
// 组件挂载时自动加载数据
if (immediateLoad) {
onMounted(() => {
loadData()
})
}
return {
// ref
tableRef,
// 响应式数据
searchForm,
tableData,
loading,
pagination,
selectedRows,
selectedRowKeys,
// 配置
columns,
rowKey,
rowSelection,
// 方法
loadData,
handleSearch,
handleReset,
handlePaginationChange,
handleSelectChange,
handleSelectAll,
refreshTable,
clearSelection,
setSelectedRows,
setSearchForm,
setTableData
}
}

View File

@@ -1,36 +1,23 @@
<template>
<div v-show="showTags" class="tags-view">
<a-dropdown :trigger="['contextmenu']">
<div class="tags-wrapper">
<a-space :size="4">
<a-tag v-for="tag in visitedViews" :key="tag.fullPath" :closable="!tag.meta?.affix" class="tag-item"
:class="{ active: isActive(tag) }" @click="clickTag(tag)" @close="closeSelectedTag(tag)"
@contextmenu.prevent="handleContextMenu($event, tag)">
{{ tag.meta?.title || tag.name }}
</a-tag>
</a-space>
</div>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="refresh">
<ReloadOutlined />
<span>刷新</span>
</a-menu-item>
<a-menu-item v-if="!selectedTag.meta?.affix" key="close">
<CloseOutlined />
<span>关闭</span>
</a-menu-item>
<a-menu-item key="closeOthers">
<ColumnWidthOutlined />
<span>关闭其他</span>
</a-menu-item>
<a-menu-item key="closeAll">
<CloseCircleOutlined />
<span>关闭所有</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<div class="tags-wrapper" @contextmenu.prevent>
<a-space :size="4">
<a-tag
v-for="tag in visitedViews"
:key="tag.fullPath"
:closable="!tag.meta?.affix"
class="tag-item"
:class="{ active: isActive(tag), 'tag-affix': tag.meta?.affix }"
@click="clickTag(tag)"
@close="closeSelectedTag(tag)"
@contextmenu.prevent="handleContextMenu($event, tag)">
<template #icon v-if="tag.meta?.affix">
<PushpinFilled />
</template>
{{ tag.meta?.title || tag.name }}
</a-tag>
</a-space>
</div>
<div class="tags-actions">
<a-tooltip title="刷新当前页">
@@ -49,17 +36,55 @@
</a-button>
</a-tooltip>
</div>
<!-- 右键菜单 -->
<teleport to="body">
<div
v-if="contextMenu.visible"
:style="{
position: 'fixed',
left: contextMenu.x + 'px',
top: contextMenu.y + 'px',
zIndex: 9999
}"
class="context-menu"
@click="closeContextMenu">
<a-menu @click="handleMenuClick">
<a-menu-item key="refresh">
<ReloadOutlined />
<span>刷新</span>
</a-menu-item>
<a-menu-item v-if="selectedTag && !selectedTag.meta?.affix" key="close">
<CloseOutlined />
<span>关闭</span>
</a-menu-item>
<a-menu-item key="closeOthers">
<ColumnWidthOutlined />
<span>关闭其他</span>
</a-menu-item>
<a-menu-item key="closeAll">
<CloseCircleOutlined />
<span>关闭所有</span>
</a-menu-item>
</a-menu>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useLayoutStore } from '@/stores/modules/layout'
import { ReloadOutlined, CloseOutlined, ColumnWidthOutlined, CloseCircleOutlined } from '@ant-design/icons-vue'
import {
ReloadOutlined,
CloseOutlined,
ColumnWidthOutlined,
CloseCircleOutlined,
PushpinFilled
} from '@ant-design/icons-vue'
import config from '@/config'
// 定义组件名称(多词命名)
defineOptions({
name: 'TagsView',
})
@@ -69,8 +94,15 @@ const router = useRouter()
const layoutStore = useLayoutStore()
const showTags = ref(true)
const selectedTag = ref({})
const selectedTag = ref(null)
const visitedViews = computed(() => layoutStore.viewTags)
console.log(visitedViews)
// 右键菜单状态
const contextMenu = ref({
visible: false,
x: 0,
y: 0
})
// 判断是否是当前激活的标签
const isActive = (tag) => {
@@ -87,7 +119,7 @@ const addTags = () => {
name: name,
query: route.query,
params: route.params,
meta: route.meta,
meta: route.meta
})
}
}
@@ -120,7 +152,9 @@ const closeOthersTags = () => {
}
// 保留固定标签和当前选中的标签
const tagsToKeep = visitedViews.value.filter((tag) => tag.meta?.affix || tag.fullPath === selectedTag.value.fullPath)
const tagsToKeep = visitedViews.value.filter(
(tag) => tag.meta?.affix || tag.fullPath === selectedTag.value.fullPath
)
// 更新标签列表
layoutStore.viewTags = tagsToKeep
@@ -142,7 +176,7 @@ const closeAllTags = () => {
router.push(affixTags[0].fullPath)
} else {
// 如果没有固定标签,跳转到首页
router.push('/home')
router.push(config.DASHBOARD_URL)
}
}
@@ -153,23 +187,52 @@ const clickTag = (tag) => {
}
}
// 刷新当前标签
// 刷新指定标签
const refreshTag = (tag) => {
// 如果刷新的是当前激活的标签
if (isActive(tag)) {
// 调用 store 的刷新方法,触发组件重新渲染
layoutStore.refreshTag()
} else {
// 如果刷新的是其他标签,先跳转到该标签
router.push(tag.fullPath)
}
}
// 刷新当前选中的标签(用于顶部操作按钮)
const refreshSelectedTag = () => {
// 使用 router.go(0) 刷新页面
router.go(0)
// 找到当前激活的标签
const currentTag = visitedViews.value.find((tag) => isActive(tag))
if (currentTag) {
refreshTag(currentTag)
}
}
// 右键菜单处理
const handleContextMenu = (event, tag) => {
event.preventDefault()
event.stopPropagation()
selectedTag.value = tag
contextMenu.value = {
visible: true,
x: event.clientX,
y: event.clientY
}
}
// 关闭右键菜单
const closeContextMenu = () => {
contextMenu.value.visible = false
}
// 菜单点击处理
const handleMenuClick = ({ key }) => {
switch (key) {
case 'refresh':
refreshSelectedTag()
if (selectedTag.value) {
refreshTag(selectedTag.value)
}
break
case 'close':
if (selectedTag.value && !selectedTag.value.meta?.affix) {
@@ -183,6 +246,17 @@ const handleMenuClick = ({ key }) => {
closeAllTags()
break
}
closeContextMenu()
}
// 点击其他地方关闭右键菜单
const handleClickOutside = (event) => {
if (contextMenu.value.visible) {
const menuElement = document.querySelector('.context-menu')
if (menuElement && !menuElement.contains(event.target)) {
closeContextMenu()
}
}
}
// 监听路由变化,自动添加标签
@@ -191,15 +265,22 @@ watch(
() => {
addTags()
// 更新当前选中的标签
selectedTag.value = visitedViews.value.find((tag) => isActive(tag)) || {}
selectedTag.value = visitedViews.value.find((tag) => isActive(tag)) || null
},
{ immediate: true },
{ immediate: true }
)
onMounted(() => {
addTags()
// 初始化选中的标签
selectedTag.value = visitedViews.value.find((tag) => isActive(tag)) || {}
selectedTag.value = visitedViews.value.find((tag) => isActive(tag)) || null
// 添加点击事件监听器
document.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
// 移除点击事件监听器
document.removeEventListener('click', handleClickOutside)
})
</script>
@@ -236,6 +317,7 @@ onMounted(() => {
.tag-item {
display: inline-flex;
align-items: center;
gap: 4px;
height: 28px;
line-height: 28px;
padding: 0 12px;
@@ -245,6 +327,7 @@ onMounted(() => {
border-radius: 2px;
cursor: pointer;
transition: all 0.3s;
user-select: none;
&:hover {
color: #1890ff;
@@ -262,6 +345,28 @@ onMounted(() => {
}
}
&.tag-affix {
background-color: #fff7e6;
border-color: #ffd591;
color: #fa8c16;
&.active {
background-color: #fa8c16;
border-color: #fa8c16;
color: #ffffff;
}
&:hover {
background-color: #ffe7ba;
border-color: #ffa940;
}
&.active:hover {
background-color: #d46b08;
border-color: #d46b08;
}
}
:deep(.ant-tag-close-icon) {
color: inherit;
@@ -289,4 +394,32 @@ onMounted(() => {
}
}
}
.context-menu {
background: #ffffff;
border-radius: 2px;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
padding: 4px 0;
min-width: 160px;
:deep(.ant-menu) {
background: transparent;
border: none;
box-shadow: none;
padding: 0;
.ant-menu-item {
margin: 0;
padding: 8px 12px;
height: auto;
line-height: normal;
&:hover {
background-color: #f5f5f5;
}
}
}
}
</style>

View File

@@ -43,7 +43,7 @@
<a-layout-content class="app-main">
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
<component :is="Component" :key="refreshKey" />
</keep-alive>
</router-view>
</a-layout-content>
@@ -100,7 +100,7 @@
<a-layout-content class="app-main top-content">
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
<component :is="Component" :key="refreshKey" />
</keep-alive>
</router-view>
</a-layout-content>
@@ -163,6 +163,9 @@ const layoutClass = computed(() => {
}
})
// 获取刷新 key
const refreshKey = computed(() => layoutStore.refreshKey)
const openKeys = ref([])
const selectedKeys = ref([])
const menuList = computed(() => {

View File

@@ -22,14 +22,14 @@
<a-button type="primary" @click="handleAdd">
<template #icon><plus-outlined /></template>
</a-button>
<a-button danger :disabled="selection.length === 0" @click="handleBatchDelete">
<a-button danger :disabled="selectedRows.length === 0" @click="handleBatchDelete">
<template #icon><delete-outlined /></template>
</a-button>
</div>
</div>
<div class="table-content">
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading" :pagination="false"
:row-key="rowKey" :row-selection="rowSelection" @refresh="loadData" @select="handleSelectChange"
:row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable" @select="handleSelectChange"
@selectAll="handleSelectAll">
<template #action="{ record }">
<a-space>
@@ -49,18 +49,40 @@
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ref, reactive, onMounted } 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'
import { useTable } from '@/hooks/useTable'
defineOptions({
name: 'authDepartment'
})
// 表格引用
const tableRef = ref(null)
// 使用useTable hooks
const {
tableRef,
searchForm,
tableData,
loading,
selectedRows,
rowSelection,
handleSearch,
handleReset,
handleSelectChange,
handleSelectAll,
refreshTable
} = useTable({
api: authApi.department.list.get,
searchForm: {
keyword: ''
},
columns: [],
needPagination: false,
needSelection: true,
immediateLoad: false
})
// 对话框状态
const dialog = reactive({
@@ -70,29 +92,9 @@ const dialog = reactive({
// 弹窗引用
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' },
@@ -102,69 +104,6 @@ const columns = [
{ 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,
...searchForm
}
const res = await authApi.department.list.get(params)
if (res.code === 1) {
tableData.value = res.data || []
} else {
message.error(res.message || '加载数据失败')
}
} catch (error) {
console.error('加载部门列表失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// 选择变化处理
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 = () => {
loadData()
}
// 重置
const handleReset = () => {
searchForm.keyword = ''
selection.value = []
loadData()
}
// 新增部门
const handleAdd = () => {
@@ -196,7 +135,7 @@ const handleDelete = async (record) => {
const res = await authApi.department.delete.post({ id: record.id })
if (res.code === 1) {
message.success('删除成功')
loadData()
refreshTable()
} else {
message.error(res.message || '删除失败')
}
@@ -208,25 +147,25 @@ const handleDelete = async (record) => {
// 批量删除
const handleBatchDelete = () => {
if (selection.value.length === 0) {
if (selectedRows.value.length === 0) {
message.warning('请选择要删除的部门')
return
}
Modal.confirm({
title: '确认删除',
content: `确定删除选中的 ${selection.value.length} 个部门吗?如果删除项中含有子集将会被一并删除`,
content: `确定删除选中的 ${selectedRows.value.length} 个部门吗?如果删除项中含有子集将会被一并删除`,
okText: '确定',
cancelText: '取消',
okType: 'danger',
onOk: async () => {
try {
const ids = selection.value.map(item => item.id)
const ids = selectedRows.value.map(item => item.id)
const res = await authApi.department.delete.post({ ids })
if (res.code === 1) {
message.success('删除成功')
selection.value = []
loadData()
selectedRows.value = []
refreshTable()
} else {
message.error(res.message || '删除失败')
}
@@ -238,32 +177,14 @@ const handleBatchDelete = () => {
})
}
// 根据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()
const handleSaveSuccess = () => {
refreshTable()
}
// 初始化
onMounted(() => {
loadData()
refreshTable()
})
</script>

View File

@@ -23,17 +23,17 @@
<a-button type="primary" @click="handleAdd">
<template #icon><plus-outlined /></template>
</a-button>
<a-button danger :disabled="selection.length === 0" @click="handleBatchDelete">
<a-button danger :disabled="selectedRows.length === 0" @click="handleBatchDelete">
<template #icon><delete-outlined /></template>
</a-button>
<a-button :disabled="selection.length !== 1" @click="handlePermission">
<a-button :disabled="selectedRows.length !== 1" @click="handlePermission">
<template #icon><key-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" :row-selection="rowSelection" @refresh="loadData"
:pagination="pagination" :row-key="rowKey" :row-selection="rowSelection" @refresh="refreshTable"
@paginationChange="handlePaginationChange" @select="handleSelectChange" @selectAll="handleSelectAll">
<template #status="{ record }">
<a-tag :color="record.status === 1 ? 'success' : 'error'">
@@ -62,19 +62,42 @@
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ref, reactive } from 'vue'
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'
import { useTable } from '@/hooks/useTable'
defineOptions({
name: 'authRole'
})
// 表格引用
const tableRef = ref(null)
// 使用useTable hooks
const {
tableRef,
searchForm,
tableData,
loading,
pagination,
selectedRows,
rowSelection,
handleSearch,
handleReset,
handlePaginationChange,
handleSelectChange,
handleSelectAll,
refreshTable
} = useTable({
api: authApi.role.list.get,
searchForm: {
keyword: ''
},
columns: [],
needPagination: true,
needSelection: true
})
// 对话框状态
const dialog = reactive({
@@ -86,39 +109,9 @@ const dialog = reactive({
const saveDialogRef = ref(null)
const permissionDialogRef = ref(null)
// 选中的行
const selection = ref([])
// 搜索表单
const searchForm = reactive({
keyword: ''
})
// 表格数据
const tableData = ref([])
const loading = ref(false)
// 分页配置
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
}
}))
// 表格列配置
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80, align: 'center' },
@@ -129,80 +122,6 @@ const columns = [
{ 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 || '加载数据失败')
}
} catch (error) {
console.error('加载角色列表失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// 选择变化处理
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 = () => {
@@ -234,7 +153,7 @@ const handleDelete = async (record) => {
const res = await authApi.role.delete.post({ id: record.id })
if (res.code === 1) {
message.success('删除成功')
loadData()
refreshTable()
} else {
message.error(res.message || '删除失败')
}
@@ -246,25 +165,25 @@ const handleDelete = async (record) => {
// 批量删除
const handleBatchDelete = () => {
if (selection.value.length === 0) {
if (selectedRows.value.length === 0) {
message.warning('请选择要删除的角色')
return
}
Modal.confirm({
title: '确认删除',
content: `确定删除选中的 ${selection.value.length} 个角色吗?`,
content: `确定删除选中的 ${selectedRows.value.length} 个角色吗?`,
okText: '确定',
cancelText: '取消',
okType: 'danger',
onOk: async () => {
try {
const ids = selection.value.map(item => item.id)
const ids = selectedRows.value.map(item => item.id)
const res = await authApi.role.delete.post({ ids })
if (res.code === 1) {
message.success('删除成功')
selection.value = []
loadData()
selectedRows.value = []
refreshTable()
} else {
message.error(res.message || '删除失败')
}
@@ -278,34 +197,25 @@ const handleBatchDelete = () => {
// 权限设置
const handlePermission = () => {
if (selection.value.length !== 1) {
if (selectedRows.value.length !== 1) {
message.error('请选择一个角色进行权限设置')
return
}
dialog.permission = true
setTimeout(() => {
permissionDialogRef.value?.open().setData(selection.value[0])
permissionDialogRef.value?.open().setData(selectedRows.value[0])
}, 0)
}
// 保存成功回调
const handleSaveSuccess = (data, mode) => {
if (mode === 'add') {
loadData()
} else if (mode === 'edit') {
loadData()
}
const handleSaveSuccess = () => {
refreshTable()
}
// 权限设置成功回调
const permissionSuccess = () => {
loadData()
refreshTable()
}
// 初始化
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">

View File

@@ -35,9 +35,9 @@
<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-button @click="handleUserReset">
<template #icon><redo-outlined /></template>
</a-button>
</a-space>
</a-form-item>
</a-form>
@@ -52,9 +52,9 @@
</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">
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
:pagination="pagination" :row-key="rowKey" @refresh="refreshTable"
@paginationChange="handlePaginationChange">
<template #avatar="{ record }">
<a-avatar :src="record.avatar" :size="32">
<template #icon><user-outlined /></template>
@@ -102,13 +102,33 @@ import scTable from '@/components/scTable/index.vue'
import saveDialog from './save.vue'
import roleDialog from './role.vue'
import authApi from '@/api/auth'
import { useTable } from '@/hooks/useTable'
defineOptions({
name: 'authUser'
})
// 表格引用
const tableRef = ref(null)
// 使用useTable hooks
const {
tableRef,
searchForm,
tableData,
loading,
pagination,
handleSearch,
handleReset,
handlePaginationChange,
refreshTable
} = useTable({
api: authApi.users.list.get,
searchForm: {
username: '',
nickname: '',
department_id: null
},
columns: [],
needPagination: true
})
// 对话框状态
const dialog = reactive({
@@ -126,37 +146,9 @@ const filteredDepartmentTree = ref([])
const selectedDeptKeys = ref([])
const departmentKeyword = ref('')
// 搜索表单
const searchForm = reactive({
username: '',
nickname: '',
department_id: null
})
// 表格数据
const tableData = ref([])
const loading = ref(false)
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['20', '50', '100', '200']
})
// 行key
const rowKey = 'id'
// 分页变化处理
const handlePaginationChange = ({ page, pageSize }) => {
pagination.current = page
pagination.pageSize = pageSize
loadData()
}
// 表格列配置
const columns = [
{ title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' },
@@ -210,34 +202,15 @@ const handleDeptSearch = (e) => {
filteredDepartmentTree.value = filterTree(departmentTree.value)
}
// 加载用户列表数据
const loadData = async () => {
loading.value = true
try {
const params = {
page: pagination.current,
limit: pagination.pageSize,
...searchForm
}
const res = await authApi.users.list.get(params)
if (res.code === 1) {
tableData.value = res.data?.data || []
pagination.total = res.data?.total || 0
} else {
message.error(res.message || '加载数据失败')
}
} catch (error) {
console.error('加载用户列表失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// 表格变化处理(排序、筛选)
const handleTableChange = (pagination, filters, sorter) => {
// 如果需要处理排序或筛选,可以在这里添加逻辑
console.log('表格变化:', { pagination, filters, sorter })
// 重置 - 覆盖useTable的handleReset以添加额外逻辑
const handleUserReset = () => {
searchForm.username = ''
searchForm.nickname = ''
searchForm.department_id = null
selectedDeptKeys.value = []
departmentKeyword.value = ''
filteredDepartmentTree.value = departmentTree.value
handleReset()
}
// 部门选择事件
@@ -247,31 +220,7 @@ const onDeptSelect = (selectedKeys) => {
} else {
searchForm.department_id = null
}
pagination.current = 1
loadData()
}
// 搜索
const handleSearch = () => {
pagination.current = 1
loadData()
}
// 重置
const handleReset = () => {
searchForm.username = ''
searchForm.nickname = ''
searchForm.department_id = null
selectedDeptKeys.value = []
departmentKeyword.value = ''
filteredDepartmentTree.value = departmentTree.value
pagination.current = 1
loadData()
}
// 刷新表格
const refreshTable = () => {
loadData()
handleSearch()
}
// 导出数据
@@ -320,7 +269,7 @@ const handleDelete = async (record) => {
const res = await authApi.users.delete.post({ id: record.id })
if (res.code === 1) {
message.success('删除成功')
loadData()
refreshTable()
} else {
message.error(res.message || '删除失败')
}
@@ -331,23 +280,18 @@ const handleDelete = async (record) => {
}
// 保存成功回调
const handleSaveSuccess = (data, mode) => {
if (mode === 'add') {
loadData()
} else if (mode === 'edit') {
loadData()
}
const handleSaveSuccess = () => {
refreshTable()
}
// 角色设置成功回调
const handleRoleSuccess = () => {
loadData()
refreshTable()
}
// 初始化
onMounted(() => {
loadDepartmentTree()
loadData()
})
</script>

View File

@@ -28,7 +28,7 @@
<a-button type="primary" @click="handleSearch">
<template #icon><search-outlined /></template>
</a-button>
<a-button @click="handleReset">
<a-button @click="handleLogReset">
<template #icon><redo-outlined /></template>
</a-button>
</a-space>
@@ -44,7 +44,7 @@
</div>
<div class="table-content">
<scTable ref="tableRef" :columns="columns" :data-source="tableData" :loading="loading"
:pagination="pagination" :row-key="rowKey" @refresh="loadData" @paginationChange="handlePaginationChange"
:pagination="pagination" :row-key="rowKey" @refresh="refreshTable" @paginationChange="handlePaginationChange"
:row-selection="rowSelection">
<template #method="{ record }">
<a-tag :color="getMethodColor(record.method)">{{ record.method }}</a-tag>
@@ -67,19 +67,44 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import { SearchOutlined, RedoOutlined, DeleteOutlined, FolderOutlined, FileOutlined } from '@ant-design/icons-vue'
import scTable from '@/components/scTable/index.vue'
import logInfoDialog from './info.vue'
import systemApi from '@/api/system'
import { useTable } from '@/hooks/useTable'
defineOptions({
name: 'systemLog'
})
// 表格引用
const tableRef = ref(null)
// 使用useTable hooks
const {
tableRef,
searchForm,
tableData,
loading,
pagination,
selectedRowKeys,
rowSelection,
handleSearch,
handleReset,
handlePaginationChange,
refreshTable
} = useTable({
api: systemApi.log.list.get,
searchForm: {
title: '',
method: null,
status: null,
created_at: []
},
columns: [],
needPagination: true,
needSelection: true,
immediateLoad: false
})
// 对话框状态
const dialog = reactive({
@@ -89,6 +114,7 @@ const dialog = reactive({
// 弹窗引用
const infoDialogRef = ref(null)
// 树数据
const treeData = ref([
{
@@ -116,39 +142,6 @@ const treeData = ref([
// 选中的树节点
const selectedKeys = ref([])
// 选中的行
const selectedRowKeys = ref([])
// 行选择配置
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (selectedKeys) => {
selectedRowKeys.value = selectedKeys
},
}))
// 搜索表单
const searchForm = reactive({
title: '',
method: null,
status: null,
created_at: []
})
// 表格数据
const tableData = ref([])
const loading = ref(false)
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['20', '50', '100', '200']
})
// 行key
const rowKey = 'id'
@@ -175,40 +168,6 @@ const columns = [
{ title: '操作', dataIndex: 'action', key: 'action', width: 150, align: 'center', slot: 'action', fixed: 'right' }
]
// 加载日志列表数据
const loadData = async () => {
loading.value = true
try {
const params = {
page: pagination.current,
limit: pagination.pageSize,
title: searchForm.title,
method: searchForm.method,
status: searchForm.status,
created_at: searchForm.created_at
}
const res = await systemApi.log.list.get(params)
if (res.code === 1) {
tableData.value = res.data?.data || []
pagination.total = res.data?.total || 0
} else {
message.error(res.message || '加载数据失败')
}
} catch (error) {
console.error('加载日志列表失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// 分页变化处理
const handlePaginationChange = ({ page, pageSize }) => {
pagination.current = page
pagination.pageSize = pageSize
loadData()
}
// 树节点选择事件
const onSelect = (selectedKeys, info) => {
@@ -230,27 +189,10 @@ const onSelect = (selectedKeys, info) => {
searchForm.status = null
}
pagination.current = 1
loadData()
refreshTable()
}
}
// 搜索
const handleSearch = () => {
pagination.current = 1
loadData()
}
// 重置
const handleReset = () => {
searchForm.title = ''
searchForm.method = null
searchForm.status = null
searchForm.timeRange = []
selectedKeys.value = []
pagination.current = 1
loadData()
}
// 查看日志详情
const handleView = (record) => {
dialog.info = true
@@ -265,7 +207,7 @@ const handleDelete = async (record) => {
const res = await systemApi.log.delete.post({ id: record.id })
if (res.code === 1) {
message.success('删除成功')
loadData()
refreshTable()
selectedRowKeys.value = []
} else {
message.error(res.message || '删除失败')
@@ -295,7 +237,7 @@ const handleClearLog = () => {
await Promise.all(promises)
message.success('清空成功')
selectedRowKeys.value = []
loadData()
refreshTable()
} catch (error) {
console.error('清空日志失败:', error)
message.error('清空失败')
@@ -306,7 +248,7 @@ const handleClearLog = () => {
// 初始化
onMounted(() => {
loadData()
refreshTable()
})
</script>

View File

@@ -73,6 +73,7 @@ function transformMenusToRoutes(menus) {
icon: menu.meta?.icon || menu.icon,
hidden: menu.hidden || menu.meta?.hidden,
keepAlive: menu.meta?.keepAlive || false,
affix: menu.meta?.affix || 0,
role: menu.meta?.role || []
}
}

View File

@@ -26,6 +26,9 @@ export const useLayoutStore = defineStore(
// 视图标签页(用于记录页面滚动位置)
const viewTags = ref([])
// 刷新标签的 key用于触发组件刷新
const refreshKey = ref(0)
// 切换侧边栏折叠
const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value
@@ -81,6 +84,11 @@ export const useLayoutStore = defineStore(
showBreadcrumb.value = show
}
// 刷新标签
const refreshTag = () => {
refreshKey.value++
}
// 重置主题设置
const resetTheme = () => {
themeColor.value = '#1890ff'
@@ -98,6 +106,7 @@ export const useLayoutStore = defineStore(
themeColor,
showTags,
showBreadcrumb,
refreshKey,
toggleSidebar,
setLayoutMode,
setSelectedParentMenu,
@@ -108,13 +117,14 @@ export const useLayoutStore = defineStore(
setShowTags,
setShowBreadcrumb,
resetTheme,
refreshTag,
}
},
{
persist: {
key: 'layout-store',
storage: customStorage,
pick: ['layoutMode', 'sidebarCollapsed', 'themeColor', 'showTags', 'showBreadcrumb'],
pick: ['layoutMode', 'sidebarCollapsed', 'themeColor', 'showTags', 'showBreadcrumb', 'viewTags'],
},
},
)

View File

@@ -1048,6 +1048,11 @@
resolved "https://mirrors.huaweicloud.com/repository/npm/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
"@element-plus/icons-vue@^2.3.2":
version "2.3.2"
resolved "https://mirrors.huaweicloud.com/repository/npm/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz#7e9cb231fb738b2056f33e22c3a29e214b538dcf"
integrity sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==
"@emotion/hash@^0.9.0":
version "0.9.2"
resolved "https://mirrors.huaweicloud.com/repository/npm/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b"