整理页面
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>后台管理系统</title>
|
||||
<title>项目后台管理系统</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-aside :width="isCollapse ? '64px' : '200px'" class="sidebar">
|
||||
<div class="logo">
|
||||
<img v-if="!isCollapse" src="/logo.svg" alt="Logo" class="logo-img" />
|
||||
<span v-if="!isCollapse" class="logo-text">后台管理</span>
|
||||
<span v-if="!isCollapse" class="logo-text">项目后台管理</span>
|
||||
<el-icon v-else class="logo-icon"><Setting /></el-icon>
|
||||
</div>
|
||||
|
||||
@@ -47,42 +47,11 @@
|
||||
<el-icon><Promotion /></el-icon>
|
||||
<template #title>融豆管理</template>
|
||||
</el-menu-item>
|
||||
|
||||
|
||||
<el-menu-item v-if="userStore.isAdmin" index="/daily-transfer-stats">
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
<template #title>昨日转账统计</template>
|
||||
</el-menu-item>
|
||||
|
||||
<!-- <el-menu-item v-if="userStore.isAdmin" index="/matching-management">
|
||||
<el-icon><Connection /></el-icon>
|
||||
<template #title>匹配管理</template>
|
||||
</el-menu-item> -->
|
||||
|
||||
<el-menu-item v-if="userStore.isAdmin" index="/agents">
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<template #title>代理管理</template>
|
||||
</el-menu-item>
|
||||
|
||||
<el-menu-item v-if="userStore.isAdmin" index="/announcements">
|
||||
<el-icon><Bell /></el-icon>
|
||||
<template #title>通知公告</template>
|
||||
</el-menu-item>
|
||||
|
||||
<!-- <el-menu-item v-if="userStore.isAdmin" index="/database-monitor">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
<template #title>数据库监控</template>
|
||||
</el-menu-item> -->
|
||||
|
||||
<el-menu-item index="/profile">
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<template #title>个人资料</template>
|
||||
</el-menu-item>
|
||||
|
||||
<!-- <el-menu-item v-if="userStore.isAdmin" index="/settings">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<template #title>系统设置</template>
|
||||
</el-menu-item> -->
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
@@ -302,7 +271,7 @@ const breadcrumbs = computed(() => {
|
||||
matched.forEach(item => {
|
||||
if (item.path !== '/') {
|
||||
breadcrumbList.push({
|
||||
title: item.meta.title.replace(' - 后台管理系统', ''),
|
||||
title: item.meta.title.replace(' - 项目后台管理系统', ''),
|
||||
path: item.path
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,46 +81,6 @@ const routes = [
|
||||
requiresAdmin: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'daily-transfer-stats',
|
||||
name: 'DailyTransferStats',
|
||||
component: () => import('@/views/DailyTransferStats.vue'),
|
||||
meta: {
|
||||
title: '昨日转账统计 - 炬融圈',
|
||||
icon: 'DataAnalysis',
|
||||
requiresAdmin: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'agents',
|
||||
name: 'Agents',
|
||||
component: () => import('@/views/Agents.vue'),
|
||||
meta: {
|
||||
title: '代理管理 - 炬融圈',
|
||||
icon: 'Avatar',
|
||||
requiresAdmin: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'announcements',
|
||||
name: 'Announcements',
|
||||
component: () => import('@/views/Announcements.vue'),
|
||||
meta: {
|
||||
title: '通知公告 - 炬融圈',
|
||||
icon: 'Bell',
|
||||
requiresAdmin: true
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: 'database-monitor',
|
||||
// name: 'DatabaseMonitor',
|
||||
// component: () => import('@/views/DatabaseMonitor.vue'),
|
||||
// meta: {
|
||||
// title: '数据库监控 - 炬融圈',
|
||||
// icon: 'Monitor',
|
||||
// requiresAdmin: true
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: 'profile',
|
||||
name: 'Profile',
|
||||
@@ -130,16 +90,6 @@ const routes = [
|
||||
icon: 'UserFilled'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'settings',
|
||||
// name: 'Settings',
|
||||
// component: () => import('@/views/Settings.vue'),
|
||||
// meta: {
|
||||
// title: '系统设置 - 炬融圈',
|
||||
// icon: 'Setting',
|
||||
// requiresAdmin: true
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
1288
src/views/Agents.vue
1288
src/views/Agents.vue
File diff suppressed because it is too large
Load Diff
@@ -1,684 +0,0 @@
|
||||
<template>
|
||||
<div class="announcements-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">通知公告管理</h1>
|
||||
<p class="page-subtitle">管理系统中的所有通知公告</p>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="search-card" shadow="never">
|
||||
<el-row :gutter="20" class="search-row">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="搜索公告标题或内容"
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="选择状态"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已发布" value="published" />
|
||||
<el-option label="已归档" value="archived" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="选择类型"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="系统通知" value="system" />
|
||||
<el-option label="维护公告" value="maintenance" />
|
||||
<el-option label="活动推广" value="promotion" />
|
||||
<el-option label="重要警告" value="warning" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" class="button-group">
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 公告列表 -->
|
||||
<el-card class="table-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">公告列表 ({{ pagination.total }})</span>
|
||||
<el-button type="primary" @click="showCreateDialog">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新建公告
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="announcements"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
|
||||
|
||||
<el-table-column prop="title" label="标题" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="title-cell">
|
||||
<el-tag v-if="row.is_pinned" type="danger" size="small" class="pin-tag">
|
||||
置顶
|
||||
</el-tag>
|
||||
<span class="title-text">{{ row.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="type" label="类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTypeTagType(row.type)" size="small">
|
||||
{{ getTypeLabel(row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="priority" label="优先级" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPriorityTagType(row.priority)" size="small">
|
||||
{{ getPriorityLabel(row.priority) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusTagType(row.status)" size="small">
|
||||
{{ getStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="creator_name" label="创建者" width="120" />
|
||||
|
||||
<el-table-column prop="publish_time" label="发布时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.publish_time ? formatDateTime(row.publish_time) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" sortable="custom">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="showEditDialog(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'draft'"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="publishAnnouncement(row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteAnnouncement(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.limit"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 创建/编辑公告对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑公告' : '新建公告'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="公告标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入公告标题" maxlength="255" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="公告内容" prop="content">
|
||||
<el-input
|
||||
v-model="form.content"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="请输入公告内容"
|
||||
maxlength="2000"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公告类型" prop="type">
|
||||
<el-select v-model="form.type" placeholder="选择公告类型" style="width: 100%">
|
||||
<el-option label="系统通知" value="system" />
|
||||
<el-option label="维护公告" value="maintenance" />
|
||||
<el-option label="活动推广" value="promotion" />
|
||||
<el-option label="重要警告" value="warning" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="优先级" prop="priority">
|
||||
<el-select v-model="form.priority" placeholder="选择优先级" style="width: 100%">
|
||||
<el-option label="低" value="low" />
|
||||
<el-option label="中" value="medium" />
|
||||
<el-option label="高" value="high" />
|
||||
<el-option label="紧急" value="urgent" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="选择状态" style="width: 100%">
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="发布" value="published" />
|
||||
<el-option label="归档" value="archived" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否置顶">
|
||||
<el-switch v-model="form.is_pinned" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="form.publish_time"
|
||||
type="datetime"
|
||||
placeholder="选择发布时间"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="过期时间">
|
||||
<el-date-picker
|
||||
v-model="form.expire_time"
|
||||
type="datetime"
|
||||
placeholder="选择过期时间"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
||||
{{ isEdit ? '更新' : '创建' }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import api from '@/utils/api'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
// 公告列表
|
||||
const announcements = ref([])
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
status: '',
|
||||
type: ''
|
||||
})
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
id: null,
|
||||
title: '',
|
||||
content: '',
|
||||
type: 'system',
|
||||
priority: 'medium',
|
||||
status: 'draft',
|
||||
is_pinned: false,
|
||||
publish_time: '',
|
||||
expire_time: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入公告标题', trigger: 'blur' },
|
||||
{ min: 1, max: 255, message: '标题长度在 1 到 255 个字符', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入公告内容', trigger: 'blur' },
|
||||
{ min: 1, max: 2000, message: '内容长度在 1 到 2000 个字符', trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择公告类型', trigger: 'change' }
|
||||
],
|
||||
priority: [
|
||||
{ required: true, message: '请选择优先级', trigger: 'change' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const totalPages = computed(() => Math.ceil(pagination.total / pagination.limit))
|
||||
|
||||
// 方法
|
||||
const fetchAnnouncements = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
limit: pagination.limit,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
const response = await api.get('/announcements', { params })
|
||||
if (response.data.success) {
|
||||
announcements.value = response.data.data.announcements
|
||||
pagination.total = response.data.data.total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取公告列表失败:', error)
|
||||
ElMessage.error('获取公告列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
fetchAnnouncements()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
keyword: '',
|
||||
status: '',
|
||||
type: ''
|
||||
})
|
||||
pagination.page = 1
|
||||
fetchAnnouncements()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
pagination.limit = size
|
||||
pagination.page = 1
|
||||
fetchAnnouncements()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page) => {
|
||||
pagination.page = page
|
||||
fetchAnnouncements()
|
||||
}
|
||||
|
||||
const handleSortChange = ({ prop, order }) => {
|
||||
// 实现排序逻辑
|
||||
fetchAnnouncements()
|
||||
}
|
||||
|
||||
const showCreateDialog = () => {
|
||||
isEdit.value = false
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const showEditDialog = (row) => {
|
||||
isEdit.value = true
|
||||
Object.assign(form, {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
content: row.content,
|
||||
type: row.type,
|
||||
priority: row.priority,
|
||||
status: row.status,
|
||||
is_pinned: row.is_pinned,
|
||||
publish_time: row.publish_time,
|
||||
expire_time: row.expire_time
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
id: null,
|
||||
title: '',
|
||||
content: '',
|
||||
type: 'system',
|
||||
priority: 'medium',
|
||||
status: 'draft',
|
||||
is_pinned: false,
|
||||
publish_time: '',
|
||||
expire_time: ''
|
||||
})
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
submitting.value = true
|
||||
|
||||
const data = { ...form }
|
||||
delete data.id
|
||||
|
||||
let response
|
||||
if (isEdit.value) {
|
||||
response = await api.put(`/announcements/${form.id}`, data)
|
||||
} else {
|
||||
response = await api.post('/announcements', data)
|
||||
}
|
||||
|
||||
if (response.data.success) {
|
||||
ElMessage.success(isEdit.value ? '公告更新成功' : '公告创建成功')
|
||||
dialogVisible.value = false
|
||||
fetchAnnouncements()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error(isEdit.value ? '公告更新失败' : '公告创建失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const publishAnnouncement = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要发布这个公告吗?', '确认发布', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await api.put(`/announcements/${row.id}`, {
|
||||
status: 'published',
|
||||
publish_time: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
ElMessage.success('公告发布成功')
|
||||
fetchAnnouncements()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('发布公告失败:', error)
|
||||
ElMessage.error('发布公告失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAnnouncement = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个公告吗?删除后无法恢复。', '确认删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await api.delete(`/announcements/${row.id}`)
|
||||
if (response.data.success) {
|
||||
ElMessage.success('公告删除成功')
|
||||
fetchAnnouncements()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除公告失败:', error)
|
||||
ElMessage.error('删除公告失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
const getTypeLabel = (type) => {
|
||||
const labels = {
|
||||
system: '系统通知',
|
||||
maintenance: '维护公告',
|
||||
promotion: '活动推广',
|
||||
warning: '重要警告'
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
const getTypeTagType = (type) => {
|
||||
const types = {
|
||||
system: 'info',
|
||||
maintenance: 'warning',
|
||||
promotion: 'success',
|
||||
warning: 'danger'
|
||||
}
|
||||
return types[type] || 'info'
|
||||
}
|
||||
|
||||
const getPriorityLabel = (priority) => {
|
||||
const labels = {
|
||||
low: '低',
|
||||
medium: '中',
|
||||
high: '高',
|
||||
urgent: '紧急'
|
||||
}
|
||||
return labels[priority] || priority
|
||||
}
|
||||
|
||||
const getPriorityTagType = (priority) => {
|
||||
const types = {
|
||||
low: 'info',
|
||||
medium: '',
|
||||
high: 'warning',
|
||||
urgent: 'danger'
|
||||
}
|
||||
return types[priority] || ''
|
||||
}
|
||||
|
||||
const getStatusLabel = (status) => {
|
||||
const labels = {
|
||||
draft: '草稿',
|
||||
published: '已发布',
|
||||
archived: '已归档'
|
||||
}
|
||||
return labels[status] || status
|
||||
}
|
||||
|
||||
const getStatusTagType = (status) => {
|
||||
const types = {
|
||||
draft: 'info',
|
||||
published: 'success',
|
||||
archived: 'warning'
|
||||
}
|
||||
return types[status] || 'info'
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTime) => {
|
||||
if (!dateTime) return '-'
|
||||
return new Date(dateTime).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchAnnouncements()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.announcements-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.title-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pin-tag {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.announcements-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.search-row .el-col {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,525 +0,0 @@
|
||||
<template>
|
||||
<div class="daily-transfer-stats-container">
|
||||
<el-card class="page-header">
|
||||
<h2>昨日转账统计</h2>
|
||||
<p>统计用户昨日转出金额、今日入账金额及平账差额</p>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计概览 -->
|
||||
<el-card class="stats-overview">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ statsData.totalUsers }}</div>
|
||||
<div class="stat-label">涉及用户数</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">¥{{ statsData.totalYesterdayOut.toLocaleString() }}</div>
|
||||
<div class="stat-label">昨日总转出</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">¥{{ statsData.totalTodayIn.toLocaleString() }}</div>
|
||||
<div class="stat-label">今日总入账</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value" :class="{ 'negative': statsData.totalBalanceNeeded > 0, 'positive': statsData.totalBalanceNeeded < 0 }">
|
||||
¥{{ Math.abs(statsData.totalBalanceNeeded).toLocaleString() }}
|
||||
</div>
|
||||
<div class="stat-label">{{ statsData.totalBalanceNeeded > 0 ? '总缺口' : '总盈余' }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<el-card class="table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>用户转账统计详情</span>
|
||||
<div class="date-info">
|
||||
<el-tag type="info" size="small">昨日: {{ dateInfo.yesterday }}</el-tag>
|
||||
<el-tag type="success" size="small">今日: {{ dateInfo.today }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="userList"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
:default-sort="{ prop: 'balance_needed', order: 'descending' }"
|
||||
>
|
||||
<el-table-column prop="username" label="用户名" width="120" />
|
||||
<el-table-column prop="real_name" label="真实姓名" width="120" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
|
||||
<el-table-column label="用户余额" width="120" sortable prop="balance">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
class="amount-text"
|
||||
:class="{
|
||||
'negative': parseFloat(row.balance) < 0,
|
||||
'positive': parseFloat(row.balance) > 0,
|
||||
'zero': parseFloat(row.balance) === 0
|
||||
}"
|
||||
>
|
||||
¥{{ parseFloat(row.balance).toLocaleString() }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column label="昨日转出金额" width="140" sortable prop="yesterday_out_amount">
|
||||
<template #default="{ row }">
|
||||
<span class="amount-text">¥{{ parseFloat(row.yesterday_out_amount).toLocaleString() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="今日转账金额" width="140" sortable prop="today_in_amount">
|
||||
<template #default="{ row }">
|
||||
<span class="amount-text positive">¥{{ parseFloat(row.confirmed_from_amount).toLocaleString() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="今日入账金额" width="140" sortable prop="today_in_amount">
|
||||
<template #default="{ row }">
|
||||
<span class="amount-text positive">¥{{ parseFloat(row.today_in_amount).toLocaleString() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="平账差额" width="140" sortable prop="balance_needed">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
class="amount-text"
|
||||
:class="{
|
||||
'negative': parseFloat(row.balance_needed) > 0,
|
||||
'positive': parseFloat(row.balance_needed) < 0,
|
||||
'zero': parseFloat(row.balance_needed) === 0
|
||||
}"
|
||||
>
|
||||
{{ parseFloat(row.balance_needed) > 0 ? '还需' : parseFloat(row.balance_needed) < 0 ? '盈余' : '已平账' }}
|
||||
{{ parseFloat(row.balance_needed) !== 0 ? '¥' + Math.abs(parseFloat(row.balance_needed)).toLocaleString() : '' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="平账状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="getBalanceStatusType(parseFloat(row.balance_needed))"
|
||||
size="small"
|
||||
>
|
||||
{{ getBalanceStatusText(parseFloat(row.balance_needed)) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="viewUserTransfers(row.user_id)"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="showTransferDialog(row)"
|
||||
:disabled="parseFloat(row.balance_needed) <= 0"
|
||||
>
|
||||
转账平账
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 转账对话框 -->
|
||||
<el-dialog v-model="transferDialog.visible" title="转账平账" width="500px">
|
||||
<el-form :model="transferForm" :rules="transferRules" ref="transferFormRef" label-width="100px">
|
||||
<el-form-item label="目标用户">
|
||||
<el-input :value="transferDialog.targetUser?.username + ' (' + (transferDialog.targetUser?.real_name || '未设置') + ')'" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前缺口">
|
||||
<el-input :value="'¥' + Math.abs(parseFloat(transferDialog.targetUser?.balance_needed || 0)).toLocaleString()" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="转出用户" prop="from_user_id">
|
||||
<el-select v-model="transferForm.from_user_id" placeholder="选择转出用户" @change="onFromUserChange">
|
||||
<el-option
|
||||
v-for="user in users"
|
||||
:key="user.id"
|
||||
:label="`${user.username} (${user.real_name || '未设置'}) - 余额: ¥${user.balance || 0}`"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
<div v-if="selectedFromUser" class="user-balance-info">
|
||||
<el-text type="info" size="small">
|
||||
当前余额: ¥{{ selectedFromUser.balance || 0 }}
|
||||
</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="转账金额" prop="amount">
|
||||
<el-input-number
|
||||
v-model="transferForm.amount"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
placeholder="请输入转账金额"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div class="amount-suggestion">
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="setFullAmount"
|
||||
>
|
||||
设为全额平账 (¥{{ Math.abs(parseFloat(transferDialog.targetUser?.balance_needed || 0)).toLocaleString() }})
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="description">
|
||||
<el-input
|
||||
v-model="transferForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入转账备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="transferDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="createTransfer" :loading="transferDialog.loading">
|
||||
确认转账
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import api from '@/utils/api'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const userList = ref([])
|
||||
const users = ref([])
|
||||
const dateInfo = reactive({
|
||||
yesterday: '',
|
||||
today: ''
|
||||
})
|
||||
|
||||
// 转账对话框相关数据
|
||||
const transferDialog = ref({
|
||||
visible: false,
|
||||
loading: false,
|
||||
targetUser: null
|
||||
})
|
||||
|
||||
const transferForm = ref({
|
||||
from_user_id: '',
|
||||
amount: null,
|
||||
description: ''
|
||||
})
|
||||
|
||||
const selectedFromUser = ref(null)
|
||||
const transferFormRef = ref(null)
|
||||
|
||||
// 转账表单验证规则
|
||||
const transferRules = {
|
||||
from_user_id: [
|
||||
{ required: true, message: '请选择转出用户', trigger: 'change' }
|
||||
],
|
||||
amount: [
|
||||
{ required: true, message: '请输入转账金额', trigger: 'blur' },
|
||||
{ type: 'number', min: 0.01, message: '金额必须大于0.01', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
const statsData = computed(() => {
|
||||
const totalUsers = userList.value.length
|
||||
const totalYesterdayOut = userList.value.reduce((sum, user) => sum + parseFloat(user.yesterday_out_amount), 0)
|
||||
const totalTodayIn = userList.value.reduce((sum, user) => sum + parseFloat(user.today_in_amount), 0)
|
||||
const totalBalanceNeeded = userList.value.reduce((sum, user) => sum + parseFloat(user.balance_needed), 0)
|
||||
|
||||
return {
|
||||
totalUsers,
|
||||
totalYesterdayOut,
|
||||
totalTodayIn,
|
||||
totalBalanceNeeded
|
||||
}
|
||||
})
|
||||
|
||||
// 获取平账状态类型
|
||||
const getBalanceStatusType = (balanceNeeded) => {
|
||||
if (balanceNeeded > 0) return 'danger'
|
||||
if (balanceNeeded < 0) return 'success'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
// 获取平账状态文本
|
||||
const getBalanceStatusText = (balanceNeeded) => {
|
||||
if (balanceNeeded > 0) return '未平账'
|
||||
if (balanceNeeded < 0) return '有盈余'
|
||||
return '已平账'
|
||||
}
|
||||
|
||||
// 查看用户转账详情
|
||||
const viewUserTransfers = (userId) => {
|
||||
router.push(`/transfers?userId=${userId}`)
|
||||
}
|
||||
|
||||
// 显示转账对话框
|
||||
const showTransferDialog = (user) => {
|
||||
transferDialog.value.targetUser = user
|
||||
transferForm.value = {
|
||||
from_user_id: '',
|
||||
amount: null,
|
||||
description: `为用户 ${user.username} 平账转账`
|
||||
}
|
||||
selectedFromUser.value = null
|
||||
transferDialog.value.visible = true
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
// 转出用户变更时的处理
|
||||
const onFromUserChange = (userId) => {
|
||||
selectedFromUser.value = users.value.find(user => user.id === userId) || null
|
||||
}
|
||||
|
||||
// 设置全额平账金额
|
||||
const setFullAmount = () => {
|
||||
if (transferDialog.value.targetUser) {
|
||||
const balanceNeeded = Math.abs(parseFloat(transferDialog.value.targetUser.balance_needed))
|
||||
transferForm.value.amount = balanceNeeded
|
||||
}
|
||||
}
|
||||
|
||||
// 创建转账
|
||||
const createTransfer = async () => {
|
||||
if (!transferFormRef.value) return
|
||||
|
||||
try {
|
||||
await transferFormRef.value.validate()
|
||||
transferDialog.value.loading = true
|
||||
|
||||
const transferData = {
|
||||
to_user_id: transferDialog.value.targetUser.user_id,
|
||||
amount: transferForm.value.amount,
|
||||
description: transferForm.value.description,
|
||||
transfer_type: 'user_to_user'
|
||||
}
|
||||
|
||||
await api.transfers.createAdminTransfer(transferForm.value.from_user_id, transferData)
|
||||
|
||||
ElMessage.success('转账成功')
|
||||
transferDialog.value.visible = false
|
||||
fetchDailyStats() // 刷新统计数据
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.error?.message || error.response?.data?.message || error.message
|
||||
ElMessage.error('转账失败: ' + errorMessage)
|
||||
} finally {
|
||||
transferDialog.value.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await api.users.getUsers({ limit: 1000 })
|
||||
users.value = response.data.users || []
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取昨日转账统计数据
|
||||
const fetchDailyStats = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.transfers.getDailyStats()
|
||||
if (response.data.success) {
|
||||
userList.value = response.data.data.users
|
||||
dateInfo.yesterday = response.data.data.date.yesterday
|
||||
dateInfo.today = response.data.data.date.today
|
||||
} else {
|
||||
ElMessage.error(response.data.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取昨日转账统计失败:', error)
|
||||
ElMessage.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDailyStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.daily-transfer-stats-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.stat-value.positive {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-info {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount-text.positive {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.amount-text.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.amount-text.zero {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 转账对话框样式 */
|
||||
.user-balance-info {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.amount-suggestion {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.amount-suggestion .el-button {
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.amount-suggestion .el-button:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
/* 改进表格样式 */
|
||||
.el-table {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-table .el-table__header {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.el-table .el-table__header th {
|
||||
background-color: #f8f9fa;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 改进按钮间距 */
|
||||
.el-table .el-button + .el-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 改进统计卡片样式 */
|
||||
.stats-overview .el-card__body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
border-radius: 8px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 改进页面头部 */
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="login-header">
|
||||
<div class="logo">
|
||||
<el-icon class="logo-icon"><Setting /></el-icon>
|
||||
<h1 class="title">后台管理系统</h1>
|
||||
<h1 class="title">项目后台管理系统</h1>
|
||||
</div>
|
||||
<p class="subtitle">欢迎回来,请登录您的账户</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user