瀏覽代碼

feat: api改造,登录改造,字典改造

zhanghaijun 1 年之前
父節點
當前提交
1e21653314

+ 4 - 1
.vscode/settings.json

@@ -47,5 +47,8 @@
     "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,CNAME,.npmrc,.browserslistrc,.nvmrc,.node-version",
     "eslint.config.mjs": ".eslintignore,.prettierrc,.prettierignore,.stylelintignore,commitlint.*,prettier.*,stylelint.*,.eslintrc-auto-import.*,.release*,.editorconfig,.stylelintrc.*,.auto-import.json"
   },
-  "cSpell.words": ["unplugin"]
+  "cSpell.words": [
+    "iconfont",
+    "unplugin"
+  ]
 }

+ 1 - 2
src/App.vue

@@ -13,7 +13,6 @@
   import { setThemeTransitionClass } from './utils/theme/animation'
   import { checkStorageCompatibility } from './utils/storage'
   import { useDictStore } from './store/modules/dict'
-  import { DictService } from './api/dict.api'
 
   const userStore = useUserStore()
   const dictStore = useDictStore()
@@ -28,7 +27,7 @@
     setThemeTransitionClass(true)
     // 这里可以一次性初始化所有字典
     if (userStore.isLogin) {
-      dictStore.initAllDicts(DictService.getAllDict)
+      dictStore.initAllDicts()
     }
   })
 

+ 3 - 30
src/api/auth.api.ts

