feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// 当前两个产品选项同时决定 OpenAPI GenealogyCreate/UpdateBody 的
|
||||
// visibility 与 joinMode,因此它们是“访问预设”,不是单一可见范围。
|
||||
export const GENEALOGY_ACCESS_PRESET = Object.freeze({
|
||||
MEMBER_ONLY: "MEMBER_ONLY",
|
||||
PUBLIC_APPLY: "PUBLIC_APPLY",
|
||||
});
|
||||
|
||||
export const GENEALOGY_VISIBILITY = Object.freeze({
|
||||
PRIVATE: "0",
|
||||
PUBLIC: "1",
|
||||
MEMBER_ONLY: "2",
|
||||
});
|
||||
|
||||
export const GENEALOGY_JOIN_MODE = Object.freeze({
|
||||
CLOSED: "0",
|
||||
REVIEW: "1",
|
||||
INVITATION: "2",
|
||||
});
|
||||
|
||||
export const GENEALOGY_ACCESS_PRESET_OPTIONS = Object.freeze([
|
||||
Object.freeze({
|
||||
value: GENEALOGY_ACCESS_PRESET.MEMBER_ONLY,
|
||||
label: "仅成员可见",
|
||||
}),
|
||||
Object.freeze({
|
||||
value: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
|
||||
label: "公开可申请",
|
||||
}),
|
||||
]);
|
||||
|
||||
export const isGenealogyAccessPreset = (preset) =>
|
||||
Object.values(GENEALOGY_ACCESS_PRESET).includes(preset);
|
||||
|
||||
export const isGenealogyVisibility = (value) =>
|
||||
Object.values(GENEALOGY_VISIBILITY).includes(value);
|
||||
|
||||
export const isGenealogyJoinMode = (value) =>
|
||||
Object.values(GENEALOGY_JOIN_MODE).includes(value);
|
||||
|
||||
// OpenAPI 为两个字段分别声明了 0/1/2,但当前产品只提供以下两个完整预设。
|
||||
// 其他合法组合不能被悄悄改写成相近选项,页面应明确显示“访问规则待确认”。
|
||||
const apiAccessByPreset = Object.freeze({
|
||||
[GENEALOGY_ACCESS_PRESET.MEMBER_ONLY]: Object.freeze({
|
||||
visibility: GENEALOGY_VISIBILITY.MEMBER_ONLY,
|
||||
joinMode: GENEALOGY_JOIN_MODE.CLOSED,
|
||||
}),
|
||||
[GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY]: Object.freeze({
|
||||
visibility: GENEALOGY_VISIBILITY.PUBLIC,
|
||||
joinMode: GENEALOGY_JOIN_MODE.REVIEW,
|
||||
}),
|
||||
});
|
||||
|
||||
export const getGenealogyAccessPresetLabel = (preset) =>
|
||||
GENEALOGY_ACCESS_PRESET_OPTIONS.find((item) => item.value === preset)
|
||||
?.label || "访问规则待确认";
|
||||
|
||||
export const toApiGenealogyAccess = (preset) => {
|
||||
const access = apiAccessByPreset[preset];
|
||||
return access ? { ...access } : null;
|
||||
};
|
||||
|
||||
export const fromApiGenealogyAccess = ({ visibility, joinMode } = {}) => {
|
||||
const match = Object.entries(apiAccessByPreset).find(
|
||||
([, access]) =>
|
||||
access.visibility === visibility && access.joinMode === joinMode,
|
||||
);
|
||||
return match?.[0] || null;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
const CURRENT_GENEALOGY_KEY = 'jiapu_current_genealogy_id'
|
||||
const CURRENT_GENEALOGY_INVALIDATED_KEY = 'jiapu_current_genealogy_invalidated'
|
||||
|
||||
const normalizeGenealogyId = (genealogyId) => {
|
||||
if (typeof genealogyId !== 'string') {
|
||||
throw new TypeError('当前家谱 ID 必须是词法字符串')
|
||||
}
|
||||
if (!genealogyId || genealogyId.trim() !== genealogyId || /[\u0000-\u001f\u007f]/.test(genealogyId)) {
|
||||
throw new TypeError('当前家谱 ID 必须是无边界空白的非空字符串')
|
||||
}
|
||||
return genealogyId
|
||||
}
|
||||
|
||||
const markCurrentGenealogyInvalidated = () => {
|
||||
uni.removeStorageSync(CURRENT_GENEALOGY_KEY)
|
||||
uni.setStorageSync(CURRENT_GENEALOGY_INVALIDATED_KEY, '1')
|
||||
}
|
||||
|
||||
const readCurrentGenealogyInvalidated = () => {
|
||||
const stored = uni.getStorageSync(CURRENT_GENEALOGY_INVALIDATED_KEY)
|
||||
if (stored === '' || stored === null || stored === undefined) return false
|
||||
if (stored !== '1') uni.setStorageSync(CURRENT_GENEALOGY_INVALIDATED_KEY, '1')
|
||||
return true
|
||||
}
|
||||
|
||||
const readCurrentGenealogyId = () => {
|
||||
const stored = uni.getStorageSync(CURRENT_GENEALOGY_KEY)
|
||||
if (stored === '' || stored === null || stored === undefined) return ''
|
||||
try {
|
||||
return normalizeGenealogyId(stored)
|
||||
} catch {
|
||||
// 旧版本或损坏的存储值不应继续流入路由和权限判断。发现异常时只清理
|
||||
// 这一唯一键;下一次取得可用家谱列表后再按确定顺序建立新上下文。
|
||||
markCurrentGenealogyInvalidated()
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const genealogyContext = {
|
||||
getCurrentGenealogyId: readCurrentGenealogyId,
|
||||
setCurrentGenealogyId: (genealogyId) => {
|
||||
const normalizedId = normalizeGenealogyId(genealogyId)
|
||||
uni.setStorageSync(CURRENT_GENEALOGY_KEY, normalizedId)
|
||||
uni.removeStorageSync(CURRENT_GENEALOGY_INVALIDATED_KEY)
|
||||
return normalizedId
|
||||
},
|
||||
invalidateCurrentGenealogyId: () => {
|
||||
markCurrentGenealogyInvalidated()
|
||||
return ''
|
||||
},
|
||||
isCurrentGenealogyInvalidated: readCurrentGenealogyInvalidated,
|
||||
clearCurrentGenealogyId: () => {
|
||||
uni.removeStorageSync(CURRENT_GENEALOGY_KEY)
|
||||
uni.removeStorageSync(CURRENT_GENEALOGY_INVALIDATED_KEY)
|
||||
},
|
||||
reconcileCurrentGenealogyId: (availableIds, preferredId = '') => {
|
||||
if (!Array.isArray(availableIds)) {
|
||||
throw new TypeError('可用家谱 ID 必须是数组')
|
||||
}
|
||||
const normalizedIds = availableIds.map(normalizeGenealogyId)
|
||||
if (new Set(normalizedIds).size !== normalizedIds.length) {
|
||||
throw new TypeError('可用家谱列表包含重复 ID')
|
||||
}
|
||||
const normalizedPreferredId = preferredId === ''
|
||||
? ''
|
||||
: normalizeGenealogyId(preferredId)
|
||||
const storedId = readCurrentGenealogyId()
|
||||
if (normalizedPreferredId) {
|
||||
if (!normalizedIds.includes(normalizedPreferredId)) {
|
||||
markCurrentGenealogyInvalidated()
|
||||
return ''
|
||||
}
|
||||
return genealogyContext.setCurrentGenealogyId(normalizedPreferredId)
|
||||
}
|
||||
if (storedId) {
|
||||
if (!normalizedIds.includes(storedId)) {
|
||||
markCurrentGenealogyInvalidated()
|
||||
return ''
|
||||
}
|
||||
return genealogyContext.setCurrentGenealogyId(storedId)
|
||||
}
|
||||
if (readCurrentGenealogyInvalidated()) return ''
|
||||
if (!normalizedIds.length) {
|
||||
uni.removeStorageSync(CURRENT_GENEALOGY_KEY)
|
||||
return ''
|
||||
}
|
||||
return genealogyContext.setCurrentGenealogyId(normalizedIds[0])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { GENERATION_POEM_STATUS } from '@/services/api/generation-poem-contract.js'
|
||||
|
||||
export { GENERATION_POEM_STATUS }
|
||||
|
||||
// 三个限制逐一对应 GenerationPoemBatchBody:输入总长 26000、单代文字
|
||||
// 最多 50 字符、单批最多 500 个世代,不能再合并成一个含义模糊的“长度”。
|
||||
export const MAX_GENERATION_COUNT = 500
|
||||
export const MAX_GENERATION_TEXT_LENGTH = 50
|
||||
export const MAX_GENERATION_POEM_INPUT_LENGTH = 26000
|
||||
|
||||
const separatorPattern = /[\s,,;;、/|]/u
|
||||
const separatorRunPattern = /[\s,,;;、/|]+/u
|
||||
const forbiddenControlPattern = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u
|
||||
const codePointLength = (value) => Array.from(value).length
|
||||
|
||||
const normalizeGenerationText = (value) => {
|
||||
const text = String(value ?? '').trim()
|
||||
if (
|
||||
!text ||
|
||||
codePointLength(text) > MAX_GENERATION_TEXT_LENGTH ||
|
||||
forbiddenControlPattern.test(text)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// 按 OpenAPI 的批量输入规则解析:没有分隔符时每个 Unicode code point 对应
|
||||
// 一代;存在空格、逗号、分号、顿号、斜杠或竖线时,每个分段可包含多字。
|
||||
export const validateGenerationPoemText = (value) => {
|
||||
const input = String(value ?? '')
|
||||
if (!input.trim()) {
|
||||
return Object.freeze({ valid: false, generations: [], message: '请录入字辈内容' })
|
||||
}
|
||||
if (codePointLength(input) > MAX_GENERATION_POEM_INPUT_LENGTH) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations: [],
|
||||
message: `字辈输入最多 ${MAX_GENERATION_POEM_INPUT_LENGTH} 个字符`,
|
||||
})
|
||||
}
|
||||
if (forbiddenControlPattern.test(input)) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations: [],
|
||||
message: '字辈内容包含不支持的控制字符',
|
||||
})
|
||||
}
|
||||
|
||||
const generations = separatorPattern.test(input)
|
||||
? input.split(separatorRunPattern).map((item) => item.trim()).filter(Boolean)
|
||||
: Array.from(input)
|
||||
if (!generations.length) {
|
||||
return Object.freeze({ valid: false, generations, message: '请录入字辈内容' })
|
||||
}
|
||||
if (generations.length > MAX_GENERATION_COUNT) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations,
|
||||
message: `一次最多录入 ${MAX_GENERATION_COUNT} 个世代`,
|
||||
})
|
||||
}
|
||||
if (generations.some((item) => normalizeGenerationText(item) === null)) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations,
|
||||
message: `每代字辈文字不能为空且最多 ${MAX_GENERATION_TEXT_LENGTH} 个字符`,
|
||||
})
|
||||
}
|
||||
return Object.freeze({ valid: true, generations, message: '' })
|
||||
}
|
||||
Reference in New Issue
Block a user