|
|
@@ -5,6 +5,7 @@ import DelSvg from '~/assets/icons/search/del.svg'
|
|
|
import type { PlaymateSearchVo } from '~/types/api'
|
|
|
import { playmateApi } from '~/api/playmate'
|
|
|
import { useApi } from '~/composables/useApi'
|
|
|
+import { SEARCH_HISTORY_KEY } from '~/constants'
|
|
|
|
|
|
defineOptions({
|
|
|
name: 'SearchPage',
|
|
|
@@ -27,6 +28,42 @@ const listFinished = ref(false)
|
|
|
const searched = ref(false)
|
|
|
const listRef = ref<{ check: () => void } | null>(null)
|
|
|
|
|
|
+const MAX_HISTORY = 10
|
|
|
+const searchHistory = ref<string[]>([])
|
|
|
+
|
|
|
+function loadSearchHistory() {
|
|
|
+ if (import.meta.client) {
|
|
|
+ try {
|
|
|
+ const raw = localStorage.getItem(SEARCH_HISTORY_KEY)
|
|
|
+ const parsed = raw ? JSON.parse(raw) : []
|
|
|
+ searchHistory.value = Array.isArray(parsed) ? parsed.slice(0, MAX_HISTORY) : []
|
|
|
+ }
|
|
|
+ catch {
|
|
|
+ searchHistory.value = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function saveSearchHistory(keyword: string) {
|
|
|
+ const trimmed = keyword.trim()
|
|
|
+ if (!trimmed)
|
|
|
+ return
|
|
|
+ const next = [trimmed, ...searchHistory.value.filter(k => k !== trimmed)].slice(0, MAX_HISTORY)
|
|
|
+ searchHistory.value = next
|
|
|
+ if (import.meta.client) {
|
|
|
+ try {
|
|
|
+ localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(next))
|
|
|
+ }
|
|
|
+ catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadSearchHistory()
|
|
|
+})
|
|
|
+
|
|
|
const handleBack = () => {
|
|
|
router.back()
|
|
|
}
|
|
|
@@ -95,10 +132,18 @@ const loadList = async (isLoadMore = false) => {
|
|
|
}
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
+ const q = keyword.value.trim()
|
|
|
+ if (q)
|
|
|
+ saveSearchHistory(q)
|
|
|
resetList()
|
|
|
loadList(false)
|
|
|
}
|
|
|
|
|
|
+const handleHistoryClick = (item: string) => {
|
|
|
+ keyword.value = item
|
|
|
+ handleSearch()
|
|
|
+}
|
|
|
+
|
|
|
const handleUserClick = (userNo: string) => {
|
|
|
router.push(`/user/profile?id=${userNo}`)
|
|
|
}
|
|
|
@@ -146,70 +191,93 @@ const handleUserClick = (userNo: string) => {
|
|
|
</button>
|
|
|
</header>
|
|
|
|
|
|
- <!-- Related Contacts -->
|
|
|
<section class="search-page__content">
|
|
|
- <h2
|
|
|
- v-if="searched"
|
|
|
- class="search-page__section-title"
|
|
|
- >
|
|
|
- {{ t('search.relatedContacts') }}
|
|
|
- </h2>
|
|
|
-
|
|
|
+ <!-- History (Figma: 搜索/记录标签) -->
|
|
|
<div
|
|
|
- v-if="listLoading && !list.length"
|
|
|
- class="search-page__loading"
|
|
|
+ v-if="searchHistory.length > 0"
|
|
|
+ class="search-page__history"
|
|
|
>
|
|
|
- {{ t('common.loading') }}
|
|
|
- </div>
|
|
|
- <div
|
|
|
- v-else-if="searched && !list.length"
|
|
|
- class="search-page__empty"
|
|
|
- >
|
|
|
- {{ t('search.noResult') }}
|
|
|
- </div>
|
|
|
- <van-list
|
|
|
- v-else-if="searched"
|
|
|
- ref="listRef"
|
|
|
- v-model:loading="listLoading"
|
|
|
- :finished="listFinished"
|
|
|
- :finished-text="t('common.noMoreData')"
|
|
|
- :loading-text="t('common.loading')"
|
|
|
- :immediate-check="false"
|
|
|
- @load="loadList(true)"
|
|
|
- >
|
|
|
- <ul class="search-page__list">
|
|
|
- <li
|
|
|
- v-for="item in list"
|
|
|
- :key="item.userNo"
|
|
|
- class="search-page__row"
|
|
|
- @click="handleUserClick(item.userNo)"
|
|
|
+ <h2 class="search-page__section-title">
|
|
|
+ {{ t('search.history') }}
|
|
|
+ </h2>
|
|
|
+ <div class="search-page__history-tags">
|
|
|
+ <button
|
|
|
+ v-for="item in searchHistory"
|
|
|
+ :key="item"
|
|
|
+ type="button"
|
|
|
+ class="search-page__history-tag"
|
|
|
+ @click="handleHistoryClick(item)"
|
|
|
>
|
|
|
- <div class="search-page__row-left">
|
|
|
- <div class="search-page__avatar-wrap">
|
|
|
- <NuxtImg
|
|
|
- :src="item.avatar"
|
|
|
- :alt="item.nickname"
|
|
|
- class="search-page__avatar"
|
|
|
- loading="lazy"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="search-page__info">
|
|
|
- <div class="search-page__name-row">
|
|
|
- <span class="search-page__name">{{ item.nickname }}</span>
|
|
|
- <CommonGender
|
|
|
- :gender="item.gender"
|
|
|
- :age="item.age"
|
|
|
+ {{ item }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Related Contacts -->
|
|
|
+ <div class="search-page__related">
|
|
|
+ <h2
|
|
|
+ v-if="searched"
|
|
|
+ class="search-page__section-title"
|
|
|
+ >
|
|
|
+ {{ t('search.relatedContacts') }}
|
|
|
+ </h2>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="listLoading && !list.length"
|
|
|
+ class="search-page__loading"
|
|
|
+ >
|
|
|
+ {{ t('common.loading') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-else-if="searched && !list.length"
|
|
|
+ class="search-page__empty"
|
|
|
+ >
|
|
|
+ {{ t('search.noResult') }}
|
|
|
+ </div>
|
|
|
+ <van-list
|
|
|
+ v-else-if="searched"
|
|
|
+ ref="listRef"
|
|
|
+ v-model:loading="listLoading"
|
|
|
+ :finished="listFinished"
|
|
|
+ :finished-text="t('common.noMoreData')"
|
|
|
+ :loading-text="t('common.loading')"
|
|
|
+ :immediate-check="false"
|
|
|
+ @load="loadList(true)"
|
|
|
+ >
|
|
|
+ <ul class="search-page__list">
|
|
|
+ <li
|
|
|
+ v-for="item in list"
|
|
|
+ :key="item.userNo"
|
|
|
+ class="search-page__row"
|
|
|
+ @click="handleUserClick(item.userNo)"
|
|
|
+ >
|
|
|
+ <div class="search-page__row-left">
|
|
|
+ <div class="search-page__avatar-wrap">
|
|
|
+ <NuxtImg
|
|
|
+ :src="item.avatar"
|
|
|
+ :alt="item.nickname"
|
|
|
+ class="search-page__avatar"
|
|
|
+ loading="lazy"
|
|
|
/>
|
|
|
</div>
|
|
|
- <div class="search-page__meta">
|
|
|
- <span>ID {{ item.userNo }}</span>
|
|
|
- <!-- <span>{{ t('search.fansCount', { count: item.fansCount ?? 0 }) }}</span> -->
|
|
|
+ <div class="search-page__info">
|
|
|
+ <div class="search-page__name-row">
|
|
|
+ <span class="search-page__name">{{ item.nickname }}</span>
|
|
|
+ <CommonGender
|
|
|
+ :gender="item.gender"
|
|
|
+ :age="item.age"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="search-page__meta">
|
|
|
+ <span>ID {{ item.userNo }}</span>
|
|
|
+ <!-- <span>{{ t('search.fansCount', { count: item.fansCount ?? 0 }) }}</span> -->
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
- </van-list>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </van-list>
|
|
|
+ </div>
|
|
|
</section>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -300,6 +368,32 @@ const handleUserClick = (userNo: string) => {
|
|
|
.search-page__content {
|
|
|
padding: 0 16px;
|
|
|
padding-top: 10px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-page__history-tags {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-page__history-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 22px;
|
|
|
+ padding: 3px 8px;
|
|
|
+ border: 1px solid #c9cdd4;
|
|
|
+ border-radius: 200px;
|
|
|
+ background: transparent;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 14px;
|
|
|
+ color: #454a50;
|
|
|
+ -webkit-tap-highlight-color: transparent;
|
|
|
+ max-width: 120px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.search-page__section-title {
|