@@ -1,4 +1,4 @@
-import request from '@/utils/http'
+import request from '@/api'
 
 interface LoginParams {
   userName: string
@@ -11,34 +11,7 @@ interface LoginResponse {
 
 export class AuthService {
   // 登录
-  static login = (params: LoginParams) => request.post<LoginResponse>({ url: '/auth/login', params })
+  static login = (params: LoginParams) => request.post<LoginResponse>('/auth/login', params)
   // 退出登录
-  static logout = () => request.post({ url: '/auth/logout' })
-}
-/**
- * 登录
- * @param params 登录参数
- * @returns 登录响应
- */
-export function fetchLogin(params: Api.Auth.LoginParams) {
-  return request.post<Api.Auth.LoginResponse>({
-    url: '/api/auth/login',
-    params
-    // showSuccessMessage: true // 显示成功消息
-    // showErrorMessage: false // 不显示错误消息
-  })
-}
-
-/**
- * 获取用户信息
- * @returns 用户信息
- */
-export function fetchGetUserInfo() {
-  return request.get<Api.Auth.UserInfo>({
-    url: '/api/user/info'
-    // 自定义请求头
-    // headers: {
-    //   'X-Custom-Header': 'your-custom-value'
-    // }
-  })
+  static logout = () => request.post('/auth/logout')
 }

+ 6 - 6
src/api/course.api.ts

@@ -1,4 +1,4 @@
-import request from '@/utils/http'
+import request from '@/api'
 
 /** 课程模型 */
 export interface Course {
@@ -20,9 +20,9 @@ export interface Course {
 }
 
 export class CourseServer {
-  public static list = (data: any) => request.post<Course[]>({ url: '/course/list', data })
-  public static detail = (courseID: number) => request.get<Course>({ url: '/course/detail', params: { courseID } })
-  public static save = (data: any) => request.post({ url: '/course/save', data })
-  public static update = (data: any) => request.post({ url: '/course/update', data })
-  public static delete = (courseID: number) => request.post({ url: '/course/delete', params: { courseID } })
+  public static list = (data: any) => request.post<Course[]>('/course/list', data)
+  public static detail = (courseID: number) => request.get<Course>('/course/detail', { courseID })
+  public static save = (data: any) => request.post('/course/save', data)
+  public static update = (data: any) => request.post('/course/update', data)
+  public static delete = (courseID: number) => request.post('/course/delete', { courseID })
 }

+ 43 - 8
src/api/dict.api.ts

@@ -1,11 +1,46 @@
-import request from '@/utils/http'
+import request from '@/api'
 
+export interface Dict {
+  code: string /** 字典编码 */
+  name: string /** 字典名称 */
+  remark: string /** 备注 */
+  sort: number /** 排序 */
+}
+export interface DictItem {
+  dictCode: string /** 字典编码 */
+  code: string /** 字典项编码 */
+  name: string /** 字典项名称 */
+  remark: string /** 备注 */
+  sort: number /** 排序 */
+}
+export interface DictList extends Dict {
+  items: DictItem[] /** 字典项列表 */
+}
+/** 字典查询 */
 export class DictService {
-  // 获取所有字典
-  static getAllDict() {
-    return request.post<any>({
-      url: '/sys/dict/enumDictList',
-      showErrorMessage: false // 不显示错误消息
-    })
-  }
+  /** 获取所有字典 */
+  static dictList = async () => request.post<DictList[]>('/system/dict/all')
+  // 分页查询
+  static page = (params: any) => request.post('/system/dict/page', params)
+  // 详情
+  static detail = (dictCode: string) => request.post<Dict>('/system/dict/detail', { dictCode })
+  // 添加
+  static save = (params: any) => request.post('/system/dict/save', params)
+  // 修改
+  static update = (params: any) => request.post('/system/dict/update', params)
+  // 删除
+  static delete = (dictCode: string) => request.post('/system/dict/remove', { dictCode })
+}
+/** 字典项查询 */
+export class DictItemService {
+  // 获取字典项列表
+  static dictItemList = async (dictCode: string) => request.post<DictItem[]>('/system/dict/item/list', { dictCode })
+  // 分页查询
+  static page = (params: any) => request.postPage<DictItem[]>('/system/dict/item/page', params)
+  // 添加
+  static save = (params: any) => request.post('/system/dict/item/save', params)
+  // 修改
+  static update = (params: any) => request.post('/system/dict/item/update', params)
+  // 删除
+  static delete = (dictItemID: number) => request.post('/system/dict/item/remove', { dictItemID })
 }

+ 28 - 0
src/api/index.ts

@@ -0,0 +1,28 @@
+import { RequestHttp } from '@utils/http/server.axios'
+
+const { VITE_API_URL, VITE_WITH_CREDENTIALS } = import.meta.env
+
+const request = new RequestHttp({
+  // 默认地址请求地址
+  baseURL: VITE_API_URL || '/api',
+  // 设置超时时间
+  timeout: 1000 * 5,
+  // 跨域时候允许携带凭证
+  withCredentials: VITE_WITH_CREDENTIALS === 'true',
+  // 验证状态码
+  validateStatus: (status) => status >= 200 && status < 300,
+  transformResponse: [
+    (data, headers) => {
+      const contentType = headers['content-type']
+      if (contentType?.includes('application/json')) {
+        try {
+          return JSON.parse(data)
+        } catch {
+          return data
+        }
+      }
+      return data
+    }
+  ]
+})
+export default request

+ 2 - 3
src/components/form/components/api-dict.vue

@@ -17,10 +17,10 @@
 </template>
 <script setup lang="ts">
   import { useDictStore } from '@/store/modules/dict'
-  import { NewOption } from '@/types/component/dict'
   import Select from './select.vue'
   import RadioGroup from './radio-group.vue'
   import CheckboxGroup from './checkbox-group.vue'
+  import { DictItem } from '@/api/dict.api'
   const props = defineProps<{
     code: string
     renderType?: 'Select' | 'RadioGroup' | 'CheckboxGroup'
@@ -29,11 +29,10 @@
   onBeforeMount(() => {
     requestData()
   })
-  const options = ref<NewOption[]>([])
+  const options = ref<DictItem[]>([])
   const requestData = async () => {
     const code = props.code
     if (!code) return
-
     try {
       // 从 store 获取字典数据(会自动缓存)
       const data = await dictStore.getDictByType(code as string)

+ 1 - 6
src/components/view/api-view.vue

@@ -38,12 +38,7 @@
 
   const displayValue = computed(() => {
     // 如果没有选项或值为空,直接返回值
-    if (
-      !props.options ||
-      !props.options.length ||
-      props.value === undefined ||
-      props.value === null
-    ) {
+    if (!props.options || !props.options.length || props.value === undefined || props.value === null) {
       return props.value
     }
     let values = props.value

+ 1 - 5
src/components/view/component-map.ts

@@ -60,11 +60,7 @@ export function getAllComponents() {
  * @param column 列配置
  */
 export function viewColumnFormatter(column: ColumnOption<any>) {
-  if (
-    column.component &&
-    hasComponent(column.component as string) &&
-    column.formatter === undefined
-  ) {
+  if (column.component && hasComponent(column.component as string) && column.formatter === undefined) {
     column.formatter = (row: any) => {
       const component = getComponent(column.component as string)
       return h(component, {

+ 13 - 35
src/components/view/components/api-dict.vue

@@ -2,14 +2,7 @@
   <template v-if="isTag">
     <el-space v-if="cNewOptions.length">
       <template v-for="item in cNewOptions" :key="item.value">
-        <el-tag
-          :size="size"
-          :hit="hit"
-          :effect="effect"
-          v-if="item.tagProps"
-          v-bind="item.tagProps"
-          >{{ item.label }}</el-tag
-        >
+        <el-tag :size="size" :hit="hit" :effect="effect" v-if="item.tagProps" v-bind="item.tagProps">{{ item.label }}</el-tag>
         <span v-else>{{ item.label }}</span>
       </template>
     </el-space>
@@ -21,6 +14,7 @@
   import { useChart } from '@/composables/useChart'
   import { PropType } from 'vue'
   import { ColorType, NewOption } from '@/types/component/dict'
+  import { DictItem } from '@/api/dict.api'
   // 使用基础的 useChart hook
   const { isDark } = useChart()
   defineOptions({
@@ -101,16 +95,11 @@
   })
 
   const dictStore = useDictStore()
-  const options = ref<NewOption[]>([])
+  const options = ref<DictItem[]>([])
   const effect = computed<'dark' | 'light' | 'plain'>(() => {
     return isDark.value ? 'dark' : 'light'
   })
-  const isTag = computed(
-    () =>
-      Object.keys(props.typeMap).length > 0 ||
-      Object.keys(props.colorMap).length > 0 ||
-      props.useColorList
-  )
+  const isTag = computed(() => Object.keys(props.typeMap).length > 0 || Object.keys(props.colorMap).length > 0 || props.useColorList)
   type NewOptionType = NewOption & { tagProps?: any }
   const cValue = computed(() => {
     // 查找匹配的选项并返回标签
@@ -118,25 +107,21 @@
       // 处理数组值的情况
       return props.value
         .map((val) => {
-          const option = options.value.find((opt) => opt.value === val)
-          return option ? option.label : val
+          const option = options.value.find((opt) => opt.name === val)
+          return option ? option.name : val
         })
         .join(', ')
     } else {
       // 处理单个值的情况
-      const option = options.value.find((opt) => opt.value === props.value)
-      return option ? option.label : props.value
+      const option = options.value.find((opt) => opt.name === props.value)
+      return option ? option.name : props.value
     }
   })
   const createOption = (label: string | number, value: string | number): NewOptionType => ({
     label: String(label),
     value
   })
-  const createTagOption = (
-    option: NewOption | null,
-    value: string | number,
-    index: number
-  ): NewOptionType => {
+  const createTagOption = (option: NewOption | null, value: string | number, index: number): NewOptionType => {
     if (!option) {
       return createOption(value, value)
     }
@@ -144,10 +129,7 @@
     // 从 colorMap 获取颜色配置
     const colorMapConfig = props.colorMap[option.value]
     // 从 colorList 获取颜色配置(处理索引越界)
-    const colorListConfig =
-      props.useColorList && props.colorList.length > 0
-        ? props.colorList[index % props.colorList.length]
-        : undefined
+    const colorListConfig = props.useColorList && props.colorList.length > 0 ? props.colorList[index % props.colorList.length] : undefined
 
     // 优先使用 colorMap 配置,其次使用 colorList 配置
     const colorConfig = colorMapConfig || colorListConfig
@@ -167,7 +149,7 @@
   const cNewOptions = computed<NewOptionType[]>(() => {
     if (Array.isArray(props.value)) {
       return props.value.map((val: any) => {
-        const option = options.value.find((opt) => opt.value === val) ?? null
+        const option = options.value.find((opt) => opt.name === val) ?? null
         // 修复:根据选项在 options 数组中的实际索引来获取 colorList 颜色
         const optionIndex = option ? options.value.indexOf(option) : 0
         return createTagOption(option, val, optionIndex)
@@ -176,14 +158,10 @@
       if (props.value === undefined || props.value === null) {
         return []
       }
-      const option = options.value.find((opt) => opt.value === props.value)
+      const option = options.value.find((opt) => opt.name === props.value)
       // 修复:根据选项在 options 数组中的实际索引来获取 colorList 颜色
       const optionIndex = option ? options.value.indexOf(option) : 0
-      return option
-        ? [createTagOption(option, props.value, optionIndex)]
-        : props.value !== null && props.value !== undefined
-          ? [createOption(props.value, props.value)]
-          : []
+      return option ? [createTagOption(option, props.value, optionIndex)] : props.value !== null && props.value !== undefined ? [createOption(props.value, props.value)] : []
     }
   })
 

+ 5 - 30
src/composables/useChart.ts

@@ -15,15 +15,7 @@ export const useChartOps = (): ChartThemeConfig => ({
   /** 主题颜色 */
   themeColor: getCssVar('--el-color-primary-light-1'),
   /** 颜色组 */
-  colors: [
-    getCssVar('--el-color-primary-light-1'),
-    '#4ABEFF',
-    '#EDF2FF',
-    '#14DEBA',
-    '#FFAF20',
-    '#FA8A6C',
-    '#FFAF20'
-  ]
+  colors: [getCssVar('--el-color-primary-light-1'), '#4ABEFF', '#EDF2FF', '#14DEBA', '#FFAF20', '#FA8A6C', '#FFAF20']
 })
 
 // 常量定义
@@ -174,10 +166,7 @@ export function useChart(options: UseChartOptions = {}) {
   })
 
   // 获取统一的图例配置
-  const getLegendStyle = (
-    position: 'bottom' | 'top' | 'left' | 'right' = 'bottom',
-    customOptions: any = {}
-  ) => {
+  const getLegendStyle = (position: 'bottom' | 'top' | 'left' | 'right' = 'bottom', customOptions: any = {}) => {
     const baseConfig = {
       textStyle: {
         color: isDark.value ? '#fff' : '#333'
@@ -228,11 +217,7 @@ export function useChart(options: UseChartOptions = {}) {
   }
 
   // 根据图例位置计算 grid 配置
-  const getGridWithLegend = (
-    showLegend: boolean,
-    legendPosition: 'bottom' | 'top' | 'left' | 'right' = 'bottom',
-    baseGrid: any = {}
-  ) => {
+  const getGridWithLegend = (showLegend: boolean, legendPosition: 'bottom' | 'top' | 'left' | 'right' = 'bottom', baseGrid: any = {}) => {
     const defaultGrid = {
       top: 15,
       right: 15,
@@ -365,10 +350,7 @@ export function useChart(options: UseChartOptions = {}) {
       `
 
       // 确保父容器有相对定位
-      if (
-        chartRef.value.style.position !== 'relative' &&
-        chartRef.value.style.position !== 'absolute'
-      ) {
+      if (chartRef.value.style.position !== 'relative' && chartRef.value.style.position !== 'absolute') {
         chartRef.value.style.position = 'relative'
       }
 
@@ -534,14 +516,7 @@ interface UseChartComponentOptions<T extends BaseChartProps> {
 }
 
 export function useChartComponent<T extends BaseChartProps>(options: UseChartComponentOptions<T>) {
-  const {
-    props,
-    generateOptions,
-    checkEmpty,
-    watchSources = [],
-    onVisible,
-    chartOptions = {}
-  } = options
+  const { props, generateOptions, checkEmpty, watchSources = [], onVisible, chartOptions = {} } = options
 
   const chart = useChart(chartOptions)
   const { chartRef, initChart, isDark, emptyStateManager } = chart

+ 18 - 37
src/store/modules/dict.ts

@@ -1,71 +1,52 @@
 // src/store/modules/dict.ts
 import { defineStore } from 'pinia'
-import request from '@/utils/http'
-import { Dict, NewOption } from '@/types/component/dict'
+import { DictItem, DictService, DictItemService } from '@/api/dict.api'
 
 interface DictState {
-  dictMap: Map<string, NewOption[]>
+  dictMap: Map<string, DictItem[]>
   loadingMap: Map<string, boolean>
 }
 
 export const useDictStore = defineStore('dict', {
   state: (): DictState => ({
-    dictMap: new Map<string, NewOption[]>(),
+    dictMap: new Map<string, DictItem[]>(),
     loadingMap: new Map<string, boolean>()
   }),
 
   actions: {
-    async getDictByType(dictType: string): Promise<NewOption[]> {
+    async getDictByType(dictCode: string): Promise<DictItem[]> {
       // 如果已经存在缓存,直接返回
-      if (this.dictMap.has(dictType)) {
-        return this.dictMap.get(dictType) || []
+      if (this.dictMap.has(dictCode)) {
+        return this.dictMap.get(dictCode) || []
       }
-
       // 如果正在加载中,等待一段时间后再次尝试获取
-      if (this.loadingMap.get(dictType)) {
+      if (this.loadingMap.get(dictCode)) {
         // 简单等待机制,可以使用更复杂的等待逻辑
         await new Promise((resolve) => setTimeout(resolve, 10))
-        return this.getDictByType(dictType)
+        return this.getDictByType(dictCode)
       }
-
       // 标记为正在加载
-      this.loadingMap.set(dictType, true)
-
+      this.loadingMap.set(dictCode, true)
       try {
-        const res = await request.post<NewOption[]>({
-          url: '/sys/dict/getByDictType',
-          params: { dictType }
-        })
-
-        const data = res || []
-        // 缓存数据
-        this.dictMap.set(dictType, data)
-        return data
+        const { data } = await DictItemService.dictItemList(dictCode)
+        const dictItems = data || []
+        this.dictMap.set(dictCode, dictItems)
+        return dictItems
       } finally {
         // 加载完成后清除标记
-        this.loadingMap.set(dictType, false)
+        this.loadingMap.set(dictCode, false)
       }
     },
 
     // 初始化所有字典数据 - 适配新的数据结构
-    async initAllDicts(apiFun: () => Promise<Dict[]>): Promise<void> {
+    async initAllDicts(): Promise<void> {
       try {
         // 调用传入的API函数获取所有字典数据
-        const allDicts = await apiFun()
-
+        const { data } = await DictService.dictList()
+        const allDicts = data || []
         // 将获取到的所有字典数据转换并存入缓存
         allDicts.forEach((dict) => {
-          // 将 DictItem 转换为 Option 格式
-          const options: NewOption[] = dict.items.map((item) => ({
-            label: item.name,
-            value: item.dictItemValue,
-            disabled: item.disabled ? true : false,
-            bgColor: item.bgColor,
-            textColor: item.textColor
-          }))
-
-          // 以 dictKey 作为键存储
-          this.dictMap.set(dict.dictKey, options)
+          this.dictMap.set(dict.code, dict.items)
         })
       } catch (error) {
         console.error('初始化所有字典数据失败:', error)

+ 1 - 0
src/utils/http/index.ts

@@ -3,6 +3,7 @@ import { useUserStore } from '@/store/modules/user'
 import { ApiStatus } from './status'
 import { HttpError, handleError, showError, showSuccess } from './server.error'
 import { $t } from '@/locales'
+import { ApiResponse } from './server.type'
 
 /** 请求配置常量 */
 const LOGOUT_DELAY = 500

+ 13 - 33
src/utils/http/server.axios.ts

@@ -1,38 +1,18 @@
 import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
 import { useUserStore } from '@/store/modules/user'
 import { $t } from '@/locales'
+import { ApiResponse, ApiResponsePage, ApiStatus } from './server.type'
 
-export enum ApiStatus {
-  success = 0, // 成功
-  error = 400, // 错误
-  unauthorized = 401, // 未授权
-  forbidden = 403, // 禁止访问
-  notFound = 404, // 未找到
-  methodNotAllowed = 405, // 方法不允许
-  requestTimeout = 408, // 请求超时
-  internalServerError = 500, // 服务器错误
-  notImplemented = 501, // 未实现
-  badGateway = 502, // 网关错误
-  serviceUnavailable = 503, // 服务不可用
-  gatewayTimeout = 504, // 网关超时
-  httpVersionNotSupported = 505 // HTTP版本不支持
-}
-export interface ApiResponse<T = unknown> {
-  code: number /* 状态码 */
-  msg: string /* 消息 */
-  data?: T /* 数据 */
-}
-export interface ApiResponsePage<T = unknown> {
-  pageNum: number /* 当前页码。从 1 开始计数,表示当前是第几页。 */
-  pageSize: number /* 每页大小。表示每页包含多少条数据。 */
-  totalCount: number /* 总条数。表示数据总共有多少条。 */
-  list: T[] /* 数据列表 */
+/** 扩展 AxiosRequestConfig */
+interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
+  showErrorMessage?: boolean
+  showSuccessMessage?: boolean
 }
 
 export class RequestHttp {
   service: AxiosInstance
 
-  public constructor(config: AxiosRequestConfig) {
+  public constructor(config: ExtendedAxiosRequestConfig) {
     this.service = axios.create(config)
 
     /**
@@ -77,33 +57,33 @@ export class RequestHttp {
     )
   }
 
-  public request<T = any>(config: AxiosRequestConfig): Promise<T> {
+  public request<T = any>(config: ExtendedAxiosRequestConfig): Promise<T> {
     return this.service.request(config)
   }
   /**
    * @description 常用请求方法封装
    */
-  public get<T>(url: string, params?: object, _object: AxiosRequestConfig = {}): Promise<ApiResponse<T>> {
+  public get<T>(url: string, params?: object, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse<T>> {
     return this.service.get(url, { params, ..._object })
   }
 
-  public post<T>(url: string, data?: object | string, _object: AxiosRequestConfig = {}): Promise<ApiResponse<T>> {
+  public post<T>(url: string, data?: object | string, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse<T>> {
     return this.service.post(url, data, _object)
   }
 
-  public postPage<T>(url: string, data?: object | string, _object: AxiosRequestConfig = {}): Promise<ApiResponse<ApiResponsePage<T>>> {
+  public postPage<T>(url: string, data?: object | string, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse<ApiResponsePage<T>>> {
     return this.post<ApiResponsePage<T>>(url, data, { ..._object, params: { pageNum: 1, pageSize: 10 } })
   }
 
-  public put<T>(url: string, data?: object, _object: AxiosRequestConfig = {}): Promise<ApiResponse<T>> {
+  public put<T>(url: string, data?: object, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse<T>> {
     return this.service.put(url, data, _object)
   }
 
-  public delete<T>(url: string, params?: object, _object: AxiosRequestConfig = {}): Promise<ApiResponse<T>> {
+  public delete<T>(url: string, params?: object, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse<T>> {
     return this.service.delete(url, { params, ..._object })
   }
 
-  public download(url: string, params?: object, _object: AxiosRequestConfig = {}): Promise<BlobPart> {
+  public download(url: string, params?: object, _object: ExtendedAxiosRequestConfig = {}): Promise<BlobPart> {
     return this.service.post(url, params, { ..._object, responseType: 'blob' })
   }
 }

+ 42 - 0
src/utils/http/server.type.ts

@@ -0,0 +1,42 @@
+export interface ApiResponse<T = unknown> {
+  code: number /* 状态码 */
+  msg: string /* 消息 */
+  data?: T /* 数据 */
+}
+export interface ApiResponsePage<T = unknown> {
+  pageNum: number /* 当前页码。从 1 开始计数,表示当前是第几页。 */
+  pageSize: number /* 每页大小。表示每页包含多少条数据。 */
+  totalCount: number /* 总条数。表示数据总共有多少条。 */
+  list: T[] /* 数据列表 */
+}
+
+export enum ApiStatus {
+  success = 0, // 成功
+  error = 400, // 错误
+  unauthorized = 401, // 未授权
+  forbidden = 403, // 禁止访问
+  notFound = 404, // 未找到
+  methodNotAllowed = 405, // 方法不允许
+  requestTimeout = 408, // 请求超时
+  internalServerError = 500, // 服务器错误
+  notImplemented = 501, // 未实现
+  badGateway = 502, // 网关错误
+  serviceUnavailable = 503, // 服务不可用
+  gatewayTimeout = 504, // 网关超时
+  httpVersionNotSupported = 505 // HTTP版本不支持
+}
+
+export const httpStatus: { [key: number]: string } = {
+  400: '请求失败!请您稍后重试',
+  401: '登录失效!请您重新登录',
+  403: '当前账号无权限访问!',
+  404: '你所访问的资源不存在!',
+  405: '请求方式错误!请您稍后重试',
+  406: '请求参数错误!请您稍后重试',
+  408: '请求超时!请您稍后重试',
+  410: '请求资源已被删除!',
+  500: '服务器发生错误,请联系管理员!',
+  502: '🤖 网关错误!',
+  503: '🤖 服务不可用!',
+  504: '🤖 网关超时!'
+}

+ 9 - 83
src/views/auth/login/index.vue

@@ -34,13 +34,6 @@
           <h3 class="title">{{ $t('login.title') }}</h3>
           <p class="sub-title">{{ $t('login.subTitle') }}</p>
           <ElForm ref="formRef" :model="formData" :rules="rules" @keyup.enter="handleSubmit" style="margin-top: 25px">
-            <ElFormItem prop="account">
-              <ElSelect v-model="formData.account" @change="setupAccount" class="account-select">
-                <ElOption v-for="account in accounts" :key="account.key" :label="account.label" :value="account.key">
-                  <span>{{ account.label }}</span>
-                </ElOption>
-              </ElSelect>
-            </ElFormItem>
             <ElFormItem prop="username">
               <ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
             </ElFormItem>
@@ -69,7 +62,6 @@
               <ElCheckbox v-model="formData.rememberPassword">
                 {{ $t('login.rememberPwd') }}
               </ElCheckbox>
-              <RouterLink :to="RoutesAlias.ForgetPassword">{{ $t('login.forgetPwd') }}</RouterLink>
             </div>
 
             <div style="margin-top: 30px">
@@ -77,13 +69,6 @@
                 {{ $t('login.btnText') }}
               </ElButton>
             </div>
-
-            <div class="footer">
-              <p>
-                {{ $t('login.noAccount') }}
-                <RouterLink :to="RoutesAlias.Register">{{ $t('login.register') }}</RouterLink>
-              </p>
-            </div>
           </ElForm>
         </div>
       </div>
@@ -93,7 +78,6 @@
 
 <script setup lang="ts">
   import AppConfig from '@/config'
-  import { RoutesAlias } from '@/router/routesAlias'
   import { ElNotification, ElMessage } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { getCssVar } from '@/utils/ui'
@@ -102,46 +86,16 @@
   import { useI18n } from 'vue-i18n'
   import { HttpError } from '@/utils/http/server.error'
   defineOptions({ name: 'Login' })
-  const dictStore = useDictStore()
+  // 切换主题
+  import { useTheme } from '@/composables/useTheme'
+  import { UserService } from '@/api/sys/user'
+  import { useDictStore } from '@/store/modules/dict'
 
+  const dictStore = useDictStore()
   const { t } = useI18n()
   import { useSettingStore } from '@/store/modules/setting'
   import type { FormInstance, FormRules } from 'element-plus'
 
-  type AccountKey = 'super' | 'admin' | 'user'
-
-  export interface Account {
-    key: AccountKey
-    label: string
-    userName: string
-    password: string
-    roles: string[]
-  }
-
-  const accounts = computed<Account[]>(() => [
-    {
-      key: 'super',
-      label: t('login.roles.super'),
-      userName: 'superAdmin',
-      password: '123456',
-      roles: ['R_SUPER']
-    },
-    {
-      key: 'admin',
-      label: t('login.roles.admin'),
-      userName: 'admin',
-      password: '123456',
-      roles: ['R_ADMIN']
-    },
-    {
-      key: 'user',
-      label: t('login.roles.user'),
-      userName: 'test',
-      password: '123456',
-      roles: ['R_USER']
-    }
-  ])
-
   const settingStore = useSettingStore()
   const { isDark, systemThemeType } = storeToRefs(settingStore)
 
@@ -154,11 +108,10 @@
 
   const systemName = AppConfig.systemInfo.name
   const formRef = ref<FormInstance>()
-
+  const loading = ref(false)
   const formData = reactive({
-    account: '',
-    username: '',
-    password: '',
+    username: 'admin',
+    password: '123456',
     rememberPassword: true
   })
 
@@ -167,24 +120,9 @@
     password: [{ required: true, message: t('login.placeholder[1]'), trigger: 'blur' }]
   }))
 
-  const loading = ref(false)
-
-  onMounted(() => {
-    setupAccount('super')
-  })
-
-  // 设置账号
-  const setupAccount = (key: AccountKey) => {
-    const selectedAccount = accounts.value.find((account: Account) => account.key === key)
-    formData.account = key
-    formData.username = selectedAccount?.userName ?? ''
-    formData.password = selectedAccount?.password ?? ''
-  }
-
   // 登录
   const handleSubmit = async () => {
     if (!formRef.value) return
-
     try {
       // 表单验证
       const valid = await formRef.value.validate()
@@ -195,25 +133,19 @@
         isClickPass.value = true
         return
       }
-
       loading.value = true
-
       // 登录请求
       const { username, password } = formData
-
       const { token, refreshToken } = await UserService.login({ userName: username, password })
-
       // 验证token
       if (!token) {
         throw new Error('Login failed - no token received')
       }
-
       // 存储token和用户信息
       userStore.setToken(token, refreshToken)
       userStore.setLoginStatus(true)
       // 这里可以一次性初始化所有字典
-      dictStore.initAllDicts(DictService.getAllDict)
-
+      dictStore.initAllDicts()
       // 登录成功处理
       showLoginSuccessNotice()
       router.push('/')
@@ -259,12 +191,6 @@
     userStore.setLanguage(lang)
   }
 
-  // 切换主题
-  import { useTheme } from '@/composables/useTheme'
-  import { UserService } from '@/api/sys/user'
-  import { useDictStore } from '@/store/modules/dict'
-  import { DictService } from '@/api/dict.api'
-
   const toggleTheme = () => {
     let { LIGHT, DARK } = SystemThemeEnum
     useTheme().switchThemeStyles(systemThemeType.value === LIGHT ? DARK : LIGHT)

+ 275 - 0
src/views/auth/login/index1.vue

@@ -0,0 +1,275 @@
+<template>
+  <div class="login">
+    <LoginLeftView></LoginLeftView>
+
+    <div class="right-wrap">
+      <div class="top-right-wrap">
+        <div class="btn theme-btn" @click="toggleTheme">
+          <i class="iconfont-sys">
+            {{ isDark ? '&#xe6b5;' : '&#xe725;' }}
+          </i>
+        </div>
+        <ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
+          <div class="btn language-btn">
+            <i class="iconfont-sys icon-language">&#xe611;</i>
+          </div>
+          <template #dropdown>
+            <ElDropdownMenu>
+              <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
+                <ElDropdownItem :command="lang.value" :class="{ 'is-selected': locale === lang.value }">
+                  <span class="menu-txt">{{ lang.label }}</span>
+                  <i v-if="locale === lang.value" class="iconfont-sys icon-check">&#xe621;</i>
+                </ElDropdownItem>
+              </div>
+            </ElDropdownMenu>
+          </template>
+        </ElDropdown>
+      </div>
+      <div class="header">
+        <ArtLogo class="icon" />
+        <h1>{{ systemName }}</h1>
+      </div>
+      <div class="login-wrap">
+        <div class="form">
+          <h3 class="title">{{ $t('login.title') }}</h3>
+          <p class="sub-title">{{ $t('login.subTitle') }}</p>
+          <ElForm ref="formRef" :model="formData" :rules="rules" @keyup.enter="handleSubmit" style="margin-top: 25px">
+            <ElFormItem prop="account">
+              <ElSelect v-model="formData.account" @change="setupAccount" class="account-select">
+                <ElOption v-for="account in accounts" :key="account.key" :label="account.label" :value="account.key">
+                  <span>{{ account.label }}</span>
+                </ElOption>
+              </ElSelect>
+            </ElFormItem>
+            <ElFormItem prop="username">
+              <ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
+            </ElFormItem>
+            <ElFormItem prop="password">
+              <ElInput :placeholder="$t('login.placeholder[1]')" v-model.trim="formData.password" type="password" radius="8px" autocomplete="off" show-password />
+            </ElFormItem>
+            <div class="drag-verify">
+              <div class="drag-verify-content" :class="{ error: !isPassing && isClickPass }">
+                <ArtDragVerify
+                  ref="dragVerify"
+                  v-model:value="isPassing"
+                  :text="$t('login.sliderText')"
+                  textColor="var(--art-gray-800)"
+                  :successText="$t('login.sliderSuccessText')"
+                  :progressBarBg="getCssVar('--el-color-primary')"
+                  background="var(--art-gray-200)"
+                  handlerBg="var(--art-main-bg-color)"
+                />
+              </div>
+              <p class="error-text" :class="{ 'show-error-text': !isPassing && isClickPass }">
+                {{ $t('login.placeholder[2]') }}
+              </p>
+            </div>
+
+            <div class="forget-password">
+              <ElCheckbox v-model="formData.rememberPassword">
+                {{ $t('login.rememberPwd') }}
+              </ElCheckbox>
+              <RouterLink :to="RoutesAlias.ForgetPassword">{{ $t('login.forgetPwd') }}</RouterLink>
+            </div>
+
+            <div style="margin-top: 30px">
+              <ElButton class="login-btn" type="primary" @click="handleSubmit" :loading="loading" v-ripple>
+                {{ $t('login.btnText') }}
+              </ElButton>
+            </div>
+
+            <div class="footer">
+              <p>
+                {{ $t('login.noAccount') }}
+                <RouterLink :to="RoutesAlias.Register">{{ $t('login.register') }}</RouterLink>
+              </p>
+            </div>
+          </ElForm>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import AppConfig from '@/config'
+  import { RoutesAlias } from '@/router/routesAlias'
+  import { ElNotification, ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { getCssVar } from '@/utils/ui'
+  import { languageOptions } from '@/locales'
+  import { LanguageEnum, SystemThemeEnum } from '@/enums/appEnum'
+  import { useI18n } from 'vue-i18n'
+  import { HttpError } from '@/utils/http/server.error'
+  defineOptions({ name: 'Login' })
+  const dictStore = useDictStore()
+
+  const { t } = useI18n()
+  import { useSettingStore } from '@/store/modules/setting'
+  import type { FormInstance, FormRules } from 'element-plus'
+
+  type AccountKey = 'super' | 'admin' | 'user'
+
+  export interface Account {
+    key: AccountKey
+    label: string
+    userName: string
+    password: string
+    roles: string[]
+  }
+
+  const accounts = computed<Account[]>(() => [
+    {
+      key: 'super',
+      label: t('login.roles.super'),
+      userName: 'superAdmin',
+      password: '123456',
+      roles: ['R_SUPER']
+    },
+    {
+      key: 'admin',
+      label: t('login.roles.admin'),
+      userName: 'admin',
+      password: '123456',
+      roles: ['R_ADMIN']
+    },
+    {
+      key: 'user',
+      label: t('login.roles.user'),
+      userName: 'test',
+      password: '123456',
+      roles: ['R_USER']
+    }
+  ])
+
+  const settingStore = useSettingStore()
+  const { isDark, systemThemeType } = storeToRefs(settingStore)
+
+  const dragVerify = ref()
+
+  const userStore = useUserStore()
+  const router = useRouter()
+  const isPassing = ref(false)
+  const isClickPass = ref(false)
+
+  const systemName = AppConfig.systemInfo.name
+  const formRef = ref<FormInstance>()
+
+  const formData = reactive({
+    account: '',
+    username: '',
+    password: '',
+    rememberPassword: true
+  })
+
+  const rules = computed<FormRules>(() => ({
+    username: [{ required: true, message: t('login.placeholder[0]'), trigger: 'blur' }],
+    password: [{ required: true, message: t('login.placeholder[1]'), trigger: 'blur' }]
+  }))
+
+  const loading = ref(false)
+
+  onMounted(() => {
+    setupAccount('super')
+  })
+
+  // 设置账号
+  const setupAccount = (key: AccountKey) => {
+    const selectedAccount = accounts.value.find((account: Account) => account.key === key)
+    formData.account = key
+    formData.username = selectedAccount?.userName ?? ''
+    formData.password = selectedAccount?.password ?? ''
+  }
+
+  // 登录
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+
+    try {
+      // 表单验证
+      const valid = await formRef.value.validate()
+      if (!valid) return
+
+      // 拖拽验证
+      if (!isPassing.value) {
+        isClickPass.value = true
+        return
+      }
+
+      loading.value = true
+
+      // 登录请求
+      const { username, password } = formData
+
+      const { token, refreshToken } = await UserService.login({ userName: username, password })
+
+      // 验证token
+      if (!token) {
+        throw new Error('Login failed - no token received')
+      }
+
+      // 存储token和用户信息
+      userStore.setToken(token, refreshToken)
+      userStore.setLoginStatus(true)
+      // 这里可以一次性初始化所有字典
+      dictStore.initAllDicts()
+
+      // 登录成功处理
+      showLoginSuccessNotice()
+      router.push('/')
+    } catch (error) {
+      // 处理 HttpError
+      if (error instanceof HttpError) {
+        // console.log(error.code)
+      } else {
+        // 处理非 HttpError
+        ElMessage.error('登录失败,请稍后重试')
+        console.error('[Login] Unexpected error:', error)
+      }
+    } finally {
+      loading.value = false
+      resetDragVerify()
+    }
+  }
+
+  // 重置拖拽验证
+  const resetDragVerify = () => {
+    dragVerify.value.reset()
+  }
+
+  // 登录成功提示
+  const showLoginSuccessNotice = () => {
+    setTimeout(() => {
+      ElNotification({
+        title: t('login.success.title'),
+        type: 'success',
+        duration: 2500,
+        zIndex: 10000,
+        message: `${t('login.success.message')}, ${systemName}!`
+      })
+    }, 150)
+  }
+
+  // 切换语言
+  const { locale } = useI18n()
+
+  const changeLanguage = (lang: LanguageEnum) => {
+    if (locale.value === lang) return
+    locale.value = lang
+    userStore.setLanguage(lang)
+  }
+
+  // 切换主题
+  import { useTheme } from '@/composables/useTheme'
+  import { UserService } from '@/api/sys/user'
+  import { useDictStore } from '@/store/modules/dict'
+
+  const toggleTheme = () => {
+    let { LIGHT, DARK } = SystemThemeEnum
+    useTheme().switchThemeStyles(systemThemeType.value === LIGHT ? DARK : LIGHT)
+  }
+</script>
+
+<style lang="scss" scoped>
+  @use './index';
+</style>

+ 0 - 645
src/views/examples/permission/button-auth/index.vue

@@ -1,645 +0,0 @@
-<template>
-  <div class="button-auth-page">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <h2>{{ $t('menus.examples.permission.buttonAuth') }}</h2>
-      <p class="description"> 此页面演示按钮级别的权限控制,不同用户身份下可见的按钮会有所不同。权限控制基于用户角色和权限码实现。 </p>
-    </div>
-
-    <div class="current-auth-card">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>当前用户权限信息</span>
-          </div>
-        </template>
-        <div class="auth-info">
-          <div class="auth-item">
-            <span class="label">用户角色:</span>
-            <ElTag :type="getRoleTagType(currentUserRole)">
-              {{ getRoleDisplayName(currentUserRole) }}
-            </ElTag>
-          </div>
-          <div class="auth-item">
-            <span class="label">权限码:</span>
-            <div class="permissions-list">
-              <ElTag v-for="permission in currentUserPermissions" :key="permission" size="small" type="info" class="permission-tag">
-                {{ permission }}
-              </ElTag>
-              <span v-if="!currentUserPermissions.length" class="no-data">无权限码</span>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 基于角色的权限控制演示 -->
-    <div class="role-based-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>基于角色的权限控制(v-roles 指令)</span>
-          </div>
-        </template>
-        <div class="demo-content">
-          <p class="section-desc"> 使用 <code>v-roles</code> 指令控制按钮显示,只有拥有指定角色的用户才能看到对应按钮。 </p>
-
-          <div class="button-group">
-            <div class="button-item">
-              <ElButton type="primary" plain v-roles="'R_SUPER'"> 超级管理员可见 </ElButton>
-              <div class="button-desc">
-                <code>v-roles="'R_SUPER'"</code>
-                <span>只有超级管理员可见</span>
-              </div>
-            </div>
-
-            <!-- 管理员级别按钮 -->
-            <div class="button-item">
-              <ElButton type="warning" plain v-roles="['R_SUPER', 'R_ADMIN']"> 管理员可见 </ElButton>
-              <div class="button-desc">
-                <code>v-roles="['R_SUPER', 'R_ADMIN']"</code>
-                <span>超级管理员和管理员可见</span>
-              </div>
-            </div>
-
-            <!-- 所有已登录用户可见 -->
-            <div class="button-item">
-              <ElButton type="success" plain v-roles="['R_SUPER', 'R_ADMIN', 'R_USER']"> 所有用户可见 </ElButton>
-              <div class="button-desc">
-                <code>v-roles="['R_SUPER', 'R_ADMIN', 'R_USER']"</code>
-                <span>所有已登录用户可见</span>
-              </div>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 后端模式:基于路由权限配置的控制演示 -->
-    <div class="backend-mode-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>后端模式权限控制(v-auth 指令)</span>
-            <ElTag v-if="!isFrontendMode" type="success" size="small">当前模式</ElTag>
-            <ElTag v-else type="info" size="small">非当前模式</ElTag>
-          </div>
-        </template>
-        <div class="demo-content">
-          <p class="section-desc">
-            <code>v-auth</code>
-            指令在后端模式下根据当前路由的 <code>meta.authList</code> 配置检查权限。 权限列表来源于路由元数据,适用于权限配置由后端统一管理的场景。
-          </p>
-
-          <div class="auth-config-info">
-            <h4>当前路由权限配置:</h4>
-            <div class="config-display">
-              <code>{{ JSON.stringify(backendAuthList, null, 2) }}</code>
-            </div>
-          </div>
-
-          <div class="button-group">
-            <!-- 新增权限 -->
-            <div class="button-item">
-              <ElButton type="primary" plain v-auth="'add'"> 新增 </ElButton>
-              <div class="button-desc">
-                <code>v-auth="'add'"</code>
-                <span>检查路由 meta.authList 中是否存在 authMark: 'add'</span>
-              </div>
-            </div>
-
-            <!-- 编辑权限 -->
-            <div class="button-item">
-              <ElButton type="warning" plain v-auth="'edit'"> 编辑 </ElButton>
-              <div class="button-desc">
-                <code>v-auth="'edit'"</code>
-                <span>检查路由 meta.authList 中是否存在 authMark: 'edit'</span>
-              </div>
-            </div>
-
-            <!-- 删除权限 -->
-            <div class="button-item">
-              <ElButton type="danger" plain v-auth="'delete'"> 删除 </ElButton>
-              <div class="button-desc">
-                <code>v-auth="'delete'"</code>
-                <span>检查路由 meta.authList 中是否存在 authMark: 'delete'</span>
-              </div>
-            </div>
-
-            <!-- 导出权限 -->
-            <div class="button-item">
-              <ElButton type="info" plain v-auth="'export'"> 导出 </ElButton>
-              <div class="button-desc">
-                <code>v-auth="'export'"</code>
-                <span>检查路由 meta.authList 中是否存在 authMark: 'export'</span>
-              </div>
-            </div>
-          </div>
-
-          <div class="mode-note">
-            <ElAlert
-              :type="isFrontendMode ? 'warning' : 'success'"
-              :title="isFrontendMode ? '注意:当前为前端模式,v-auth 指令在后端模式下才生效' : '当前为后端模式,v-auth 指令正常工作'"
-              show-icon
-              :closable="false"
-            />
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 前端模式:编程式权限控制演示 -->
-    <div class="frontend-mode-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>前端模式权限控制(hasAuth 方法)</span>
-            <ElTag v-if="isFrontendMode" type="success" size="small">当前模式</ElTag>
-            <ElTag v-else type="info" size="small">非当前模式</ElTag>
-          </div>
-        </template>
-        <div class="demo-content">
-          <p class="section-desc">
-            <code>hasAuth</code>
-            方法在前端模式下根据用户信息中的 <code>buttons</code> 字段检查权限。 权限列表存储在用户状态中,适用于权限完全由前端管理的场景。
-          </p>
-
-          <div class="auth-config-info">
-            <h4>当前用户权限列表(buttons 字段):</h4>
-            <div class="config-display">
-              <code>{{ JSON.stringify(frontendAuthList, null, 2) }}</code>
-            </div>
-          </div>
-
-          <div class="button-group">
-            <!-- 条件渲染按钮 -->
-            <div class="button-item">
-              <ElButton v-if="hasAuth('view')" type="primary"> 查看详情 </ElButton>
-              <ElButton v-else type="info" disabled> 无查看权限 </ElButton>
-              <div class="button-desc">
-                <code>v-if="hasAuth('view')"</code>
-                <span>检查用户 buttons 数组是否包含 'view'</span>
-              </div>
-            </div>
-
-            <!-- 动态按钮状态 -->
-            <div class="button-item">
-              <ElButton :disabled="!hasAuth('publish')" :type="hasAuth('publish') ? 'success' : 'info'" @click="handlePublish">
-                {{ hasAuth('publish') ? '发布' : '无发布权限' }}
-              </ElButton>
-              <div class="button-desc">
-                <code>:disabled="!hasAuth('publish')"</code>
-                <span>检查用户 buttons 数组是否包含 'publish'</span>
-              </div>
-            </div>
-
-            <!-- 批量操作按钮 -->
-            <div class="button-item">
-              <ElDropdown @command="handleBatchAction" :disabled="!hasBatchPermissions">
-                <ElButton :type="hasBatchPermissions ? 'warning' : 'info'">
-                  批量操作
-                  <ElIcon class="el-icon--right">
-                    <ArrowDown />
-                  </ElIcon>
-                </ElButton>
-                <template #dropdown>
-                  <ElDropdownMenu>
-                    <ElDropdownItem command="batchEdit" :disabled="!hasAuth('edit')"> 批量编辑 </ElDropdownItem>
-                    <ElDropdownItem command="batchDelete" :disabled="!hasAuth('delete')"> 批量删除 </ElDropdownItem>
-                    <ElDropdownItem command="batchExport" :disabled="!hasAuth('export')"> 批量导出 </ElDropdownItem>
-                  </ElDropdownMenu>
-                </template>
-              </ElDropdown>
-              <div class="button-desc">
-                <code>computed(() => hasAuth('edit') || hasAuth('delete'))</code>
-                <span>复合权限检查,检查多个权限的并集</span>
-              </div>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 后端模式:编程式权限控制演示 -->
-    <div class="backend-programmatic-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>后端模式权限控制(hasAuth 方法)</span>
-            <ElTag v-if="!isFrontendMode" type="success" size="small">当前模式</ElTag>
-            <ElTag v-else type="info" size="small">非当前模式</ElTag>
-          </div>
-        </template>
-        <div class="demo-content">
-          <p class="section-desc">
-            <code>hasAuth</code>
-            方法在后端模式下根据当前路由的
-            <code>meta.authList</code> 配置检查权限。与 v-auth 指令使用相同的权限来源,但提供编程式的权限检查能力。
-          </p>
-
-          <div class="button-group">
-            <!-- 动态功能切换 -->
-            <div class="button-item">
-              <ElSwitch v-model="dynamicFeatureEnabled" :disabled="!hasAuth('config')" active-text="功能开启" inactive-text="功能关闭" />
-              <div class="button-desc">
-                <code>:disabled="!hasAuth('config')"</code>
-                <span>检查路由权限控制功能开关</span>
-              </div>
-            </div>
-
-            <!-- 条件显示操作区域 -->
-            <div class="button-item">
-              <div v-if="hasAuth('manage')" class="operation-area">
-                <ElButton type="primary" size="small">管理操作</ElButton>
-                <ElButton type="warning" size="small">高级设置</ElButton>
-              </div>
-              <div v-else class="no-permission-tip">
-                <ElIcon><Lock /></ElIcon>
-                <span>需要管理权限才能查看此区域</span>
-              </div>
-              <div class="button-desc">
-                <code>v-if="hasAuth('manage')"</code>
-                <span>基于后端路由权限的条件渲染</span>
-              </div>
-            </div>
-
-            <!-- 权限状态指示器 -->
-            <div class="button-item">
-              <div class="permission-indicators">
-                <ElBadge :value="hasAuth('add') ? '✓' : '✗'" :type="hasAuth('add') ? 'success' : 'danger'">
-                  <ElButton size="small">新增权限</ElButton>
-                </ElBadge>
-                <ElBadge :value="hasAuth('edit') ? '✓' : '✗'" :type="hasAuth('edit') ? 'success' : 'danger'">
-                  <ElButton size="small">编辑权限</ElButton>
-                </ElBadge>
-                <ElBadge :value="hasAuth('delete') ? '✓' : '✗'" :type="hasAuth('delete') ? 'success' : 'danger'">
-                  <ElButton size="small">删除权限</ElButton>
-                </ElBadge>
-              </div>
-              <div class="button-desc">
-                <code>hasAuth('permission')</code>
-                <span>实时权限状态指示器</span>
-              </div>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 权限模式对比说明 -->
-    <div class="comparison-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>权限控制模式对比</span>
-          </div>
-        </template>
-        <div class="demo-content">
-          <div class="comparison-table">
-            <ElTable :data="comparisonData" border>
-              <ElTableColumn prop="feature" label="功能特性" width="150" />
-              <ElTableColumn prop="frontend" label="前端模式" />
-              <ElTableColumn prop="backend" label="后端模式" />
-            </ElTable>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
-  import { ArrowDown, Lock } from '@element-plus/icons-vue'
-  import { useAuth } from '@/composables/useAuth'
-  import { useUserStore } from '@/store/modules/user'
-  import { useCommon } from '@/composables/useCommon'
-  import { useRoute } from 'vue-router'
-  import type { AppRouteRecord } from '@/types/router'
-
-  defineOptions({ name: 'PermissionButtonAuth' })
-
-  const { hasAuth } = useAuth()
-  const { isFrontendMode } = useCommon()
-  const userStore = useUserStore()
-  const route = useRoute()
-
-  // 动态功能状态
-  const dynamicFeatureEnabled = ref(false)
-
-  // 当前用户角色
-  const currentUserRole = computed(() => {
-    return userStore.info?.roles?.[0] || ''
-  })
-
-  // 当前用户权限码
-  const currentUserPermissions = computed(() => {
-    return userStore.info?.buttons || []
-  })
-
-  // 前端模式权限列表(用户的 buttons 字段)
-  const frontendAuthList = computed(() => {
-    return userStore.info?.buttons || []
-  })
-
-  // 后端模式权限列表(路由 meta.authList 配置)
-  const backendAuthList = computed(() => {
-    type AuthItem = NonNullable<AppRouteRecord['meta']['authList']>[number]
-    return Array.isArray(route.meta.authList) ? (route.meta.authList as AuthItem[]) : []
-  })
-
-  // 是否拥有批量操作权限
-  const hasBatchPermissions = computed(() => {
-    return hasAuth('edit') || hasAuth('delete') || hasAuth('export')
-  })
-
-  // 权限对比数据
-  const comparisonData = computed(() => [
-    {
-      feature: '权限来源',
-      frontend: '用户状态中的 buttons 字段',
-      backend: '路由配置中的 meta.authList'
-    },
-    {
-      feature: 'v-auth 指令',
-      frontend: '不可用(前端模式下指令无效)',
-      backend: '可用(根据路由权限配置检查)'
-    },
-    {
-      feature: 'hasAuth 方法',
-      frontend: '可用(检查 buttons 数组)',
-      backend: '可用(检查 meta.authList)'
-    },
-    {
-      feature: '权限管理',
-      frontend: '完全由前端控制,灵活度高',
-      backend: '后端统一管理,安全性更高'
-    },
-    {
-      feature: '适用场景',
-      frontend: '快速原型、简单应用',
-      backend: '企业级应用、复杂权限体系'
-    }
-  ])
-
-  // 获取角色标签类型
-  const getRoleTagType = (role: string): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
-    const roleMap: Record<string, 'primary' | 'success' | 'info' | 'warning' | 'danger'> = {
-      R_SUPER: 'warning',
-      R_ADMIN: 'primary',
-      R_USER: 'success'
-    }
-    return roleMap[role] || 'info'
-  }
-
-  // 获取角色显示名称
-  const getRoleDisplayName = (role: string) => {
-    const roleMap: Record<string, string> = {
-      R_SUPER: '超级管理员',
-      R_ADMIN: '管理员',
-      R_USER: '普通用户'
-    }
-    return roleMap[role] || '未知角色'
-  }
-
-  // 处理发布操作
-  const handlePublish = () => {
-    if (hasAuth('publish')) {
-      ElMessage.success('发布成功!')
-    } else {
-      ElMessage.warning('您没有发布权限')
-    }
-  }
-
-  // 处理批量操作
-  const handleBatchAction = (command: string) => {
-    const actions: Record<string, string> = {
-      batchEdit: '批量编辑',
-      batchDelete: '批量删除',
-      batchExport: '批量导出'
-    }
-
-    const permission = command.replace('batch', '').toLowerCase()
-
-    if (hasAuth(permission)) {
-      ElMessage.success(`${actions[command]}操作执行成功!`)
-    } else {
-      ElMessage.warning(`您没有${actions[command]}权限`)
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .button-auth-page {
-    width: 100%;
-    padding: 10px 0;
-
-    .page-header {
-      margin-bottom: 24px;
-
-      h2 {
-        margin: 0 0 8px;
-        font-size: 22px;
-        font-weight: 500;
-        color: var(--el-text-color-primary);
-      }
-
-      .description {
-        margin: 0;
-        font-size: 14px;
-        line-height: 1.6;
-        color: var(--el-text-color-regular);
-      }
-    }
-
-    .current-auth-card {
-      margin-bottom: 24px;
-
-      .auth-info {
-        .auth-item {
-          display: flex;
-          align-items: flex-start;
-          margin-bottom: 16px;
-
-          &:last-child {
-            margin-bottom: 0;
-          }
-
-          .label {
-            min-width: 80px;
-            font-weight: 600;
-            color: var(--el-text-color-primary);
-          }
-
-          .permissions-list {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 8px;
-
-            .permission-tag {
-              margin: 0;
-            }
-
-            .no-data {
-              font-style: italic;
-              color: var(--el-text-color-placeholder);
-            }
-          }
-        }
-      }
-    }
-
-    .role-based-section,
-    .backend-mode-section,
-    .frontend-mode-section,
-    .backend-programmatic-section,
-    .comparison-section {
-      margin-bottom: 24px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-
-    .card-header {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      font-weight: 600;
-      color: var(--el-text-color-primary);
-
-      span {
-        flex: 1;
-      }
-    }
-
-    .demo-content {
-      .section-desc {
-        padding: 12px;
-        margin: 0 0 20px;
-        font-size: 14px;
-        line-height: 1.6;
-        color: var(--el-text-color-regular);
-        background: var(--el-bg-color-page);
-        border-radius: 4px;
-        // border-left: 4px solid var(--el-color-primary);
-
-        code {
-          padding: 2px 6px;
-          font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
-          font-size: 13px;
-          color: var(--el-color-primary);
-          background: var(--el-color-primary-light-9);
-          border-radius: 4px;
-        }
-      }
-
-      .button-group {
-        display: grid;
-        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-        gap: 20px;
-
-        .button-item {
-          display: flex;
-          flex-direction: column;
-          gap: 8px;
-
-          .button-desc {
-            font-size: 12px;
-            color: var(--el-text-color-secondary);
-
-            code {
-              display: block;
-              padding: 4px 8px;
-              margin-bottom: 4px;
-              font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
-              color: var(--el-text-color-primary);
-              background: var(--el-fill-color-light);
-              border: 1px solid var(--el-border-color-lighter);
-              border-radius: 4px;
-            }
-
-            span {
-              color: var(--el-text-color-regular);
-            }
-          }
-        }
-      }
-
-      .auth-config-info {
-        margin-bottom: 20px;
-
-        h4 {
-          margin: 0 0 8px;
-          font-size: 14px;
-          font-weight: 600;
-          color: var(--el-text-color-primary);
-        }
-
-        .config-display {
-          max-height: 120px;
-          padding: 12px;
-          overflow-y: auto;
-          font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
-          font-size: 12px;
-          color: var(--el-text-color-primary);
-          word-break: break-all;
-          white-space: pre-wrap;
-          background: var(--el-fill-color-light);
-          border: 1px solid var(--el-border-color-lighter);
-          border-radius: 4px;
-        }
-      }
-
-      .mode-note {
-        margin-top: 16px;
-      }
-
-      .operation-area {
-        display: flex;
-        gap: 8px;
-        padding: 12px;
-        background: var(--el-bg-color-page);
-        border: 1px solid var(--el-border-color-lighter);
-        border-radius: 4px;
-      }
-
-      .no-permission-tip {
-        display: flex;
-        gap: 8px;
-        align-items: center;
-        padding: 12px;
-        color: var(--el-text-color-placeholder);
-        background: var(--el-fill-color-light);
-        border: 1px dashed var(--el-border-color);
-        border-radius: 4px;
-      }
-
-      .permission-indicators {
-        display: flex;
-        flex-wrap: wrap;
-        gap: 12px;
-      }
-
-      .comparison-table {
-        margin-top: 16px;
-      }
-    }
-  }
-
-  @media (width <= 768px) {
-    .button-auth-page {
-      padding: 16px 0;
-
-      .permission-indicators {
-        flex-direction: column;
-        gap: 8px;
-      }
-
-      .operation-area {
-        flex-direction: column;
-      }
-    }
-  }
-</style>

+ 0 - 411
src/views/examples/permission/page-visibility/index.vue

@@ -1,411 +0,0 @@
-<template>
-  <div class="page-visibility-page">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <h2>{{ $t('menus.examples.permission.pageVisibility') }}</h2>
-      <p class="description"> 此页面仅对<strong>超级管理员</strong>用户可见,演示页面级别的权限控制。 如果您能看到此页面,说明您拥有相应的访问权限。 </p>
-    </div>
-
-    <div class="success-card">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>权限验证成功</span>
-          </div>
-        </template>
-        <div class="success-content">
-          <div class="success-info">
-            <div class="success-text">
-              <h3>您拥有访问此页面的权限</h3>
-              <p>
-                当前用户:<strong>{{ currentUser.userName }}</strong>
-              </p>
-              <p>
-                用户角色:
-                <ElTag type="warning">{{ getRoleDisplayName(currentUser.roles?.[0] || '') }}</ElTag>
-              </p>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 页面级权限控制说明 -->
-    <div class="permission-explanation">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>页面级权限控制说明</span>
-          </div>
-        </template>
-        <div class="explanation-content">
-          <ElTimeline>
-            <ElTimelineItem timestamp="前端控制模式" type="primary" size="large">
-              <ElCard shadow="never">
-                <h4>基于角色的权限控制</h4>
-                <p>
-                  在前端控制模式下,页面访问权限由路由配置文件中的 <code>meta.roles</code>
-                  字段定义,前端会根据用户接口所拥有的角色对路由和菜单进行过滤与控制
-                </p>
-                <pre><code>{
-  path: 'page-visibility',
-  name: 'PermissionPageVisibility',
-  component: RoutesAlias.PermissionPageVisibility,
-  meta: {
-    title: 'menus.permission.pageVisibility',
-    roles: ['R_SUPER'], // 仅超级管理员可访问
-    keepAlive: true
-  }
-}</code></pre>
-                <p><strong>权限验证流程:</strong></p>
-                <ul>
-                  <li>用户登录后,接口返回用户角色信息(如 R_SUPER、R_ADMIN、R_USER)</li>
-                  <li> 在 <code>beforeEach</code> 路由守卫中检查目标路由的 <code>roles</code> 配置 </li>
-                  <li>比较用户角色是否包含在允许访问的角色列表中</li>
-                  <li>权限不足时跳转到 403 页面</li>
-                </ul>
-              </ElCard>
-            </ElTimelineItem>
-
-            <ElTimelineItem timestamp="后端控制模式" type="warning" size="large">
-              <ElCard shadow="never">
-                <h4>基于菜单接口的权限控制</h4>
-                <p>在后端控制模式下,页面访问权限由后端统一管理,前端通过解析后端接口返回的菜单列表来生成可访问的路由,从而实现权限控制</p>
-                <p>接口地址:src/api/menuApi.ts getMenuList</p>
-                <pre><code>
-{
-  "code": 200,
-  "data": [
-    {
-      "id": 1,
-      "path": "/permission",
-      "name": "Permission",
-      "component": "Layout",
-      "meta": {
-        "title": "menus.permission.title",
-        "icon": ""
-      },
-      "children": [
-        {
-          "id": 11,
-          "path": "page-visibility",
-          "name": "PermissionPageVisibility",
-          "component": "permission/page-visibility/index",
-          "meta": {
-            "title": "menus.permission.pageVisibility",
-            "keepAlive": true
-          }
-        }
-      ]
-    }
-  ]
-}</code></pre>
-                <p><strong>权限验证流程:</strong></p>
-                <ul>
-                  <li>用户登录成功后获取 Token</li>
-                  <li>前端调用菜单接口获取用户可访问的菜单列表</li>
-                  <li>前端根据菜单列表动态注册路由</li>
-                  <li>菜单中存在的页面用户可以正常访问,不存在的页面会跳转到 404</li>
-                </ul>
-              </ElCard>
-            </ElTimelineItem>
-
-            <ElTimelineItem timestamp="菜单显示控制" type="success" size="large">
-              <ElCard shadow="never">
-                <h4>侧边栏菜单可见性</h4>
-                <p><strong>前端控制模式:</strong></p>
-                <ul>
-                  <li>有权限的用户:菜单项正常显示,可以点击访问</li>
-                  <li>无权限的用户:菜单项不显示,无法通过菜单导航到页面</li>
-                  <li>即使通过直接输入URL尝试访问,也会被路由守卫拦截</li>
-                </ul>
-                <p><strong>后端控制模式:</strong></p>
-                <ul>
-                  <li>侧边栏菜单根据后端返回的菜单列表进行渲染</li>
-                  <li>后端应该根据用户权限过滤,只返回用户有权限访问的菜单项</li>
-                  <li>前端只显示后端返回的菜单,确保用户只能看到和访问有权限的页面</li>
-                </ul>
-              </ElCard>
-            </ElTimelineItem>
-          </ElTimeline>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 权限控制最佳实践 -->
-    <div class="best-practices">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>权限控制最佳实践</span>
-          </div>
-        </template>
-        <div class="practices-content">
-          <ElRow :gutter="24">
-            <ElCol :span="12">
-              <div class="practice-item">
-                <div class="practice-icon">
-                  <ElIcon size="20" color="#409EFF"><Lock /></ElIcon>
-                </div>
-                <div class="practice-content">
-                  <h4>多层权限验证</h4>
-                  <p>在前端路由、后端接口、UI组件等多个层面实施权限控制,确保安全性。</p>
-                </div>
-              </div>
-            </ElCol>
-            <ElCol :span="12">
-              <div class="practice-item">
-                <div class="practice-icon">
-                  <ElIcon size="20" color="#67C23A"><User /></ElIcon>
-                </div>
-                <div class="practice-content">
-                  <h4>基于角色的访问控制</h4>
-                  <p>采用RBAC模型,通过角色分配权限,简化权限管理复杂度。</p>
-                </div>
-              </div>
-            </ElCol>
-            <ElCol :span="12">
-              <div class="practice-item">
-                <div class="practice-icon">
-                  <ElIcon size="20" color="#E6A23C"><Key /></ElIcon>
-                </div>
-                <div class="practice-content">
-                  <h4>细粒度权限控制</h4>
-                  <p>支持页面级、按钮级、数据级等多种粒度的权限控制。</p>
-                </div>
-              </div>
-            </ElCol>
-            <ElCol :span="12">
-              <div class="practice-item">
-                <div class="practice-icon">
-                  <ElIcon size="20" color="#F56C6C"><View /></ElIcon>
-                </div>
-                <div class="practice-content">
-                  <h4>安全性优先原则</h4>
-                  <p>始终遵循最小权限原则,确保用户只能访问必要的功能和数据。</p>
-                </div>
-              </div>
-            </ElCol>
-          </ElRow>
-        </div>
-      </ElCard>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { computed } from 'vue'
-
-  import { Lock, User, Key, View } from '@element-plus/icons-vue'
-  import { useUserStore } from '@/store/modules/user'
-
-  defineOptions({ name: 'PermissionPageVisibility' })
-
-  const userStore = useUserStore()
-
-  // 当前用户信息
-  const currentUser = computed(() => userStore.info)
-
-  // 获取角色显示名称
-  const getRoleDisplayName = (role: string) => {
-    const roleMap: Record<string, string> = {
-      R_SUPER: '超级管理员',
-      R_ADMIN: '管理员',
-      R_USER: '普通用户'
-    }
-    return roleMap[role] || '未知角色'
-  }
-</script>
-
-<style lang="scss" scoped>
-  .page-visibility-page {
-    width: 100%;
-    padding: 10px 0;
-
-    .page-header {
-      margin-bottom: 24px;
-
-      h2 {
-        margin: 0 0 8px;
-        font-size: 22px;
-        font-weight: 500;
-        color: var(--el-text-color-primary);
-      }
-
-      .description {
-        margin: 0;
-        font-size: 14px;
-        line-height: 1.6;
-        color: var(--el-text-color-regular);
-
-        strong {
-          font-weight: 600;
-          color: var(--el-color-warning);
-        }
-      }
-    }
-
-    .success-card {
-      margin-bottom: 24px;
-
-      .card-header {
-        display: flex;
-        gap: 8px;
-        align-items: center;
-        font-weight: 600;
-
-        &.success {
-          color: var(--el-color-success);
-        }
-      }
-
-      .success-content {
-        .success-info {
-          display: flex;
-          gap: 20px;
-          align-items: center;
-
-          .success-text {
-            h3 {
-              margin: 0 0 8px;
-              font-size: 18px;
-              font-weight: 600;
-              color: var(--el-text-color-primary);
-            }
-
-            p {
-              margin: 4px 0;
-              font-size: 14px;
-              color: var(--el-text-color-regular);
-
-              strong {
-                font-weight: 600;
-                color: var(--el-text-color-primary);
-              }
-            }
-          }
-        }
-      }
-    }
-
-    .permission-explanation,
-    .best-practices {
-      margin-bottom: 24px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-
-    .card-header {
-      display: flex;
-      align-items: center;
-      font-weight: 600;
-      color: var(--el-text-color-primary);
-    }
-
-    .explanation-content {
-      pre {
-        padding: 16px;
-        margin: 12px 0 0;
-        overflow-x: auto;
-        font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
-        font-size: 13px;
-        line-height: 1.5;
-        background: var(--el-fill-color-light);
-        border: 1px solid var(--el-border-color-lighter);
-        border-radius: 6px;
-
-        code {
-          color: var(--el-text-color-primary);
-        }
-      }
-
-      h4 {
-        margin: 0 0 8px;
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--el-text-color-primary);
-      }
-
-      p {
-        margin: 0 0 8px;
-        line-height: 1.6;
-        color: var(--el-text-color-regular);
-
-        code {
-          padding: 2px 6px;
-          font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
-          font-size: 13px;
-          color: var(--el-color-primary);
-          background: var(--el-color-primary-light-9);
-          border-radius: 4px;
-        }
-      }
-
-      ul {
-        padding-left: 20px;
-        margin: 8px 0;
-
-        li {
-          margin: 4px 0;
-          line-height: 1.5;
-          color: var(--el-text-color-regular);
-        }
-      }
-    }
-
-    .practices-content {
-      .practice-item {
-        display: flex;
-        gap: 16px;
-        align-items: flex-start;
-        margin-bottom: 15px !important;
-
-        &:last-child {
-          margin-bottom: 0;
-        }
-
-        .practice-icon {
-          display: flex;
-          flex-shrink: 0;
-          align-items: center;
-          justify-content: center;
-          width: 44px;
-          height: 44px;
-          background: var(--el-fill-color-light);
-          border-radius: 8px;
-        }
-
-        .practice-content {
-          flex: 1;
-
-          h4 {
-            font-size: 16px;
-            font-weight: 600;
-            color: var(--el-text-color-primary);
-          }
-
-          p {
-            margin: 0;
-            font-size: 14px;
-            line-height: 1.6;
-            color: var(--el-text-color-regular);
-          }
-        }
-      }
-    }
-  }
-
-  @media (width <= 768px) {
-    .page-visibility-page {
-      padding: 16px 0;
-
-      .success-info {
-        flex-direction: column;
-        gap: 16px !important;
-        text-align: center;
-      }
-    }
-  }
-</style>

+ 0 - 305
src/views/examples/permission/switch-role/index.vue

@@ -1,305 +0,0 @@
-<template>
-  <div class="switch-role-page">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <h2>权限切换演示</h2>
-      <p class="description"> 点击下方按钮切换不同用户身份,模拟不同用户登录系统的效果。切换后会影响整个系统的菜单显示和按钮权限。 </p>
-    </div>
-
-    <!-- 当前用户信息 -->
-    <div class="current-user-card">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>当前登录用户</span>
-          </div>
-        </template>
-        <div class="user-info">
-          <div class="user-details">
-            <div class="info-item">
-              <span class="label">用户名:</span>
-              <span class="value">{{ currentUser.userName || '未登录' }}</span>
-            </div>
-            <div class="info-item">
-              <span class="label">角色:</span>
-              <ElTag :type="getRoleTagType(currentUser.roles?.[0])">
-                {{ getRoleDisplayName(currentUser.roles?.[0]) }}
-              </ElTag>
-            </div>
-            <div class="info-item">
-              <span class="label">权限码:</span>
-              <div class="buttons-list">
-                <ElTag v-for="button in currentUser.buttons" :key="button" size="small" type="info" class="button-tag">
-                  {{ button }}
-                </ElTag>
-                <span v-if="!currentUser.buttons?.length" class="no-data">无权限码</span>
-              </div>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-
-    <!-- 用户角色切换 -->
-    <div class="role-switch-section">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="card-header">
-            <span>账号切换</span>
-          </div>
-        </template>
-        <div class="role-buttons">
-          <div v-for="account in accounts" :key="account.key" class="role-card" :class="{ active: currentUser.userName === account.userName }">
-            <div class="role-info">
-              <div class="role-details">
-                <h4>{{ account.label }}</h4>
-                <p>{{ account.description }}</p>
-                <div class="role-meta">
-                  <span>用户名: {{ account.userName }}</span>
-                  <span>角色: {{ account.roles.join(', ') }}</span>
-                </div>
-              </div>
-            </div>
-            <div class="role-actions">
-              <ElButton v-if="currentUser.userName !== account.userName" type="primary" @click="switchRole(account)" :loading="switching"> 切换到此角色 </ElButton>
-              <ElTag v-else type="success">当前用户</ElTag>
-            </div>
-          </div>
-        </div>
-      </ElCard>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
-  import { useUserStore } from '@/store/modules/user'
-  import { useI18n } from 'vue-i18n'
-  import { fetchLogin, fetchGetUserInfo } from '@/api/auth'
-
-  defineOptions({ name: 'PermissionSwitchRole' })
-
-  const { t } = useI18n()
-  const userStore = useUserStore()
-
-  // 响应式数据
-  const switching = ref(false)
-
-  // 当前用户信息
-  const currentUser = computed(() => userStore.info)
-
-  // 账号列表 - 与登录页面保持一致
-  const accounts = computed(() => [
-    {
-      key: 'super',
-      label: t('login.roles.super'),
-      userName: 'Super',
-      password: '123456',
-      roles: ['R_SUPER'],
-      color: '#E6A23C',
-      description: '拥有系统最高权限,可以访问所有功能模块'
-    },
-    {
-      key: 'admin',
-      label: t('login.roles.admin'),
-      userName: 'Admin',
-      password: '123456',
-      roles: ['R_ADMIN'],
-      color: '#409EFF',
-      description: '拥有管理权限,可以管理用户和部分系统设置'
-    },
-    {
-      key: 'user',
-      label: t('login.roles.user'),
-      userName: 'User',
-      password: '123456',
-      roles: ['R_USER'],
-      color: '#67C23A',
-      description: '普通用户权限,只能访问基础功能模块'
-    }
-  ])
-
-  // 获取角色标签类型
-  const getRoleTagType = (role?: string): 'info' | 'warning' | 'primary' | 'success' | 'danger' => {
-    if (!role) return 'info'
-    const roleMap: Record<string, 'info' | 'warning' | 'primary' | 'success' | 'danger'> = {
-      R_SUPER: 'warning',
-      R_ADMIN: 'primary',
-      R_USER: 'success'
-    }
-    return roleMap[role] || 'info'
-  }
-
-  // 获取角色显示名称
-  const getRoleDisplayName = (role?: string): string => {
-    if (!role) return '未知角色'
-    const roleMap: Record<string, string> = {
-      R_SUPER: '超级管理员',
-      R_ADMIN: '管理员',
-      R_USER: '普通用户'
-    }
-    return roleMap[role] || '未知角色'
-  }
-
-  // 切换角色
-  const switchRole = async (account: any) => {
-    try {
-      switching.value = true
-
-      // 模拟登录请求
-      const { token, refreshToken } = await fetchLogin({
-        userName: account.userName,
-        password: account.password
-      })
-
-      // 验证token
-      if (!token) {
-        throw new Error('Login failed - no token received')
-      }
-
-      // 存储token和用户信息
-      userStore.setToken(token, refreshToken)
-      const userInfo = await fetchGetUserInfo()
-      userStore.setUserInfo(userInfo)
-
-      // 延迟刷新页面以应用新权限
-      setTimeout(() => {
-        window.location.reload()
-      }, 100)
-    } catch (error) {
-      if (error !== 'cancel') {
-        ElMessage.error('切换用户身份失败,请稍后重试')
-        console.error('[SwitchRole] Error:', error)
-      }
-    } finally {
-      switching.value = false
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .switch-role-page {
-    padding: 10px 0;
-
-    .page-header {
-      margin-bottom: 24px;
-
-      h2 {
-        margin: 0 0 8px;
-        font-size: 22px;
-        font-weight: 500;
-      }
-
-      .description {
-        margin: 0;
-        line-height: 1.6;
-        color: var(--el-text-color-regular);
-      }
-    }
-
-    .current-user-card,
-    .role-switch-section {
-      margin-bottom: 24px;
-    }
-
-    .card-header {
-      font-weight: 600;
-    }
-
-    .info-item {
-      display: flex;
-      align-items: flex-start;
-      margin-bottom: 12px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-
-      .label {
-        min-width: 80px;
-        font-weight: 600;
-      }
-
-      .buttons-list {
-        display: flex;
-        flex-wrap: wrap;
-        gap: 8px;
-
-        .no-data {
-          font-style: italic;
-          color: var(--el-text-color-placeholder);
-        }
-      }
-    }
-
-    .role-buttons {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
-      gap: 16px;
-    }
-
-    .role-card {
-      padding: 20px;
-      border: 1.5px solid var(--el-border-color-light);
-      border-radius: 8px;
-      transition: all 0.3s ease;
-
-      &:hover {
-        border-color: var(--el-color-primary);
-        box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
-      }
-
-      &.active {
-        background: var(--el-color-success-light-9);
-        border-color: var(--el-color-success);
-      }
-
-      .role-info {
-        margin-bottom: 16px;
-      }
-
-      .role-details {
-        h4 {
-          margin: 0 0 8px;
-          font-size: 16px;
-          font-weight: 600;
-        }
-
-        p {
-          margin: 0 0 8px;
-          line-height: 1.5;
-          color: var(--el-text-color-regular);
-        }
-
-        .role-meta {
-          display: flex;
-          flex-direction: column;
-          gap: 4px;
-
-          span {
-            font-size: 12px;
-            color: var(--el-text-color-secondary);
-          }
-        }
-      }
-
-      .role-actions {
-        text-align: right;
-      }
-    }
-
-    @media (width <= 768px) {
-      padding: 16px 0;
-
-      .role-buttons {
-        grid-template-columns: 1fr;
-      }
-
-      .user-info {
-        flex-direction: column;
-        gap: 16px;
-      }
-    }
-  }
-</style>