Files
jiapuapp/pages/family/album-detail.vue
T

267 lines
8.1 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.
<template>
<view class="album-detail-page">
<ModulePageBackground module="family" />
<view class="album-detail-header">
<PageHeader
title="相册详情"
:action="valid ? '添加' : ''"
custom-back
@back="returnToAlbums"
@action="addPhoto"
/>
</view>
<view class="album-detail-content">
<view v-if="!valid" class="album-state-card">
<text>暂时无法打开相册</text>
<AppButton block label="返回相册列表" @click="returnToAlbums" />
</view>
<view v-else-if="albumPhotoListState === 'loading'" class="album-state-card">
<AppLoading text="正在读取相册照片" />
</view>
<view v-else-if="albumPhotoListState === 'error'" class="album-state-card">
<text>暂时无法读取相册照片</text>
<AppButton block type="secondary" label="重新加载" @click="loadPhotos" />
</view>
<view v-else-if="albumPhotoListState === 'empty'" class="album-state-card">
<text>还没有照片</text>
<text>添加照片后页面会显示最新相册内容</text>
<AppButton block label="添加照片" @click="addPhoto" />
</view>
<view v-else class="photo-list">
<text v-if="deleteError" class="photo-list__error">{{ deleteError }}</text>
<view v-for="item in photos" :key="item.id" class="photo-card">
<image
class="photo-card__image"
:src="item.photoFile.accessUrl"
mode="widthFix"
role="button"
:aria-label="`查看大图:${item.title}`"
@click="previewPhoto(item)"
/>
<text>{{ item.title }}</text>
<text v-if="item.description">{{ item.description }}</text>
<text v-if="item.meta">{{ item.meta }}</text>
<view v-if="item.canDelete" class="photo-card__actions">
<AppButton compact type="secondary" label="删除照片" @click.stop="requestDeletePhoto(item)" />
</view>
</view>
</view>
</view>
<AppDialog
:visible="deleteConfirmationVisible"
:close-on-mask="false"
eyebrow="删除确认"
title="删除这张照片?"
message="删除后无法恢复,请确认影像已另行保存。"
confirm-text="确认删除"
cancel-text="保留照片"
show-cancel
@confirm="deletePhoto"
@cancel="closeDeleteConfirmation"
/>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
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 { familyMediaApi } from "@/services/api/family-media-service.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
import { goBack, openPage, returnTo } from "@/utils/navigation/gateway.js";
const genealogyId = ref("");
const albumId = ref("");
const photos = ref([]);
const albumPhotoListState = ref("loading");
const deleteTarget = ref(null);
const deleteConfirmationVisible = ref(false);
const deletingPhotoId = ref("");
const deleteError = ref("");
const albumPhotoListController = createRequestController();
const albumPhotoDeleteController = createRequestController();
let isPageActive = true;
const valid = computed(
() =>
/^[1-9]\d*$/.test(genealogyId.value) && /^[1-9]\d*$/.test(albumId.value),
);
const loadPhotos = async () => {
if (!valid.value) return;
albumPhotoListController.abort();
albumPhotoListState.value = "loading";
try {
const albumPhotos = await familyMediaApi.getAlbumPhotos(genealogyId.value, albumId.value, {
requestController: albumPhotoListController,
});
if (!isPageActive) return;
photos.value = albumPhotos.map((photo) => ({
...photo,
meta: [photo.photographer, photo.shootTime].filter(Boolean).join(" · "),
}));
albumPhotoListState.value = photos.value.length ? "ready" : "empty";
} catch (error) {
if (!isPageActive || isRequestCancelled(error)) return;
albumPhotoListState.value = "error";
}
};
const returnToAlbums = () =>
/^[1-9]\d*$/.test(genealogyId.value)
? returnTo("F07", { genealogyId: genealogyId.value })
: goBack();
const addPhoto = () =>
valid.value
? openPage(
"F09",
{ genealogyId: genealogyId.value, albumId: albumId.value },
"F08",
)
: Promise.resolve(false);
const previewPhoto = (photo) => {
const urls = photos.value.map((item) => item.photoFile?.accessUrl).filter(Boolean);
const current = photo?.photoFile?.accessUrl;
if (!current || !urls.length || typeof uni?.previewImage !== "function") return;
uni.previewImage({ current, urls });
};
const requestDeletePhoto = (photo) => {
if (!photo?.canDelete || deletingPhotoId.value) return;
deleteError.value = "";
deleteTarget.value = photo;
deleteConfirmationVisible.value = true;
};
const closeDeleteConfirmation = () => {
if (!deletingPhotoId.value) {
deleteConfirmationVisible.value = false;
deleteTarget.value = null;
}
};
const deletePhoto = async () => {
const photo = deleteTarget.value;
if (!photo?.canDelete || deletingPhotoId.value) return;
deletingPhotoId.value = photo.id;
deleteError.value = "";
try {
await familyMediaApi.deleteAlbumPhoto(genealogyId.value, albumId.value, photo.id, {
requestController: albumPhotoDeleteController,
});
if (!isPageActive) return;
deleteConfirmationVisible.value = false;
deleteTarget.value = null;
await loadPhotos();
} catch (error) {
if (!isPageActive || isRequestCancelled(error)) return;
deleteError.value = getRequestErrorMessage(error, "照片删除失败,请稍后重试。");
deleteConfirmationVisible.value = false;
} finally {
if (isPageActive) deletingPhotoId.value = "";
}
};
onLoad((query) => {
genealogyId.value = String(query?.genealogyId || "");
albumId.value = String(query?.albumId || "");
if (valid.value) loadPhotos();
});
onShow(() => {
if (valid.value && albumPhotoListState.value !== "loading") loadPhotos();
});
onUnload(() => {
isPageActive = false;
albumPhotoListController.abort();
albumPhotoDeleteController.abort();
});
</script>
<style scoped lang="scss">
@import "../../styles/adaptive-frame-profiles.scss";
.album-detail-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.album-detail-header,
.album-detail-content {
z-index: 1;
}
.album-detail-content {
padding: 22rpx 24rpx calc(48rpx + env(safe-area-inset-bottom));
}
.album-state-card,
.photo-card {
@include adaptive-family-content;
}
.album-state-card {
width: 100%;
min-height: 340rpx;
padding: 84rpx 44rpx 56rpx;
box-sizing: border-box;
text-align: center;
}
.album-state-card text {
display: block;
}
.album-state-card text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: clamp(19px, 36rpx, 24px);
font-weight: 700;
}
.album-state-card text:nth-child(2) {
margin-top: 10rpx;
color: $ink-muted;
font-size: clamp(14px, 23rpx, 17px);
line-height: 1.6;
}
.album-state-card .app-button {
margin-top: 34rpx;
}
.photo-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.photo-card {
padding: 28rpx 32rpx;
}
.photo-list__error {
display: block;
color: $brand-red;
font-size: clamp(13px, 21rpx, 16px);
line-height: 1.5;
}
.photo-card__image {
display: block;
width: 100%;
margin-bottom: 20rpx;
border-radius: 10rpx;
background: rgba(128, 89, 49, 0.12);
}
.photo-card__actions {
display: flex;
justify-content: flex-end;
margin-top: 16rpx;
}
.photo-card text {
display: block;
}
.photo-card text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: clamp(17px, 30rpx, 22px);
font-weight: 700;
}
.photo-card text:not(:first-child) {
margin-top: 8rpx;
color: $ink-muted;
font-size: clamp(14px, 22rpx, 17px);
line-height: 1.5;
}
</style>