fix:save_to_json

This commit is contained in:
dichgrem
2025-12-26 11:00:57 +08:00
parent d076ee1d3a
commit 61655580fd
9 changed files with 550 additions and 303 deletions

View File

@@ -32,9 +32,9 @@
"endTime": 1672099200000,
"palce": "福州市养老院",
"isYourSchool": false,
"canApply": true,
"canApply": false,
"actPic": "./imgs/actImg.jpeg"
}
]
}
}
}

View File

@@ -5,6 +5,17 @@
"pageSize": 10,
"pageCount": 1,
"list": [
{
"id": 3,
"title": "社区敬老院服务",
"startTime": 1672012800000,
"endTime": 1672099200000,
"palce": "福州市养老院",
"isYourSchool": false,
"canApply": true,
"actPic": "./imgs/actImg.jpeg",
"applyStatus": 1
},
{
"id": 1,
"title": "运动会志愿者招募",
@@ -29,4 +40,4 @@
}
]
}
}
}

View File

@@ -2,7 +2,7 @@
"error": 0,
"data": {
"provinceRank": 156,
"totalScore": 128,
"totalScore": 132,
"grandeRank": 8
}
}
}

View File

@@ -5,6 +5,15 @@
"pageSize": 10,
"pageCount": 1,
"list": [
{
"id": 4,
"pic": "./imgs/actImg.jpeg",
"content": "666",
"publisher": "未知来源",
"time": 1766717931972,
"status": 1,
"hour": 2
},
{
"id": 1,
"pic": "./imgs/actImg.jpeg",
@@ -31,4 +40,4 @@
}
]
}
}
}

View File

@@ -2,13 +2,13 @@
"error": 0,
"data": {
"avatar": "./imgs/avatar.png",
"name": "李华",
"code": 12343434,
"name": "666",
"code": 114514,
"gender": 0,
"phoneNum": "18787698789",
"phoneNum": "1919810",
"school": "福州大学至诚学院",
"profession": "软件工程",
"userType": 0,
"totalScore": 128
"profession": "计算机",
"userType": 2,
"totalScore": 132
}
}
}

View File

@@ -3,6 +3,6 @@
"data": {
"avatar": "./imgs/avatar.png",
"name": "李华",
"totalScore": 128
"totalScore": 132
}
}
}

View File

@@ -1,8 +1,8 @@
{
"error": 0,
"data": {
"times": 12,
"duration": 48,
"score": 96
"times": 13,
"duration": 50,
"score": 100
}
}
}

View File

