diff --git a/.env.development b/.env.development index d7062c9..acde742 100644 --- a/.env.development +++ b/.env.development @@ -7,7 +7,7 @@ # @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE ### # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 开发环境配置 VITE_APP_ENV = 'development' diff --git a/.env.production b/.env.production index 67fa8fd..dc0acff 100644 --- a/.env.production +++ b/.env.production @@ -7,14 +7,17 @@ # @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE ### # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 生产环境配置 VITE_APP_ENV = 'production' # 若依管理系统/生产环境 -VITE_APP_BASE_API = '/' -VITE_APP_AI_API = 'https://whmnjy.sztzjy.com:8764/' -VITE_APP_BI_URL = 'https://dshlw.sztzjy.com:196' -# 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip +VITE_APP_BASE_API = '/' +VITE_APP_AI_API = 'https://whmnjy.sztzjy.com:8764/' +# BI 可视化分析:走本机 Nginx 的 /bi/ 同机反代,保持与主系统同源, +# 登录 Cookie(Admin-Token)才能随 iframe 请求带给 BI,避免 BI 弹自己的登录页。 +# 若部署机可直接用 *.sztzjy.com 域名 + HTTPS 访问,也可改回绝对地址 https://dshlw.sztzjy.com:196 +VITE_APP_BI_URL = '/bi/' +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip diff --git a/.env.staging b/.env.staging index a296a2a..5152829 100644 --- a/.env.staging +++ b/.env.staging @@ -1,5 +1,5 @@ # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 生产环境配置 VITE_APP_ENV = 'staging' @@ -8,4 +8,4 @@ VITE_APP_ENV = 'staging' VITE_APP_BASE_API = '/stage-api' # 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip \ No newline at end of file +VITE_BUILD_COMPRESS = gzip diff --git a/.gitignore b/.gitignore index 1da7204..2887a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ selenium-debug.log # Editor directories and files .idea +.workbuddy .cursor/ .vscode *.suo diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index fe13175..1f1d9d5 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -1,4 +1,4 @@ -# 电商互联网产品开发实训系统 - 项目认知 +# 互联网产品开发实训系统 - 项目认知 > 本文件为 AI 助手提供项目上下文,每次新对话开始时会读取此文件。 @@ -6,7 +6,7 @@ ## 1. 项目基本信息 -- **项目名称**:电商互联网产品开发实训系统 +- **项目名称**:互联网产品开发实训系统 - **技术栈**:Vue 3 (Composition API) + Vite 3 + Element Plus 2.2.x + Pinia + Vue Router 4 + ECharts 5.4 + SCSS - **基础框架**:RuoYi-Vue 3.8.6(若依前后端分离系统增强版) - **项目路径**:`D:\TZ\dianshang-qianduan` 或 `d:\TZ\dianshang-qianduan` diff --git a/index.html b/index.html index 78ccecf..aad1267 100644 --- a/index.html +++ b/index.html @@ -1,352 +1,363 @@ - + + + + + + + + 互联网产品开发实训系统 + + - - - -
-
-
- -
-
Teaching Lab System
-

正在初始化教学环境

-

正在同步课程资源、实训数据与学习进度,请稍候。

