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,7 +32,7 @@
"endTime": 1672099200000, "endTime": 1672099200000,
"palce": "福州市养老院", "palce": "福州市养老院",
"isYourSchool": false, "isYourSchool": false,
"canApply": true, "canApply": false,
"actPic": "./imgs/actImg.jpeg" "actPic": "./imgs/actImg.jpeg"
} }
] ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,54 +5,52 @@
<ul> <ul>
<li> <li>
<p class="label"><span>*</span>姓名</p> <p class="label"><span>*</span>姓名</p>
<span class="txt">{{ name }}</span> <input placeholder="请输入姓名" v-model.trim="name" />
</li> </li>
<li> <li>
<p class="label"><span>*</span>编号</p> <p class="label"><span>*</span>编号</p>
<span class="txt">{{ code }}</span> <input placeholder="请输入编号" v-model.trim="codeStr" />
</li> </li>
<li> <li>
<p class="label"><span>*</span>性别</p> <p class="label"><span>*</span>性别</p>
<input type="radio" value="0" v-model="gender" /> <input type="radio" value="0" v-model="gender" />
<input type="radio" value="1" v-model="gender" /> <input type="radio" value="1" v-model="gender" />
</li> </li>
<li> <li>
<p class="label"><span>*</span>手机号</p> <p class="label"><span>*</span>手机号</p>
<input placeholder="请输入手机号" v-model.trim="phoneNum" /> <input placeholder="请输入手机号" v-model.trim="phoneNum" />
</li> </li>
<li> <li>
<p class="label"><span>*</span>学校</p> <p class="label"><span>*</span>学校</p>
<input placeholder="请输入学校名称" v-model.trim="school" /> <input placeholder="请输入学校名称" v-model.trim="school" />
</li> </li>
<li> <li>
<p class="label"><span>*</span>专业</p> <p class="label"><span>*</span>专业</p>
<input <input placeholder="请输入专业名称" v-model.trim="profession" />
placeholder="请输入专业名称"
v-model.trim="profession"
/>
</li> </li>
<li> <li>
<p class="label"><span>*</span>人员属性</p> <p class="label"><span>*</span>人员属性</p>
<div class="userTypeList"> <div class="userTypeList">
<span <span :class="{ active: userType === 0 }" @click="changeUserType(0)">群众</span>
:class="{ active: userType === 0 }" <span :class="{ active: userType === 1 }" @click="changeUserType(1)">团员</span>
@click="changeUserType(0)" <span :class="{ active: userType === 2 }" @click="changeUserType(2)">党员</span>
>群众</span
>
<span
:class="{ active: userType === 1 }"
@click="changeUserType(1)"
>团员</span
>
<span
:class="{ active: userType === 2 }"
@click="changeUserType(2)"
>党员</span
>
</div> </div>
</li> </li>
</ul> </ul>
<button class="subBtn" @click="submitClick">提交</button>
<button class="subBtn" :disabled="saving" @click="submitClick">
{{ saving ? "保存中..." : "保存" }}
</button>
<button class="subBtn subBtn-ghost" :disabled="saving" @click="resetClick">
取消修改
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -66,54 +64,99 @@ export default {
return { return {
name: "", name: "",
code: null, code: null,
gender: 0, codeStr: "",
gender: "0",
phoneNum: "", phoneNum: "",
school: "", school: "",
profession: "", profession: "",
userType: 0, userType: 0,
saving: false,
original: null,
}; };
}, },
methods: { methods: {
changeUserType(value) { changeUserType(value) {
this.userType = 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;
if (phoneNum === "") { 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,
};
},
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("手机号不可为空"); showToast("手机号不可为空");
return; return;
} }
if (school === "") { if (!school) {
showToast("学校不可为空"); showToast("学校不可为空");
return; return;
} }
if (profession === "") { if (!profession) {
showToast("专业不可为空"); showToast("专业不可为空");
return; return;
} }
const codeNum = Number(codeStr);
const code = Number.isFinite(codeNum) ? codeNum : codeStr;
const payload = { const payload = {
name: this.name, name,
code: this.code, code,
gender: this.gender, gender: Number(this.gender),
phoneNum, phoneNum,
school, school,
profession, profession,
@@ -121,23 +164,42 @@ export default {
}; };
return payload; return payload;
}, },
submitClick() {
var payload = this.check();
if (!payload) {
return;
}
submitClick() {
const payload = this.check();
if (!payload) return;
if (this.saving) return;
this.saving = true;
axios axios
.post("/api/userInfo/submit", payload) .post("/api/userInfo/submit", payload)
.then(function (response) { .then((response) => {
const { error, msg } = response.data; const { error, msg } = response.data || {};
if (error === 0) { if (error === 0) {
showSuccessToast("操作成功"); showSuccessToast("保存成功");
this.original = this.snapshot();
} else { } else {
showFailToast(msg || "网络错误,请稍后重试"); 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() { mounted() {
this.fetchUsrInfo(); this.fetchUsrInfo();
@@ -180,10 +242,6 @@ li {
color: red; color: red;
} }
.txt {
color: #666;
}
li input:not([type="radio"]) { li input:not([type="radio"]) {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
@@ -239,4 +297,15 @@ input[type="radio"]:first-of-type {
margin-top: 20px; margin-top: 20px;
cursor: pointer; cursor: pointer;
} }
.subBtn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.subBtn-ghost {
background: transparent;
color: #333;
border: 1px solid #ddd;
}
</style> </style>

View File

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