@@ -1,60 +1,58 @@
<template>
<div class="container">
<div class="inner">
<h1>个人信息</h1>
<ul>
<li>
<p class="label"><span>*</span>姓名</p>
<span class="txt">{{ name }}</span>
</li>
<li>
<p class="label"><span>*</span>编号</p>
<span class="txt">{{ code }}</span>
</li>
<li>
<p class="label"><span>*</span>性别</p>
<input type="radio" value="0" v-model="gender" />
<input type="radio" value="1" v-model="gender" />
</li>
<li>
<p class="label"><span>*</span>手机号</p>
<input placeholder="请输入手机号" v-model.trim="phoneNum" />
</li>
<li>
<p class="label"><span>*</span>学校</p>
<input placeholder="请输入学校名称" v-model.trim="school" />
</li>
<li>
<p class="label"><span>*</span>专业</p>
<input
placeholder="请输入专业名称"
v-model.trim="profession"
/>
</li>
<li>
<p class="label"><span>*</span>人员属性</p>
<div class="userTypeList">
<span
:class="{ active: userType === 0 }"
@click="changeUserType(0)"
>群众</span
>
<span
:class="{ active: userType === 1 }"
@click="changeUserType(1)"
>团员</span
>
<span
:class="{ active: userType === 2 }"
@click="changeUserType(2)"
>党员</span
>
</div>
</li>
</ul>
<button class="subBtn" @click="submitClick">提交</button>
</div>
<div class="container">
<div class="inner">
<h1>个人信息</h1>
<ul>
<li>
<p class="label"><span>*</span>姓名</p>
<input placeholder="请输入姓名" v-model.trim="name" />
</li>
<li>
<p class="label"><span>*</span>编号</p>
<input placeholder="请输入编号" v-model.trim="codeStr" />
</li>
<li>
<p class="label"><span>*</span>性别</p>
<input type="radio" value="0" v-model="gender" />
<input type="radio" value="1" v-model="gender" />
</li>
<li>
<p class="label"><span>*</span>手机号</p>
<input placeholder="请输入手机号" v-model.trim="phoneNum" />
</li>
<li>
<p class="label"><span>*</span>学校</p>
<input placeholder="请输入学校名称" v-model.trim="school" />
</li>
<li>
<p class="label"><span>*</span>专业</p>
<input placeholder="请输入专业名称" v-model.trim="profession" />
</li>
<li>
<p class="label"><span>*</span>人员属性</p>
<div class="userTypeList">
<span :class="{ active: userType === 0 }" @click="changeUserType(0)">群众</span>
<span :class="{ active: userType === 1 }" @click="changeUserType(1)">团员</span>
<span :class="{ active: userType === 2 }" @click="changeUserType(2)">党员</span>
</div>
</li>
</ul>
<button class="subBtn" :disabled="saving" @click="submitClick">
{{ saving ? "保存中..." : "保存" }}
</button>
<button class="subBtn subBtn-ghost" :disabled="saving" @click="resetClick">
取消修改
</button>
</div>
</div>
</template>
<script>
@@ -62,181 +60,252 @@ import axios from "axios";
import { showToast, showSuccessToast, showFailToast } from "vant";
export default {
data() {
return {
name: "",
code: null,
gender: 0,
phoneNum: "",
school: "",
profession: "",
userType: 0,
};
},
methods: {
changeUserType(value) {
this.userType = value;
},
fetchUsrInfo() {
const that = this;
axios.get("/api/userInfo").then(function (response) {
const { error, data = {} } = response.data;
if (error === 0) {
that.name = data.name;
that.code = data.code;
that.gender = data.gender;
that.phoneNum = data.phoneNum;
that.school = data.school;
that.profession = data.profession;
that.userType = data.userType;
}
});
},
check() {
var phoneNum = this.phoneNum;
var school = this.school;
var profession = this.profession;
data() {
return {
name: "",
code: null,
codeStr: "",
if (phoneNum === "") {
showToast("手机号不可为空");
return;
}
if (school === "") {
showToast("学校不可为空");
return;
}
if (profession === "") {
showToast("专业不可为空");
return;
}
gender: "0",
phoneNum: "",
school: "",
profession: "",
userType: 0,
const payload = {
name: this.name,
code: this.code,
gender: this.gender,
phoneNum,
school,
profession,
userType: this.userType,
};
return payload;
},
submitClick() {
var payload = this.check();
if (!payload) {
return;
}
saving: false,
original: null,
};
},
methods: {
changeUserType(value) {
this.userType = value;
},
axios
.post("/api/userInfo/submit", payload)
.then(function (response) {
const { error, msg } = response.data;
if (error === 0) {
showSuccessToast("操作成功");
} else {
showFailToast(msg || "网络错误,请稍后重试");
}
});
},
snapshot() {
return {
name: this.name,
code: this.code,
codeStr: this.codeStr,
gender: this.gender,
phoneNum: this.phoneNum,
school: this.school,
profession: this.profession,
userType: this.userType,
};
},
mounted() {
this.fetchUsrInfo();
applyData(data = {}) {
this.name = data.name ?? "";
this.code = data.code ?? null;
this.codeStr = data.code != null ? String(data.code) : "";
this.gender = data.gender != null ? String(data.gender) : "0";
this.phoneNum = data.phoneNum ?? "";
this.school = data.school ?? "";
this.profession = data.profession ?? "";
this.userType = data.userType ?? 0;
},
fetchUsrInfo() {
axios
.get("/api/userInfo")
.then((response) => {
const { error, data = {}, msg } = response.data || {};
if (error === 0) {
this.applyData(data);
this.original = this.snapshot();
} else {
showFailToast(msg || "获取用户信息失败");
}
})
.catch(() => showFailToast("网络错误,请稍后重试"));
},
check() {
const name = this.name.trim();
const codeStr = this.codeStr.trim();
const phoneNum = this.phoneNum.trim();
const school = this.school.trim();
const profession = this.profession.trim();
if (!name) {
showToast("姓名不可为空");
return;
}
if (!codeStr) {
showToast("编号不可为空");
return;
}
if (!phoneNum) {
showToast("手机号不可为空");
return;
}
if (!school) {
showToast("学校不可为空");
return;
}
if (!profession) {
showToast("专业不可为空");
return;
}
const codeNum = Number(codeStr);
const code = Number.isFinite(codeNum) ? codeNum : codeStr;
const payload = {
name,
code,
gender: Number(this.gender),
phoneNum,
school,
profession,
userType: this.userType,
};
return payload;
},
submitClick() {
const payload = this.check();
if (!payload) return;
if (this.saving) return;
this.saving = true;
axios
.post("/api/userInfo/submit", payload)
.then((response) => {
const { error, msg } = response.data || {};
if (error === 0) {
showSuccessToast("保存成功");
this.original = this.snapshot();
} else {
showFailToast(msg || "网络错误,请稍后重试");
}
})
.catch(() => showFailToast("网络错误,请稍后重试"))
.finally(() => {
this.saving = false;
});
},
resetClick() {
if (!this.original) return;
this.name = this.original.name;
this.code = this.original.code;
this.codeStr = this.original.codeStr;
this.gender = this.original.gender;
this.phoneNum = this.original.phoneNum;
this.school = this.original.school;
this.profession = this.original.profession;
this.userType = this.original.userType;
showToast("已还原");
},
},
mounted() {
this.fetchUsrInfo();
},
};
</script>
<style scoped>
.inner {
padding: 20px;
background: white;
color: #333;
padding: 20px;
background: white;
color: #333;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #333;
text-align: center;
margin-bottom: 20px;
color: #333;
}
ul {
list-style: none;
padding: 0;
margin: 0;
list-style: none;
padding: 0;
margin: 0;
}
li {
padding: 15px 0;
border-bottom: 1px solid #eee;
color: #333;
padding: 15px 0;
border-bottom: 1px solid #eee;
color: #333;
}
.label {
font-weight: bold;
margin-bottom: 10px;
color: #333;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.label span {
color: red;
}
.txt {
color: #666;
color: red;
}
li input:not([type="radio"]) {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 5px;
box-sizing: border-box;
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 5px;
box-sizing: border-box;
color: #333;
background-color: #fff;
color: #333;
background-color: #fff;
}
li input:not([type="radio"])::placeholder {
color: #999;
color: #999;
}
input[type="radio"] {
margin-right: 6px;
margin-left: 10px;
margin-right: 6px;
margin-left: 10px;
}
input[type="radio"]:first-of-type {
margin-left: 0;
margin-left: 0;
}
.userTypeList {
display: flex;
gap: 10px;
margin-top: 10px;
display: flex;
gap: 10px;
margin-top: 10px;
}
.userTypeList span {
padding: 8px 20px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
color: #333;
padding: 8px 20px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
color: #333;
}
.userTypeList span.active {
background: #ff5d23;
color: white;
border-color: #ff5d23;
background: #ff5d23;
color: white;
border-color: #ff5d23;
}
.subBtn {
width: 100%;
padding: 15px;
background: #ff5d23;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
margin-top: 20px;
cursor: pointer;
width: 100%;
padding: 15px;
background: #ff5d23;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
margin-top: 20px;
cursor: pointer;
}
.subBtn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.subBtn-ghost {
background: transparent;
color: #333;
border: 1px solid #ddd;
}
</style>

View File

@@ -5,137 +5,299 @@ 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((req, res, next) => {
server.middlewares.use(async (req, res, next) => {
const pathname = getPathname(req);
// ========== GET仍然走你原来的静态文件 ==========
const sendJSON = (file) => {
const filePath = path.resolve(process.cwd(), `mock/${file}`);
if (!fs.existsSync(filePath)) {
res.statusCode = 404;
res.setHeader("Content-Type", "application/json");
return res.end(
JSON.stringify({
error: `Mock file not found: ${file}`,
}),
);
const p = filePath(file);
if (!fs.existsSync(p)) {
return send(res, { error: `Mock file not found: ${file}` }, 404);
}
const json = fs.readFileSync(filePath, "utf-8");
res.setHeader("Content-Type", "application/json");
res.end(json);
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.end(fs.readFileSync(p, "utf-8"));
};
// 用户信息
if (req.url === "/api/userInfo" && req.method === "GET") {
if (pathname === "/api/userInfo" && req.method === "GET")
return sendJSON("userInfo.json");
}
// 提交用户信息
if (req.url === "/api/userInfo/submit" && req.method === "POST") {
return sendJSON("success.json");
}
// 活动列表
if (req.url.startsWith("/api/actList") && req.method === "GET") {
if (pathname.startsWith("/api/actList") && req.method === "GET")
return sendJSON("actList.json");
}
// 积分概览
if (
req.url.startsWith("/api/report/myOverview") &&
pathname.startsWith("/api/report/myOverview") &&
req.method === "GET"
) {
)
return sendJSON("scoreOverview.json");
}
// 排名列表
if (
req.url.startsWith("/api/report/rankList") &&
req.method === "GET"
) {
if (pathname.startsWith("/api/report/rankList") && req.method === "GET")
return sendJSON("rank.json");
}
// 活动详情
if (
req.url.startsWith("/api/actDetails/details") &&
pathname.startsWith("/api/actDetails/details") &&
req.method === "GET"
) {
)
return sendJSON("actDetails.json");
}
// 申请/撤销活动
if (
req.url.startsWith("/api/actDetails/apply") &&
req.method === "POST"
) {
return sendJSON("success.json");
}
// 我的报名列表
if (req.url.startsWith("/api/myApplyList") && req.method === "GET") {
if (pathname.startsWith("/api/myApplyList") && req.method === "GET")
return sendJSON("myApplyList.json");
}
// 活动来源列表
if (
req.url.startsWith("/api/act/publisherList") &&
pathname.startsWith("/api/act/publisherList") &&
req.method === "GET"
) {
)
return sendJSON("publishers.json");
}
// 服务时长列表
if (
req.url.startsWith("/api/act/durationsList") &&
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")
return sendJSON("serviceList.json");
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 (
req.url.startsWith("/api/act/uploadService") &&
pathname.startsWith("/api/actDetails/apply") &&
req.method === "POST"
) {
return sendJSON("success.json");
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 (
req.url.startsWith("/api/service/userScore") &&
req.method === "GET"
pathname.startsWith("/api/act/uploadService") &&
req.method === "POST"
) {
return sendJSON("userScore.json");
}
try {
const incoming = await readBodyJson(req);
// 约定:时长字段可能叫 hour / duration / value
const hour =
Number(
incoming.hour ?? incoming.duration ?? incoming.value ?? 0,
) || 0;
// 年份列表
if (
req.url.startsWith("/api/service/yearList") &&
req.method === "GET"
) {
return sendJSON("yearList.json");
}
// 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 || [];
// 年度积分
if (
req.url.startsWith("/api/service/yearScore") &&
req.method === "GET"
) {
return sendJSON("yearScore.json");
}
const nextId =
list.reduce((m, x) => Math.max(m, Number(x.id) || 0), 0) + 1;
// 服务记录列表
if (req.url.startsWith("/api/service/list") && req.method === "GET") {
return sendJSON("serviceList.json");
}
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,
});
// 服务详情
if (
req.url.startsWith("/api/service/details") &&
req.method === "GET"
) {
return sendJSON("recordDetails.json");
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();
@@ -145,10 +307,6 @@ function mockPlugin() {
}
export default defineConfig({
base: './',
plugins: [
vue(),
mockPlugin(),
VueDevTools(),
],
})
base: "./",
plugins: [vue(), mockPlugin(), VueDevTools()],
});