- - - - - + + diff --git a/src/api/AI.js b/src/api/AI.js index 24895f9..2f74787 100644 --- a/src/api/AI.js +++ b/src/api/AI.js @@ -225,5 +225,13 @@ export function getLearningAnalysis(params) { data: stuData, // AI 服务使用独立鉴权,不能携带本系统的登录令牌。 headers: { isToken: false } + }).then((res) => { + // AI 服务端点故障时返回 HTTP 200 + "Error: ..." 纯文本,这里统一转成失败, + // 由调用方回退到可用的 AI 对话通道(api/kimi4)。 + const text = typeof res === 'string' ? res.trim() : String(res?.data ?? '').trim() + if (/^error\b/i.test(text)) { + throw new Error(text) + } + return res }) } diff --git a/src/api/aiTrainingEvaluation.js b/src/api/aiTrainingEvaluation.js index f935116..90b4690 100644 --- a/src/api/aiTrainingEvaluation.js +++ b/src/api/aiTrainingEvaluation.js @@ -1,5 +1,17 @@ import request from "@/utils/request"; +/** + * 后端 AI 助学/助评的前置校验消息是英文(该文案要落库,不能直接改中文), + * 前端按场景转成中文引导;未识别的消息回退到调用方给定的兜底文案。 + */ +export function friendlyAiEvaluationMessage(error, fallback) { + const raw = String(error?.response?.data?.msg || error?.message || ""); + if (raw.includes("at least one training step")) { + return "请先在实训页填写并保存至少一个步骤的内容,再使用 AI 助学/助评。"; + } + return fallback; +} + export function getAiTrainingEvaluation(taskKey) { return request({ url: `/api/student/training-tasks/${taskKey}/ai-evaluation`, @@ -13,6 +25,9 @@ export function requestAiTrainingHelp(taskKey, question, stepContext = "") { method: "post", data: { question, stepContext }, headers: { repeatSubmit: false }, + // 后端对这类前置校验返回英文原文(还要落库,不能直接改中文), + // 屏蔽全局错误弹窗,由侧边栏按场景给出中文提示 + skipErrorMessage: true, }); } @@ -21,5 +36,6 @@ export function requestAiTrainingAssessment(taskKey) { url: `/api/student/training-tasks/${taskKey}/ai-evaluation/assessment`, method: "post", headers: { repeatSubmit: false }, + skipErrorMessage: true, }); } diff --git a/src/api/index.js b/src/api/index.js index 7b32744..876f453 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -57,7 +57,8 @@ export const getUserRank = (params) => { }) } export const getCurrentExperimentReport = () => { - return request({ url: '/api/student/experiment-report/current', method: 'get' }) + // 未加入教学班等场景后端返回 400,页面有自己的空态/错误提示,不在拦截器里重复弹原始报错 + return request({ url: '/api/student/experiment-report/current', method: 'get', skipErrorMessage: true }) } // 更新访问次数 export const updateVisitCount = (params) => { diff --git a/src/api/projectStudyTime.js b/src/api/projectStudyTime.js index 35bbfca..9f95c08 100644 --- a/src/api/projectStudyTime.js +++ b/src/api/projectStudyTime.js @@ -13,5 +13,6 @@ export function getCurrentProjectStudyTime() { return request({ url: "/api/student/project-study-time/current", method: "get", + skipErrorMessage: true, }); } diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss index 788f1d7..b293d44 100644 --- a/src/assets/styles/sidebar.scss +++ b/src/assets/styles/sidebar.scss @@ -1006,6 +1006,47 @@ } } +/* + * 折叠把手的箭头再挂一条 .hideSidebar 链 + * + * 侧栏宽度由 .app-wrapper 上的 .hideSidebar 控制,而箭头此前只由 + * .sidebar-container 上的 .is-collapse 控制 —— 两者本该同源,却走了 + * 两条独立的 class 链。一旦失步就会出现「宽度已切、箭头不动」的现象。 + * 这里把箭头也绑到控制宽度的同一个 class 上,二者再也无法不同步。 + * 特异性 (1,4,0) 与基础隐藏规则相同,靠书写顺序在后面取胜。 + */ +#app .hideSidebar .sidebar-container .course-sidebar-toggle { + .toggle-arrow--left { + display: none; + } + .toggle-arrow--right { + display: inline-flex; + } +} + +/* + * 兜底:展开态下不允许 el-menu 停留在折叠外观 + * + * 菜单文字的显隐由 Element Plus 内部的 .el-menu--collapse 决定,它只跟随 + * 这个 prop。若该 prop 与外层状态不同步(失步的根因), + * 就会出现「侧栏已展开,菜单却仍是 64px 图标轨道、文字不显示」。 + * 这里以控制宽度的 .hideSidebar 为准做强制纠正:只要侧栏处于展开态, + * 即便 el-menu 仍带着 .el-menu--collapse,也要撑满并恢复文字。 + * 特异性 (1,5,0)+ ,足以压过 Element Plus 自身的隐藏规则。 + */ +#app .app-wrapper:not(.hideSidebar) .sidebar-container .student-course-menu.el-menu--collapse { + width: 100% !important; + + > .el-menu-item > span, + > .el-sub-menu > .el-sub-menu__title > span { + height: auto; + width: auto; + overflow: visible; + visibility: visible; + display: inline-block; + } +} + /* * 教师端 sidebar 宽度单独缩减 50%(304 → 152px) * 教师端使用 el-menu 直显式布局,无需学生端的内嵌卡片空间,原 304px 造成大量空白 @@ -1032,6 +1073,30 @@ } } +/* + * 学生端收起态(非移动端):侧边栏必须收敛到 64px 图标轨道 + * + * 为什么必须单独补这一条: + * RuoYi 原始收起规则 #app .hideSidebar .sidebar-container { width: 54px !important } + * 后加的学生端规则 #app .studentLayout .sidebar-container { width: 304px !important } + * 两者特异性完全相同(1 个 ID + 2 个类),而后者在源码中位置更靠后, + * 按"同特异性、后出现者胜"被后者压掉 —— 收起后容器仍留 304px 宽, + * 但 el-menu 已切到 collapse 态,于是只剩一列图标、菜单文字全部消失。 + * 教师端由 #app .teacherLayout.hideSidebar 处理,学生端此前只在实训页 + * (:has(.student-training-shell)) 补过,非实训页全部中招,这里补齐。 + * + * :not(.mobile) 用来排除移动端:移动端需保留 304px 抽屉宽度 + translate3d 滑出。 + */ +#app .studentLayout.hideSidebar:not(.mobile) { + .sidebar-container { + width: 64px !important; + } + + .main-container { + margin-left: 64px !important; + } +} + /* * 学生端 + 教师端实训页背景覆盖 * student-training.scss 中 .student-training-shell 的暗色背景统一覆盖为亮色 diff --git a/src/assets/styles/student-training.scss b/src/assets/styles/student-training.scss index 809ec88..4d4556e 100644 --- a/src/assets/styles/student-training.scss +++ b/src/assets/styles/student-training.scss @@ -1064,7 +1064,7 @@ body.student-light-mode { white-space: nowrap !important; } -@media (max-width: 1100px) { +@media (max-width: 800px) { /* AI 悬浮球/面板在窄屏仍 fixed 悬浮;面板宽度自适应 100vw-32px */ .student-training-shell .workbench-actions { diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index 8c7554f..ce2d91f 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -1,871 +1,908 @@ @@ -1005,160 +1044,170 @@ onMounted(() => { diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index 45e4ea5..cbb635a 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -10,14 +10,16 @@
@@ -753,7 +753,7 @@ const onClick = (type, idx) => { if (!type?.isTemplateBased) { let arr = ["课程知识点", "核心知识点", "最难的部分", "实务知识概述", "学习建议", "业务现状", "理论研究", "发展趋势"] if (arr.includes(type.name)) { - inputMessage.value = type.com.replace("xxx", "电商互联网产品开发实训系统") + inputMessage.value = type.com.replace("xxx", "互联网产品开发实训系统") } else { inputMessage.value = type.com } @@ -908,7 +908,7 @@ onUnmounted(() => { if (resizeObserver) { resizeObserver.disconnect() } -}); +}) diff --git a/src/views/digitalMarketingAlgorithms/components/bigData.vue b/src/views/digitalMarketingAlgorithms/components/bigData.vue index 2ed08c6..53475ed 100644 --- a/src/views/digitalMarketingAlgorithms/components/bigData.vue +++ b/src/views/digitalMarketingAlgorithms/components/bigData.vue @@ -29,9 +29,9 @@
- + @@ -65,19 +65,9 @@ const nzIndex = ref(0); const nzData = ref([]); const zjIndex = ref(-1); const zjData = ref([]); -const tableLabel = ref([]); -const tableLabel2 = ref([]); -const arrhander = ref([]); -const arrhanderkey = ref([]); -// const arrMap = { -// 客户细分数据表: tableLabel.value, -// 销售预测数据表: tableLabel4.value, -// 购物车数据表: tableLabel5.value, -// 用户评论表: tableLabel3.value, -// }; +const tableColumns = ref([]); const goNZ = (index, item) => { nzIndex.value = index; - // tableLabel.value = arrMap[item]; n_dataTableQuery.value.tableName = item; delete n_dataTableQuery.value.selfTableName; getSurface(); @@ -105,22 +95,25 @@ const getSurface = () => { portraitModel .getUserTableData(n_dataTableQuery.value) .then((res) => { - tableLabel.value = Object.keys(res?.data?.list[0]); - tableData.value = res?.data?.list; - console.log(tableData.value, "454545"); - datatotal.value = res?.data?.total; - tableLabel2.value = []; - if (tableData.value[0]?.stepOneA) { - arrhander.value = JSON.parse(tableData.value[0].stepOneA); - arrhanderkey.value = Object.keys(tableData.value[0]).slice(2, arrhander.value.length + 2); - arrhander.value.forEach((item, index) => { - tableLabel2.value.push({ - prop: arrhanderkey.value[index], - label: item, - }); - }); - tableLabel.value = arrhanderkey.value; + tableData.value = res?.data?.list || []; + datatotal.value = res?.data?.total || 0; + const firstRow = tableData.value[0] || {}; + const properties = Object.keys(firstRow); + let headers = []; + if (firstRow.stepOneA) { + try { + headers = JSON.parse(firstRow.stepOneA); + } catch (error) { + headers = []; + } } + const dataProperties = headers.length + ? properties.slice(2, headers.length + 2) + : properties; + tableColumns.value = dataProperties.map((prop, index) => ({ + prop, + label: headers[index] || prop, + })); setTimeout(() => { loading.value = false; }, 1000); diff --git a/src/views/login.vue b/src/views/login.vue index 40299c4..1cc95b6 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,239 +1,857 @@ - - - - - + + + + + diff --git a/src/views/schoolAdmin/admin-theme.scss b/src/views/schoolAdmin/admin-theme.scss index 3f405c2..8331f6f 100644 --- a/src/views/schoolAdmin/admin-theme.scss +++ b/src/views/schoolAdmin/admin-theme.scss @@ -100,7 +100,7 @@ font-weight: var(--admin-fw-bolder); letter-spacing: 0; color: transparent; - background: linear-gradient(135deg, #ffffff, #2563eb); + background: linear-gradient(135deg, #1f2937, #2563eb); -webkit-background-clip: text; background-clip: text; } @@ -152,7 +152,7 @@ padding: 14px 16px; border: 1px solid #e5e7eb; border-radius: 18px; - background: rgba(0, 35, 55, 0.44); + background: #ffffff; } .school-admin-workbench .toolbar-left, @@ -502,7 +502,7 @@ overflow: hidden; border-radius: 18px; border-color: #e5e7eb; - background: rgba(3, 24, 36, 0.72); + background: #ffffff; color: #374151; font-family: var(--admin-font-base); font-size: var(--admin-fs-body); @@ -531,7 +531,7 @@ .school-admin-workbench .el-table tr, .school-admin-workbench .el-table td.el-table__cell { - background: rgba(3, 24, 36, 0.58); + background: #ffffff; border-color: #e5e7eb; font-family: var(--admin-font-base); font-size: var(--admin-fs-body-sm); @@ -548,7 +548,7 @@ } .school-admin-workbench .el-table .el-loading-mask { - background: rgba(4, 18, 29, 0.72) !important; + background: rgba(249, 250, 251, 0.8) !important; backdrop-filter: blur(1px); } @@ -567,7 +567,7 @@ .school-admin-workbench .el-pagination { --el-pagination-bg-color: transparent; - --el-pagination-button-bg-color: rgba(3, 24, 36, 0.85); + --el-pagination-button-bg-color: #ffffff; --el-pagination-hover-color: #2563eb; color: #374151; font-family: var(--admin-font-base); @@ -580,6 +580,33 @@ font-size: var(--admin-fs-body-sm); } +/* 覆盖全局分页深色样式(全局规则带 !important,这里必须同样加 !important 才能生效): + 学校管理员工作台为浅色主题 */ +.school-admin-workbench .pagination-container .el-pagination .el-select .el-input__wrapper, +.school-admin-workbench .el-pagination .el-select .el-input__wrapper { + background: #ffffff !important; + border: 1px solid #d1d5db !important; + box-shadow: none !important; +} + +.school-admin-workbench .el-pagination .el-select .el-input__inner { + color: #374151 !important; +} + +.school-admin-workbench .el-pagination .btn-prev, +.school-admin-workbench .el-pagination .btn-next, +.school-admin-workbench .el-pagination .el-pager li { + background-color: #ffffff !important; + color: #374151 !important; + border: 1px solid #d1d5db !important; +} + +.school-admin-workbench .el-pagination .el-pager li.is-active { + background: #2563eb !important; + color: #ffffff !important; + border-color: #2563eb !important; +} + .school-admin-workbench .el-pagination__total, .school-admin-workbench .el-pagination__jump { font-family: var(--admin-font-base); @@ -592,7 +619,7 @@ padding: 18px; border: 1px solid #e5e7eb; border-radius: 22px; - background: rgba(0, 20, 35, 0.62); + background: #ffffff; } .school-admin-workbench .el-form-item__label { diff --git a/src/views/schoolAdmin/class/index.vue b/src/views/schoolAdmin/class/index.vue index e387228..ade0f5d 100644 --- a/src/views/schoolAdmin/class/index.vue +++ b/src/views/schoolAdmin/class/index.vue @@ -1,10 +1,5 @@