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,238 @@
<template>
<Card class="template-panel">
<view class="panel-header">
<Typography variant="h4" weight="semibold">搜索模板</Typography>
<Button size="small" type="text" @click="handleRefresh">
<Icon name="refresh" size="16rpx" color="rgba(113, 113, 122, 255)" />
</Button>
</view>
<view class="search-bar">
<view class="search-input">
<Icon name="search" size="16rpx" color="rgba(113, 113, 122, 255)" />
<input
v-model="searchKeyword"
placeholder="搜索模板"
class="input-field"
/>
</view>
</view>
<view class="category-tabs">
<view
v-for="category in categories"
:key="category"
class="category-tab"
:class="{ 'category-tab--active': selectedCategory === category }"
@click="selectCategory(category)"
>
<Typography variant="body" class="category-label">{{ category }}</Typography>
</view>
</view>
<view v-if="filteredTemplates.length === 0" class="empty-templates">
<EmptyState
icon="empty"
title="暂无模板"
description="没有找到符合条件的模板"
size="small"
/>
</view>
<view v-else class="template-list">
<view
v-for="template in filteredTemplates"
:key="template.id"
class="template-item"
@click="handleSelectTemplate(template)"
>
<view class="template-content">
<Typography variant="body" weight="semibold" class="template-name">
{{ template.name }}
</Typography>
<Typography variant="caption" class="template-description">
{{ template.description }}
</Typography>
<Typography variant="caption" class="template-category">
{{ template.category }}
</Typography>
</view>
<Icon name="right" size="16rpx" color="rgba(113, 113, 122, 255)" />
</view>
</view>
</Card>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { SearchRequest, SearchTemplate } from '../../types/search'
import templateService from '../../services/templateService'
import Card from '../Card/Card.vue'
import Button from '../Button/Button.vue'
import Icon from '../Icon/Icon.vue'
import Typography from '../Typography/Typography.vue'
import EmptyState from '../EmptyState/index.vue'
const emit = defineEmits<{
select: [condition: SearchRequest]
}>()
const searchKeyword = ref('')
const selectedCategory = ref('全部')
const templates = ref<SearchTemplate[]>([])
const categories = computed(() => {
const allCategories = templateService.getCategories()
return ['全部', ...allCategories]
})
const filteredTemplates = computed(() => {
let result = templates.value
if (selectedCategory.value !== '全部') {
result = templateService.getTemplatesByCategory(selectedCategory.value)
}
if (searchKeyword.value.trim()) {
result = templateService.searchTemplates(searchKeyword.value.trim())
}
return result
})
onMounted(() => {
loadTemplates()
})
function loadTemplates() {
templates.value = templateService.getTemplates()
}
function handleRefresh() {
loadTemplates()
}
function selectCategory(category: string) {
selectedCategory.value = category
}
function handleSelectTemplate(template: SearchTemplate) {
emit('select', template.condition)
}
</script>
<style scoped lang="scss">
.template-panel {
margin-bottom: 16px;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.search-bar {
margin-bottom: 16px;
}
.search-input {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: rgba(244, 244, 245, 255);
border-radius: var(--radius-md);
}
.input-field {
flex: 1;
border: none;
background: transparent;
font-size: 14px;
color: var(--color-text);
outline: none;
&::placeholder {
color: var(--color-text-secondary);
}
}
.category-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.category-tab {
padding: 6px 12px;
background: rgba(244, 244, 245, 255);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition-fast);
&:active {
background: rgba(230, 225, 220, 255);
}
}
.category-tab--active {
background: var(--color-primary);
}
.category-label {
color: var(--color-text);
font-size: 13px;
.category-tab--active & {
color: #FFFFFF;
}
}
.empty-templates {
padding: 24px 0;
}
.template-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.template-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 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);
}
}
.template-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.template-name {
color: var(--color-text);
}
.template-description {
color: var(--color-text-secondary);
}
.template-category {
color: var(--color-primary);
font-weight: 500;
}
</style>