feat: 完成前端业务闭环与后端联调

This commit is contained in:
2026-08-24 23:37:56 +08:00
parent c59e36f933
commit 9ad572907b
175 changed files with 10711 additions and 1740 deletions
+18 -43
View File
@@ -8,6 +8,7 @@ import {
} from './response-normalizers.js'
import {
assertPlainPayload,
normalizeOptionalCurrencyNumber,
normalizeOptionalNormalDisableStatus,
normalizeOptionalSafeInteger,
normalizeOptionalText,
@@ -19,6 +20,7 @@ import {
normalizeResourceCapabilities
} from './business-file-contract.js'
import { normalizeGenealogyPathId } from './genealogy-contract.js'
import { normalizeBusinessOptionProjection } from './business-dictionary-contract.js'
const normalizeAppCeremonyCoordinate = (value, field) => {
if (value === undefined || value === null || value === '') return null
@@ -29,18 +31,6 @@ const normalizeAppCeremonyCoordinate = (value, field) => {
return coordinate
}
const CEREMONY_TYPE_LABELS = Object.freeze({
ancestor: '祭祖',
memorial: '纪念',
funeral: '丧礼',
wedding: '婚礼',
birth: '出生宴',
birthday: '生日宴',
banquet: '宴席',
other: '其他'
})
const ceremonyTypes = new Set(Object.keys(CEREMONY_TYPE_LABELS))
export const CEREMONY_INVITATION_STATUS = Object.freeze({
PENDING: 'PENDING',
ACCEPTED: 'ACCEPTED',
@@ -79,12 +69,22 @@ export const normalizeAppCeremony = (value, expectedGenealogyId, expectedCeremon
if ((longitude === null) !== (latitude === null)) {
throw createRequestError('礼仪活动响应的经纬度必须同时存在或同时为空', 'CEREMONY_RESPONSE_INVALID')
}
const ceremonyType = normalizeBusinessOptionProjection(
value.ceremonyType,
value.ceremonyTypeLabel,
value.ceremonyTypeOptionState,
'礼仪活动类型',
'CEREMONY_RESPONSE_INVALID'
)
return {
id,
genealogyId,
title: normalizeResponseText(value.ceremonyTitle, 'ceremonyTitle') || '未命名活动',
type: normalizeResponseText(value.ceremonyType, 'ceremonyType'),
type: ceremonyType.value,
typeLabel: ceremonyType.label,
typeOptionState: ceremonyType.state,
time: normalizeResponseText(value.ceremonyTime, 'ceremonyTime'),
createTime: normalizeResponseText(value.createTime, 'createTime'),
location,
locationAddress,
longitude,
@@ -98,21 +98,13 @@ export const normalizeAppCeremony = (value, expectedGenealogyId, expectedCeremon
}
}
export const normalizeAppCeremonyWithTypeLabel = (ceremony) => {
const typeLabel = CEREMONY_TYPE_LABELS[ceremony.type]
if (!ceremony.type || !typeLabel) {
throw createRequestError('礼仪活动响应包含未公开的活动类型', 'CEREMONY_RESPONSE_INVALID')
}
return { ...ceremony, typeLabel }
}
export const normalizeAppCeremonies = (value, expectedGenealogyId) => {
if (!Array.isArray(value)) throw createRequestError('礼仪活动响应不是列表', 'CEREMONY_RESPONSE_INVALID')
const ceremonies = value.map((item) => normalizeAppCeremony(item, expectedGenealogyId))
if (new Set(ceremonies.map((item) => item.id)).size !== ceremonies.length) {
throw createRequestError('礼仪活动响应包含重复标识', 'CEREMONY_RESPONSE_INVALID')
}
return ceremonies.map((item) => normalizeAppCeremonyWithTypeLabel(item))
return ceremonies
}
export const normalizeAppCeremonyGift = (value, expectedGenealogyId, expectedCeremonyId) => {
@@ -147,29 +139,12 @@ export const normalizeAppCeremonyGifts = (value, expectedGenealogyId, expectedCe
return gifts
}
export const projectPreviewCeremonies = (value, expectedGenealogyId) =>
value.map((item) => ({
id: String(item.ceremonyId),
genealogyId: expectedGenealogyId,
title: String(item.ceremonyTitle || '未命名活动'),
type: String(item.ceremonyType || ''),
typeLabel: CEREMONY_TYPE_LABELS[String(item.ceremonyType || '')] || '',
time: String(item.ceremonyTime || ''),
location: String(item.location || ''),
description: String(item.ceremonyDesc || ''),
coverFile: null,
giftCount: Number.isSafeInteger(item.giftCount) ? item.giftCount : 0,
canEdit: false,
canDelete: false
}))
export const normalizeCeremonyPayload = (payload) => {
const allowedFields = new Set(['ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime', 'location', 'locationAddress', 'longitude', 'latitude', 'coverOssId', 'sortOrder', 'status'])
assertPlainPayload(payload, allowedFields, '礼仪活动请求')
const ceremonyType = normalizeOptionalText(payload.ceremonyType, 'ceremonyType')
const ceremonyTitle = normalizeOptionalText(payload.ceremonyTitle, 'ceremonyTitle')
if (!ceremonyType || !ceremonyTitle) throw new TypeError('请填写活动类型和活动标题')
if (!ceremonyTypes.has(ceremonyType)) throw new TypeError('请选择有效的活动类型')
const normalizedPayload = { ceremonyType, ceremonyTitle }
for (const field of ['ceremonyDesc', 'ceremonyTime', 'location', 'locationAddress']) {
const normalizedText = normalizeOptionalText(payload[field], field)
@@ -189,8 +164,9 @@ export const normalizeCeremonyPayload = (payload) => {
normalizedPayload.longitude = longitude
normalizedPayload.latitude = latitude
}
if (payload.coverOssId !== undefined && payload.coverOssId !== null && payload.coverOssId !== '') {
normalizedPayload.coverOssId = normalizeOssIdString(payload.coverOssId, 'coverOssId')
if (Object.prototype.hasOwnProperty.call(payload, 'coverOssId')) {
if (payload.coverOssId === null) normalizedPayload.coverOssId = null
else if (payload.coverOssId !== '') normalizedPayload.coverOssId = normalizeOssIdString(payload.coverOssId, 'coverOssId')
}
const sortOrder = normalizeOptionalSafeInteger(payload.sortOrder, 'sortOrder')
if (sortOrder !== undefined) {
@@ -205,8 +181,7 @@ export const normalizeCeremonyGiftPayload = (payload) => {
if (payload.giftAmount === undefined || payload.giftAmount === null || payload.giftAmount === '') {
throw new TypeError('请填写献礼金额')
}
const giftAmount = typeof payload.giftAmount === 'number' ? payload.giftAmount : Number(payload.giftAmount)
if (!Number.isFinite(giftAmount)) throw new TypeError('献礼金额必须是有限数字')
const giftAmount = normalizeOptionalCurrencyNumber(payload.giftAmount, '献礼金额')
const normalizedPayload = { giftAmount }
for (const field of ['giverName', 'giftMessage']) {
const normalizedText = normalizeOptionalText(payload[field], field)