Commit de2d3fa6 authored by 水玉婷's avatar 水玉婷
Browse files

feat:添加在线用户列表

parent e3a28c68
......@@ -14,6 +14,12 @@ const routes = [
component: () => import('../views/History.vue'),
meta: { requiresAuth: true }
},
{
path: '/user',
name: 'User',
component: () => import('../views/User.vue'),
meta: { requiresAuth: true }
},
{
path: '/login',
name: 'Login',
......
<template>
<div class="user-list-container">
<!-- 用户列表头部 -->
<div class="user-list-header">
<div class="header-title">
<h3>在线用户</h3>
<span class="user-count">({{ userList.length }})</span>
</div>
<div class="header-actions">
<a-button
type="text"
size="small"
@click="refreshList"
class="refresh-button"
:loading="refreshing"
>
<template #icon>
<reload-outlined />
</template>
刷新
</a-button>
</div>
</div>
<!-- 搜索框 -->
<div class="user-search">
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索用户..."
size="small"
@search="handleSearch"
class="search-input"
/>
</div>
<!-- 用户列表 -->
<div class="user-list-content">
<div
v-for="user in filteredUsers"
:key="user.id"
:class="['user-item', { active: user.id === activeUserId }]"
@click="handleUserClick(user)"
>
<div class="user-info">
<span class="name-text">{{ user.cname }}</span>
</div>
</div>
<!-- 空状态 -->
<div v-if="filteredUsers.length === 0" class="empty-state">
<div class="empty-icon">
<user-outlined />
</div>
<p class="empty-text">暂无在线用户</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import {
ReloadOutlined,
UserOutlined
} from '@ant-design/icons-vue'
import { get } from '../utils/axios'
// 用户接口定义
interface User {
id: string
cname: string
}
// Props
interface Props {
activeUserId?: string
autoRefresh?: boolean
refreshInterval?: number
}
const props = withDefaults(defineProps<Props>(), {
autoRefresh: true,
refreshInterval: 30000 // 30秒
})
// Emits
const emit = defineEmits<{
'user-click': [user: User]
'refresh': []
}>()
// 响应式数据
const userList = ref<User[]>([])
const searchKeyword = ref('')
const refreshing = ref(false)
const refreshTimer = ref<number | null>(null)
// 计算属性
const filteredUsers = computed(() => {
return userList.value.filter(user =>
user.cname.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
// 方法
const refreshList = async () => {
refreshing.value = true
try {
// 模拟API调用
await fetchUserList()
emit('refresh')
} finally {
refreshing.value = false
}
}
const handleSearch = () => {
// 搜索逻辑已通过计算属性处理
}
const handleUserClick = (user: User) => {
emit('user-click', user)
}
// 获取用户列表
const fetchUserList = async () => {
try {
const response = await get(`${import.meta.env.VITE_SSE_PATH}/sse/list-user`)
if (response.data.code === 0) {
userList.value = response.data.data || []
} else {
console.error('获取用户列表失败:', response.data.msg)
}
} catch (error) {
console.error('调用用户列表接口失败:', error)
}
}
// 清理定时器
const clearRefreshTimer = () => {
if (refreshTimer.value) {
clearInterval(refreshTimer.value)
refreshTimer.value = null
}
}
// 生命周期
onMounted(() => {
fetchUserList()
// 自动刷新
if (props.autoRefresh) {
refreshTimer.value = setInterval(refreshList, props.refreshInterval)
}
})
onUnmounted(() => {
clearRefreshTimer()
})
</script>
<style lang="less" scoped>
@import './components/style.less';
.user-list-container {
background: @white;
border-radius: 8px;
height: 100%;
display: flex;
flex-direction: column;
}
.user-list-header {
padding: 16px;
border-bottom: 1px solid @gray-3;
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
display: flex;
align-items: center;
gap: 8px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: @gray-7;
}
.user-count {
color: @gray-5;
font-size: 14px;
}
}
.header-actions {
.ant-btn {
color: @gray-6;
&:hover {
color: @primary-color;
background: @blue-light-1;
}
}
}
}
.user-search {
padding: 12px 16px;
border-bottom: 1px solid @gray-3;
.search-input {
.ant-input {
border-radius: 20px;
}
}
}
.user-list-content {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.user-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background: @blue-light-1;
}
&.active {
background: @blue-light-2;
border-left: 3px solid @primary-color;
}
.user-info {
.name-text {
font-weight: 500;
color: @gray-7;
font-size: 14px;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: @gray-5;
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
color: @gray-4;
}
.empty-text {
margin: 0;
font-size: 14px;
}
}
// 响应式设计
@media (max-width: 768px) {
.user-list-container {
border-radius: 0;
box-shadow: none;
}
.user-item {
padding: 10px 12px;
}
}
</style>
\ No newline at end of file
......@@ -426,7 +426,7 @@ export class ContentTemplateService {
// 计算思考用时
const thinkingTime = this.thinkingStartTime ? Math.round((Date.now() - this.thinkingStartTime) / 1000) : 0;
// 对于历史数据,直接显示"已完成思考",不显示具体用时
const thinkingTimeText = isHistoryData ? '已完成思考' : (thinkingTime > 0 ? '已完成思考' : '');
const thinkingTimeText = isHistoryData ? '已完成思考' : (thinkingTime > 0 ? '已完成思考' : '已完成思考');
updatedResponse.contentBlocks[updatedBlockIndex].thinkContent = currentThinkContent;
// 添加思考时长信息到内容块中,供前端模板使用
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment