提交修改,后台管理页面bug修复,已经发布后台管理界面V1.0版本
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>admin-web</title>
|
||||
<title>宝来威办公助手后台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
type="text"
|
||||
:icon="Menu"
|
||||
@click="toggleSidebar"
|
||||
></el-button>
|
||||
>菜单</el-button>
|
||||
<div class="logo">
|
||||
<h1>后台管理</h1>
|
||||
<h1>宝来威办公助手后台</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,7 @@ import { Menu, ArrowDown, SwitchButton } from '@element-plus/icons-vue'
|
||||
import { useAuthStore } from '../../store/auth'
|
||||
import ThemeSwitcher from '../ThemeSwitcher.vue'
|
||||
|
||||
|
||||
// 路由实例
|
||||
const router = useRouter()
|
||||
|
||||
@@ -56,9 +57,6 @@ const authStore = useAuthStore()
|
||||
// 用户名
|
||||
const username = computed(() => authStore.username)
|
||||
|
||||
// 侧边栏显示状态(手机端)
|
||||
const isSidebarOpen = ref(false)
|
||||
|
||||
// 响应式布局:判断是否为手机端
|
||||
const isMobile = ref(false)
|
||||
|
||||
@@ -78,11 +76,9 @@ onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 切换侧边栏显示(手机端)
|
||||
// 打开侧边栏抽屉(手机端)
|
||||
const toggleSidebar = () => {
|
||||
isSidebarOpen.value = !isSidebarOpen.value
|
||||
// 发送事件给父组件
|
||||
emit('toggleSidebar', isSidebarOpen.value)
|
||||
emit('open-menu')
|
||||
}
|
||||
|
||||
// 登出处理
|
||||
@@ -92,10 +88,10 @@ const handleLogout = () => {
|
||||
}
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['toggleSidebar'])
|
||||
const emit = defineEmits(['open-menu'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -146,7 +142,7 @@ const emit = defineEmits(['toggleSidebar'])
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-size: 18px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.username {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<template>
|
||||
<div class="layout-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<Header @toggle-sidebar="handleToggleSidebar" />
|
||||
<Header @open-menu="handleOpenMenu" />
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<Sidebar
|
||||
:is-open="isSidebarOpen"
|
||||
@close-sidebar="handleCloseSidebar"
|
||||
/>
|
||||
<Sidebar v-model="isMenuOpen" />
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<main class="main-content" :class="{ 'main-content--open': isSidebarOpen }">
|
||||
<main class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
@@ -24,20 +21,15 @@ import Header from './Header.vue'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
|
||||
// 侧边栏显示状态(手机端)
|
||||
const isSidebarOpen = ref(false)
|
||||
const isMenuOpen = ref(false)
|
||||
|
||||
// 处理侧边栏切换事件(来自Header组件)
|
||||
const handleToggleSidebar = (isOpen) => {
|
||||
isSidebarOpen.value = isOpen
|
||||
}
|
||||
|
||||
// 处理侧边栏关闭事件(来自Sidebar组件)
|
||||
const handleCloseSidebar = () => {
|
||||
isSidebarOpen.value = false
|
||||
// Header 触发:打开抽屉菜单(手机端)
|
||||
const handleOpenMenu = () => {
|
||||
isMenuOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.layout-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -64,13 +56,6 @@ const handleCloseSidebar = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端侧边栏打开时,主内容区域添加左侧边距 */
|
||||
.main-content--open {
|
||||
@media (max-width: 767px) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,105 +1,105 @@
|
||||
<template>
|
||||
<aside
|
||||
class="sidebar"
|
||||
:class="{ 'sidebar--open': isOpen }"
|
||||
>
|
||||
<nav class="sidebar-nav">
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item" v-for="menu in menuList" :key="menu.path">
|
||||
<router-link
|
||||
:to="menu.path"
|
||||
class="nav-link"
|
||||
:class="{ 'is-active': $route.path === menu.path }"
|
||||
@click="handleMenuClick"
|
||||
>
|
||||
<el-icon class="nav-icon"><component :is="menu.icon" /></el-icon>
|
||||
<span class="nav-text">{{ menu.title }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 遮罩层(手机端) -->
|
||||
<div
|
||||
v-if="isOpen && isMobile"
|
||||
class="sidebar-mask"
|
||||
@click="handleMaskClick"
|
||||
></div>
|
||||
<!-- 桌面/平板:固定侧边栏(不做展开/收起动画) -->
|
||||
<aside v-if="!isMobile" class="sidebar-desktop">
|
||||
<el-menu router :default-active="activePath" class="sidebar-menu" @select="handleSelect">
|
||||
<el-menu-item index="/">
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
<span>首页</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/conversations">
|
||||
<el-icon><Message /></el-icon>
|
||||
<span>会话记录管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/users">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</aside>
|
||||
|
||||
<!-- 手机端:抽屉菜单(Element Plus 自带隐藏/遮罩/动画) -->
|
||||
<el-drawer
|
||||
v-else
|
||||
v-model="drawerOpen"
|
||||
direction="ltr"
|
||||
size="250px"
|
||||
:with-header="false"
|
||||
>
|
||||
<el-menu router :default-active="activePath" class="drawer-menu" @select="handleSelect">
|
||||
<el-menu-item index="/">
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
<span>首页</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/conversations">
|
||||
<el-icon><Message /></el-icon>
|
||||
<span>会话记录管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/users">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { HomeFilled, Message, User } from '@element-plus/icons-vue'
|
||||
|
||||
// 菜单数据
|
||||
const menuList = [
|
||||
{
|
||||
path: '/',
|
||||
title: '首页',
|
||||
icon: HomeFilled
|
||||
},
|
||||
{
|
||||
path: '/conversations',
|
||||
title: '会话记录管理',
|
||||
icon: Message
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
title: '用户管理',
|
||||
icon: User
|
||||
}
|
||||
]
|
||||
|
||||
// 侧边栏显示状态(手机端)
|
||||
const props = defineProps({
|
||||
isOpen: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['closeSidebar'])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 响应式布局:判断是否为手机端
|
||||
const isMobile = ref(false)
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
// 如果不是手机端,关闭侧边栏
|
||||
if (!isMobile.value) {
|
||||
emit('closeSidebar')
|
||||
// 切回非手机端时,确保抽屉关闭
|
||||
if (!isMobile.value && props.modelValue) {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化响应式状态
|
||||
onMounted(() => {
|
||||
handleResize()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 清理事件监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 菜单点击处理(手机端)
|
||||
const handleMenuClick = () => {
|
||||
if (isMobile.value) {
|
||||
emit('closeSidebar')
|
||||
}
|
||||
}
|
||||
const activePath = computed(() => route.path)
|
||||
|
||||
// 遮罩层点击处理(手机端)
|
||||
const handleMaskClick = () => {
|
||||
emit('closeSidebar')
|
||||
// el-drawer v-model 适配
|
||||
const drawerOpen = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(val) {
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
})
|
||||
|
||||
const handleSelect = () => {
|
||||
// 手机端点击菜单项后自动关闭抽屉
|
||||
if (isMobile.value) {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sidebar {
|
||||
<style scoped lang="scss">
|
||||
.sidebar-desktop {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
@@ -107,86 +107,20 @@ const handleMaskClick = () => {
|
||||
width: 250px;
|
||||
background-color: var(--background-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 99;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 桌面端默认显示,手机端默认隐藏 */
|
||||
@media (max-width: 767px) {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 侧边栏打开状态(手机端) */
|
||||
.sidebar--open {
|
||||
@media (max-width: 767px) {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 24px;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color-lighter);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: var(--primary-color);
|
||||
border-right: 3px solid var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 遮罩层(手机端) */
|
||||
.sidebar-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 98;
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端侧边栏宽度调整 */
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
.sidebar {
|
||||
@media (max-width: 1023px) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-menu,
|
||||
.drawer-menu {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
:deep(.el-drawer__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,14 +2,16 @@ import { defineStore } from 'pinia'
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
isLoggedIn: false,
|
||||
username: ''
|
||||
isLoggedIn: localStorage.getItem('isLoggedIn') === 'true',
|
||||
username: localStorage.getItem('username') || ''
|
||||
}),
|
||||
actions: {
|
||||
login(username, password) {
|
||||
if (username === 'Admin' && password === 'Admin') {
|
||||
this.isLoggedIn = true
|
||||
this.username = username
|
||||
localStorage.setItem('isLoggedIn', 'true')
|
||||
localStorage.setItem('username', username)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -17,6 +19,8 @@ export const useAuthStore = defineStore('auth', {
|
||||
logout() {
|
||||
this.isLoggedIn = false
|
||||
this.username = ''
|
||||
localStorage.removeItem('isLoggedIn')
|
||||
localStorage.removeItem('username')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,9 +13,37 @@
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="filterForm" inline label-width="80px" size="small">
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker v-model="filterForm.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
|
||||
end-placeholder="结束日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable></el-date-picker>
|
||||
<el-form-item label="快捷范围">
|
||||
<el-select v-model="filterForm.quickRange" placeholder="请选择" clearable style="width: 150px" @change="handleQuickRangeChange">
|
||||
<el-option label="今天" value="today"></el-option>
|
||||
<el-option label="最近3天" value="last3"></el-option>
|
||||
<el-option label="最近7天" value="last7"></el-option>
|
||||
<el-option label="最近30天" value="last30"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker
|
||||
v-model="filterForm.startDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
class="date-picker"
|
||||
@change="handleManualDateChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker
|
||||
v-model="filterForm.endDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
class="date-picker"
|
||||
@change="handleManualDateChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户">
|
||||
@@ -110,12 +138,51 @@ const emptyText = computed(() => loading.value ? '加载中...' : '暂无数据'
|
||||
|
||||
// 筛选表单数据
|
||||
const filterForm = reactive({
|
||||
dateRange: [],
|
||||
quickRange: 'last7',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
userKey: '',
|
||||
messageType: '',
|
||||
department: ''
|
||||
})
|
||||
|
||||
const formatDate = (date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
const addDays = (date, days) => {
|
||||
const d = new Date(date)
|
||||
d.setDate(d.getDate() + days)
|
||||
return d
|
||||
}
|
||||
|
||||
const applyQuickRange = (range) => {
|
||||
const today = new Date()
|
||||
const end = formatDate(today)
|
||||
let start = end
|
||||
|
||||
if (range === 'today') start = end
|
||||
if (range === 'last3') start = formatDate(addDays(today, -2))
|
||||
if (range === 'last7') start = formatDate(addDays(today, -6))
|
||||
if (range === 'last30') start = formatDate(addDays(today, -29))
|
||||
|
||||
filterForm.startDate = start
|
||||
filterForm.endDate = end
|
||||
}
|
||||
|
||||
const handleQuickRangeChange = (value) => {
|
||||
if (!value) return
|
||||
applyQuickRange(value)
|
||||
}
|
||||
|
||||
const handleManualDateChange = () => {
|
||||
// 手动修改开始/结束日期时,取消快捷范围选中态
|
||||
filterForm.quickRange = ''
|
||||
}
|
||||
|
||||
// 会话记录列表
|
||||
const conversationList = ref([])
|
||||
|
||||
@@ -135,12 +202,13 @@ const handleQuery = () => {
|
||||
const handleReset = () => {
|
||||
// 重置表单
|
||||
Object.keys(filterForm).forEach(key => {
|
||||
if (key === 'dateRange') {
|
||||
filterForm[key] = []
|
||||
} else {
|
||||
filterForm[key] = ''
|
||||
}
|
||||
filterForm[key] = ''
|
||||
})
|
||||
|
||||
// 默认最近7天
|
||||
filterForm.quickRange = 'last7'
|
||||
applyQuickRange('last7')
|
||||
|
||||
currentPage.value = 1
|
||||
fetchConversations()
|
||||
}
|
||||
@@ -196,8 +264,8 @@ const fetchConversations = async (isLoadMore = false) => {
|
||||
try {
|
||||
// 构建请求参数
|
||||
const params = {
|
||||
startTime: filterForm.dateRange[0] ? new Date(filterForm.dateRange[0]).getTime() : null,
|
||||
endTime: filterForm.dateRange[1] ? new Date(filterForm.dateRange[1]).getTime() + 24 * 60 * 60 * 1000 - 1 : null,
|
||||
startTime: filterForm.startDate ? `${filterForm.startDate}T00:00:00` : null,
|
||||
endTime: filterForm.endDate ? `${filterForm.endDate}T23:59:59.999` : null,
|
||||
userKey: filterForm.userKey,
|
||||
messageType: filterForm.messageType ? Number(filterForm.messageType) : null,
|
||||
department: filterForm.department,
|
||||
@@ -272,6 +340,8 @@ const fetchUsers = async () => {
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
// 默认最近7天
|
||||
applyQuickRange('last7')
|
||||
fetchUsers()
|
||||
fetchConversations()
|
||||
})
|
||||
|
||||
@@ -4,13 +4,26 @@
|
||||
|
||||
<!-- 系统统计卡片 -->
|
||||
<div class="stats-container">
|
||||
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon department-icon">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ totalUserCount }}</h3>
|
||||
<p>总用户数</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon user-icon">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ userCount }}</h3>
|
||||
<h3>{{ activeUserCount }}</h3>
|
||||
<p>活跃用户</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,23 +35,12 @@
|
||||
<el-icon><Message /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ conversationCount }}</h3>
|
||||
<h3>{{ totalConversationCount }}</h3>
|
||||
<p>会话记录</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon department-icon">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ departmentCount }}</h3>
|
||||
<p>部门数量</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
@@ -46,8 +48,8 @@
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ todayCount }}</h3>
|
||||
<p>今日新增</p>
|
||||
<h3>{{ todayNewConversationCount }}</h3>
|
||||
<p>今日会话</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -95,51 +97,25 @@ const authStore = useAuthStore()
|
||||
const username = computed(() => authStore.username)
|
||||
|
||||
// 统计数据
|
||||
const userCount = ref(0)
|
||||
const conversationCount = ref(0)
|
||||
const departmentCount = ref(0)
|
||||
const todayCount = ref(0)
|
||||
const activeUserCount = ref(0)
|
||||
const totalConversationCount = ref(0)
|
||||
const totalUserCount = ref(0)
|
||||
const todayNewConversationCount = ref(0)
|
||||
|
||||
// 最近会话记录
|
||||
const recentConversations = ref([])
|
||||
|
||||
// 数据转换:将后端返回的会话数据转换为前端所需格式
|
||||
const convertConversationData = (data) => {
|
||||
return data.map(item => ({
|
||||
id: item.Id,
|
||||
recordTime: new Date(item.RecordTime).toLocaleString('zh-CN'),
|
||||
recordTimeUTCStamp: item.RecordTimeUTCStamp,
|
||||
userName: item.UserName,
|
||||
conversationContent: item.ConversationContent,
|
||||
messageType: item.MessageType
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取系统统计数据
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
// 获取用户列表数据,用于统计
|
||||
const usersData = await request.get('/Admin/QueryUsers')
|
||||
const users = Array.isArray(usersData) ? usersData : usersData.data || []
|
||||
|
||||
// 获取会话记录数据,用于统计
|
||||
const conversationsData = await request.post('/Admin/QueryConversations', {})
|
||||
const conversations = Array.isArray(conversationsData) ? conversationsData : conversationsData.data || []
|
||||
|
||||
// 计算统计数据
|
||||
userCount.value = users.length
|
||||
conversationCount.value = conversations.length
|
||||
|
||||
// 计算部门数量
|
||||
const departments = new Set(users.map(user => user.Department))
|
||||
departmentCount.value = departments.size
|
||||
|
||||
// 计算今日新增会话数
|
||||
const today = new Date().toDateString()
|
||||
const todayConversations = conversations.filter(conv => {
|
||||
return new Date(conv.RecordTime).toDateString() === today
|
||||
})
|
||||
todayCount.value = todayConversations.length
|
||||
const res = await request.get('/Admin/QueryStats')
|
||||
const stats = res?.data ?? res
|
||||
|
||||
activeUserCount.value = stats?.activeUsers ?? 0
|
||||
totalConversationCount.value = stats?.totalConversations ?? 0
|
||||
totalUserCount.value = stats?.totalUsers ?? 0
|
||||
todayNewConversationCount.value = stats?.todayNewConversations ?? 0
|
||||
|
||||
// 获取最近会话记录
|
||||
await fetchRecentConversations()
|
||||
@@ -152,21 +128,28 @@ const fetchStats = async () => {
|
||||
// 获取最近会话记录
|
||||
const fetchRecentConversations = async () => {
|
||||
try {
|
||||
// 调用API获取最近的5条会话记录
|
||||
const data = await request.post('/Admin/QueryConversations', {})
|
||||
|
||||
const params = {
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
userKey: '',
|
||||
messageType: null,
|
||||
department: '',
|
||||
page: 1,
|
||||
pageSize: 8
|
||||
}
|
||||
|
||||
// 调用API获取会话记录
|
||||
const data = await request.post('/Admin/QueryConversations', JSON.stringify(params))
|
||||
|
||||
// 检查返回数据格式,确保是数组
|
||||
const rawData = Array.isArray(data) ? data : data.data || []
|
||||
|
||||
// 转换数据格式
|
||||
const convertedData = convertConversationData(rawData)
|
||||
|
||||
// 按时间排序(最新的记录在前面),并只显示最近5条
|
||||
recentConversations.value = convertedData
|
||||
recentConversations.value = rawData
|
||||
.sort((a, b) => {
|
||||
return b.recordTimeUTCStamp - a.recordTimeUTCStamp
|
||||
})
|
||||
.slice(0, 5)
|
||||
.slice(0, 10)
|
||||
} catch (error) {
|
||||
console.error('获取最近会话记录失败:', error)
|
||||
recentConversations.value = []
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '../store/auth'
|
||||
@@ -45,7 +45,7 @@ const loading = ref(false)
|
||||
|
||||
// 登录表单数据
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
username: 'Admin',
|
||||
password: ''
|
||||
})
|
||||
|
||||
@@ -65,6 +65,13 @@ const router = useRouter()
|
||||
// 认证状态管理
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 处理键盘按键事件
|
||||
const handleKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleLogin()
|
||||
}
|
||||
}
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
// 表单验证
|
||||
@@ -92,6 +99,16 @@ const handleLogin = async () => {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时添加键盘监听
|
||||
onMounted(() => {
|
||||
window.addEventListener('keypress', handleKeyPress)
|
||||
})
|
||||
|
||||
// 组件卸载时移除键盘监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keypress', handleKeyPress)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h2>用户管理</h2>
|
||||
|
||||
<!-- 筛选表单 -->
|
||||
<el-card class="filter-card">
|
||||
<!-- <el-card class="filter-card">
|
||||
<el-form :model="filterForm" inline label-width="80px">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-card> -->
|
||||
|
||||
<!-- 用户列表表格 -->
|
||||
<el-card class="table-card">
|
||||
|
||||
Reference in New Issue
Block a user