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
+120 -4
View File
@@ -89,6 +89,13 @@
label="移出家谱"
@click="openConfirmation('remove', member)"
/>
<AppButton
v-if="isOwnerViewer && member.roleType === GENEALOGY_MEMBER_ROLE.ADMIN"
compact
type="secondary"
label="权限设置"
@click="openPermissionEditor(member)"
/>
<AppButton
v-if="member.capabilities.canLeave"
compact
@@ -159,6 +166,18 @@
>
<text v-if="operationError" class="edit-error" role="alert">{{ operationError }}</text>
</AppDialog>
<AppDialog :visible="Boolean(permissionTarget)" eyebrow="管理员权限" :title="`设置${permissionTarget?.memberName || '成员'}的权限`" :confirm-text="permissionSaving ? '正在保存' : '保存权限'" cancel-text="取消" show-cancel :close-on-mask="!permissionSaving" @confirm="savePermissions" @cancel="closePermissionEditor">
<AppLoading v-if="permissionLoading" text="正在读取权限目录" />
<view v-else-if="permissionOptions.length" class="permission-list">
<label v-for="option in permissionOptions" :key="option.code" class="permission-item" :class="{ 'permission-item--disabled': !option.enabled }">
<checkbox :checked="selectedPermissionCodes.includes(option.code)" :disabled="!option.enabled || permissionSaving" @click="togglePermission(option)" />
<view><text>{{ option.label }}</text><text v-if="option.description">{{ option.description }}</text></view>
</label>
</view>
<text v-else-if="!permissionError" class="edit-hint">当前没有可授权的权限项</text>
<textarea v-model.trim="permissionReason" maxlength="200" placeholder="授权原因(可选)" class="permission-reason" />
<text v-if="permissionError" class="edit-error">{{ permissionError }}</text>
</AppDialog>
</view>
</template>
@@ -179,6 +198,7 @@ import {
isRequestCancelled
} from "@/services/api/request-controller.js";
import { genealogyMemberApi } from "@/services/api/genealogy-member-service.js";
import { genealogyCapabilityApi } from "@/services/api/genealogy-capability-service.js";
import { lineageApi } from "@/services/api/lineage-service.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
import { formatMinuteTimestamp } from "@/utils/display-time.js";
@@ -198,6 +218,17 @@ const hasLeftGenealogy = ref(false);
const personOptions = ref([]);
const personOptionsState = ref("idle");
const memberListController = createRequestController();
const permissionTarget = ref(null);
const selectedPermissionCodes = ref([]);
const permissionReason = ref("");
const permissionSaving = ref(false);
const permissionLoading = ref(false);
const permissionError = ref("");
const permissionOptions = ref([]);
const permissionCatalogController = createRequestController();
const memberPermissionController = createRequestController();
const memberPermissionSaveController = createRequestController();
let permissionLoadSequence = 0;
const personOptionsController = createRequestController();
const memberUpdateController = createRequestController();
const membershipOperationController = createRequestController();
@@ -470,12 +501,86 @@ const returnToOverview = () =>
hasLeftGenealogy.value
? returnTo("G01")
: returnTo("G05", { genealogyId: genealogyId.value });
const openPermissionEditor = async (member) => {
const activeLoad = ++permissionLoadSequence;
permissionCatalogController.abort();
memberPermissionController.abort();
permissionTarget.value = member;
permissionOptions.value = [];
selectedPermissionCodes.value = [];
permissionReason.value = "";
permissionError.value = "";
permissionLoading.value = true;
try {
const catalog = await genealogyCapabilityApi.getPermissionCatalog(
genealogyId.value,
{ requestController: permissionCatalogController },
);
const enabledCodes = catalog.filter((option) => option.enabled).map((option) => option.code);
const permissions = await genealogyCapabilityApi.getMemberPermissions(
genealogyId.value,
member.memberId,
enabledCodes,
{ requestController: memberPermissionController },
);
if (!isPageActive || activeLoad !== permissionLoadSequence) return;
permissionOptions.value = catalog;
selectedPermissionCodes.value = permissions.permissionCodes;
} catch (error) {
if (!isPageActive || activeLoad !== permissionLoadSequence || isRequestCancelled(error)) return;
permissionError.value = getRequestErrorMessage(error, "权限目录暂时无法读取,当前不会保存任何变更。");
} finally {
if (isPageActive && activeLoad === permissionLoadSequence) permissionLoading.value = false;
}
};
const closePermissionEditor = () => {
if (permissionSaving.value) return;
permissionLoadSequence += 1;
permissionCatalogController.abort();
memberPermissionController.abort();
permissionTarget.value = null;
permissionLoading.value = false;
};
const togglePermission = (option) => {
if (!option?.enabled || permissionSaving.value || permissionLoading.value) return;
selectedPermissionCodes.value = selectedPermissionCodes.value.includes(option.code)
? selectedPermissionCodes.value.filter((code) => code !== option.code)
: [...selectedPermissionCodes.value, option.code];
};
const savePermissions = async () => {
if (!permissionTarget.value || permissionSaving.value || permissionLoading.value || permissionError.value) return;
const enabledCodes = permissionOptions.value.filter((option) => option.enabled).map((option) => option.code);
permissionSaving.value = true;
permissionError.value = "";
try {
await genealogyCapabilityApi.saveMemberPermissions(
genealogyId.value,
permissionTarget.value.memberId,
selectedPermissionCodes.value,
permissionReason.value,
enabledCodes,
{ requestController: memberPermissionSaveController },
);
if (!isPageActive) return;
feedbackMessage.value = "管理员权限已保存";
permissionTarget.value = null;
} catch (error) {
if (!isPageActive || isRequestCancelled(error)) return;
permissionError.value = getRequestErrorMessage(error, "权限保存失败,请稍后重试。");
} finally {
if (isPageActive) permissionSaving.value = false;
}
};
const requestBack = () =>
runBackGuard({
transientOpen: Boolean(editTarget.value || confirmTarget.value),
submitting: operationPending.value,
transientOpen: Boolean(editTarget.value || confirmTarget.value || permissionTarget.value),
submitting: operationPending.value || permissionSaving.value,
"close-transient": () =>
editTarget.value ? closeEdit() : closeConfirmation(),
editTarget.value
? closeEdit()
: confirmTarget.value
? closeConfirmation()
: closePermissionEditor(),
"block-submitting": () => true,
});
@@ -500,6 +605,9 @@ onUnload(() => {
personOptionsController.abort();
memberUpdateController.abort();
membershipOperationController.abort();
permissionCatalogController.abort();
memberPermissionController.abort();
memberPermissionSaveController.abort();
});
</script>
@@ -518,7 +626,7 @@ onUnload(() => {
}
.page-content {
padding: 18rpx 24rpx 72rpx;
padding: 18rpx 24rpx calc(72rpx + env(safe-area-inset-bottom));
}
.state-card,
@@ -725,4 +833,12 @@ onUnload(() => {
.edit-error {
color: $brand-red;
}
.permission-list { margin-top: 18rpx; }
.permission-item { display: flex; min-height: 76rpx; align-items: flex-start; padding: 18rpx 0; border-bottom: 1rpx solid rgba($gold, 0.38); color: $ink; font-size: 26rpx; }
.permission-item checkbox { margin-right: 16rpx; }
.permission-item view { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 6rpx; }
.permission-item view text:last-child:not(:first-child) { color: $ink-muted; font-size: 22rpx; line-height: 1.45; }
.permission-item--disabled { opacity: 0.54; }
.permission-reason { width: 100%; min-height: 100rpx; margin-top: 18rpx; padding: 16rpx; box-sizing: border-box; border: 1rpx solid rgba($gold, 0.38); border-radius: 10rpx; }
</style>