保存视觉审核与文档流迁移进度
This commit is contained in:
@@ -192,6 +192,7 @@ const openSection = (key) => {
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.feed-shortcut {
|
||||
position: relative;
|
||||
@@ -212,10 +213,14 @@ const openSection = (key) => {
|
||||
.feed-card,
|
||||
.feed-state-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 230rpx;
|
||||
min-height: 98px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-top: 17rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.feed-card__skin,
|
||||
.feed-state-card > image,
|
||||
@@ -224,11 +229,12 @@ const openSection = (key) => {
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.feed-card__copy {
|
||||
position: absolute;
|
||||
inset: 15% 9%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 15% 9%;
|
||||
}
|
||||
.feed-card text {
|
||||
display: block;
|
||||
@@ -260,9 +266,10 @@ const openSection = (key) => {
|
||||
margin-top: 70rpx;
|
||||
}
|
||||
.feed-state-card > view {
|
||||
position: absolute;
|
||||
inset: 23% 10%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 23% 10%;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
.feed-state-card text {
|
||||
|
||||
@@ -99,19 +99,22 @@ onUnmounted(() => {
|
||||
}
|
||||
.publish-panel {
|
||||
width: calc(100% - 32rpx);
|
||||
height: min(640px, calc((100vw - 16px) * 1.48));
|
||||
min-height: min(640px, calc((100vw - 16px) * 1.48));
|
||||
margin: 18rpx auto 0;
|
||||
padding: 9%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.publish-panel__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.publish-form,
|
||||
.publish-result {
|
||||
position: absolute;
|
||||
inset: 9% 9%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.publish-form > text,
|
||||
.publish-result > text {
|
||||
@@ -133,20 +136,23 @@ onUnmounted(() => {
|
||||
}
|
||||
.publish-field {
|
||||
position: relative;
|
||||
height: 250rpx;
|
||||
display: flex;
|
||||
min-height: 250rpx;
|
||||
margin-top: 24rpx;
|
||||
padding: 25rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.publish-field image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.publish-field textarea {
|
||||
position: absolute;
|
||||
inset: 25rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: auto;
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
@@ -159,7 +165,7 @@ onUnmounted(() => {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.publish-result {
|
||||
top: 29%;
|
||||
margin-top: 20%;
|
||||
text-align: center;
|
||||
}
|
||||
.publish-result > .app-button {
|
||||
|
||||
@@ -1,5 +1,386 @@
|
||||
<!-- 页面编号:F-06;用途:新建与编辑谱文。 -->
|
||||
<template><ModulePage page-id="f06" /></template>
|
||||
<template>
|
||||
<view
|
||||
class="article-editor-page"
|
||||
:class="`article-editor-state--${editorState}`"
|
||||
>
|
||||
<ModulePageBackground module="family" />
|
||||
<view class="article-editor-page__header">
|
||||
<PageHeader title="编辑谱文" />
|
||||
</view>
|
||||
|
||||
<view v-if="editorState === 'loading'" class="article-editor-loading">
|
||||
<AppLoading
|
||||
text="正在打开谱文编辑器"
|
||||
description="请稍候,草稿正在展开。"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-else-if="editorState === 'success'"
|
||||
class="article-editor-content"
|
||||
>
|
||||
<view class="editor-result-card">
|
||||
<view class="editor-result-card__body">
|
||||
<text class="editor-eyebrow">保存结果</text>
|
||||
<text class="editor-result-card__title">谱文已保存</text>
|
||||
<text class="editor-result-card__copy"
|
||||
>这篇谱文已收录,可返回谱文列表继续查看。</text
|
||||
>
|
||||
<AppButton block label="返回谱文列表" @click="returnToList" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="article-editor-content">
|
||||
<view class="editor-panel">
|
||||
<view class="editor-panel__body">
|
||||
<text class="editor-eyebrow">草稿 · 未发布</text>
|
||||
<text class="editor-title">把值得传承的故事写下来</text>
|
||||
<text class="editor-intro"
|
||||
>补充标题、分类和正文,保存后可返回谱文列表继续查看。</text
|
||||
>
|
||||
|
||||
<view class="editor-field">
|
||||
<text class="editor-field__label">文章标题</text>
|
||||
<view
|
||||
class="editor-control"
|
||||
:class="{ 'editor-control--error': fieldErrors.title }"
|
||||
>
|
||||
<image :src="fieldFrame" mode="scaleToFill" />
|
||||
<input
|
||||
v-model="form.title"
|
||||
maxlength="40"
|
||||
placeholder="请输入文章标题"
|
||||
placeholder-class="editor-placeholder"
|
||||
@input="clearFieldError('title')"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.title" class="editor-field__error">{{
|
||||
fieldErrors.title
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view class="editor-field">
|
||||
<text class="editor-field__label">文章分类</text>
|
||||
<view
|
||||
class="editor-control"
|
||||
:class="{ 'editor-control--error': fieldErrors.category }"
|
||||
>
|
||||
<image :src="fieldFrame" mode="scaleToFill" />
|
||||
<input
|
||||
v-model="form.category"
|
||||
maxlength="20"
|
||||
placeholder="如:家风家训"
|
||||
placeholder-class="editor-placeholder"
|
||||
@input="clearFieldError('category')"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.category" class="editor-field__error">{{
|
||||
fieldErrors.category
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view class="editor-field">
|
||||
<text class="editor-field__label">正文内容</text>
|
||||
<view
|
||||
class="editor-control editor-control--textarea"
|
||||
:class="{ 'editor-control--error': fieldErrors.content }"
|
||||
>
|
||||
<image :src="fieldFrame" mode="scaleToFill" />
|
||||
<textarea
|
||||
v-model="form.content"
|
||||
maxlength="1200"
|
||||
placeholder="记录家训、往事或想留给后人的话"
|
||||
placeholder-class="editor-placeholder"
|
||||
@input="clearFieldError('content')"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="fieldErrors.content" class="editor-field__error">{{
|
||||
fieldErrors.content
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<text class="editor-draft-note"
|
||||
>当前为未发布草稿;真实草稿保存将在接口阶段接入。</text
|
||||
>
|
||||
<text v-if="editorState === 'error'" class="editor-save-error"
|
||||
>保存失败,已填写内容仍保留,请稍后重试。</text
|
||||
>
|
||||
<AppButton
|
||||
block
|
||||
:label="actionLabel"
|
||||
:disabled="editorState === 'saving'"
|
||||
@click="submit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const fieldFrame =
|
||||
"/static/assets/modules/family/transparent/module-field-frame.png";
|
||||
const draftFixture = {
|
||||
title: "汤氏家训辑录",
|
||||
category: "家风家训",
|
||||
content:
|
||||
"孝友传家,勤俭立业;敬祖睦宗,诚实待人。愿后人常怀感恩,彼此扶持。",
|
||||
};
|
||||
const editorState = ref("form");
|
||||
const allowedStates = new Set([
|
||||
"draft",
|
||||
"loading",
|
||||
"validation",
|
||||
"saving",
|
||||
"error",
|
||||
"success",
|
||||
]);
|
||||
const form = reactive({ title: "", category: "", content: "" });
|
||||
const fieldErrors = reactive({ title: "", category: "", content: "" });
|
||||
let saveTimer = null;
|
||||
|
||||
const actionLabel = computed(() =>
|
||||
editorState.value === "saving"
|
||||
? "正在保存…"
|
||||
: editorState.value === "error"
|
||||
? "重新保存"
|
||||
: "保存谱文",
|
||||
);
|
||||
|
||||
const fillDraft = (failed = false) => {
|
||||
Object.assign(form, draftFixture, failed ? { title: "保存失败" } : {});
|
||||
};
|
||||
const showAllFieldErrors = () => {
|
||||
fieldErrors.title = "请填写文章标题";
|
||||
fieldErrors.category = "请填写文章分类";
|
||||
fieldErrors.content = "请填写正文内容";
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
const requestedState = allowedStates.has(query.state) ? query.state : "form";
|
||||
if (["draft", "saving", "error"].includes(requestedState)) {
|
||||
fillDraft(requestedState === "error");
|
||||
}
|
||||
if (requestedState === "validation") showAllFieldErrors();
|
||||
editorState.value = requestedState;
|
||||
});
|
||||
|
||||
const clearFieldError = (field) => {
|
||||
fieldErrors[field] = "";
|
||||
if (
|
||||
editorState.value === "error" ||
|
||||
editorState.value === "validation"
|
||||
) {
|
||||
editorState.value = "form";
|
||||
}
|
||||
};
|
||||
const validate = () => {
|
||||
fieldErrors.title = form.title.trim() ? "" : "请填写文章标题";
|
||||
fieldErrors.category = form.category.trim() ? "" : "请填写文章分类";
|
||||
fieldErrors.content = form.content.trim() ? "" : "请填写正文内容";
|
||||
return !fieldErrors.title && !fieldErrors.category && !fieldErrors.content;
|
||||
};
|
||||
const submit = () => {
|
||||
if (editorState.value === "saving") return;
|
||||
if (!validate()) {
|
||||
editorState.value = "validation";
|
||||
return;
|
||||
}
|
||||
editorState.value = "saving";
|
||||
saveTimer = setTimeout(() => {
|
||||
editorState.value =
|
||||
form.title.trim() === "保存失败" ? "error" : "success";
|
||||
saveTimer = null;
|
||||
}, 320);
|
||||
};
|
||||
const returnToList = () => uni.navigateBack();
|
||||
|
||||
onUnload(() => {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.article-editor-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.article-editor-page__header,
|
||||
.article-editor-loading,
|
||||
.article-editor-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.article-editor-loading {
|
||||
padding-top: 150rpx;
|
||||
}
|
||||
.article-editor-content {
|
||||
padding: 18rpx 20rpx calc(38rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.editor-panel,
|
||||
.editor-result-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 20rpx solid transparent;
|
||||
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
|
||||
border-image-slice: 72 fill;
|
||||
border-image-width: 64rpx;
|
||||
border-image-repeat: stretch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.editor-panel__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 38rpx 34rpx 42rpx;
|
||||
}
|
||||
.editor-eyebrow {
|
||||
display: block;
|
||||
color: $brand-red;
|
||||
font-size: 23rpx;
|
||||
letter-spacing: 3rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-title {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-intro {
|
||||
display: block;
|
||||
margin: 10rpx 8rpx 22rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.55;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-field {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.editor-field__label {
|
||||
display: block;
|
||||
margin: 0 8rpx 8rpx;
|
||||
color: $ink;
|
||||
font-size: 25rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.editor-control {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 82rpx;
|
||||
align-items: center;
|
||||
}
|
||||
.editor-control > image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.editor-control input,
|
||||
.editor-control textarea {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.editor-control input {
|
||||
width: 100%;
|
||||
height: 82rpx;
|
||||
padding: 0 28rpx;
|
||||
}
|
||||
.editor-control--textarea {
|
||||
height: 220rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.editor-control textarea {
|
||||
width: 100%;
|
||||
height: 180rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.editor-control--error {
|
||||
filter: sepia(0.18) saturate(1.2);
|
||||
}
|
||||
.editor-placeholder {
|
||||
color: #9e8e79;
|
||||
}
|
||||
.editor-field__error,
|
||||
.editor-save-error {
|
||||
display: block;
|
||||
color: $brand-red;
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
.editor-field__error {
|
||||
margin: 5rpx 8rpx 0;
|
||||
}
|
||||
.editor-draft-note {
|
||||
display: block;
|
||||
margin: 20rpx 10rpx 0;
|
||||
color: $ink-muted;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.55;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-save-error {
|
||||
margin: 12rpx 8rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-panel__body > .app-button {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
.editor-result-card {
|
||||
min-height: 420rpx;
|
||||
}
|
||||
.editor-result-card__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 112rpx 52rpx 68rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.editor-result-card__title {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.editor-result-card__copy {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.editor-result-card__body > .app-button {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
@media (max-width: 340px) {
|
||||
.article-editor-content {
|
||||
padding-right: 14rpx;
|
||||
padding-left: 14rpx;
|
||||
}
|
||||
.editor-panel__body {
|
||||
padding-right: 26rpx;
|
||||
padding-left: 26rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,342 @@
|
||||
<!-- 页面编号:F-08;用途:相册详情与照片墙。 -->
|
||||
<template><ModulePage page-id="f08" /></template>
|
||||
<template>
|
||||
<view class="album-detail-page" :class="`album-state--${albumState}`">
|
||||
<ModulePageBackground module="family" />
|
||||
<view class="album-detail-header"><PageHeader title="相册详情" /></view>
|
||||
|
||||
<view class="album-detail-content">
|
||||
<view v-if="albumState === 'expired'" class="album-expired-state">
|
||||
<view class="album-state-card__body">
|
||||
<text class="album-state-card__eyebrow">相册状态</text>
|
||||
<text class="album-state-card__title">相册已失效</text>
|
||||
<text class="album-state-card__copy">这个相册已无法查看</text>
|
||||
<AppButton block label="返回相册列表" @click="returnToAlbums" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view class="album-heading">
|
||||
<text class="album-heading__eyebrow">家族影像 · 2024</text>
|
||||
<text class="album-heading__title">2024 春节团圆</text>
|
||||
<text class="album-heading__copy"
|
||||
>一家人围坐团圆,让今朝欢聚与祖居旧影留在同一本相册里。</text
|
||||
>
|
||||
<text class="album-heading__count"
|
||||
>{{ albumState === "empty" ? "0 张照片" : "5 张照片" }}</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view v-if="albumState === 'empty'" class="album-empty-state">
|
||||
<view class="album-state-card__body">
|
||||
<text class="album-state-card__eyebrow">照片墙</text>
|
||||
<text class="album-state-card__title">相册里还没有照片</text>
|
||||
<text class="album-state-card__copy"
|
||||
>添加第一张团圆影像,把值得珍藏的时刻留在这里。</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="album-wall">
|
||||
<view
|
||||
class="album-photo-tile album-photo-tile--hero"
|
||||
@click="openPreview(0)"
|
||||
>
|
||||
<image
|
||||
class="album-hero-photo"
|
||||
:src="photos[0].src"
|
||||
:alt="photos[0].alt"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text class="album-photo-tile__caption">{{ photos[0].caption }}</text>
|
||||
</view>
|
||||
|
||||
<view class="album-photo-grid">
|
||||
<view
|
||||
v-for="(photo, index) in photos.slice(1)"
|
||||
:key="photo.src"
|
||||
class="album-photo-tile album-photo-tile--grid"
|
||||
@click="openPreview(index + 1)"
|
||||
>
|
||||
<image
|
||||
class="album-photo-tile__image"
|
||||
:src="photo.src"
|
||||
:alt="photo.alt"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text class="album-photo-tile__caption">{{ photo.caption }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="album-upload-action" @click="showUploadNotice">
|
||||
<AppButton block label="添加照片" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view v-if="previewVisible" class="album-preview">
|
||||
<view class="album-preview__toolbar">
|
||||
<text class="album-preview__title">照片预览</text>
|
||||
<view class="album-preview__close" @click="closePreview"><text>关闭</text></view>
|
||||
</view>
|
||||
<image
|
||||
class="album-preview__image"
|
||||
:src="photos[previewIndex].src"
|
||||
:alt="photos[previewIndex].alt"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="album-preview__footer">
|
||||
<text class="album-preview__position"
|
||||
>{{ previewIndex + 1 }} / {{ photos.length }}</text
|
||||
>
|
||||
<text class="album-preview__caption">{{ photos[previewIndex].caption }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<AppToast :visible="uploadNoticeVisible" :message="uploadNoticeMessage" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { ref } from "vue";
|
||||
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppToast from "@/components/AppToast.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const albumState = ref("normal");
|
||||
const previewVisible = ref(false);
|
||||
const previewIndex = ref(0);
|
||||
const uploadNoticeVisible = ref(false);
|
||||
const uploadNoticeMessage = "上传照片将在入口接通后开放";
|
||||
let uploadNoticeTimer;
|
||||
|
||||
const photos = [
|
||||
{ src: "/static/assets/modules/family/f08/f08-reunion-hero.png", alt: "春节团圆时三代家人的合影", caption: "除夕团圆 · 2024" },
|
||||
{ src: "/static/assets/modules/family/f08/f08-family-portrait.png", alt: "家人在院落前的春节合影", caption: "院前合影 · 2024" },
|
||||
{ src: "/static/assets/modules/family/f08/f08-reunion-table.png", alt: "家人围坐吃年夜饭", caption: "围桌守岁 · 2024" },
|
||||
{ src: "/static/assets/modules/family/f08/f08-ancestral-home.png", alt: "祖居院落的复古旧照", caption: "祖居旧影 · 1968" },
|
||||
{ src: "/static/assets/modules/family/f08/f08-ancestral-portrait.png", alt: "老一辈家人在祖居门前的合影", caption: "门前合影 · 1972" },
|
||||
];
|
||||
|
||||
const openPreview = (index) => {
|
||||
previewIndex.value = index;
|
||||
previewVisible.value = true;
|
||||
albumState.value = "preview";
|
||||
};
|
||||
|
||||
const closePreview = () => {
|
||||
previewVisible.value = false;
|
||||
albumState.value = "normal";
|
||||
};
|
||||
|
||||
const showUploadNotice = () => {
|
||||
uploadNoticeVisible.value = true;
|
||||
clearTimeout(uploadNoticeTimer);
|
||||
uploadNoticeTimer = setTimeout(() => {
|
||||
uploadNoticeVisible.value = false;
|
||||
}, 1800);
|
||||
};
|
||||
|
||||
const returnToAlbums = () => {
|
||||
uni.redirectTo({ url: "/pages/family/f07-album-list" });
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
albumState.value =
|
||||
query.state === "empty"
|
||||
? "empty"
|
||||
: query.state === "preview"
|
||||
? "preview"
|
||||
: query.state === "expired"
|
||||
? "expired"
|
||||
: "normal";
|
||||
previewVisible.value = albumState.value === "preview";
|
||||
});
|
||||
|
||||
onBackPress(() => {
|
||||
if (!previewVisible.value) return false;
|
||||
closePreview();
|
||||
return true;
|
||||
});
|
||||
|
||||
onUnload(() => {
|
||||
clearTimeout(uploadNoticeTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.album-detail-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.album-detail-header,
|
||||
.album-detail-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.album-detail-content {
|
||||
padding: 22rpx 24rpx calc(48rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.album-heading {
|
||||
padding: 18rpx 12rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.album-heading text,
|
||||
.album-state-card__body text,
|
||||
.album-preview__footer text {
|
||||
display: block;
|
||||
}
|
||||
.album-heading__eyebrow,
|
||||
.album-state-card__eyebrow {
|
||||
color: $brand-red;
|
||||
font-size: 22rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.album-heading__title,
|
||||
.album-state-card__title {
|
||||
margin-top: 8rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.album-heading__copy,
|
||||
.album-state-card__copy {
|
||||
margin-top: 10rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.album-heading__count {
|
||||
margin-top: 12rpx;
|
||||
color: #806a51;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.album-wall {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.album-photo-tile {
|
||||
position: relative;
|
||||
min-height: 44px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid rgba(179, 133, 63, 0.62);
|
||||
background: rgba(245, 238, 226, 0.86);
|
||||
box-shadow: 0 8rpx 18rpx rgba(91, 60, 32, 0.12);
|
||||
}
|
||||
.album-photo-tile--hero {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
.album-photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14rpx;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
.album-photo-tile--grid {
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
.album-hero-photo,
|
||||
.album-photo-tile__image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.album-photo-tile__caption {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 10rpx 12rpx;
|
||||
background: rgba(48, 35, 25, 0.72);
|
||||
color: #fffaf0;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.album-upload-action {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.album-empty-state,
|
||||
.album-expired-state {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 430rpx;
|
||||
box-sizing: border-box;
|
||||
border: 20rpx solid transparent;
|
||||
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
|
||||
border-image-slice: 72 fill;
|
||||
border-image-width: 64rpx;
|
||||
border-image-repeat: stretch;
|
||||
}
|
||||
.album-state-card__body {
|
||||
padding: 104rpx 40rpx 62rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.album-state-card__body > .app-button {
|
||||
margin-top: 34rpx;
|
||||
}
|
||||
.album-preview {
|
||||
position: fixed;
|
||||
z-index: 80;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(24, 17, 13, 0.96);
|
||||
}
|
||||
.album-preview__toolbar {
|
||||
display: flex;
|
||||
min-height: 96rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: calc(env(safe-area-inset-top) + 12rpx) 24rpx 12rpx;
|
||||
}
|
||||
.album-preview__title {
|
||||
color: #fffaf0;
|
||||
font-size: 29rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.album-preview__close {
|
||||
display: flex;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fffaf0;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
.album-preview__image {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.album-preview__footer {
|
||||
padding: 20rpx 28rpx calc(30rpx + env(safe-area-inset-bottom));
|
||||
color: #fffaf0;
|
||||
text-align: center;
|
||||
}
|
||||
.album-preview__position {
|
||||
font-size: 23rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.album-preview__caption {
|
||||
margin-top: 8rpx;
|
||||
color: rgba(255, 250, 240, 0.78);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
@media (max-width: 340px) {
|
||||
.album-detail-content {
|
||||
padding-right: 16rpx;
|
||||
padding-left: 16rpx;
|
||||
}
|
||||
.album-photo-grid {
|
||||
gap: 10rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,656 @@
|
||||
<!-- 页面编号:F-09;用途:图片预览、上传与失败状态。 -->
|
||||
<template><ModulePage page-id="f09" /></template>
|
||||
<!-- 页面编号:F-09;用途:照片选择、说明、上传进度与失败重试视觉候选。 -->
|
||||
<template>
|
||||
<view
|
||||
class="media-upload-page"
|
||||
:class="`media-upload-state--${uploadState}`"
|
||||
>
|
||||
<ModulePageBackground module="family" />
|
||||
<view class="media-upload-header"><PageHeader title="上传照片" /></view>
|
||||
|
||||
<view class="media-upload-content">
|
||||
<view class="media-album-card">
|
||||
<image
|
||||
class="media-album-card__cover"
|
||||
:src="mockLibrary[0].src"
|
||||
:alt="mockLibrary[0].alt"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="media-album-card__copy">
|
||||
<text class="media-album-card__eyebrow">当前相册</text>
|
||||
<text class="media-album-card__title">2024 春节团圆</text>
|
||||
<text class="media-album-card__limit">最多可选择 9 张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="uploadState === 'permission'" class="media-permission-card">
|
||||
<text class="media-state-card__eyebrow">照片访问权限</text>
|
||||
<text class="media-state-card__title">需要照片访问权限</text>
|
||||
<text class="media-state-card__copy"
|
||||
>授权后才能选择要加入当前相册的照片;当前仅展示 H5 审核状态。</text
|
||||
>
|
||||
<view class="media-permission-action" @click="selectMockPhotos">
|
||||
<AppButton block label="重新授权" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="uploadState === 'success'" class="media-success-card">
|
||||
<text class="media-state-card__eyebrow">上传结果</text>
|
||||
<text class="media-success-title">4 张照片已上传</text>
|
||||
<text class="media-state-card__copy"
|
||||
>照片已加入“2024 春节团圆”,可以返回相册继续查看。</text
|
||||
>
|
||||
<view class="media-success-action" @click="returnToAlbum">
|
||||
<AppButton block label="返回当前相册" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view class="media-section-heading">
|
||||
<text>选择照片</text>
|
||||
<text>{{ selectedPhotos.length }} / {{ MAX_PHOTOS }}</text>
|
||||
</view>
|
||||
|
||||
<view class="media-photo-grid">
|
||||
<view
|
||||
v-for="(photo, index) in selectedPhotos"
|
||||
:key="photo.id"
|
||||
class="media-photo-tile"
|
||||
:class="{
|
||||
'media-photo-tile--active': index === activePhotoIndex,
|
||||
'media-photo-tile--failed': photo.status === 'error',
|
||||
}"
|
||||
@click="selectPhoto(index)"
|
||||
>
|
||||
<image
|
||||
class="media-photo-tile__image"
|
||||
:src="photo.src"
|
||||
:alt="photo.alt"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text class="media-photo-order">{{ index + 1 }}</text>
|
||||
<text
|
||||
v-if="index === activePhotoIndex"
|
||||
class="media-photo-current"
|
||||
>当前</text
|
||||
>
|
||||
<view
|
||||
v-if="!isLocked"
|
||||
class="media-photo-remove"
|
||||
@click.stop="removePhoto(index)"
|
||||
>
|
||||
<text>删除</text>
|
||||
</view>
|
||||
<text
|
||||
v-if="photo.status === 'error'"
|
||||
class="media-photo-status media-photo-status--error"
|
||||
>上传失败</text
|
||||
>
|
||||
<text
|
||||
v-else-if="photo.status === 'uploading'"
|
||||
class="media-photo-status media-photo-status--uploading"
|
||||
>{{ photo.progress }}%</text
|
||||
>
|
||||
<text
|
||||
v-else-if="photo.status === 'uploaded'"
|
||||
class="media-photo-status media-photo-status--uploaded"
|
||||
>已上传</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="selectedPhotos.length < MAX_PHOTOS && !isLocked"
|
||||
class="media-add-tile"
|
||||
@click="selectedPhotos.length ? addMockPhoto() : selectMockPhotos()"
|
||||
>
|
||||
<text class="media-add-tile__title">{{
|
||||
selectedPhotos.length ? "继续添加" : "选择照片"
|
||||
}}</text>
|
||||
<text class="media-add-tile__copy">{{
|
||||
selectedPhotos.length ? `还可选择 ${MAX_PHOTOS - selectedPhotos.length} 张` : "最多 9 张"
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="selectedPhotos.length" class="media-description-panel">
|
||||
<view class="media-batch-field">
|
||||
<view class="media-field-heading">
|
||||
<text>整批说明</text><text>必填</text>
|
||||
</view>
|
||||
<textarea
|
||||
v-model="batchDescription"
|
||||
:disabled="isLocked"
|
||||
maxlength="120"
|
||||
placeholder="例如:2024 年春节全家团圆留影"
|
||||
placeholder-class="media-field-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="validationMessage" class="media-field-error">{{
|
||||
validationMessage
|
||||
}}</text>
|
||||
|
||||
<view v-if="activePhoto" class="media-photo-field">
|
||||
<view class="media-photo-editor">
|
||||
<image
|
||||
class="media-photo-editor__thumb"
|
||||
:src="activePhoto.src"
|
||||
:alt="activePhoto.alt"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="media-photo-editor__copy">
|
||||
<text class="media-photo-editor__position"
|
||||
>第 {{ activePhotoIndex + 1 }} 张 / 共
|
||||
{{ selectedPhotos.length }} 张</text
|
||||
>
|
||||
<text class="media-photo-editor__hint"
|
||||
>正在编辑此照片的说明</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view class="media-field-heading">
|
||||
<text>当前照片说明</text><text>选填</text>
|
||||
</view>
|
||||
<textarea
|
||||
:value="activePhoto.note"
|
||||
:disabled="isLocked"
|
||||
maxlength="80"
|
||||
placeholder="补充人物、时间或拍摄地点"
|
||||
placeholder-class="media-field-placeholder"
|
||||
@input="updateActiveNote"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-if="uploadState === 'uploading'">
|
||||
<text class="media-progress-copy"
|
||||
>正在上传 2/{{ selectedPhotos.length }}</text
|
||||
>
|
||||
<view class="media-progress-action">
|
||||
<AppButton block label="正在上传" />
|
||||
</view>
|
||||
</template>
|
||||
<view
|
||||
v-else-if="uploadState === 'error'"
|
||||
class="media-retry-action"
|
||||
@click="retryFailed"
|
||||
>
|
||||
<AppButton block label="重试 2 张失败照片" />
|
||||
</view>
|
||||
<view v-else class="media-primary-action" @click="startUpload">
|
||||
<AppButton
|
||||
block
|
||||
:label="
|
||||
selectedPhotos.length
|
||||
? `上传 ${selectedPhotos.length} 张照片`
|
||||
: '选择照片'
|
||||
"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const MAX_PHOTOS = 9;
|
||||
const uploadState = ref("initial");
|
||||
const activePhotoIndex = ref(0);
|
||||
const batchDescription = ref("");
|
||||
const validationMessage = ref("");
|
||||
|
||||
const mockLibrary = [
|
||||
{
|
||||
id: "reunion",
|
||||
src: "/static/assets/modules/family/f08/f08-reunion-hero.png",
|
||||
alt: "春节团圆时三代家人的合影",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
id: "portrait",
|
||||
src: "/static/assets/modules/family/f08/f08-family-portrait.png",
|
||||
alt: "家人在院落前的春节合影",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
id: "table",
|
||||
src: "/static/assets/modules/family/f08/f08-reunion-table.png",
|
||||
alt: "家人围坐吃年夜饭",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
id: "home",
|
||||
src: "/static/assets/modules/family/f08/f08-ancestral-home.png",
|
||||
alt: "祖居院落的复古旧照",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
id: "ancestor",
|
||||
src: "/static/assets/modules/family/f08/f08-ancestral-portrait.png",
|
||||
alt: "老一辈家人在祖居门前的合影",
|
||||
note: "",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedPhotos = ref([]);
|
||||
const activePhoto = computed(
|
||||
() => selectedPhotos.value[activePhotoIndex.value] || null,
|
||||
);
|
||||
const isLocked = computed(() => uploadState.value === "uploading");
|
||||
|
||||
const makeSelectedPhotos = (state) =>
|
||||
mockLibrary.slice(0, 4).map((photo, index) => ({
|
||||
...photo,
|
||||
note: "",
|
||||
progress: state === "uploading" ? [100, 62, 35, 0][index] : 0,
|
||||
status:
|
||||
state === "error"
|
||||
? index < 2
|
||||
? "error"
|
||||
: "uploaded"
|
||||
: state,
|
||||
}));
|
||||
|
||||
const selectMockPhotos = () => {
|
||||
selectedPhotos.value = makeSelectedPhotos("selected");
|
||||
activePhotoIndex.value = 0;
|
||||
validationMessage.value = "";
|
||||
uploadState.value = "selected";
|
||||
};
|
||||
|
||||
const selectPhoto = (index) => {
|
||||
if (!isLocked.value) activePhotoIndex.value = index;
|
||||
};
|
||||
|
||||
const removePhoto = (index) => {
|
||||
if (isLocked.value) return;
|
||||
selectedPhotos.value.splice(index, 1);
|
||||
activePhotoIndex.value = Math.max(
|
||||
0,
|
||||
Math.min(activePhotoIndex.value, selectedPhotos.value.length - 1),
|
||||
);
|
||||
};
|
||||
|
||||
const addMockPhoto = () => {
|
||||
if (isLocked.value || selectedPhotos.value.length >= MAX_PHOTOS) return;
|
||||
const nextIndex = selectedPhotos.value.length;
|
||||
const next = mockLibrary[nextIndex % mockLibrary.length];
|
||||
selectedPhotos.value.push({
|
||||
...next,
|
||||
id: `${next.id}-${nextIndex + 1}`,
|
||||
note: "",
|
||||
progress: 0,
|
||||
status: "selected",
|
||||
});
|
||||
};
|
||||
|
||||
const updateActiveNote = (event) => {
|
||||
if (!isLocked.value && activePhoto.value) {
|
||||
activePhoto.value.note = event.detail.value;
|
||||
}
|
||||
};
|
||||
|
||||
const startUpload = () => {
|
||||
if (!selectedPhotos.value.length) {
|
||||
selectMockPhotos();
|
||||
return;
|
||||
}
|
||||
if (!batchDescription.value.trim()) {
|
||||
validationMessage.value = "请填写整批照片说明";
|
||||
return;
|
||||
}
|
||||
validationMessage.value = "";
|
||||
selectedPhotos.value = selectedPhotos.value.map((photo, index) => ({
|
||||
...photo,
|
||||
progress: [100, 62, 35, 0][index] || 0,
|
||||
status: "uploading",
|
||||
}));
|
||||
uploadState.value = "uploading";
|
||||
};
|
||||
|
||||
const retryFailed = () => {
|
||||
selectedPhotos.value = selectedPhotos.value.map((photo, index) => ({
|
||||
...photo,
|
||||
progress: [100, 62, 35, 0][index] || 0,
|
||||
status: "uploading",
|
||||
}));
|
||||
uploadState.value = "uploading";
|
||||
};
|
||||
|
||||
const returnToAlbum = () => {
|
||||
uni.redirectTo({ url: "/pages/family/f08-album-detail" });
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
uploadState.value = ["permission", "selected", "uploading", "error", "success"].includes(query.state)
|
||||
? query.state
|
||||
: "initial";
|
||||
|
||||
if (!["initial", "permission"].includes(uploadState.value)) {
|
||||
selectedPhotos.value = makeSelectedPhotos(uploadState.value);
|
||||
batchDescription.value = "春节团圆照片整理";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-upload-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.media-upload-header,
|
||||
.media-upload-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.media-upload-content {
|
||||
padding: 20rpx 24rpx calc(48rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.media-album-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
min-height: 154rpx;
|
||||
box-sizing: border-box;
|
||||
grid-template-columns: 132rpx minmax(0, 1fr);
|
||||
gap: 20rpx;
|
||||
align-items: center;
|
||||
padding: 14rpx 20rpx;
|
||||
border: 20rpx solid transparent;
|
||||
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
|
||||
border-image-slice: 72 fill;
|
||||
border-image-width: 58rpx;
|
||||
border-image-repeat: stretch;
|
||||
}
|
||||
.media-album-card__cover {
|
||||
width: 132rpx;
|
||||
height: 132rpx;
|
||||
border: 2rpx solid rgba(179, 133, 63, 0.62);
|
||||
box-shadow: 0 6rpx 14rpx rgba(91, 60, 32, 0.12);
|
||||
}
|
||||
.media-album-card__copy text,
|
||||
.media-state-card__eyebrow,
|
||||
.media-state-card__title,
|
||||
.media-state-card__copy,
|
||||
.media-success-title,
|
||||
.media-photo-status,
|
||||
.media-progress-copy {
|
||||
display: block;
|
||||
}
|
||||
.media-album-card__eyebrow {
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.media-album-card__title {
|
||||
margin-top: 7rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-album-card__limit {
|
||||
margin-top: 7rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
.media-section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 24rpx 8rpx 12rpx;
|
||||
color: $brand-red;
|
||||
font-size: 25rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-section-heading text:last-child {
|
||||
color: $ink-muted;
|
||||
font-size: 21rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
.media-photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12rpx;
|
||||
}
|
||||
.media-photo-tile,
|
||||
.media-add-tile {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
aspect-ratio: 1;
|
||||
border: 2rpx solid rgba(179, 133, 63, 0.62);
|
||||
background: rgba(247, 241, 231, 0.88);
|
||||
}
|
||||
.media-photo-tile--active {
|
||||
outline: 4rpx solid rgba(159, 44, 35, 0.78);
|
||||
outline-offset: -4rpx;
|
||||
}
|
||||
.media-photo-tile--failed {
|
||||
border-style: dashed;
|
||||
border-color: #9f2c23;
|
||||
}
|
||||
.media-photo-tile__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.media-photo-order,
|
||||
.media-photo-current,
|
||||
.media-photo-status {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
color: #fffaf0;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.media-photo-order {
|
||||
top: 7rpx;
|
||||
left: 7rpx;
|
||||
min-width: 34rpx;
|
||||
padding: 3rpx 7rpx;
|
||||
border-radius: 999px;
|
||||
background: rgba(43, 31, 23, 0.78);
|
||||
text-align: center;
|
||||
}
|
||||
.media-photo-current {
|
||||
bottom: 7rpx;
|
||||
left: 7rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
background: rgba(74, 45, 27, 0.84);
|
||||
}
|
||||
.media-photo-remove {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
color: #fffaf0;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.media-photo-remove text {
|
||||
padding: 7rpx 8rpx;
|
||||
background: rgba(145, 36, 29, 0.88);
|
||||
}
|
||||
.media-photo-status {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 7rpx 8rpx;
|
||||
background: rgba(58, 43, 32, 0.82);
|
||||
text-align: center;
|
||||
}
|
||||
.media-photo-status--error {
|
||||
background: rgba(132, 35, 29, 0.92);
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-photo-status--uploaded {
|
||||
background: rgba(55, 82, 51, 0.88);
|
||||
}
|
||||
.media-add-tile {
|
||||
display: flex;
|
||||
min-height: 44px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-style: dashed;
|
||||
color: $brand-red;
|
||||
text-align: center;
|
||||
}
|
||||
.media-add-tile__title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-add-tile__copy {
|
||||
margin-top: 6rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 19rpx;
|
||||
}
|
||||
.media-description-panel {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
.media-batch-field,
|
||||
.media-photo-field {
|
||||
padding: 18rpx 20rpx 20rpx;
|
||||
border: 2rpx solid rgba(179, 133, 63, 0.55);
|
||||
background: rgba(250, 246, 237, 0.76);
|
||||
}
|
||||
.media-photo-field {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.media-photo-editor {
|
||||
display: grid;
|
||||
grid-template-columns: 78rpx minmax(0, 1fr);
|
||||
gap: 14rpx;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 14rpx;
|
||||
border-bottom: 1px solid rgba(179, 133, 63, 0.32);
|
||||
}
|
||||
.media-photo-editor__thumb {
|
||||
width: 78rpx;
|
||||
height: 78rpx;
|
||||
border: 2rpx solid rgba(159, 44, 35, 0.62);
|
||||
}
|
||||
.media-photo-editor__copy text {
|
||||
display: block;
|
||||
}
|
||||
.media-photo-editor__position {
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-photo-editor__hint {
|
||||
margin-top: 6rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.media-field-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-field-heading text:last-child {
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.media-batch-field textarea,
|
||||
.media-photo-field textarea {
|
||||
width: 100%;
|
||||
height: 92rpx;
|
||||
min-height: 92rpx;
|
||||
margin-top: 12rpx;
|
||||
box-sizing: border-box;
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.media-field-placeholder {
|
||||
color: #9e8e79;
|
||||
}
|
||||
.media-field-error {
|
||||
display: block;
|
||||
margin: 9rpx 6rpx 0;
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.media-primary-action,
|
||||
.media-retry-action,
|
||||
.media-progress-action {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.media-progress-copy {
|
||||
margin-top: 22rpx;
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.media-permission-card,
|
||||
.media-success-card {
|
||||
position: relative;
|
||||
min-height: 410rpx;
|
||||
margin-top: 24rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 98rpx 46rpx 58rpx;
|
||||
border: 20rpx solid transparent;
|
||||
border-image-source: url("/static/assets/modules/family/transparent/module-content-frame.png");
|
||||
border-image-slice: 72 fill;
|
||||
border-image-width: 64rpx;
|
||||
border-image-repeat: stretch;
|
||||
text-align: center;
|
||||
}
|
||||
.media-state-card__eyebrow {
|
||||
color: $brand-red;
|
||||
font-size: 22rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.media-state-card__title,
|
||||
.media-success-title {
|
||||
margin-top: 12rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 35rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.media-state-card__copy {
|
||||
margin-top: 16rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.media-permission-action,
|
||||
.media-success-action {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
@media (max-width: 340px) {
|
||||
.media-upload-content {
|
||||
padding-right: 16rpx;
|
||||
padding-left: 16rpx;
|
||||
}
|
||||
.media-photo-grid {
|
||||
gap: 8rpx;
|
||||
}
|
||||
.media-album-card {
|
||||
grid-template-columns: 116rpx minmax(0, 1fr);
|
||||
gap: 14rpx;
|
||||
}
|
||||
.media-album-card__cover {
|
||||
width: 116rpx;
|
||||
height: 116rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,143 @@
|
||||
<!-- 页面编号:F-10;用途:家族视频列表、详情与待开放状态。 -->
|
||||
<template><ModulePage page-id="f10" /></template>
|
||||
<!-- 页面编号:F-10;用途:家族视频待开放状态。 -->
|
||||
<template>
|
||||
<view class="video-status-page">
|
||||
<ModulePageBackground module="family" />
|
||||
<view class="video-status-header"><PageHeader title="家族视频" /></view>
|
||||
|
||||
<view class="video-status-content">
|
||||
<view class="video-status-lead">
|
||||
<image
|
||||
src="/static/assets/modules/genealogy/transparent/section-divider.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text>记录会动的家族记忆</text>
|
||||
</view>
|
||||
|
||||
<view class="video-status-card">
|
||||
<image
|
||||
class="video-status-card__skin"
|
||||
src="/static/assets/modules/family/transparent/module-content-frame.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view class="video-status-card__body">
|
||||
<text class="video-status-card__eyebrow">视频 · 服务说明</text>
|
||||
<text class="video-status-card__title">视频服务暂未开放</text>
|
||||
<text class="video-status-card__copy"
|
||||
>开放后可在这里浏览家族影像与纪念视频</text
|
||||
>
|
||||
<view class="video-return-action" @click="returnToFamily">
|
||||
<AppButton block label="返回家族首页" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const returnToFamily = () => {
|
||||
uni.reLaunch({ url: "/pages/family/f01-family-feed" });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.video-status-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.video-status-header,
|
||||
.video-status-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.video-status-content {
|
||||
padding: 16rpx 26rpx 56rpx;
|
||||
}
|
||||
.video-status-lead {
|
||||
position: relative;
|
||||
height: 56rpx;
|
||||
margin: 0 12rpx 18rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.video-status-lead image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.video-status-lead text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.video-status-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 330rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.video-status-card__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.video-status-card__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 70rpx 60rpx 48rpx;
|
||||
}
|
||||
.video-status-card__eyebrow,
|
||||
.video-status-card__title,
|
||||
.video-status-card__copy {
|
||||
display: block;
|
||||
}
|
||||
.video-status-card__eyebrow {
|
||||
color: $brand-red;
|
||||
font-size: 23rpx;
|
||||
letter-spacing: 3rpx;
|
||||
}
|
||||
.video-status-card__title {
|
||||
margin-top: 14rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 35rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.video-status-card__copy {
|
||||
margin-top: 17rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.video-return-action {
|
||||
margin: 28rpx auto 0;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.video-status-content {
|
||||
padding-right: 34rpx;
|
||||
padding-left: 34rpx;
|
||||
}
|
||||
}
|
||||
@media (max-width: 340px) {
|
||||
.video-status-content {
|
||||
padding-right: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
.video-status-card__body {
|
||||
padding-right: 44rpx;
|
||||
padding-left: 44rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user