增加标签搜索功能,增加作品标签点击跳转
This commit is contained in:
@@ -10,6 +10,7 @@ router.get('/search', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
keyword,
|
keyword,
|
||||||
|
tags,
|
||||||
type = 'all',
|
type = 'all',
|
||||||
sort = 'date_desc',
|
sort = 'date_desc',
|
||||||
duration = 'all',
|
duration = 'all',
|
||||||
@@ -17,16 +18,27 @@ router.get('/search', async (req, res) => {
|
|||||||
limit = 30
|
limit = 30
|
||||||
} = req.query;
|
} = req.query;
|
||||||
|
|
||||||
if (!keyword) {
|
// 处理标签参数
|
||||||
|
let tagsArray = [];
|
||||||
|
if (tags) {
|
||||||
|
if (Array.isArray(tags)) {
|
||||||
|
tagsArray = tags;
|
||||||
|
} else {
|
||||||
|
tagsArray = [tags];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keyword && (!tagsArray || tagsArray.length === 0)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Search keyword is required'
|
error: 'Search keyword or tags are required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const artworkService = new ArtworkService(req.backend.getAuth());
|
const artworkService = new ArtworkService(req.backend.getAuth());
|
||||||
const result = await artworkService.searchArtworks({
|
const result = await artworkService.searchArtworks({
|
||||||
keyword,
|
keyword,
|
||||||
|
tags: tagsArray,
|
||||||
type,
|
type,
|
||||||
sort,
|
sort,
|
||||||
duration,
|
duration,
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class ArtworkService {
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
keyword,
|
keyword,
|
||||||
|
tags,
|
||||||
type = 'all',
|
type = 'all',
|
||||||
sort = 'date_desc',
|
sort = 'date_desc',
|
||||||
duration = 'all',
|
duration = 'all',
|
||||||
@@ -159,10 +160,10 @@ class ArtworkService {
|
|||||||
} = searchOptions;
|
} = searchOptions;
|
||||||
|
|
||||||
// 验证搜索参数
|
// 验证搜索参数
|
||||||
if (!keyword || keyword.trim() === '') {
|
if ((!keyword || keyword.trim() === '') && (!tags || tags.length === 0)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Search keyword is required'
|
error: 'Search keyword or tags are required'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +188,17 @@ class ArtworkService {
|
|||||||
'within_last_month': 'within_last_month'
|
'within_last_month': 'within_last_month'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 构建搜索关键词
|
||||||
|
let searchWord = '';
|
||||||
|
if (keyword && keyword.trim()) {
|
||||||
|
searchWord = keyword.trim();
|
||||||
|
} else if (tags && tags.length > 0) {
|
||||||
|
// 将标签数组转换为搜索关键词,用空格分隔
|
||||||
|
searchWord = tags.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
word: keyword.trim(),
|
word: searchWord,
|
||||||
search_target: searchTargetMap[type] || 'partial_match_for_tags',
|
search_target: searchTargetMap[type] || 'partial_match_for_tags',
|
||||||
sort: sortMap[sort] || 'date_desc',
|
sort: sortMap[sort] || 'date_desc',
|
||||||
offset: parseInt(offset) || 0,
|
offset: parseInt(offset) || 0,
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -56,7 +56,19 @@ class ArtworkService {
|
|||||||
*/
|
*/
|
||||||
async searchArtworks(params: SearchParams): Promise<ApiResponse<{ artworks: Artwork[]; next_url?: string; total: number }>> {
|
async searchArtworks(params: SearchParams): Promise<ApiResponse<{ artworks: Artwork[]; next_url?: string; total: number }>> {
|
||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
|
// 处理关键词搜索
|
||||||
|
if (params.keyword) {
|
||||||
queryParams.append('keyword', params.keyword);
|
queryParams.append('keyword', params.keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理标签搜索
|
||||||
|
if (params.tags && params.tags.length > 0) {
|
||||||
|
params.tags.forEach(tag => {
|
||||||
|
queryParams.append('tags', tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (params.type) queryParams.append('type', params.type);
|
if (params.type) queryParams.append('type', params.type);
|
||||||
if (params.sort) queryParams.append('sort', params.sort);
|
if (params.sort) queryParams.append('sort', params.sort);
|
||||||
if (params.duration) queryParams.append('duration', params.duration);
|
if (params.duration) queryParams.append('duration', params.duration);
|
||||||
|
|||||||
@@ -114,7 +114,8 @@ export interface DownloadTask {
|
|||||||
|
|
||||||
// 搜索参数
|
// 搜索参数
|
||||||
export interface SearchParams {
|
export interface SearchParams {
|
||||||
keyword: string;
|
keyword?: string;
|
||||||
|
tags?: string[];
|
||||||
type?: 'all' | 'art' | 'manga' | 'novel';
|
type?: 'all' | 'art' | 'manga' | 'novel';
|
||||||
sort?: 'date_desc' | 'date_asc' | 'popular_desc';
|
sort?: 'date_desc' | 'date_asc' | 'popular_desc';
|
||||||
duration?: 'all' | 'within_last_day' | 'within_last_week' | 'within_last_month';
|
duration?: 'all' | 'within_last_day' | 'within_last_week' | 'within_last_month';
|
||||||
|
|||||||
@@ -158,13 +158,16 @@
|
|||||||
<div class="artwork-tags">
|
<div class="artwork-tags">
|
||||||
<h3>标签</h3>
|
<h3>标签</h3>
|
||||||
<div class="tags-list">
|
<div class="tags-list">
|
||||||
<span
|
<button
|
||||||
v-for="tag in artwork.tags"
|
v-for="tag in artwork.tags"
|
||||||
:key="tag.name"
|
:key="tag.name"
|
||||||
class="tag"
|
@click="handleTagClick($event, tag.name)"
|
||||||
|
class="tag tag-clickable"
|
||||||
|
:class="{ 'tag-selected': selectedTags.includes(tag.name) }"
|
||||||
|
:title="`搜索标签: ${tag.name} (按住Ctrl键点击选择多个标签,松开Ctrl键搜索)`"
|
||||||
>
|
>
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -534,6 +537,88 @@ const goBackToArtist = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 选中的标签状态
|
||||||
|
const selectedTags = ref<string[]>([]);
|
||||||
|
const isCtrlPressed = ref(false);
|
||||||
|
|
||||||
|
// 处理标签点击
|
||||||
|
const handleTagClick = (event: MouseEvent, tagName: string) => {
|
||||||
|
// 阻止默认行为
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
console.log('标签点击事件:', {
|
||||||
|
tagName,
|
||||||
|
ctrlKey: event.ctrlKey,
|
||||||
|
metaKey: event.metaKey,
|
||||||
|
isCtrlPressed: isCtrlPressed.value
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果按住Ctrl键,则添加到选中状态(不跳转)
|
||||||
|
if (event.ctrlKey || event.metaKey || isCtrlPressed.value) {
|
||||||
|
console.log('检测到Ctrl键,添加到选中状态');
|
||||||
|
|
||||||
|
// 切换标签的选中状态
|
||||||
|
const index = selectedTags.value.indexOf(tagName);
|
||||||
|
if (index > -1) {
|
||||||
|
// 如果已选中,则取消选中
|
||||||
|
selectedTags.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// 如果未选中,则添加到选中列表
|
||||||
|
selectedTags.value.push(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('当前选中的标签:', selectedTags.value);
|
||||||
|
|
||||||
|
// 保存到sessionStorage
|
||||||
|
sessionStorage.setItem('currentSearchTags', JSON.stringify(selectedTags.value));
|
||||||
|
|
||||||
|
// 不跳转,只是更新选中状态
|
||||||
|
} else {
|
||||||
|
console.log('普通点击,执行单标签搜索');
|
||||||
|
// 普通点击,只搜索当前标签,清除之前的多标签选择
|
||||||
|
selectedTags.value = [];
|
||||||
|
sessionStorage.removeItem('currentSearchTags');
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
path: '/search',
|
||||||
|
query: {
|
||||||
|
mode: 'tags',
|
||||||
|
tag: tagName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Control' || event.key === 'Meta') {
|
||||||
|
isCtrlPressed.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Control' || event.key === 'Meta') {
|
||||||
|
isCtrlPressed.value = false;
|
||||||
|
|
||||||
|
// 当松开Ctrl键时,如果有选中的标签,则跳转到搜索页面
|
||||||
|
if (selectedTags.value.length > 0) {
|
||||||
|
console.log('松开Ctrl键,跳转到搜索页面,标签:', selectedTags.value);
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
path: '/search',
|
||||||
|
query: {
|
||||||
|
mode: 'tags',
|
||||||
|
tags: selectedTags.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空选中状态
|
||||||
|
selectedTags.value = [];
|
||||||
|
sessionStorage.removeItem('currentSearchTags');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 监听路由变化,重新获取作品详情和导航数据
|
// 监听路由变化,重新获取作品详情和导航数据
|
||||||
watch(() => route.params.id, () => {
|
watch(() => route.params.id, () => {
|
||||||
// 清理之前的任务状态
|
// 清理之前的任务状态
|
||||||
@@ -573,11 +658,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 添加键盘事件监听
|
// 添加键盘事件监听
|
||||||
document.addEventListener('keydown', handleKeydown);
|
document.addEventListener('keydown', handleKeydown);
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
document.addEventListener('keyup', handleKeyUp);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件卸载时移除事件监听和清理SSE连接
|
// 组件卸载时移除事件监听和清理SSE连接
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('keydown', handleKeydown);
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
document.removeEventListener('keyup', handleKeyUp);
|
||||||
stopTaskStreaming();
|
stopTaskStreaming();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -841,6 +930,35 @@ onUnmounted(() => {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-clickable {
|
||||||
|
background: #e0f2fe;
|
||||||
|
color: #0369a1;
|
||||||
|
border: 1px solid #bae6fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-clickable:hover {
|
||||||
|
background: #bae6fd;
|
||||||
|
color: #075985;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-selected {
|
||||||
|
background: #3b82f6 !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: #2563eb !important;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-selected:hover {
|
||||||
|
background: #2563eb !important;
|
||||||
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-description {
|
.artwork-description {
|
||||||
|
|||||||
+219
-5
@@ -10,6 +10,9 @@
|
|||||||
<button @click="searchMode = 'keyword'" class="tab-btn" :class="{ active: searchMode === 'keyword' }">
|
<button @click="searchMode = 'keyword'" class="tab-btn" :class="{ active: searchMode === 'keyword' }">
|
||||||
关键词搜索
|
关键词搜索
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="searchMode = 'tags'" class="tab-btn" :class="{ active: searchMode === 'tags' }">
|
||||||
|
标签搜索
|
||||||
|
</button>
|
||||||
<button @click="searchMode = 'artwork'" class="tab-btn" :class="{ active: searchMode === 'artwork' }">
|
<button @click="searchMode = 'artwork'" class="tab-btn" :class="{ active: searchMode === 'artwork' }">
|
||||||
作品ID
|
作品ID
|
||||||
</button>
|
</button>
|
||||||
@@ -30,6 +33,45 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签搜索 -->
|
||||||
|
<div v-if="searchMode === 'tags'" class="tags-search-section">
|
||||||
|
<div class="tags-input-group">
|
||||||
|
<input
|
||||||
|
v-model="tagInput"
|
||||||
|
type="text"
|
||||||
|
placeholder="输入标签,按回车或逗号分隔..."
|
||||||
|
class="search-input"
|
||||||
|
@keyup.enter="addTag"
|
||||||
|
@keyup.space="addTag"
|
||||||
|
@keyup.comma="addTag"
|
||||||
|
/>
|
||||||
|
<button @click="addTag" class="search-btn" :disabled="loading">
|
||||||
|
添加标签
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已添加的标签 -->
|
||||||
|
<div v-if="searchTags.length > 0" class="tags-display">
|
||||||
|
<div class="tags-list">
|
||||||
|
<span
|
||||||
|
v-for="(tag, index) in searchTags"
|
||||||
|
:key="index"
|
||||||
|
class="tag-item"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
<button @click="removeTag(index)" class="tag-remove" title="移除标签">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button @click="handleTagSearch" class="search-btn" :disabled="loading || searchTags.length === 0">
|
||||||
|
搜索标签
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 作品ID搜索 -->
|
<!-- 作品ID搜索 -->
|
||||||
<div v-if="searchMode === 'artwork'" class="search-input-group">
|
<div v-if="searchMode === 'artwork'" class="search-input-group">
|
||||||
<input v-model="artworkId" type="text" placeholder="输入作品ID..." class="search-input"
|
<input v-model="artworkId" type="text" placeholder="输入作品ID..." class="search-input"
|
||||||
@@ -122,8 +164,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import artworkService from '@/services/artwork';
|
import artworkService from '@/services/artwork';
|
||||||
import type { Artwork, SearchParams } from '@/types';
|
import type { Artwork, SearchParams } from '@/types';
|
||||||
@@ -132,13 +174,16 @@ import ErrorMessage from '@/components/common/ErrorMessage.vue';
|
|||||||
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
// 搜索状态
|
// 搜索状态
|
||||||
const searchKeyword = ref('');
|
const searchKeyword = ref('');
|
||||||
const searchMode = ref<'keyword' | 'artwork' | 'artist'>('keyword');
|
const searchMode = ref<'keyword' | 'tags' | 'artwork' | 'artist'>('keyword');
|
||||||
const artworkId = ref('');
|
const artworkId = ref('');
|
||||||
const artistId = ref('');
|
const artistId = ref('');
|
||||||
|
const searchTags = ref<string[]>([]);
|
||||||
|
const tagInput = ref('');
|
||||||
|
|
||||||
// 关键词搜索参数
|
// 关键词搜索参数
|
||||||
const searchType = ref<'all' | 'art' | 'manga' | 'novel'>('all');
|
const searchType = ref<'all' | 'art' | 'manga' | 'novel'>('all');
|
||||||
@@ -191,7 +236,15 @@ const handleSearch = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
if (!searchKeyword.value.trim() || loadingMore.value) {
|
if (loadingMore.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有搜索条件
|
||||||
|
const hasKeyword = searchKeyword.value.trim();
|
||||||
|
const hasTags = searchTags.value.length > 0;
|
||||||
|
|
||||||
|
if (!hasKeyword && !hasTags) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +253,6 @@ const loadMore = async () => {
|
|||||||
offset.value += 30;
|
offset.value += 30;
|
||||||
|
|
||||||
const params: SearchParams = {
|
const params: SearchParams = {
|
||||||
keyword: searchKeyword.value.trim(),
|
|
||||||
type: searchType.value,
|
type: searchType.value,
|
||||||
sort: searchSort.value,
|
sort: searchSort.value,
|
||||||
duration: searchDuration.value,
|
duration: searchDuration.value,
|
||||||
@@ -208,6 +260,13 @@ const loadMore = async () => {
|
|||||||
limit: 30
|
limit: 30
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据当前搜索模式添加相应的参数
|
||||||
|
if (searchMode.value === 'keyword' && hasKeyword) {
|
||||||
|
params.keyword = searchKeyword.value.trim();
|
||||||
|
} else if (searchMode.value === 'tags' && hasTags) {
|
||||||
|
params.tags = searchTags.value;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await artworkService.searchArtworks(params);
|
const response = await artworkService.searchArtworks(params);
|
||||||
|
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
@@ -261,9 +320,91 @@ const handleArtistSearch = () => {
|
|||||||
router.push(`/artist/${id}`);
|
router.push(`/artist/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 标签相关方法
|
||||||
|
const addTag = () => {
|
||||||
|
const tag = tagInput.value.trim();
|
||||||
|
if (tag && !searchTags.value.includes(tag)) {
|
||||||
|
searchTags.value.push(tag);
|
||||||
|
tagInput.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTag = (index: number) => {
|
||||||
|
searchTags.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTagSearch = async () => {
|
||||||
|
if (searchTags.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
offset.value = 0;
|
||||||
|
hasSearched.value = true;
|
||||||
|
|
||||||
|
const params: SearchParams = {
|
||||||
|
tags: searchTags.value,
|
||||||
|
type: searchType.value,
|
||||||
|
sort: searchSort.value,
|
||||||
|
duration: searchDuration.value,
|
||||||
|
offset: 0,
|
||||||
|
limit: 30
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await artworkService.searchArtworks(params);
|
||||||
|
|
||||||
|
if (response.success && response.data) {
|
||||||
|
searchResults.value = response.data.artworks;
|
||||||
|
totalResults.value = response.data.total;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '标签搜索失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : '标签搜索失败';
|
||||||
|
console.error('标签搜索失败:', err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const clearError = () => {
|
const clearError = () => {
|
||||||
error.value = null;
|
error.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听路由变化,处理URL参数
|
||||||
|
watch(() => route.query, () => {
|
||||||
|
const urlMode = route.query.mode as string;
|
||||||
|
const urlTag = route.query.tag as string;
|
||||||
|
const urlTags = route.query.tags;
|
||||||
|
|
||||||
|
if (urlMode === 'tags') {
|
||||||
|
// 自动设置标签搜索模式
|
||||||
|
searchMode.value = 'tags';
|
||||||
|
|
||||||
|
if (urlTags) {
|
||||||
|
// 处理多个标签
|
||||||
|
if (Array.isArray(urlTags)) {
|
||||||
|
searchTags.value = urlTags.filter(tag => tag !== null) as string[];
|
||||||
|
} else {
|
||||||
|
searchTags.value = urlTags ? [urlTags] : [];
|
||||||
|
}
|
||||||
|
// 保存到sessionStorage
|
||||||
|
sessionStorage.setItem('currentSearchTags', JSON.stringify(searchTags.value));
|
||||||
|
} else if (urlTag) {
|
||||||
|
// 处理单个标签
|
||||||
|
searchTags.value = [urlTag];
|
||||||
|
// 清除sessionStorage中的多标签选择
|
||||||
|
sessionStorage.removeItem('currentSearchTags');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有标签,自动执行搜索
|
||||||
|
if (searchTags.value.length > 0) {
|
||||||
|
handleTagSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -368,6 +509,66 @@ const clearError = () => {
|
|||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 标签搜索样式 */
|
||||||
|
.tags-search-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-display {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: #e0f2fe;
|
||||||
|
color: #0369a1;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-remove {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #0369a1;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-remove:hover {
|
||||||
|
background: #0369a1;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-remove svg {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.search-filters {
|
.search-filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -496,5 +697,18 @@ const clearError = () => {
|
|||||||
.artworks-grid {
|
.artworks-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tags-input-group {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-list {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.375rem 0.625rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user