117 lines
7.6 KiB
Vue
117 lines
7.6 KiB
Vue
<!-- 页面编号:G-06;用途:公开家谱检索,统一承载初始、加载、结果与无结果状态。 -->
|
||
<template>
|
||
<view class="search-page">
|
||
<!-- 底图只提供完整纸纹,不以 CSS 补色或绘制装饰。 -->
|
||
<image class="search-page__paper" src="/static/assets/foundation/opaque/page-paper.jpg" mode="aspectFill" />
|
||
<image class="search-page__footer" src="/static/assets/foundation/transparent/footer-mountain-bamboo.png" mode="widthFix" />
|
||
<view class="search-page__header"><PageHeader title="查找家谱" /></view>
|
||
|
||
<view class="search-page__content">
|
||
<!-- 选定题签稿:题签位图只提供材质,品牌谱印和标题保留为真实应用元素。 -->
|
||
<view class="search-title-strip">
|
||
<image class="search-title-strip__skin" src="/static/assets/modules/genealogy/opaque/g06-search-title-strip.png" mode="scaleToFill" />
|
||
<image class="search-title-strip__seal" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
|
||
<text class="search-title-strip__text">公开家谱检索</text>
|
||
</view>
|
||
<view class="search-controls">
|
||
<view class="search-field">
|
||
<image class="search-field__skin" src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png" mode="scaleToFill" />
|
||
<input v-model="keyword" class="search-input" placeholder="输入姓氏、谱名或地区" placeholder-class="search-input__placeholder" @confirm="search" />
|
||
</view>
|
||
<view class="search-action" @click="search">
|
||
<image class="search-action__skin" src="/static/assets/modules/genealogy/opaque/g06-search-button.png" mode="scaleToFill" />
|
||
<text class="search-action__label">搜索</text>
|
||
</view>
|
||
</view>
|
||
<image class="search-divider" src="/static/assets/modules/genealogy/transparent/section-divider.png" mode="scaleToFill" />
|
||
|
||
<!-- 同一路由内以数据状态切换,避免为结果或空结果新增占位页面。 -->
|
||
<view v-if="searchState === 'loading'" class="search-status search-loading">
|
||
<text>正在整理可申请加入的家谱</text>
|
||
</view>
|
||
<view v-else-if="searchState === 'initial'" class="search-status search-initial">
|
||
<text class="search-status__lead">从姓氏、谱名或祖居地开始</text>
|
||
<text class="search-status__copy">找到合适的家谱后,可申请加入并查阅。</text>
|
||
<image class="search-status__hall" src="/static/assets/foundation/transparent/root-header-hall.png" mode="aspectFit" />
|
||
</view>
|
||
<view v-else-if="searchState === 'results'" class="search-results">
|
||
<view class="search-results__head"><text>检索到 {{ results.length }} 部可申请的家谱</text><text>点选后填写申请</text></view>
|
||
<GenealogyCard v-for="item in results" :key="item.id" :genealogy="item" role="可申请" @select="openApply" />
|
||
</view>
|
||
<view v-else class="search-status search-empty">
|
||
<image class="search-status__cloud" src="/static/assets/modules/genealogy/transparent/create-cloud.png" mode="aspectFit" />
|
||
<text class="search-status__lead">暂未找到公开家谱</text>
|
||
<text class="search-status__copy">可换一个姓氏、谱名或祖居地再试。</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onMounted, ref } from 'vue'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
import GenealogyCard from '@/components/GenealogyCard.vue'
|
||
import { appApi } from '@/utils/api.js'
|
||
|
||
const keyword = ref('')
|
||
const allResults = ref([])
|
||
const results = ref([])
|
||
const searchState = ref('initial')
|
||
|
||
onMounted(async () => {
|
||
searchState.value = 'loading'
|
||
allResults.value = await appApi.getPublicGenealogies()
|
||
searchState.value = 'initial'
|
||
})
|
||
|
||
const search = () => {
|
||
const value = keyword.value.trim()
|
||
results.value = value
|
||
? allResults.value.filter((item) => `${item.name}${item.surname}${item.location}`.includes(value))
|
||
: allResults.value
|
||
searchState.value = results.value.length ? 'results' : 'empty'
|
||
}
|
||
|
||
const openApply = (genealogy) => uni.navigateTo({ url: `/pages/genealogy/g08-join-application?genealogyId=${genealogy.id}` })
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
/* G06 的完整检索位图与叠放控件样式必须作为同一 SFC 样式模块刷新。 */
|
||
.search-page { position: relative; min-height: 100vh; overflow: hidden; background: $paper; }
|
||
.search-page__paper { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; opacity: .98; pointer-events: none; }
|
||
.search-page__footer { position: absolute; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; opacity: .15; pointer-events: none; }
|
||
.search-page__header { position: relative; z-index: 2; }
|
||
.search-page__content { position: relative; z-index: 3; min-height: calc(100vh - 104rpx); padding: 30rpx 24rpx calc(58rpx + env(safe-area-inset-bottom)); box-sizing: border-box; }
|
||
|
||
.search-title-strip { position: relative; width: 100%; height: 138rpx; }
|
||
.search-title-strip__skin { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||
.search-title-strip__seal { position: absolute; top: 27rpx; left: 52rpx; z-index: 1; width: 72rpx; height: 84rpx; }
|
||
.search-title-strip__text { position: absolute; top: 0; right: 170rpx; bottom: 0; left: 156rpx; z-index: 1; display: flex; align-items: center; justify-content: center; color: #f5d596; font-family: 'STKaiti', 'KaiTi', serif; font-size: 39rpx; font-weight: 700; letter-spacing: 5rpx; }
|
||
.search-controls { display: flex; align-items: center; gap: 20rpx; margin-top: 48rpx; }
|
||
.search-field { position: relative; width: 430rpx; height: 95rpx; }
|
||
.search-field__skin, .search-action__skin { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||
.search-input { position: relative; z-index: 1; width: 100%; height: 100%; padding: 0 30rpx; box-sizing: border-box; color: $ink; font-size: 26rpx; }
|
||
.search-input__placeholder { color: #ab9c83; }
|
||
.search-action { position: relative; display: flex; width: 200rpx; height: 88rpx; align-items: center; justify-content: center; }
|
||
.search-action__label { position: relative; z-index: 1; color: #fff4dc; font-family: 'STKaiti', 'KaiTi', serif; font-size: 31rpx; font-weight: 700; letter-spacing: 7rpx; }
|
||
.search-divider { display: block; width: 100%; height: 30rpx; margin: 40rpx 0 34rpx; }
|
||
|
||
.search-status { display: flex; min-height: 190rpx; flex-direction: column; align-items: center; justify-content: center; padding: 10rpx 28rpx 24rpx; box-sizing: border-box; text-align: center; }
|
||
.search-status__cloud { width: 92rpx; height: 46rpx; margin-bottom: 4rpx; opacity: .78; }
|
||
.search-status__lead { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 29rpx; font-weight: 700; letter-spacing: 2rpx; }
|
||
.search-status__copy { margin-top: 12rpx; color: $ink-muted; font-size: 22rpx; }
|
||
.search-status__hall { width: 330rpx; height: 132rpx; margin-top: 330rpx; opacity: .22; filter: grayscale(1); }
|
||
.search-loading { min-height: 168rpx; color: $ink-muted; font-size: 24rpx; }
|
||
.search-results { display: flex; flex-direction: column; gap: 18rpx; margin-top: 6rpx; }
|
||
.search-results__head { display: flex; align-items: center; justify-content: space-between; padding: 0 16rpx; color: $ink-muted; font-size: 22rpx; }
|
||
.search-results__head text:first-child { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 27rpx; font-weight: 700; }
|
||
|
||
@media screen and (max-width: 340px) {
|
||
.search-page__content { padding-right: 20rpx; padding-left: 20rpx; }
|
||
.search-title-strip__seal { left: 44rpx; }
|
||
.search-title-strip__text { right: 148rpx; left: 138rpx; font-size: 37rpx; }
|
||
.search-field { width: 430rpx; }
|
||
.search-action { width: 200rpx; height: 88rpx; }
|
||
}
|
||
</style>
|