aslan-h5/src/views/CoinSellerSearchView.vue
2025-08-22 15:18:48 +08:00

471 lines
9.5 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>
<div class="coin-seller-search gradient-background-circles">
<MobileHeader title="Search user" />
<div class="content">
<!-- 搜索框 -->
<div class="search-section">
<div class="search-box">
<span class="search-icon">🔍</span>
<input
v-model="searchQuery"
type="text"
placeholder="Please enter the host ID"
class="search-input"
@keyup.enter="handleEnterPress"
@input="handleInputChange"
/>
<button
v-if="searchQuery"
@click="clearSearch"
class="clear-btn"
>
×
</button>
<button
class="action-btn"
:class="{ 'confirm': hasValidSelection }"
@click="handleActionButton"
:disabled="isSearching"
>
{{ getActionButtonText() }}
</button>
</div>
</div>
<!-- 搜索结果 -->
<div v-if="searchResults.length > 0" class="results-section">
<div
v-for="user in searchResults"
:key="user.id"
class="user-item"
:class="{ 'selected': selectedUser?.id === user.id }"
@click="toggleUserSelection(user)"
>
<div class="user-info">
<div class="user-avatar">
<img
v-if="user.userAvatar"
:src="user.userAvatar"
:alt="user.name"
class="avatar-image"
@error="(e) => e.target.style.display = 'none'"
/>
<span v-if="!user.userAvatar" class="avatar-text">{{ user.name.charAt(0) }}</span>
</div>
<div class="user-details">
<h4>{{ user.name }}</h4>
<p>ID: {{ user.id }}</p>
</div>
</div>
<div class="add-icon">
<span></span>
</div>
</div>
</div>
<!-- 加载状态 -->
<div v-if="isSearching" class="loading-state">
<div class="loading-spinner"></div>
<p>Searching...</p>
</div>
<!-- 空状态 -->
<div v-else-if="searchQuery && !isSearching && searchResults.length === 0" class="empty-state">
<div class="empty-icon">🔍</div>
<p>No users found</p>
<span>Try searching with a different ID</span>
</div>
<!-- 搜索提示 -->
<div v-else-if="!searchQuery" class="search-hint">
<div class="hint-icon">👤</div>
<p>Search for users</p>
<span>Enter host ID to find users</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { setSelectedUser } from '../utils/coinSellerStore.js'
import { searchFreightUser } from '../api/wallet.js'
const router = useRouter()
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)
const hasSearched = ref(false)
const selectedUser = ref(null)
// Computed properties
const hasValidSelection = computed(() => {
return selectedUser.value !== null
})
// Handle input changes
const handleInputChange = () => {
if (!searchQuery.value.trim()) {
searchResults.value = []
hasSearched.value = false
selectedUser.value = null
}
}
// Handle enter key press
const handleEnterPress = () => {
if (searchQuery.value.trim()) {
performSearch()
}
}
// Handle action button click
const handleActionButton = () => {
if (hasValidSelection.value) {
confirmSelection()
} else if (searchQuery.value.trim() && !hasSearched.value) {
performSearch()
} else {
cancelSearch()
}
}
// Get button text
const getActionButtonText = () => {
if (isSearching.value) return 'Searching...'
if (hasValidSelection.value) return 'Confirm'
if (searchQuery.value.trim() && !hasSearched.value) return 'Search'
return 'Cancel'
}
// Perform search
const performSearch = async () => {
if (!searchQuery.value.trim()) {
searchResults.value = []
return
}
isSearching.value = true
try {
const response = await searchFreightUser(searchQuery.value.trim())
if (response && response.status && response.body && response.body.userProfile) {
const profile = response.body.userProfile
searchResults.value = [{
id: profile.account || profile.id,
userId: profile.id, // 保存数值型ID用于接口调用
name: profile.userNickname || 'User',
userNickname: profile.userNickname,
account: profile.account,
userAvatar: profile.userAvatar
}]
} else {
searchResults.value = []
}
hasSearched.value = true
selectedUser.value = null // Reset selection
} catch (error) {
console.error('Search user failed:', error)
searchResults.value = []
hasSearched.value = true
} finally {
isSearching.value = false
}
}
// Clear search
const clearSearch = () => {
searchQuery.value = ''
searchResults.value = []
hasSearched.value = false
selectedUser.value = null
}
// Cancel search
const cancelSearch = () => {
router.go(-1)
}
// Toggle user selection
const toggleUserSelection = (user) => {
if (selectedUser.value?.id === user.id) {
selectedUser.value = null // Deselect
} else {
selectedUser.value = user // Select user
}
}
// Confirm selection
const confirmSelection = () => {
if (selectedUser.value) {
// Save selected user info to global state
setSelectedUser({
id: selectedUser.value.id,
userId: selectedUser.value.userId, // Numeric ID
name: selectedUser.value.name,
userNickname: selectedUser.value.userNickname,
account: selectedUser.value.account,
userAvatar: selectedUser.value.userAvatar
})
// Go back to previous page
router.go(-1)
}
}
</script>
<style scoped>
.coin-seller-search {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.content {
padding: 16px;
position: relative;
z-index: 2;
}
/* 搜索部分 */
.search-section {
margin-bottom: 20px;
}
.search-box {
display: flex;
align-items: center;
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 8px 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.search-icon {
margin-right: 8px;
color: #9ca3af;
}
.search-input {
flex: 1;
border: none;
background: none;
outline: none;
font-size: 14px;
color: #333;
}
.search-input::placeholder {
color: #9ca3af;
}
.clear-btn {
background: none;
border: none;
font-size: 18px;
color: #9ca3af;
cursor: pointer;
padding: 0 8px;
}
.action-btn {
background: none;
border: none;
color: #8B5CF6;
font-size: 14px;
font-weight: 500;
cursor: pointer;
padding: 4px 8px;
margin-left: 8px;
}
.action-btn:active {
color: #7C3AED;
}
.action-btn.confirm {
background-color: #8B5CF6;
color: white;
border-radius: 6px;
padding: 6px 12px;
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 搜索结果 */
.results-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.user-item {
background-color: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: space-between;
}
.user-item:active {
transform: scale(0.98);
}
.user-item.selected {
border: 2px solid #8B5CF6;
background-color: #F3F4F6;
}
.user-item.selected .add-icon {
color: #8B5CF6;
font-weight: bold;
}
.user-info {
display: flex;
align-items: center;
flex: 1;
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: 25px;
background-color: #F59E0B;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
font-weight: 600;
margin-right: 12px;
overflow: hidden;
position: relative;
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 25px;
}
.avatar-text {
color: white;
font-size: 18px;
font-weight: 600;
}
.user-details h4 {
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.user-details p {
margin: 0;
font-size: 14px;
color: #666;
}
.add-icon {
color: #10B981;
font-size: 20px;
}
/* 加载状态 */
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #666;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #F59E0B;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-state p {
margin: 0;
font-size: 14px;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #666;
text-align: center;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-state p {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 500;
}
.empty-state span {
font-size: 14px;
color: #9ca3af;
}
/* 搜索提示 */
.search-hint {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #666;
text-align: center;
}
.hint-icon {
font-size: 48px;
margin-bottom: 16px;
}
.search-hint p {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 500;
}
.search-hint span {
font-size: 14px;
color: #9ca3af;
}
</style>