feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,271 @@
<template>
<view class="page">
<view class="page-header">
<Typography variant="h3" weight="bold" class="page-title">黄历搜索</Typography>
</view>
<view class="page-content">
<TemplatePanel @select="selectTemplate" />
<SearchConditionPanel
:conditions="searchRequest.conditions"
:days="searchRequest.days"
@update:conditions="updateConditions"
@update:days="updateDays"
@search="performSearch"
/>
<view v-if="loading" class="loading-container">
<LoadingIndicator text="搜索中..." />
</view>
<view v-else-if="error" class="error-container">
<Typography variant="body" class="error-text">{{ error }}</Typography>
<Button @click="performSearch">重试</Button>
</view>
<SearchResultList
v-else-if="searchResults.length > 0"
:results="searchResults"
:sortBy="searchRequest.sortBy"
:sortOrder="searchRequest.sortOrder"
@update:sortBy="updateSortBy"
@update:sortOrder="updateSortOrder"
@loadMore="loadMoreResults"
/>
<EmptyState
v-else
:showAction="hasSearched"
actionText="重新搜索"
@action="performSearch"
/>
</view>
<BottomNavigation currentTab="almanac" />
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { SearchCondition, SearchRequest, SearchResult } from '../../types/search'
import BottomNavigation from '../../components/BottomNavigation/BottomNavigation.vue'
import TemplatePanel from '../../components/TemplatePanel/index.vue'
import SearchConditionPanel from '../../components/SearchConditionPanel/index.vue'
import SearchResultList from '../../components/SearchResultList/index.vue'
import LoadingIndicator from '../../components/LoadingIndicator/index.vue'
import EmptyState from '../../components/EmptyState/index.vue'
import Typography from '../../components/Typography/Typography.vue'
import Button from '../../components/Button/Button.vue'
import searchService from '../../services/searchService'
const loading = ref(false)
const error = ref('')
const hasSearched = ref(false)
const searchResults = ref<SearchResult[]>([])
const searchMode = ref<'keyword' | 'advanced'>('advanced')
const searchRequest = ref<SearchRequest>({
conditions: [],
days: 30,
sortBy: 'date',
sortOrder: 'asc'
})
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
const options = currentPage?.options || {}
if (options.keyword) {
searchMode.value = 'keyword'
handleKeywordSearch(decodeURIComponent(options.keyword))
} else if (options.conditions) {
try {
searchRequest.value.conditions = JSON.parse(decodeURIComponent(options.conditions))
if (options.days) {
searchRequest.value.days = parseInt(options.days)
}
performSearch()
} catch (err) {
console.error('解析搜索条件失败:', err)
}
}
})
const handleKeywordSearch = async (keyword: string) => {
if (!keyword.trim()) {
error.value = '请输入搜索关键词'
return
}
loading.value = true
error.value = ''
hasSearched.value = true
try {
const results = await searchService.searchByKeyword(keyword)
searchResults.value = results
if (results.length === 0) {
error.value = '没有找到符合条件的结果'
}
} catch (err: any) {
console.error('搜索失败:', err)
error.value = '搜索失败,请稍后重试'
} finally {
loading.value = false
}
}
const updateConditions = (conditions: SearchCondition[]) => {
searchRequest.value.conditions = conditions
}
const updateDays = (days: number) => {
searchRequest.value.days = days
}
const updateSortBy = (sortBy: 'date' | 'matchCount') => {
searchRequest.value.sortBy = sortBy
sortResults()
}
const updateSortOrder = (sortOrder: 'asc' | 'desc') => {
searchRequest.value.sortOrder = sortOrder
sortResults()
}
const sortResults = () => {
const sorted = [...searchResults.value].sort((a, b) => {
let comparison = 0
if (searchRequest.value.sortBy === 'date') {
comparison = new Date(a.date).getTime() - new Date(b.date).getTime()
} else if (searchRequest.value.sortBy === 'matchCount') {
comparison = b.matchCount - a.matchCount
}
return searchRequest.value.sortOrder === 'desc' ? -comparison : comparison
})
searchResults.value = sorted
}
const performSearch = async () => {
if (searchRequest.value.conditions.length === 0) {
error.value = '请至少添加一个搜索条件'
return
}
loading.value = true
error.value = ''
hasSearched.value = true
try {
const results = await searchService.search(searchRequest.value)
searchResults.value = results
if (results.length === 0) {
error.value = '没有找到符合条件的结果'
}
} catch (err: any) {
console.error('搜索失败:', err)
error.value = '搜索失败,请稍后重试'
} finally {
loading.value = false
}
}
const loadMoreResults = () => {
console.log('加载更多结果')
}
const selectTemplate = (condition: SearchRequest) => {
searchRequest.value = { ...condition }
performSearch()
}
</script>
<style scoped lang="scss">
.page {
min-height: 100vh;
background-color: rgba(244, 244, 245, 0.3);
padding-bottom: 80px;
}
.page-header {
background-color: rgba(255, 255, 255, 255);
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
position: sticky;
top: 0;
z-index: 10;
}
.page-title {
color: rgba(44, 24, 16, 255);
font-size: 18px;
line-height: 28px;
font-weight: 700;
}
.page-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
}
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
gap: 16px;
}
.error-text {
color: rgba(196, 30, 58, 255);
font-size: 14px;
line-height: 20px;
text-align: center;
}
@media screen and (max-width: 375px) {
.page-content {
padding: 12px;
gap: 12px;
}
.page-header {
padding: 14px;
}
.page-title {
font-size: 16px;
line-height: 26px;
}
.loading-container,
.error-container {
padding: 32px 16px;
}
.error-text {
font-size: 13px;
line-height: 18px;
}
}
</style>