码道 │ 校园服务助手AI:用纯前端打造「能办事」的智能校园助手

发布时间:2026/10/7 12:55:25
码道 │ 校园服务助手AI:用纯前端打造「能办事」的智能校园助手 从一份 Python 参考实现出发到一套零依赖的 HTML5CSS3JavaScript 完整应用——记录我在码道CodeArts中从需求、选型、实现到上线的全过程以及藏在代码背后关于提示词工程、流式传输和前端渲染的一些思考。仓库地址https://gitcode.com/gcw_FBvLjF2H/xiaoyuanfuwuzhushouAI.git码道项目生成一、缘起校园服务的最后一公里移动互联网让我们的生活越来越便利但走进高校校园同学们依然要面对不少传统的困扰想请假不知道流程要走哪几步饭卡丢了翻遍校园网不知道去哪补办宿舍空调坏了报修电话忘在了某张皱巴巴的通知单上想找个自习室图书馆开没开门都搞不清楚……这些信息并非不存在而是散落在校园网公告、各学院公众号、辅导员通知群和各种纸质文件里。信息越分散找起来就越麻烦流程越不透明办起事就越心累。这背后其实是校园服务的最后一公里问题服务已经数字化但入口和体验还没有真正走向学生。于是我想做一个校园服务助手——让同学只需要像聊天一样提问就能得到该找谁、去哪办、怎么办、带什么材料这样清清楚楚的答案。更进一步如果 AI 能直接返回结构化的服务卡片那么办事流程、通知列表、报修入口就可以像手机 App 一样优雅地呈现在页面上而不是让用户在一大段文字里自己划重点。这个想法最终落地为一个完整的开源项目校园服务助手AICampus Service Assistant AI。它全部由 HTML5、CSS3 和 JavaScript 写成不依赖任何第三方框架和构建工具打开浏览器即可运行并且通过与大模型DeepSeek-V4-Flash的深度协作把纯聊天升级为智能办事。二、项目概览它到底能做什么校园服务助手AI 是一个面向高校师生的智能问答与办事平台页面采用「左侧快捷服务 右侧对话区」的双栏布局移动端自动切换为单栏纵向排列。它的核心能力可以概括为一句话听懂问题 → 识别服务意图 → 返回结构化数据 → 渲染业务卡片 → 引导下一步操作。具体来说系统目前覆盖七类校园服务场景办事指南uri_guide请假、补饭卡、成绩单打印、奖学金申请等流程类问题返回分步骤的操作指引并给出负责部门、办理地点、咨询电话和办理时间校园通知notice近期重要通知与活动汇总以列表 分类标签 日期形式呈现报修服务repair水电、网络、空调等故障报修返回报修步骤、报修电话和处理时效提示地点导航map图书馆、食堂、教学楼等位置的开放时间与路线说明课程查询course课表示例与选课相关指引标注教师、时间、地点等信息图书馆服务library开放时间、借阅规则、座位预约等普通聊天chat服务范围之外的问题作为通用校园助手应答。每一次回答还附带 2~3 个「追问建议」用户点一下就能继续深入形成自然的对话闭环。技术层面项目由 6 个文件构成结构非常克制xiaoyuanfuwuzhushouAI/ ├── index.html # 页面骨架与交互容器 ├── css/style.css # 主题与全部样式含业务卡片、气泡、响应式 └── js/ ├── config.js # 接口配置、系统提示词、快捷服务配置 ├── api.js # 流式请求封装SSE 逐行解析 └── app.js # 应用逻辑对话、流式渲染、JSON 解析、卡片渲染整个项目没有任何package.json、没有node_modules、没有构建步骤。无论你把服务器搭在一台古董级的主机上还是拷贝到学生的树莓派里它都能一秒跑起来。这正是纯前端方案最打动我的地方极低的运行门槛和极高的可移植性。三、技术选型为什么坚持零依赖在开发之前我认真权衡过技术选型。作为一个校园项目我面对的现实约束有三个第一运行环境不可控。项目最终可能跑在实验室的旧电脑、学生会的共享服务器甚至老师的笔记本上未必装得了 Node.js 或 Python 环境更不可能维护一套前后端分离的工程。第二维护成本要压到最低。校园项目最常见的情况是开发者往往是我这样的学生毕业离校后项目就失去了维护者。如果技术栈足够简单朴素学弟学妹们打开三四个文件就能看懂全貌项目才有活下去的可能。第三AI 能力要能直接用。当时的想法很朴素既然大模型已经开放了 HTTP 接口浏览器本身就有 fetch 能力那为什么一定要中间再隔一层后端针对第三点我做了关键性的验证——跨域CORS。如果浏览器访问 AI 接口时被跨域策略拦下纯前端方案就得推倒重来。我用一次带Origin头的请求实测了接口的响应头看到了Access-Control-Allow-Origin、Access-Control-Allow-Headers: ...Authorization...等字段确认接口支持浏览器直连。那一刻我知道这个方案可行。最终的技术栈由此确定HTML5 CSS3 JavaScript一个文件都不多装也没有一行后端代码。浏览器直连大模型接口天然避开了一些同学担心的后端反代复杂度。当然纯前端方案也有它的代价——比如 API Key 直接暴露在前端代码里这一点我会在后面「安全与配置」一节详细讨论。四、核心攻坚一把 Python 流式实现移植到 JavaScript项目最早的雏形是一份 Python 参考实现它用requests库以流式方式调用大模型接口逐行读取并解析返回的 SSE 数据responserequests.post(API_URL,headersheaders,jsonpayload,streamTrue)forlineinresponse.iter_lines():ifnotline.startswith(bdata:):continueifline.strip()bdata:[DONE]:returnyieldjson.loads(line.decode(utf-8).lstrip(data:))这份代码的核心能力是流式读取大模型的回复是逐 token 生成的接口通过 SSEServer-Sent Events协议把这些 token 分帧推送过来客户端边收边显示用户等答案的体验就从盯着转圈变成了看着 AI 打字。要在浏览器里复刻同样的能力我要面对的是一系列和 Python 侧截然不同的问题1. 从iter_lines()到ReadableStreamPython 的iter_lines()天然按行迭代而浏览器的fetch返回的response.body是一个字节流。标准做法是用getReader()配合TextDecoder把字节流解码为文本然后自己按\n切分constreaderresponse.body.getReader();constdecodernewTextDecoder(utf-8);letbuffer;while(true){const{done,value}awaitreader.read();if(done)break;bufferdecoder.decode(value,{stream:true});constlinesbuffer.split(\n);bufferlines.pop();// 保留可能被拆散的半行// 逐行处理...}这里藏着一个新手最容易踩的坑网络分帧和 SSE 行并不对齐。一次reader.read()返回的可能是半行 JSON也可能是好几行粘连在一起。所以必须维护一个缓冲区先把尾巴留住等下一帧到达后再拼完整。我在api.js里专门处理了这个问题还在流结束后的收尾逻辑里兜底解析了残留的缓冲区数据。2. 按行解析 SSE 帧每条 SSE 帧的格式是data: {...}以\n为界。解析逻辑和 Python 版几乎一一对应constlinerawLine.trim();if(!line.startsWith(data:))continue;// 等价于 Python 的 startswith(bdata:)constdataline.slice(5).trim();if(!data||data[DONE])continue;// 结束标记constchunkJSON.parse(data);// 解析增量constdeltachunk.choices[0].delta;if(delta.content)onContent(delta.content);// 正文增量需要特别说明的是当前模型返回的增量里其实有两个字段delta.reasoning_content模型的思考过程和delta.content最终输出。这个思考与回答分离的特性很有意思——思考过程默认是隐藏的我在界面上用一个「 正在思考请稍候…」的呼吸动画来承接这个过程等正式内容开始输出后动画自动收起正文以打字机效果逐字展现。比起干等空白用户至少能感知到它真的在动脑。3. 中断控制Python 版的流式调用一旦发起就难以优雅中断而浏览器给我们提供了AbortController——用户点击停止生成按钮时controller.abort()会通过signal传播到正在读取的流代码捕获AbortError后把已收到的部分内容保留下来不做报错处理体验干净利落。4. 错误兜底网络异常、接口鉴权失败、响应流中断……我把这些情况分门别类做了处理HTTP 非 2xx 时尽量从响应体里解析出服务端的错误信息流读取抛错时给出友好提示解析帧失败时直接跳过SSE 数据量大个别脏帧不值得让整个会话崩溃。至此Python 侧的能力在浏览器里完整复刻而且只用了不到一百行代码。移植过程给我的体会是所谓语言迁移最大的工作量从来不在语法而在数据流模型与异常边界的对齐。五、核心攻坚二提示词工程让 AI 学会说 JSON如果只是把 Python 示例改成 JS那这个项目充其量是个会聊天的页面。让它变得真正有用靠的是第二步让模型按照约定的 JSON 结构回答问题。试想一下如果模型回复一段纯文字“请假要经过三步……”前端只能把它原样展示用户还是要在文字里自己找重点。但如果我们约定好返回结构{intent:uri_guide,reply_text:同学你好办理请假手续通常需要先与辅导员说明情况……,data:{title:请假手续办理,steps:[{title:提前沟通,desc:至少提前1-2个工作日向辅导员说明请假原因……},{title:填写申请单,desc:登录学校网上办事大厅或领取纸质请假申请单……}],department:学院学生工作办公室,location:各学院学工办,phone:以学院通知为准,time:工作日办公时间},suggestions:[病假需要准备哪些证明材料,销假流程是怎样的]}前端就能把这些字段精确渲染成带步骤编号、带元信息栏、带追问按钮的服务卡片。聊天界面瞬间从查资料升级成了办事情。1. 系统提示词的设计这个项目的灵魂就藏在js/config.js里那段系统提示词System Prompt中。设计它的时候我坚持了三条原则角色先行先明确你是谁、服务谁。模型被定义为熟悉的校园服务助手熟悉办事流程、通知公告、报修服务、地点、课程与图书馆服务对象是师生语气亲切专业格式硬约束明确要求每一次回答只输出一个合法的 JSON 对象不要输出任何多余文字不要用 markdown 代码块包裹。表述越具体模型跑题的概率越低Schema 示例把七种意图的data字段一一示范清楚。对模型来说一个精确的字段示例胜过一百句抽象描述——这是提示词工程里Few-shot的典型收益。2. 意图识别AI 的路由层intent字段是整套渲染机制的中枢。它相当于一个由大模型自己完成意图路由的路由器用户问怎么请假 →uri_guide用户问最近有什么活动 →notice用户报告空调坏了 →repair。前端拿到intent就知道该调用哪个渲染器显示哪种卡片。实测结果让我相当惊喜六类典型问题的意图识别全部正确。请假 →uri_guide、通知 →notice、报修 →repair、地点 →map、课表 →course、闲聊 →chat没有一个串门。3. 容错解析把模型当偶尔马虎的同事理想很丰满现实是模型偶尔还是会犯迷糊——可能在 JSON 外面包一层 json 代码块可能在你提问前后夹带一两句话。为此我在前端实现了一套三层容错的解析策略第一层直接JSON.parse模型严格遵守约定时走这条最快最干净第二层剥掉首尾的 json 代码块标记后再解析第三层截取文本中从第一个{到最后一个}的片段再做一次解析。如果三层全部失败就降级为普通文本模式原样展示保证对话永远不中断。这三层兜底让系统的健壮性有了质的提升——在真实业务里模型输出不可控是常态工程的价值就是把不可控收敛到可控的边界之内。六、渲染引擎intent 驱动的业务卡片拿到结构化 JSON 之后剩下的就是前端展示的艺术了。app.js里维护了一套基于intent的渲染映射每种意图对应一个卡片渲染函数办事指南卡带编号的步骤条 部门/地点/电话/时间元信息网格通知卡标题 彩色分类标签 日期 摘要的列表项报修卡步骤条 报修电话 时效提示地点卡地点名称 位置 开放时间徽标课表卡课程名 教师/时间/地点 周次徽标图书馆卡服务资源 开放时间 说明。每条卡片都带有淡入上移的入场动画由 CSS 的keyframes实现没有引入任何动画库。用户侧的消息是右对齐的蓝色气泡AI 侧是左对齐的白色气泡配紫色渐变头像一左一右界限分明。1. 轻量 Markdown 渲染与 XSS 防护模型回复的reply_text是 Markdown 格式但直接innerHTML是不可接受的——如果模型输出的文本里混入script或onerror之类的 HTML就会带来 XSS 注入风险。我的方案是先转义再转换先用escapeHtml把、、、、全部转义为实体这一步消灭了所有注入面再在转义后的安全文本上做 Markdown 转换标题、列表、加粗、行内代码、围栏代码块、引用、表格链接统一经过safeUrl校验只放行http:/https:协议杜绝javascript:伪协议。代码量和阅读体验都有一个不错的平衡。安全不该是功能做完后的补丁而应该是渲染链路里的默认值。2. 卡片的最后一公里追问建议我特别满意的是suggestions字段带来的交互闭环。每次回答结束卡片下方会出现 2~3 个追问按钮比如病假需要准备哪些证明材料“销假流程是什么样的”。用户点击后这些问题会被当作新消息发送模型会带着上下文继续回答。这个设计让从提问到办成事的路径变得非常自然——用户不需要思考’接下来该问什么’系统替他想好了。对话由此从单点问答进化成了多轮办理。3. 会话记忆与状态管理app.js维护了一个消息数组每次请求都会把系统提示词 最近 8 轮对话一起发给模型让 AI 记住上下文。为什么是 8 轮这是一个刻意的取舍校园助手的问题通常短小聚焦太久远的上下文既消耗 token 又可能引入噪音8 轮足够应付绝大多数场景也让请求体积保持克制。同时“停止生成”清空对话两个按钮把会话掌控权完全交还给用户。实现上它们都极其简单——前者靠AbortController后者只需清空消息数组并重建欢迎区。七、工程细节那些决定体验的小事1. API Key 的三级覆盖机制如前所述纯前端方案的最大软肋是 Key 会暴露在源码里。作为演示项目我在config.js里内置了一个默认 Key 保证开箱即用同时设计了三级覆盖机制方便使用者换上自己的 KeyURL 参数index.html?apiKey你的密钥优先级最高浏览器本地存储localStorage.setItem(campus_ai_key, 你的密钥)后刷新页面默认配置config.js中的默认值兜底。在博客里我必须把丑话说在前面任何前端代码里写死密钥的方案都不适合生产环境。真实场景的正确做法是让后端代理 AI 请求、通过环境变量持有密钥。这个项目的 URL/本地存储方案更多是为了演示与个人使用场景的便利README 里我也做了明确的提示。2. 状态可见性连接状态与错误提示页面的右上角常驻一个连接状态徽标绿色圆点表示服务在线红色则表示接口异常并会通过 Toast 弹出具体错误信息比如接口返回异常HTTP 401请检查 API Key。参数化报错在排障时价值巨大——把错误码和可能原因一起给用户而不是一句模糊的请求失败。3. 响应式体验桌面端是快捷服务侧栏 对话区的双栏窄屏小于 860px时侧栏折叠为顶部的横向快捷入口聊天区占满全宽更小屏下快捷服务自动变为两列。这些全都由 CSS 媒体查询完成没有一行额外 JavaScript。说实话写响应式布局的时候我脑子里想的是同学们的半屏小屏手机终于能舒舒服服地用了。八、测试与验证用数据说服自己代码写完验证必须跟上。这个过程是接口级 页面级两层进行的。1. 接口级验证六意图全部归位我写了一个批量测试脚本依次放入六类典型问题检查模型返回的 JSON 是否合规测试问题返回意图结果如何办理请假手续uri_guide✅ data 含步骤/部门/地点/电话/时间最近校园有什么重要通知notice✅ data 含通知列表与标签宿舍空调坏了怎么报修repair✅ data 含步骤与报修电话图书馆和第一食堂在哪里map✅ data 含地点与开放时间周三上午有什么课course✅ data 含课表条目你好介绍一下你自己吧chat✅ data 为 null六个用例全部返回规范 JSONsuggestions均稳定给出 3 条追问。这说明系统提示词写到位了。2. 页面级验证Playwright 真机演练接口对了还不够页面能不能跑通才是关键。我用 Playwright 启动了无头 Chromium对页面做了完整的功能演练页面加载 → 导航栏、6 个快捷入口、5 条欢迎建议全部就位点击办事指南 → 等待流式输出确认请假手续办理卡片渲染出 4 个步骤、3 条元信息点击追问建议 → 连续多轮对话正常自定义输入报修问题 → 报修卡正确渲染停止生成 → 发送按钮状态正确恢复清空对话 → 聊天区重置、欢迎页重现。整个跑测过程零 JS 报错。截图里卡片、气泡、徽标、追问按钮层层分明视觉得到了真实反馈。自动化的意义在于它把肉眼觉得没问题变成了可重复的合格证据。九、部署与使用纯前端项目在部署上几乎没有故事可讲——唯独有一个绕不开的坎必须在 HTTP 服务器下运行直接双击打开 HTML 会因浏览器安全策略file://下的跨域限制而失败。使用方式只有三步# 1. 进入项目目录并启动任意静态服务器python3-mhttp.server8080# 2. 可选通过 URL 参数注入自己的 API Key# http://localhost:8080/index.html?apiKey你的密钥# 3. 浏览器访问http://localhost:8080当然也可以用npx serve或 VS Code 的 Live Server本质都是一样的给浏览器一个合法的 HTTP 源。要上生产环境随便找一台最小配置的云主机、一个 Nginx把 6 个文件丢进去就成了一个校园服务站点。要是再想优雅一点套一层 HTTPS 域名就完全是正式产品的形态了。十、踩坑记那些值得记住的教训写到这里我想把开发过程中真正花过时间的坑串成清单既是自己的复盘也希望后来者少走弯路坑一GET 和 POST 的 CORS 是两回事。我一度用浏览器直接导航某个 GET 地址验证接口连通性以为 CORS 没问题了结果 POST 预检时才暴露 Authorization 头不在允许列表里。判断接口能否被浏览器直连必须带上真实请求头做实测。坑二网络分帧与 SSE 行不对齐。前面说过这是流式解析最容易隐性出错的地方。症状是偶尔丢字、偶尔卡片解析失败根源往往是缓冲区处理不严谨。流式代码必须把半行缓存当一等公民对待。坑三模型输出必须做多级容错。即便系统提示词写得再硬模型也可能加代码块、加前后缀。脚本化测试帮我抓到了这类问题的规律进而催生了三层解析 文本降级的兜底设计。坑四安全红线不能后补。Markdown 渲染几乎天然和 XSS 纠缠不清。先转义后渲染的顺序不能反链接协议白名单不能省——哪怕只是演示项目。十一、总结与展望项目做完了但它留给我的思考还在持续。从技术上看这个项目验证了一条对中小型校园开发者非常有价值的路径大模型接口的 HTTP 化 浏览器 CORS 的开放让纯前端接入 AI成为完全可行的架构。不需要后端、不需要运维、不需要构建链6 个文件就能跑起一个具备办事逻辑的 AI 应用。提示词工程在这里扮演了架构角色——JSON 结构化输出实际上成为前后端之间的接口契约这是项目最让我有收获的地方。当然我也清楚当前的版本仍有不少边界课表、成绩等个人数据因缺少教务系统对接而只能提供示例与指引API Key 直接暴露在前端只适合演示场景单机部署无法支撑并发访问。展望未来我设想中的进化方向有四个接入真实数据通过学校开放平台或合法授权的数据接口让通知、课表、报修工单活起来后端代理层增加一层轻量后端统一管理密钥、鉴权、限流与日志把安全边界收回来多端适配桌面 Web 之外继续打磨移动端体验甚至封装成 IM 机器人入口企业微信/钉钉Agent 化从问答 卡片走向工具调用——让 AI 能真正帮你提交请假单、生成报修工单完成信息的最后一跳。最后我想说的是这个项目诞生于对校园服务最后一公里的一点同理心成型于码道CodeArts里一次次写写改改、跑跑测测。它不算宏大但它把一个日常痛点、一项 AI 能力和一套克制的前端工程完整地串了起来。如果你也在做校园类项目欢迎把这份代码 fork 走、改造成属于你自己学校的数字助手——校园服务的智能化正是从这样一个个小的、能跑起来的尝试开始的。项目地址https://atomgit.com/gcw_FBvLjF2H/xiaoyuanfuwuzhushouAI技术栈HTML5 / CSS3 / JavaScript零依赖 · DeepSeek-V4-Flash · SSE 流式对话