Files
jiapuapp/pages/records/r01-people-list.vue
T
2026-07-20 17:23:13 +08:00

353 lines
8.6 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.
<!-- 页面编号R-01用途人物录列表搜索空态与失败状态 -->
<template>
<view
class="people-page"
:class="{
'people-state--ready': peopleState === 'ready',
'people-state--empty': peopleState === 'empty',
'people-state--error': peopleState === 'error',
}"
>
<ModulePageBackground module="records" />
<view class="people-page__header"><PageHeader title="人物录" /></view>
<view class="people-content">
<template v-if="peopleState === 'ready'">
<view class="people-search">
<image
src="/static/assets/modules/records/transparent/r01-search-input-frame.png"
mode="scaleToFill"
/>
<input
v-model="keywordInput"
confirm-type="search"
placeholder="搜索姓名、身份或世代"
placeholder-class="people-search__placeholder"
@confirm="applySearch"
/>
<view
class="people-search__action"
role="button"
aria-label="搜索人物"
@click="applySearch"
><text>{{ keyword ? "重置" : "搜索" }}</text></view
>
</view>
<view v-if="filteredPeople.length" class="people-list">
<view
v-for="person in filteredPeople"
:key="person.id"
class="person-card"
@click="openPerson(person)"
>
<image
class="person-card__skin"
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
mode="scaleToFill"
/>
<view class="person-card__copy">
<text class="person-card__name">{{ person.name }}</text>
<text class="person-card__meta"
>{{ person.role }} · {{ person.generation }} </text
>
<text class="person-card__hint">查看人物档案</text>
</view>
</view>
<AppButton
class="people-primary-action"
block
label="新建人物"
@click="showCreateNotice"
/>
</view>
<view v-else class="people-result-empty">
<image
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
mode="scaleToFill"
/>
<view
><text>没有找到相关人物</text
><text>请更换姓名身份或世代关键词后再试</text></view
>
<AppButton
type="secondary"
block
label="清空搜索"
@click="clearSearch"
/>
</view>
</template>
<view v-else class="people-state-card">
<image
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
mode="scaleToFill"
/>
<view>
<text>{{
peopleState === "empty" ? "还没有人物记录" : "人物录暂不可用"
}}</text>
<text>{{
peopleState === "empty"
? "从第一位值得铭记的家人开始建立人物录。"
: "请稍后重新进入,已有档案不会受到影响。"
}}</text>
</view>
<AppButton
:type="peopleState === 'error' ? 'secondary' : 'primary'"
block
:label="peopleState === 'error' ? '重新查看' : '新建人物'"
@click="peopleState === 'error' ? restoreList() : showCreateNotice()"
/>
</view>
</view>
<AppToast :visible="toastVisible" message="新建人物将在后续功能阶段开放" />
</view>
</template>
<script setup>
import { computed, onUnmounted, ref } from "vue";
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 people = [
{ id: 1, name: "汤文正", role: "家谱管理员", generation: 18 },
{ id: 2, name: "汤淑华", role: "家族长辈", generation: 17 },
{ id: 3, name: "汤文清", role: "青年代表", generation: 19 },
];
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 peopleState = ref(
query.state === "empty"
? "empty"
: query.state === "error"
? "error"
: "ready",
);
const keywordInput = ref("");
const keyword = ref("");
const toastVisible = ref(false);
let toastTimer = null;
const filteredPeople = computed(() => {
const value = keyword.value.trim().toLowerCase();
if (!value) return people;
return people.filter((person) =>
`${person.name} ${person.role}${person.generation}${person.generation}`
.toLowerCase()
.includes(value),
);
});
const applySearch = () => {
if (keyword.value) {
clearSearch();
return;
}
keyword.value = keywordInput.value.trim();
};
const clearSearch = () => {
keywordInput.value = "";
keyword.value = "";
};
const restoreList = () => {
peopleState.value = "ready";
};
const openPerson = (person) =>
uni.navigateTo({
url: `/pages/records/r02-person-detail?personId=${person.id}`,
});
const showCreateNotice = () => {
toastVisible.value = true;
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toastVisible.value = false;
toastTimer = null;
}, 1800);
};
onUnmounted(() => {
if (toastTimer) clearTimeout(toastTimer);
});
</script>
<style scoped lang="scss">
.people-page {
position: relative;
min-height: 100vh;
overflow-x: hidden;
background: $paper;
}
.people-page__header,
.people-content {
position: relative;
z-index: 2;
}
.people-content {
padding: 22rpx 24rpx 100rpx;
}
.people-search {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 126rpx;
width: 100%;
height: 82rpx;
min-height: 44px;
align-items: center;
}
.people-search > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.people-search input {
position: relative;
z-index: 1;
width: 100%;
min-width: 0;
height: 100%;
padding-left: 31rpx;
box-sizing: border-box;
color: $ink;
font-size: 24rpx;
}
.people-search__placeholder {
color: #8a7965;
}
.people-search__action {
position: relative;
z-index: 2;
display: flex;
width: 126rpx;
height: 100%;
min-height: 44px;
align-items: center;
justify-content: center;
color: $brand-red;
font-size: 24rpx;
font-weight: 700;
}
.people-list {
margin-top: 12px;
}
.person-card {
position: relative;
display: flex;
width: 100%;
height: clamp(92px, 190rpx, 108px);
align-items: center;
margin-top: 12px;
padding: 16% 10%;
box-sizing: border-box;
}
.person-card:first-child {
margin-top: 0;
}
.person-card__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.person-card__copy {
position: relative;
z-index: 1;
display: flex;
width: 100%;
flex-direction: column;
justify-content: center;
}
.person-card__name {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: 31rpx;
font-weight: 700;
}
.person-card__meta {
margin-top: 7rpx;
color: $ink-muted;
font-size: 23rpx;
}
.person-card__hint {
margin-top: 8rpx;
color: #806a51;
font-size: 21rpx;
}
.people-primary-action {
margin: 22rpx auto 0;
}
.people-result-empty,
.people-state-card {
position: relative;
margin-top: 38rpx;
text-align: center;
}
.people-result-empty > image,
.people-state-card > image {
position: absolute;
top: 0;
right: 0;
left: 0;
width: 100%;
height: 220rpx;
min-height: 118px;
pointer-events: none;
}
.people-result-empty > view,
.people-state-card > view {
position: relative;
z-index: 1;
display: flex;
height: 220rpx;
min-height: 118px;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 54rpx;
}
.people-result-empty text,
.people-state-card text {
display: block;
}
.people-result-empty text:first-child,
.people-state-card text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: 31rpx;
font-weight: 700;
}
.people-result-empty text:last-child,
.people-state-card text:last-child {
margin-top: 13rpx;
color: $ink-muted;
font-size: 23rpx;
line-height: 1.5;
}
.people-result-empty .app-button,
.people-state-card .app-button {
margin: 20rpx auto 0;
}
@media (min-width: 400px) {
.people-content {
padding-right: 32rpx;
padding-left: 32rpx;
}
}
</style>