修改完成待测试
This commit is contained in:
+55
-309
@@ -1,346 +1,92 @@
|
||||
<template>
|
||||
<view
|
||||
class="rank-page"
|
||||
:class="{
|
||||
'rank-state--loading': rankState === 'loading',
|
||||
'rank-state--form': rankState === 'form',
|
||||
'rank-state--error': rankState === 'error',
|
||||
}"
|
||||
>
|
||||
<view class="rank-redirect-page">
|
||||
<ModulePageBackground module="tree" />
|
||||
<view class="rank-page__header"
|
||||
><PageHeader title="调整排行" custom-back @back="requestBack"
|
||||
/></view>
|
||||
<view class="rank-panel">
|
||||
<PageHeader title="编辑成员" />
|
||||
<view class="rank-redirect-panel">
|
||||
<AppLoading
|
||||
v-if="rankState === 'loading'"
|
||||
text="正在读取成员资料"
|
||||
description="请稍候,正在确认待调整人物。"
|
||||
v-if="redirectState === 'loading'"
|
||||
text="正在打开成员资料"
|
||||
description="排行称谓已合并到成员编辑页面。"
|
||||
/>
|
||||
|
||||
<view v-else-if="rankState === 'form' && member" class="rank-form">
|
||||
<text class="form-eyebrow">同辈排行</text>
|
||||
<text class="form-title">调整{{ member.name }}的排行</text>
|
||||
<text class="form-copy"
|
||||
>这里只调整这位成员的排行;同辈成员的整体排序暂不能在这里修改。</text
|
||||
>
|
||||
|
||||
<view class="member-context">
|
||||
<text>当前成员</text
|
||||
><text>第 {{ member.generation }} 世 · {{ member.branch }}</text>
|
||||
</view>
|
||||
<view class="rank-field">
|
||||
<text>排序值</text>
|
||||
<input
|
||||
v-model="sortOrder"
|
||||
type="number"
|
||||
:disabled="savingRank"
|
||||
placeholder="请输入整数,值越小越靠前"
|
||||
placeholder-class="rank-placeholder"
|
||||
@input="rankError = ''"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="rankError" class="rank-error">{{ rankError }}</text>
|
||||
<view
|
||||
class="form-action"
|
||||
:class="{ 'form-action--disabled': savingRank }"
|
||||
@click="saveRank"
|
||||
>
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/scroll-primary.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text>{{ savingRank ? "正在保存…" : "保存排行" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="rank-result">
|
||||
<text class="form-eyebrow">暂时无法打开成员页面</text>
|
||||
<text class="form-title">暂时无法读取成员资料</text>
|
||||
<text class="form-copy">{{ errorMessage }}</text>
|
||||
<view class="form-action" @click="goBack">
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/scroll-primary.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text>返回世系树</text>
|
||||
<view v-else class="rank-redirect-error">
|
||||
<text class="rank-redirect-error__title">暂时无法打开成员资料</text>
|
||||
<text class="rank-redirect-error__copy">{{ errorMessage }}</text>
|
||||
<view class="rank-redirect-error__action" @click="goBack">
|
||||
<text>返回上一页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="discardVisible"
|
||||
eyebrow="未保存修改"
|
||||
title="放弃排行修改?"
|
||||
message="当前排序值还没有保存。"
|
||||
confirm-text="确认放弃"
|
||||
cancel-text="继续编辑"
|
||||
show-cancel
|
||||
:close-on-mask="false"
|
||||
@confirm="confirmDiscard"
|
||||
@cancel="cancelDiscard"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import {
|
||||
createRequestController,
|
||||
isRequestCancelled
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { lineageApi } from "@/services/api/lineage-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
|
||||
import { goBack, handleBackPress, returnTo, runBackGuard } from "@/utils/navigation/gateway.js";
|
||||
import { goBack, returnTo } from "@/utils/navigation/gateway.js";
|
||||
|
||||
const rankState = ref("loading");
|
||||
const genealogyId = ref("");
|
||||
const personId = ref("");
|
||||
const member = ref(null);
|
||||
const redirectState = ref("loading");
|
||||
const errorMessage = ref("");
|
||||
const sortOrder = ref("");
|
||||
const savingRank = ref(false);
|
||||
const rankError = ref("");
|
||||
const committedSortOrder = ref("");
|
||||
const sortOrderBaseline = ref("");
|
||||
const discardVisible = ref(false);
|
||||
const memberRankReadRequestController = createRequestController();
|
||||
const memberRankSaveRequestController = createRequestController();
|
||||
let pageActive = true;
|
||||
let loadSequence = 0;
|
||||
const discardConfirmation = createDiscardConfirmation((visible) => {
|
||||
discardVisible.value = visible;
|
||||
});
|
||||
const requestDiscardConfirmation = discardConfirmation.request;
|
||||
const confirmDiscard = discardConfirmation.confirm;
|
||||
const cancelDiscard = discardConfirmation.cancel;
|
||||
const isDirty = computed(() =>
|
||||
rankState.value === "form" && sortOrder.value !== sortOrderBaseline.value,
|
||||
);
|
||||
|
||||
const loadMember = async () => {
|
||||
const activeLoad = ++loadSequence;
|
||||
rankState.value = "loading";
|
||||
onLoad(async (query) => {
|
||||
const genealogyId = String(query?.genealogyId || "");
|
||||
const personId = String(query?.personId || "");
|
||||
if (!/^[1-9]\d*$/.test(genealogyId) || !/^[1-9]\d*$/.test(personId)) {
|
||||
redirectState.value = "error";
|
||||
errorMessage.value = "页面参数已经失效,请从成员资料重新进入。";
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const personDetail = await lineageApi.getPerson(genealogyId.value, personId.value, {
|
||||
requestController: memberRankReadRequestController,
|
||||
});
|
||||
if (!pageActive || activeLoad !== loadSequence) return;
|
||||
member.value = personDetail;
|
||||
sortOrder.value = personDetail.sortOrder === null || personDetail.sortOrder === undefined
|
||||
? ""
|
||||
: String(personDetail.sortOrder);
|
||||
sortOrderBaseline.value = sortOrder.value;
|
||||
committedSortOrder.value = "";
|
||||
errorMessage.value = "";
|
||||
rankState.value = "form";
|
||||
await returnTo("T05", { genealogyId, personId });
|
||||
} catch (error) {
|
||||
if (!pageActive || activeLoad !== loadSequence || isRequestCancelled(error)) return;
|
||||
member.value = null;
|
||||
errorMessage.value =
|
||||
getRequestErrorMessage(error, "当前成员资料暂不可用,请返回世系树后重试。");
|
||||
rankState.value = "error";
|
||||
redirectState.value = "error";
|
||||
errorMessage.value = error?.message || "请返回后重新打开成员资料。";
|
||||
}
|
||||
};
|
||||
|
||||
const saveRank = async () => {
|
||||
if (savingRank.value || !member.value) return;
|
||||
const normalizedSortOrder = String(sortOrder.value).trim();
|
||||
if (!/^-?\d+$/.test(normalizedSortOrder)) {
|
||||
rankError.value = "请填写整数,例如 1、2、3。";
|
||||
return;
|
||||
}
|
||||
const numericSortOrder = Number(normalizedSortOrder);
|
||||
if (!Number.isSafeInteger(numericSortOrder)) {
|
||||
rankError.value = "排序值超出可保存范围";
|
||||
return;
|
||||
}
|
||||
savingRank.value = true;
|
||||
rankError.value = "";
|
||||
try {
|
||||
if (committedSortOrder.value !== normalizedSortOrder) {
|
||||
await lineageApi.updatePersonSortOrder(
|
||||
genealogyId.value,
|
||||
personId.value,
|
||||
numericSortOrder,
|
||||
{ requestController: memberRankSaveRequestController },
|
||||
);
|
||||
if (!pageActive) return;
|
||||
committedSortOrder.value = normalizedSortOrder;
|
||||
sortOrderBaseline.value = normalizedSortOrder;
|
||||
}
|
||||
await returnTo("T01", {
|
||||
genealogyId: genealogyId.value,
|
||||
selectedId: personId.value,
|
||||
});
|
||||
} catch (error) {
|
||||
if (pageActive && !isRequestCancelled(error)) {
|
||||
rankError.value = committedSortOrder.value === normalizedSortOrder
|
||||
? "排行已经保存,但页面返回失败。请再次点击保存重试返回。"
|
||||
: getRequestErrorMessage(error, "排行尚未保存,请稍后重试。");
|
||||
}
|
||||
} finally {
|
||||
if (pageActive) savingRank.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const requestBack = () => runBackGuard({
|
||||
transientOpen: discardVisible.value,
|
||||
dirty: isDirty.value,
|
||||
submitting: savingRank.value,
|
||||
"close-transient": cancelDiscard,
|
||||
"block-submitting": () => true,
|
||||
"confirm-discard": requestDiscardConfirmation,
|
||||
});
|
||||
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query.genealogyId || "");
|
||||
personId.value = String(query.personId || "");
|
||||
if (
|
||||
!/^[1-9]\d*$/.test(genealogyId.value) ||
|
||||
!/^[1-9]\d*$/.test(personId.value) ||
|
||||
query.mode !== "rank"
|
||||
) {
|
||||
rankState.value = "error";
|
||||
errorMessage.value = "请从成员资料页重新进入排行调整。";
|
||||
return;
|
||||
}
|
||||
void loadMember();
|
||||
});
|
||||
|
||||
onUnload(() => {
|
||||
pageActive = false;
|
||||
loadSequence += 1;
|
||||
memberRankReadRequestController.abort();
|
||||
memberRankSaveRequestController.abort();
|
||||
discardConfirmation.dispose();
|
||||
});
|
||||
|
||||
onBackPress((event) => handleBackPress(event, requestBack));
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../styles/adaptive-frame-profiles.scss";
|
||||
|
||||
.rank-page {
|
||||
display: flex;
|
||||
.rank-redirect-page {
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: $paper;
|
||||
}
|
||||
.rank-page__header {
|
||||
z-index: 3;
|
||||
}
|
||||
.rank-panel {
|
||||
@include adaptive-tree-panel;
|
||||
z-index: 2;
|
||||
width: calc(100% - 32rpx);
|
||||
margin: 18rpx auto calc(28rpx + env(safe-area-inset-bottom));
|
||||
padding: 7.5% 8%;
|
||||
}
|
||||
.form-eyebrow {
|
||||
display: block;
|
||||
color: $brand-red;
|
||||
font-size: clamp(14px, 22rpx, 17px);
|
||||
letter-spacing: 3rpx;
|
||||
}
|
||||
.form-title {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: $ink;
|
||||
font-family: "STKaiti", "KaiTi", serif;
|
||||
font-size: clamp(19px, 34rpx, 24px);
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.form-copy {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(15px, 24rpx, 18px);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.member-context,
|
||||
.rank-field {
|
||||
@include adaptive-tree-field;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
min-height: 78rpx;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
padding: 12rpx 22rpx;
|
||||
}
|
||||
.member-context text:first-child,
|
||||
.rank-field text {
|
||||
color: $ink;
|
||||
font-size: clamp(15px, 24rpx, 18px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-context text:last-child {
|
||||
min-width: 0;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
line-height: 1.45;
|
||||
text-align: right;
|
||||
}
|
||||
.rank-field input {
|
||||
min-width: 0;
|
||||
min-height: var(--app-touch-min);
|
||||
padding: 0 14rpx;
|
||||
border: 1rpx solid rgba(143, 108, 63, 0.34);
|
||||
border-radius: 8rpx;
|
||||
background: rgba(255, 253, 247, 0.8);
|
||||
color: $ink;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
text-align: right;
|
||||
}
|
||||
.rank-placeholder { color: $ink-muted; }
|
||||
.rank-error { display: block; margin-top: 12rpx; color: $brand-red; font-size: clamp(14px, 22rpx, 17px); }
|
||||
.form-action {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: var(--app-touch-min);
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
.form-action--disabled { opacity: 0.58; pointer-events: none; }
|
||||
.form-action image,
|
||||
.form-action text {
|
||||
grid-area: 1 / 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.form-action text {
|
||||
|
||||
.rank-redirect-panel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 48rpx 32rpx;
|
||||
}
|
||||
|
||||
.rank-redirect-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff9ed;
|
||||
font-size: clamp(15px, 24rpx, 18px);
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
padding: 64rpx 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rank-redirect-error__title {
|
||||
color: $ink;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.rank-result {
|
||||
margin-top: 30%;
|
||||
text-align: center;
|
||||
|
||||
.rank-redirect-error__copy {
|
||||
color: $ink-muted;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.rank-result .form-eyebrow,
|
||||
.rank-result .form-copy {
|
||||
text-align: center;
|
||||
}
|
||||
.rank-result .form-action {
|
||||
width: 420rpx;
|
||||
max-width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.rank-panel {
|
||||
width: calc(100% - 48rpx);
|
||||
}
|
||||
|
||||
.rank-redirect-error__action {
|
||||
min-width: 240rpx;
|
||||
padding: 22rpx 36rpx;
|
||||
border: 1rpx solid rgba($brand-red, 0.4);
|
||||
color: $brand-red;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user