Generating commit message...
This commit is contained in:
193
admin-system/src/pages/Login.vue
Normal file
193
admin-system/src/pages/Login.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-form">
|
||||
<div class="login-header">
|
||||
<h1>{{ appName }}</h1>
|
||||
<p>欢迎回来,请登录您的账号</p>
|
||||
</div>
|
||||
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="login"
|
||||
autocomplete="off"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请输入用户名!' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
size="large"
|
||||
placeholder="用户名"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请输入密码!' }]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
size="large"
|
||||
placeholder="密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
block
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>© 2025 结伴客系统 - 后台管理系统 v{{ appVersion }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
interface FormState {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const loading = ref(false)
|
||||
|
||||
const formState = reactive<FormState>({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || '结伴客后台管理系统'
|
||||
const appVersion = import.meta.env.VITE_APP_VERSION || '1.0.0'
|
||||
|
||||
const onFinish = async (values: FormState) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 模拟登录过程
|
||||
console.log('登录信息:', values)
|
||||
|
||||
// TODO: 调用真实登录接口
|
||||
// const response = await authAPI.login(values)
|
||||
|
||||
// 模拟登录成功
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 保存token
|
||||
localStorage.setItem('admin_token', 'mock_token_123456')
|
||||
|
||||
// 更新用户状态
|
||||
appStore.setUser({
|
||||
id: 1,
|
||||
username: values.username,
|
||||
nickname: '管理员',
|
||||
role: 'admin'
|
||||
})
|
||||
|
||||
message.success('登录成功!')
|
||||
|
||||
// 跳转到首页或重定向页面
|
||||
const redirect = router.currentRoute.value.query.redirect as string
|
||||
router.push(redirect || '/dashboard')
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
message.error('登录失败,请检查用户名和密码')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('表单验证失败:', errorInfo)
|
||||
message.warning('请填写完整的登录信息')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.login-footer p {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn) {
|
||||
border-radius: 6px;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
688
admin-system/src/pages/animal/index.vue
Normal file
688
admin-system/src/pages/animal/index.vue
Normal file
@@ -0,0 +1,688 @@
|
||||
<template>
|
||||
<div class="animal-management">
|
||||
<a-page-header
|
||||
title="动物管理"
|
||||
sub-title="管理动物信息和认领记录"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新增动物
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<a-tab-pane key="animals" tab="动物列表">
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="关键词">
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="动物名称/编号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.type"
|
||||
placeholder="全部类型"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="alpaca">羊驼</a-select-option>
|
||||
<a-select-option value="dog">狗狗</a-select-option>
|
||||
<a-select-option value="cat">猫咪</a-select-option>
|
||||
<a-select-option value="rabbit">兔子</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="available">可认领</a-select-option>
|
||||
<a-select-option value="claimed">已认领</a-select-option>
|
||||
<a-select-option value="reserved">预留中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 动物表格 -->
|
||||
<a-table
|
||||
:columns="animalColumns"
|
||||
:data-source="animalList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image
|
||||
:width="60"
|
||||
:height="60"
|
||||
:src="record.image_url"
|
||||
:fallback="fallbackImage"
|
||||
style="border-radius: 6px;"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'price'">
|
||||
¥{{ record.price }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleViewAnimal(record)">
|
||||
<EyeOutlined />
|
||||
查看
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="handleEditAnimal(record)">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" danger @click="handleDeleteAnimal(record)">
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="claims" tab="认领记录">
|
||||
<a-card>
|
||||
<!-- 认领记录搜索 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="claimSearchForm">
|
||||
<a-form-item label="关键词">
|
||||
<a-input
|
||||
v-model:value="claimSearchForm.keyword"
|
||||
placeholder="用户/动物名称"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="claimSearchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleClaimSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleClaimReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 认领记录表格 -->
|
||||
<a-table
|
||||
:columns="claimColumns"
|
||||
:data-source="claimList"
|
||||
:loading="claimLoading"
|
||||
:pagination="claimPagination"
|
||||
:row-key="record => record.id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'animal_image'">
|
||||
<a-image
|
||||
:width="40"
|
||||
:height="40"
|
||||
:src="record.animal_image"
|
||||
:fallback="fallbackImage"
|
||||
style="border-radius: 4px;"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getClaimStatusColor(record.status)">
|
||||
{{ getClaimStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-button size="small" type="primary" @click="handleApproveClaim(record)">
|
||||
<CheckOutlined />
|
||||
通过
|
||||
</a-button>
|
||||
<a-button size="small" danger @click="handleRejectClaim(record)">
|
||||
<CloseOutlined />
|
||||
拒绝
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-button size="small" @click="handleViewClaim(record)">
|
||||
<EyeOutlined />
|
||||
详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
PlusOutlined,
|
||||
EyeOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface Animal {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
breed: string
|
||||
age: number
|
||||
price: number
|
||||
status: string
|
||||
image_url: string
|
||||
description: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface AnimalClaim {
|
||||
id: number
|
||||
animal_id: number
|
||||
animal_name: string
|
||||
animal_image: string
|
||||
user_name: string
|
||||
user_phone: string
|
||||
status: string
|
||||
applied_at: string
|
||||
processed_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
keyword: string
|
||||
type: string
|
||||
status: string
|
||||
}
|
||||
|
||||
interface ClaimSearchForm {
|
||||
keyword: string
|
||||
status: string
|
||||
}
|
||||
|
||||
const activeTab = ref('animals')
|
||||
const loading = ref(false)
|
||||
const claimLoading = ref(false)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
keyword: '',
|
||||
type: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const claimSearchForm = reactive<ClaimSearchForm>({
|
||||
keyword: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const animalList = ref<Animal[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '小白',
|
||||
type: 'alpaca',
|
||||
breed: '苏利羊驼',
|
||||
age: 2,
|
||||
price: 1000,
|
||||
status: 'available',
|
||||
image_url: 'https://api.dicebear.com/7.x/bottts/svg?seed=alpaca',
|
||||
description: '温顺可爱的羊驼',
|
||||
created_at: '2024-01-10'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '旺财',
|
||||
type: 'dog',
|
||||
breed: '金毛寻回犬',
|
||||
age: 1,
|
||||
price: 800,
|
||||
status: 'claimed',
|
||||
image_url: 'https://api.dicebear.com/7.x/bottts/svg?seed=dog',
|
||||
description: '活泼聪明的金毛',
|
||||
created_at: '2024-02-15'
|
||||
}
|
||||
])
|
||||
|
||||
const claimList = ref<AnimalClaim[]>([
|
||||
{
|
||||
id: 1,
|
||||
animal_id: 1,
|
||||
animal_name: '小白',
|
||||
animal_image: 'https://api.dicebear.com/7.x/bottts/svg?seed=alpaca',
|
||||
user_name: '张先生',
|
||||
user_phone: '13800138000',
|
||||
status: 'pending',
|
||||
applied_at: '2024-03-01',
|
||||
processed_at: ''
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const claimPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 30,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const animalColumns = [
|
||||
{
|
||||
title: '图片',
|
||||
key: 'image',
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '品种',
|
||||
dataIndex: 'breed',
|
||||
key: 'breed',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '年龄',
|
||||
dataIndex: 'age',
|
||||
key: 'age',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
customRender: ({ text }: { text: number }) => `${text}岁`
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
key: 'price',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 150,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
const claimColumns = [
|
||||
{
|
||||
title: '动物',
|
||||
key: 'animal_image',
|
||||
width: 60,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '动物名称',
|
||||
dataIndex: 'animal_name',
|
||||
key: 'animal_name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
dataIndex: 'user_name',
|
||||
key: 'user_name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'user_phone',
|
||||
key: 'user_phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applied_at',
|
||||
key: 'applied_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '处理时间',
|
||||
dataIndex: 'processed_at',
|
||||
key: 'processed_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
极速版 key: 'actions',
|
||||
width: 150,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
const fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIiBmaWxsPSIjRkZGIi8+CjxwYXRoIGQ9Ik0zMCAxNUMzMS42NTY5IDE1IDMzIDE2LjM0MzEgMzMgMThDMzMgMTkuNjU2OSAzMS42NTY5IDIxIDMwIDIxQzI4LjM0MzEgMjEgMjcgMTkuNjU2OSAyNyAxOEMyNyAxNi4zNDMxIDI4LjM0MzEgMTUgMzAgMTVaIiBmaWxsPSIjQ0NDQ0NDIi8+CjxwYXRoIGQ9Ik0yMi41IDI1QzIyLjUgMjUuODI4NCAyMS44Mjg0IDI2LjUgMjEgMjYuNUgxOUMxOC4xNzE2IDI2LjUgMTcuNSAyNS44Mjg0IDE3LjUgMjVDMTcuNSAyNC4xNzE2IDE4LjE3MTYgMjMuNSAxOSAyMy41SDIxQzIxLjgyODQgMjMuNSAyMi41IDI0LjE3MTYgMjIuNSAyNVoiIGZpbGw9IiNDQ0NDQ0MiLz4KPHBhdGggZD0iTTQyLjUgMjVDNDIuNSAyNS44Mjg0IDQxLjgyODQgMjYuNSA0MSAyNi41SDM5QzM4LjE3MTYgMjYuNSAzNy41IDI1LjgyODQgMzcuNSAyNUMzNy41IDI0LjE3MTYgMzguMTcxNiAyMy41IDM5IDIzLjVMNDEgMjMuNUM0MS44Mjg0IDIzLjUgNDIuNSAyNC4xNzE2IDQyLjUgMjVaIiBmaWxsPSIjQ0NDQ0NDIi8+Cjwvc3ZnPgo='
|
||||
|
||||
// 类型映射
|
||||
const getTypeColor = (type: string) => {
|
||||
const colors = {
|
||||
alpaca: 'pink',
|
||||
dog: 'orange',
|
||||
cat: 'blue',
|
||||
rabbit: 'green'
|
||||
}
|
||||
return colors[type as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type: string) => {
|
||||
const texts = {
|
||||
alpaca: '羊驼',
|
||||
dog: '狗狗',
|
||||
cat: '猫咪',
|
||||
rabbit: '兔子'
|
||||
}
|
||||
return texts[type as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 状态映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
available: 'green',
|
||||
claimed: 'blue',
|
||||
reserved: 'orange'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
available: '可认领',
|
||||
claimed: '已认领',
|
||||
reserved: '预留中'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
const getClaimStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
completed: 'blue'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getClaimStatusText = (status: string) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
completed: '已完成'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadAnimals()
|
||||
loadClaims()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadAnimals = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载动物列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadClaims = async () => {
|
||||
claimLoading.value = true
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载认领记录失败')
|
||||
} finally {
|
||||
claimLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key === 'animals') {
|
||||
loadAnimals()
|
||||
} else if (key === 'claims') {
|
||||
loadClaims()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadAnimals()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
keyword: '',
|
||||
type: '',
|
||||
status: ''
|
||||
})
|
||||
pagination.current = 1
|
||||
loadAnimals()
|
||||
}
|
||||
|
||||
const handleClaimSearch = () => {
|
||||
claimPagination.current = 1
|
||||
loadClaims()
|
||||
}
|
||||
|
||||
const handleClaimReset = () => {
|
||||
Object.assign(claimSearchForm, {
|
||||
keyword: '',
|
||||
status: ''
|
||||
})
|
||||
claimPagination.current = 1
|
||||
loadClaims()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (activeTab.value === 'animals') {
|
||||
loadAnimals()
|
||||
} else {
|
||||
loadClaims()
|
||||
}
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadAnimals()
|
||||
}
|
||||
|
||||
const handleViewAnimal = (record: Animal) => {
|
||||
message.info(`查看动物: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleEditAnimal = (record: Animal) => {
|
||||
message.info(`编辑动物: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleDeleteAnimal = (record: Animal) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除动物 "${record.name}" 吗?`,
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('动物已删除')
|
||||
loadAnimals()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleApproveClaim = (record: AnimalClaim) => {
|
||||
Modal.confirm({
|
||||
title: '确认通过',
|
||||
content: `确定要通过用户 "${record.user_name}" 的认领申请吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('认领申请已通过')
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRejectClaim = (record: AnimalClaim) => {
|
||||
Modal.confirm({
|
||||
title: '确认拒绝',
|
||||
content: `确定要拒绝用户 "${record.user_name}" 的认领申请吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('认领申请已拒绝')
|
||||
loadClaims()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleViewClaim = (record: AnimalClaim) => {
|
||||
message.info(`查看认领详情: ${record.animal_name}`)
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新增动物功能开发中')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.animal-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
245
admin-system/src/pages/dashboard/index.vue
Normal file
245
admin-system/src/pages/dashboard/index.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<a-page-header
|
||||
title="仪表板"
|
||||
sub-title="系统概览和统计数据"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button>刷新</a-button>
|
||||
<a-button type="primary">导出数据</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-row">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="总用户数"
|
||||
:value="11284"
|
||||
:precision="0"
|
||||
suffix="人"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="商家数量"
|
||||
:value="356"
|
||||
:precision="0"
|
||||
suffix="家"
|
||||
>
|
||||
<template #prefix>
|
||||
<ShopOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="旅行计划"
|
||||
:value="1287"
|
||||
:precision="0"
|
||||
suffix="个"
|
||||
>
|
||||
<template #prefix>
|
||||
<CompassOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="动物认领"
|
||||
:value="542"
|
||||
:precision="0"
|
||||
suffix="只"
|
||||
>
|
||||
<template #prefix>
|
||||
<HeartOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" class="chart-row">
|
||||
<a-col :span="12">
|
||||
<a-card title="用户增长趋势" class="chart-card">
|
||||
<div class="chart-placeholder">
|
||||
<BarChartOutlined />
|
||||
<p>用户增长图表</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="订单统计" class="chart-card">
|
||||
<div class="chart-placeholder">
|
||||
<PieChartOutlined />
|
||||
<p>订单分布图表</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" class="activity-row">
|
||||
<a-col :span="16">
|
||||
<a-card title="最近活动" class="activity-card">
|
||||
<a-list item-layout="horizontal" :data-source="recentActivities">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta
|
||||
:description="item.description"
|
||||
>
|
||||
<template #title>
|
||||
<a>{{ item.title }}</a>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.avatar" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
<div>{{ item.time }}</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card title="系统信息" class="info-card">
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="系统版本">
|
||||
{{ appVersion }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行环境">
|
||||
{{ environment }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="启动时间">
|
||||
{{ startupTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内存使用">
|
||||
256MB / 2GB
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
UserOutlined,
|
||||
ShopOutlined,
|
||||
CompassOutlined,
|
||||
HeartOutlined,
|
||||
BarChartOutlined,
|
||||
PieChartOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const appVersion = import.meta.env.VITE_APP_VERSION || '1.0.0'
|
||||
const environment = import.meta.env.MODE || 'development'
|
||||
const startupTime = new Date().toLocaleString()
|
||||
|
||||
const recentActivities = [
|
||||
{
|
||||
title: '新用户注册',
|
||||
description: '用户"旅行爱好者"完成了注册',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=1',
|
||||
time: '2分钟前'
|
||||
},
|
||||
{
|
||||
title: '旅行计划创建',
|
||||
description: '用户"探险家"发布了西藏旅行计划',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=2',
|
||||
time: '5分钟前'
|
||||
},
|
||||
{
|
||||
title: '动物认领',
|
||||
description: '用户"动物之友"认领了一只羊驼',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=3',
|
||||
time: '10分钟前'
|
||||
},
|
||||
{
|
||||
title: '订单完成',
|
||||
description: '花店"鲜花坊"完成了一笔鲜花订单',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=4',
|
||||
time: '15分钟前'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-container {
|
||||
padding: 24px;
|
||||
background: #f5f5f5;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.chart-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.activity-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.chart-card,
|
||||
.activity-card,
|
||||
.info-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chart-placeholder .anticon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.ant-statistic) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.ant-statistic-title) {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-statistic-content) {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
420
admin-system/src/pages/merchant/index.vue
Normal file
420
admin-system/src/pages/merchant/index.vue
Normal file
@@ -0,0 +1,420 @@
|
||||
<template>
|
||||
<div class="merchant-management">
|
||||
<a-page-header
|
||||
title="商家管理"
|
||||
sub-title="管理入驻商家信息"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon>
|
||||
<ShopOutlined />
|
||||
</template>
|
||||
新增商家
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="关键词">
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="商家名称/联系人/手机号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.type"
|
||||
placeholder="全部类型"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="flower_shop">花店</a-select-option>
|
||||
<a-select-option value="activity_organizer">活动组织</a-select-option>
|
||||
<a-select-option value="farm_owner">农场主</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="pending">待审核</a-select-option>
|
||||
<a-select-option value="approved">已通过</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="disabled">已禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 商家表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="merchantList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleView(record)">
|
||||
<EyeOutlined />
|
||||
查看
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-button size="small" type="primary" @click="handleApprove(record)">
|
||||
<CheckOutlined />
|
||||
通过
|
||||
</a-button>
|
||||
<a-button size="small" danger @click="handleReject(record)">
|
||||
<CloseOutlined />
|
||||
拒绝
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="record.status === 'approved'">
|
||||
<a-button size="small" danger @click="handleDisable(record)">
|
||||
<StopOutlined />
|
||||
禁用
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="record.status === 'disabled'">
|
||||
<a-button size="small" type="primary" @click="handleEnable(record)">
|
||||
<PlayCircleOutlined />
|
||||
启用
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ShopOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
EyeOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
StopOutlined,
|
||||
PlayCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface Merchant {
|
||||
id: number
|
||||
business_name: string
|
||||
merchant_type: string
|
||||
contact_person: string
|
||||
contact_phone: string
|
||||
status: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
keyword: string
|
||||
type: string
|
||||
status: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
keyword: '',
|
||||
type: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 模拟商家数据
|
||||
const merchantList = ref<Merchant[]>([
|
||||
{
|
||||
id: 1,
|
||||
business_name: '鲜花坊',
|
||||
merchant_type: 'flower_shop',
|
||||
contact_person: '张经理',
|
||||
contact_phone: '13800138000',
|
||||
status: 'approved',
|
||||
created_at: '2024-01-15'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
business_name: '阳光农场',
|
||||
merchant_type: 'farm_owner',
|
||||
contact_person: '李场主',
|
||||
contact_phone: '13800138001',
|
||||
status: 'pending',
|
||||
created_at: '2024-02-20'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '商家名称',
|
||||
dataIndex: 'business_name',
|
||||
key: 'business_name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '联系人',
|
||||
dataIndex: 'contact_person',
|
||||
key: 'contact_person',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'contact_phone',
|
||||
key: 'contact_phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '入驻时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
// 类型映射
|
||||
const getTypeColor = (type: string) => {
|
||||
const colors = {
|
||||
flower_shop: 'pink',
|
||||
activity_organizer: 'green',
|
||||
farm_owner: 'orange'
|
||||
}
|
||||
return colors[type as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type: string) => {
|
||||
const texts = {
|
||||
flower_shop: '花店',
|
||||
activity_organizer: '活动组织',
|
||||
farm_owner: '农场'
|
||||
}
|
||||
return texts[type as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 状态映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
approved: 'green',
|
||||
rejected: 'red',
|
||||
disabled: 'default'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
disabled: '已禁用'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadMerchants()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadMerchants = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 调用真实API
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载商家列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadMerchants()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
keyword: '',
|
||||
type: '',
|
||||
status: ''
|
||||
})
|
||||
pagination.current = 1
|
||||
loadMerchants()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadMerchants()
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadMerchants()
|
||||
}
|
||||
|
||||
const handleView = (record: Merchant) => {
|
||||
message.info(`查看商家: ${record.business_name}`)
|
||||
}
|
||||
|
||||
const handleApprove = async (record: Merchant) => {
|
||||
Modal.confirm({
|
||||
title: '确认通过',
|
||||
content: `确定要通过商家 "${record.business_name}" 的入驻申请吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('商家入驻申请已通过')
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleReject = async (record: Merchant) => {
|
||||
Modal.confirm({
|
||||
title: '确认拒绝',
|
||||
content: `确定要拒绝商家 "${record.business_name}" 的入驻申请吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('商家入驻申请已拒绝')
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDisable = async (record: Merchant) => {
|
||||
Modal.confirm({
|
||||
title: '确认禁用',
|
||||
content: `确定要禁用商家 "${record.business_name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('商家已禁用')
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnable = async (record: Merchant) => {
|
||||
Modal.confirm({
|
||||
title: '确认启用',
|
||||
content: `确定要启用商家 "${record.business_name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('商家已启用')
|
||||
loadMerchants()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新增商家功能开发中')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.merchant-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
437
admin-system/src/pages/order/index.vue
Normal file
437
admin-system/src/pages/order/index.vue
Normal file
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<div class="order-management">
|
||||
<a-page-header title="订单管理" sub-title="管理花束订单和交易记录">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showStats">
|
||||
<template #icon><BarChartOutlined /></template>
|
||||
销售统计
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<a-tab-pane key="orders" tab="订单列表">
|
||||
<a-card>
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="订单号">
|
||||
<a-input v-model:value="searchForm.order_no" placeholder="输入订单号" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
|
||||
<a-select-option value="pending">待支付</a-select-option>
|
||||
<a-select-option value="paid">已支付</a-select-option>
|
||||
<a-select-option value="shipped">已发货</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
<a-select-option value极速版="refunded">已退款</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="下单时间">
|
||||
<a-range-picker v-model:value="searchForm.orderTime" :placeholder="['开始时间', '结束时间']" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="orderColumns"
|
||||
:data-source="orderList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'order_no'">
|
||||
<a-typography-text copyable>{{ record.order_no }}</a-typography-text>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'amount'">¥{{ record.amount }}</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'payment_method'">
|
||||
<span>{{ getPaymentMethodText(record.payment_method) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleViewOrder(record)">
|
||||
<EyeOutlined />详情
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'paid'">
|
||||
<a-button size="small" type="primary" @click="handleShip(record)">
|
||||
<CarOutlined />发货
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="record.status === 'shipped'">
|
||||
<a-button size="small" type="primary" @click="handleComplete(record)">
|
||||
<CheckCircleOutlined />完成
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="['pending', 'paid'].includes(record.status)">
|
||||
<a-button size="small" danger @click="handleCancel(record)">
|
||||
<极速版CloseOutlined />取消
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="record.status === '极速版paid'">
|
||||
<a-button size="small" danger @click="handleRefund(record)">
|
||||
<RollbackOutlined />退款
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="statistics" tab="销售统计">
|
||||
<a-card title="销售数据概览">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-statistic title="今日订单" :value="statistics.today_orders" :precision="0" :value-style="{ color: '#3f8600' }">
|
||||
<template #prefix><ShoppingOutlined /></template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="今日销售额" :value="statistics.today_sales" :precision="2" prefix="¥" :value-style="{ color: '#cf1322' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="本月订单" :value="statistics.month_orders" :precision="0" :value-style="{ color: '#1890极速版ff' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="本月销售额" :value="statistics.month_sales" :precision="2" prefix="¥" :极速版value-style="{ color: '#722ed1' }" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="销售趋势" style="margin-top: 16px;">
|
||||
<div style="height: 300px;">
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #999;">
|
||||
<BarChartOutlined style="font-size: 48px; margin-right: 12px;" />
|
||||
<span>销售趋势图表开发中</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
BarChartOutlined,
|
||||
EyeOutlined,
|
||||
CarOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseOutlined,
|
||||
RollbackOutlined,
|
||||
ShoppingOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface Order {
|
||||
id: number
|
||||
order_no: string
|
||||
user_id: number
|
||||
user_name: string
|
||||
user_phone: string
|
||||
amount: number
|
||||
status: string
|
||||
payment_method: string
|
||||
created_at: string
|
||||
paid_at: string
|
||||
shipped_at: string
|
||||
completed_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
order_no: string
|
||||
status: string
|
||||
orderTime: any[]
|
||||
}
|
||||
|
||||
interface Statistics {
|
||||
today_orders: number
|
||||
today_sales: number
|
||||
month_orders: number
|
||||
month_sales: number
|
||||
}
|
||||
|
||||
const activeTab = ref('orders')
|
||||
const loading = ref(false)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
order_no: '',
|
||||
status: '',
|
||||
orderTime: []
|
||||
})
|
||||
|
||||
const statistics = reactive<Statistics>({
|
||||
today_orders: 0,
|
||||
today_sales: 0,
|
||||
month_orders: 0,
|
||||
month_sales: 0
|
||||
})
|
||||
|
||||
const orderList = ref<Order[]>([
|
||||
{
|
||||
id: 1,
|
||||
order_no: 'ORD202403150001',
|
||||
user_id: 1001,
|
||||
user_name: '张先生',
|
||||
user_phone: '13800138000',
|
||||
amount: 299.99,
|
||||
status: 'paid',
|
||||
payment_method: 'wechat',
|
||||
created_at: '2024-03-15 10:30:00',
|
||||
paid_at: '2024-03-15 10:35:00',
|
||||
shipped_at: '',
|
||||
completed_at: ''
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
order_no: 'ORD202403140002',
|
||||
user_id: 1002,
|
||||
user_name: '李女士',
|
||||
user_phone: '13800138001',
|
||||
amount: 极速版199.99,
|
||||
status: 'shipped',
|
||||
payment_method: 'alipay',
|
||||
created_at: '2024-03-14 14:20:00',
|
||||
paid_at: '2024-03-14 14:25:00',
|
||||
shipped_at: '2024-03-15 09:00:00',
|
||||
completed_at: ''
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const orderColumns = [
|
||||
{ title: '订单号', key: 'order_no', width: 160 },
|
||||
{ title: '用户', dataIndex: 'user_name', key: 'user_name', width: 100 },
|
||||
{ title: '联系电话', dataIndex: 'user极速版_phone', key: 'user_phone', width: 120 },
|
||||
{ title: '金额', key: 'amount', width: 100, align: 'center' },
|
||||
{ title: '状态', key: 'status', width: 100, align: 'center' },
|
||||
{ title: '支付方式', key: 'payment_method', width: 100, align: 'center' },
|
||||
{ title: '下单时间', dataIndex: 'created_at', key: 'created_at', width: 150 },
|
||||
{ title: '操作', key: 'actions', width: 200, align: 'center' }
|
||||
]
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
paid: 'blue',
|
||||
shipped: 'green',
|
||||
completed: 'purple',
|
||||
cancelled: 'red',
|
||||
refunded: 'default'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
pending: '待支付',
|
||||
paid: '已支付',
|
||||
shipped: '已发货',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
refunded: '已退款'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
const getPaymentMethodText = (method: string) => {
|
||||
const texts = {
|
||||
wechat: '微信支付',
|
||||
alipay: '支付宝',
|
||||
bank: '银行卡',
|
||||
balance: '余额支付'
|
||||
}
|
||||
return texts[method as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOrders()
|
||||
loadStatistics()
|
||||
})
|
||||
|
||||
const loadOrders = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载订单列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadStatistics = async () => {
|
||||
try {
|
||||
statistics.today_orders = 15
|
||||
statistics.today_sales = 4500.50
|
||||
statistics.month_orders = 120
|
||||
statistics.month_sales = 35600.80
|
||||
} catch (error) {
|
||||
message.error('加载统计数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key === 'orders') {
|
||||
loadOrders()
|
||||
} else if (key === 'statistics') {
|
||||
loadStatistics()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
order_no: '',
|
||||
status: '',
|
||||
orderTime: []
|
||||
})
|
||||
pagination.current = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (activeTab.value === 'orders') {
|
||||
loadOrders()
|
||||
} else {
|
||||
loadStatistics()
|
||||
}
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange极速版'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
const handleViewOrder = (record: Order) => {
|
||||
message.info(`查看订单: ${record.order_no}`)
|
||||
}
|
||||
|
||||
const handleShip = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认发货',
|
||||
content: `确定要发货订单 "${record.order_no}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('订单已发货')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleComplete = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认完成',
|
||||
content: `确定要完成订单 "${record.order_no}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('订单已完成')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
极速版 }
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认取消',
|
||||
content: `确定要取消订单 "${record.order极速版_no}" 吗?`,
|
||||
on极速版Ok: async () => {
|
||||
try {
|
||||
message.success('订单已取消')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRefund = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认退款',
|
||||
content: `确定要退款订单 "${record.order_no}" 吗?退款金额: ¥${record.amount}`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('退款申请已提交')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showStats = () => {
|
||||
activeTab.value = 'statistics'
|
||||
loadStatistics()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.order-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
573
admin-system/src/pages/promotion/index.vue
Normal file
573
admin-system/src/pages/promotion/index.vue
Normal file
@@ -0,0 +1,573 @@
|
||||
<template>
|
||||
<div class="promotion-management">
|
||||
<a-page-header title="推广管理" sub-title="管理推广活动和奖励">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建活动
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handle极速版TabChange">
|
||||
<a-tab-pane key="activities" tab="推广活动">
|
||||
<a-card>
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="活动名称">
|
||||
<a-input v-model:value="searchForm.name" placeholder="输入活动名称" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
|
||||
<a-select-option value="active">进行中</a-select-option>
|
||||
<a-select-option value="upcoming">未开始</a-select-极速版option>
|
||||
<a-select-option value="ended">已结束</a-select-option>
|
||||
<a-select-option value="paused极速版">已暂停</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="活动时间">
|
||||
<a-range-picker v-model:value="searchForm.activityTime" :placeholder="['开始极速版时间', '结束时间']" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="activityColumns"
|
||||
:data-source="activityList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'reward_type'">
|
||||
<span>{{ getRewardTypeText(record.reward_type) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'reward_amount'">
|
||||
<span v-if="record.reward_type === 'cash'">¥{{ record.reward_amount }}</span>
|
||||
<span v-else-if="record.reward_type === 'points'">{{ record.reward_amount }}积分</span>
|
||||
<span v-else-if="record.reward_type === 'coupon'">{{ record.re极速版ward_amount }}元券</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'participants'">
|
||||
<a-progress
|
||||
:percent="record.current_participants / record.max_participants * 100"
|
||||
size="small"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div style="font-size: 12px; text-align: center;">
|
||||
{{ record.current_participants }}/{{ record.max_participants }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleViewActivity(record)">
|
||||
<EyeOutlined />详情
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="handleEditActivity(record)">
|
||||
<EditOutlined />编辑
|
||||
</极速版a-button>
|
||||
|
||||
<template v-if="record.status === 'active'">
|
||||
极速版 <a-button size="small" danger @click="handlePauseActivity(record)">
|
||||
<PauseOutlined />暂停
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="record.status === 'paused'">
|
||||
<a-button size="small" type="primary" @click="handleResumeActivity(record)">
|
||||
<PlayCircleOutlined />继续
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-button size="small" danger @click="handleDeleteActivity(record)">
|
||||
<DeleteOutlined />删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="rewards" tab="奖励记录">
|
||||
<a-card>
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="rewardSearchForm">
|
||||
<a-form-item label="用户">
|
||||
<a-input v-model:value="rewardSearchForm.user" placeholder="用户名/手机号" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="奖励类型">
|
||||
<a-select v-model:value="rewardSearchForm.reward_type" placeholder="全部类型" style="width: 120px" allow-clear>
|
||||
<a-select-option value="cash">现金</a-select-option>
|
||||
<a-select-option value="points">积分</a-select-option>
|
||||
<a-select-option value="coupon">优惠券</a-select-option>
|
||||
</a-select>
|
||||
</极速版a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="rewardSearchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
|
||||
<a-select-option value="pending">待发放</a-select-option>
|
||||
<a-select-option value="issued">已发放</a-select-option>
|
||||
<a-select-option value="failed">发放失败</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleRewardSearch">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleRewardReset">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="rewardColumns"
|
||||
:data-source="rewardList"
|
||||
:loading="rewardLoading"
|
||||
:pagination="rewardPagination"
|
||||
:极速版row-key="record => record.id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'reward_type'">
|
||||
<span>{{ getRewardTypeText(record.reward_type) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'reward_amount'">
|
||||
<span v-if="record.reward_type === 'cash'">¥{{ record.reward_amount }}</span>
|
||||
<span v-else-if="record.reward_type === 'points'">{{ record.reward_amount }}积分</span>
|
||||
<span v-else-if="record.reward_type === 'coupon'">{{ record.reward_amount }}元券</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getRewardStatusColor(record.status)">{{ getRewardStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-button size="small" type="primary" @click="handleIssueReward(record)">
|
||||
<CheckOutlined />发放
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-button size="small" @click="handleViewRew极速版ard(record)">
|
||||
<EyeOutlined />详情
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a极速版-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
PlusOutlined,
|
||||
EyeOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PauseOutlined,
|
||||
PlayCircleOutlined,
|
||||
CheckOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface PromotionActivity {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
reward_type: string极速版
|
||||
reward_amount: number
|
||||
status: string
|
||||
start_time: string
|
||||
end_time: string
|
||||
max_participants:极速版 number
|
||||
current_participants: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface RewardRecord {
|
||||
id: number
|
||||
user_id: number
|
||||
user_name: string
|
||||
user_phone: string
|
||||
activity_id: number
|
||||
activity_name: string
|
||||
reward_type: string
|
||||
reward_amount: number
|
||||
status: string
|
||||
issued_at: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
name: string
|
||||
status: string
|
||||
activityTime: any[]
|
||||
}
|
||||
|
||||
interface RewardSearchForm {
|
||||
user: string
|
||||
reward_type: string
|
||||
status: string
|
||||
}
|
||||
|
||||
const activeTab = ref('activities')
|
||||
const loading = ref(false)
|
||||
const rewardLoading = ref(false)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
name: '',
|
||||
status: '',
|
||||
activityTime: []
|
||||
})
|
||||
|
||||
const rewardSearchForm = reactive<RewardSearchForm>({
|
||||
user: '',
|
||||
reward_type: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
const activityList = ref<PromotionActivity[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '邀请好友得现金',
|
||||
description: '邀请好友注册即可获得现金奖励',
|
||||
reward_type: 'cash',
|
||||
reward_amount: 10,
|
||||
status: 'active',
|
||||
start_time: '极速版2024-03-01',
|
||||
end_time: '2024-03-31',
|
||||
max_participants: 1000极速版,
|
||||
current_participants: 356,
|
||||
created_at: '2024-02-20'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '分享活动得积分',
|
||||
description: '分享活动页面即可获得积分奖励',
|
||||
reward_type: 'points',
|
||||
reward_amount: 100,
|
||||
status: 'upcoming',
|
||||
start_time: '2024-04-01',
|
||||
end_time: '202极速版4-04-30',
|
||||
max_participants: 500,
|
||||
current_p极速版articipants: 0,
|
||||
created_at: '2024-03-10'
|
||||
}
|
||||
])
|
||||
|
||||
const rewardList = ref<RewardRecord[]>([
|
||||
{
|
||||
id: 1,
|
||||
user_id: 1001,
|
||||
user_name: '张先生',
|
||||
user_phone: '13800138000',
|
||||
activity_id: 1,
|
||||
activity_name: '邀请好友极速版得现金',
|
||||
reward_type: 'cash',
|
||||
reward_amount: 10,
|
||||
status: 'issued',
|
||||
issued_at: '2024-03-05 14:30:00',
|
||||
created_at: '2024-03-05 14:25:00'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const rewardPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 30,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const activityColumns = [
|
||||
{ title: '活动名称', dataIndex: 'name', key: '极速版name', width: 150 },
|
||||
{ title极速版: '奖励类型', key: 'reward_type', width: 100, align: 'center' },
|
||||
{ title: '奖励金额', key: 'reward_amount', width: 100, align: 'center' },
|
||||
{ title: '状态', key: 'status', width: 100, align: 'center' },
|
||||
{ title: '活动时间', key: 'time', width: 200,
|
||||
customRender: ({ record }: { record: PromotionActivity }) =>
|
||||
`${record.start_time} 至 ${record.end_time}`
|
||||
},
|
||||
{ title: '参与人数', key: 'participants', width: 120, align: 'center' },
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 120 },
|
||||
{ title: '操作', key: 'actions', width: 200, align: 'center' }
|
||||
]
|
||||
|
||||
const rewardColumns = [
|
||||
{ title: '用户', dataIndex: 'user_name', key: 'user_name', width: 100 },
|
||||
{ title: '联系电话', dataIndex: 'user_phone', key: 'user_phone', width: 120 },
|
||||
{ title: '活动名称', dataIndex: 'activity_name', key: 'activity_name', width: 150 },
|
||||
{ title: '奖励类型', key: 'reward_type', width: 100, align: 'center极速版' },
|
||||
{ title: '奖励金额', key: 'reward_amount', width: 100, align: 'center' },
|
||||
{ title: '状态', key: 'status', width: 100, align: 'center' },
|
||||
{ title: '发放时间', dataIndex: 'issued_at极速版', key: 'issued_at', width: 极速版150 },
|
||||
{ title: '申请时间', dataIndex: 'created_at', key: 'created_at', width: 150 },
|
||||
{ title: '操作', key: 'actions', width: 120, align: 'center' }
|
||||
]
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
upcoming: 'blue',
|
||||
ended: 'default',
|
||||
paused: 'orange'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string)极速版 {
|
||||
const texts = {
|
||||
active: '进行中',
|
||||
upcoming: '未开始',
|
||||
ended: '已结束',
|
||||
paused: '已暂停'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
const getRewardTypeText = (type: string) => {
|
||||
const texts = {
|
||||
cash: '现金',
|
||||
points: '积分',
|
||||
coupon: '优惠券'
|
||||
}
|
||||
return texts[type as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
const getRewardStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending极速版: 'orange',
|
||||
issued: 'green',
|
||||
failed: 'red'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getRewardStatusText = (status: string) => {
|
||||
const texts = {
|
||||
pending: '待发放',
|
||||
issued: '已发放',
|
||||
failed: '发放失败'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadActivities()
|
||||
loadRewards()
|
||||
})
|
||||
|
||||
const loadActivities = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载活动列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadRewards = async () => {
|
||||
rewardLoading.value = true
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载奖励记录失败')
|
||||
} finally {
|
||||
rewardLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key === 'activities') {
|
||||
loadActivities()
|
||||
} else if (key === 'rewards') {
|
||||
loadRewards()
|
||||
极速版 }
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadActivities()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
name: '',
|
||||
status: '',
|
||||
activityTime: []
|
||||
})
|
||||
pagination.current = 1极速版
|
||||
loadActivities()
|
||||
}
|
||||
|
||||
const handleRewardSearch = () => {
|
||||
rewardPagination.current = 1
|
||||
loadRewards()
|
||||
}
|
||||
|
||||
const handleRewardReset = () => {
|
||||
Object.assign(rewardSearchForm, {
|
||||
user: '',
|
||||
reward_type: '',
|
||||
status: ''
|
||||
})
|
||||
rewardPagination.current = 1
|
||||
loadRewards()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (activeTab.value === 'activities') {
|
||||
loadActivities()
|
||||
} else {
|
||||
loadRewards()
|
||||
}
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadActivities()
|
||||
}
|
||||
|
||||
const handleViewActivity = (record: PromotionActivity) => {
|
||||
message.info(`查看活动: ${record.name}`)
|
||||
}
|
||||
|
||||
const handleEditActivity = (record: PromotionActivity) => {
|
||||
message.info(`编辑活动: ${record.name极速版}`)
|
||||
}
|
||||
|
||||
const handlePauseActivity = async (record: PromotionActivity) => {
|
||||
Modal.confirm({
|
||||
title: '确认暂停',
|
||||
content: `确定要暂停活动 "${record.name}" 极速版吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('活动已暂停')
|
||||
loadActivities()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleResumeActivity = async (record: PromotionActivity) => {
|
||||
Modal.confirm({
|
||||
title: '确认继续',
|
||||
content: `确定要继续活动 "${record.name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('活动已继续')
|
||||
loadActivities()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeleteActivity = async (record: PromotionActivity) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除活动 "${record.name}" 吗?`,
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('活动已删除')
|
||||
loadActivities()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleIssueReward = async (record: RewardRecord) => {
|
||||
Modal.confirm({
|
||||
title: '确认发放',
|
||||
content: `确定要发放奖励给用户 "${record.user_name}" 吗?`,
|
||||
onOk: async ()极速版 => {
|
||||
try {
|
||||
message.success('奖励已发放')
|
||||
loadRewards()
|
||||
极速版 } catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleViewReward = (record: RewardRecord) => {
|
||||
message.info(`查看奖励记录: ${record.user_name}`)
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
message.info('新建活动功能开发中')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.promotion-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
289
admin-system/src/pages/system/index.vue
Normal file
289
admin-system/src/pages/system/index.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="system-management">
|
||||
<a-page-header title="系统管理" sub-title="管理系统设置和配置">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<ReloadOutlined />
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-card title="系统信息" size="small">
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="系统版本">v1.0.0</a-descriptions-item>
|
||||
<a-descriptions-item label="运行环境">Production</a-descriptions-item>
|
||||
<极速版a-descriptions-item label="启动时间">2024-03-15 10:00:00</a-descriptions-item>
|
||||
<a-descriptions-item label="运行时长">12天3小时</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="8">
|
||||
<a-card title="数据库状态" size="small">
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="连接状态">
|
||||
<a-tag color="green">正常</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label极速版="数据库类型">MySQL</a-descriptions-item>
|
||||
<a-descriptions-item label="连接数">15极速版/100</a-descriptions-item>
|
||||
<a-descriptions-item label="查询次数">1,234次/分钟</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="8">
|
||||
<a-card title="缓存状态" size="small">
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="Redis状态">
|
||||
<a-tag color="green">正常</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内存使用">65%</a-descriptions-item>
|
||||
<a-descriptions-item label="命中率">92%</a-descriptions-item>
|
||||
<a-descriptions-item label="键数量">1,234</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px;">
|
||||
<a-col :span="12">
|
||||
<a-card title="服务监控" size="small">
|
||||
<a-list item-layout="horizontal" :data-source="services">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="item.description">
|
||||
<template #title>
|
||||
<a-space>
|
||||
<a-tag :color="item.status === 'running' ? 'green' : 'red'">
|
||||
{{ item.status === 'running' ? '运行中' : '停止' }}
|
||||
</a-tag>
|
||||
{{ item.name }}
|
||||
</a-space>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :style="{ backgroundColor: item.status === 'running' ? '#52c41a' : '#ff4d4f' }">
|
||||
<DatabaseOutlined v-if="item.type === 'database'" />
|
||||
<CloudServerOutlined v-if="item.type === 'cache'" />
|
||||
<MessageOutlined v-if="item.type === 'mq'" />
|
||||
</a-avatar>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
<template #actions>
|
||||
<a-button size="small" v-if="item.status === 'running'" danger @click="handleStopService(item)">
|
||||
停止
|
||||
</a-button>
|
||||
<a-button size="small" v-if="item.status === 'stopped'" type="primary" @click="handleStartService(item)">
|
||||
启动
|
||||
</a-button>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-card title="系统日志" size="small">
|
||||
<a-timeline>
|
||||
<a-t极速版imeline-item color="green">
|
||||
<p>用户登录成功 - admin (2024-03-15 14:30:22)</p>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="blue">
|
||||
<p>数据库备份完成 - 备份文件: backup_20240315.sql (2024-03-15 14:00:00)</p>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="orange">
|
||||
<p>系统警告 - 内存使用率超过80% (2024-03-15 13:极速版45:18)</极速版p>
|
||||
</a-timeline-item>
|
||||
<a-timeline-item color="green">
|
||||
<p>定时任务执行 - 清理过期日志 (2024-03-15 13:30:00)</p>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
<div style="text-align: center; margin-top: 16px;">
|
||||
<a-button type="link" @click="viewLogs">查看完整日志</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px;">
|
||||
<a-col :span="24">
|
||||
<a-card title="系统极速版设置" size="small">
|
||||
<a-form :model="systemSettings" layout="vertical">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="系统名称">
|
||||
<a-input v-model:value="systemSettings.systemName" placeholder="请输入系统名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="系统版本">
|
||||
<a-input v-model:value="systemSettings.systemVersion" placeholder="请输入系统版本极速版" />
|
||||
</a-form-item>
|
||||
</极速版a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="维护模式">
|
||||
<a-switch v-model:checked="systemSettings.maintenanceMode" />
|
||||
</极速版a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="极速版16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="会话超时(分钟)">
|
||||
<a-input-number v-model:value="systemSettings.sessionTimeout" :min="5" :max="480" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="每页显示数量">
|
||||
<a-input-number v-model:value="systemSettings.pageSize" :min="10" :max="100" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="启用API文档">
|
||||
<a-switch v-model:checked="systemSettings.enableSwagger" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="saveSettings">保存设置</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSettings">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
DatabaseOutlined,
|
||||
CloudServerOutlined,
|
||||
MessageOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface Service {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
description: string
|
||||
status: string
|
||||
}
|
||||
|
||||
interface SystemSettings {
|
||||
systemName: string
|
||||
systemVersion: string
|
||||
maintenanceMode: boolean
|
||||
sessionTimeout: number
|
||||
pageSize: number
|
||||
enableSwagger: boolean
|
||||
}
|
||||
|
||||
const services = ref<Service[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: 'MySQL数据库',
|
||||
type: 'database',
|
||||
description: '主数据库服务',
|
||||
status: 'running'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Redis缓存',
|
||||
type: 'cache',
|
||||
description: '缓存服务',
|
||||
status: 'running'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'RabbitMQ',
|
||||
type: 'mq',
|
||||
description: '消息队列服务',
|
||||
status: 'stopped'
|
||||
}
|
||||
])
|
||||
|
||||
const systemSettings = reactive<SystemSettings>({
|
||||
systemName: '结伴客管理系统',
|
||||
systemVersion: 'v1.0.0',
|
||||
maintenanceMode: false,
|
||||
sessionTimeout: 30,
|
||||
pageSize: 20,
|
||||
enableSwagger: true
|
||||
})
|
||||
|
||||
const handleRefresh = () => {
|
||||
message.success('系统状态已刷新')
|
||||
}
|
||||
|
||||
const handleStopService = (service: Service极速版) => {
|
||||
Modal.confirm({
|
||||
title: '确认停止',
|
||||
content: `确定要停止服务 "${service.name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
service.status = 'stopped'
|
||||
message.success('服务已停止')
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleStartService = (service: Service) => {
|
||||
Modal.confirm({
|
||||
title: '确认启动',
|
||||
content: `确定要启动服务 "${service.name}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
service.status = 'running'
|
||||
message.success('服务已启动')
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const viewLogs = () => {
|
||||
message.info('查看系统日志功能开发中')
|
||||
}
|
||||
|
||||
const saveSettings = () => {
|
||||
message.success('系统设置已保存')
|
||||
}
|
||||
|
||||
const resetSettings = () => {
|
||||
Object.assign(systemSettings, {
|
||||
systemName: '结伴客管理系统',
|
||||
systemVersion: 'v1.0.极速版0',
|
||||
maintenanceMode: false,
|
||||
sessionTimeout: 30,
|
||||
pageSize: 极速版20,
|
||||
enableSwagger: true
|
||||
})
|
||||
message.success('设置已重置')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.system-management {
|
||||
.ant-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ant-descriptions-item-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
382
admin-system/src/pages/travel/index.vue
Normal file
382
admin-system/src/pages/travel/index.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<div class="travel-management">
|
||||
<a-page-header
|
||||
title="旅行管理"
|
||||
sub-title="管理旅行计划和匹配"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showStats">
|
||||
<template #icon>
|
||||
<BarChartOutlined />
|
||||
</template>
|
||||
数据统计
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="目的地">
|
||||
<a-input
|
||||
v-model:value="searchForm.destination"
|
||||
placeholder="输入目的地"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="recruiting">招募中</a-select-option>
|
||||
<a-select-option value="full">已满员</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="旅行时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.travelTime"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 旅行计划表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="travelList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'destination'">
|
||||
<strong>{{ record.destination }}</strong>
|
||||
<div style="font-size: 12px; color: #666;">
|
||||
{{ record.start_date }} 至 {{ record.end_date }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'budget'">
|
||||
¥{{ record.budget }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'members'">
|
||||
<a-progress
|
||||
:percent="(record.current_members / record.max_members) * 100"
|
||||
size="small"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div style="font-size: 12px; text-align: center;">
|
||||
{{ record.current_members }}/{{ record.max_members }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleView(record)">
|
||||
<EyeOutlined />
|
||||
详情
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="handleMembers(record)">
|
||||
<TeamOutlined />
|
||||
成员
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'recruiting'">
|
||||
<a-button size="small" type="primary" @click="handlePromote(record)">
|
||||
<RocketOutlined />
|
||||
推广
|
||||
</a-button>
|
||||
<a-button size="small" danger @click="handleClose(record)">
|
||||
<CloseOutlined />
|
||||
关闭
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
BarChartOutlined,
|
||||
EyeOutlined,
|
||||
TeamOutlined,
|
||||
RocketOutlined,
|
||||
CloseOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface TravelPlan {
|
||||
id: number
|
||||
destination: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
budget: number
|
||||
max_members: number
|
||||
current_members: number
|
||||
status: string
|
||||
creator: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
destination: string
|
||||
status: string
|
||||
travelTime: any[]
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
destination: '',
|
||||
status: '',
|
||||
travelTime: []
|
||||
})
|
||||
|
||||
// 模拟旅行数据
|
||||
const travelList = ref<TravelPlan[]>([
|
||||
{
|
||||
id: 1,
|
||||
destination: '西藏',
|
||||
start_date: '2024-07-01',
|
||||
end_date: '2024-07-15',
|
||||
budget: 5000,
|
||||
max_members: 6,
|
||||
current_members: 3,
|
||||
status: 'recruiting',
|
||||
creator: '旅行爱好者',
|
||||
created_at: '2024-06-01'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
destination: '云南',
|
||||
start_date: '2024-08-10',
|
||||
end_date: '2024-08-20',
|
||||
budget: 3000,
|
||||
max_members: 4,
|
||||
current_members: 4,
|
||||
status: 'full',
|
||||
creator: '探险家',
|
||||
created_at: '2024-07-15'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '旅行信息',
|
||||
key: 'destination',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '预算',
|
||||
key: 'budget',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '成员',
|
||||
key: 'members',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
dataIndex: 'creator',
|
||||
key: 'creator',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
// 状态映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
recruiting: 'blue',
|
||||
full: 'green',
|
||||
completed: 'purple',
|
||||
cancelled: 'red'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
recruiting: '招募中',
|
||||
full: '已满员',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadTravelPlans()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadTravelPlans = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 调用真实API
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
message.error('加载旅行计划失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 极速版1
|
||||
loadTravelPlans()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
destination: '',
|
||||
status: '',
|
||||
travelTime: []
|
||||
})
|
||||
pagination.current = 1
|
||||
loadTravelPlans()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadTravelPlans()
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadTravelPlans()
|
||||
}
|
||||
|
||||
const handleView = (record: TravelPlan) => {
|
||||
message.info(`查看旅行计划: ${record.destination}`)
|
||||
}
|
||||
|
||||
const handleMembers = (record: TravelPlan) => {
|
||||
message.info(`查看成员: ${record.destination}`)
|
||||
}
|
||||
|
||||
const handlePromote = (record: TravelPlan) => {
|
||||
Modal.confirm({
|
||||
title: '确认推广',
|
||||
content: `确定要推广旅行计划 "${record.destination}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('旅行计划已推广')
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = (record: TravelPlan) => {
|
||||
Modal.confirm({
|
||||
title: '确认关闭',
|
||||
content: `确定要关闭旅行计划 "${record.destination}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
message.success('旅行计划已关闭')
|
||||
loadTravelPlans()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showStats = () => {
|
||||
message.info('数据统计功能开发中')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.travel-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
650
admin-system/src/pages/user/index.vue
Normal file
650
admin-system/src/pages/user/index.vue
Normal file
@@ -0,0 +1,650 @@
|
||||
<template>
|
||||
<div class="user-management">
|
||||
<a-page-header
|
||||
title="用户管理"
|
||||
sub-title="管理系统用户信息"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon>
|
||||
<UserAddOutlined />
|
||||
</template>
|
||||
新增用户
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="关键词">
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="用户名/昵称/手机号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="active">正常</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
<a-select-option value="banned">封禁</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="注册时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.registerTime"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="userList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'avatar'">
|
||||
<a-avatar :src="record.avatar" :size="32">
|
||||
{{ record.nickname?.charAt(0) }}
|
||||
</a-avatar>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<a-tag color="blue">Lv.{{ record.level }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleView(record)">
|
||||
<EyeOutlined />
|
||||
查看
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="handleEdit(record)">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<a-dropdown>
|
||||
<a-button size="small">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
v-if="record.status === 'active'"
|
||||
@click="handleDisable(record)"
|
||||
>
|
||||
<StopOutlined />
|
||||
禁用
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
v-else
|
||||
@click="handleEnable(record)"
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
启用
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
v-if="record.status !== 'banned'"
|
||||
@click="handleBan(record)"
|
||||
>
|
||||
<WarningOutlined />
|
||||
封禁
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
danger
|
||||
@click="handleDelete(record)"
|
||||
>
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 创建/编辑用户模态框 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
width="600px"
|
||||
:confirm-loading="modalLoading"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="昵称" name="nickname">
|
||||
<a-input
|
||||
v-model:value="formState.nickname"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="密码" name="password" v-if="isCreate">
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input
|
||||
v-model:value="formState.phone"
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input
|
||||
v-model:value="formState.email"
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formState.status" placeholder="请选择状态">
|
||||
<a-select-option value="active">正常</a-select-option>
|
||||
<a-select-option value="inactive">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="formState.remark"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import {
|
||||
UserAddOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
EyeOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
StopOutlined,
|
||||
PlayCircleOutlined,
|
||||
WarningOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
email: string
|
||||
phone: string
|
||||
status: string
|
||||
level: number
|
||||
points: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
interface SearchForm {
|
||||
keyword: string
|
||||
status: string
|
||||
registerTime: any[]
|
||||
}
|
||||
|
||||
interface FormState {
|
||||
username: string
|
||||
nickname: string
|
||||
password: string
|
||||
email: string
|
||||
phone: string
|
||||
status: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const modalLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const editingUser = ref<User | null>(null)
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
keyword: '',
|
||||
status: '',
|
||||
registerTime: []
|
||||
})
|
||||
|
||||
const formState = reactive<FormState>({
|
||||
username: '',
|
||||
nickname: '',
|
||||
password: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
status: 'active',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 4, max: 20, message: '用户名长度为4-20个字符', trigger: 'blur' }
|
||||
],
|
||||
nickname: [
|
||||
{ required: true, message: '请输入昵称', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度至少6个字符', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 模拟用户数据
|
||||
const userList = ref<User[]>([
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
nickname: '系统管理员',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=admin',
|
||||
email: 'admin@jiebanke.com',
|
||||
phone: '13800138000',
|
||||
status: 'active',
|
||||
level: 10,
|
||||
points: 10000,
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user001',
|
||||
nickname: '旅行爱好者',
|
||||
avatar: 'https://api.dicebear.com/7.x/miniavs/svg?seed=user1',
|
||||
email: 'user001@example.com',
|
||||
phone: '13800138001',
|
||||
status: 'active',
|
||||
level: 3,
|
||||
points: 1500,
|
||||
created_at: '2024-02-15',
|
||||
updated_at: '2024-02-15'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 50,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '用户',
|
||||
key: 'avatar',
|
||||
width: 60,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '等级',
|
||||
key: 'level',
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
dataIndex: 'points',
|
||||
key: 'points',
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
const modalTitle = computed(() => editingUser.value ? '编辑用户' : '新增用户')
|
||||
const isCreate = computed(() => !editingUser.value)
|
||||
|
||||
// 状态映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
active: 'green',
|
||||
inactive: 'orange',
|
||||
banned: 'red'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
active: '正常',
|
||||
inactive: '禁用',
|
||||
banned: '封禁'
|
||||
}
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadUsers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 调用真实API
|
||||
// const response = await userAPI.getUsers({
|
||||
// page: pagination.current,
|
||||
// pageSize: pagination.pageSize,
|
||||
// ...searchForm
|
||||
// })
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
// userList.value = response.data.users
|
||||
// pagination.total = response.data.pagination.total
|
||||
} catch (error) {
|
||||
message.error('加载用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
keyword: '',
|
||||
status: '',
|
||||
registerTime: []
|
||||
})
|
||||
pagination.current = 1
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadUsers()
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
pagination.current = pag.current!
|
||||
pagination.pageSize = pag.pageSize!
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
const showCreateModal = () => {
|
||||
editingUser.value = null
|
||||
Object.assign(formState, {
|
||||
username: '',
|
||||
nickname: '',
|
||||
password: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
status: 'active',
|
||||
remark: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleView = (record: User) => {
|
||||
// TODO: 跳转到用户详情页
|
||||
message.info(`查看用户: ${record.nickname}`)
|
||||
}
|
||||
|
||||
const handleEdit = (record: User) => {
|
||||
editingUser.value = record
|
||||
Object.assign(formState, {
|
||||
username: record.username,
|
||||
nickname: record.nickname,
|
||||
password: '',
|
||||
email: record.email,
|
||||
phone: record.phone,
|
||||
status: record.status,
|
||||
remark: ''
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleDisable = async (record: User) => {
|
||||
Modal.confirm({
|
||||
title: '确认禁用',
|
||||
content: `确定要禁用用户 "${record.nickname}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
// await userAPI.updateUser(record.id, { status: 'inactive' })
|
||||
message.success('用户已禁用')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnable = async (record: User) => {
|
||||
Modal.confirm({
|
||||
title: '确认启用',
|
||||
content: `确定要启用用户 "${record.nickname}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
// await userAPI.updateUser(record.id, { status: 'active' })
|
||||
message.success('用户已启用')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleBan = async (record: User) => {
|
||||
Modal.confirm({
|
||||
title: '确认封禁',
|
||||
content: `确定要封禁用户 "${record.nickname}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
// await userAPI.updateUser(record.id, { status: 'banned' })
|
||||
message.success('用户已封禁')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (record: User) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除用户 "${record.nickname}" 吗?此操作不可恢复。`,
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
// await userAPI.deleteUser(record.id)
|
||||
message.success('用户已删除')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
modalLoading.value = true
|
||||
|
||||
if (editingUser.value) {
|
||||
// 编辑用户
|
||||
// await userAPI.updateUser(editingUser.value.id, formState)
|
||||
message.success('用户信息更新成功')
|
||||
} else {
|
||||
// 创建用户
|
||||
// await userAPI.createUser(formState)
|
||||
message.success('用户创建成功')
|
||||
}
|
||||
|
||||
modalVisible.value = false
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.user-management {
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user