502 lines
10 KiB
Vue
502 lines
10 KiB
Vue
|
|
<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>
|