Files
jiapuapp/pages/family/f02-publish-feed.vue
T
2026-07-24 07:56:33 +08:00

147 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 页面编号F-02用途 Apifox 已声明的 feedContent 发布家族动态 -->
<template>
<view class="publish-page" :class="`publish-state--${publishState}`">
<ModulePageBackground module="family" />
<view class="publish-page__header"><PageHeader title="发布动态" custom-back @back="requestBack" /></view>
<view class="publish-panel">
<view v-if="publishState === 'form'" class="publish-form">
<text>记录此刻</text>
<text>当前接口只明确文本动态的 `feedContent` 请求字段媒体排序和状态均不由本页猜测或提交</text>
<view class="publish-field"><textarea v-model="content" auto-height maxlength="300" placeholder="写下想对家人说的话" placeholder-class="publish-placeholder" @input="submitError = ''" /></view>
<text v-if="submitError" class="publish-error">{{ submitError }}</text>
<AppButton block :label="isSubmitting ? '正在提交' : '提交动态'" :disabled="isSubmitting" @click="submit" />
</view>
<view v-else class="publish-result">
<text>{{ resultCopy.title }}</text>
<text>{{ resultCopy.copy }}</text>
<AppButton block :label="resultCopy.action" @click="handleResultAction" />
</view>
</view>
<AppDialog
:visible="discardVisible"
title="放弃动态草稿?"
message="当前内容尚未提交服务器,确认返回后不会保留。"
confirm-text="放弃并返回"
cancel-text="继续填写"
show-cancel
:close-on-mask="false"
@confirm="confirmDiscard"
@cancel="cancelDiscard"
/>
</view>
</template>
<script setup>
import { computed, onUnmounted, ref } from "vue";
import { onBackPress, onLoad } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { appApi, createRequestController, isRequestCancelled } from "@/utils/api.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import { goBack, handleBackPress, returnTo, runBackGuard } from "@/utils/navigation.js";
const genealogyId = ref("");
const content = ref("");
const publishState = ref("form");
const isSubmitting = ref(false);
const submitError = ref("");
const discardVisible = ref(false);
const requestController = createRequestController();
const isDirty = computed(() => Boolean(content.value.trim()));
const hasValidContext = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
const resultCopy = computed(() => ({
success: {
title: "动态已提交服务端",
copy: "服务端已返回成功信封。动态列表仍缺条目 DTO,返回后不会把本地内容伪装成列表项。",
action: "返回家族动态",
},
error: {
title: "动态未提交",
copy: "当前文字仍保留在页面中,可返回表单继续核对。",
action: "返回填写",
},
invalid: {
title: "动态入口无效",
copy: "没有取得有效家谱标识,页面不会创建无归属动态。",
action: "返回上一页",
},
}[publishState.value]));
const discardConfirmation = createDiscardConfirmation((visible) => {
discardVisible.value = visible;
});
const requestDiscardConfirmation = discardConfirmation.request;
const confirmDiscard = discardConfirmation.confirm;
const cancelDiscard = discardConfirmation.cancel;
onLoad((query) => {
genealogyId.value = String(query?.genealogyId || "");
if (!hasValidContext.value) publishState.value = "invalid";
});
const submit = async () => {
if (isSubmitting.value || !hasValidContext.value) return;
const feedContent = content.value.trim();
if (!feedContent) {
submitError.value = "请先写下动态内容";
return;
}
isSubmitting.value = true;
submitError.value = "";
try {
await appApi.createFeed(genealogyId.value, { feedContent }, { requestController });
content.value = "";
publishState.value = "success";
} catch (error) {
if (isRequestCancelled(error)) return;
publishState.value = "error";
submitError.value = error?.message || "动态提交失败,请稍后重试。";
} finally {
isSubmitting.value = false;
}
};
const requestBack = () => runBackGuard({
transientOpen: discardVisible.value,
dirty: isDirty.value,
submitting: isSubmitting.value,
"close-transient": cancelDiscard,
"block-submitting": () => true,
"confirm-discard": requestDiscardConfirmation,
});
const returnToFamily = async () => {
const confirmed = isDirty.value ? await requestDiscardConfirmation() : true;
if (!confirmed) return false;
return returnTo("F01", { genealogyId: genealogyId.value });
};
const handleResultAction = () => {
if (publishState.value === "success") return returnToFamily();
if (publishState.value === "error") {
publishState.value = "form";
return;
}
return goBack();
};
onBackPress((event) => handleBackPress(event, requestBack));
onUnmounted(() => {
requestController.abort();
discardConfirmation.dispose();
});
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.publish-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.publish-page__header, .publish-panel { @include adaptive.adaptive-family-content; z-index: 1; }
.publish-panel { width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 9%; box-sizing: border-box; }
.publish-form > text, .publish-result > text { display: block; }
.publish-form > text:first-child, .publish-result > text:first-child { color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 36rpx; font-weight: 700; }
.publish-form > text:nth-child(2), .publish-result > text:nth-child(2) { margin-top: 12rpx; color: $ink-muted; font-size: 23rpx; line-height: 1.6; }
.publish-field { @include adaptive.adaptive-family-field; display: flex; min-height: 250rpx; margin-top: 24rpx; padding: 25rpx; box-sizing: border-box; }
.publish-field textarea { width: 100%; min-height: 200rpx; color: $ink; font-size: 24rpx; line-height: 1.7; }
.publish-placeholder { color: #8e806e; }
.publish-error { margin-top: 12rpx; color: $brand-red; font-size: 22rpx; line-height: 1.5; }
.publish-form > .app-button { margin-top: 24rpx; }
.publish-result { margin-top: 20%; text-align: center; }
.publish-result > .app-button { margin: 30rpx auto 0; }
</style>