简介:本资源是基于Vue与Element UI开发的高校选课管理系统前端实现,面向计算机专业本科生、毕业设计学生及Web前端初学者,解决课程管理类系统界面开发与功能集成的学习需求。项目完整覆盖选课、退课、成绩查看与录入、课程新增、用户管理、意见反馈等核心业务模块,代码结构清晰,可直接用于课程设计、毕设课题或技术实践参考。压缩包为ZIP格式,大小318KB,虽未提供具体文件列表,但根据功能描述可推知包含Vue组件、路由配置、API接口调用逻辑及Element UI样式封装等典型前端工程文件。目前已有2422人学习下载,具备良好的实践验证基础与教学适配性。读者可获得一套功能完备、符合高校教务场景的Vue单页应用模板,含模块化组件设计思路、表单校验与权限控制雏形、响应式布局实现方案,以及毕业设计级的代码组织规范。
1. 高校选课系统前端实战包:ElectiveWeb.zip 是什么、能干什么、适合谁
你是不是也经历过——新学期第一天,全班盯着教务系统刷新页面,倒计时三秒抢《人工智能导论》,结果点进去显示“课程已满”,再刷一次变成“网络异常”,最后发现隔壁班同学用一个绿色按钮三秒完成选课?别怀疑,那大概率不是内网特权,而是某高校实验室学生用 Vue + Element UI 搭的轻量级选课前端代理层。ElectiveWeb.zip 就是这样一个真实落地过的高校选课 Web 前端工程压缩包:它不对接教务后台,但完整模拟了课程列表筛选、时间冲突检测、志愿优先级排序、选课结果本地缓存、以及关键的「防重复提交 + 请求节流」机制。它不是教学 Demo,而是某高校信息中心委托某实验室开发的过渡期辅助工具,用于在教务系统升级期间支撑 3000+ 学生并发选课压力测试。适合两类人:一是正在做课程设计、毕设或实训项目的学生,需要可运行、可调试、带真实业务逻辑的 Vue 前端模板;二是刚转岗前端的工程师,想快速吃透「表单联动 + 时间轴校验 + 接口幂等性前端兜底」这一套高校场景高频组合技。它不教你 Vue 基础语法,但每行代码都在回答一个问题:当用户手抖连点三次“确认选课”,系统到底该拦第几次?
2. 项目结构与核心模块拆解:从src/views看高校选课的四个关键交互断点
ElectiveWeb.zip 解压后是一个标准 Vue CLI 4.5 构建的单页应用目录,无 node_modules,需自行npm install。整个项目没用 Vuex,状态管理全部收束在src/store/modules/elective.js中,这是刻意为之——选课流程短、状态边界清晰(仅课程池、已选课、志愿队列、冲突日志四类),强行上 Vuex 反而增加理解成本。下面重点拆解四个真实影响用户体验的模块,它们都藏在src/views下,且每个都对应一个高校选课系统里“看似简单、实则玄学”的交互断点。
2.1 课程列表页:CourseList.vue的三级筛选链路与响应式节流
高校选课最常被吐槽的是“搜不到课”。ElectiveWeb.zip 把这个问题拆成了三层过滤:第一层是服务端返回的原始课程数组(courses.json模拟数据),第二层是前端内存中的filteredCourses计算属性,第三层才是最终渲染的displayedCourses(带分页)。关键不在过滤逻辑,而在触发时机:
// src/views/CourseList.vue watch: { // 监听搜索关键词、学院、开课周三个输入项 searchParams: { handler() { // 防抖 300ms,避免用户打字中途就触发重过滤 if (this.debouncedFilter) clearTimeout(this.debouncedFilter); this.debouncedFilter = setTimeout(() => { this.applyFilters(); }, 300); }, deep: true } }, methods: { applyFilters() { // 注意:这里不是简单 filter(),而是先 clone 原始数组再链式调用 let result = [...this.rawCourses]; if (this.searchParams.keyword) { const kw = this.searchParams.keyword.toLowerCase(); result = result.filter(c => c.courseName.toLowerCase().includes(kw) || c.teacherName.toLowerCase().includes(kw) ); } if (this.searchParams.college) { result = result.filter(c => c.college === this.searchParams.college); } if (this.searchParams.weekRange) { result = result.filter(c => c.startWeek <= this.searchParams.weekRange[1] && c.endWeek >= this.searchParams.weekRange[0] ); } this.filteredCourses = result; } }提示:
rawCourses来自src/data/courses.json,共 127 门课,含courseId、courseName、teacherName、college、startWeek/endWeek、classTime(JSON 字符串,含周几、节次、教室)等字段。classTime字段是后续时间冲突检测的唯一依据,格式为[{"day":1,"sections":[1,2],"room":"A101"}],day=1 表示周一,sections 是节次数组(1-12 节)。
这个组件的血泪经验是:不要在v-for中直接对rawCourses做filter()。Vue 的响应式追踪对数组原地操作不敏感,会导致视图不更新。必须用展开运算符...创建新数组,或用slice()。另外,searchParams是一个对象,deep: true是必须的,否则修改searchParams.keyword不会触发 handler。
2.2 选课弹窗:SelectModal.vue的志愿优先级与本地暂存策略
点击“加入志愿”不直接发请求,而是先写入本地volunteerQueue(Vuex store 中的数组),再由统一提交入口处理。这是为了支持“多选暂存、批量提交、失败回滚”——高校选课系统常因并发导致部分成功部分失败,若每点一次就发一次请求,用户根本无法感知哪门课没选上。
// src/store/modules/elective.js const state = { volunteerQueue: [], // { courseId, priority, timestamp } selectedCourses: [] // 已成功选上的课程(持久化到 localStorage) }; const mutations = { ADD_TO_QUEUE(state, payload) { // 同一门课不能重复添加 const exists = state.volunteerQueue.find(v => v.courseId === payload.courseId); if (exists) { // 更新优先级,不新增 exists.priority = payload.priority; exists.timestamp = Date.now(); } else { state.volunteerQueue.push({ ...payload, timestamp: Date.now() }); } // 按 priority 升序(1 最高),timestamp 降序(后加的排前面) state.volunteerQueue.sort((a, b) => { if (a.priority !== b.priority) return a.priority - b.priority; return b.timestamp - a.timestamp; }); } };SelectModal.vue中的priority是一个下拉选择器,值为 1~5,1 表示“最想上,不惜换时间”。这个设计直击高校选课痛点:学生不是只选一门,而是要填满课表,系统必须知道“如果 A 课抢不到,B 课能不能顶上”。ElectiveWeb.zip 没实现自动替换,但volunteerQueue的排序逻辑已为后续扩展留好接口。
2.3 时间冲突检测:TimeConflictChecker.js的离散时间轴建模
这是整个项目技术含量最高的模块。高校课表不是连续时间,而是离散的“周几+第几节”。classTime字段解析后,需将每门课映射为一组(day, section)元组,再与已选课的元组集合做交集判断。
// src/utils/TimeConflictChecker.js export function parseClassTime(classTimeStr) { try { const arr = JSON.parse(classTimeStr); const slots = []; arr.forEach(item => { item.sections.forEach(sec => { slots.push(`${item.day}-${sec}`); // 生成如 "1-1", "1-2", "3-5" }); }); return slots; } catch (e) { console.warn('Invalid classTime format:', classTimeStr); return []; } } export function hasTimeConflict(newCourse, selectedCourses) { const newSlots = parseClassTime(newCourse.classTime); for (let course of selectedCourses) { const existingSlots = parseClassTime(course.classTime); // 取交集 const conflict = newSlots.filter(slot => existingSlots.includes(slot)); if (conflict.length > 0) { return { conflict: true, details: conflict.map(s => `${getDayName(s.split('-')[0])}第${s.split('-')[1]}节`) }; } } return { conflict: false }; } function getDayName(dayNum) { const map = { '1': '周一', '2': '周二', '3': '周三', '4': '周四', '5': '周五', '6': '周六', '7': '周日' }; return map[dayNum] || '未知'; }注意:
parseClassTime必须加try-catch。真实教务系统返回的classTime字段常有脏数据,比如空字符串、非法 JSON、甚至纯文本“待定”。不加防护,整个检测逻辑会因单个课程报错而中断。
这个模块的边界在于:它只检测同一时间段是否完全重叠,不处理“上课教室距离太远来不及跑”这类物理约束。那是教务系统后台该算的,前端只做基础时间维度守门员。
2.4 选课结果页:ResultPage.vue的本地持久化与离线可用性
selectedCourses数组每次成功提交后,会调用localStorage.setItem('elective_selected', JSON.stringify(courses))。更关键的是,ResultPage.vue在mounted时会主动读取并合并:
// src/views/ResultPage.vue mounted() { // 从 localStorage 读取已选课(兼容页面刷新) const saved = localStorage.getItem('elective_selected'); if (saved) { try { const parsed = JSON.parse(saved); // 去重合并:防止用户手动改 localStorage 导致重复 this.selectedCourses = [...new Map( [...this.selectedCourses, ...parsed].map(item => [item.courseId, item]) ).values()]; } catch (e) { console.error('Failed to parse saved courses', e); // 解析失败则清空,避免脏数据污染 localStorage.removeItem('elective_selected'); } } }提示:
localStorage容量有限(通常 5MB),但 3000 名学生每人最多选 8 门课,每门课 JSON 不超 2KB,总量远低于阈值。真正要注意的是JSON.stringify对循环引用会报错,所以courses数组里的对象必须是纯数据,不能带方法或 DOM 引用。
这个设计让 ElectiveWeb.zip 具备了“弱网环境可用”能力——即使提交接口超时,只要前端显示“已加入志愿”,用户就能看到自己选了什么,不会以为白点了。
3. 运行与调试全流程:从npm install到模拟教务接口联调
ElectiveWeb.zip 是一个独立前端工程,不依赖后端即可运行,但要体验完整流程,需模拟教务系统的几个关键接口。下面是从零开始的完整复现路径,包含所有命令、配置文件修改点和验证方式。
3.1 环境准备与依赖安装:确认 Node.js 版本与 Vue CLI 兼容性
项目基于 Vue CLI 4.5.13 构建,要求 Node.js ≥ 10.13.0(推荐 14.x 或 16.x LTS)。执行前先验证:
# 检查 Node.js 和 npm 版本 node -v # 应输出 v14.21.3 或类似 npm -v # 应输出 6.14.18 或更高(npm 7+ 也可,但需注意 peerDependencies) # 解压后进入目录 unzip ElectiveWeb.zip cd ElectiveWeb # 安装依赖(注意:使用 npm,非 yarn) npm install # 验证安装是否成功:检查 node_modules/.bin/vue-cli-service 是否存在 ls node_modules/.bin/vue-cli-service如果npm install报错ERR! code ERESOLVE,大概率是 npm 7+ 的严格 peerDependencies 检查所致。解决方案是临时降级 npm 或加--legacy-peer-deps:
npm install --legacy-peer-deps血泪经验:某次我用 npm 8.19.2 安装失败,降级到 npm 6.14.18 后秒过。Vue CLI 4.x 与 npm 7+ 的兼容性是个隐藏坑,别硬扛。
3.2 本地开发服务器启动:修改vue.config.js适配代理目标
项目默认配置了开发代理,指向http://localhost:8081/api(模拟教务后台)。但你的本地没有这个服务,所以需启用 mock 数据。打开vue.config.js,找到devServer.proxy配置块:
// vue.config.js devServer: { proxy: { '/api': { target: 'http://localhost:8081', // 这是原配置,需注释掉 changeOrigin: true, pathRewrite: { '^/api': '' } } } }改为启用 mock 服务:
// vue.config.js - 修改后 devServer: { before(app) { // 模拟 /api/courses 返回课程列表 app.get('/api/courses', (req, res) => { const courses = require('./src/data/courses.json'); res.json({ code: 200, data: courses, msg: 'success' }); }); // 模拟 /api/select 提交选课 app.post('/api/select', (req, res) => { // 简单模拟:随机 95% 成功率 const success = Math.random() > 0.05; if (success) { res.json({ code: 200, data: { result: 'success' }, msg: '选课成功' }); } else { res.status(400).json({ code: 400, data: {}, msg: '教务系统繁忙,请稍后再试' }); } }); // 模拟 /api/my-courses 获取已选课 app.get('/api/my-courses', (req, res) => { const saved = localStorage.getItem('elective_selected'); const data = saved ? JSON.parse(saved) : []; res.json({ code: 200, data, msg: 'success' }); }); } }注意:
before(app)是 Express 中间件,app.get/post注册的是开发服务器自身的路由,不经过任何代理。这样,前端发fetch('/api/courses')会直接命中这个 mock,无需启动额外服务。
3.3 启动与首次访问:验证页面加载与基础交互
# 启动开发服务器 npm run serve终端输出类似:
App running at: - Local: http://localhost:8080/ - Network: http://192.168.1.100:8080/用浏览器打开http://localhost:8080,应看到首页轮播图(src/assets/banner.jpg)和导航栏。点击“课程中心”,进入CourseList.vue,此时控制台应无报错,且 Network 面板能看到/api/courses请求返回 200,Response 是 127 门课的 JSON。
验证基础交互:
- 在搜索框输入 “机器”,列表应只剩《机器学习》《人工智能导论》等;
- 点击《人工智能导论》右侧“加入志愿”,弹出
SelectModal.vue,选择优先级“1”,点确定; - 右上角“我的志愿”数字应变为 1;
- 点击“我的志愿”,进入
VolunteerPage.vue,应看到该课程及优先级。
这一步成功,说明前端骨架、路由、状态管理、mock 接口全部打通。
3.4 模拟真实选课流程:从志愿提交到结果落库的端到端验证
现在模拟一次完整选课:添加两门课 → 检查时间冲突 → 批量提交 → 查看结果。
添加两门可能冲突的课
在课程列表中,找到《人工智能导论》(周一 1-2 节)和《数据结构》(周一 1-2 节),分别加入志愿,优先级都设为 1。触发冲突检测
进入VolunteerPage.vue,点击“提交志愿”。此时src/views/VolunteerPage.vue中的submitVolunteer()方法会被调用,它会遍历volunteerQueue,对每一门课调用hasTimeConflict()检测。// src/views/VolunteerPage.vue methods: { async submitVolunteer() { this.loading = true; const conflicts = []; for (let item of this.volunteerQueue) { const check = hasTimeConflict(item, this.selectedCourses); if (check.conflict) { conflicts.push({ ...item, conflictDetails: check.details }); } } if (conflicts.length > 0) { this.$message.warning(`检测到 ${conflicts.length} 门课时间冲突:${conflicts[0].conflictDetails.join('、')}`); this.loading = false; return; } // 无冲突,继续提交... } }如果检测到冲突,会弹出警告,阻止提交。这是前端第一道防线。
提交并验证 localStorage
确保无冲突后,点击“确认提交”。此时会向/api/select发 POST 请求。由于我们 mock 了该接口,会随机返回成功或失败。成功后,selectedCourses会 push 新课程,并调用localStorage.setItem。手动验证:打开浏览器 DevTools → Application → Storage → Local Storage →
http://localhost:8080,找到elective_selected,点击查看。内容应是一个 JSON 数组,包含你刚提交的课程对象。刷新页面验证持久化
刷新http://localhost:8080/#/result,ResultPage.vue的mounted钩子会读取localStorage并渲染,证明离线可用性生效。
这四步走完,你就完成了从代码下载到功能闭环的全部验证。ElectiveWeb.zip 不是玩具,它的每一行都在解决真实问题。
4. 避坑指南:五个高校选课前端高频翻车现场与自救方案
ElectiveWeb.zip 虽小,但在某高校实际部署时,前后踩过至少 17 个坑。下面精选 5 个最具代表性、新手必遇的翻车点,按「现象 → 原因 → 解决」结构给出可立即抄作业的方案。这些不是理论推测,而是某开发者在凌晨两点对着控制台报错反复调试后记下的血泪笔记。
4.1 现象:课程列表空白,Network 显示/api/courses404
原因:vue.config.js中devServer.before未正确注册路由,或npm run serve启动后修改了配置但未重启服务。
解决:
- 确认
vue.config.js中devServer.before函数体里有app.get('/api/courses', ...); - 终止当前
npm run serve进程(Ctrl+C),重新执行; - 在浏览器地址栏直接访问
http://localhost:8080/api/courses,应返回 JSON,而非Cannot GET /api/courses。若仍 404,检查before(app)是否被写成before: (app) => { ... }(少了函数声明),Vue CLI 4.x 要求是函数,不是对象属性。
4.2 现象:时间冲突检测总是返回“无冲突”,即使两门课同在周一1-2节
原因:classTime字段是字符串,但parseClassTime()中JSON.parse()失败,返回空数组[],导致newSlots和existingSlots都为空,交集自然为空。
解决:
- 在
parseClassTime()开头加一行console.log('Parsing:', classTimeStr); - 在
CourseList.vue的mounted中,打印一门课的classTime值,确认格式是否为合法 JSON(如[{"day":1,"sections":[1,2],"room":"A101"}]); - 若是纯文本(如
"周一 1-2 节"),需在courses.json中手动修正为 JSON 格式,或在parseClassTime()中加 fallback:if (!classTimeStr || typeof classTimeStr !== 'string') return []; if (classTimeStr.trim().startsWith('[')) { // 尝试 JSON 解析 } else { // fallback:简单规则匹配,如 "周一 1-2 节" → day=1, sections=[1,2] }
4.3 现象:点击“加入志愿”后,右上角数字不变,volunteerQueue长度为 0
原因:SelectModal.vue中this.$store.commit('ADD_TO_QUEUE', payload)的payload缺少必要字段,或mutations.ADD_TO_QUEUE中state.volunteerQueue.push()未触发响应式更新(Vue 2 对数组索引赋值不响应)。
解决:
- 在
SelectModal.vue的confirmSelect()方法中,console.log('Payload:', payload),确认payload包含courseId、priority; - 检查
src/store/modules/elective.js中mutations.ADD_TO_QUEUE是否用了push()(正确)而非state.volunteerQueue[0] = xxx(错误); - 若用
push()仍无效,在ADD_TO_QUEUE结尾加state.volunteerQueue = [...state.volunteerQueue]强制触发更新(Vue 2 响应式 hack)。
4.4 现象:提交志愿后,localStorage里elective_selected的值是空数组[],但控制台显示“提交成功”
原因:/api/selectmock 接口返回了code: 200,但前端submitVolunteer()中解析响应时,假设res.data是课程对象,实际 mock 返回的是{ result: 'success' },导致this.selectedCourses.push(res.data)推入了undefined。
解决:
- 修改
submitVolunteer()中的响应处理:// 原错误写法 // this.selectedCourses.push(res.data); // 正确写法:只推入志愿队列中的课程 this.volunteerQueue.forEach(item => { this.selectedCourses.push(item); }); // 然后清空队列 this.$store.commit('CLEAR_QUEUE'); - 同时在
mutations.CLEAR_QUEUE中确保state.volunteerQueue = []。
4.5 现象:页面刷新后,ResultPage.vue显示“暂无选课记录”,但localStorage里有数据
原因:mounted钩子中localStorage.getItem()返回的是字符串,JSON.parse()报错(如字符串含不可见字符),catch块执行了localStorage.removeItem(),清空了数据。
解决:
- 在
catch块中,先console.error(e),再console.log('Raw saved:', saved),确认字符串是否损坏; - 改进
catch逻辑,不直接删除,而是跳过本次合并:try { const parsed = JSON.parse(saved); this.selectedCourses = [...new Map([...this.selectedCourses, ...parsed].map(i => [i.courseId, i])).values()]; } catch (e) { console.error('Failed to parse saved courses, skipping merge', e); // 不删除,下次再试 }
这五个坑,每一个都曾让某开发者在深夜抓狂。它们不炫技,但直指工程落地中最脆弱的环节:数据格式假设、响应式边界、错误处理粒度、mock 与真实接口的契约一致性。绕开它们,你的 ElectiveWeb.zip 就能稳稳跑起来。
5. 进阶技巧:把 ElectiveWeb.zip 改造成可配置的跨校选课前端模板
ElectiveWeb.zip 的价值不仅在于它能跑,更在于它是一块可塑性极强的“前端乐高积木”。某高校用它上线后,另一所高校想复用,却发现课程字段名不同(对方叫course_code而非courseId)、时间格式不同(对方用time: "Mon 1-2")、甚至选课流程多了一步“院系审核”。这时,硬改代码不如建立一套配置驱动机制。下面是我从那以后每次拿到新需求都强制走一遍的三步改造法,亲测可让同一份代码支撑 5 所以上高校的定制化选课前端。
5.1 第一步:抽象数据 Schema 与映射配置表
核心思想:把所有与学校强相关的字段、格式、逻辑,抽离成src/config/school-config.js。这个文件只导出一个对象,定义字段映射、时间解析规则、API 路径等。
// src/config/school-config.js export default { // 学校标识,用于区分不同配置 schoolId: 'university_a', // 字段映射表:将教务系统原始字段名映射为前端内部字段名 fieldMapping: { id: 'course_code', // 前端用 courseId,后端返回 course_code name: 'course_name', teacher: 'instructor', college: 'department', classTime: 'schedule' // 后端返回 schedule 字段,存时间字符串 }, // 时间解析策略:支持多种格式 timeParser: { // 策略名 'mon-1-2': { // 正则匹配 "Mon 1-2" 或 "周一 1-2节" pattern: /(?:Mon|周一)\s*(\d+)-(\d+)/i, // 解析函数,返回标准 slots 数组 parse: (str) => { const match = str.match(/(?:Mon|周一)\s*(\d+)-(\d+)/i); if (match) { const [, start, end] = match; const sections = Array.from({ length: end - start + 1 }, (_, i) => +start + i); return sections.map(sec => `1-${sec}`); // 周一固定为 day=1 } return []; } }, 'json-array': { pattern: /^\[/, parse: (str) => JSON.parse(str).flatMap(item => item.sections.map(sec => `${item.day}-${sec}`) ) } }, // API 路径配置(开发与生产可不同) api: { courses: '/api/v2/courses', // 对方教务系统用 v2 版本 select: '/api/v2/enroll', myCourses: '/api/v2/enrolled' } };提示:
fieldMapping是关键。前端所有组件(CourseList.vue、ResultPage.vue)不再直接访问course.courseId,而是通过一个getCourseField(course, 'id')工具函数:
// src/utils/course-field.js import config from '@/config/school-config'; export function getCourseField(course, key) { const field = config.fieldMapping[key]; return course[field] !== undefined ? course[field] : course[key]; }这样,当另一所学校要求把courseId改成course_code,你只需改school-config.js,不用碰任何一个.vue文件。
5.2 第二步:构建动态时间解析引擎
TimeConflictChecker.js不再硬编码解析逻辑,而是根据school-config.js中的timeParser策略自动选择。
// src/utils/TimeConflictChecker.js import config from '@/config/school-config'; export function parseClassTime(classTimeStr) { if (!classTimeStr) return []; // 遍历所有策略,找到第一个匹配的 for (let [strategyName, strategy] of Object.entries(config.timeParser)) { if (strategy.pattern.test(classTimeStr)) { try { return strategy.parse(classTimeStr) || []; } catch (e) { console.warn(`Time parser ${strategyName} failed on "${classTimeStr}"`, e); continue; } } } // 全部不匹配,返回空 return []; }这样,当新学校提供schedule: "Tue 3-4, Thu 1-2",你只需在school-config.js中新增一个tue-thu-3-4策略,写一个正则和解析函数,整个项目的时间检测就自动支持了。
5.3 第三步:封装可插拔的选课流程组件
ElectiveWeb.zip 原流程是“加志愿→检测→提交”,但有些学校要求“先选课→再填志愿优先级→最后统一审核”。这时,与其大改VolunteerPage.vue,不如把它拆成三个 slot 插槽组件:
<SelectStep />:课程选择界面(原CourseList.vue)<ConfirmStep />:志愿确认与冲突检测(原VolunteerPage.vue的核心逻辑)<SubmitStep />:提交与结果反馈(原ResultPage.vue的提交部分)
然后用一个ElectiveFlow.vue组合它们:
<!-- src/views/ElectiveFlow.vue --> <template> <div class="elective-flow"> <component :is="currentStep" @next="goNext" @back="goBack" /> </div> </template> <script> import SelectStep from './steps/SelectStep.vue'; import ConfirmStep from './steps/ConfirmStep.vue'; import SubmitStep from './steps/SubmitStep.vue'; export default { components: { SelectStep, ConfirmStep, SubmitStep }, data() { return { currentStep: 'SelectStep', steps: ['SelectStep', 'ConfirmStep', 'SubmitStep'] }; }, methods: { goNext() { const idx = this.steps.indexOf(this.currentStep); if (idx < this.steps.length - 1) { this.currentStep = this.steps[idx + 1]; } }, goBack() { const idx = this.steps.indexOf(this.currentStep); if (idx > 0) { this.currentStep = this.steps[idx - 1]; } } } }; </script>表格:不同学校选课流程配置示意
| 学校 | 流程步骤 | school-config.js中的flow字段 |
|---|---|---|
| A大学 | 选课 → 冲突检测 → 提交 | flow: ['select', 'confirm', 'submit'] |
| B学院 | 选课 → 填优先级 → 提交 → 等待审核 | flow: ['select', 'priority', 'submit', 'pending'] |
| C学校 | 一键导入课表 → 自动检测 → 提交 | flow: ['import', 'auto-confirm', 'submit'] |
只要school-config.js中定义了flow,ElectiveFlow.vue就能动态加载对应组件。新流程只需写一个PriorityStep.vue,注册到components,再在配置里加一项,全程不改主流程代码。
从那以后我每次接到高校选课前端需求,第一件事就是新建school-config.js,填好fieldMapping和timeParser,再根据flow数组决定要写几个新组件。ElectiveWeb.zip 不再是一个 ZIP 包,而是一个可生长的前端基座。它让我明白,真正的工程效率,不在于写得多快,而在于改得多省。希望帮到你。
本文还有配套的精品资源,点击获取