Review changes batch 6 of 6
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<view
|
||||
class="app-button"
|
||||
:class="[
|
||||
`app-button--${type}`,
|
||||
{ 'app-button--block': block, 'app-button--disabled': disabled },
|
||||
]"
|
||||
:hover-class="disabled ? 'none' : 'app-button--pressed'"
|
||||
@click="handleClick"
|
||||
>
|
||||
<image class="app-button__skin" :src="skin" mode="aspectFit" />
|
||||
<text class="app-button__label"
|
||||
><slot>{{ label }}</slot></text
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
label: { type: String, default: "" },
|
||||
type: { type: String, default: "primary" },
|
||||
block: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
const emit = defineEmits(["click"]);
|
||||
|
||||
const skin = computed(() =>
|
||||
props.type === "secondary"
|
||||
? "/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
|
||||
: "/static/assets/foundation/transparent/a01-scroll-primary-v3.png",
|
||||
);
|
||||
|
||||
const handleClick = (event) => {
|
||||
if (!props.disabled) emit("click", event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-button {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 420rpx;
|
||||
max-width: 100%;
|
||||
min-height: 88rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app-button--block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.app-button__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.app-button__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 0 38rpx;
|
||||
color: #fffaf0;
|
||||
font-size: 29rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 3rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.app-button--secondary .app-button__label {
|
||||
color: #5c4330;
|
||||
}
|
||||
.app-button--disabled {
|
||||
opacity: 0.48;
|
||||
}
|
||||
.app-button--pressed {
|
||||
opacity: 0.78;
|
||||
transform: translateY(1rpx);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="visible"
|
||||
class="app-dialog-layer"
|
||||
@click="closeOnMask && cancel()"
|
||||
>
|
||||
<view class="app-dialog" @click.stop>
|
||||
<image
|
||||
class="app-dialog__skin"
|
||||
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="app-dialog__content">
|
||||
<text v-if="eyebrow" class="app-dialog__eyebrow">{{ eyebrow }}</text>
|
||||
<text class="app-dialog__title">{{ title }}</text>
|
||||
<text v-if="message" class="app-dialog__message">{{ message }}</text>
|
||||
<slot />
|
||||
<view
|
||||
class="app-dialog__actions"
|
||||
:class="{ 'app-dialog__actions--single': !showCancel }"
|
||||
>
|
||||
<AppButton
|
||||
v-if="showCancel"
|
||||
type="secondary"
|
||||
:label="cancelText"
|
||||
@click="cancel"
|
||||
/>
|
||||
<AppButton :label="confirmText" @click="$emit('confirm')" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
|
||||
defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
eyebrow: { type: String, default: "" },
|
||||
title: { type: String, required: true },
|
||||
message: { type: String, default: "" },
|
||||
confirmText: { type: String, default: "我知道了" },
|
||||
cancelText: { type: String, default: "取消" },
|
||||
showCancel: { type: Boolean, default: false },
|
||||
closeOnMask: { type: Boolean, default: true },
|
||||
});
|
||||
const emit = defineEmits(["confirm", "cancel", "close"]);
|
||||
const cancel = () => {
|
||||
emit("cancel");
|
||||
emit("close");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-dialog-layer {
|
||||
position: fixed;
|
||||
z-index: 80;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
padding: 40rpx;
|
||||
background: rgba(35, 18, 10, 0.62);
|
||||
}
|
||||
.app-dialog {
|
||||
position: relative;
|
||||
width: 650rpx;
|
||||
max-width: 100%;
|
||||
min-height: 520rpx;
|
||||
}
|
||||
.app-dialog__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.app-dialog__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
min-height: 520rpx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 74rpx 66rpx 54rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.app-dialog__eyebrow {
|
||||
color: #9f170f;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
.app-dialog__title {
|
||||
color: #8f160f;
|
||||
font-size: 42rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
.app-dialog__eyebrow + .app-dialog__title {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.app-dialog__message {
|
||||
margin-top: 22rpx;
|
||||
color: #513a28;
|
||||
font-size: 27rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.app-dialog__actions {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16rpx;
|
||||
margin-top: auto;
|
||||
}
|
||||
.app-dialog__actions--single {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.app-dialog__actions .app-button {
|
||||
width: 100%;
|
||||
min-height: 82rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<view class="app-loading" :class="`app-loading--${variant}`">
|
||||
<view class="app-loading__emblem" aria-hidden="true">
|
||||
<image
|
||||
class="app-loading__seal"
|
||||
src="/static/assets/foundation/transparent/brand-seal.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<image
|
||||
class="app-loading__knot"
|
||||
src="/static/assets/foundation/transparent/auth-divider-knot.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="app-loading__copy">{{ text }}</text>
|
||||
<text v-if="description" class="app-loading__description">{{
|
||||
description
|
||||
}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
variant: {
|
||||
type: String,
|
||||
default: "page",
|
||||
validator: (value) => ["page", "section"].includes(value),
|
||||
},
|
||||
text: { type: String, default: "正在展开,请稍候…" },
|
||||
description: { type: String, default: "" },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-loading {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #62584c;
|
||||
text-align: center;
|
||||
}
|
||||
.app-loading__emblem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.app-loading__seal {
|
||||
animation: app-loading-seal-breathe 1.6s ease-in-out infinite;
|
||||
}
|
||||
.app-loading__knot {
|
||||
margin-top: 8rpx;
|
||||
opacity: 0.72;
|
||||
animation: app-loading-knot-breathe 1.6s ease-in-out infinite;
|
||||
}
|
||||
.app-loading__copy {
|
||||
font-weight: 500;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.app-loading__description {
|
||||
color: #62584c;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.app-loading--page {
|
||||
min-height: 320rpx;
|
||||
}
|
||||
.app-loading--page .app-loading__seal {
|
||||
width: 132rpx;
|
||||
height: 136rpx;
|
||||
}
|
||||
.app-loading--page .app-loading__knot {
|
||||
width: 56rpx;
|
||||
height: 18rpx;
|
||||
}
|
||||
.app-loading--page .app-loading__copy {
|
||||
margin-top: 22rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.app-loading--page .app-loading__description {
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.app-loading--section {
|
||||
min-height: 180rpx;
|
||||
}
|
||||
.app-loading--section .app-loading__seal {
|
||||
width: 88rpx;
|
||||
height: 90rpx;
|
||||
}
|
||||
.app-loading--section .app-loading__knot {
|
||||
width: 42rpx;
|
||||
height: 14rpx;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
.app-loading--section .app-loading__copy {
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.app-loading--section .app-loading__description {
|
||||
margin-top: 10rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
@keyframes app-loading-seal-breathe {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.82;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes app-loading-knot-breathe {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.44;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.76;
|
||||
}
|
||||
}
|
||||
@keyframes app-loading-seal-essential-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.58;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes app-loading-knot-essential-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.28;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.86;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.app-loading__seal {
|
||||
animation: app-loading-seal-essential-pulse 1.2s ease-in-out infinite;
|
||||
transform: none;
|
||||
}
|
||||
.app-loading__knot {
|
||||
animation: app-loading-knot-essential-pulse 1.2s ease-in-out 0.2s infinite;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+44
-11
@@ -1,25 +1,55 @@
|
||||
<!-- 公共组件:根页面底部导航;仅维护家谱、家族、我的三项已确认入口及其透明图标。 -->
|
||||
<template>
|
||||
<view class="app-tabbar">
|
||||
<view v-for="item in items" :key="item.key" class="tab-item" @click="switchTab(item)">
|
||||
<image class="tab-icon" :src="active === item.key ? item.activeIcon : item.icon" mode="aspectFit" />
|
||||
<text :class="['tab-label', { active: active === item.key }]">{{ item.label }}</text>
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
class="tab-item"
|
||||
@click="switchTab(item)"
|
||||
>
|
||||
<image
|
||||
class="tab-icon"
|
||||
:src="active === item.key ? item.activeIcon : item.icon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text :class="['tab-label', { active: active === item.key }]">{{
|
||||
item.label
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({ active: { type: String, required: true } })
|
||||
const props = defineProps({ active: { type: String, required: true } });
|
||||
|
||||
const items = [
|
||||
{ key: 'genealogy', label: '家谱', path: '/pages/genealogy/g01-my-genealogies', icon: '/static/assets/foundation/transparent/tab-genealogy.png', activeIcon: '/static/assets/foundation/transparent/tab-genealogy-active.png' },
|
||||
{ key: 'family', label: '家族', path: '/pages/family/f01-family-feed', icon: '/static/assets/foundation/transparent/tab-family.png', activeIcon: '/static/assets/foundation/transparent/tab-family-active.png' },
|
||||
{ key: 'profile', label: '我的', path: '/pages/profile/m01-profile-home', icon: '/static/assets/foundation/transparent/tab-profile.png', activeIcon: '/static/assets/foundation/transparent/tab-profile-active.png' }
|
||||
]
|
||||
{
|
||||
key: "genealogy",
|
||||
label: "家谱",
|
||||
path: "/pages/genealogy/g01-my-genealogies",
|
||||
icon: "/static/assets/foundation/transparent/tab-genealogy.png",
|
||||
activeIcon:
|
||||
"/static/assets/foundation/transparent/tab-genealogy-active.png",
|
||||
},
|
||||
{
|
||||
key: "family",
|
||||
label: "家族",
|
||||
path: "/pages/family/f01-family-feed",
|
||||
icon: "/static/assets/foundation/transparent/tab-family.png",
|
||||
activeIcon: "/static/assets/foundation/transparent/tab-family-active.png",
|
||||
},
|
||||
{
|
||||
key: "profile",
|
||||
label: "我的",
|
||||
path: "/pages/profile/m01-profile-home",
|
||||
icon: "/static/assets/foundation/transparent/tab-profile.png",
|
||||
activeIcon: "/static/assets/foundation/transparent/tab-profile-active.png",
|
||||
},
|
||||
];
|
||||
|
||||
const switchTab = (item) => {
|
||||
if (item.key !== props.active) uni.reLaunch({ url: item.path })
|
||||
}
|
||||
if (item.key !== props.active) uni.reLaunch({ url: item.path });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -61,5 +91,8 @@ const switchTab = (item) => {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tab-label.active { color: $brand-red; font-weight: 700; }
|
||||
.tab-label.active {
|
||||
color: $brand-red;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view v-if="visible" class="app-toast" aria-live="polite">
|
||||
<text class="app-toast__copy">{{ message }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
message: { type: String, default: "" },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-toast {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: calc(env(safe-area-inset-top) + 24rpx);
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 590rpx;
|
||||
max-width: calc(100vw - 48rpx);
|
||||
min-height: 82rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border: 16rpx solid transparent;
|
||||
border-width: 16rpx 74rpx;
|
||||
border-image-source: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png");
|
||||
border-image-slice: 58 280 fill;
|
||||
border-image-width: 16rpx 74rpx;
|
||||
border-image-repeat: stretch;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.app-toast__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 14rpx 30rpx;
|
||||
color: #5c4330;
|
||||
font-size: 25rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -34,6 +34,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-detail-row">
|
||||
<view class="card-updated">
|
||||
<text>更新于 {{ genealogy.updatedAt }}</text>
|
||||
</view>
|
||||
<view class="card-metas">
|
||||
<view class="card-meta-item">
|
||||
<image
|
||||
@@ -52,9 +55,6 @@
|
||||
<text class="card-meta">{{ genealogy.memberCount }} 位成员</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-updated">
|
||||
<text>更新于 {{ genealogy.updatedAt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -73,7 +73,7 @@ defineEmits(["select"]);
|
||||
.genealogy-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 156rpx;
|
||||
min-height: 178rpx;
|
||||
align-items: center;
|
||||
padding: 18rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
@@ -177,25 +177,34 @@ defineEmits(["select"]);
|
||||
.card-detail-row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.card-metas {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.card-meta-item {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
margin-right: 14rpx;
|
||||
margin-right: 0;
|
||||
}
|
||||
.card-meta-item + .card-meta-item {
|
||||
margin-left: 18rpx;
|
||||
}
|
||||
.card-meta-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 6rpx;
|
||||
opacity: 1;
|
||||
filter: saturate(1.35) brightness(0.82) contrast(1.15);
|
||||
}
|
||||
.card-meta {
|
||||
color: #62584c;
|
||||
@@ -204,9 +213,12 @@ defineEmits(["select"]);
|
||||
}
|
||||
.card-updated {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-left: 10rpx;
|
||||
justify-content: flex-end;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
color: #62584c;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<view class="genealogy-page-background">
|
||||
<image
|
||||
class="genealogy-page-background__art"
|
||||
src="/static/assets/modules/genealogy/opaque/genealogy-page-background-long.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.genealogy-page-background {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
background: #e7ded1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.genealogy-page-background__art {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
opacity: 0.28;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view
|
||||
class="module-page-background"
|
||||
:class="`module-page-background--${module}`"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<image
|
||||
class="module-page-background__image"
|
||||
:src="source"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
module: { type: String, required: true },
|
||||
});
|
||||
|
||||
const sources = {
|
||||
tree: "/static/assets/modules/tree/opaque/tree-page-background-long.png",
|
||||
family:
|
||||
"/static/assets/modules/family/opaque/family-page-background-long.png",
|
||||
records:
|
||||
"/static/assets/modules/records/opaque/records-page-background-long.png",
|
||||
notification:
|
||||
"/static/assets/modules/notification/opaque/notification-page-background-long.png",
|
||||
profile:
|
||||
"/static/assets/modules/profile/opaque/profile-page-background-long.png",
|
||||
};
|
||||
const source = computed(() => sources[props.module] || sources.profile);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.module-page-background {
|
||||
position: fixed;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: #f5eee2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.module-page-background__image {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 0.36;
|
||||
}
|
||||
.module-page-background--family .module-page-background__image {
|
||||
opacity: 0.32;
|
||||
}
|
||||
.module-page-background--records .module-page-background__image {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.module-page-background--notification .module-page-background__image {
|
||||
opacity: 0.28;
|
||||
}
|
||||
.module-page-background--profile .module-page-background__image {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
+88
-24
@@ -2,25 +2,58 @@
|
||||
<template>
|
||||
<view class="page-header" :class="{ 'page-header--root': root }">
|
||||
<!-- 根页头部使用不透明朱砂底图与透明祠堂线稿两层完整资产。 -->
|
||||
<image v-if="root" class="header-texture" src="/static/assets/foundation/opaque/root-header-cinnabar.jpg" mode="scaleToFill" />
|
||||
<image v-if="root" class="header-hall" src="/static/assets/foundation/transparent/root-header-hall.png" mode="aspectFit" />
|
||||
<image
|
||||
v-if="root"
|
||||
class="header-texture"
|
||||
src="/static/assets/foundation/opaque/root-header-cinnabar.jpg"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<image
|
||||
v-if="root"
|
||||
class="header-hall"
|
||||
src="/static/assets/foundation/transparent/root-header-hall.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="header-side header-side--left">
|
||||
<view v-if="root" class="header-icon-button" @click="$emit('brand')">
|
||||
<image class="header-logo" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
|
||||
<image
|
||||
class="header-logo"
|
||||
src="/static/assets/foundation/transparent/brand-seal.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="header-back" hover-class="header-back--pressed" @click="goBack">
|
||||
<image class="header-back__icon" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
|
||||
<view
|
||||
v-else
|
||||
class="header-back"
|
||||
hover-class="header-back--pressed"
|
||||
@click="goBack"
|
||||
>
|
||||
<image
|
||||
class="header-back__icon"
|
||||
src="/static/assets/foundation/transparent/chevron-right.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="header-title">{{ title }}</text>
|
||||
|
||||
<view class="header-side header-side--right">
|
||||
<view v-if="root" class="header-icon-button header-notice" @click="$emit('notice')">
|
||||
<image class="header-notice-icon" src="/static/assets/foundation/transparent/notice.png" mode="aspectFit" />
|
||||
<view
|
||||
v-if="root"
|
||||
class="header-icon-button header-notice"
|
||||
@click="$emit('notice')"
|
||||
>
|
||||
<image
|
||||
class="header-notice-icon"
|
||||
src="/static/assets/foundation/transparent/notice.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-if="unreadCount > 0" class="notice-dot"></view>
|
||||
</view>
|
||||
<view v-else class="header-action" @click="$emit('action')">{{ action }}</view>
|
||||
<view v-else class="header-action" @click="$emit('action')">{{
|
||||
action
|
||||
}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -28,12 +61,12 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: { type: String, required: true },
|
||||
action: { type: String, default: '' },
|
||||
action: { type: String, default: "" },
|
||||
root: { type: Boolean, default: false },
|
||||
unreadCount: { type: Number, default: 0 }
|
||||
})
|
||||
unreadCount: { type: Number, default: 0 },
|
||||
});
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
const goBack = () => uni.navigateBack();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -75,12 +108,15 @@ const goBack = () => uni.navigateBack()
|
||||
z-index: 1;
|
||||
width: 476rpx;
|
||||
height: 166rpx;
|
||||
opacity: .29;
|
||||
opacity: 0.29;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.page-header--root .header-side,
|
||||
.page-header--root .header-title { position: relative; z-index: 2; }
|
||||
.page-header--root .header-title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.header-side {
|
||||
display: flex;
|
||||
@@ -89,8 +125,12 @@ const goBack = () => uni.navigateBack()
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-side--left { justify-content: flex-start; }
|
||||
.header-side--right { justify-content: flex-end; }
|
||||
.header-side--left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.header-side--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-icon-button {
|
||||
position: relative;
|
||||
@@ -101,8 +141,14 @@ const goBack = () => uni.navigateBack()
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header-logo { width: 66rpx; height: 66rpx; }
|
||||
.header-notice-icon { width: 48rpx; height: 48rpx; }
|
||||
.header-logo {
|
||||
width: 66rpx;
|
||||
height: 66rpx;
|
||||
}
|
||||
.header-notice-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.notice-dot {
|
||||
position: absolute;
|
||||
@@ -120,10 +166,26 @@ const goBack = () => uni.navigateBack()
|
||||
font-size: 27rpx;
|
||||
}
|
||||
|
||||
.header-back { display: flex; width: 88rpx; height: 88rpx; align-items: center; justify-content: flex-start; }
|
||||
.header-back__icon { width: 42rpx; height: 42rpx; filter: brightness(0) invert(1); transform: scaleX(-1); }
|
||||
.header-back--pressed { opacity: .62; }
|
||||
.header-action { color: #ffe3a7; text-align: right; }
|
||||
.header-back {
|
||||
display: flex;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.header-back__icon {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
filter: brightness(0) invert(1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
.header-back--pressed {
|
||||
opacity: 0.62;
|
||||
}
|
||||
.header-action {
|
||||
color: #ffe3a7;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
@@ -140,7 +202,7 @@ const goBack = () => uni.navigateBack()
|
||||
|
||||
.page-header--root .header-title {
|
||||
color: #ffe3a7;
|
||||
font-family: 'STKaiti', 'KaiTi', serif;
|
||||
font-family: "STKaiti", "KaiTi", serif;
|
||||
font-size: 48rpx;
|
||||
letter-spacing: 5rpx;
|
||||
transform: translateY(2rpx);
|
||||
@@ -152,5 +214,7 @@ const goBack = () => uni.navigateBack()
|
||||
transform: translate(-6rpx, -6rpx);
|
||||
}
|
||||
|
||||
.page-header--root .header-notice { transform: translateY(4rpx); }
|
||||
.page-header--root .header-notice {
|
||||
transform: translateY(4rpx);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,51 @@
|
||||
<!-- T04-T06 共用成员表单:仅复用视觉与交互骨架,各页面通过 kind 拥有独立任务和文案。 -->
|
||||
<template>
|
||||
<view class="member-form-page" :class="{
|
||||
'form-state--form': formState === 'form',
|
||||
'form-state--success': formState === 'success',
|
||||
'form-state--conflict': formState === 'conflict',
|
||||
'form-state--error': formState === 'error'
|
||||
}">
|
||||
<view
|
||||
class="member-form-page"
|
||||
:class="{
|
||||
'form-state--form': formState === 'form',
|
||||
'form-state--success': formState === 'success',
|
||||
'form-state--conflict': formState === 'conflict',
|
||||
'form-state--error': formState === 'error',
|
||||
}"
|
||||
>
|
||||
<ModulePageBackground module="tree" />
|
||||
<view class="member-form-page__header"><PageHeader :title="config.pageTitle" /></view>
|
||||
<view class="member-form-page__header"
|
||||
><PageHeader :title="config.pageTitle"
|
||||
/></view>
|
||||
<view class="member-form-panel">
|
||||
<image class="member-form-panel__skin" src="/static/assets/modules/genealogy/opaque/g03-create-flow-panel.png" mode="scaleToFill" />
|
||||
<image
|
||||
class="member-form-panel__skin"
|
||||
src="/static/assets/modules/tree/transparent/t01-state-panel.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
|
||||
<view v-if="formState === 'form'" class="member-form">
|
||||
<text class="member-form__eyebrow">{{ config.eyebrow }}</text>
|
||||
<text class="member-form__title">{{ config.title }}</text>
|
||||
<text class="member-form__copy">{{ config.copy }}</text>
|
||||
<view v-for="field in config.fields" :key="field.key" class="member-field">
|
||||
<image src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
|
||||
<view
|
||||
v-for="field in config.fields"
|
||||
:key="field.key"
|
||||
class="member-field"
|
||||
>
|
||||
<image
|
||||
src="/static/assets/modules/tree/transparent/t07-search-input-frame.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<text>{{ field.label }}</text>
|
||||
<input v-model="form[field.key]" :placeholder="field.placeholder" placeholder-class="member-placeholder" />
|
||||
<input
|
||||
v-model="form[field.key]"
|
||||
:placeholder="field.placeholder"
|
||||
placeholder-class="member-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<text class="member-form__note">{{ config.note }}</text>
|
||||
<view class="member-form-action" @click="saveForm">
|
||||
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>{{ config.action }}</text>
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
|
||||
mode="aspectFit"
|
||||
/><text>{{ config.action }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -32,14 +55,23 @@
|
||||
<text class="member-form__copy">{{ resultCopy.copy }}</text>
|
||||
<view v-if="formState === 'conflict'" class="conflict-actions">
|
||||
<view class="member-form-action" @click="formState = 'form'">
|
||||
<image src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png" mode="aspectFit" /><text class="member-form-action__secondary">返回核对</text>
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
|
||||
mode="aspectFit"
|
||||
/><text class="member-form-action__secondary">返回核对</text>
|
||||
</view>
|
||||
<view class="member-form-action" @click="showConflictHelp">
|
||||
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>查看冲突</text>
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
|
||||
mode="aspectFit"
|
||||
/><text>查看冲突</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="member-form-action" @click="formState = 'form'">
|
||||
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>{{ formState === 'success' ? '继续完善' : '重新填写' }}</text>
|
||||
<image
|
||||
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
|
||||
mode="aspectFit"
|
||||
/><text>{{ formState === "success" ? "继续完善" : "重新填写" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -56,93 +88,268 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import AppDialog from '@/components/AppDialog.vue'
|
||||
import ModulePageBackground from '@/components/ModulePageBackground.vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const props = defineProps({ kind: { type: String, required: true } })
|
||||
const props = defineProps({ kind: { type: String, required: true } });
|
||||
const configs = {
|
||||
add: {
|
||||
pageTitle: '新增亲属', eyebrow: '补全家族关系', title: '为汤文远添加一位亲属',
|
||||
copy: '先确认与当前成员的关系,再录入基础身份信息。', action: '保存亲属', note: '保存后将在世系图中显示,可继续补充详细档案。',
|
||||
pageTitle: "新增亲属",
|
||||
eyebrow: "补全家族关系",
|
||||
title: "为汤文远添加一位亲属",
|
||||
copy: "先确认与当前成员的关系,再录入基础身份信息。",
|
||||
action: "保存亲属",
|
||||
note: "保存后将在世系图中显示,可继续补充详细档案。",
|
||||
fields: [
|
||||
{ key: 'name', label: '姓名', placeholder: '请输入真实姓名' },
|
||||
{ key: 'relation', label: '与本人关系', placeholder: '例如:长子、配偶' },
|
||||
{ key: 'birthDate', label: '出生日期', placeholder: '例如:1992年' },
|
||||
{ key: 'gender', label: '性别', placeholder: '请选择或输入' }
|
||||
]
|
||||
{ key: "name", label: "姓名", placeholder: "请输入真实姓名" },
|
||||
{ key: "relation", label: "与本人关系", placeholder: "例如:长子、配偶" },
|
||||
{ key: "birthDate", label: "出生日期", placeholder: "例如:1992年" },
|
||||
{ key: "gender", label: "性别", placeholder: "请选择或输入" },
|
||||
],
|
||||
},
|
||||
edit: {
|
||||
pageTitle: '编辑成员', eyebrow: '成员档案维护', title: '完善汤文远的生命记录',
|
||||
copy: '基础身份用于世系展示,生平信息可在成员档案中继续补充。', action: '保存资料', note: '隐私字段只向本人和有权限的家谱管理员展示。',
|
||||
pageTitle: "编辑成员",
|
||||
eyebrow: "成员档案维护",
|
||||
title: "完善汤文远的生命记录",
|
||||
copy: "基础身份用于世系展示,生平信息可在成员档案中继续补充。",
|
||||
action: "保存资料",
|
||||
note: "隐私字段只向本人和有权限的家谱管理员展示。",
|
||||
fields: [
|
||||
{ key: 'name', label: '姓名', placeholder: '汤文远' },
|
||||
{ key: 'generation', label: '字辈', placeholder: '例如:文' },
|
||||
{ key: 'birthDate', label: '出生日期', placeholder: '1940年' },
|
||||
{ key: 'summary', label: '人物简介', placeholder: '简要记录生平' }
|
||||
]
|
||||
{ key: "name", label: "姓名", placeholder: "汤文远" },
|
||||
{ key: "generation", label: "字辈", placeholder: "例如:文" },
|
||||
{ key: "birthDate", label: "出生日期", placeholder: "1940年" },
|
||||
{ key: "summary", label: "人物简介", placeholder: "简要记录生平" },
|
||||
],
|
||||
},
|
||||
relation: {
|
||||
pageTitle: '关系维护', eyebrow: '亲属关系校正', title: '确认两位成员的家族关系',
|
||||
copy: '调整关系会影响世系位置,保存前请核对成员和关系类型。', action: '保存关系', note: '若形成重复父母、代际倒置等情况,页面会先提示冲突。',
|
||||
pageTitle: "关系维护",
|
||||
eyebrow: "亲属关系校正",
|
||||
title: "确认两位成员的家族关系",
|
||||
copy: "调整关系会影响世系位置,保存前请核对成员和关系类型。",
|
||||
action: "保存关系",
|
||||
note: "若形成重复父母、代际倒置等情况,页面会先提示冲突。",
|
||||
fields: [
|
||||
{ key: 'source', label: '当前成员', placeholder: '汤文远' },
|
||||
{ key: 'target', label: '关联成员', placeholder: '请选择家谱成员' },
|
||||
{ key: 'relation', label: '关系类型', placeholder: '父子、配偶等' },
|
||||
{ key: 'effectiveDate', label: '生效日期', placeholder: '选填' }
|
||||
]
|
||||
}
|
||||
}
|
||||
const config = computed(() => configs[props.kind])
|
||||
const form = reactive({ name: '', relation: '', birthDate: '', gender: '', generation: '', summary: '', source: '', target: '', effectiveDate: '' })
|
||||
{ key: "source", label: "当前成员", placeholder: "汤文远" },
|
||||
{ key: "target", label: "关联成员", placeholder: "请选择家谱成员" },
|
||||
{ key: "relation", label: "关系类型", placeholder: "父子、配偶等" },
|
||||
{ key: "effectiveDate", label: "生效日期", placeholder: "选填" },
|
||||
],
|
||||
},
|
||||
};
|
||||
const config = computed(() => configs[props.kind]);
|
||||
const form = reactive({
|
||||
name: "",
|
||||
relation: "",
|
||||
birthDate: "",
|
||||
gender: "",
|
||||
generation: "",
|
||||
summary: "",
|
||||
source: "",
|
||||
target: "",
|
||||
effectiveDate: "",
|
||||
});
|
||||
const query = (() => {
|
||||
if (typeof location !== 'undefined') return Object.fromEntries(new URLSearchParams(location.hash.split('?')[1] || ''))
|
||||
const pages = getCurrentPages()
|
||||
return pages[pages.length - 1]?.options || {}
|
||||
})()
|
||||
const formState = ref(query.state === 'success' ? 'success' : query.state === 'conflict' ? 'conflict' : query.state === 'error' ? 'error' : 'form')
|
||||
const conflictHelpVisible = ref(false)
|
||||
const resultCopy = computed(() => ({
|
||||
success: { eyebrow: '本地预览已更新', title: `${config.value.pageTitle}内容已保存`, copy: '返回世系树后可查看新的展示位置;正式数据将在接口阶段接入。' },
|
||||
conflict: { eyebrow: '发现关系冲突', title: '这段关系会造成代际矛盾', copy: '目标成员已经存在父级关系,请先核对原关系,再决定是否调整。' },
|
||||
error: { eyebrow: '内容未保存', title: '暂时无法完成这次操作', copy: '请返回成员档案重新进入,当前填写内容不会写入正式数据。' }
|
||||
}[formState.value] || {}))
|
||||
if (typeof location !== "undefined")
|
||||
return Object.fromEntries(
|
||||
new URLSearchParams(location.hash.split("?")[1] || ""),
|
||||
);
|
||||
const pages = getCurrentPages();
|
||||
return pages[pages.length - 1]?.options || {};
|
||||
})();
|
||||
const formState = ref(
|
||||
query.state === "success"
|
||||
? "success"
|
||||
: query.state === "conflict"
|
||||
? "conflict"
|
||||
: query.state === "error"
|
||||
? "error"
|
||||
: "form",
|
||||
);
|
||||
const conflictHelpVisible = ref(false);
|
||||
const resultCopy = computed(
|
||||
() =>
|
||||
({
|
||||
success: {
|
||||
eyebrow: "本地预览已更新",
|
||||
title: `${config.value.pageTitle}内容已保存`,
|
||||
copy: "返回世系树后可查看新的展示位置;正式数据将在接口阶段接入。",
|
||||
},
|
||||
conflict: {
|
||||
eyebrow: "发现关系冲突",
|
||||
title: "这段关系会造成代际矛盾",
|
||||
copy: "目标成员已经存在父级关系,请先核对原关系,再决定是否调整。",
|
||||
},
|
||||
error: {
|
||||
eyebrow: "内容未保存",
|
||||
title: "暂时无法完成这次操作",
|
||||
copy: "请返回成员档案重新进入,当前填写内容不会写入正式数据。",
|
||||
},
|
||||
})[formState.value] || {},
|
||||
);
|
||||
|
||||
const saveForm = () => {
|
||||
if (props.kind === 'relation' && (!form.target.trim() || !form.relation.trim())) {
|
||||
formState.value = 'conflict'
|
||||
return
|
||||
if (
|
||||
props.kind === "relation" &&
|
||||
(!form.target.trim() || !form.relation.trim())
|
||||
) {
|
||||
formState.value = "conflict";
|
||||
return;
|
||||
}
|
||||
formState.value = 'success'
|
||||
}
|
||||
const showConflictHelp = () => { conflictHelpVisible.value = true }
|
||||
formState.value = "success";
|
||||
};
|
||||
const showConflictHelp = () => {
|
||||
conflictHelpVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.member-form-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; }
|
||||
.member-form-page__header { position: relative; z-index: 3; }
|
||||
.member-form-panel { position: relative; z-index: 2; width: calc(100% - 32rpx); height: min(640px, calc((100vw - 16px) * 1.48)); margin: 18rpx auto 0; }
|
||||
.member-form-panel__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.member-form, .member-form-result { position: absolute; inset: 7.5% 8%; }
|
||||
.member-form__eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
|
||||
.member-form__title { display: block; margin-top: 10rpx; color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 34rpx; font-weight: 700; }
|
||||
.member-form__copy { display: block; margin-top: 9rpx; color: $ink-muted; font-size: 20rpx; line-height: 1.5; }
|
||||
.member-field { position: relative; height: 78rpx; margin-top: 12rpx; }
|
||||
.member-field image { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.member-field > text { position: absolute; top: 27rpx; left: 22rpx; z-index: 1; color: $ink; font-size: 21rpx; font-weight: 700; }
|
||||
.member-field input { position: absolute; top: 0; right: 18rpx; bottom: 0; left: 164rpx; z-index: 1; height: 78rpx; color: $ink; font-size: 21rpx; line-height: 78rpx; }
|
||||
.member-placeholder { color: #a79884; }
|
||||
.member-form__note { display: block; margin-top: 14rpx; color: $ink-muted; font-size: 19rpx; line-height: 1.45; text-align: center; }
|
||||
.member-form-action { position: relative; width: 100%; height: 76rpx; margin-top: 17rpx; }
|
||||
.member-form-action image { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.member-form-action text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: #fff9ed; font-size: 24rpx; font-weight: 700; }
|
||||
.member-form-action .member-form-action__secondary { color: $ink; }
|
||||
.member-form-result { top: 29%; text-align: center; }
|
||||
.member-form-result .member-form__eyebrow { text-align: center; }
|
||||
.member-form-result .member-form__copy { margin-top: 20rpx; }
|
||||
.member-form-result > .member-form-action { width: 420rpx; max-width: 100%; margin: 32rpx auto 0; }
|
||||
.conflict-actions { display: flex; gap: 14rpx; margin-top: 30rpx; }
|
||||
.conflict-actions .member-form-action { width: calc(50% - 7rpx); margin-top: 0; }
|
||||
@media (min-width: 400px) { .member-form-panel { width: calc(100% - 48rpx); } }
|
||||
.member-form-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background: $paper;
|
||||
}
|
||||
.member-form-page__header {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
.member-form-panel {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: calc(100% - 32rpx);
|
||||
height: min(640px, calc((100vw - 16px) * 1.48));
|
||||
margin: 18rpx auto 0;
|
||||
}
|
||||
.member-form-panel__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.member-form,
|
||||
.member-form-result {
|
||||
position: absolute;
|
||||
inset: 7.5% 8%;
|
||||
}
|
||||
.member-form__eyebrow {
|
||||
display: block;
|
||||
color: $brand-red;
|
||||
font-size: 22rpx;
|
||||
letter-spacing: 3rpx;
|
||||
}
|
||||
.member-form__title {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: $ink;
|
||||
font-family: "STKaiti", "KaiTi", serif;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-form__copy {
|
||||
display: block;
|
||||
margin-top: 9rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.member-field {
|
||||
position: relative;
|
||||
height: 78rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.member-field image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.member-field > text {
|
||||
position: absolute;
|
||||
top: 27rpx;
|
||||
left: 22rpx;
|
||||
z-index: 1;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-field input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 18rpx;
|
||||
bottom: 0;
|
||||
left: 164rpx;
|
||||
z-index: 1;
|
||||
height: 78rpx;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
line-height: 78rpx;
|
||||
}
|
||||
.member-placeholder {
|
||||
color: #a79884;
|
||||
}
|
||||
.member-form__note {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
text-align: center;
|
||||
}
|
||||
.member-form-action {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 76rpx;
|
||||
margin-top: 17rpx;
|
||||
}
|
||||
.member-form-action image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.member-form-action text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #fff9ed;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-form-action .member-form-action__secondary {
|
||||
color: $ink;
|
||||
}
|
||||
.member-form-result {
|
||||
top: 29%;
|
||||
text-align: center;
|
||||
}
|
||||
.member-form-result .member-form__eyebrow {
|
||||
text-align: center;
|
||||
}
|
||||
.member-form-result .member-form__copy {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.member-form-result > .member-form-action {
|
||||
width: 420rpx;
|
||||
max-width: 100%;
|
||||
margin: 32rpx auto 0;
|
||||
}
|
||||
.conflict-actions {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.conflict-actions .member-form-action {
|
||||
width: calc(50% - 7rpx);
|
||||
margin-top: 0;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.member-form-panel {
|
||||
width: calc(100% - 48rpx);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user