feat: migrate app routes and business modules
This commit is contained in:
@@ -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;
|
||||
};
|
||||
@@ -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(/^-/, "")}`;
|
||||
};
|
||||
@@ -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 };
|
||||
};
|
||||
Reference in New Issue
Block a user