核心架构:添加服务层、路由和状态管理
- 添加API服务层(src/service/) - HTTP客户端配置 - 登录认证API - 系统管理API(用户、角色、菜单、部门、字典、文件等) - 监控API(在线用户、服务器、缓存、Redis等) - 添加路由系统(src/router/) - 路由实例配置 - 路由守卫逻辑 - 静态路由和内置路由 - 添加状态管理(src/store/) - 认证状态(auth) - 路由状态(router) - 应用状态(app) - 标签页状态(tab) - 字典状态(dict)
This commit is contained in:
parent
715270aa49
commit
cb98681927
150
src/router/guard.ts
Normal file
150
src/router/guard.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import type { Router } from 'vue-router'
|
||||
import { useAppStore, useRouteStore, useTabStore } from '@/store'
|
||||
import { local } from '@/utils'
|
||||
import { handleRouterError } from '@/utils/router-safety'
|
||||
|
||||
const title = import.meta.env.VITE_APP_NAME
|
||||
|
||||
export function setupRouterGuard(router: Router) {
|
||||
const appStore = useAppStore()
|
||||
const routeStore = useRouteStore()
|
||||
const tabStore = useTabStore()
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
try {
|
||||
// 判断是否是外链,如果是直接打开网页并拦截跳转
|
||||
if (to.meta.href) {
|
||||
window.open(to.meta.href)
|
||||
next(false) // 取消当前导航
|
||||
return
|
||||
}
|
||||
// 开始 loadingBar
|
||||
appStore.showProgress && window.$loadingBar?.start()
|
||||
|
||||
// 判断有无TOKEN,登录鉴权
|
||||
const isLogin = Boolean(local.get('accessToken'))
|
||||
|
||||
// 如果是login路由,直接放行
|
||||
if (to.name === 'login') {
|
||||
// login页面不需要任何认证检查,直接放行
|
||||
// 继续执行后面的逻辑
|
||||
}
|
||||
// 如果路由明确设置了requiresAuth为false,直接放行
|
||||
else if (to.meta.requiresAuth === false) {
|
||||
// 明确设置为false的路由直接放行
|
||||
// 继续执行后面的逻辑
|
||||
}
|
||||
// 如果路由设置了requiresAuth为true,且用户未登录,重定向到登录页
|
||||
else if (to.meta.requiresAuth === true && !isLogin) {
|
||||
const redirect = to.name === '404' ? undefined : to.fullPath
|
||||
next({ path: '/login', query: { redirect } })
|
||||
return
|
||||
}
|
||||
|
||||
// 判断路由有无进行初始化
|
||||
if (!routeStore.isInitAuthRoute) {
|
||||
// 只有在已登录状态下才初始化路由,避免退出登录时的无效请求
|
||||
if (!isLogin) {
|
||||
// 未登录状态下,如果访问的不是登录页,重定向到登录页
|
||||
if (to.name !== 'login') {
|
||||
const redirect = to.name === '404' ? undefined : to.fullPath
|
||||
next({ path: '/login', query: { redirect } })
|
||||
return
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 已登录状态下才初始化路由
|
||||
try {
|
||||
await routeStore.initAuthRoute()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('路由初始化失败:', error)
|
||||
|
||||
// 检查是否是网络错误
|
||||
const isNetworkError = !navigator.onLine
|
||||
|| (error instanceof Error && (
|
||||
error.message.includes('网络')
|
||||
|| error.message.includes('Network')
|
||||
|| error.message.includes('fetch')
|
||||
|| error.message.includes('timeout')
|
||||
))
|
||||
|
||||
if (isNetworkError) {
|
||||
// 网络错误,清除认证信息并重定向到登录页
|
||||
local.remove('accessToken')
|
||||
local.remove('refreshToken')
|
||||
}
|
||||
|
||||
// 路由初始化失败,重定向到登录页
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 动态路由加载完回到根路由
|
||||
if (to.name === 'notFound' || to.name === '404') {
|
||||
// 等待权限路由加载好了,回到之前的路由,否则404
|
||||
next({
|
||||
path: to.fullPath,
|
||||
replace: true,
|
||||
query: to.query,
|
||||
hash: to.hash,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果用户已登录且访问login页面,重定向到首页
|
||||
if (to.name === 'login' && isLogin) {
|
||||
next({ path: import.meta.env.VITE_HOME_PATH || '/dashboard' })
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('路由守卫执行错误:', error)
|
||||
// 发生错误时确保loadingBar结束
|
||||
appStore.showProgress && window.$loadingBar?.error?.()
|
||||
next(false)
|
||||
}
|
||||
})
|
||||
|
||||
router.beforeResolve((to) => {
|
||||
try {
|
||||
// 设置菜单高亮
|
||||
routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath)
|
||||
// 添加tabs
|
||||
tabStore.addTab(to)
|
||||
// 设置高亮标签;
|
||||
tabStore.setCurrentTab(to.fullPath as string)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('路由beforeResolve错误:', error)
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
try {
|
||||
// 修改网页标题
|
||||
document.title = `${to.meta.title} - ${title}`
|
||||
// 结束 loadingBar
|
||||
if (failure) {
|
||||
appStore.showProgress && window.$loadingBar?.error?.()
|
||||
}
|
||||
else {
|
||||
appStore.showProgress && window.$loadingBar?.finish()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('路由afterEach错误:', error)
|
||||
appStore.showProgress && window.$loadingBar?.error?.()
|
||||
}
|
||||
})
|
||||
|
||||
// 添加路由错误处理
|
||||
router.onError((error) => {
|
||||
handleRouterError(error, '全局路由')
|
||||
appStore.showProgress && window.$loadingBar?.error?.()
|
||||
})
|
||||
}
|
||||
29
src/router/index.ts
Normal file
29
src/router/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { App } from 'vue'
|
||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
||||
import { setupRouterGuard } from './guard'
|
||||
import { routes } from './routes.inner'
|
||||
import { createNavigationGuard } from '@/utils/navigation-guard'
|
||||
|
||||
const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env
|
||||
export const router = createRouter({
|
||||
history: VITE_ROUTE_MODE === 'hash' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
|
||||
routes,
|
||||
// 添加路由配置优化
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
}
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
// 创建导航防护实例
|
||||
export const navigationGuard = createNavigationGuard(router)
|
||||
|
||||
// 安装vue路由
|
||||
export async function installRouter(app: App) {
|
||||
// 添加路由守卫
|
||||
setupRouterGuard(router)
|
||||
app.use(router)
|
||||
await router.isReady() // https://router.vuejs.org/zh/api/index.html#isready
|
||||
}
|
||||
77
src/router/routes.inner.ts
Normal file
77
src/router/routes.inner.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { safeAsyncComponent } from '@/utils/component-guard'
|
||||
|
||||
/* 页面中的一些固定路由,错误页等 */
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'root',
|
||||
redirect: '/appRoot',
|
||||
children: [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: safeAsyncComponent(
|
||||
() => import('@/views/login/index.vue'),
|
||||
{ delay: 0, timeout: 8000 },
|
||||
),
|
||||
meta: {
|
||||
title: '登录',
|
||||
withoutTab: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
name: '403',
|
||||
component: safeAsyncComponent(
|
||||
() => import('@/views/error/403/index.vue'),
|
||||
{ delay: 0, timeout: 5000 },
|
||||
),
|
||||
meta: {
|
||||
title: '用户无权限',
|
||||
withoutTab: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: '404',
|
||||
component: safeAsyncComponent(
|
||||
() => import('@/views/error/404/index.vue'),
|
||||
{ delay: 0, timeout: 5000 },
|
||||
),
|
||||
meta: {
|
||||
title: '找不到页面',
|
||||
icon: 'icon-park-outline:ghost',
|
||||
withoutTab: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: '500',
|
||||
component: safeAsyncComponent(
|
||||
() => import('@/views/error/500/index.vue'),
|
||||
{ delay: 0, timeout: 5000 },
|
||||
),
|
||||
meta: {
|
||||
title: '服务器错误',
|
||||
icon: 'icon-park-outline:close-wifi',
|
||||
withoutTab: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: safeAsyncComponent(
|
||||
() => import('@/views/error/404/index.vue'),
|
||||
{ delay: 0, timeout: 5000 },
|
||||
),
|
||||
name: 'notFound',
|
||||
meta: {
|
||||
title: '找不到页面',
|
||||
icon: 'icon-park-outline:ghost',
|
||||
withoutTab: true,
|
||||
},
|
||||
},
|
||||
|
||||
]
|
||||
25
src/router/routes.static.ts
Normal file
25
src/router/routes.static.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export const staticRoutes: AppRoute.RowRoute[] = [
|
||||
{
|
||||
name: 'dict-data',
|
||||
path: '/system/dict/data',
|
||||
title: '字典数据详情',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:table-file',
|
||||
menuType: '2',
|
||||
componentPath: 'system/dict/data',
|
||||
id: 1001,
|
||||
pid: null,
|
||||
hide: true, // 隐藏在菜单中,只用于路由跳转
|
||||
},
|
||||
// {
|
||||
// name: 'personal-center',
|
||||
// path: '/personal-center',
|
||||
// title: '个人中心',
|
||||
// requiresAuth: true,
|
||||
// icon: 'icon-park-outline:id-card-h',
|
||||
// menuType: '2',
|
||||
// componentPath: '/personal-center/index',
|
||||
// id: 4,
|
||||
// pid: null,
|
||||
// },
|
||||
]
|
||||
84
src/service/api/auth/index.ts
Normal file
84
src/service/api/auth/index.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { request } from '../../http'
|
||||
|
||||
interface LoginRequest {
|
||||
loginName: string
|
||||
password: string
|
||||
codeKey: string
|
||||
securityCode: string
|
||||
rememberMe?: boolean
|
||||
}
|
||||
|
||||
interface RegisterRequest {
|
||||
loginName: string
|
||||
password: string
|
||||
userName: string
|
||||
codeKey: string
|
||||
securityCode: string
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
tokenName: string
|
||||
tokenValue: string
|
||||
}
|
||||
|
||||
interface CaptchaResponse {
|
||||
codeKey: string
|
||||
captchaPicture: string
|
||||
captchaText?: string
|
||||
}
|
||||
|
||||
export function fetchLogin(data: LoginRequest) {
|
||||
const methodInstance = request.Post<Service.ResponseResult<LoginResponse>>('/auth/login', data)
|
||||
methodInstance.meta = {
|
||||
authRole: null,
|
||||
}
|
||||
return methodInstance
|
||||
}
|
||||
|
||||
export function fetchLogout() {
|
||||
return request.Get<Service.ResponseResult<string>>('/auth/logout')
|
||||
}
|
||||
|
||||
export function fetchRegister(data: RegisterRequest) {
|
||||
const methodInstance = request.Post<Service.ResponseResult<string>>('/auth/register', data)
|
||||
methodInstance.meta = {
|
||||
authRole: null,
|
||||
}
|
||||
return methodInstance
|
||||
}
|
||||
|
||||
export function fetchUpdateToken(data: any) {
|
||||
const method = request.Post<Service.ResponseResult<LoginResponse>>('/updateToken', data)
|
||||
method.meta = {
|
||||
authRole: 'refreshToken',
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
export function fetchUserRoutesOld(params: { id: number }) {
|
||||
return request.Get<Service.ResponseResult<AppRoute.RowRoute[]>>('/getUserRoutes', { params })
|
||||
}
|
||||
|
||||
export function fetchLoginUserInfo() {
|
||||
return request.Get<Service.ResponseResult<Api.Login.UserInfoResponse>>('/coder/sysLoginUser/getLoginUserInformation')
|
||||
}
|
||||
|
||||
// 验证码相关接口
|
||||
|
||||
// 获取PNG格式验证码
|
||||
export function fetchCaptchaPng() {
|
||||
const methodInstance = request.Get<Service.ResponseResult<CaptchaResponse>>('/captcha/png')
|
||||
methodInstance.meta = {
|
||||
authRole: null,
|
||||
}
|
||||
return methodInstance
|
||||
}
|
||||
|
||||
// 获取GIF格式验证码
|
||||
export function fetchCaptchaGif() {
|
||||
const methodInstance = request.Get<Service.ResponseResult<CaptchaResponse>>('/captcha/gif')
|
||||
methodInstance.meta = {
|
||||
authRole: null,
|
||||
}
|
||||
return methodInstance
|
||||
}
|
||||
84
src/service/api/dashboard/index.ts
Normal file
84
src/service/api/dashboard/index.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { request } from '../../http'
|
||||
import type {
|
||||
DailyActivityStatsVo,
|
||||
DashboardQueryBo,
|
||||
DashboardStatisticsVo,
|
||||
LoginStatsVo,
|
||||
LoginTrendVo,
|
||||
StorageStatsVo,
|
||||
UserStatsVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
DailyActivityStatsVo,
|
||||
DashboardQueryBo,
|
||||
DashboardStatisticsVo,
|
||||
LoginStatsVo,
|
||||
LoginTrendItemVo,
|
||||
LoginTrendVo,
|
||||
StorageStatsVo,
|
||||
UserStatsVo,
|
||||
} from './types'
|
||||
|
||||
// 仪表盘相关API
|
||||
|
||||
/**
|
||||
* 获取仪表盘统计数据
|
||||
*/
|
||||
export function getDashboardStatistics() {
|
||||
return request.Get<Service.ResponseResult<DashboardStatisticsVo>>('/coder/dashboard/getStatistics')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录趋势数据
|
||||
* @param days 查询天数,默认7天
|
||||
*/
|
||||
export function getLoginTrend(days: number = 7) {
|
||||
return request.Get<Service.ResponseResult<LoginTrendVo>>('/coder/dashboard/getLoginTrend', {
|
||||
params: { days },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整仪表盘数据
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export function getAllDashboardData(params: DashboardQueryBo = {}) {
|
||||
const { includeTrend = true, trendDays = 7 } = params
|
||||
return request.Get<Service.ResponseResult<DashboardStatisticsVo>>('/coder/dashboard/getAllData', {
|
||||
params: { includeTrend, trendDays },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户统计数据
|
||||
*/
|
||||
export function getUserStats() {
|
||||
return request.Get<Service.ResponseResult<UserStatsVo>>('/coder/dashboard/getUserStats')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录统计数据
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export function getLoginStats(params: DashboardQueryBo = {}) {
|
||||
const { includeTrend = false, trendDays = 7 } = params
|
||||
return request.Get<Service.ResponseResult<LoginStatsVo>>('/coder/dashboard/getLoginStats', {
|
||||
params: { includeTrend, trendDays },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储统计数据
|
||||
*/
|
||||
export function getStorageStats() {
|
||||
return request.Get<Service.ResponseResult<StorageStatsVo>>('/coder/dashboard/getStorageStats')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日活跃统计数据
|
||||
*/
|
||||
export function getDailyActivityStats() {
|
||||
return request.Get<Service.ResponseResult<DailyActivityStatsVo>>('/coder/dashboard/getDailyActivityStats')
|
||||
}
|
||||
91
src/service/api/dashboard/types.ts
Normal file
91
src/service/api/dashboard/types.ts
Normal file
@ -0,0 +1,91 @@
|
||||
// 仪表盘API类型定义
|
||||
|
||||
// 用户统计数据
|
||||
export interface UserStatsVo {
|
||||
/** 总用户数 */
|
||||
totalUsers: number
|
||||
/** 今日新增用户数 */
|
||||
todayNewUsers: number
|
||||
/** 活跃用户数 */
|
||||
activeUsers: number
|
||||
/** 当前在线用户数 */
|
||||
onlineUsers: number
|
||||
}
|
||||
|
||||
// 登录趋势项
|
||||
export interface LoginTrendItemVo {
|
||||
/** 日期(YYYY-MM-DD格式) */
|
||||
date: string
|
||||
/** 当日登录次数 */
|
||||
count: number
|
||||
/** 显示标签(用于图表展示) */
|
||||
label: string
|
||||
}
|
||||
|
||||
// 登录统计数据
|
||||
export interface LoginStatsVo {
|
||||
/** 今日登录次数 */
|
||||
todayLogins: number
|
||||
/** 累计登录次数 */
|
||||
totalLogins: number
|
||||
/** 登录趋势数据 */
|
||||
loginTrend?: LoginTrendItemVo[]
|
||||
}
|
||||
|
||||
// 存储统计数据
|
||||
export interface StorageStatsVo {
|
||||
/** 总文件数 */
|
||||
totalFiles: number
|
||||
/** 总图片数 */
|
||||
totalImages: number
|
||||
/** 总存储大小(格式化) */
|
||||
totalSize: string
|
||||
/** 今日上传文件数 */
|
||||
todayUploads: number
|
||||
/** 存储使用率(百分比) */
|
||||
storageUsage: number
|
||||
/** 可用空间(格式化) */
|
||||
availableSpace: string
|
||||
}
|
||||
|
||||
// 今日活跃统计数据
|
||||
export interface DailyActivityStatsVo {
|
||||
/** 今日访问量 */
|
||||
todayVisits: number
|
||||
/** 今日操作数 */
|
||||
todayOperations: number
|
||||
/** 活跃用户数 */
|
||||
activeUsers: number
|
||||
/** 新增内容数 */
|
||||
newContent: number
|
||||
/** API调用次数 */
|
||||
apiCalls: number
|
||||
/** 平均响应时间(毫秒) */
|
||||
avgResponseTime: number
|
||||
}
|
||||
|
||||
// 仪表盘统计数据响应对象
|
||||
export interface DashboardStatisticsVo {
|
||||
/** 用户统计数据 */
|
||||
userStats?: UserStatsVo
|
||||
/** 登录统计数据 */
|
||||
loginStats?: LoginStatsVo
|
||||
/** 存储统计数据 */
|
||||
storageStats?: StorageStatsVo
|
||||
/** 今日活跃统计数据 */
|
||||
dailyActivityStats?: DailyActivityStatsVo
|
||||
}
|
||||
|
||||
// 登录趋势数据响应对象
|
||||
export interface LoginTrendVo {
|
||||
/** 登录趋势数据列表 */
|
||||
loginTrend: LoginTrendItemVo[]
|
||||
}
|
||||
|
||||
// 查询参数类型
|
||||
export interface DashboardQueryBo {
|
||||
/** 是否包含趋势数据 */
|
||||
includeTrend?: boolean
|
||||
/** 趋势数据天数 */
|
||||
trendDays?: number
|
||||
}
|
||||
55
src/service/api/monitor/cache/index.ts
vendored
Normal file
55
src/service/api/monitor/cache/index.ts
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
import { request } from '@/service/http'
|
||||
import type { DeleteCacheKeyBo, GetCacheValueBo, SysCacheVo } from './types'
|
||||
|
||||
/**
|
||||
* 查询Redis缓存所有Key
|
||||
*/
|
||||
export function getRedisCache() {
|
||||
return request.Get<Service.ResponseResult<SysCacheVo[]>>('/coder/monitor/cache/getRedisCache')
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询Redis缓存键名列表
|
||||
*/
|
||||
export function getCacheKeys(cacheName: string) {
|
||||
// 对缓存名称进行URL编码,处理冒号等特殊字符
|
||||
const encodedCacheName = encodeURIComponent(cacheName)
|
||||
return request.Get<Service.ResponseResult<string[]>>(`/coder/monitor/cache/getCacheKeys/${encodedCacheName}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Redis缓存内容
|
||||
*/
|
||||
export function getCacheValue(data: GetCacheValueBo) {
|
||||
return request.Post<Service.ResponseResult<SysCacheVo>>('/coder/monitor/cache/getValue', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Redis指定名称缓存
|
||||
*/
|
||||
export function deleteCacheName(cacheName: string) {
|
||||
// 对缓存名称进行URL编码,处理冒号等特殊字符
|
||||
const encodedCacheName = encodeURIComponent(cacheName)
|
||||
return request.Post<Service.ResponseResult<void>>(`/coder/monitor/cache/deleteCacheName/${encodedCacheName}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Redis指定键名缓存
|
||||
*/
|
||||
export function deleteCacheKey(data: DeleteCacheKeyBo) {
|
||||
return request.Post<Service.ResponseResult<void>>('/coder/monitor/cache/deleteCacheKey', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Redis所有信息
|
||||
*/
|
||||
export function deleteCacheAll() {
|
||||
return request.Post<Service.ResponseResult<void>>('/coder/monitor/cache/deleteCacheAll')
|
||||
}
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
DeleteCacheKeyBo,
|
||||
GetCacheValueBo,
|
||||
SysCacheVo,
|
||||
} from './types'
|
||||
33
src/service/api/monitor/cache/types.ts
vendored
Normal file
33
src/service/api/monitor/cache/types.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 缓存管理相关类型定义
|
||||
*/
|
||||
|
||||
// 缓存信息
|
||||
export interface SysCacheVo {
|
||||
/** 缓存名称 */
|
||||
cacheName: string
|
||||
/** 缓存键名 */
|
||||
cacheKey?: string
|
||||
/** 缓存内容 */
|
||||
cacheValue?: string
|
||||
/** 缓存过期时间 */
|
||||
expireTime?: string
|
||||
/** 备注信息 */
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 获取缓存内容请求参数
|
||||
export interface GetCacheValueBo {
|
||||
/** 缓存名称 */
|
||||
cacheName: string
|
||||
/** 缓存键名 */
|
||||
cacheKey: string
|
||||
}
|
||||
|
||||
// 删除缓存键请求参数
|
||||
export interface DeleteCacheKeyBo {
|
||||
/** 缓存名称 */
|
||||
cacheName?: string
|
||||
/** 缓存键名 */
|
||||
cacheKey: string
|
||||
}
|
||||
77
src/service/api/monitor/job/index.ts
Normal file
77
src/service/api/monitor/job/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '@/service/http'
|
||||
import type {
|
||||
PageSysJobVo,
|
||||
SysJobForm,
|
||||
SysJobQueryBo,
|
||||
SysJobSearchForm,
|
||||
SysJobVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型定义
|
||||
export type { PageSysJobVo, SysJobForm, SysJobQueryBo, SysJobSearchForm, SysJobVo }
|
||||
|
||||
/**
|
||||
* 分页查询定时任务列表
|
||||
*/
|
||||
export function getSysJobListPage(params: SysJobQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageSysJobVo>>('/coder/sysJob/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有定时任务
|
||||
*/
|
||||
export function getSysJobList(params?: SysJobQueryBo) {
|
||||
return request.Get<Service.ResponseResult<SysJobVo[]>>('/coder/sysJob/list', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询定时任务详情
|
||||
*/
|
||||
export function getSysJobById(id: string) {
|
||||
return request.Get<Service.ResponseResult<SysJobVo>>(`/coder/sysJob/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增定时任务
|
||||
*/
|
||||
export function addSysJob(data: SysJobForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysJob/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改定时任务
|
||||
*/
|
||||
export function updateSysJob(data: SysJobForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysJob/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除定时任务
|
||||
*/
|
||||
export function deleteSysJobById(id: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysJob/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除定时任务
|
||||
*/
|
||||
export function batchDeleteSysJob(jobIds: string[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysJob/batchDelete', jobIds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改任务状态
|
||||
* @param id 任务ID
|
||||
* @param jobStatus 任务状态[0正常 1暂停]
|
||||
* @param policyStatus 计划策略[1-立即执行 2-执行一次 3-放弃执行]
|
||||
*/
|
||||
export function updateSysJobStatus(id: string, jobStatus: string, policyStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysJob/updateStatus/${id}/${jobStatus}/${policyStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即执行任务
|
||||
*/
|
||||
export function runSysJobNow(id: string) {
|
||||
return request.Get<Service.ResponseResult<string>>(`/coder/sysJob/runNow/${id}`)
|
||||
}
|
||||
102
src/service/api/monitor/job/types.ts
Normal file
102
src/service/api/monitor/job/types.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 定时任务管理 - 类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 定时任务实体
|
||||
*/
|
||||
export interface SysJobVo {
|
||||
/** 任务ID */
|
||||
jobId: string
|
||||
/** 任务名称 */
|
||||
jobName: string
|
||||
/** 任务类型[1-管理平台 2-小程序 3-App] */
|
||||
jobType: string
|
||||
/** 类路径 */
|
||||
classPath: string
|
||||
/** 方法名称 */
|
||||
methodName: string
|
||||
/** cron执行表达式 */
|
||||
cronExpression: string
|
||||
/** cron计划策略[1-立即执行 2-执行一次 3-放弃执行] */
|
||||
policyStatus: string
|
||||
/** 任务状态[0正常 1暂停] */
|
||||
jobStatus: string
|
||||
/** 任务参数 */
|
||||
jobParams?: string
|
||||
/** 任务备注 */
|
||||
remark?: string
|
||||
/** 创建者 */
|
||||
createBy?: string
|
||||
/** 创建时间 */
|
||||
createTime?: string
|
||||
/** 更新者 */
|
||||
updateBy?: string
|
||||
/** 更新时间 */
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务查询参数
|
||||
*/
|
||||
export interface SysJobQueryBo {
|
||||
/** 页码 */
|
||||
pageNo?: number
|
||||
/** 页大小 */
|
||||
pageSize?: number
|
||||
/** 任务名称 */
|
||||
jobName?: string
|
||||
/** 任务类型 */
|
||||
jobType?: string
|
||||
/** 任务状态 */
|
||||
jobStatus?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务搜索表单
|
||||
*/
|
||||
export interface SysJobSearchForm {
|
||||
/** 任务名称 */
|
||||
jobName?: string
|
||||
/** 任务类型 */
|
||||
jobType?: string
|
||||
/** 任务状态 */
|
||||
jobStatus?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务表单数据
|
||||
*/
|
||||
export interface SysJobForm {
|
||||
/** 任务ID */
|
||||
jobId?: string
|
||||
/** 任务名称 */
|
||||
jobName: string
|
||||
/** 任务类型[1-管理平台 2-小程序 3-App] */
|
||||
jobType: string
|
||||
/** 类路径 */
|
||||
classPath: string
|
||||
/** 方法名称 */
|
||||
methodName: string
|
||||
/** cron执行表达式 */
|
||||
cronExpression: string
|
||||
/** cron计划策略[1-立即执行 2-执行一次 3-放弃执行] */
|
||||
policyStatus: string
|
||||
/** 任务状态[0正常 1暂停] */
|
||||
jobStatus: string
|
||||
/** 任务参数 */
|
||||
jobParams?: string
|
||||
/** 任务备注 */
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*/
|
||||
export interface PageSysJobVo {
|
||||
records: SysJobVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
33
src/service/api/monitor/online/index.ts
Normal file
33
src/service/api/monitor/online/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { request } from '@/service/http'
|
||||
import type {
|
||||
OnlineUserCountVo,
|
||||
OnlineUserSearchForm,
|
||||
OnlineUserVo,
|
||||
PageOnlineUserVo,
|
||||
SysUserOnlineQueryBo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型定义
|
||||
export type { OnlineUserCountVo, OnlineUserSearchForm, OnlineUserVo, PageOnlineUserVo, SysUserOnlineQueryBo }
|
||||
|
||||
/**
|
||||
* 分页查询在线用户列表
|
||||
*/
|
||||
export function getOnlineUserListPage(params: SysUserOnlineQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageOnlineUserVo>>('/coder/sysUserOnline/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制注销
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
export function logoutUser(userId: string) {
|
||||
return request.Get<Service.ResponseResult<string>>(`/coder/sysUserOnline/logout/${userId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户统计信息
|
||||
*/
|
||||
export function getOnlineUserCount() {
|
||||
return request.Get<Service.ResponseResult<OnlineUserCountVo>>('/coder/sysUserOnline/count')
|
||||
}
|
||||
97
src/service/api/monitor/online/types.ts
Normal file
97
src/service/api/monitor/online/types.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 在线用户监控 - 类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 在线用户实体
|
||||
*/
|
||||
export interface OnlineUserVo {
|
||||
/** 用户ID */
|
||||
userId: string
|
||||
/** 登录名称 */
|
||||
loginName: string
|
||||
/** 用户名 */
|
||||
userName: string
|
||||
/** 用户头像 */
|
||||
avatar?: string
|
||||
/** 性别[1-男 2-女 3-未知] */
|
||||
sex?: string
|
||||
/** 手机号 */
|
||||
phone?: string
|
||||
/** 邮箱 */
|
||||
email?: string
|
||||
/** 用户类型[1-系统用户 2-注册用户 3-微信用户] */
|
||||
userType?: string
|
||||
/** 城市ID[可新增城市表-暂时无用] */
|
||||
cityId?: string
|
||||
/** 登录时间 */
|
||||
loginTime?: string
|
||||
/** 创建时间 */
|
||||
createTime?: string
|
||||
/** 登录IP */
|
||||
loginIp?: string
|
||||
/** 登录地址 */
|
||||
loginAddress?: string
|
||||
/** 浏览器类型 */
|
||||
browser?: string
|
||||
/** 操作系统 */
|
||||
os?: string
|
||||
/** 设备名字 */
|
||||
deviceName?: string
|
||||
/** 是否超级管理员 */
|
||||
isCoderAdmin?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线用户查询参数
|
||||
*/
|
||||
export interface SysUserOnlineQueryBo {
|
||||
/** 页码 */
|
||||
pageNo?: number
|
||||
/** 页大小 */
|
||||
pageSize?: number
|
||||
/** 登录名称 */
|
||||
loginName?: string
|
||||
/** 用户名字 */
|
||||
userName?: string
|
||||
/** IP地址 */
|
||||
loginIp?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线用户搜索表单
|
||||
*/
|
||||
export interface OnlineUserSearchForm {
|
||||
/** 登录名称 */
|
||||
loginName?: string
|
||||
/** 用户名字 */
|
||||
userName?: string
|
||||
/** IP地址 */
|
||||
loginIp?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页在线用户响应
|
||||
*/
|
||||
export interface PageOnlineUserVo {
|
||||
/** 总记录数 */
|
||||
total: number
|
||||
/** 当前页 */
|
||||
current: number
|
||||
/** 每页大小 */
|
||||
size: number
|
||||
/** 总页数 */
|
||||
pages: number
|
||||
/** 数据列表 */
|
||||
records: OnlineUserVo[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线用户统计响应
|
||||
*/
|
||||
export interface OnlineUserCountVo {
|
||||
/** 在线用户总数 */
|
||||
onlineCount: number
|
||||
/** 统计时间戳 */
|
||||
timestamp: number
|
||||
}
|
||||
15
src/service/api/monitor/redis/index.ts
Normal file
15
src/service/api/monitor/redis/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { request } from '@/service/http'
|
||||
import type { RedisInfoVo } from './types'
|
||||
|
||||
/**
|
||||
* 获取Redis监控信息
|
||||
*/
|
||||
export function getRedisInformation() {
|
||||
return request.Get<Service.ResponseResult<RedisInfoVo>>('/coder/monitor/redis/getRedisInformation')
|
||||
}
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
RedisCommandStatVo,
|
||||
RedisInfoVo,
|
||||
} from './types'
|
||||
21
src/service/api/monitor/redis/types.ts
Normal file
21
src/service/api/monitor/redis/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Redis监控相关类型定义
|
||||
*/
|
||||
|
||||
// Redis命令统计项
|
||||
export interface RedisCommandStatVo {
|
||||
/** 命令名称 */
|
||||
name: string
|
||||
/** 调用次数 */
|
||||
value: string
|
||||
}
|
||||
|
||||
// Redis监控信息
|
||||
export interface RedisInfoVo {
|
||||
/** Redis基本信息 */
|
||||
info: Record<string, any>
|
||||
/** 数据库大小 */
|
||||
dbSize: number
|
||||
/** 命令统计 */
|
||||
commandStats: RedisCommandStatVo[]
|
||||
}
|
||||
19
src/service/api/monitor/server/index.ts
Normal file
19
src/service/api/monitor/server/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { request } from '@/service/http'
|
||||
import type { ServerVo } from './types'
|
||||
|
||||
/**
|
||||
* 获取服务器监控信息
|
||||
*/
|
||||
export function getServerInformation() {
|
||||
return request.Get<Service.ResponseResult<ServerVo>>('/coder/monitor/server/getServerInformation')
|
||||
}
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
CpuVo,
|
||||
JvmVo,
|
||||
MemVo,
|
||||
ServerVo,
|
||||
SysFileVo,
|
||||
SysVo,
|
||||
} from './types'
|
||||
123
src/service/api/monitor/server/types.ts
Normal file
123
src/service/api/monitor/server/types.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 服务器监控相关类型定义
|
||||
*/
|
||||
|
||||
// CPU信息
|
||||
export interface CpuVo {
|
||||
/** 核心数 */
|
||||
cpuNum: number
|
||||
/** CPU总的使用率 */
|
||||
total: number
|
||||
/** CPU系统使用率 */
|
||||
sys: number
|
||||
/** CPU用户使用率 */
|
||||
used: number
|
||||
/** CPU当前等待率 */
|
||||
wait: number
|
||||
/** CPU当前空闲率 */
|
||||
free: number
|
||||
/** CPU使用率百分比 */
|
||||
cpuUsage: number
|
||||
/** CPU系统使用率百分比 */
|
||||
sysUsage: number
|
||||
/** CPU用户使用率百分比 */
|
||||
userUsage: number
|
||||
/** CPU等待率百分比 */
|
||||
waitUsage: number
|
||||
/** CPU空闲率百分比 */
|
||||
freeUsage: number
|
||||
}
|
||||
|
||||
// 内存信息
|
||||
export interface MemVo {
|
||||
/** 内存总量 */
|
||||
total: number
|
||||
/** 已用内存 */
|
||||
used: number
|
||||
/** 剩余内存 */
|
||||
free: number
|
||||
/** 内存使用率 */
|
||||
usage: number
|
||||
/** 总内存(格式化) */
|
||||
totalStr: string
|
||||
/** 已用内存(格式化) */
|
||||
usedStr: string
|
||||
/** 剩余内存(格式化) */
|
||||
freeStr: string
|
||||
}
|
||||
|
||||
// JVM信息
|
||||
export interface JvmVo {
|
||||
/** 当前JVM占用的内存总数(M) */
|
||||
total: number
|
||||
/** JVM最大可用内存总数(M) */
|
||||
max: number
|
||||
/** JVM空闲内存(M) */
|
||||
free: number
|
||||
/** JDK版本 */
|
||||
version: string
|
||||
/** JDK路径 */
|
||||
home: string
|
||||
/** JVM已用内存 */
|
||||
used: number
|
||||
/** JVM内存使用率 */
|
||||
usage: number
|
||||
/** 总内存(格式化) */
|
||||
totalStr: string
|
||||
/** 已用内存(格式化) */
|
||||
usedStr: string
|
||||
/** 剩余内存(格式化) */
|
||||
freeStr: string
|
||||
/** 最大内存(格式化) */
|
||||
maxStr: string
|
||||
/** JVM启动时间 */
|
||||
startTime: string
|
||||
/** JVM运行时间 */
|
||||
runTime: string
|
||||
}
|
||||
|
||||
// 系统信息
|
||||
export interface SysVo {
|
||||
/** 服务器名称 */
|
||||
computerName: string
|
||||
/** 服务器IP */
|
||||
computerIp: string
|
||||
/** 项目路径 */
|
||||
userDir: string
|
||||
/** 操作系统 */
|
||||
osName: string
|
||||
/** 系统架构 */
|
||||
osArch: string
|
||||
}
|
||||
|
||||
// 磁盘文件信息
|
||||
export interface SysFileVo {
|
||||
/** 盘符路径 */
|
||||
dirName: string
|
||||
/** 盘符类型 */
|
||||
sysTypeName: string
|
||||
/** 文件类型 */
|
||||
typeName: string
|
||||
/** 总大小 */
|
||||
total: string
|
||||
/** 剩余大小 */
|
||||
free: string
|
||||
/** 已经使用量 */
|
||||
used: string
|
||||
/** 资源的使用率 */
|
||||
usage: number
|
||||
}
|
||||
|
||||
// 服务器信息
|
||||
export interface ServerVo {
|
||||
/** CPU相关信息 */
|
||||
cpu: CpuVo
|
||||
/** 内存相关信息 */
|
||||
mem: MemVo
|
||||
/** JVM相关信息 */
|
||||
jvm: JvmVo
|
||||
/** 服务器相关信息 */
|
||||
sys: SysVo
|
||||
/** 磁盘相关信息 */
|
||||
sysFiles: SysFileVo[]
|
||||
}
|
||||
66
src/service/api/personal/index.ts
Normal file
66
src/service/api/personal/index.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { request } from '../../http'
|
||||
|
||||
// 个人资料数据类型
|
||||
export interface PersonalDataVo {
|
||||
userId: number
|
||||
userName: string
|
||||
loginName: string
|
||||
email?: string
|
||||
phone?: string
|
||||
sex?: string
|
||||
avatar?: string
|
||||
userStatus?: string
|
||||
createTime?: string
|
||||
roleNames?: string[] // 角色名称数组
|
||||
roleName?: string // 主要角色名称
|
||||
}
|
||||
|
||||
// 修改个人资料请求类型
|
||||
export interface UpdatePersonalBo {
|
||||
userName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
sex?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
// 修改密码请求类型
|
||||
export interface UpdatePasswordBo {
|
||||
password: string
|
||||
newPassword: string
|
||||
confirmPassword: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人资料
|
||||
*/
|
||||
export function getPersonalData() {
|
||||
return request.Get<Service.ResponseResult<PersonalDataVo>>('/coder/sysLoginUser/getPersonalData')
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改个人基本资料
|
||||
*/
|
||||
export function updateBasicData(data: UpdatePersonalBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/updateBasicData', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改登录密码
|
||||
*/
|
||||
export function updatePassword(data: UpdatePasswordBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/updateUserPwd', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传头像文件
|
||||
* @param file 文件
|
||||
* @param fileSize 文件大小限制(MB)
|
||||
*/
|
||||
export function uploadAvatar(file: File, fileSize: number = 5) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
// 注意:不要手动设置 Content-Type,让浏览器自动设置以包含正确的 boundary
|
||||
// 使用数字标识符 "1" 代表头像类型,避免数据库字段长度限制
|
||||
return request.Post<Service.ResponseResult<any>>(`/coder/file/uploadFile/${fileSize}/pictures/1`, formData)
|
||||
}
|
||||
183
src/service/api/system/dict/index.ts
Normal file
183
src/service/api/system/dict/index.ts
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 字典管理 API 接口
|
||||
*/
|
||||
|
||||
import { request } from '@/service/http'
|
||||
import type {
|
||||
DictDataForm,
|
||||
DictDataOption,
|
||||
DictDataQueryBo,
|
||||
DictDataVo,
|
||||
DictTypeForm,
|
||||
DictTypeOption,
|
||||
DictTypeQueryBo,
|
||||
DictTypeVo,
|
||||
PageDictDataVo,
|
||||
PageDictTypeVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
DictDataForm,
|
||||
DictDataOption,
|
||||
DictDataQueryBo,
|
||||
DictDataSearchForm,
|
||||
DictDataVo,
|
||||
DictTypeForm,
|
||||
DictTypeOption,
|
||||
DictTypeQueryBo,
|
||||
DictTypeSearchForm,
|
||||
DictTypeVo,
|
||||
PageDictDataVo,
|
||||
PageDictTypeVo,
|
||||
} from './types'
|
||||
|
||||
export { DictStatus, DictTag } from './types'
|
||||
|
||||
/** ======================= 字典类型管理 API ======================= */
|
||||
|
||||
/**
|
||||
* 分页查询字典类型列表
|
||||
*/
|
||||
export function getDictTypeList(params: DictTypeQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageDictTypeVo>>('/coder/sysDictType/listPage', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有字典类型
|
||||
*/
|
||||
export function getAllDictTypes() {
|
||||
return request.Get<Service.ResponseResult<DictTypeVo[]>>('/coder/sysDictType/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询字典类型详情
|
||||
*/
|
||||
export function getDictTypeById(id: string) {
|
||||
return request.Get<Service.ResponseResult<DictTypeVo>>(`/coder/sysDictType/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典类型
|
||||
*/
|
||||
export function addDictType(data: DictTypeForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典类型
|
||||
*/
|
||||
export function updateDictType(data: DictTypeForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典类型
|
||||
*/
|
||||
export function deleteDictType(id: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictType/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除字典类型
|
||||
*/
|
||||
export function batchDeleteDictType(ids: string[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictType/batchDelete', ids)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典类型状态
|
||||
*/
|
||||
export function updateDictTypeStatus(dictId: string, dictStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictType/updateStatus/${dictId}/${dictStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询字典类型下拉框选项
|
||||
*/
|
||||
export function getDictTypeOptions() {
|
||||
return request.Get<Service.ResponseResult<DictTypeOption[]>>('/coder/sysDictType/listDictType')
|
||||
}
|
||||
|
||||
/** ======================= 字典数据管理 API ======================= */
|
||||
|
||||
/**
|
||||
* 分页查询字典数据列表
|
||||
*/
|
||||
export function getDictDataList(params: DictDataQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageDictDataVo>>('/coder/sysDictData/listPage', {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有字典数据
|
||||
*/
|
||||
export function getAllDictData() {
|
||||
return request.Get<Service.ResponseResult<DictDataVo[]>>('/coder/sysDictData/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询字典数据详情
|
||||
*/
|
||||
export function getDictDataById(id: string) {
|
||||
return request.Get<Service.ResponseResult<DictDataVo>>(`/coder/sysDictData/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典数据
|
||||
*/
|
||||
export function addDictData(data: DictDataForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新排序号
|
||||
*/
|
||||
export function getDictDataSorted(dictType: string) {
|
||||
return request.Get<Service.ResponseResult<number>>(`/coder/sysDictData/getSorted/${dictType}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典数据
|
||||
*/
|
||||
export function updateDictData(data: DictDataForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典数据
|
||||
*/
|
||||
export function deleteDictData(id: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictData/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除字典数据
|
||||
*/
|
||||
export function batchDeleteDictData(ids: string[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysDictData/batchDelete', ids)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典数据状态
|
||||
*/
|
||||
export function updateDictDataStatus(dictId: string, dictStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysDictData/updateStatus/${dictId}/${dictStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型查询字典数据
|
||||
*/
|
||||
export function getDictDataByType(dictType: string) {
|
||||
return request.Get<Service.ResponseResult<DictDataOption[]>>(`/coder/sysDictData/listDataByType/${dictType}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步字典缓存到Redis
|
||||
*/
|
||||
export function syncDictCache() {
|
||||
return request.Get<Service.ResponseResult<string>>('/coder/sysDictData/listDictCacheRedis')
|
||||
}
|
||||
145
src/service/api/system/dict/types.ts
Normal file
145
src/service/api/system/dict/types.ts
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 字典管理相关的类型定义
|
||||
*/
|
||||
|
||||
/** 字典类型相关类型定义 */
|
||||
|
||||
// 字典类型实体
|
||||
export interface DictTypeVo {
|
||||
dictId: string
|
||||
dictType: string
|
||||
dictName: string
|
||||
dictStatus: string
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 字典类型查询参数
|
||||
export interface DictTypeQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
dictName?: string
|
||||
dictType?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典类型搜索表单
|
||||
export interface DictTypeSearchForm {
|
||||
dictName?: string
|
||||
dictType?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典类型表单
|
||||
export interface DictTypeForm {
|
||||
dictId?: string
|
||||
dictType: string
|
||||
dictName: string
|
||||
dictStatus: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 字典数据相关类型定义 */
|
||||
|
||||
// 字典数据实体
|
||||
export interface DictDataVo {
|
||||
dictId: string
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictStatus: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
sorted: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 字典数据查询参数
|
||||
export interface DictDataQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
dictType?: string
|
||||
dictLabel?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典数据搜索表单
|
||||
export interface DictDataSearchForm {
|
||||
dictType?: string
|
||||
dictLabel?: string
|
||||
dictStatus?: string
|
||||
}
|
||||
|
||||
// 字典数据表单
|
||||
export interface DictDataForm {
|
||||
dictId?: string
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictStatus: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
sorted: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 分页结果类型 */
|
||||
|
||||
// 字典类型分页结果
|
||||
export interface PageDictTypeVo {
|
||||
records: DictTypeVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 字典数据分页结果
|
||||
export interface PageDictDataVo {
|
||||
records: DictDataVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/** 选项类型 */
|
||||
|
||||
// 字典类型下拉选项
|
||||
export interface DictTypeOption {
|
||||
dictType: string
|
||||
dictName: string
|
||||
}
|
||||
|
||||
// 字典数据选项
|
||||
export interface DictDataOption {
|
||||
dictLabel: string
|
||||
dictValue: string
|
||||
dictType: string
|
||||
dictTag: string
|
||||
dictColor?: string
|
||||
}
|
||||
|
||||
/** 状态常量 */
|
||||
|
||||
// 字典状态枚举
|
||||
export const DictStatus = {
|
||||
NORMAL: '0', // 正常
|
||||
DISABLED: '1', // 停用
|
||||
} as const
|
||||
|
||||
// 字典标签类型枚举
|
||||
export const DictTag = {
|
||||
PRIMARY: 'primary',
|
||||
SUCCESS: 'success',
|
||||
INFO: 'info',
|
||||
WARNING: 'warning',
|
||||
ERROR: 'error',
|
||||
} as const
|
||||
94
src/service/api/system/file/index.ts
Normal file
94
src/service/api/system/file/index.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
FileUploadResult,
|
||||
PageSysFileVo,
|
||||
SysFileQueryBo,
|
||||
SysFileVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
FileServiceType,
|
||||
FileTypeEnum,
|
||||
FileUploadResult,
|
||||
PageSysFileVo,
|
||||
SysFileQueryBo,
|
||||
SysFileSearchForm,
|
||||
SysFileVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出常量值供外部使用
|
||||
export {
|
||||
FILE_SERVICE_OPTIONS,
|
||||
FILE_TYPE_OPTIONS,
|
||||
} from './types'
|
||||
|
||||
// 文件管理相关API
|
||||
|
||||
// 分页查询文件列表
|
||||
export function getSysFileList(params: SysFileQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageSysFileVo>>('/coder/sysFile/listPage', { params })
|
||||
}
|
||||
|
||||
// 查询所有文件(不分页)
|
||||
export function getAllSysFiles(params?: Omit<SysFileQueryBo, 'pageNo' | 'pageSize'>) {
|
||||
return request.Get<Service.ResponseResult<SysFileVo[]>>('/coder/sysFile/list', { params })
|
||||
}
|
||||
|
||||
// 根据ID查询文件
|
||||
export function getSysFileById(id: number) {
|
||||
return request.Get<Service.ResponseResult<SysFileVo>>(`/coder/sysFile/getById/${id}`)
|
||||
}
|
||||
|
||||
// 新增文件记录
|
||||
export function addSysFile(data: SysFileVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysFile/add', data)
|
||||
}
|
||||
|
||||
// 修改文件信息
|
||||
export function updateSysFile(data: SysFileVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysFile/update', data)
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
export function deleteSysFile(id: number) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysFile/deleteById/${id}`)
|
||||
}
|
||||
|
||||
// 批量删除文件
|
||||
export function batchDeleteSysFiles(ids: number[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysFile/batchDelete', ids)
|
||||
}
|
||||
|
||||
// 文件上传相关API
|
||||
|
||||
// 上传文件
|
||||
export function uploadFile(file: File, folderName: string, fileSize = 2, fileParam = '-1', storageType?: string) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 如果指定了存储类型,添加到表单数据中
|
||||
if (storageType) {
|
||||
formData.append('storageType', storageType)
|
||||
}
|
||||
|
||||
return request.Post<FileUploadResult>(`/coder/file/uploadFile/${fileSize}/${folderName}/${fileParam}`, formData)
|
||||
}
|
||||
|
||||
// 匿名上传文件(无需登录)
|
||||
export function uploadAnyFile(file: File, folderName: string, fileSize = 2, fileParam = '-1', storageType?: string) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 如果指定了存储类型,添加到表单数据中
|
||||
if (storageType) {
|
||||
formData.append('storageType', storageType)
|
||||
}
|
||||
|
||||
return request.Post<FileUploadResult>(`/coder/file/uploadAnyFile/${fileSize}/${folderName}/${fileParam}`, formData)
|
||||
}
|
||||
|
||||
// 兼容性导出 - 保持原有函数名以确保向后兼容
|
||||
export const fetchSysFilePage = getSysFileList
|
||||
export const fetchAllSysFiles = getAllSysFiles
|
||||
export const fetchSysFileById = getSysFileById
|
||||
109
src/service/api/system/file/types.ts
Normal file
109
src/service/api/system/file/types.ts
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 文件管理相关类型定义
|
||||
*/
|
||||
|
||||
// 文件查询条件类型
|
||||
export interface SysFileQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
fileName?: string
|
||||
fileSuffix?: string
|
||||
fileService?: string
|
||||
fileType?: string
|
||||
}
|
||||
|
||||
// 文件信息类型
|
||||
export interface SysFileVo {
|
||||
fileId?: number
|
||||
fileName: string
|
||||
newName?: string
|
||||
fileType: string
|
||||
fileSize?: string
|
||||
fileSuffix: string
|
||||
fileUpload?: string
|
||||
filePath?: string
|
||||
fileService: string
|
||||
createTime?: string
|
||||
createBy?: string
|
||||
updateTime?: string
|
||||
updateBy?: string
|
||||
}
|
||||
|
||||
// 文件搜索表单类型
|
||||
export interface SysFileSearchForm {
|
||||
fileName?: string
|
||||
fileSuffix?: string
|
||||
fileService?: string
|
||||
fileType?: string
|
||||
}
|
||||
|
||||
// 分页结果类型
|
||||
export interface PageSysFileVo {
|
||||
records: SysFileVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 文件上传结果类型
|
||||
export interface FileUploadResult {
|
||||
fileName: string
|
||||
newName: string
|
||||
fileSize: string
|
||||
suffixName: string
|
||||
filePath: string
|
||||
fileUploadPath: string
|
||||
}
|
||||
|
||||
// 文件服务类型枚举
|
||||
export enum FileServiceType {
|
||||
LOCAL = 'LOCAL', // 本地存储
|
||||
MINIO = 'MINIO', // MinIO对象存储
|
||||
OSS = 'OSS', // 阿里云对象存储
|
||||
}
|
||||
|
||||
// 文件服务类型映射(用于数据库存储)
|
||||
export const FileServiceTypeMapping = {
|
||||
LOCAL: '1',
|
||||
MINIO: '2',
|
||||
OSS: '3',
|
||||
} as const
|
||||
|
||||
// 文件类型枚举
|
||||
export enum FileTypeEnum {
|
||||
ALL = '0', // 全部
|
||||
IMAGE = '1', // 图片
|
||||
DOCUMENT = '2', // 文档
|
||||
AUDIO = '3', // 音频
|
||||
VIDEO = '4', // 视频
|
||||
ARCHIVE = '5', // 压缩包
|
||||
APPLICATION = '6', // 应用程序
|
||||
OTHER = '9', // 其他
|
||||
}
|
||||
|
||||
// 文件类型选项
|
||||
export const FILE_TYPE_OPTIONS = [
|
||||
{ label: '全部', value: FileTypeEnum.ALL },
|
||||
{ label: '图片', value: FileTypeEnum.IMAGE },
|
||||
{ label: '文档', value: FileTypeEnum.DOCUMENT },
|
||||
{ label: '音频', value: FileTypeEnum.AUDIO },
|
||||
{ label: '视频', value: FileTypeEnum.VIDEO },
|
||||
{ label: '压缩包', value: FileTypeEnum.ARCHIVE },
|
||||
{ label: '应用程序', value: FileTypeEnum.APPLICATION },
|
||||
{ label: '其他', value: FileTypeEnum.OTHER },
|
||||
]
|
||||
|
||||
// 文件服务类型选项
|
||||
export const FILE_SERVICE_OPTIONS = [
|
||||
{ label: '本地存储', value: FileServiceType.LOCAL },
|
||||
{ label: 'MinIO存储', value: FileServiceType.MINIO },
|
||||
{ label: '阿里云OSS', value: FileServiceType.OSS },
|
||||
]
|
||||
|
||||
// 文件服务类型数据库值选项(用于搜索)
|
||||
export const FILE_SERVICE_DB_OPTIONS = [
|
||||
{ label: '本地存储', value: '1' },
|
||||
{ label: 'MinIO存储', value: '2' },
|
||||
{ label: '阿里云OSS', value: '3' },
|
||||
]
|
||||
54
src/service/api/system/loginlog/index.ts
Normal file
54
src/service/api/system/loginlog/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { request } from '../../../http'
|
||||
import type { LoginLogForm, LoginLogQueryBo, LoginLogVo } from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type { LoginLogForm, LoginLogQueryBo, LoginLogVo } from './types'
|
||||
|
||||
/**
|
||||
* 分页查询登录日志列表
|
||||
*/
|
||||
export function getLoginLogListPage(params: LoginLogQueryBo) {
|
||||
return request.Get<Service.ResponseResult<Service.PageResult<LoginLogVo>>>('/coder/sysLoginLog/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询登录日志列表
|
||||
*/
|
||||
export function getLoginLogList() {
|
||||
return request.Get<Service.ResponseResult<LoginLogVo[]>>('/coder/sysLoginLog/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询登录日志详情
|
||||
*/
|
||||
export function getLoginLogById(id: number) {
|
||||
return request.Get<Service.ResponseResult<LoginLogVo>>(`/coder/sysLoginLog/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增登录日志
|
||||
*/
|
||||
export function addLoginLog(data: LoginLogForm) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysLoginLog/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改登录日志
|
||||
*/
|
||||
export function updateLoginLog(data: LoginLogForm) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysLoginLog/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除登录日志
|
||||
*/
|
||||
export function deleteLoginLog(id: number) {
|
||||
return request.Post<Service.ResponseResult<null>>(`/coder/sysLoginLog/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除登录日志
|
||||
*/
|
||||
export function batchDeleteLoginLog(ids: number[]) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysLoginLog/batchDelete', ids)
|
||||
}
|
||||
42
src/service/api/system/loginlog/types.ts
Normal file
42
src/service/api/system/loginlog/types.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 登录日志模块类型定义
|
||||
*/
|
||||
|
||||
// 登录日志查询参数
|
||||
export interface LoginLogQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
loginName?: string
|
||||
loginIp?: string
|
||||
loginStatus?: string
|
||||
deviceName?: string
|
||||
beginTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
|
||||
// 登录日志响应数据
|
||||
export interface LoginLogVo {
|
||||
loginLogId: number
|
||||
loginName: string
|
||||
deviceName?: string
|
||||
loginIp: string
|
||||
loginAddress?: string
|
||||
browser?: string
|
||||
os?: string
|
||||
loginStatus: string
|
||||
message?: string
|
||||
loginTime: string
|
||||
}
|
||||
|
||||
// 登录日志表单数据
|
||||
export interface LoginLogForm {
|
||||
loginLogId?: number
|
||||
loginName: string
|
||||
deviceName?: string
|
||||
loginIp: string
|
||||
loginLocation?: string
|
||||
loginBrowser?: string
|
||||
loginOs?: string
|
||||
loginStatus: string
|
||||
loginMsg?: string
|
||||
}
|
||||
138
src/service/api/system/menu/index.ts
Normal file
138
src/service/api/system/menu/index.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
MenuCascaderBo,
|
||||
MenuForm,
|
||||
MenuNormalResponse,
|
||||
MenuQueryBo,
|
||||
MenuRouterBo,
|
||||
MenuVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
MenuCascaderBo,
|
||||
MenuForm,
|
||||
MenuNormalResponse,
|
||||
MenuQueryBo,
|
||||
MenuRouterBo,
|
||||
MenuVo,
|
||||
RoleMenuPermissionBo,
|
||||
} from './types'
|
||||
|
||||
// 兼容性类型别名
|
||||
export interface MenuPermissionData extends MenuNormalResponse {}
|
||||
|
||||
// 菜单管理基础API
|
||||
|
||||
/**
|
||||
* 分页查询菜单列表
|
||||
*/
|
||||
export function getMenuListPage(params: MenuQueryBo) {
|
||||
return request.Get<Service.ResponseResult<Service.PageResult<MenuVo>>>('/coder/sysMenu/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询菜单列表
|
||||
*/
|
||||
export function getMenuList(params?: MenuQueryBo) {
|
||||
return request.Get<Service.ResponseResult<MenuVo[]>>('/coder/sysMenu/list', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询菜单详情
|
||||
*/
|
||||
export function getMenuById(id: string) {
|
||||
return request.Get<Service.ResponseResult<MenuVo>>(`/coder/sysMenu/getById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增菜单
|
||||
*/
|
||||
export function addMenu(data: MenuForm) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysMenu/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单
|
||||
*/
|
||||
export function updateMenu(data: MenuForm) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysMenu/update', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*/
|
||||
export function deleteMenu(id: string) {
|
||||
return request.Post<Service.ResponseResult<null>>(`/coder/sysMenu/deleteById/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除菜单
|
||||
*/
|
||||
export function batchDeleteMenu(ids: string[]) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysMenu/batchDelete', ids)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单状态
|
||||
*/
|
||||
export function updateMenuStatus(id: string, menuStatus: string) {
|
||||
return request.Post<Service.ResponseResult<null>>(`/coder/sysMenu/updateStatus/${id}/${menuStatus}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单展开状态
|
||||
*/
|
||||
export function updateMenuSpread(id: string, isSpread: string) {
|
||||
return request.Post<Service.ResponseResult<null>>(`/coder/sysMenu/updateSpread/${id}/${isSpread}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单级联下拉框数据
|
||||
*/
|
||||
export function getMenuCascaderList() {
|
||||
return request.Get<Service.ResponseResult<MenuCascaderBo[]>>('/coder/sysMenu/cascaderList')
|
||||
}
|
||||
|
||||
// 菜单路由管理相关API
|
||||
|
||||
/**
|
||||
* 获取用户动态路由信息
|
||||
*/
|
||||
export function getUserRoutes() {
|
||||
return request.Get<Service.ResponseResult<MenuRouterBo[]>>('/coder/sysMenu/listRouters')
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有正常的菜单和展开节点(角色分配菜单权限使用)
|
||||
*/
|
||||
export function getMenuPermissionData() {
|
||||
return request.Get<Service.ResponseResult<MenuNormalResponse>>('/coder/sysMenu/listMenuNormal')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色ID查询菜单权限ID列表
|
||||
*/
|
||||
export function getMenuIdsByRoleId(roleId: string) {
|
||||
return request.Get<Service.ResponseResult<string[]>>(`/coder/sysMenu/listMenuIdsByRoleId/${roleId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存角色和菜单权限之间的关系
|
||||
*/
|
||||
export function saveRoleMenuPermission(roleId: string, menuIds: string[]) {
|
||||
// 空数组时传递 ["-1"] 表示取消所有权限
|
||||
// 保持字符串格式避免精度丢失
|
||||
const menuIdsStr = menuIds.length > 0 ? menuIds : ['-1']
|
||||
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysMenu/saveRoleMenu', {
|
||||
roleId,
|
||||
menuIds: menuIdsStr,
|
||||
})
|
||||
}
|
||||
|
||||
// 兼容性导出 - 保持原有函数名以确保向后兼容
|
||||
export const fetchUserRoutes = getUserRoutes
|
||||
export const fetchMenuPermissionData = getMenuPermissionData
|
||||
export const fetchMenuIdsByRoleId = getMenuIdsByRoleId
|
||||
export const saveRoleMenu = saveRoleMenuPermission
|
||||
103
src/service/api/system/menu/types.ts
Normal file
103
src/service/api/system/menu/types.ts
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 菜单管理模块类型定义
|
||||
*/
|
||||
|
||||
// 菜单查询参数
|
||||
export interface MenuQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
menuName?: string
|
||||
menuStatus?: string
|
||||
auth?: string
|
||||
}
|
||||
|
||||
// 菜单响应数据
|
||||
export interface MenuVo {
|
||||
menuId: string // 改为字符串避免大整数精度丢失
|
||||
menuName: string
|
||||
enName?: string
|
||||
parentId: string // 改为字符串避免大整数精度丢失
|
||||
menuType: string
|
||||
path?: string
|
||||
name?: string
|
||||
component?: string
|
||||
icon?: string
|
||||
auth?: string
|
||||
menuStatus: string
|
||||
activeMenu?: string
|
||||
isHide: string
|
||||
isLink?: string
|
||||
isKeepAlive: string
|
||||
isFull?: string
|
||||
isAffix: string
|
||||
isSpread: string
|
||||
sorted: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
children?: MenuVo[]
|
||||
}
|
||||
|
||||
// 菜单表单数据
|
||||
export interface MenuForm {
|
||||
menuId?: string // 改为字符串避免大整数精度丢失
|
||||
menuName: string
|
||||
enName?: string
|
||||
parentId: string // 改为字符串避免大整数精度丢失
|
||||
menuType: string
|
||||
path?: string
|
||||
name?: string
|
||||
component?: string
|
||||
icon?: string
|
||||
auth?: string
|
||||
menuStatus: string
|
||||
activeMenu?: string
|
||||
isHide: string
|
||||
isLink?: string
|
||||
isKeepAlive: string
|
||||
isFull?: string
|
||||
isAffix: string
|
||||
isSpread: string
|
||||
sorted: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 菜单路由数据
|
||||
export interface MenuRouterBo {
|
||||
menuId: string // 改为字符串避免大整数精度丢失
|
||||
menuName: string
|
||||
path: string
|
||||
component?: string
|
||||
redirect?: string
|
||||
meta?: {
|
||||
title: string
|
||||
icon?: string
|
||||
auth?: string
|
||||
roles?: string[]
|
||||
keepAlive?: boolean
|
||||
affix?: boolean
|
||||
hide?: boolean
|
||||
}
|
||||
children?: MenuRouterBo[]
|
||||
}
|
||||
|
||||
// 级联选择器数据
|
||||
export interface MenuCascaderBo {
|
||||
value: string // 改为字符串避免大整数精度丢失
|
||||
label: string
|
||||
children?: MenuCascaderBo[]
|
||||
}
|
||||
|
||||
// 菜单正常数据返回结构
|
||||
export interface MenuNormalResponse {
|
||||
menuList: MenuVo[]
|
||||
spreadList: string[] // 改为字符串数组避免大整数精度丢失
|
||||
}
|
||||
|
||||
// 角色菜单权限分配请求参数
|
||||
export interface RoleMenuPermissionBo {
|
||||
roleId: string // 字符串格式避免精度丢失
|
||||
menuIds: string[] // 字符串格式避免精度丢失
|
||||
}
|
||||
61
src/service/api/system/operlog/index.ts
Normal file
61
src/service/api/system/operlog/index.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { request } from '../../../http'
|
||||
import type { OperLogQueryBo, OperLogVo } from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type { OperLogForm, OperLogQueryBo, OperLogSearchForm, OperLogVo } from './types'
|
||||
|
||||
/**
|
||||
* 分页查询操作日志列表
|
||||
*/
|
||||
export function getOperLogListPage(params: OperLogQueryBo) {
|
||||
return request.Get<Service.ResponseResult<Service.PageResult<OperLogVo>>>('/coder/sysOperLog/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询操作日志详情
|
||||
*/
|
||||
export function getOperLogById(operId: number) {
|
||||
return request.Get<Service.ResponseResult<OperLogVo>>(`/coder/sysOperLog/getById/${operId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询操作日志详情
|
||||
*/
|
||||
export function getOperLogDetailById(operId: number) {
|
||||
return request.Get<Service.ResponseResult<OperLogVo>>(`/coder/sysOperLog/getDetailById/${operId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除操作日志
|
||||
*/
|
||||
export function deleteOperLog(operId: number) {
|
||||
return request.Post<Service.ResponseResult<null>>(`/coder/sysOperLog/deleteById/${operId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除操作日志
|
||||
*/
|
||||
export function batchDeleteOperLog(operIds: number[]) {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysOperLog/batchDelete', operIds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空操作日志
|
||||
*/
|
||||
export function clearOperLog() {
|
||||
return request.Post<Service.ResponseResult<null>>('/coder/sysOperLog/clear')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作统计
|
||||
*/
|
||||
export function getOperLogStatistics() {
|
||||
return request.Get<Service.ResponseResult<Record<string, any>>>('/coder/sysOperLog/statistics')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取仪表盘统计
|
||||
*/
|
||||
export function getOperLogDashboard() {
|
||||
return request.Get<Service.ResponseResult<Record<string, any>>>('/coder/sysOperLog/dashboard')
|
||||
}
|
||||
71
src/service/api/system/operlog/types.ts
Normal file
71
src/service/api/system/operlog/types.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 操作日志模块类型定义
|
||||
*/
|
||||
|
||||
// 操作日志查询参数
|
||||
export interface OperLogQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
operName?: string // 操作名称
|
||||
operMan?: string // 操作人员
|
||||
operType?: string // 操作类型
|
||||
operStatus?: string // 操作状态 (0成功 1失败)
|
||||
operUrl?: string // 请求URL
|
||||
requestMethod?: string // 请求方式
|
||||
operIp?: string // 操作IP
|
||||
beginTime?: string // 开始时间
|
||||
endTime?: string // 结束时间
|
||||
}
|
||||
|
||||
// 操作日志响应数据
|
||||
export interface OperLogVo {
|
||||
operId: number // 操作主键
|
||||
operName: string // 操作名称
|
||||
operType: string // 操作类型
|
||||
methodName: string // 方法名称
|
||||
requestMethod?: string // 请求方式
|
||||
systemType?: string // 系统类型
|
||||
operMan: string // 操作人员
|
||||
operUrl: string // 请求URL
|
||||
operIp: string // 主机地址
|
||||
operLocation?: string // 操作地点
|
||||
operParam?: string // 请求参数
|
||||
jsonResult?: string // 返回参数
|
||||
operStatus: string // 操作状态 (0成功 1失败)
|
||||
errorMsg?: string // 错误消息
|
||||
operTime: string // 操作时间
|
||||
costTime?: string // 消耗时间
|
||||
}
|
||||
|
||||
// 操作日志表单数据
|
||||
export interface OperLogForm {
|
||||
operId?: number
|
||||
operName: string
|
||||
operType: string
|
||||
methodName: string
|
||||
requestMethod?: string
|
||||
systemType?: string
|
||||
operMan: string
|
||||
operUrl: string
|
||||
operIp: string
|
||||
operLocation?: string
|
||||
operParam?: string
|
||||
jsonResult?: string
|
||||
operStatus: string
|
||||
errorMsg?: string
|
||||
costTime?: string
|
||||
}
|
||||
|
||||
// 操作日志搜索表单
|
||||
export interface OperLogSearchForm {
|
||||
operName?: string
|
||||
operMan?: string
|
||||
operType?: string
|
||||
operStatus?: string | null
|
||||
operUrl?: string
|
||||
requestMethod?: string
|
||||
operIp?: string
|
||||
timeRange?: [number, number] | null
|
||||
beginTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
94
src/service/api/system/picture/index.ts
Normal file
94
src/service/api/system/picture/index.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
PageSysPictureVo,
|
||||
PictureUploadResult,
|
||||
SysPictureQueryBo,
|
||||
SysPictureVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
PageSysPictureVo,
|
||||
PictureServiceType,
|
||||
PictureTypeEnum,
|
||||
PictureUploadResult,
|
||||
SysPictureQueryBo,
|
||||
SysPictureSearchForm,
|
||||
SysPictureVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出常量值供外部使用
|
||||
export {
|
||||
PICTURE_SERVICE_OPTIONS,
|
||||
PICTURE_TYPE_OPTIONS,
|
||||
} from './types'
|
||||
|
||||
// 图库管理相关API
|
||||
|
||||
// 分页查询图片列表
|
||||
export function getSysPictureList(params: SysPictureQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageSysPictureVo>>('/coder/sysPicture/listPage', { params })
|
||||
}
|
||||
|
||||
// 查询所有图片(不分页)
|
||||
export function getAllSysPictures(params?: Omit<SysPictureQueryBo, 'pageNo' | 'pageSize'>) {
|
||||
return request.Get<Service.ResponseResult<SysPictureVo[]>>('/coder/sysPicture/list', { params })
|
||||
}
|
||||
|
||||
// 根据ID查询图片
|
||||
export function getSysPictureById(id: number) {
|
||||
return request.Get<Service.ResponseResult<SysPictureVo>>(`/coder/sysPicture/getById/${id}`)
|
||||
}
|
||||
|
||||
// 新增图片记录
|
||||
export function addSysPicture(data: SysPictureVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysPicture/add', data)
|
||||
}
|
||||
|
||||
// 修改图片信息
|
||||
export function updateSysPicture(data: SysPictureVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysPicture/update', data)
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
export function deleteSysPicture(id: number) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysPicture/deleteById/${id}`)
|
||||
}
|
||||
|
||||
// 批量删除图片
|
||||
export function batchDeleteSysPictures(ids: number[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysPicture/batchDelete', ids)
|
||||
}
|
||||
|
||||
// 图片上传相关API
|
||||
|
||||
// 上传图片
|
||||
export function uploadPicture(file: File, pictureType = '9', fileSize = 2, storageType?: string) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 如果指定了存储类型,添加到表单数据中
|
||||
if (storageType) {
|
||||
formData.append('storageType', storageType)
|
||||
}
|
||||
|
||||
return request.Post<PictureUploadResult>(`/coder/file/uploadFile/${fileSize}/pictures/${pictureType}`, formData)
|
||||
}
|
||||
|
||||
// 匿名上传图片(无需登录)
|
||||
export function uploadAnyPicture(file: File, pictureType = '9', fileSize = 2, storageType?: string) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 如果指定了存储类型,添加到表单数据中
|
||||
if (storageType) {
|
||||
formData.append('storageType', storageType)
|
||||
}
|
||||
|
||||
return request.Post<PictureUploadResult>(`/coder/file/uploadAnyFile/${fileSize}/pictures/${pictureType}`, formData)
|
||||
}
|
||||
|
||||
// 兼容性导出 - 保持原有函数名以确保向后兼容
|
||||
export const fetchSysPicturePage = getSysPictureList
|
||||
export const fetchAllSysPictures = getAllSysPictures
|
||||
export const fetchSysPictureById = getSysPictureById
|
||||
95
src/service/api/system/picture/types.ts
Normal file
95
src/service/api/system/picture/types.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 图库管理相关类型定义
|
||||
*/
|
||||
|
||||
// 图片查询条件类型
|
||||
export interface SysPictureQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
pictureName?: string
|
||||
pictureSuffix?: string
|
||||
pictureService?: string
|
||||
pictureType?: string
|
||||
}
|
||||
|
||||
// 图片信息类型
|
||||
export interface SysPictureVo {
|
||||
pictureId?: number
|
||||
pictureName: string
|
||||
newName?: string
|
||||
pictureSize?: string
|
||||
pictureSuffix: string
|
||||
pictureUpload?: string
|
||||
picturePath?: string
|
||||
pictureService: string
|
||||
pictureType: string
|
||||
createTime?: string
|
||||
createBy?: string
|
||||
updateTime?: string
|
||||
updateBy?: string
|
||||
}
|
||||
|
||||
// 图片搜索表单类型
|
||||
export interface SysPictureSearchForm {
|
||||
pictureName?: string
|
||||
pictureSuffix?: string
|
||||
pictureService?: string
|
||||
pictureType?: string
|
||||
}
|
||||
|
||||
// 分页结果类型
|
||||
export interface PageSysPictureVo {
|
||||
records: SysPictureVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 图片上传结果类型
|
||||
export interface PictureUploadResult {
|
||||
fileName: string
|
||||
newName: string
|
||||
fileSize: string
|
||||
suffixName: string
|
||||
filePath: string
|
||||
fileUploadPath: string
|
||||
}
|
||||
|
||||
// 图片服务类型枚举
|
||||
export enum PictureServiceType {
|
||||
LOCAL = '1', // 本地存储
|
||||
MINIO = '2', // MinIO对象存储
|
||||
OSS = '3', // 阿里云对象存储
|
||||
}
|
||||
|
||||
// 图片类型枚举
|
||||
export enum PictureTypeEnum {
|
||||
ALL = '0', // 全部数据
|
||||
USER_AVATAR = '1', // 用户头像
|
||||
ANIMATION = '2', // 动漫分类
|
||||
BEAUTY = '3', // 美女分类
|
||||
SCENERY = '4', // 风景分类
|
||||
STAR = '5', // 明星分类
|
||||
ANIMAL = '6', // 动物分类
|
||||
OTHER = '9', // 其他分类
|
||||
}
|
||||
|
||||
// 图片类型选项
|
||||
export const PICTURE_TYPE_OPTIONS = [
|
||||
{ label: '全部数据', value: PictureTypeEnum.ALL },
|
||||
{ label: '用户头像', value: PictureTypeEnum.USER_AVATAR },
|
||||
{ label: '动漫分类', value: PictureTypeEnum.ANIMATION },
|
||||
{ label: '美女分类', value: PictureTypeEnum.BEAUTY },
|
||||
{ label: '风景分类', value: PictureTypeEnum.SCENERY },
|
||||
{ label: '明星分类', value: PictureTypeEnum.STAR },
|
||||
{ label: '动物分类', value: PictureTypeEnum.ANIMAL },
|
||||
{ label: '其他分类', value: PictureTypeEnum.OTHER },
|
||||
]
|
||||
|
||||
// 图片服务类型选项
|
||||
export const PICTURE_SERVICE_OPTIONS = [
|
||||
{ label: '本地存储', value: PictureServiceType.LOCAL },
|
||||
{ label: 'MinIO存储', value: PictureServiceType.MINIO },
|
||||
{ label: '阿里云OSS', value: PictureServiceType.OSS },
|
||||
]
|
||||
86
src/service/api/system/role/index.ts
Normal file
86
src/service/api/system/role/index.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
RoleForm,
|
||||
RoleQueryBo,
|
||||
RoleSelectVo,
|
||||
RoleTransferVo,
|
||||
RoleVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
RoleForm,
|
||||
RoleQueryBo,
|
||||
RoleSearchForm,
|
||||
RoleSelectVo,
|
||||
RoleTransferVo,
|
||||
RoleVo,
|
||||
} from './types'
|
||||
|
||||
// 角色管理相关API
|
||||
|
||||
// 分页查询角色列表
|
||||
export function getRolePage(params: RoleQueryBo) {
|
||||
return request.Get<Service.ResponseResult<Service.PageResult<RoleVo>>>('/coder/sysRole/listPage', { params })
|
||||
}
|
||||
|
||||
// 获取所有角色列表
|
||||
export function getRoleList() {
|
||||
return request.Get<Service.ResponseResult<RoleVo[]>>('/coder/sysRole/list')
|
||||
}
|
||||
|
||||
// 根据ID查询角色详情
|
||||
export function getRoleById(id: number) {
|
||||
return request.Get<Service.ResponseResult<RoleVo>>(`/coder/sysRole/getById/${id}`)
|
||||
}
|
||||
|
||||
// 新增角色
|
||||
export function addRole(data: RoleForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysRole/add', data)
|
||||
}
|
||||
|
||||
// 修改角色
|
||||
export function updateRole(data: RoleForm) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysRole/update', data)
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
export function deleteRole(id: number) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysRole/deleteById/${id}`)
|
||||
}
|
||||
|
||||
// 批量删除角色
|
||||
export function batchDeleteRoles(ids: number[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysRole/batchDelete', ids)
|
||||
}
|
||||
|
||||
// 修改角色状态
|
||||
export function updateRoleStatus(roleId: number, roleStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysRole/updateStatus/${roleId}/${roleStatus}`)
|
||||
}
|
||||
|
||||
// 获取最新排序号
|
||||
export function getRoleSorted() {
|
||||
return request.Get<Service.ResponseResult<number>>('/coder/sysRole/getSorted')
|
||||
}
|
||||
|
||||
// 获取角色下拉框数据
|
||||
export function getRoleElSelect() {
|
||||
return request.Get<Service.ResponseResult<RoleSelectVo[]>>('/coder/sysRole/listRoleElSelect')
|
||||
}
|
||||
|
||||
// 查询正常角色穿梭框
|
||||
export function getNormalRoleForUser(userId: number) {
|
||||
return request.Get<Service.ResponseResult<RoleTransferVo>>(`/coder/sysRole/listNormalRole/${userId}`)
|
||||
}
|
||||
|
||||
// 分配用户角色
|
||||
export function assignUserRole(userId: number, roleIds: string) {
|
||||
return request.Get<Service.ResponseResult<string>>(`/coder/sysRole/assignUserRole/${userId}/${roleIds}`)
|
||||
}
|
||||
|
||||
// 兼容性导出 - 保持原有函数名以确保向后兼容
|
||||
export const fetchRoleList = getRoleList
|
||||
export const fetchRoleElSelect = getRoleElSelect
|
||||
export const fetchNormalRoleForUser = getNormalRoleForUser
|
||||
export const fetchRolePage = getRolePage
|
||||
54
src/service/api/system/role/types.ts
Normal file
54
src/service/api/system/role/types.ts
Normal file
@ -0,0 +1,54 @@
|
||||
// 角色信息类型
|
||||
export interface RoleVo {
|
||||
roleId: number
|
||||
roleName: string
|
||||
roleCode: string
|
||||
roleStatus: string
|
||||
remark?: string
|
||||
sorted?: number
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
createBy?: string
|
||||
updateBy?: string
|
||||
}
|
||||
|
||||
// 角色查询参数类型
|
||||
export interface RoleQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
roleName?: string
|
||||
roleCode?: string
|
||||
roleStatus?: string
|
||||
beginTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
|
||||
// 角色搜索表单类型
|
||||
export interface RoleSearchForm {
|
||||
roleName?: string
|
||||
roleCode?: string
|
||||
roleStatus?: string | null
|
||||
timeRange?: [number, number] | null
|
||||
}
|
||||
|
||||
// 角色表单类型
|
||||
export interface RoleForm {
|
||||
roleId?: number
|
||||
roleName: string
|
||||
roleCode: string
|
||||
roleStatus: string
|
||||
remark: string
|
||||
sorted?: number
|
||||
}
|
||||
|
||||
// 角色选择器选项类型
|
||||
export interface RoleSelectVo {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
// 角色穿梭框选项类型
|
||||
export interface RoleTransferVo {
|
||||
data1: RoleSelectVo[]
|
||||
data2: number[]
|
||||
}
|
||||
121
src/service/api/system/user/index.ts
Normal file
121
src/service/api/system/user/index.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
PageUserVo,
|
||||
PasswordUpdateBo,
|
||||
PersonalDataVo,
|
||||
UserQueryBo,
|
||||
UserVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
PageUserVo,
|
||||
PasswordUpdateBo,
|
||||
PersonalDataVo,
|
||||
UserQueryBo,
|
||||
UserSearchForm,
|
||||
UserVo,
|
||||
} from './types'
|
||||
|
||||
// 用户管理相关API
|
||||
|
||||
// 分页查询用户列表
|
||||
export function getUserList(params: UserQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageUserVo>>('/coder/sysLoginUser/listPage', { params })
|
||||
}
|
||||
|
||||
// 查询所有用户(不分页)
|
||||
export function getAllUsers(params?: Omit<UserQueryBo, 'pageNo' | 'pageSize'>) {
|
||||
return request.Get<Service.ResponseResult<UserVo[]>>('/coder/sysLoginUser/list', { params })
|
||||
}
|
||||
|
||||
// 根据ID查询用户
|
||||
export function getUserById(id: number) {
|
||||
return request.Get<Service.ResponseResult<UserVo>>(`/coder/sysLoginUser/getById/${id}`)
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
export function addUser(data: UserVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/add', data)
|
||||
}
|
||||
|
||||
// 修改用户信息
|
||||
export function updateUser(data: UserVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/update', data)
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export function deleteUser(id: number) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysLoginUser/deleteById/${id}`)
|
||||
}
|
||||
|
||||
// 批量删除用户
|
||||
export function batchDeleteUsers(ids: number[]) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/batchDelete', ids)
|
||||
}
|
||||
|
||||
// 修改用户状态
|
||||
export function updateUserStatus(userId: number, userStatus: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysLoginUser/updateStatus/${userId}/${userStatus}`)
|
||||
}
|
||||
|
||||
// 重置用户密码
|
||||
export function resetUserPassword(id: number, password: string) {
|
||||
return request.Post<Service.ResponseResult<string>>(`/coder/sysLoginUser/resetPwd/${id}/${password}`)
|
||||
}
|
||||
|
||||
// 个人资料相关API
|
||||
|
||||
// 获取个人资料
|
||||
export function getPersonalData() {
|
||||
return request.Get<Service.ResponseResult<PersonalDataVo>>('/coder/sysLoginUser/getPersonalData')
|
||||
}
|
||||
|
||||
// 修改个人资料
|
||||
export function updatePersonalData(data: PersonalDataVo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/updateBasicData', data)
|
||||
}
|
||||
|
||||
// 修改登录密码
|
||||
export function updateUserPassword(data: PasswordUpdateBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/sysLoginUser/updateUserPwd', data)
|
||||
}
|
||||
|
||||
// 用户数据导入导出相关API
|
||||
|
||||
// 下载用户导入模板
|
||||
export function downloadExcelTemplate() {
|
||||
const method = request.Get<Blob>('/coder/sysLoginUser/downloadExcelTemplate')
|
||||
method.meta = {
|
||||
isBlob: true,
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
// 导出用户数据
|
||||
export function exportExcelData(params?: UserQueryBo) {
|
||||
const method = request.Get<Blob>('/coder/sysLoginUser/exportExcelData', { params })
|
||||
method.meta = {
|
||||
isBlob: true,
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
// 导入用户数据
|
||||
export function importUserData(file: File, updateSupport = false) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('updateSupport', String(updateSupport))
|
||||
|
||||
return request.Post<Service.ResponseResult<{
|
||||
total: number
|
||||
success: number
|
||||
failed: number
|
||||
message: string
|
||||
}>>('/coder/sysLoginUser/importExcelData', formData)
|
||||
}
|
||||
|
||||
// 兼容性导出 - 保持原有函数名以确保向后兼容
|
||||
export const fetchUserPage = getUserList
|
||||
export const fetchAllUsers = getAllUsers
|
||||
export const fetchUserById = getUserById
|
||||
73
src/service/api/system/user/types.ts
Normal file
73
src/service/api/system/user/types.ts
Normal file
@ -0,0 +1,73 @@
|
||||
// 用户查询参数类型
|
||||
export interface UserQueryBo {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
loginName?: string
|
||||
userName?: string
|
||||
userType?: string
|
||||
phone?: string
|
||||
sex?: string
|
||||
userStatus?: string
|
||||
beginTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
|
||||
// 用户信息类型 (完整的用户实体)
|
||||
export interface UserVo {
|
||||
userId: number
|
||||
loginName: string
|
||||
userName: string
|
||||
password?: string
|
||||
userType: string
|
||||
email?: string
|
||||
phone?: string
|
||||
sex?: string
|
||||
avatar?: string
|
||||
userStatus: string // 0-启用 1-停用
|
||||
loginIp?: string
|
||||
loginTime?: string
|
||||
pwdUpdateTime?: string
|
||||
remark?: string
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
roleIds?: number[]
|
||||
}
|
||||
|
||||
// 分页结果类型
|
||||
export interface PageUserVo {
|
||||
records: UserVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 个人资料类型
|
||||
export interface PersonalDataVo {
|
||||
userName: string
|
||||
email?: string
|
||||
phone?: string
|
||||
sex?: string
|
||||
avatar?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 密码修改类型
|
||||
export interface PasswordUpdateBo {
|
||||
oldPassword: string
|
||||
newPassword: string
|
||||
confirmPassword: string
|
||||
}
|
||||
|
||||
// 用户搜索表单类型
|
||||
export interface UserSearchForm {
|
||||
loginName?: string
|
||||
userName?: string
|
||||
phone?: string
|
||||
userStatus?: string
|
||||
beginTime?: string
|
||||
endTime?: string
|
||||
timeRange?: [number, number] | null
|
||||
}
|
||||
116
src/service/http/alova.ts
Normal file
116
src/service/http/alova.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { local } from '@/utils'
|
||||
import { coiMsgError } from '@/utils/coi'
|
||||
import { createAlova } from 'alova'
|
||||
import { createServerTokenAuthentication } from 'alova/client'
|
||||
import adapterFetch from 'alova/fetch'
|
||||
import VueHook from 'alova/vue'
|
||||
import type { VueHookType } from 'alova/vue'
|
||||
import {
|
||||
DEFAULT_ALOVA_OPTIONS,
|
||||
DEFAULT_BACKEND_OPTIONS,
|
||||
} from './config'
|
||||
import {
|
||||
handleBusinessError,
|
||||
handleRefreshToken,
|
||||
handleResponseError,
|
||||
handleServiceResult,
|
||||
} from './handle'
|
||||
|
||||
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<VueHookType>({
|
||||
// 服务端判定token过期
|
||||
refreshTokenOnSuccess: {
|
||||
// 当服务端返回401时,表示token过期
|
||||
isExpired: (response, method) => {
|
||||
const isExpired = method.meta && method.meta.isExpired
|
||||
return response.status === 401 && !isExpired
|
||||
},
|
||||
|
||||
// 当token过期时触发,在此函数中触发刷新token
|
||||
handler: async (_response, method) => {
|
||||
// 此处采取限制,防止过期请求无限循环重发
|
||||
if (!method.meta)
|
||||
method.meta = { isExpired: true }
|
||||
else
|
||||
method.meta.isExpired = true
|
||||
|
||||
await handleRefreshToken()
|
||||
},
|
||||
},
|
||||
// 添加token到请求头
|
||||
assignToken: (method) => {
|
||||
method.config.headers.Authorization = `Bearer ${local.get('accessToken')}`
|
||||
},
|
||||
})
|
||||
|
||||
// docs path of alova.js https://alova.js.org/
|
||||
export function createAlovaInstance(
|
||||
alovaConfig: Service.AlovaConfig,
|
||||
backendConfig?: Service.BackendConfig,
|
||||
) {
|
||||
const _backendConfig = { ...DEFAULT_BACKEND_OPTIONS, ...backendConfig }
|
||||
const _alovaConfig = { ...DEFAULT_ALOVA_OPTIONS, ...alovaConfig }
|
||||
|
||||
return createAlova({
|
||||
statesHook: VueHook,
|
||||
requestAdapter: adapterFetch(),
|
||||
cacheFor: null,
|
||||
baseURL: _alovaConfig.baseURL,
|
||||
timeout: _alovaConfig.timeout,
|
||||
|
||||
beforeRequest: onAuthRequired((method) => {
|
||||
if (method.meta?.isFormPost) {
|
||||
method.config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
method.data = new URLSearchParams(method.data as URLSearchParams).toString()
|
||||
}
|
||||
alovaConfig.beforeRequest?.(method)
|
||||
}),
|
||||
responded: onResponseRefreshToken({
|
||||
// 请求成功的拦截器
|
||||
onSuccess: async (response, method) => {
|
||||
const { status } = response
|
||||
|
||||
if (status === 200) {
|
||||
// 返回blob数据
|
||||
if (method.meta?.isBlob)
|
||||
return response.blob()
|
||||
|
||||
// 返回json数据
|
||||
const apiData = await response.json()
|
||||
// 请求成功
|
||||
if (apiData[_backendConfig.codeKey] === _backendConfig.successCode)
|
||||
return handleServiceResult(apiData)
|
||||
|
||||
// 业务请求失败
|
||||
const errorResult = handleBusinessError(apiData, _backendConfig)
|
||||
return handleServiceResult(errorResult, false)
|
||||
}
|
||||
// 接口请求失败
|
||||
const errorResult = await handleResponseError(response)
|
||||
return handleServiceResult(errorResult, false)
|
||||
},
|
||||
onError: (error, _method) => {
|
||||
// 根据错误类型提供更友好的提示
|
||||
let userMessage = '网络请求失败,请稍后重试'
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
userMessage = '请求已取消'
|
||||
}
|
||||
else if (error.name === 'TimeoutError') {
|
||||
userMessage = '请求超时,请检查网络连接'
|
||||
}
|
||||
else if (error.message.includes('fetch')) {
|
||||
userMessage = '网络连接失败,请检查网络状态'
|
||||
}
|
||||
else if (error.message.includes('413')) {
|
||||
userMessage = '文件大小超出限制,请选择较小的文件'
|
||||
}
|
||||
|
||||
coiMsgError(userMessage)
|
||||
},
|
||||
|
||||
onComplete: async (_method) => {
|
||||
// 处理请求完成逻辑
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
36
src/service/http/config.ts
Normal file
36
src/service/http/config.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { $t } from '@/utils'
|
||||
|
||||
/** 默认实例的Aixos配置 */
|
||||
export const DEFAULT_ALOVA_OPTIONS = {
|
||||
// 请求超时时间,默认15秒
|
||||
timeout: 15 * 1000,
|
||||
}
|
||||
|
||||
/** 默认实例的后端字段配置 */
|
||||
export const DEFAULT_BACKEND_OPTIONS = {
|
||||
codeKey: 'code',
|
||||
dataKey: 'data',
|
||||
msgKey: 'msg',
|
||||
successCode: 1,
|
||||
}
|
||||
|
||||
/** 请求不成功各种状态的错误 */
|
||||
export const ERROR_STATUS = {
|
||||
default: $t('http.defaultTip'),
|
||||
400: $t('http.400'),
|
||||
401: $t('http.401'),
|
||||
403: $t('http.403'),
|
||||
404: $t('http.404'),
|
||||
405: $t('http.405'),
|
||||
408: $t('http.408'),
|
||||
413: '文件大小超出限制,请选择较小的文件',
|
||||
500: $t('http.500'),
|
||||
501: $t('http.501'),
|
||||
502: $t('http.502'),
|
||||
503: $t('http.503'),
|
||||
504: $t('http.504'),
|
||||
505: $t('http.505'),
|
||||
}
|
||||
|
||||
/** 没有错误提示的code */
|
||||
export const ERROR_NO_TIP_STATUS = [10000]
|
||||
145
src/service/http/handle.ts
Normal file
145
src/service/http/handle.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { fetchUpdateToken } from '@/service/api/auth'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { local } from '@/utils'
|
||||
import { coiMsgError } from '@/utils/coi'
|
||||
import {
|
||||
ERROR_NO_TIP_STATUS,
|
||||
ERROR_STATUS,
|
||||
} from './config'
|
||||
|
||||
type ErrorStatus = keyof typeof ERROR_STATUS
|
||||
|
||||
/**
|
||||
* @description: 处理请求成功,但返回后端服务器报错
|
||||
* @param {Response} response
|
||||
* @return {*}
|
||||
*/
|
||||
export async function handleResponseError(response: Response) {
|
||||
const error: Service.RequestError = {
|
||||
errorType: 'Response Error',
|
||||
code: 0,
|
||||
message: ERROR_STATUS.default,
|
||||
data: null,
|
||||
}
|
||||
const errorCode: ErrorStatus = response.status as ErrorStatus
|
||||
let message = ERROR_STATUS[errorCode] || ERROR_STATUS.default
|
||||
|
||||
// 尝试解析响应体中的具体错误信息
|
||||
try {
|
||||
const responseText = await response.text()
|
||||
if (responseText) {
|
||||
try {
|
||||
const responseData = JSON.parse(responseText)
|
||||
// 如果后端返回了具体的错误信息,优先使用
|
||||
if (responseData && responseData.msg) {
|
||||
message = responseData.msg
|
||||
}
|
||||
else if (responseData && responseData.message) {
|
||||
message = responseData.message
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 如果不是JSON格式,可能是HTML错误页面,使用预定义消息
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 读取错误响应失败,使用默认错误消息
|
||||
}
|
||||
|
||||
Object.assign(error, { code: errorCode, message })
|
||||
|
||||
// 检查是否是401未授权错误,直接执行登出
|
||||
if (response.status === 401) {
|
||||
const authStore = useAuthStore()
|
||||
authStore.logout()
|
||||
return error
|
||||
}
|
||||
|
||||
showError(error)
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @param {Record} data 接口返回的后台数据
|
||||
* @param {Service} config 后台字段配置
|
||||
* @return {*}
|
||||
*/
|
||||
export function handleBusinessError(data: Record<string, any>, config: Required<Service.BackendConfig>) {
|
||||
const { codeKey, msgKey } = config
|
||||
const error: Service.RequestError = {
|
||||
errorType: 'Business Error',
|
||||
code: data[codeKey],
|
||||
message: data[msgKey],
|
||||
data: data.data,
|
||||
}
|
||||
|
||||
// 检查是否是token失效的业务错误
|
||||
const errorMessage = data[msgKey] || ''
|
||||
const isTokenExpired = errorMessage.includes('token 无效')
|
||||
|| errorMessage.includes('Token无效')
|
||||
|| errorMessage.includes('未提供有效的Token')
|
||||
|| errorMessage.includes('登录已过期')
|
||||
|| errorMessage.includes('未登录')
|
||||
|
||||
if (isTokenExpired) {
|
||||
// Token失效,执行登出逻辑
|
||||
const authStore = useAuthStore()
|
||||
authStore.logout()
|
||||
return error
|
||||
}
|
||||
|
||||
showError(error)
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 统一成功和失败返回类型
|
||||
* @param {any} data
|
||||
* @param {boolean} isSuccess
|
||||
* @return {*} result
|
||||
*/
|
||||
export function handleServiceResult(data: any, isSuccess: boolean = true) {
|
||||
const result = {
|
||||
isSuccess,
|
||||
errorType: null,
|
||||
...data,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 处理接口token刷新
|
||||
* @return {*}
|
||||
*/
|
||||
export async function handleRefreshToken() {
|
||||
const authStore = useAuthStore()
|
||||
const isAutoRefresh = import.meta.env.VITE_AUTO_REFRESH_TOKEN === 'Y'
|
||||
if (!isAutoRefresh) {
|
||||
await authStore.logout()
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新token
|
||||
const { data } = await fetchUpdateToken({ refreshToken: local.get('refreshToken') })
|
||||
if (data) {
|
||||
local.set('accessToken', data.accessToken)
|
||||
local.set('refreshToken', data.refreshToken)
|
||||
}
|
||||
else {
|
||||
// 刷新失败,退出
|
||||
await authStore.logout()
|
||||
}
|
||||
}
|
||||
|
||||
export function showError(error: Service.RequestError) {
|
||||
// 如果error不需要提示,则跳过
|
||||
const code = Number(error.code)
|
||||
if (ERROR_NO_TIP_STATUS.includes(code))
|
||||
return
|
||||
|
||||
coiMsgError(error.message)
|
||||
}
|
||||
15
src/service/http/index.ts
Normal file
15
src/service/http/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { generateProxyPattern } from '@/../build/proxy'
|
||||
import { serviceConfig } from '@/../service.config'
|
||||
import { createAlovaInstance } from './alova'
|
||||
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'
|
||||
|
||||
const { url } = generateProxyPattern(serviceConfig[import.meta.env.MODE])
|
||||
|
||||
export const request = createAlovaInstance({
|
||||
baseURL: isHttpProxy ? url.proxy : url.value,
|
||||
})
|
||||
|
||||
export const blankInstance = createAlovaInstance({
|
||||
baseURL: '',
|
||||
})
|
||||
137
src/store/app/index.ts
Normal file
137
src/store/app/index.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
import { local, setLocale } from '@/utils'
|
||||
import { colord } from 'colord'
|
||||
import { set } from 'radash'
|
||||
import themeConfig from './theme.json'
|
||||
|
||||
export type TransitionAnimation = '' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
|
||||
export type LayoutMode = 'leftMenu' | 'topMenu' | 'mixMenu'
|
||||
|
||||
const { VITE_DEFAULT_LANG, VITE_COPYRIGHT_INFO } = import.meta.env
|
||||
|
||||
const docEle = ref(document.documentElement)
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen(docEle)
|
||||
|
||||
const { system, store } = useColorMode({
|
||||
emitAuto: true,
|
||||
})
|
||||
|
||||
export const useAppStore = defineStore('app-store', {
|
||||
state: () => {
|
||||
return {
|
||||
footerText: VITE_COPYRIGHT_INFO,
|
||||
lang: VITE_DEFAULT_LANG,
|
||||
theme: themeConfig as GlobalThemeOverrides,
|
||||
primaryColor: themeConfig.common.primaryColor,
|
||||
collapsed: false,
|
||||
grayMode: false,
|
||||
colorWeak: false,
|
||||
loadFlag: true,
|
||||
showLogo: true,
|
||||
showTabs: true,
|
||||
showFooter: true,
|
||||
showProgress: true,
|
||||
showBreadcrumb: true,
|
||||
showBreadcrumbIcon: true,
|
||||
showSetting: false,
|
||||
transitionAnimation: 'zoom-out' as TransitionAnimation,
|
||||
layoutMode: 'leftMenu' as LayoutMode,
|
||||
contentFullScreen: false,
|
||||
menuAccordion: true,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
storeColorMode() {
|
||||
return store.value
|
||||
},
|
||||
colorMode() {
|
||||
return store.value === 'auto' ? system.value : store.value
|
||||
},
|
||||
fullScreen() {
|
||||
return isFullscreen.value
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 重置所有设置
|
||||
resetAlltheme() {
|
||||
this.theme = themeConfig
|
||||
this.primaryColor = '#4834D4'
|
||||
this.collapsed = false
|
||||
this.grayMode = false
|
||||
this.colorWeak = false
|
||||
this.loadFlag = true
|
||||
this.showLogo = true
|
||||
this.showTabs = true
|
||||
this.showFooter = true
|
||||
this.showBreadcrumb = true
|
||||
this.showBreadcrumbIcon = true
|
||||
this.transitionAnimation = 'zoom-out'
|
||||
this.layoutMode = 'leftMenu'
|
||||
this.contentFullScreen = false
|
||||
this.menuAccordion = true
|
||||
|
||||
// 重置所有配色
|
||||
this.setPrimaryColor(this.primaryColor)
|
||||
},
|
||||
setAppLang(lang: App.lang) {
|
||||
setLocale(lang)
|
||||
local.set('lang', lang)
|
||||
this.lang = lang
|
||||
},
|
||||
/* 设置主题色 */
|
||||
setPrimaryColor(color: string) {
|
||||
const brightenColor = colord(color).lighten(0.05).toHex()
|
||||
const darkenColor = colord(color).darken(0.05).toHex()
|
||||
set(this.theme, 'common.primaryColor', color)
|
||||
set(this.theme, 'common.primaryColorHover', brightenColor)
|
||||
set(this.theme, 'common.primaryColorPressed', darkenColor)
|
||||
set(this.theme, 'common.primaryColorSuppl', brightenColor)
|
||||
},
|
||||
setColorMode(mode: 'light' | 'dark' | 'auto') {
|
||||
store.value = mode
|
||||
},
|
||||
/* 切换侧边栏收缩 */
|
||||
toggleCollapse() {
|
||||
this.collapsed = !this.collapsed
|
||||
},
|
||||
/* 切换全屏 */
|
||||
toggleFullScreen() {
|
||||
toggle()
|
||||
},
|
||||
/**
|
||||
* @description: 页面内容重载
|
||||
* @param {number} delay - 延迟毫秒数
|
||||
* @return {*}
|
||||
*/
|
||||
async reloadPage(delay = 600) {
|
||||
this.loadFlag = false
|
||||
await nextTick()
|
||||
if (delay) {
|
||||
setTimeout(() => {
|
||||
this.loadFlag = true
|
||||
}, delay)
|
||||
}
|
||||
else {
|
||||
this.loadFlag = true
|
||||
}
|
||||
},
|
||||
/* 切换色弱模式 */
|
||||
toggleColorWeak() {
|
||||
docEle.value.classList.toggle('color-weak')
|
||||
this.colorWeak = docEle.value.classList.contains('color-weak')
|
||||
},
|
||||
/* 切换灰色模式 */
|
||||
toggleGrayMode() {
|
||||
docEle.value.classList.toggle('gray-mode')
|
||||
this.grayMode = docEle.value.classList.contains('gray-mode')
|
||||
},
|
||||
/* 切换菜单手风琴模式 */
|
||||
toggleMenuAccordion() {
|
||||
this.menuAccordion = !this.menuAccordion
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: localStorage,
|
||||
},
|
||||
})
|
||||
24
src/store/app/theme.json
Normal file
24
src/store/app/theme.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"common": {
|
||||
"primaryColor": "#4834D4",
|
||||
"primaryColorHover": "#5b49d8",
|
||||
"primaryColorPressed": "#3d2ac5",
|
||||
"primaryColorSuppl": "#5b49d8",
|
||||
"infoColor": "#2080f0",
|
||||
"infoColorHover": "#4098fc",
|
||||
"infoColorPressed": "#1060c9",
|
||||
"infoColorSuppl": "#4098fc",
|
||||
"successColor": "#18a058",
|
||||
"successColorHover": "#36ad6a",
|
||||
"successColorPressed": "#0c7a43",
|
||||
"successColorSuppl": "#36ad6a",
|
||||
"warningColor": "#f0a020",
|
||||
"warningColorHover": "#fcb040",
|
||||
"warningColorPressed": "#c97c10",
|
||||
"warningColorSuppl": "#fcb040",
|
||||
"errorColor": "#d03050",
|
||||
"errorColorHover": "#de576d",
|
||||
"errorColorPressed": "#ab1f3f",
|
||||
"errorColorSuppl": "#de576d"
|
||||
}
|
||||
}
|
||||
141
src/store/auth.ts
Normal file
141
src/store/auth.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { router } from '@/router'
|
||||
import { fetchLogin, fetchLoginUserInfo, fetchLogout } from '@/service/api/auth'
|
||||
import { local } from '@/utils'
|
||||
import { coiMsgSuccess } from '@/utils/coi'
|
||||
import { useRouteStore } from './router'
|
||||
import { useTabStore } from './tab'
|
||||
|
||||
interface AuthStatus {
|
||||
userInfo: Api.Login.Info | null
|
||||
token: string
|
||||
}
|
||||
export const useAuthStore = defineStore('auth-store', {
|
||||
state: (): AuthStatus => {
|
||||
return {
|
||||
userInfo: local.get('userInfo'),
|
||||
token: local.get('accessToken') || '',
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
/** 是否登录 */
|
||||
isLogin(state) {
|
||||
return Boolean(state.token)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/* 登录退出,重置用户信息等 */
|
||||
async logout() {
|
||||
const route = unref(router.currentRoute)
|
||||
|
||||
// 先清除本地缓存,立即使token失效
|
||||
this.clearAuthStorage()
|
||||
// 重置当前存储库
|
||||
this.$reset()
|
||||
// 清空路由、菜单等数据
|
||||
const routeStore = useRouteStore()
|
||||
routeStore.resetRouteStore()
|
||||
// 清空标签栏数据
|
||||
const tabStore = useTabStore()
|
||||
tabStore.clearAllTabs()
|
||||
|
||||
// 最后调用后端退出登录接口
|
||||
try {
|
||||
await fetchLogout()
|
||||
}
|
||||
catch (error) {
|
||||
// 后端接口调用失败不影响前端清理
|
||||
console.warn('后端退出登录接口调用失败:', error)
|
||||
}
|
||||
|
||||
// 重定向到登录页
|
||||
if (route.meta.requiresAuth) {
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: route.fullPath,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
clearAuthStorage() {
|
||||
local.remove('accessToken')
|
||||
local.remove('refreshToken')
|
||||
local.remove('userInfo')
|
||||
},
|
||||
|
||||
/* 用户登录 */
|
||||
async login(loginName: string, password: string, codeKey: string, securityCode: string, rememberMe = false) {
|
||||
try {
|
||||
const { isSuccess, data } = await fetchLogin({ loginName, password, codeKey, securityCode, rememberMe })
|
||||
if (!isSuccess) {
|
||||
// 登录失败时抛出错误,让上层组件处理验证码刷新
|
||||
throw new Error('登录失败')
|
||||
}
|
||||
|
||||
// 保存Token
|
||||
local.set('accessToken', data.tokenValue)
|
||||
this.token = data.tokenValue
|
||||
|
||||
// 获取用户信息
|
||||
const userInfoResult = await fetchLoginUserInfo()
|
||||
if (!userInfoResult.isSuccess) {
|
||||
// 获取用户信息失败时也抛出错误
|
||||
throw new Error('获取用户信息失败')
|
||||
}
|
||||
|
||||
// 处理登录信息 - 转换后端返回的数据结构
|
||||
const userInfo = {
|
||||
id: userInfoResult.data.loginUser.userId,
|
||||
userId: userInfoResult.data.loginUser.userId,
|
||||
userName: userInfoResult.data.loginUser.userName,
|
||||
avatar: userInfoResult.data.loginUser.avatar,
|
||||
role: userInfoResult.data.roles,
|
||||
buttons: userInfoResult.data.buttons, // 用户权限按钮列表
|
||||
accessToken: data.tokenValue,
|
||||
refreshToken: data.tokenValue, // 没有单独的refreshToken,暂时使用相同值
|
||||
}
|
||||
await this.handleLoginInfo(userInfo as Api.Login.Info)
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('[Login Error]:', e)
|
||||
// 重新抛出错误,确保上层组件能够捕获
|
||||
throw e
|
||||
}
|
||||
},
|
||||
|
||||
/* 处理登录返回的数据 */
|
||||
async handleLoginInfo(data: Api.Login.Info) {
|
||||
// 将token和userInfo保存下来
|
||||
local.set('userInfo', data)
|
||||
local.set('accessToken', data.accessToken)
|
||||
local.set('refreshToken', data.refreshToken)
|
||||
this.token = data.accessToken
|
||||
this.userInfo = data
|
||||
|
||||
// 添加路由和菜单
|
||||
const routeStore = useRouteStore()
|
||||
await routeStore.initAuthRoute()
|
||||
|
||||
// 进行重定向跳转
|
||||
const route = unref(router.currentRoute)
|
||||
const query = route.query as { redirect: string }
|
||||
|
||||
// 登录成功提示
|
||||
coiMsgSuccess('登录成功!')
|
||||
|
||||
router.push({
|
||||
path: query.redirect || import.meta.env.VITE_HOME_PATH || '/dashboard',
|
||||
})
|
||||
},
|
||||
|
||||
/* 更新用户信息 */
|
||||
updateUserInfo(updates: Partial<Api.Login.Info>) {
|
||||
if (this.userInfo) {
|
||||
// 更新内存中的用户信息
|
||||
this.userInfo = { ...this.userInfo, ...updates }
|
||||
// 更新本地存储
|
||||
local.set('userInfo', this.userInfo)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
102
src/store/dict.ts
Normal file
102
src/store/dict.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { getDictDataByType } from '@/service/api/system/dict'
|
||||
import type { DictDataOption } from '@/service/api/system/dict'
|
||||
|
||||
type DictValue = string | number | boolean | null | undefined
|
||||
|
||||
export const useDictStore = defineStore('dict-store', () => {
|
||||
const dictMap = ref<Record<string, DictDataOption[]>>({})
|
||||
const loadingMap = ref<Record<string, boolean>>({})
|
||||
const pendingMap: Record<string, Promise<DictDataOption[]>> = {}
|
||||
|
||||
function getDictOptions(dictType: string) {
|
||||
return dictMap.value[dictType] ?? []
|
||||
}
|
||||
|
||||
function getDictOption(dictType: string, value: DictValue) {
|
||||
if (value === undefined || value === null)
|
||||
return undefined
|
||||
|
||||
const target = String(value)
|
||||
return getDictOptions(dictType).find(option => option.dictValue === target)
|
||||
}
|
||||
|
||||
function getDictLabel(dictType: string, value: DictValue, fallback?: string) {
|
||||
const option = getDictOption(dictType, value)
|
||||
if (option)
|
||||
return option.dictLabel
|
||||
|
||||
if (fallback !== undefined)
|
||||
return fallback
|
||||
|
||||
if (value === undefined || value === null || value === '')
|
||||
return ''
|
||||
|
||||
return String(value)
|
||||
}
|
||||
|
||||
async function fetchDict(dictType: string, force = false) {
|
||||
if (!dictType)
|
||||
return [] as DictDataOption[]
|
||||
|
||||
if (!force && dictMap.value[dictType])
|
||||
return dictMap.value[dictType]
|
||||
|
||||
if (!force && pendingMap[dictType])
|
||||
return pendingMap[dictType]
|
||||
|
||||
const promise = (async () => {
|
||||
loadingMap.value[dictType] = true
|
||||
try {
|
||||
const { isSuccess, data } = await getDictDataByType(dictType)
|
||||
if (isSuccess && Array.isArray(data))
|
||||
dictMap.value[dictType] = data
|
||||
else if (!dictMap.value[dictType])
|
||||
dictMap.value[dictType] = []
|
||||
|
||||
return dictMap.value[dictType]
|
||||
}
|
||||
finally {
|
||||
loadingMap.value[dictType] = false
|
||||
delete pendingMap[dictType]
|
||||
}
|
||||
})()
|
||||
|
||||
if (!force)
|
||||
pendingMap[dictType] = promise
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
async function fetchDicts(dictTypes: string[], force = false) {
|
||||
const uniqueTypes = Array.from(new Set(dictTypes.filter(Boolean)))
|
||||
await Promise.all(uniqueTypes.map(type => fetchDict(type, force)))
|
||||
}
|
||||
|
||||
function isLoading(dictType: string) {
|
||||
return Boolean(loadingMap.value[dictType])
|
||||
}
|
||||
|
||||
function invalidate(dictType?: string) {
|
||||
if (dictType) {
|
||||
delete dictMap.value[dictType]
|
||||
delete loadingMap.value[dictType]
|
||||
delete pendingMap[dictType]
|
||||
}
|
||||
else {
|
||||
dictMap.value = {}
|
||||
loadingMap.value = {}
|
||||
Object.keys(pendingMap).forEach(key => delete pendingMap[key])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dictMap,
|
||||
fetchDict,
|
||||
fetchDicts,
|
||||
getDictOptions,
|
||||
getDictOption,
|
||||
getDictLabel,
|
||||
isLoading,
|
||||
invalidate,
|
||||
}
|
||||
})
|
||||
15
src/store/index.ts
Normal file
15
src/store/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { App } from 'vue'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
export * from './app/index'
|
||||
export * from './auth'
|
||||
export * from './router'
|
||||
export * from './tab'
|
||||
export * from './dict'
|
||||
|
||||
// 安装pinia全局状态库
|
||||
export function installPinia(app: App) {
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
app.use(pinia)
|
||||
}
|
||||
288
src/store/router/helper.ts
Normal file
288
src/store/router/helper.ts
Normal file
@ -0,0 +1,288 @@
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { h } from 'vue'
|
||||
import { usePermission } from '@/hooks'
|
||||
import Layout from '@/layouts/index.vue'
|
||||
import { arrayToTree, renderIcon } from '@/utils'
|
||||
import { safeAsyncComponent } from '@/utils/component-guard'
|
||||
import { clone, min, omit, pick } from 'radash'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
const metaFields: AppRoute.MetaKeys[]
|
||||
= ['title', 'icon', 'requiresAuth', 'roles', 'auth', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType']
|
||||
|
||||
// 将后端菜单数据转换为前端路由数据
|
||||
function transformBackendToRoute(backendRoute: AppRoute.BackendRoute): AppRoute.RowRoute {
|
||||
return {
|
||||
id: backendRoute.menuId,
|
||||
pid: backendRoute.parentId === 0 ? null : backendRoute.parentId,
|
||||
name: backendRoute.name,
|
||||
path: backendRoute.path,
|
||||
componentPath: backendRoute.component || null,
|
||||
redirect: backendRoute.redirect || undefined,
|
||||
title: backendRoute.menuName,
|
||||
icon: backendRoute.icon,
|
||||
auth: backendRoute.auth, // 权限标识
|
||||
requiresAuth: true, // 动态路由都需要认证
|
||||
hide: backendRoute.isHide === '0', // 0-隐藏 1-显示
|
||||
keepAlive: backendRoute.isKeepAlive === '0', // 0-是 1-否
|
||||
pinTab: backendRoute.isAffix === '0', // 0-是 1-否
|
||||
activeMenu: backendRoute.activeMenu || undefined,
|
||||
menuType: backendRoute.menuType as AppRoute.MenuType,
|
||||
href: backendRoute.isLink === '0' ? backendRoute.path : undefined, // 如果是外链
|
||||
}
|
||||
}
|
||||
|
||||
function standardizedRoutes(route: AppRoute.RowRoute[]) {
|
||||
return clone(route).map((i) => {
|
||||
const route = omit(i, metaFields)
|
||||
|
||||
Reflect.set(route, 'meta', pick(i, metaFields))
|
||||
return route
|
||||
}) as AppRoute.Route[]
|
||||
}
|
||||
|
||||
// 处理路由数据的主函数 - 支持动态和静态路由以及混合模式
|
||||
export function createRoutes(routeData: (AppRoute.BackendRoute | AppRoute.RowRoute)[]) {
|
||||
const { hasPermission } = usePermission()
|
||||
|
||||
// 处理混合数据:分别处理后端数据和前端数据
|
||||
const backendRoutes = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[]
|
||||
const frontendRoutes = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[]
|
||||
|
||||
// 转换后端路由数据
|
||||
const transformedBackendRoutes = backendRoutes.map(transformBackendToRoute)
|
||||
|
||||
// 合并所有路由
|
||||
const routes = [...frontendRoutes, ...transformedBackendRoutes]
|
||||
|
||||
// Structure the meta field
|
||||
let resultRouter = standardizedRoutes(routes)
|
||||
|
||||
// Route permission filtering
|
||||
resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles))
|
||||
|
||||
// Generate routes, no need to import files for those with redirect
|
||||
const modules = import.meta.glob('@/views/**/*.vue')
|
||||
resultRouter = resultRouter.map((item: AppRoute.Route) => {
|
||||
if (item.componentPath && !item.redirect) {
|
||||
// 对于动态路由,只有菜单类型才需要组件;对于静态路由,都需要组件
|
||||
const needComponent = item.meta.menuType === '2' || !item.meta.menuType
|
||||
if (needComponent) {
|
||||
// 处理组件路径,确保正确的路径格式
|
||||
let componentPath = item.componentPath
|
||||
// 确保路径以 / 开头
|
||||
if (!componentPath.startsWith('/')) {
|
||||
componentPath = `/${componentPath}`
|
||||
}
|
||||
// 确保路径以 .vue 结尾
|
||||
if (!componentPath.endsWith('.vue')) {
|
||||
componentPath = `${componentPath}.vue`
|
||||
}
|
||||
const fullPath = `/src/views${componentPath}`
|
||||
const originalComponent = modules[fullPath]
|
||||
|
||||
// 如果组件未找到,输出调试信息并提供默认组件
|
||||
if (!originalComponent) {
|
||||
// console.warn(`组件未找到: ${fullPath}`)
|
||||
// console.warn('可用组件路径:', Object.keys(modules).slice(0, 10)) // 只显示前10个避免日志过长
|
||||
|
||||
// 为找不到组件的页面提供一个默认的空页面组件
|
||||
item.component = safeAsyncComponent(
|
||||
() => Promise.resolve({
|
||||
template: `
|
||||
<div class="p-4">
|
||||
<div class="text-center text-gray-500">
|
||||
<h3>页面开发中</h3>
|
||||
<p>组件路径: ${fullPath}</p>
|
||||
<p>请联系开发人员创建对应的页面组件</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
{
|
||||
delay: 0,
|
||||
timeout: 5000,
|
||||
},
|
||||
)
|
||||
}
|
||||
else {
|
||||
// 使用安全的异步组件加载器包装原有组件
|
||||
item.component = safeAsyncComponent(
|
||||
originalComponent as any,
|
||||
{
|
||||
delay: 100,
|
||||
timeout: 10000,
|
||||
onError: (error, retry, fail, attempts) => {
|
||||
console.error(`组件加载失败: ${fullPath}`, error)
|
||||
if (attempts <= 2) {
|
||||
retry()
|
||||
}
|
||||
else {
|
||||
fail()
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
else if (item.meta.menuType === '1') {
|
||||
// 目录类型不需要组件,但需要确保有children
|
||||
item.component = undefined
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
// Generate route tree
|
||||
resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
|
||||
|
||||
const appRootRoute: RouteRecordRaw = {
|
||||
path: '/appRoot',
|
||||
name: 'appRoot',
|
||||
redirect: import.meta.env.VITE_HOME_PATH || '/dashboard',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '',
|
||||
icon: 'icon-park-outline:home',
|
||||
},
|
||||
children: [],
|
||||
}
|
||||
|
||||
// Set the correct redirect path for the route
|
||||
setRedirect(resultRouter)
|
||||
|
||||
// Insert the processed route into the root route
|
||||
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
|
||||
return appRootRoute
|
||||
}
|
||||
|
||||
// Generate an array of route names that need to be kept alive
|
||||
export function generateCacheRoutes(routes: AppRoute.RowRoute[]) {
|
||||
return routes
|
||||
.filter(i => i.keepAlive)
|
||||
.map(i => i.name)
|
||||
}
|
||||
|
||||
function setRedirect(routes: AppRoute.Route[]) {
|
||||
routes.forEach((route) => {
|
||||
if (route.children) {
|
||||
if (!route.redirect) {
|
||||
// Filter out a collection of child elements that are not hidden
|
||||
const visibleChilds = route.children.filter(child => !child.meta.hide)
|
||||
|
||||
// Redirect page to the path of the first child element by default
|
||||
let target = visibleChilds[0]
|
||||
|
||||
// Filter out pages with the order attribute
|
||||
const orderChilds = visibleChilds.filter(child => child.meta.order)
|
||||
|
||||
if (orderChilds.length > 0)
|
||||
target = min(orderChilds, i => i.meta.order!) as AppRoute.Route
|
||||
|
||||
if (target)
|
||||
route.redirect = target.path
|
||||
}
|
||||
|
||||
setRedirect(route.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* 生成侧边菜单的数据 */
|
||||
export function createMenus(routeData: (AppRoute.BackendRoute | AppRoute.RowRoute)[]) {
|
||||
// 处理混合数据:分别处理后端数据和前端数据
|
||||
const backendRoutes = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[]
|
||||
const frontendRoutes = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[]
|
||||
|
||||
// 转换后端路由数据
|
||||
const transformedBackendRoutes = backendRoutes.map(transformBackendToRoute)
|
||||
|
||||
// 合并所有路由
|
||||
const userRoutes = [...frontendRoutes, ...transformedBackendRoutes]
|
||||
|
||||
const resultMenus = standardizedRoutes(userRoutes)
|
||||
|
||||
// filter menus that do not need to be displayed
|
||||
const visibleMenus = resultMenus.filter(route => !route.meta.hide && route.meta.menuType !== '3') // 过滤按钮类型
|
||||
|
||||
// 处理权限过滤和父子关系
|
||||
const menusWithPermission = processMenuPermissions(visibleMenus)
|
||||
|
||||
// generate side menu
|
||||
return arrayToTree(transformAuthRoutesToMenus(menusWithPermission))
|
||||
}
|
||||
|
||||
// 处理菜单权限,确保有子菜单权限时父菜单也可见
|
||||
function processMenuPermissions(routes: AppRoute.Route[]): AppRoute.Route[] {
|
||||
const { hasPermission } = usePermission()
|
||||
|
||||
// 创建路由映射表
|
||||
const routeMap = new Map<number | null, AppRoute.Route>()
|
||||
routes.forEach(route => routeMap.set(route.id, route))
|
||||
|
||||
// 找出有权限的路由
|
||||
const authorizedRoutes = new Set<number | null>()
|
||||
|
||||
routes.forEach((route) => {
|
||||
if (hasPermission(route.meta.roles)) {
|
||||
authorizedRoutes.add(route.id)
|
||||
|
||||
// 如果是页面类型(menuType='2')或没有menuType的路由,确保其父菜单也被包含
|
||||
if (!route.meta.menuType || route.meta.menuType === '2') {
|
||||
let parentId = route.pid
|
||||
while (parentId !== null && parentId !== undefined) {
|
||||
const parentRoute = routeMap.get(parentId)
|
||||
if (parentRoute) {
|
||||
authorizedRoutes.add(parentId)
|
||||
parentId = parentRoute.pid
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 返回有权限的路由
|
||||
return routes.filter(route => authorizedRoutes.has(route.id))
|
||||
}
|
||||
|
||||
// render the returned routing table as a sidebar
|
||||
function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
|
||||
return userRoutes
|
||||
// Sort the menu according to the order size
|
||||
.sort((a, b) => {
|
||||
if (a.meta && a.meta.order && b.meta && b.meta.order)
|
||||
return a.meta.order - b.meta.order
|
||||
else if (a.meta && a.meta.order)
|
||||
return -1
|
||||
else if (b.meta && b.meta.order)
|
||||
return 1
|
||||
else return 0
|
||||
})
|
||||
// Convert to side menu data structure
|
||||
.map((item) => {
|
||||
const target: MenuOption = {
|
||||
id: item.id,
|
||||
pid: item.pid,
|
||||
label:
|
||||
(!item.meta.menuType || item.meta.menuType === '2')
|
||||
? () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
path: item.path,
|
||||
},
|
||||
},
|
||||
{ default: () => item.meta.title },
|
||||
)
|
||||
: () => item.meta.title,
|
||||
key: item.path,
|
||||
icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
|
||||
}
|
||||
return target
|
||||
})
|
||||
}
|
||||
147
src/store/router/index.ts
Normal file
147
src/store/router/index.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import { router } from '@/router'
|
||||
import { staticRoutes } from '@/router/routes.static'
|
||||
import { fetchUserRoutes } from '@/service/api/system/menu'
|
||||
import { $t } from '@/utils'
|
||||
import { coiMsgError } from '@/utils/coi'
|
||||
import { createMenus, createRoutes, generateCacheRoutes } from './helper'
|
||||
|
||||
interface RoutesStatus {
|
||||
isInitAuthRoute: boolean
|
||||
menus: MenuOption[]
|
||||
rowRoutes: AppRoute.RowRoute[]
|
||||
backendRoutes: AppRoute.BackendRoute[]
|
||||
activeMenu: string | null
|
||||
cacheRoutes: string[]
|
||||
}
|
||||
export const useRouteStore = defineStore('route-store', {
|
||||
state: (): RoutesStatus => {
|
||||
return {
|
||||
isInitAuthRoute: false,
|
||||
activeMenu: null,
|
||||
menus: [],
|
||||
rowRoutes: [],
|
||||
backendRoutes: [],
|
||||
cacheRoutes: [],
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
resetRouteStore() {
|
||||
this.resetRoutes()
|
||||
this.$reset()
|
||||
},
|
||||
resetRoutes() {
|
||||
// 获取所有路由名称
|
||||
const allRouteNames = router.getRoutes().map(route => route.name).filter(Boolean)
|
||||
|
||||
// 保护固定路由,不删除这些基础路由
|
||||
const protectedRoutes = ['root', 'login', '403', '404', '500', 'notFound']
|
||||
|
||||
// 删除除了保护路由之外的所有路由
|
||||
allRouteNames.forEach((name) => {
|
||||
if (name && !protectedRoutes.includes(name as string)) {
|
||||
if (router.hasRoute(name)) {
|
||||
router.removeRoute(name)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// set the currently highlighted menu key
|
||||
setActiveMenu(key: string) {
|
||||
this.activeMenu = key
|
||||
},
|
||||
|
||||
async initRouteInfo() {
|
||||
// 始终加载静态路由(仪表盘等基础路由)
|
||||
const allRoutes = [...staticRoutes]
|
||||
|
||||
if (import.meta.env.VITE_ROUTE_LOAD_MODE === 'dynamic') {
|
||||
try {
|
||||
// 获取动态路由并合并
|
||||
const { isSuccess, data } = await fetchUserRoutes()
|
||||
|
||||
if (isSuccess && data) {
|
||||
const dynamicRoutes = Array.isArray(data) ? data : []
|
||||
return [...allRoutes, ...dynamicRoutes]
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// 检查是否是网络错误
|
||||
const isNetworkError = !navigator.onLine
|
||||
|| (error instanceof Error && (
|
||||
error.message.includes('网络')
|
||||
|| error.message.includes('Network')
|
||||
|| error.message.includes('fetch')
|
||||
|| error.message.includes('timeout')
|
||||
|| error.message.includes('ERR_NETWORK')
|
||||
))
|
||||
|
||||
if (isNetworkError) {
|
||||
throw new Error('网络连接失败,请检查网络状态')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allRoutes
|
||||
},
|
||||
async initAuthRoute() {
|
||||
this.isInitAuthRoute = false
|
||||
|
||||
// Initialize route information
|
||||
const routeData = await this.initRouteInfo()
|
||||
if (!routeData) {
|
||||
coiMsgError($t(`app.getRouteError`))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否包含动态路由数据(通过是否有menuId字段判断)
|
||||
const hasDynamicRoutes = routeData.some(item => 'menuId' in item)
|
||||
|
||||
if (hasDynamicRoutes) {
|
||||
// 混合模式:分离静态路由和动态路由
|
||||
const staticRouteData = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[]
|
||||
const dynamicRouteData = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[]
|
||||
|
||||
// 保存动态路由原始数据
|
||||
this.backendRoutes = dynamicRouteData
|
||||
|
||||
// 转换动态路由为前端格式
|
||||
const transformedDynamicRoutes = dynamicRouteData.map(item => ({
|
||||
id: item.menuId,
|
||||
pid: item.parentId === 0 ? null : item.parentId,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
componentPath: item.component || null,
|
||||
redirect: item.redirect || undefined,
|
||||
title: item.menuName,
|
||||
icon: item.icon,
|
||||
requiresAuth: true,
|
||||
hide: item.isHide === '0',
|
||||
keepAlive: item.isKeepAlive === '0',
|
||||
pinTab: item.isAffix === '0',
|
||||
activeMenu: item.activeMenu || undefined,
|
||||
menuType: item.menuType as AppRoute.MenuType,
|
||||
href: item.isLink === '0' ? item.path : undefined,
|
||||
} as AppRoute.RowRoute))
|
||||
|
||||
// 合并静态路由和转换后的动态路由
|
||||
this.rowRoutes = [...staticRouteData, ...transformedDynamicRoutes]
|
||||
this.cacheRoutes = generateCacheRoutes(this.rowRoutes)
|
||||
}
|
||||
else {
|
||||
// 纯静态路由模式
|
||||
this.rowRoutes = routeData as AppRoute.RowRoute[]
|
||||
this.cacheRoutes = generateCacheRoutes(this.rowRoutes)
|
||||
}
|
||||
|
||||
// Generate actual route and insert
|
||||
const routes = createRoutes(routeData)
|
||||
router.addRoute(routes)
|
||||
|
||||
// Generate side menu
|
||||
this.menus = createMenus(routeData)
|
||||
|
||||
this.isInitAuthRoute = true
|
||||
},
|
||||
},
|
||||
})
|
||||
116
src/store/tab.ts
Normal file
116
src/store/tab.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import { navigationGuard } from '@/router'
|
||||
|
||||
interface TabState {
|
||||
pinTabs: RouteLocationNormalized[]
|
||||
tabs: RouteLocationNormalized[]
|
||||
currentTabPath: string
|
||||
}
|
||||
export const useTabStore = defineStore('tab-store', {
|
||||
state: (): TabState => {
|
||||
return {
|
||||
pinTabs: [],
|
||||
tabs: [],
|
||||
currentTabPath: '',
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
allTabs: state => [...state.pinTabs, ...state.tabs],
|
||||
},
|
||||
actions: {
|
||||
addTab(route: RouteLocationNormalized) {
|
||||
// 根据meta确定是否不添加,可用于错误页,登录页等
|
||||
if (route.meta.withoutTab)
|
||||
return
|
||||
|
||||
// 如果标签名称已存在则不添加
|
||||
if (this.hasExistTab(route.fullPath as string))
|
||||
return
|
||||
|
||||
// 根据meta.pinTab传递到不同的分组中
|
||||
if (route.meta.pinTab)
|
||||
this.pinTabs.push(route)
|
||||
else
|
||||
this.tabs.push(route)
|
||||
},
|
||||
async closeTab(fullPath: string) {
|
||||
try {
|
||||
const tabsLength = this.tabs.length
|
||||
// 如果动态标签大于一个,才会标签跳转
|
||||
if (this.tabs.length > 1) {
|
||||
// 获取关闭的标签索引
|
||||
const index = this.getTabIndex(fullPath)
|
||||
const isLast = index + 1 === tabsLength
|
||||
// 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
|
||||
if (this.currentTabPath === fullPath && !isLast) {
|
||||
// 跳转到后一个标签
|
||||
await navigationGuard.safePush(this.tabs[index + 1].fullPath)
|
||||
}
|
||||
else if (this.currentTabPath === fullPath && isLast) {
|
||||
// 已经是最后一个了,就跳转前一个
|
||||
await navigationGuard.safePush(this.tabs[index - 1].fullPath)
|
||||
}
|
||||
}
|
||||
// 删除标签
|
||||
this.tabs = this.tabs.filter((item) => {
|
||||
return item.fullPath !== fullPath
|
||||
})
|
||||
// 删除后如果清空了,就跳转到默认首页
|
||||
if (tabsLength - 1 === 0)
|
||||
await navigationGuard.safePush('/')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('关闭标签页时发生错误:', error)
|
||||
}
|
||||
},
|
||||
|
||||
closeOtherTabs(fullPath: string) {
|
||||
const index = this.getTabIndex(fullPath)
|
||||
this.tabs = this.tabs.filter((item, i) => i === index)
|
||||
},
|
||||
closeLeftTabs(fullPath: string) {
|
||||
const index = this.getTabIndex(fullPath)
|
||||
this.tabs = this.tabs.filter((item, i) => i >= index)
|
||||
},
|
||||
closeRightTabs(fullPath: string) {
|
||||
const index = this.getTabIndex(fullPath)
|
||||
this.tabs = this.tabs.filter((item, i) => i <= index)
|
||||
},
|
||||
clearAllTabs() {
|
||||
this.tabs.length = 0
|
||||
this.pinTabs.length = 0
|
||||
},
|
||||
async closeAllTabs() {
|
||||
try {
|
||||
this.tabs.length = 0
|
||||
await navigationGuard.safePush('/')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('关闭所有标签页时发生错误:', error)
|
||||
}
|
||||
},
|
||||
|
||||
hasExistTab(fullPath: string) {
|
||||
const _tabs = [...this.tabs, ...this.pinTabs]
|
||||
return _tabs.some((item) => {
|
||||
return item.fullPath === fullPath
|
||||
})
|
||||
},
|
||||
/* 设置当前激活的标签 */
|
||||
setCurrentTab(fullPath: string) {
|
||||
this.currentTabPath = fullPath
|
||||
},
|
||||
getTabIndex(fullPath: string) {
|
||||
return this.tabs.findIndex((item) => {
|
||||
return item.fullPath === fullPath
|
||||
})
|
||||
},
|
||||
modifyTab(fullPath: string, modifyFn: (route: RouteLocationNormalized) => void) {
|
||||
const index = this.getTabIndex(fullPath)
|
||||
modifyFn(this.tabs[index])
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: sessionStorage,
|
||||
},
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user