first commit

This commit is contained in:
2026-02-13 17:36:42 +08:00
commit f067e1bb78
155 changed files with 46676 additions and 0 deletions

501
src/pages/search/index.vue Normal file
View File

@@ -0,0 +1,501 @@
<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>