import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import VueDevTools from "vite-plugin-vue-devtools"; import fs from "fs"; import path from "path"; function mockPlugin() { const mockDir = path.resolve(process.cwd(), "mock"); const filePath = (file) => path.resolve(mockDir, file); const send = (res, obj, code = 200) => { res.statusCode = code; res.setHeader("Content-Type", "application/json; charset=utf-8"); res.end(JSON.stringify(obj)); }; const readJson = (file, fallback) => { const p = filePath(file); if (!fs.existsSync(p)) return fallback; try { return JSON.parse(fs.readFileSync(p, "utf-8") || "null") ?? fallback; } catch { return fallback; } }; const writeJson = (file, data) => { const p = filePath(file); fs.writeFileSync(p, JSON.stringify(data, null, 2), "utf-8"); }; const readBodyJson = (req) => new Promise((resolve, reject) => { let body = ""; req.on("data", (c) => (body += c)); req.on("end", () => { try { resolve(JSON.parse(body || "{}")); } catch (e) { reject(e); } }); }); const getPathname = (req) => { try { return new URL(req.url, "http://localhost").pathname; } catch { return req.url; } }; // 统一 success const ok = (res) => send(res, { error: 0, msg: "success" }); // ===== 业务联动:积分更新规则 ===== // yearScore: score=duration*2,看起来是 1小时=2分 const POINTS_PER_HOUR = 2; const addScore = (deltaHour) => { const deltaScore = (Number(deltaHour) || 0) * POINTS_PER_HOUR; // userInfo.json const ui = readJson("userInfo.json", { error: 0, data: {} }); ui.data = ui.data || {}; ui.data.totalScore = (Number(ui.data.totalScore) || 0) + deltaScore; writeJson("userInfo.json", ui); // userScore.json const us = readJson("userScore.json", { error: 0, data: {} }); us.data = us.data || {}; us.data.totalScore = (Number(us.data.totalScore) || 0) + deltaScore; writeJson("userScore.json", us); // scoreOverview.json const ov = readJson("scoreOverview.json", { error: 0, data: {} }); ov.data = ov.data || {}; ov.data.totalScore = (Number(ov.data.totalScore) || 0) + deltaScore; writeJson("scoreOverview.json", ov); // yearScore.json const ys = readJson("yearScore.json", { error: 0, data: {} }); ys.data = ys.data || {}; ys.data.times = (Number(ys.data.times) || 0) + 1; ys.data.duration = (Number(ys.data.duration) || 0) + (Number(deltaHour) || 0); ys.data.score = (Number(ys.data.score) || 0) + deltaScore; writeJson("yearScore.json", ys); }; return { name: "simple-mock-plugin", configureServer(server) { server.middlewares.use(async (req, res, next) => { const pathname = getPathname(req); // ========== GET:仍然走你原来的静态文件 ========== const sendJSON = (file) => { const p = filePath(file); if (!fs.existsSync(p)) { return send(res, { error: `Mock file not found: ${file}` }, 404); } res.setHeader("Content-Type", "application/json; charset=utf-8"); res.end(fs.readFileSync(p, "utf-8")); }; if (pathname === "/api/userInfo" && req.method === "GET") return sendJSON("userInfo.json"); if (pathname.startsWith("/api/actList") && req.method === "GET") return sendJSON("actList.json"); if ( pathname.startsWith("/api/report/myOverview") && req.method === "GET" ) return sendJSON("scoreOverview.json"); if (pathname.startsWith("/api/report/rankList") && req.method === "GET") return sendJSON("rank.json"); if ( pathname.startsWith("/api/actDetails/details") && req.method === "GET" ) return sendJSON("actDetails.json"); if (pathname.startsWith("/api/myApplyList") && req.method === "GET") return sendJSON("myApplyList.json"); if ( pathname.startsWith("/api/act/publisherList") && req.method === "GET" ) return sendJSON("publishers.json"); if ( pathname.startsWith("/api/act/durationsList") && req.method === "GET" ) return sendJSON("durationList.json"); if ( pathname.startsWith("/api/service/userScore") && req.method === "GET" ) return sendJSON("userScore.json"); if ( pathname.startsWith("/api/service/yearList") && req.method === "GET" ) return sendJSON("yearList.json"); if ( pathname.startsWith("/api/service/yearScore") && req.method === "GET" ) return sendJSON("yearScore.json"); if (pathname.startsWith("/api/service/list") && req.method === "GET") { const sl = readJson("serviceList.json", { error: 0, data: { current: 1, pageSize: 10, pageCount: 1, list: [] }, }); const url = new URL(req.url, `http://${req.headers.host}`); const year = url.searchParams.get("year"); let list = sl.data?.list || []; if (year && year !== "all") { const yearNum = Number(year); list = list.filter((item) => { const date = new Date(item.time); return date.getFullYear() === yearNum; }); } const result = { error: 0, data: { ...sl.data, list, current: 1, pageCount: 1, }, }; return send(res, result); } if (pathname.startsWith("/api/service/details") && req.method === "GET") return sendJSON("recordDetails.json"); // ========== POST:全部改成“落盘” ========== // 1) 用户信息保存 if (pathname === "/api/userInfo/submit" && req.method === "POST") { try { const incoming = await readBodyJson(req); // {name,code,gender,phoneNum,school,profession,userType...} const old = readJson("userInfo.json", { error: 0, data: {} }); const next = { error: 0, data: { ...(old.data || {}), ...incoming }, }; writeJson("userInfo.json", next); return ok(res); } catch { return send(res, { error: 1, msg: "Invalid JSON body" }, 400); } } // 2) 申请/撤销活动:更新 myApplyList + actDetails(可选 actList) if ( pathname.startsWith("/api/actDetails/apply") && req.method === "POST" ) { try { const incoming = await readBodyJson(req); // 约定:至少要有 id;可选传 action: "apply" | "cancel" const id = Number(incoming.id ?? incoming.actId); const action = incoming.action || "apply"; if (!id) return send(res, { error: 1, msg: "missing id" }, 400); // 读活动列表,用来补齐标题/时间/地点等字段 const actList = readJson("actList.json", { error: 0, data: { list: [] }, }); const act = (actList.data?.list || []).find( (x) => Number(x.id) === id, ); // myApplyList.json const applyJson = readJson("myApplyList.json", { error: 0, data: { current: 1, pageSize: 10, pageCount: 1, list: [] }, }); const list = applyJson.data?.list || []; const existsIdx = list.findIndex((x) => Number(x.id) === id); if (action === "cancel") { if (existsIdx >= 0) list.splice(existsIdx, 1); } else { // apply if (existsIdx < 0) { // 申请状态:你的 mock 里 1/2 出现过。这里用 1 表示已申请 list.unshift({ ...(act || { id }), applyStatus: 1 }); } else { list[existsIdx].applyStatus = 1; } } applyJson.data = applyJson.data || {}; applyJson.data.list = list; applyJson.data.pageCount = 1; writeJson("myApplyList.json", applyJson); // actDetails.json:让详情页刷新后看到申请状态变化 const detailsJson = readJson("actDetails.json", { error: 0, data: { details: {} }, }); if ( detailsJson.data?.details && Number(detailsJson.data.details.id) === id ) { if (action === "cancel") { detailsJson.data.details.applyStatus = 0; detailsJson.data.details.canApply = true; } else { detailsJson.data.details.applyStatus = 1; detailsJson.data.details.canApply = false; } writeJson("actDetails.json", detailsJson); } // actList.json(可选):列表 canApply 同步变化 if (act) { const idx = (actList.data?.list || []).findIndex( (x) => Number(x.id) === id, ); if (idx >= 0) { actList.data.list[idx].canApply = action === "cancel"; writeJson("actList.json", actList); } } return ok(res); } catch { return send(res, { error: 1, msg: "Invalid JSON body" }, 400); } } // 3) 上传服务记录:写 serviceList + 联动积分文件 if ( pathname.startsWith("/api/act/uploadService") && req.method === "POST" ) { try { const incoming = await readBodyJson(req); // 约定:时长字段可能叫 hour / duration / value const hour = Number( incoming.hour ?? incoming.duration ?? incoming.value ?? 0, ) || 0; // serviceList.json 结构:{data:{list:[{id,pic,content,publisher,time,status}]}} const sl = readJson("serviceList.json", { error: 0, data: { current: 1, pageSize: 10, pageCount: 1, list: [] }, }); const list = sl.data?.list || []; const nextId = list.reduce((m, x) => Math.max(m, Number(x.id) || 0), 0) + 1; list.unshift({ id: nextId, pic: incoming.pic || "./imgs/actImg.jpeg", content: incoming.content || "新增服务记录", publisher: incoming.publisher || "未知来源", time: incoming.time || Date.now(), status: 1, // 1=通过?你 recordDetails.status=1,看起来是已审核/有效 // 额外字段可保留(不影响页面) hour, }); sl.data = sl.data || {}; sl.data.list = list; sl.data.pageCount = 1; writeJson("serviceList.json", sl); // 同步更新积分(按 1小时=2分) if (hour > 0) addScore(hour); return ok(res); } catch { return send(res, { error: 1, msg: "Invalid JSON body" }, 400); } } next(); }); }, }; } export default defineConfig({ base: "./", plugins: [vue(), mockPlugin(), VueDevTools()], });