From f0f4467beae444c18e85dcff22f4efa6d472b060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E4=BA=9A=E9=B9=8F?= <13666640+zhao-yapengaaa@user.noreply.gitee.com> Date: Wed, 12 Nov 2025 14:54:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B4=E5=90=88=E5=AD=A6=E7=94=9F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=E6=A0=B8=E5=BF=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 63 +++--- src/api/class.ts | 26 +++ src/api/student.ts | 21 ++ src/main.ts | 28 ++- src/permission.ts | 38 +++- src/plugins/elementPlus/index.ts | 26 +-- src/router/index.ts | 100 +++++++++ src/utils/request.ts | 22 ++ src/views/student/ClassGrade.vue | 232 +++++++++++++++++++ src/views/student/StudentManagement.vue | 287 ++++++++++++++++++++++++ vite.config.ts | 6 +- 12 files changed, 784 insertions(+), 66 deletions(-) create mode 100644 src/api/class.ts create mode 100644 src/api/student.ts create mode 100644 src/utils/request.ts create mode 100644 src/views/student/ClassGrade.vue create mode 100644 src/views/student/StudentManagement.vue diff --git a/package.json b/package.json index d452fa9..238f30c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "icon": "esno ./scripts/icon.ts" }, "dependencies": { + "@element-plus/icons-vue": "^2.3.2", "@iconify/iconify": "^3.1.1", "@iconify/vue": "^4.3.0", "@vueuse/core": "^12.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0a6f4e..dc46bb9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.13(typescript@5.7.3)) '@iconify/iconify': specifier: ^3.1.1 version: 3.1.1 @@ -901,8 +904,8 @@ packages: '@dual-bundle/import-meta-resolve@4.1.0': resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} - '@element-plus/icons-vue@2.3.1': - resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==} + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} peerDependencies: vue: ^3.2.0 @@ -1379,22 +1382,26 @@ packages: resolution: {integrity: sha512-NAmhw1l/llM0HZRpagR/ChJTNymW4ll6/4EDSJML5c8L5Hl/+k6UyF8EIgE6DeHpfheQujkSRngauViHqq6jJQ==} engines: {node: '>= 16'} - '@intlify/message-compiler@11.0.0-rc.1': - resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==} - engines: {node: '>= 16'} - '@intlify/message-compiler@11.0.1': resolution: {integrity: sha512-5RFH8x+Mn3mbjcHXnb6KCXGiczBdiQkWkv99iiA0JpKrNuTAQeW59Pjq/uObMB0eR0shnKYGTkIJxum+DbL3sw==} engines: {node: '>= 16'} - '@intlify/shared@11.0.0-rc.1': - resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} + '@intlify/message-compiler@12.0.0-alpha.3': + resolution: {integrity: sha512-mDDTN3gfYOHhBnpnlby19UHyvMaOnzdlpsIrxUfs44R/vCATfn8pMOkE8PXD2t410xkocEj3FpDcC9XC/0v4Dg==} engines: {node: '>= 16'} '@intlify/shared@11.0.1': resolution: {integrity: sha512-lH164+aDDptHZ3dBDbIhRa1dOPQUp+83iugpc+1upTOWCnwyC1PVis6rSWNMMJ8VQxvtHQB9JMib48K55y0PvQ==} engines: {node: '>= 16'} + '@intlify/shared@11.1.12': + resolution: {integrity: sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A==} + engines: {node: '>= 16'} + + '@intlify/shared@12.0.0-alpha.3': + resolution: {integrity: sha512-ryaNYBvxQjyJUmVuBBg+HHUsmGnfxcEUPR0NCeG4/K9N2qtyFE35C80S15IN6iYFE2MGWLN7HfOSyg0MXZIc9w==} + engines: {node: '>= 16'} + '@intlify/unplugin-vue-i18n@6.0.3': resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==} engines: {node: '>= 18'} @@ -2802,7 +2809,7 @@ packages: engines: {node: '>=18'} errno@0.1.8: - resolution: {integrity: sha1-i7Ppx9Rjvkl2/4iPdrSAnrwugR8=} + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true error-ex@1.3.2: @@ -3435,7 +3442,7 @@ packages: engines: {node: '>= 4'} image-size@0.5.5: - resolution: {integrity: sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=} + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} hasBin: true @@ -4018,7 +4025,7 @@ packages: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} make-dir@2.1.0: - resolution: {integrity: sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=} + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} make-iterator@1.0.1: @@ -4088,7 +4095,7 @@ packages: engines: {node: '>= 0.6'} mime@1.6.0: - resolution: {integrity: sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=} + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} hasBin: true @@ -4999,7 +5006,7 @@ packages: engines: {node: '>=0.10.0'} source-map@0.6.1: - resolution: {integrity: sha1-dHIq8y6WFOnCh6jQu95IteLxomM=} + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} source-map@0.7.4: @@ -6536,7 +6543,7 @@ snapshots: '@dual-bundle/import-meta-resolve@4.1.0': {} - '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.7.3))': + '@element-plus/icons-vue@2.3.2(vue@3.5.13(typescript@5.7.3))': dependencies: vue: 3.5.13(typescript@5.7.3) @@ -6895,8 +6902,8 @@ snapshots: '@intlify/bundle-utils@10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))': dependencies: - '@intlify/message-compiler': 11.0.0-rc.1 - '@intlify/shared': 11.0.0-rc.1 + '@intlify/message-compiler': 12.0.0-alpha.3 + '@intlify/shared': 12.0.0-alpha.3 acorn: 8.14.0 escodegen: 2.1.0 estree-walker: 2.0.2 @@ -6912,26 +6919,28 @@ snapshots: '@intlify/message-compiler': 11.0.1 '@intlify/shared': 11.0.1 - '@intlify/message-compiler@11.0.0-rc.1': - dependencies: - '@intlify/shared': 11.0.0-rc.1 - source-map-js: 1.2.1 - '@intlify/message-compiler@11.0.1': dependencies: '@intlify/shared': 11.0.1 source-map-js: 1.2.1 - '@intlify/shared@11.0.0-rc.1': {} + '@intlify/message-compiler@12.0.0-alpha.3': + dependencies: + '@intlify/shared': 12.0.0-alpha.3 + source-map-js: 1.2.1 '@intlify/shared@11.0.1': {} + '@intlify/shared@11.1.12': {} + + '@intlify/shared@12.0.0-alpha.3': {} + '@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.17.0(jiti@2.4.2))(rollup@4.30.1)(typescript@5.7.3)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@intlify/bundle-utils': 10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3))) - '@intlify/shared': 11.0.1 - '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) + '@intlify/shared': 11.1.12 + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.12)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) '@rollup/pluginutils': 5.1.4(rollup@4.30.1) '@typescript-eslint/scope-manager': 8.19.1 '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) @@ -6953,11 +6962,11 @@ snapshots: - supports-color - typescript - '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.12)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@babel/parser': 7.26.3 optionalDependencies: - '@intlify/shared': 11.0.1 + '@intlify/shared': 11.1.12 '@vue/compiler-dom': 3.5.13 vue: 3.5.13(typescript@5.7.3) vue-i18n: 11.0.1(vue@3.5.13(typescript@5.7.3)) @@ -8553,7 +8562,7 @@ snapshots: element-plus@2.9.2(vue@3.5.13(typescript@5.7.3)): dependencies: '@ctrl/tinycolor': 3.6.1 - '@element-plus/icons-vue': 2.3.1(vue@3.5.13(typescript@5.7.3)) + '@element-plus/icons-vue': 2.3.2(vue@3.5.13(typescript@5.7.3)) '@floating-ui/dom': 1.6.13 '@popperjs/core': '@sxzz/popperjs-es@2.11.7' '@types/lodash': 4.17.14 diff --git a/src/api/class.ts b/src/api/class.ts new file mode 100644 index 0000000..9f04325 --- /dev/null +++ b/src/api/class.ts @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 获取年级下的班级列表 +export const getClassList = (params) => { + return request.get('/class/list', { params }) +} + +// 新增班级 +export const addClass = (data) => { + return request.post('/class/add', data) +} + +// 编辑班级 +export const editClass = (data) => { + return request.put('/class/edit', data) +} + +// 删除班级 +export const deleteClass = (id) => { + return request.delete(`/class/delete/${id}`) +} + +// 检查班级是否有学生 +export const checkClassHasStudent = (classId) => { + return request.get('/class/check', { params: { classId } }) +} diff --git a/src/api/student.ts b/src/api/student.ts new file mode 100644 index 0000000..6ad19b1 --- /dev/null +++ b/src/api/student.ts @@ -0,0 +1,21 @@ +import request from '@/utils/request' + +// 获取学生列表(支持班级筛选) +export const getStudentList = (params) => { + return request.get('/student/list', { params }) +} + +// 新增学生 +export const addStudent = (data) => { + return request.post('/student/add', data) +} + +// 编辑学生 +export const editStudent = (data) => { + return request.put('/student/edit', data) +} + +// 删除学生 +export const deleteStudent = (id) => { + return request.delete(`/student/delete/${id}`) +} diff --git a/src/main.ts b/src/main.ts index d0f08af..8ce88bb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,9 +15,6 @@ import { setupStore } from '@/store' // 全局组件 import { setupGlobCom } from '@/components' -// 引入element-plus -import { setupElementPlus } from '@/plugins/elementPlus' - // 引入全局样式 import '@/styles/index.less' @@ -31,11 +28,20 @@ import { setupRouter } from './router' import { setupPermission } from './directives' import { createApp } from 'vue' - import App from './App.vue' - import './permission' +// =========================================== + +// 1. 完整导入 ElementPlus 和它的所有图标 +import ElementPlus from 'element-plus' +// 2. 完整导入 ElementPlus 的样式文件 +import 'element-plus/dist/index.css' +// 3. 完整导入 ElementPlus 的所有图标 +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +// ============================================ + // 创建实例 const setupAll = async () => { const app = createApp(App) @@ -46,7 +52,17 @@ const setupAll = async () => { setupGlobCom(app) - setupElementPlus(app) + // ============================================ + + // 4. 全局注册 ElementPlus + app.use(ElementPlus) + + // 5. 全局注册所有 ElementPlus 图标 + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component as any) + } + + // =========================================== setupRouter(app) diff --git a/src/permission.ts b/src/permission.ts index a14c3a8..5a2875d 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -18,6 +18,7 @@ router.beforeEach(async (to, from, next) => { const permissionStore = usePermissionStoreWithOut() const appStore = useAppStoreWithOut() const userStore = useUserStoreWithOut() + if (userStore.getUserInfo) { if (to.path === '/login') { next({ path: '/' }) @@ -27,38 +28,51 @@ router.beforeEach(async (to, from, next) => { return } - // 开发者可根据实际情况进行修改 - const roleRouters = userStore.getRoleRouters || [] + // ====================================================== - // 是否使用动态路由 - if (appStore.getDynamicRouter) { - appStore.serverDynamicRouter - ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[]) - : await permissionStore.generateRoutes('frontEnd', roleRouters as string[]) - } else { - await permissionStore.generateRoutes('static') - } + // 1. 强制禁用动态路由,使用静态路由模式 + // 这会忽略后端返回的路由和角色权限,直接加载 asyncRouterMap 中的所有路由 + appStore.setDynamicRouter(false) + + // 2. 加载静态路由 + // 'static' 模式会让 permissionStore 加载 asyncRouterMap 中的所有路由 + await permissionStore.generateRoutes('static') + + // ====================================================== + // 将生成的路由添加到 router 实例中 permissionStore.getAddRouters.forEach((route) => { router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 }) + + // 处理重定向 const redirectPath = from.query.redirect || to.path const redirect = decodeURIComponent(redirectPath as string) const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + + // 标记路由已添加,防止重复添加 permissionStore.setIsAddRouters(true) + + // 跳转到目标页面 next(nextData) } } else { + // 用户未登录 if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) { + // 在白名单内的路径可以直接访问 next() } else { - next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 + // 否则重定向到登录页,并携带目标路径作为 redirect 参数 + next(`/login?redirect=${to.path}`) } } }) router.afterEach((to) => { + // 设置页面标题 useTitle(to?.meta?.title as string) - done() // 结束Progress + // 结束进度条 + done() + // 结束页面加载动画 loadDone() }) diff --git a/src/plugins/elementPlus/index.ts b/src/plugins/elementPlus/index.ts index 29a7840..4ee4851 100644 --- a/src/plugins/elementPlus/index.ts +++ b/src/plugins/elementPlus/index.ts @@ -1,24 +1,14 @@ import type { App } from 'vue' - -// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题 -import { ElLoading, ElScrollbar } from 'element-plus' - -const plugins = [ElLoading] - -const components = [ElScrollbar] +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' export const setupElementPlus = (app: App) => { - plugins.forEach((plugin) => { - app.use(plugin) - }) + // 1. 完整注册 ElementPlus + app.use(ElementPlus) - // 为了开发环境启动更快,一次性引入所有样式 - if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') { - import('element-plus/dist/index.css') - return + // 2. 全局注册所有 Element Plus 图标 + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) } - - components.forEach((component) => { - app.component(component.name!, component) - }) } diff --git a/src/router/index.ts b/src/router/index.ts index e203a01..adb7d66 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,6 +4,8 @@ import type { App } from 'vue' import { Layout, getParentLayout } from '@/utils/routerHelper' import { useI18n } from '@/hooks/web/useI18n' import { NO_RESET_WHITE_LIST } from '@/constants' +import ClassGrade from '@/views/student/ClassGrade.vue' // 静态导入 ClassGrade 组件 +import StudentManagement from '@/views/student/StudentManagement.vue' // 静态导入 StudentManagement 组件 const { t } = useI18n() @@ -727,7 +729,103 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [ } } ] + }, + + // ============================================ + { + path: '/student-system', + component: Layout, + redirect: '/student-system/class/grade1', + name: 'StudentSystem', + meta: { + title: '学生管理系统', + icon: 'vi-ep:user', + alwaysShow: true + }, + children: [ + // 班级管理 + { + path: 'class', + component: getParentLayout(), + redirect: '/student-system/class/grade1', + name: 'ClassManagementGroup', + meta: { + title: '班级管理', + alwaysShow: true + }, + children: [ + { + path: 'grade1', + name: 'ClassGrade1', + component: ClassGrade, + meta: { + title: '一年级班级', + grade: 1 + } + }, + { + path: 'grade2', + name: 'ClassGrade2', + component: ClassGrade, + meta: { + title: '二年级班级', + grade: 2 + } + }, + { + path: 'grade3', + name: 'ClassGrade3', + component: () => ClassGrade, + meta: { + title: '三年级班级', + grade: 3 + } + } + ] + }, + // 学生管理 + { + path: 'student', + component: getParentLayout(), + redirect: '/student-system/student/grade1', + name: 'StudentManagementGroup', + meta: { + title: '学生管理', + alwaysShow: true + }, + children: [ + { + path: 'grade1', + name: 'StudentGrade1', + component: StudentManagement, + meta: { + title: '一年级学生', + grade: 1 + } + }, + { + path: 'grade2', + name: 'StudentGrade2', + component: StudentManagement, + meta: { + title: '二年级学生', + grade: 2 + } + }, + { + path: 'grade3', + name: 'StudentGrade3', + component: StudentManagement, + meta: { + title: '三年级学生', + grade: 3 + } + } + ] + } + ] } + //======================================= ] const router = createRouter({ @@ -751,3 +849,5 @@ export const setupRouter = (app: App) => { } export default router + +console.log('路由列表:', router.getRoutes()) diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..61e45ba --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,22 @@ +import axios from 'axios' + +const request = axios.create({ + baseURL: '/api', // 后端接口基础路径 + timeout: 5000 +}) + +// 请求拦截器 +request.interceptors.request.use((config) => { + return config +}) + +// 响应拦截器(处理错误) +request.interceptors.response.use( + (response) => response.data, + (error) => { + console.error('请求错误', error) + return Promise.reject(error) + } +) + +export default request diff --git a/src/views/student/ClassGrade.vue b/src/views/student/ClassGrade.vue new file mode 100644 index 0000000..259d3ff --- /dev/null +++ b/src/views/student/ClassGrade.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/views/student/StudentManagement.vue b/src/views/student/StudentManagement.vue new file mode 100644 index 0000000..ec44bab --- /dev/null +++ b/src/views/student/StudentManagement.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/vite.config.ts b/vite.config.ts index 749c3c1..6951694 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -145,9 +145,9 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { proxy: { // 选项写法 '/api': { - target: 'http://127.0.0.1:8000', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') + // 已将目标地址从 8000 修改为 8080 + target: 'http://127.0.0.1:8080', + changeOrigin: true } }, hmr: {