zhanghaijun 1 год назад
Родитель
Сommit
4cb593b032

BIN
src/assets/img/user/avatar.webp


+ 10 - 2
src/components/core/layouts/art-header-bar/index.vue

@@ -135,9 +135,17 @@
                   </div>
                 </div>
                 <ul class="user-menu">
-                  <li @click="goPage('/system/user-center')">
+                  <li @click="goPage('/system/users')">
                     <i class="menu-icon iconfont-sys">&#xe734;</i>
-                    <span class="menu-txt">{{ $t('topBar.user.userCenter') }}</span>
+                    <span class="menu-txt">用户列表</span>
+                  </li>
+                  <li @click="goPage('/system/roles')">
+                    <i class="menu-icon iconfont-sys">&#xe733;</i>
+                    <span class="menu-txt">角色管理</span>
+                  </li>
+                  <li @click="goPage('/system/dictionary')">
+                    <i class="menu-icon iconfont-sys">&#xe732;</i>
+                    <span class="menu-txt">字典管理</span>
                   </li>
                   <li @click="lockScreen()">
                     <i class="menu-icon iconfont-sys">&#xe817;</i>

+ 6 - 30
src/components/core/layouts/art-screen-lock/index.vue

@@ -22,14 +22,7 @@
           <div class="username">{{ userInfo.userName }}</div>
           <ElForm ref="formRef" :model="formData" :rules="rules" @submit.prevent="handleLock">
             <ElFormItem prop="password">
-              <ElInput
-                v-model="formData.password"
-                type="password"
-                :placeholder="$t('lockScreen.lock.inputPlaceholder')"
-                :show-password="true"
-                ref="lockInputRef"
-                @keyup.enter="handleLock"
-              >
+              <ElInput v-model="formData.password" type="password" :placeholder="$t('lockScreen.lock.inputPlaceholder')" :show-password="true" ref="lockInputRef" @keyup.enter="handleLock">
                 <template #suffix>
                   <ElIcon class="cursor-pointer" @click="handleLock">
                     <Lock />
@@ -50,20 +43,9 @@
       <div class="box">
         <img class="cover" src="@imgs/user/avatar.webp" alt="用户头像" />
         <div class="username">{{ userInfo.userName }}</div>
-        <ElForm
-          ref="unlockFormRef"
-          :model="unlockForm"
-          :rules="rules"
-          @submit.prevent="handleUnlock"
-        >
+        <ElForm ref="unlockFormRef" :model="unlockForm" :rules="rules" @submit.prevent="handleUnlock">
           <ElFormItem prop="password">
-            <ElInput
-              v-model="unlockForm.password"
-              type="password"
-              :placeholder="$t('lockScreen.unlock.inputPlaceholder')"
-              :show-password="true"
-              ref="unlockInputRef"
-            >
+            <ElInput v-model="unlockForm.password" type="password" :placeholder="$t('lockScreen.unlock.inputPlaceholder')" :show-password="true" ref="unlockInputRef">
               <template #suffix>
                 <ElIcon class="cursor-pointer" @click="handleUnlock">
                   <Unlock />
@@ -134,9 +116,7 @@
 
   // 检测是否为移动设备
   const isMobile = () => {
-    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
-      navigator.userAgent
-    )
+    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
   }
 
   // 添加禁用控制台的函数
