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,176 @@
<template>
<Card class="search-history-panel">
<view class="panel-header">
<Typography variant="h4" weight="semibold">搜索历史</Typography>
<Button size="small" type="text" @click="handleClearHistory">
<Typography variant="caption" color="rgba(196, 30, 58, 255)">清除历史</Typography>
</Button>
</view>
<view v-if="history.length === 0" class="empty-history">
<EmptyState
icon="empty"
title="暂无搜索历史"
description="执行搜索后,搜索条件将显示在这里"
size="small"
/>
</view>
<view v-else class="history-list">
<view
v-for="item in history"
:key="item.id"
class="history-item"
@click="handleSelectHistory(item)"
>
<view class="history-content">
<Typography variant="body" class="history-title">
{{ formatHistoryTitle(item.condition) }}
</Typography>
<Typography variant="caption" class="history-time">
{{ formatTime(item.createdAt) }}
</Typography>
</view>
<Icon name="right" size="16rpx" color="rgba(113, 113, 122, 255)" />
</view>
</view>
</Card>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { SearchRequest } from '../../types/search'
import searchService from '../../services/searchService'
import Card from '../Card/Card.vue'
import Button from '../Button/Button.vue'
import Typography from '../Typography/Typography.vue'
import Icon from '../Icon/Icon.vue'
import EmptyState from '../EmptyState/index.vue'
interface HistoryItem {
id: string
condition: SearchRequest
createdAt: string
}
const emit = defineEmits<{
select: [condition: SearchRequest]
}>()
const history = ref<HistoryItem[]>([])
onMounted(() => {
loadHistory()
})
function loadHistory() {
history.value = searchService.getSearchHistory()
}
function handleSelectHistory(item: HistoryItem) {
emit('select', item.condition)
}
function handleClearHistory() {
uni.showModal({
title: '确认清除',
content: '确定要清除所有搜索历史吗?',
success: (res) => {
if (res.confirm) {
searchService.clearSearchHistory()
history.value = []
}
}
})
}
function formatHistoryTitle(condition: SearchRequest): string {
const conditionCount = condition.conditions.length
const days = condition.days
let title = `${conditionCount}个条件,${days}天范围`
if (conditionCount > 0) {
const firstCondition = condition.conditions[0]
const type = firstCondition.type === 'suitable' ? '宜' : '忌'
const items = firstCondition.items.slice(0, 2).join('、')
title += `(${type}${items}${firstCondition.items.length > 2 ? '...' : ''})`
}
return title
}
function formatTime(timeStr: string): string {
const date = new Date(timeStr)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
if (diffHours < 1) {
return '刚刚'
} else if (diffHours < 24) {
return `${diffHours}小时前`
} else {
const diffDays = Math.floor(diffHours / 24)
if (diffDays < 7) {
return `${diffDays}天前`
} else {
return date.toLocaleDateString()
}
}
}
</script>
<style scoped lang="scss">
.search-history-panel {
margin-bottom: 16px;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.empty-history {
padding: 24px 0;
}
.history-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.history-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: rgba(244, 244, 245, 255);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
&:active {
background: rgba(230, 225, 220, 255);
}
}
.history-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.history-title {
color: var(--color-text);
}
.history-time {
color: var(--color-text-secondary);
}
</style>