Files
1818uniapp/src/pages/search/index.vue
2026-02-13 17:36:42 +08:00

502 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="search-page">
<!-- 顶部导航栏返回按钮 -->
<view class="nav-header" :style="{ paddingTop: navPaddingTop + 'px' }">
<view class="nav-bar" :style="{ height: navBarHeight + 'px' }">
<view class="back-btn" @click="goBack">
<image class="back-icon" src="/static/icons/Left (左).png" mode="aspectFit" />
</view>
<text class="nav-title">搜索</text>
<view class="nav-placeholder"></view>
</view>
</view>
<!-- 搜索输入框单独一行 -->
<view class="search-section">
<view class="search-input-wrap">
<image class="search-icon" src="/static/icons/search.png" mode="aspectFit" />
<input
class="search-input"
type="text"
v-model="keyword"
placeholder="搜索作品、风格、关键词..."
placeholder-class="search-placeholder"
confirm-type="search"
:focus="autoFocus"
@confirm="handleSearch"
@input="handleInput"
/>
<view class="clear-btn" v-if="keyword" @click="clearKeyword">
<text class="clear-text">×</text>
</view>
</view>
</view>
<!-- 搜索历史 -->
<view class="search-history" v-if="!hasSearched && searchHistory.length > 0">
<view class="history-header">
<text class="history-title">搜索历史</text>
<view class="clear-history" @click="clearHistory">
<image class="delete-icon" src="/static/icons/del.png" mode="aspectFit" />
</view>
</view>
<view class="history-tags">
<view
class="history-tag"
v-for="(item, index) in searchHistory"
:key="index"
@click="searchByHistory(item)"
>
<text class="history-tag-text">{{ item }}</text>
</view>
</view>
</view>
<!-- 热门搜索 -->
<view class="hot-search" v-if="!hasSearched && hotKeywords.length > 0">
<view class="hot-header">
<text class="hot-title">热门搜索</text>
</view>
<view class="hot-tags">
<view
class="hot-tag"
v-for="(item, index) in hotKeywords"
:key="index"
@click="searchByHistory(item)"
>
<text class="hot-tag-text">{{ item }}</text>
</view>
</view>
</view>
<!-- 搜索结果 -->
<view class="search-results" v-if="hasSearched">
<!-- 空状态 -->
<view class="empty-state" v-if="!isLoading && workList.length === 0">
<image class="empty-image" src="/static/icons/search.png" mode="aspectFit" />
<text class="empty-text">未找到相关作品</text>
<text class="empty-tip">换个关键词试试吧</text>
</view>
<!-- 作品列表 -->
<view class="work-list-wrap" v-else>
<WorkList
ref="workListRef"
:works="workList"
:loading="isLoading"
:finished="!hasMore"
@click="handleWorkClick"
@load-more="loadMore"
/>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onReachBottom } from '@dcloudio/uni-app'
import { searchWorks } from '@/api/work'
import WorkList from '@/components/WorkList/index.vue'
// 胶囊按钮信息
const capsuleInfo = ref({ top: 0, height: 32, width: 87, right: 10 })
const statusBarHeight = ref(0)
// 计算导航栏相关尺寸
const navPaddingTop = computed(() => capsuleInfo.value.top || statusBarHeight.value)
const navBarHeight = computed(() => capsuleInfo.value.height + 8 || 40)
const capsuleHeight = computed(() => capsuleInfo.value.height || 32)
const capsuleWidth = computed(() => capsuleInfo.value.width || 87)
const keyword = ref('')
const autoFocus = ref(true)
const hasSearched = ref(false)
const isLoading = ref(false)
const workList = ref([])
const hasMore = ref(true)
const pageNum = ref(1)
const pageSize = 10
// 搜索历史
const HISTORY_KEY = 'search_history'
const MAX_HISTORY = 10
const searchHistory = ref([])
// 热门搜索关键词
const hotKeywords = ref(['风景', '人物', '动漫', '科幻', '艺术', '写实'])
// 加载搜索历史
const loadHistory = () => {
try {
const history = uni.getStorageSync(HISTORY_KEY)
if (history) {
searchHistory.value = JSON.parse(history)
}
} catch (e) {
console.error('加载搜索历史失败', e)
}
}
// 保存搜索历史
const saveHistory = (word) => {
if (!word.trim()) return
// 去重并添加到头部
const history = searchHistory.value.filter(item => item !== word)
history.unshift(word)
// 限制数量
if (history.length > MAX_HISTORY) {
history.pop()
}
searchHistory.value = history
uni.setStorageSync(HISTORY_KEY, JSON.stringify(history))
}
// 清空历史
const clearHistory = () => {
uni.showModal({
title: '提示',
content: '确定清空搜索历史吗?',
success: (res) => {
if (res.confirm) {
searchHistory.value = []
uni.removeStorageSync(HISTORY_KEY)
}
}
})
}
// 清空关键词
const clearKeyword = () => {
keyword.value = ''
hasSearched.value = false
workList.value = []
}
// 返回
const goBack = () => {
uni.navigateBack()
}
// 处理输入
const handleInput = () => {
// 输入时重置搜索状态
if (!keyword.value.trim()) {
hasSearched.value = false
workList.value = []
}
}
// 执行搜索
const handleSearch = async () => {
const word = keyword.value.trim()
if (!word) {
uni.showToast({ title: '请输入搜索内容', icon: 'none' })
return
}
// 保存搜索历史
saveHistory(word)
// 重置状态
hasSearched.value = true
workList.value = []
pageNum.value = 1
hasMore.value = true
await loadData()
}
// 点击历史搜索
const searchByHistory = (word) => {
keyword.value = word
handleSearch()
}
// 加载数据
const loadData = async () => {
if (isLoading.value || !hasMore.value) return
isLoading.value = true
try {
const res = await searchWorks({
keyword: keyword.value.trim(),
pageNum: pageNum.value,
pageSize
})
const list = res?.list || []
if (pageNum.value === 1) {
workList.value = list
} else {
workList.value = [...workList.value, ...list]
}
hasMore.value = res?.hasNext ?? false
pageNum.value++
} catch (e) {
console.error('搜索失败', e)
} finally {
isLoading.value = false
}
}
// 加载更多
const loadMore = () => {
if (!isLoading.value && hasMore.value) {
loadData()
}
}
// 点击作品
const handleWorkClick = (work) => {
uni.navigateTo({ url: `/pages/work/detail?id=${work.id}` })
}
// 页面触底
onReachBottom(() => {
if (hasSearched.value) {
loadMore()
}
})
onMounted(() => {
// 获取系统信息
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 0
// 获取胶囊按钮信息(仅微信小程序)
// #ifdef MP-WEIXIN
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
capsuleInfo.value = {
top: menuButtonInfo.top,
height: menuButtonInfo.height,
width: menuButtonInfo.width,
right: sysInfo.windowWidth - menuButtonInfo.right
}
} catch (e) {
console.warn('获取胶囊信息失败', e)
}
// #endif
loadHistory()
})
</script>
<style scoped>
.search-page {
min-height: 100vh;
background-color: #09090b;
}
/* 顶部导航栏 */
.nav-header {
position: sticky;
top: 0;
z-index: 100;
background-color: #09090b;
}
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
}
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 22px;
height: 22px;
}
.nav-title {
font-size: 17px;
font-weight: 600;
color: #f1f5f9;
}
.nav-placeholder {
width: 40px;
}
/* 搜索输入区域 */
.search-section {
padding: 12px 16px 16px;
}
.search-input-wrap {
height: 44px;
background: #18181b;
border-radius: 22px;
display: flex;
align-items: center;
padding: 0 16px;
gap: 10px;
border: 1px solid #27272a;
}
.search-icon {
width: 18px;
height: 18px;
opacity: 0.5;
flex-shrink: 0;
}
.search-input {
flex: 1;
height: 100%;
font-size: 15px;
color: #f1f5f9;
background: transparent;
}
.search-placeholder {
color: #52525b;
font-size: 15px;
}
.clear-btn {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
background: #3f3f46;
border-radius: 50%;
flex-shrink: 0;
}
.clear-text {
font-size: 14px;
color: #a1a1aa;
line-height: 1;
}
/* 搜索历史 */
.search-history {
padding: 20px 16px;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.history-title {
font-size: 14px;
color: #a1a1aa;
font-weight: 500;
}
.clear-history {
padding: 4px;
}
.delete-icon {
width: 18px;
height: 18px;
opacity: 0.5;
}
.history-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.history-tag {
background: #27272a;
border-radius: 16px;
padding: 6px 14px;
}
.history-tag-text {
font-size: 13px;
color: #e4e4e7;
}
/* 热门搜索 */
.hot-search {
padding: 0 16px 20px;
}
.hot-header {
margin-bottom: 12px;
}
.hot-title {
font-size: 14px;
color: #a1a1aa;
font-weight: 500;
}
.hot-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hot-tag {
background: #27272a;
border-radius: 16px;
padding: 6px 14px;
}
.hot-tag-text {
font-size: 13px;
color: #e4e4e7;
}
/* 搜索结果 */
.search-results {
padding: 0 10px;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 80px 0;
}
.empty-image {
width: 120px;
height: 120px;
opacity: 0.5;
margin-bottom: 16px;
}
.empty-text {
font-size: 15px;
color: #a1a1aa;
margin-bottom: 8px;
}
.empty-tip {
font-size: 13px;
color: #71717a;
}
.work-list-wrap {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>