@@ -270,9 +250,7 @@
     const checkDevTools = () => {
       if (!isLock.value || isMobile()) return
 
-      const isDevToolsOpen =
-        window.outerHeight - window.innerHeight > threshold ||
-        window.outerWidth - window.innerWidth > threshold
+      const isDevToolsOpen = window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold
 
       if (isDevToolsOpen && !devtools.open) {
         devtools.open = true
@@ -303,9 +281,7 @@
   // 工具函数
   const verifyPassword = (inputPassword: string, storedPassword: string): boolean => {
     try {
-      const decryptedPassword = CryptoJS.AES.decrypt(storedPassword, ENCRYPT_KEY).toString(
-        CryptoJS.enc.Utf8
-      )
+      const decryptedPassword = CryptoJS.AES.decrypt(storedPassword, ENCRYPT_KEY).toString(CryptoJS.enc.Utf8)
       return inputPassword === decryptedPassword
     } catch (error) {
       console.error('密码解密失败:', error)

+ 59 - 39
src/router/routes/modules/train.ts

@@ -2,26 +2,28 @@ import { RoutesAlias } from '@/router/routesAlias'
 import { AppRouteRecord } from '@/types/router'
 
 export const routes: AppRouteRecord[] = [
+  // 教员培训
   {
-    path: '/monitor',
-    name: 'monitor',
+    path: '/trainer',
+    name: 'trainer',
     component: RoutesAlias.Layout,
-    meta: { title: '系统监控', icon: 'ant-design:dashboard-outlined' },
+    meta: { title: '教员培训', icon: 'ant-design:team-outlined' },
     children: [
       {
-        path: 'run',
-        name: 'monitor:run',
-        component: '/train/monitor/course',
-        meta: { title: '课程运行', icon: 'ant-design:dashboard-outlined' }
+        path: '/course/list',
+        name: 'trainer:course-list',
+        component: '/train/trainer/course-list',
+        meta: { title: '课程列表', icon: 'ant-design:unordered-list-outlined' }
       },
       {
-        path: '/device',
-        name: 'monitor:device',
-        component: '/train/monitor/device',
-        meta: { title: '设备监控', icon: 'ant-design:desktop-outlined' }
+        path: '/course/run',
+        name: 'trainer:course-run',
+        component: '/train/trainer/course-run',
+        meta: { title: '课程运行', icon: 'ant-design:play-circle-outlined' }
       }
     ]
   },
+  // 课程管理
   {
     path: '/course',
     name: 'course',
@@ -29,56 +31,74 @@ export const routes: AppRouteRecord[] = [
     meta: { title: '课程管理', icon: 'ant-design:book-outlined' },
     children: [
       {
-        path: 'list',
+        path: '/list',
         name: 'course:list',
-        component: '/train/course/index',
+        component: '/train/course/list',
         meta: { title: '课程列表', icon: 'ant-design:book-outlined' }
       },
       {
-        path: 'detail/:id',
+        path: '/detail/:id',
         name: 'course:detail',
         component: '/train/course/detail',
-        meta: { title: '课程详情', isHide: true, keepAlive: false }
-      },
+        meta: { title: '课程详情', isHide: true, keepAlive: false, icon: 'ant-design:file-text-outlined' }
+      }
+    ]
+  },
+  // 故障管理
+  {
+    path: '/fault',
+    name: 'fault',
+    component: RoutesAlias.Layout,
+    meta: { title: '故障管理', icon: 'ant-design:warning-outlined' },
+    children: [
       {
-        path: 'evaluate',
-        name: 'course/evaluate',
-        component: '/train/course/evaluate',
-        meta: { title: '评价管理', icon: 'ant-design:message-outlined' }
+        path: '/list',
+        name: 'fault:list',
+        component: '/train/fault/list',
+        meta: { title: '故障列表', icon: 'ant-design:warning-outlined' }
       }
     ]
   },
+
+  // 评价中心
   {
-    path: '/plan',
-    name: 'plan',
+    path: '/evaluation',
+    name: 'evaluation',
     component: RoutesAlias.Layout,
-    meta: { title: '培训计划', icon: 'ant-design:profile-outlined' },
+    meta: { title: '评价中心', icon: 'ant-design:star-outlined' },
     children: [
       {
         path: 'list',
-        name: 'plan/list',
-        component: '/train/plan/index',
-        meta: { title: '计划列表', icon: 'ant-design:profile-outlined' }
-      },
-      {
-        path: 'detail/:id',
-        name: 'plan:detail',
-        component: '/train/plan/detail',
-        meta: { title: '计划详情', isHide: true, keepAlive: false }
+        name: 'evaluation:list',
+        component: '/train/evaluation/list',
+        meta: { title: '评价列表', icon: 'ant-design:star-outlined' }
       }
     ]
   },
+  // 系统管理
   {
-    path: '/manager',
-    name: 'manager',
+    path: '/system',
+    name: 'system',
     component: RoutesAlias.Layout,
-    meta: { title: '系统设置', icon: 'ant-design:setting-outlined' },
+    meta: { title: '系统管理', icon: 'ant-design:setting-outlined' },
     children: [
       {
-        path: 'user',
-        name: 'manager:user',
-        component: '/train/manager/user',
-        meta: { title: '管理员列表', icon: 'ant-design:user-outlined' }
+        path: '/users',
+        name: 'system:admin-users',
+        component: '/train/system/users',
+        meta: { title: '用户列表', icon: 'ant-design:user-outlined' }
+      },
+      {
+        path: '/roles',
+        name: 'system:roles',
+        component: '/train/system/roles',
+        meta: { title: '角色管理', icon: 'ant-design:team-outlined' }
+      },
+      {
+        path: '/dictionary',
+        name: 'system:dictionary',
+        component: '/train/system/dictionary',
+        meta: { title: '字典管理', icon: 'ant-design:database-outlined' }
       }
     ]
   }

+ 1 - 1
src/store/modules/user.ts

@@ -96,7 +96,7 @@ export const useUserStore = defineStore(
      * @param newRefreshToken 刷新令牌(可选)
      */
     const setToken = (newAccessToken: string, newRefreshToken?: string) => {
-      accessToken.value = `Bearer ${newAccessToken}`
+      accessToken.value = `${newAccessToken}`
       if (newRefreshToken) {
         refreshToken.value = newRefreshToken
       }

+ 26 - 5
src/views/dashboard/console/index.vue

@@ -5,20 +5,41 @@
       <div class="col-span-2 row-span-2 flex items-center justify-center rounded-2xl bg-green-500 text-xl font-bold text-white shadow-md">
         <div class="text-2xl font-extrabold">教员培训</div>
         <div class="text-lg font-semibold">二级标题</div>
-        <el-button class="rounded-2xl" type="primary" size="large" @click="() => router.push('/')"> 
+        <el-button class="rounded-2xl" type="primary" size="large" @click="() => router.push('/trainer/course/list')">
           <span>开始学习课程</span>
-          <Icon :icon="'&#xe792;'"/>  
+          <Icon :icon="'&#xe792;'" />
         </el-button>
       </div>
 
       <!-- 课程编辑 -->
-      <div class="col-span-2 flex items-center justify-center rounded-2xl bg-purple-500 text-lg font-semibold text-white shadow-md"> 课程编辑 </div>
+      <div class="col-span-2 flex items-center justify-center rounded-2xl bg-purple-500 text-lg font-semibold text-white shadow-md">
+        <div class="text-2xl font-extrabold">课程编辑</div>
+        <div class="text-lg font-semibold">二级标题</div>
+        <el-button class="rounded-2xl" type="primary" size="large" @click="() => router.push('/course/list')">
+          <span>开始学习课程</span>
+          <Icon :icon="'&#xe792;'" />
+        </el-button>
+      </div>
 
       <!-- 故障管理 -->
-      <div class="flex items-center justify-center rounded-2xl bg-teal-500 text-lg font-semibold text-white shadow-md"> 故障管理 </div>
+      <div class="flex items-center justify-center rounded-2xl bg-teal-500 text-lg font-semibold text-white shadow-md">
+        <div class="text-2xl font-extrabold">故障管理</div>
+        <div class="text-lg font-semibold">二级标题</div>
+        <el-button class="rounded-2xl" type="primary" size="large" @click="() => router.push('/fault/list')">
+          <span>开始学习课程</span>
+          <Icon :icon="'&#xe792;'" />
+        </el-button>
+      </div>
 
       <!-- 评价中心 -->
-      <div class="flex aspect-square items-center justify-center rounded-2xl bg-rose-500 text-lg font-semibold text-white shadow-md"> 评价中心 </div>
+      <div class="flex aspect-square items-center justify-center rounded-2xl bg-rose-500 text-lg font-semibold text-white shadow-md">
+        <div class="text-2xl font-extrabold">评价中心</div>
+        <div class="text-lg font-semibold">二级标题</div>
+        <el-button class="rounded-2xl" type="primary" size="large" @click="() => router.push('/evaluation/list')">
+          <span>开始学习课程</span>
+          <Icon :icon="'&#xe792;'" />
+        </el-button>
+      </div>
     </div>
   </div>
 </template>

+ 0 - 17
src/views/train/course/detail.vue

@@ -1,17 +0,0 @@
-<!--
-@description: 课程详情
-@creationTime: 2025-09-23
--->
-
-<template>
-  <view>课程详情</view>
-</template>
-
-<script lang="ts" setup>
-  defineOptions({ name: '课程详情' })
-  /* ------------------------ 导入 与 引用 ----------------------------------- */
-  /* ------------------------ 函数 与 方法 ----------------------------------- */
-  /* ------------------------- 生命周期 -------------------------------------- */
-</script>
-
-<style lang="scss" scoped></style>

src/views/train/monitor/data/flow.json → src/views/train/course/detail/flow.json


+ 1 - 1
src/views/train/monitor/course.vue

@@ -17,7 +17,7 @@
   import ProcessDesigner from '@/components/business/EasyProcess/index.vue'
   import { ElMessageBox } from 'element-plus'
   import { onMounted, ref } from 'vue'
-  import mockData from './data/flow.json'
+  import mockData from './flow.json'
 
   // 定义 ProcessDesigner 的实例类型
   // type ProcessDesignerInstance = InstanceType<typeof ProcessDesigner>

+ 0 - 17
src/views/train/course/evaluate.vue

@@ -1,17 +0,0 @@
-<!--
-@description: 课程评价
-@creationTime: 2025-09-23
--->
-
-<template>
-  <view>课程评价</view>
-</template>
-
-<script lang="ts" setup>
-  defineOptions({ name: '课程评价' })
-  /* ------------------------ 导入 与 引用 ----------------------------------- */
-  /* ------------------------ 函数 与 方法 ----------------------------------- */
-  /* ------------------------- 生命周期 -------------------------------------- */
-</script>
-
-<style lang="scss" scoped></style>

+ 14 - 14
src/views/train/course/index.vue

@@ -4,20 +4,20 @@
 -->
 <template>
   <view>课程列表</view>
-   <ElRow :gutter="20">
-      <ElCol :xs="24" :sm="12" :md="6" v-for="card in imageCards" :key="card.id">
-        <ArtImageCard
-          :imageUrl="card.imageUrl"
-          :title="card.title"
-          :category="card.category"
-          :readTime="card.readTime"
-          :views="card.views"
-          :comments="card.comments"
-          :date="card.date"
-          @click="handleImageCardClick(card)"
-        />
-      </ElCol>
-    </ElRow>
+  <ElRow :gutter="20">
+    <ElCol :xs="24" :sm="12" :md="6" v-for="card in imageCards" :key="card.id">
+      <ArtImageCard
+        :imageUrl="card.imageUrl"
+        :title="card.title"
+        :category="card.category"
+        :readTime="card.readTime"
+        :views="card.views"
+        :comments="card.comments"
+        :date="card.date"
+        @click="handleImageCardClick(card)"
+      />
+    </ElCol>
+  </ElRow>
 </template>
 
 <script lang="ts" setup>

+ 8 - 0
src/views/train/evaluation/list/index.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <h2></h2>
+    <div>正在开发中,敬请期待</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="scss" scoped></style>

src/views/train/monitor/data/data.json → src/views/train/fault/list/data.json


+ 1 - 1
src/views/train/monitor/device.vue

@@ -12,7 +12,7 @@
   import Meta2dView from '@/components/business/meta2d/Meta2dView.vue'
   defineOptions({ name: 'Device' })
 
-  import data from './data/data.json'
+  import data from './data.json'
 </script>
 
 <style lang="scss" scoped></style>

+ 0 - 13
src/views/train/home/index.vue

@@ -1,13 +0,0 @@
-<!--
-@description: train_home
-@creationTime: 2025-09-28
--->
-<template>
-  <div>首页</div>
-</template>
-
-<script lang="ts" setup>
-  defineOptions({ name: 'train_home' })
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 17
src/views/train/plan/detail.vue

@@ -1,17 +0,0 @@
-<!--
-@description: 计划详情
-@creationTime: 2025-09-23
--->
-
-<template>
-  <view>计划详情</view>
-</template>
-
-<script lang="ts" setup>
-  defineOptions({ name: '计划详情' })
-  /* ------------------------ 导入 与 引用 ----------------------------------- */
-  /* ------------------------ 函数 与 方法 ----------------------------------- */
-  /* ------------------------- 生命周期 -------------------------------------- */
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 17
src/views/train/plan/index copy.vue

@@ -1,17 +0,0 @@
-<!--
-@description: 计划列表
-@creationTime: 2025-09-23
--->
-
-<template>
-  <view>计划列表</view>
-</template>
-
-<script lang="ts" setup>
-  defineOptions({ name: '计划列表' })
-  /* ------------------------ 导入 与 引用 ----------------------------------- */
-  /* ------------------------ 函数 与 方法 ----------------------------------- */
-  /* ------------------------- 生命周期 -------------------------------------- */
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 359
src/views/train/plan/index.vue

@@ -1,359 +0,0 @@
-<!-- 图标选择器 -->
-<template>
-  <div class="icon-selector">
-    <div class="select" @click="handleClick" :style="{ width: props.width }" :class="[size, { 'is-disabled': disabled }, { 'has-icon': selectValue }]">
-      <div class="icon">
-        <i :class="`iconfont-sys ${selectValue}`" v-show="props.iconType === IconTypeEnum.CLASS_NAME"></i>
-        <i class="iconfont-sys" v-html="selectValue" v-show="props.iconType === IconTypeEnum.UNICODE"></i>
-        <Icon v-show="selectValue?.includes(':')" :icon="selectValue" />
-      </div>
-      <div class="text"> {{ props.text }} </div>
-      <div class="arrow">
-        <i class="iconfont-sys arrow-icon">&#xe709;</i>
-        <i class="iconfont-sys clear-icon" @click.stop="clearIcon">&#xe83a;</i>
-      </div>
-    </div>
-    <div class="mt-4 flex items-center gap-4">
-      <i class="iconfont-sys">&#xe83a;</i>
-      <Icon :icon="'svg:download'" />
-      <Icon :icon="'svg:gitee'" />
-      <Icon :icon="'iconsys-tianjia1'" />
-      <Icon :icon="'&#xe66e;'" />
-      <Icon :icon="'&#xe83a;'" :color="'red'" />
-    </div>
-
-    <el-dialog title="选择图标" width="40%" v-model="visible" align-center>
-      <el-tabs v-model="activeTab">
-        <el-tab-pane v-for="prefix in iconifyPrefixs" :key="prefix" :label="prefix" :name="prefix">
-          <el-scrollbar height="400px">
-            <el-input v-model="searchText" placeholder="请输入图标名称"></el-input>
-            <ul class="icons-list" style="margin-top: 10px">
-              <li v-for="icon in paginatedIconifyList" :key="icon" @click="selectorIconifyIcon(icon)">
-                <i class="iconfont-sys">
-                  <Icon :icon="icon" />
-                </i>
-              </li>
-            </ul>
-            <el-pagination
-              v-if="filteredIconifyList.length > iconifyPageSize"
-              layout="prev, pager, next"
-              :total="filteredIconifyList.length"
-              :page-size="iconifyPageSize"
-              v-model:current-page="iconifyCurrentPage"
-              size="small"
-              style="margin-top: 10px; justify-content: center"
-            />
-          </el-scrollbar>
-        </el-tab-pane>
-        <el-tab-pane label="Svg图标" name="svg">
-          <el-scrollbar height="400px">
-            <ul class="icons-list">
-              <li v-for="icon in svgList" :key="icon" @click="selectorIconifyIcon(icon)">
-                <i class="iconfont-sys">
-                  <Icon :icon="icon" />
-                </i>
-              </li>
-            </ul>
-          </el-scrollbar>
-        </el-tab-pane>
-        <el-tab-pane label="原内置图标" name="icons">
-          <el-scrollbar height="400px">
-            <ul class="icons-list" v-show="activeName === 'icons'">
-              <li v-for="icon in iconsList" :key="icon.className" @click="selectorIcon(icon)">
-                <span class="text" v-if="iconType === IconTypeEnum.CLASS_NAME">{{ icon.className }}</span>
-                <span class="text" v-if="iconType === IconTypeEnum.UNICODE">{{ icon.unicode }}</span>
-                <i :class="`iconfont-sys ${icon.className}`" v-show="iconType === IconTypeEnum.CLASS_NAME"></i>
-                <i class="iconfont-sys" v-html="icon.unicode" v-show="iconType === IconTypeEnum.UNICODE"></i>
-              </li>
-            </ul>
-          </el-scrollbar>
-        </el-tab-pane>
-      </el-tabs>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="visible = false">取 消</el-button>
-          <el-button type="primary" @click="visible = false">确 定</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { IconTypeEnum } from '@/enums/appEnum'
-  import { extractIconClasses, type IconfontType } from '@/utils/constants'
-  import { Icon } from '@/components/icon'
-  import { listIcons } from '@iconify/vue'
-
-  defineOptions({ name: 'ArtIconSelector' })
-  const searchText = ref('')
-  const activeTab = ref('ant-design')
-  const iconifyCurrentPage = ref(1)
-  const iconifyPageSize = ref(60)
-
-  const svgList = computed(() => {
-    return listIcons('', 'svg')
-  })
-  const iconifyMap = reactive<any>({})
-  const iconifyPrefixs = import.meta.env.VITE_APP_ICONIFY_PREFIX?.split(',') || []
-  const iconifyList = computed(() => {
-    return iconifyMap[activeTab.value]
-  })
-  onMounted(() => {
-    iconifyPrefixs.forEach((prefix: any) => {
-      const icons = listIcons('', prefix)
-      iconifyMap[prefix] = icons
-    })
-  })
-
-  // 过滤后的图标列表(搜索功能)
-  const filteredIconifyList = computed<string[]>(() => {
-    if (!searchText.value) {
-      return iconifyList.value || []
-    }
-    return (iconifyList.value || []).filter((icon: any) => icon.includes(searchText.value))
-  })
-
-  // 分页后的图标列表
-  const paginatedIconifyList = computed<string[]>(() => {
-    const start = (iconifyCurrentPage.value - 1) * iconifyPageSize.value
-    const end = start + iconifyPageSize.value
-    return filteredIconifyList.value.slice(start, end)
-  })
-  // 组件大小类型
-  type ComponentSize = 'large' | 'default' | 'small'
-
-  // Props 接口定义
-  interface Props {
-    /** 图标类型 */
-    iconType?: IconTypeEnum
-    /** v-model 绑定的图标值 */
-    modelValue?: string
-    /** 显示文本 */
-    text?: string
-    /** 组件宽度 */
-    width?: string
-    /** 组件大小 */
-    size?: ComponentSize
-    /** 是否禁用 */
-    disabled?: boolean
-  }
-
-  // Emits 接口定义
-  interface Emits {
-    'update:modelValue': [value: string]
-    getIcon: [value: string]
-  }
-
-  // 使用 withDefaults 定义 props
-  const props = withDefaults(defineProps<Props>(), {
-    iconType: IconTypeEnum.CLASS_NAME,
-    modelValue: '',
-    text: '图标选择器',
-    width: '200px',
-    size: 'default',
-    disabled: false
-  })
-
-  // 定义 emits
-  const emits = defineEmits<Emits>()
-
-  // 响应式数据
-  const selectValue = ref<string>(props.modelValue)
-  const visible = ref<boolean>(false)
-  const activeName = ref<string>('icons')
-
-  // 图标列表 - 使用计算属性优化性能
-  const iconsList = computed<IconfontType[]>(() => extractIconClasses())
-
-  // 监听 modelValue 变化
-  watch(
-    () => props.modelValue,
-    (newVal: string) => {
-      selectValue.value = newVal
-    },
-    { immediate: true }
-  )
-
-  // 当搜索条件变化时,重置到第一页
-  watch(searchText, () => {
-    iconifyCurrentPage.value = 1
-  })
-
-  // 选择图标
-  const selectorIcon = (icon: IconfontType): void => {
-    const iconValue = props.iconType === IconTypeEnum.CLASS_NAME ? icon.className : icon.unicode || ''
-
-    selectValue.value = iconValue
-    visible.value = false
-
-    // 发射 v-model 更新事件和自定义事件
-    emits('update:modelValue', iconValue)
-    emits('getIcon', iconValue)
-  }
-  // 选择图标
-  const selectorIconifyIcon = (icon: string): void => {
-    selectValue.value = icon
-    visible.value = false
-
-    // 发射 v-model 更新事件和自定义事件
-    emits('update:modelValue', icon)
-    emits('getIcon', icon)
-  }
-
-  // 处理点击事件
-  const handleClick = (): void => {
-    if (!props.disabled) {
-      visible.value = true
-      // 重置分页和搜索条件
-      iconifyCurrentPage.value = 1
-      searchText.value = ''
-    }
-  }
-
-  // 清除图标
-  const clearIcon = (): void => {
-    selectValue.value = ''
-
-    // 发射 v-model 更新事件和自定义事件
-    emits('update:modelValue', '')
-    emits('getIcon', '')
-  }
-
-  // 计算属性:当前图标类型(用于模板中的判断)
-  const iconType = computed<IconTypeEnum>(() => props.iconType)
-</script>
-
-<style lang="scss" scoped>
-  .icon-selector {
-    width: 100%;
-
-    .select {
-      box-sizing: border-box;
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      height: var(--el-component-custom-height);
-      padding: 0 15px;
-      cursor: pointer;
-      border: 1px solid var(--art-border-dashed-color);
-      border-radius: calc(var(--custom-radius) / 3 + 2px) !important;
-      transition: border 0.3s;
-
-      @media (width <= 500px) {
-        width: 100% !important;
-      }
-
-      &.large {
-        height: 40px;
-      }
-
-      &.small {
-        height: 24px;
-      }
-
-      &:hover:not(.is-disabled).has-icon {
-        .arrow-icon {
-          display: none;
-        }
-
-        .clear-icon {
-          display: block !important;
-        }
-      }
-
-      &:hover {
-        border-color: var(--art-text-gray-400);
-      }
-
-      .icon {
-        display: flex;
-        align-items: center;
-        width: 20px;
-        color: var(--art-gray-700);
-
-        i {
-          display: block;
-          margin: 0 auto;
-          font-size: 16px;
-        }
-      }
-
-      .text {
-        display: flex;
-        display: inline-block;
-        align-items: center;
-        width: 50%;
-        font-size: 14px;
-        color: var(--art-gray-600);
-
-        @include ellipsis();
-
-        @media (width <= 500px) {
-          display: none;
-        }
-      }
-
-      .arrow {
-        display: flex;
-        align-items: center;
-        height: calc(100% - 2px);
-
-        i {
-          font-size: 13px;
-          color: var(--art-gray-600);
-        }
-
-        .clear-icon {
-          display: none;
-        }
-      }
-
-      &.is-disabled {
-        cursor: not-allowed;
-        background-color: var(--el-disabled-bg-color);
-        border-color: var(--el-border-color-lighter);
-
-        .icon,
-        .text,
-        .arrow {
-          color: var(--el-text-color-placeholder);
-        }
-
-        &:hover {
-          border-color: var(--el-border-color-lighter);
-        }
-      }
-    }
-
-    .icons-list {
-      display: grid;
-      grid-template-columns: repeat(10, 1fr);
-      border-top: 1px solid var(--art-border-color);
-      border-left: 1px solid var(--art-border-color);
-
-      li {
-        box-sizing: border-box;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        aspect-ratio: 1 / 1;
-        color: var(--art-gray-600);
-        text-align: center;
-        border-right: 1px solid var(--art-border-color);
-        border-bottom: 1px solid var(--art-border-color);
-        align-items: center;
-
-        &:hover {
-          cursor: pointer;
-          background: var(--art-gray-100);
-        }
-
-        i {
-          font-size: 22px;
-          color: var(--art-gray-800);
-        }
-      }
-    }
-  }
-</style>

+ 8 - 0
src/views/train/system/dictionary/index.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <h2>字典管理</h2>
+    <div>正在开发中,敬请期待</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="scss" scoped></style>

+ 8 - 0
src/views/train/system/roles/index.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <h2>角色管理</h2>
+    <div>正在开发中,敬请期待</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="scss" scoped></style>

+ 1 - 1
src/views/train/manager/user.vue

@@ -3,7 +3,7 @@
 @creationTime: 2025-09-23
 -->
 <template>
-  <div class="w-full art-full-height">
+  <div class="art-full-height w-full">
     <BasicCrud @reset="handleReset" />
   </div>
 </template>

src/views/train/manager/schemas/user.api.ts → src/views/train/system/users/schemas/user.api.ts


src/views/train/manager/schemas/user.tsx → src/views/train/system/users/schemas/user.tsx


+ 8 - 0
src/views/train/trainer/course-list/index.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <h2>课程列表</h2>
+    <div>正在开发中,敬请期待</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="scss" scoped></style>

+ 8 - 0
src/views/train/trainer/course-run/index.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <h2>课程运行</h2>
+    <div>正在开发中,敬请期待</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="scss" scoped></style>