feat: migrate app routes and business modules

This commit is contained in:
2026-08-12 18:22:59 +08:00
parent 555aa00043
commit cc706378c2
247 changed files with 28623 additions and 14988 deletions
+52
View File
@@ -0,0 +1,52 @@
const markdownHeading = /^(#{1,6})\s+(.+)$/;
const orderedListItem = /^(\d+)[.、]\s*(.+)$/;
const unorderedListItem = /^[-*]\s+(.+)$/;
export const formatComplianceContent = (value, documentTitle = "") => {
if (typeof value !== "string" || !value.trim()) return [];
const blocks = value
.replace(/\r\n?/g, "\n")
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
const heading = line.match(markdownHeading);
if (heading) {
return {
type: heading[1].length === 1 ? "title" : "heading",
text: heading[2].trim(),
};
}
const ordered = line.match(orderedListItem);
if (ordered) {
return {
type: "list-item",
marker: `${ordered[1]}.`,
text: ordered[2].trim(),
};
}
const unordered = line.match(unorderedListItem);
if (unordered) {
return { type: "list-item", marker: "•", text: unordered[1].trim() };
}
return { type: "paragraph", text: line };
});
const duplicateTitle =
blocks[0]?.type === "title" &&
blocks[0].text === String(documentTitle || "").trim();
if (!duplicateTitle) return blocks;
let contentBlocks = blocks.slice(1);
while (
contentBlocks[0]?.type === "paragraph" &&
/^(?:版本号|版本|发布日期|生效日期)\s*[::]/.test(contentBlocks[0].text)
) {
contentBlocks = contentBlocks.slice(1);
}
return contentBlocks;
};
+14
View File
@@ -0,0 +1,14 @@
export const parseMoneyToCents = (amount) => {
const amountParts = String(amount || "").match(/^(\d+)(?:\.(\d{1,2}))?$/);
if (!amountParts) return null;
return (
BigInt(amountParts[1]) * 100n +
BigInt((amountParts[2] || "").padEnd(2, "0"))
);
};
export const formatSignedMoney = (amount) => {
const normalizedAmount = String(amount);
const sign = normalizedAmount.startsWith("-") ? "-" : "+";
return `${sign}¥${normalizedAmount.replace(/^-/, "")}`;
};
+144
View File
@@ -0,0 +1,144 @@
import { isWriteOutcomeUnknown } from '@/utils/request-outcome.js'
const messages = {
submitted:
"反馈已提交。以下内容为本次提交记录;修改任一项后可提交新反馈。",
submittedWithEdits: "上一份反馈已提交,当前修改尚未提交。",
uncertain:
"暂时无法确认是否提交成功,请不要重复提交相同内容。你可以修改内容后再提交一份反馈。",
uncertainWithEdits: "上一份反馈暂未确认,当前修改尚未提交。",
};
const normalizeFeedback = (form) => ({
feedbackType: String(form?.feedbackType || "").trim(),
feedbackContent: String(form?.feedbackContent || "").trim(),
contactInfo: String(form?.contactInfo || "").trim(),
});
// 字段顺序固定的快照既用于脏数据判断,也用于阻止非幂等 POST 重复提交。
// 不能改成对象引用比较,否则 Vue 表单原地修改时无法识别同一份内容。
const snapshotOf = (form) => JSON.stringify(normalizeFeedback(form));
const errorMessage = (error) => {
if (error?.code === "WRITE_UNAVAILABLE") {
return "当前为本地预览,反馈没有发出。";
}
if (error?.httpStatus === 401) {
return "登录状态已失效,请重新登录后再提交。";
}
return "提交未完成,请稍后再试。";
};
const submitLabelFor = (phase) => {
if (phase === "submitting") return "正在提交";
if (phase === "success") return "反馈已提交";
if (phase === "uncertain") return "暂未确认";
if (phase === "error") return "重新提交";
return "提交反馈";
};
export const createFeedbackSubmissionSession = (initialForm) => {
let baselineSnapshot = snapshotOf(initialForm);
let lastSubmittedSnapshot = "";
let lastUncertainSnapshot = "";
let phase = "ready";
let message = "";
let tone = "";
const view = (currentForm) => {
const currentSnapshot = snapshotOf(currentForm);
const isRepeatedSubmission =
currentSnapshot === lastSubmittedSnapshot ||
currentSnapshot === lastUncertainSnapshot;
return {
phase,
message,
tone,
isDirty: currentSnapshot !== baselineSnapshot,
isSubmitDisabled: phase === "submitting" || isRepeatedSubmission,
submitLabel: submitLabelFor(phase),
};
};
const begin = (currentForm) => {
const currentSnapshot = snapshotOf(currentForm);
if (
phase === "submitting" ||
currentSnapshot === lastSubmittedSnapshot ||
currentSnapshot === lastUncertainSnapshot
) {
return null;
}
phase = "submitting";
message = "";
tone = "";
return {
snapshot: currentSnapshot,
payload: normalizeFeedback(currentForm),
};
};
const succeed = (submission, currentForm) => {
baselineSnapshot = submission.snapshot;
lastSubmittedSnapshot = submission.snapshot;
tone = "success";
if (snapshotOf(currentForm) === submission.snapshot) {
phase = "success";
message = messages.submitted;
} else {
phase = "ready";
message = messages.submittedWithEdits;
}
};
const fail = (submission, error, currentForm) => {
if (!isWriteOutcomeUnknown(error)) {
phase = "error";
message = errorMessage(error);
tone = "error";
return;
}
// 超时或异常 2xx 可能已经被后端写入。记录原快照并禁用原样重试,
// 避免用户在网络恢复后生成两条内容完全相同的反馈。
lastUncertainSnapshot = submission.snapshot;
tone = "uncertain";
if (snapshotOf(currentForm) === submission.snapshot) {
phase = "uncertain";
message = messages.uncertain;
} else {
phase = "ready";
message = messages.uncertainWithEdits;
}
};
const reconcile = (currentForm) => {
if (phase === "submitting") return;
const currentSnapshot = snapshotOf(currentForm);
if (lastSubmittedSnapshot && currentSnapshot === lastSubmittedSnapshot) {
phase = "success";
message = messages.submitted;
tone = "success";
return;
}
if (lastUncertainSnapshot && currentSnapshot === lastUncertainSnapshot) {
phase = "uncertain";
message = messages.uncertain;
tone = "uncertain";
return;
}
phase = "ready";
if (lastUncertainSnapshot) {
message = messages.uncertainWithEdits;
tone = "uncertain";
} else if (lastSubmittedSnapshot) {
message = messages.submittedWithEdits;
tone = "success";
} else {
message = "";
tone = "";
}
};
return { begin, succeed, fail, reconcile, view };
};