feat: 完成前端业务闭环与后端联调
This commit is contained in:
+42
-28
@@ -3,7 +3,7 @@
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header"
|
||||
><PageHeader
|
||||
title="家族备忘"
|
||||
:title="pageTitle"
|
||||
:action="valid && view === 'list' ? '新建' : ''"
|
||||
custom-back
|
||||
@back="requestBack"
|
||||
@@ -11,14 +11,14 @@
|
||||
/></view>
|
||||
<view class="page-content">
|
||||
<view v-if="view === 'form'" class="form-card">
|
||||
<text>{{ isEdit ? '编辑家族备忘' : '新建家族备忘' }}</text>
|
||||
<text>{{ isEdit ? `编辑${recordName}` : `新建${recordName}` }}</text>
|
||||
<text class="form-copy">{{ isEdit ? '原有关联图片、完成状态和排序会随本次保存保留。' : '红色 * 为必填项,其余内容可按需补充。' }}</text>
|
||||
<view class="field"
|
||||
><text><text class="required-mark">*</text>备忘标题</text
|
||||
><text><text class="required-mark">*</text>{{ isBenefactorMode ? "恩人姓名" : "备忘标题" }}</text
|
||||
><input
|
||||
v-model="form.memoTitle"
|
||||
maxlength="40"
|
||||
placeholder="请输入备忘标题"
|
||||
:placeholder="isBenefactorMode ? '请输入恩人姓名' : '请输入备忘标题'"
|
||||
@input="error = ''"
|
||||
/></view>
|
||||
<picker mode="date" :value="form.remindDate" @change="selectRemindDate"
|
||||
@@ -37,12 +37,12 @@
|
||||
></picker
|
||||
>
|
||||
<view class="field field--textarea"
|
||||
><text>备忘内容</text
|
||||
><text>{{ isBenefactorMode ? "恩人事迹" : "备忘内容" }}</text
|
||||
><textarea
|
||||
v-model="form.memoContent"
|
||||
auto-height
|
||||
maxlength="1200"
|
||||
placeholder="记录需要提醒的事情"
|
||||
:placeholder="isBenefactorMode ? '记录恩人事迹与家族渊源' : '记录需要提醒的事情'"
|
||||
@input="error = ''"
|
||||
/>
|
||||
</view>
|
||||
@@ -50,7 +50,7 @@
|
||||
<view
|
||||
><text>相关图片</text
|
||||
><text
|
||||
>图片上传成功后,会随备忘一起保存。</text
|
||||
>图片上传成功后,会随{{ recordName }}一起保存。</text
|
||||
></view
|
||||
>
|
||||
<button
|
||||
@@ -74,34 +74,35 @@
|
||||
label="取消"
|
||||
@click="cancelCreate" /><AppButton
|
||||
:disabled="submitting || uploading"
|
||||
:label="submitting ? '正在提交' : '提交备忘'"
|
||||
:label="submitting ? '正在提交' : `提交${recordName}`"
|
||||
@click="saveMemo"
|
||||
/></view>
|
||||
</view>
|
||||
<view v-else-if="!valid" class="state-card"
|
||||
><text>暂时无法打开家族备忘</text
|
||||
><text>暂时无法打开{{ recordName }}</text
|
||||
><AppButton block label="返回上一页" @click="requestBack"
|
||||
/></view>
|
||||
<view v-else-if="listState === 'loading'" class="state-card"
|
||||
><AppLoading text="正在读取家族备忘"
|
||||
><AppLoading :text="`正在读取${recordName}`"
|
||||
/></view>
|
||||
<view v-else-if="listState === 'error'" class="state-card"
|
||||
><text>暂时无法读取家族备忘</text
|
||||
><text>暂时无法读取{{ recordName }}</text
|
||||
><AppButton block type="secondary" label="重新加载" @click="loadMemos"
|
||||
/></view>
|
||||
<view v-else-if="listState === 'empty'" class="state-card"
|
||||
><text>还没有家族备忘</text
|
||||
><AppButton block label="新建备忘" @click="openCreate"
|
||||
><text>还没有{{ recordName }}</text
|
||||
><AppButton block :label="`新建${recordName}`" @click="openCreate"
|
||||
/></view>
|
||||
<view v-else class="memo-list">
|
||||
<text v-if="saveNotice" class="save-notice"
|
||||
>备忘已保存。</text
|
||||
>{{ recordName }}已保存。</text
|
||||
>
|
||||
<text v-if="deleteError" class="delete-error">{{ deleteError }}</text>
|
||||
<view v-for="item in memos" :key="item.id" class="memo-card" role="button" :aria-label="`查看${item.title}详情`" @click="openMemoDetail(item)">
|
||||
<view class="memo-card__copy">
|
||||
<text>{{ item.title }}</text>
|
||||
<text v-if="item.remindTime">{{ item.remindTime }}</text>
|
||||
<text v-if="item.createTime">创建于 {{ formatMinuteTimestamp(item.createTime) }}</text>
|
||||
<text v-if="item.content" class="memo-card__content">{{ item.content }}</text>
|
||||
</view>
|
||||
<view v-if="item.canEdit || item.canDelete" class="memo-card__actions">
|
||||
@@ -125,29 +126,30 @@
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="Boolean(detailTarget)"
|
||||
eyebrow="备忘详情"
|
||||
:title="detailTarget?.title || '家族备忘'"
|
||||
:eyebrow="`${recordName}详情`"
|
||||
:title="detailTarget?.title || recordName"
|
||||
confirm-text="关闭"
|
||||
:close-on-mask="detailState !== 'loading'"
|
||||
@confirm="closeMemoDetail"
|
||||
@cancel="closeMemoDetail"
|
||||
>
|
||||
<view class="detail-content">
|
||||
<AppLoading v-if="detailState === 'loading'" text="正在读取完整备忘" />
|
||||
<AppLoading v-if="detailState === 'loading'" :text="`正在读取完整${recordName}`" />
|
||||
<text v-else-if="detailState === 'error'" class="detail-error">{{ detailError }}</text>
|
||||
<template v-else>
|
||||
<text>提醒时间:{{ detailTarget?.remindTime || "未设置" }}</text>
|
||||
<text>完成状态:{{ detailTarget?.completed === "1" ? "已完成" : "未完成" }}</text>
|
||||
<text class="detail-content__body">{{ detailTarget?.content || "未填写备忘内容" }}</text>
|
||||
<text v-if="detailTarget?.createTime">创建时间:{{ formatMinuteTimestamp(detailTarget.createTime) }}</text>
|
||||
<text class="detail-content__body">{{ detailTarget?.content || `未填写${recordName}内容` }}</text>
|
||||
<view v-if="detailTarget?.mediaFiles?.length" class="detail-media">
|
||||
<image v-for="file in detailTarget.mediaFiles" :key="file.fileId" :src="file.accessUrl" mode="aspectFill" role="button" aria-label="查看备忘图片" @click="previewDetailMedia(file)" />
|
||||
<image v-for="file in detailTarget.mediaFiles" :key="file.fileId" :src="file.accessUrl" mode="aspectFill" role="button" :aria-label="`查看${recordName}图片`" @click="previewDetailMedia(file)" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</AppDialog>
|
||||
<AppDialog
|
||||
:visible="discardVisible"
|
||||
title="放弃家族备忘?"
|
||||
:title="`放弃${recordName}?`"
|
||||
message="尚未提交的内容将被清除。"
|
||||
confirm-text="放弃并返回"
|
||||
cancel-text="继续填写"
|
||||
@@ -158,10 +160,10 @@
|
||||
/>
|
||||
<AppDialog
|
||||
:visible="deleteConfirmationVisible"
|
||||
title="删除这条家族备忘?"
|
||||
message="删除后无法恢复,请确认当前内容不再需要。"
|
||||
confirm-text="确认删除"
|
||||
cancel-text="保留备忘"
|
||||
:title="`将这条${recordName}移至回收站?`"
|
||||
message="移入回收站后不再展示,家谱管理员可在保留期内恢复。"
|
||||
confirm-text="移至回收站"
|
||||
:cancel-text="`保留${recordName}`"
|
||||
show-cancel
|
||||
:close-on-mask="false"
|
||||
@confirm="deleteMemo"
|
||||
@@ -182,9 +184,11 @@ import {
|
||||
createRequestController,
|
||||
isRequestCancelled
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { MEMO_TYPE } from "@/services/api/life-record-contract.js";
|
||||
import { lifeRecordApi } from "@/services/api/life-record-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import { formatMinuteTimestamp } from "@/utils/display-time.js";
|
||||
import { createNonIdempotentWriteGuard } from "@/utils/non-idempotent-write-guard.js";
|
||||
import {
|
||||
isImagePickCancelled,
|
||||
@@ -193,6 +197,7 @@ import {
|
||||
import { goBack, handleBackPress, runBackGuard } from "@/utils/navigation/gateway.js";
|
||||
|
||||
const genealogyId = ref("");
|
||||
const memoType = ref(MEMO_TYPE.GENERAL);
|
||||
const view = ref("list");
|
||||
const listState = ref("loading");
|
||||
const memos = ref([]);
|
||||
@@ -228,6 +233,9 @@ const memoDetailRequestController = createRequestController();
|
||||
const memoCreateGuard = createNonIdempotentWriteGuard();
|
||||
let pageActive = true;
|
||||
const valid = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
|
||||
const isBenefactorMode = computed(() => memoType.value === MEMO_TYPE.BENEFACTOR);
|
||||
const recordName = computed(() => isBenefactorMode.value ? "家族恩人" : "家族备忘");
|
||||
const pageTitle = computed(() => recordName.value);
|
||||
const isEdit = computed(() => Boolean(editingMemo.value));
|
||||
const remindTime = computed(() =>
|
||||
form.remindDate ? `${form.remindDate} ${form.remindClock || "00:00"}:00` : "",
|
||||
@@ -276,7 +284,7 @@ const loadMemos = async () => {
|
||||
requestController: memoListRequestController,
|
||||
});
|
||||
if (!pageActive) return;
|
||||
memos.value = rows;
|
||||
memos.value = rows.filter((memo) => memo.memoType === memoType.value);
|
||||
listState.value = memos.value.length ? "ready" : "empty";
|
||||
if (pendingMemoId.value) {
|
||||
const target = memos.value.find((item) => item.id === pendingMemoId.value);
|
||||
@@ -413,6 +421,7 @@ const saveMemo = async () => {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
memoType: memoType.value,
|
||||
memoTitle: form.memoTitle,
|
||||
remindTime: remindTime.value,
|
||||
memoContent: form.memoContent,
|
||||
@@ -510,6 +519,9 @@ const requestBack = () =>
|
||||
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query?.genealogyId || "");
|
||||
memoType.value = query?.memoType === MEMO_TYPE.BENEFACTOR
|
||||
? MEMO_TYPE.BENEFACTOR
|
||||
: MEMO_TYPE.GENERAL;
|
||||
pendingMemoId.value = /^[1-9]\d*$/.test(String(query?.memoId || "")) ? String(query.memoId) : "";
|
||||
if (valid.value) loadMemos();
|
||||
else listState.value = "invalid";
|
||||
@@ -544,12 +556,14 @@ onUnload(() => {
|
||||
z-index: 1;
|
||||
}
|
||||
.page-content {
|
||||
padding: 18rpx 24rpx 72rpx;
|
||||
flex: 1;
|
||||
padding: 18rpx 24rpx calc(72rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.form-card,
|
||||
.state-card,
|
||||
.memo-card {
|
||||
@include adaptive-records-content;
|
||||
background-color: rgba($paper, 0.82);
|
||||
}
|
||||
.form-card {
|
||||
box-sizing: border-box;
|
||||
@@ -674,7 +688,7 @@ onUnload(() => {
|
||||
.memo-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.save-notice {
|
||||
display: block;
|
||||
@@ -707,7 +721,7 @@ onUnload(() => {
|
||||
}
|
||||
.memo-card__actions .app-button {
|
||||
width: 140rpx;
|
||||
min-height: 68rpx;
|
||||
min-height: 80rpx;
|
||||
}
|
||||
.memo-card text {
|
||||
display: block;
|
||||
|
||||
Reference in New Issue
Block a user