feat: 消息中心接入接口,修复分页功能样式,新增班级Excel导出,统计页课程详情接入接口
This commit is contained in:
parent
e687fa8ebd
commit
23c54eaf40
@ -13,6 +13,8 @@ export { default as UploadApi } from './modules/upload'
|
|||||||
export { default as StatisticsApi } from './modules/statistics'
|
export { default as StatisticsApi } from './modules/statistics'
|
||||||
export { default as ExamApi } from './modules/exam'
|
export { default as ExamApi } from './modules/exam'
|
||||||
export { ChatApi } from './modules/chat'
|
export { ChatApi } from './modules/chat'
|
||||||
|
export { default as MessageApi } from './modules/message'
|
||||||
|
export type { MessageItem, BackendMessageItem, SystemMessage } from './modules/message'
|
||||||
|
|
||||||
// API 基础配置
|
// API 基础配置
|
||||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot'
|
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot'
|
||||||
@ -234,6 +236,17 @@ export const API_ENDPOINTS = {
|
|||||||
FOLLOW: '/aiol/aiolUserFollow/follow',
|
FOLLOW: '/aiol/aiolUserFollow/follow',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 消息相关
|
||||||
|
MESSAGE: {
|
||||||
|
LIKES: '/aiol/message/likes',
|
||||||
|
COMMENTS_AT: '/aiol/message/comments_at',
|
||||||
|
UNREAD_COUNT: '/aiol/message/unread_count',
|
||||||
|
MARK_READ: '/aiol/message/likes/:id/read',
|
||||||
|
BATCH_MARK_READ: '/aiol/message/likes/batch-read',
|
||||||
|
DELETE: '/aiol/message/likes/:id',
|
||||||
|
BATCH_DELETE: '/aiol/message/likes/batch-delete',
|
||||||
|
},
|
||||||
|
|
||||||
// 资源相关
|
// 资源相关
|
||||||
RESOURCES: {
|
RESOURCES: {
|
||||||
DOWNLOAD: '/resources/:id/download',
|
DOWNLOAD: '/resources/:id/download',
|
||||||
|
@ -1228,19 +1228,10 @@ export class CourseApi {
|
|||||||
// 获取课程评论列表
|
// 获取课程评论列表
|
||||||
static async getCourseComments(courseId: string): Promise<ApiResponse<CourseComment[]>> {
|
static async getCourseComments(courseId: string): Promise<ApiResponse<CourseComment[]>> {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 获取课程评论数据,课程ID:', courseId)
|
|
||||||
console.log('🔍 API请求URL: /aiol/aiolComment/course/' + courseId + '/list')
|
|
||||||
|
|
||||||
const response = await ApiRequest.get<any>(`/aiol/aiolComment/course/${courseId}/list`)
|
const response = await ApiRequest.get<any>(`/aiol/aiolComment/course/${courseId}/list`)
|
||||||
console.log('🔍 评论API响应:', response)
|
|
||||||
|
|
||||||
// 处理后端响应格式
|
// 处理后端响应格式
|
||||||
if (response.data && response.data.success && response.data.result) {
|
if (response.data && response.data.success && response.data.result) {
|
||||||
console.log('✅ 响应状态码:', response.data.code)
|
|
||||||
console.log('✅ 响应消息:', response.data.message)
|
|
||||||
console.log('✅ 原始评论数据:', response.data.result)
|
|
||||||
console.log('✅ 评论数据数量:', response.data.result.length || 0)
|
|
||||||
|
|
||||||
// 适配数据格式
|
// 适配数据格式
|
||||||
const adaptedComments: CourseComment[] = response.data.result.map((comment: BackendComment) => ({
|
const adaptedComments: CourseComment[] = response.data.result.map((comment: BackendComment) => ({
|
||||||
id: comment.id,
|
id: comment.id,
|
||||||
@ -1256,8 +1247,6 @@ export class CourseApi {
|
|||||||
timeAgo: this.formatTimeAgo(comment.createTime) // 计算相对时间
|
timeAgo: this.formatTimeAgo(comment.createTime) // 计算相对时间
|
||||||
}))
|
}))
|
||||||
|
|
||||||
console.log('✅ 适配后的评论数据:', adaptedComments)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: response.data.code,
|
code: response.data.code,
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
|
428
src/api/modules/message.ts
Normal file
428
src/api/modules/message.ts
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
import request from '../request'
|
||||||
|
import type { ApiResponse } from '../types'
|
||||||
|
|
||||||
|
// 后端原始消息类型
|
||||||
|
export interface BackendMessageItem {
|
||||||
|
id: string
|
||||||
|
anntId: string
|
||||||
|
userId: string
|
||||||
|
titile: string
|
||||||
|
msgContent: string // JSON字符串,需要解析
|
||||||
|
sender: string
|
||||||
|
priority: string
|
||||||
|
readFlag: number
|
||||||
|
sendTime: string
|
||||||
|
pageNo: null
|
||||||
|
pageSize: null
|
||||||
|
msgCategory: string
|
||||||
|
busId: null
|
||||||
|
busType: null
|
||||||
|
openType: null
|
||||||
|
openPage: null
|
||||||
|
bizSource: null
|
||||||
|
msgAbstract: null
|
||||||
|
sendTimeBegin: null
|
||||||
|
sendTimeEnd: null
|
||||||
|
files: null
|
||||||
|
visitsNum: null
|
||||||
|
izTop: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析后的消息内容类型
|
||||||
|
export interface ParsedMessageContent {
|
||||||
|
sender: {
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
entity: {
|
||||||
|
type: string // "course", "comment" 等
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
action: string // "like", "favorite" 等
|
||||||
|
actionTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前端使用的消息类型
|
||||||
|
export interface MessageItem {
|
||||||
|
id: string
|
||||||
|
type: number // 0-点赞, 1-收藏
|
||||||
|
username: string
|
||||||
|
avatar: string
|
||||||
|
courseInfo: string
|
||||||
|
content: string
|
||||||
|
timestamp: string
|
||||||
|
courseImage?: string
|
||||||
|
isLiked: boolean
|
||||||
|
isFavorited: boolean
|
||||||
|
showReplyBox: boolean
|
||||||
|
replyContent: string
|
||||||
|
readFlag: number // 是否已读
|
||||||
|
action: string // 动作类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统消息类型
|
||||||
|
export interface SystemMessage {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
timestamp: string
|
||||||
|
isRead: boolean
|
||||||
|
type: 'info' | 'warning' | 'success' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息列表响应类型
|
||||||
|
export interface MessageListResponse {
|
||||||
|
records: BackendMessageItem[]
|
||||||
|
total: number
|
||||||
|
size: number
|
||||||
|
current: number
|
||||||
|
pages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息数量响应类型
|
||||||
|
export interface MessageCountResponse {
|
||||||
|
total: number
|
||||||
|
unread: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端消息列表响应格式(实际接口返回的格式)
|
||||||
|
export interface BackendMessageListResponse {
|
||||||
|
data: {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
code: number
|
||||||
|
result: MessageListResponse
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端消息数量响应格式(实际接口返回的格式)
|
||||||
|
export interface BackendMessageCountResponse {
|
||||||
|
data: {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
code: number
|
||||||
|
result: MessageCountResponse
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据转换函数
|
||||||
|
export const transformMessageData = (backendItem: BackendMessageItem): MessageItem => {
|
||||||
|
let parsedContent: ParsedMessageContent | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsedContent = JSON.parse(backendItem.msgContent)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析消息内容失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据action确定消息类型
|
||||||
|
const action = parsedContent?.action || 'unknown'
|
||||||
|
const type = action === 'like' ? 0 : action === 'favorite' ? 1 : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendItem.id,
|
||||||
|
type,
|
||||||
|
username: parsedContent?.sender?.username || backendItem.sender || '未知用户',
|
||||||
|
avatar: `https://picsum.photos/200/200?random=${backendItem.id}`, // 使用随机头像
|
||||||
|
courseInfo: parsedContent?.entity?.title || '未知课程',
|
||||||
|
content: action === 'like' ? '赞了我的评论' : '收藏了我的课程',
|
||||||
|
timestamp: backendItem.sendTime,
|
||||||
|
courseImage: action === 'favorite' ? `https://picsum.photos/300/200?random=${backendItem.id}` : undefined,
|
||||||
|
isLiked: false,
|
||||||
|
isFavorited: false,
|
||||||
|
showReplyBox: false,
|
||||||
|
replyContent: '',
|
||||||
|
readFlag: backendItem.readFlag,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息API类
|
||||||
|
class MessageApi {
|
||||||
|
/**
|
||||||
|
* 获取赞和收藏消息列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns Promise<BackendMessageListResponse>
|
||||||
|
*/
|
||||||
|
async getLikesMessages(params: {
|
||||||
|
current?: number
|
||||||
|
size?: number
|
||||||
|
type?: number // 可选:筛选消息类型
|
||||||
|
} = {}): Promise<BackendMessageListResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/aiol/message/likes',
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
current: params.current || 1,
|
||||||
|
size: params.size || 20,
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论和@消息列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns Promise<BackendMessageListResponse>
|
||||||
|
*/
|
||||||
|
async getCommentsAtMessages(params: {
|
||||||
|
current?: number
|
||||||
|
size?: number
|
||||||
|
type?: number // 可选:筛选消息类型
|
||||||
|
} = {}): Promise<BackendMessageListResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/aiol/message/comments_at',
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
current: params.current || 1,
|
||||||
|
size: params.size || 20,
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取赞和收藏消息数量(通过获取第一页数据来计算未读数量)
|
||||||
|
* @returns Promise<BackendMessageCountResponse>
|
||||||
|
*/
|
||||||
|
async getLikesMessageCount(): Promise<BackendMessageCountResponse> {
|
||||||
|
try {
|
||||||
|
// 通过获取第一页数据来获取总数和未读数量
|
||||||
|
const response = await this.getLikesMessages({ current: 1, size: 10 })
|
||||||
|
console.log('🔍 getLikesMessageCount 响应数据:', response)
|
||||||
|
|
||||||
|
if (response.data?.success) {
|
||||||
|
const total = response.data.result.total || 0
|
||||||
|
// 计算未读数量(readFlag为0表示未读)
|
||||||
|
const unread = response.data.result.records.filter(item => item.readFlag === 0).length
|
||||||
|
|
||||||
|
console.log('📊 赞和收藏消息统计:', {
|
||||||
|
total,
|
||||||
|
unread,
|
||||||
|
records: response.data.result.records.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
readFlag: item.readFlag,
|
||||||
|
sendTime: item.sendTime
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
message: '获取成功',
|
||||||
|
code: 200,
|
||||||
|
result: {
|
||||||
|
total,
|
||||||
|
unread
|
||||||
|
},
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: false,
|
||||||
|
message: response.data?.message || '获取失败',
|
||||||
|
code: response.data?.code || 500,
|
||||||
|
result: { total: 0, unread: 0 },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取消息数量失败:', error)
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: false,
|
||||||
|
message: '获取消息数量失败',
|
||||||
|
code: 500,
|
||||||
|
result: { total: 0, unread: 0 },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论和@消息数量(通过获取第一页数据来计算未读数量)
|
||||||
|
* @returns Promise<BackendMessageCountResponse>
|
||||||
|
*/
|
||||||
|
async getCommentsAtMessageCount(): Promise<BackendMessageCountResponse> {
|
||||||
|
try {
|
||||||
|
// 通过获取第一页数据来获取总数和未读数量
|
||||||
|
const response = await this.getCommentsAtMessages({ current: 1, size: 10 })
|
||||||
|
if (response.data?.success) {
|
||||||
|
const total = response.data.result.total || 0
|
||||||
|
// 计算未读数量(readFlag为0表示未读)
|
||||||
|
const unread = response.data.result.records.filter(item => item.readFlag === 0).length
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
message: '获取成功',
|
||||||
|
code: 200,
|
||||||
|
result: {
|
||||||
|
total,
|
||||||
|
unread
|
||||||
|
},
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: false,
|
||||||
|
message: response.data?.message || '获取失败',
|
||||||
|
code: response.data?.code || 500,
|
||||||
|
result: { total: 0, unread: 0 },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取评论@消息数量失败:', error)
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
success: false,
|
||||||
|
message: '获取评论@消息数量失败',
|
||||||
|
code: 500,
|
||||||
|
result: { total: 0, unread: 0 },
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记消息为已读
|
||||||
|
* @param messageId 消息ID
|
||||||
|
* @returns Promise<ApiResponse<null>>
|
||||||
|
*/
|
||||||
|
async markAsRead(messageId: number): Promise<ApiResponse<null>> {
|
||||||
|
return request({
|
||||||
|
url: `/aiol/message/likes/${messageId}/read`,
|
||||||
|
method: 'PUT'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量标记消息为已读
|
||||||
|
* @param messageIds 消息ID数组
|
||||||
|
* @returns Promise<ApiResponse<null>>
|
||||||
|
*/
|
||||||
|
async batchMarkAsRead(messageIds: number[]): Promise<ApiResponse<null>> {
|
||||||
|
return request({
|
||||||
|
url: '/aiol/message/likes/batch-read',
|
||||||
|
method: 'PUT',
|
||||||
|
data: { messageIds }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除消息
|
||||||
|
* @param messageId 消息ID
|
||||||
|
* @returns Promise<ApiResponse<null>>
|
||||||
|
*/
|
||||||
|
async deleteMessage(messageId: number): Promise<ApiResponse<null>> {
|
||||||
|
return request({
|
||||||
|
url: `/aiol/message/likes/${messageId}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除消息
|
||||||
|
* @param messageIds 消息ID数组
|
||||||
|
* @returns Promise<ApiResponse<null>>
|
||||||
|
*/
|
||||||
|
async batchDeleteMessages(messageIds: number[]): Promise<ApiResponse<null>> {
|
||||||
|
return request({
|
||||||
|
url: '/aiol/message/likes/batch-delete',
|
||||||
|
method: 'DELETE',
|
||||||
|
data: { messageIds }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户未读消息总数
|
||||||
|
* @returns Promise<ApiResponse<{ total: number, unread: number }>>
|
||||||
|
*/
|
||||||
|
async getUnreadMessageCount(): Promise<ApiResponse<{ total: number, unread: number }>> {
|
||||||
|
try {
|
||||||
|
const response = await request({
|
||||||
|
url: '/aiol/message/unread_count',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: '获取成功',
|
||||||
|
data: response.data || { total: 0, unread: 0 }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取未读消息数量失败:', error)
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: '获取未读消息数量失败',
|
||||||
|
data: { total: 0, unread: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统消息列表
|
||||||
|
* @param params 分页参数
|
||||||
|
* @returns Promise<ApiResponse<{ records: BackendMessageItem[], total: number, current: number, pages: number }>>
|
||||||
|
*/
|
||||||
|
async getSystemMessages(params?: { pageNo?: number, pageSize?: number }): Promise<ApiResponse<{
|
||||||
|
records: BackendMessageItem[]
|
||||||
|
total: number
|
||||||
|
current: number
|
||||||
|
pages: number
|
||||||
|
}>> {
|
||||||
|
return request({
|
||||||
|
url: '/aiol/message/system',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统消息数量统计
|
||||||
|
* @returns Promise<ApiResponse<{ total: number, unread: number }>>
|
||||||
|
*/
|
||||||
|
async getSystemMessageCount(): Promise<ApiResponse<{ total: number, unread: number }>> {
|
||||||
|
try {
|
||||||
|
const response = await this.getSystemMessages({ pageNo: 1, pageSize: 1 })
|
||||||
|
|
||||||
|
if (response.data && response.data.records) {
|
||||||
|
const total = response.data.total || 0
|
||||||
|
const unread = response.data.records.filter((item: BackendMessageItem) => item.readFlag === 0).length
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: '获取成功',
|
||||||
|
data: { total, unread }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: '获取系统消息数量失败',
|
||||||
|
data: { total: 0, unread: 0 }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取系统消息数量失败:', error)
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: '获取系统消息数量失败',
|
||||||
|
data: { total: 0, unread: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MessageApi()
|
@ -27,6 +27,7 @@ export interface TeachCourse {
|
|||||||
max_enroll?: number | null
|
max_enroll?: number | null
|
||||||
status?: number | null
|
status?: number | null
|
||||||
question?: string | null
|
question?: string | null
|
||||||
|
categoryId?: string | number | null // 课程分类ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新建课程请求参数
|
// 新建课程请求参数
|
||||||
|
@ -62,15 +62,15 @@ request.interceptors.request.use(
|
|||||||
// 添加请求时间戳
|
// 添加请求时间戳
|
||||||
config.headers['X-Request-Time'] = Date.now().toString()
|
config.headers['X-Request-Time'] = Date.now().toString()
|
||||||
|
|
||||||
// 开发环境下打印请求信息
|
// 开发环境下打印请求信息(已禁用)
|
||||||
if (import.meta.env.DEV) {
|
// if (import.meta.env.DEV) {
|
||||||
console.log('🚀 Request:', {
|
// console.log('🚀 Request:', {
|
||||||
url: config.url,
|
// url: config.url,
|
||||||
method: config.method,
|
// method: config.method,
|
||||||
params: config.params,
|
// params: config.params,
|
||||||
data: config.data,
|
// data: config.data,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
@ -85,14 +85,14 @@ request.interceptors.response.use(
|
|||||||
(response: AxiosResponse<any>) => {
|
(response: AxiosResponse<any>) => {
|
||||||
const { data } = response
|
const { data } = response
|
||||||
|
|
||||||
// 开发环境下打印响应信息
|
// 开发环境下打印响应信息(已禁用)
|
||||||
if (import.meta.env.DEV) {
|
// if (import.meta.env.DEV) {
|
||||||
console.log('✅ Response:', {
|
// console.log('✅ Response:', {
|
||||||
url: response.config.url,
|
// url: response.config.url,
|
||||||
status: response.status,
|
// status: response.status,
|
||||||
data: data,
|
// data: data,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 如果是blob响应,直接返回
|
// 如果是blob响应,直接返回
|
||||||
if (response.config.responseType === 'blob') {
|
if (response.config.responseType === 'blob') {
|
||||||
@ -105,6 +105,21 @@ request.interceptors.response.use(
|
|||||||
// 如果响应已经是标准格式
|
// 如果响应已经是标准格式
|
||||||
if (data && typeof data === 'object' && 'code' in data && 'message' in data) {
|
if (data && typeof data === 'object' && 'code' in data && 'message' in data) {
|
||||||
normalizedData = data
|
normalizedData = data
|
||||||
|
} else if (data && typeof data === 'object' && 'success' in data && 'result' in data) {
|
||||||
|
// 处理 { success, message, code, result } 格式
|
||||||
|
normalizedData = {
|
||||||
|
code: data.code || 200,
|
||||||
|
message: data.message || '请求成功',
|
||||||
|
data: data.result
|
||||||
|
}
|
||||||
|
} else if (data && typeof data === 'object' && 'data' in data && data.data && typeof data.data === 'object' && 'success' in data.data) {
|
||||||
|
// 处理 { data: { success, message, code, result } } 格式
|
||||||
|
const innerData = data.data
|
||||||
|
normalizedData = {
|
||||||
|
code: innerData.code || 200,
|
||||||
|
message: innerData.message || '请求成功',
|
||||||
|
data: innerData.result
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果响应不是标准格式,包装成标准格式
|
// 如果响应不是标准格式,包装成标准格式
|
||||||
normalizedData = {
|
normalizedData = {
|
||||||
@ -192,7 +207,10 @@ request.interceptors.response.use(
|
|||||||
errorMessage = '网络连接失败,请检查网络设置'
|
errorMessage = '网络连接失败,请检查网络设置'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于404错误,不显示错误消息,因为可能是接口不存在
|
||||||
|
if (response?.status !== 404) {
|
||||||
showMessage(errorMessage, 'error')
|
showMessage(errorMessage, 'error')
|
||||||
|
}
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -91,9 +91,7 @@ interface HotSearchItem {
|
|||||||
// 获取热门搜索数据
|
// 获取热门搜索数据
|
||||||
const fetchHotSearch = async () => {
|
const fetchHotSearch = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('🚀 获取热门搜索数据...')
|
|
||||||
const response = await ApiRequest.get<HotSearchItem[]>('/aiol/index/hot_search')
|
const response = await ApiRequest.get<HotSearchItem[]>('/aiol/index/hot_search')
|
||||||
console.log('📊 热门搜索API响应:', response)
|
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const apiResponse = response.data as any
|
const apiResponse = response.data as any
|
||||||
@ -104,7 +102,6 @@ const fetchHotSearch = async () => {
|
|||||||
if (success && apiResponse.result) {
|
if (success && apiResponse.result) {
|
||||||
hotSearchList.value = apiResponse.result
|
hotSearchList.value = apiResponse.result
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 获取热门搜索失败:', apiResponse.message || '未知错误')
|
|
||||||
// 使用模拟数据
|
// 使用模拟数据
|
||||||
hotSearchList.value = getMockHotSearch()
|
hotSearchList.value = getMockHotSearch()
|
||||||
}
|
}
|
||||||
@ -112,17 +109,12 @@ const fetchHotSearch = async () => {
|
|||||||
// 直接是数组数据
|
// 直接是数组数据
|
||||||
hotSearchList.value = apiResponse
|
hotSearchList.value = apiResponse
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 热门搜索数据格式错误')
|
|
||||||
hotSearchList.value = getMockHotSearch()
|
hotSearchList.value = getMockHotSearch()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 热门搜索响应为空')
|
|
||||||
hotSearchList.value = getMockHotSearch()
|
hotSearchList.value = getMockHotSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ 热门搜索数据加载完成:', hotSearchList.value)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 获取热门搜索异常:', error)
|
|
||||||
// 使用模拟数据
|
// 使用模拟数据
|
||||||
hotSearchList.value = getMockHotSearch()
|
hotSearchList.value = getMockHotSearch()
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
<n-button type="primary" ghost @click="showImportModal = true">
|
<n-button type="primary" ghost @click="showImportModal = true">
|
||||||
导入
|
导入
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button type="primary" ghost>
|
<n-button type="primary" ghost @click="handleExport">
|
||||||
导出
|
导出
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-input v-model:value="searchKeyword" placeholder="请输入姓名/账号" style="width: 200px"
|
<n-input v-model:value="searchKeyword" placeholder="请输入姓名/账号" style="width: 200px"
|
||||||
@ -290,6 +290,7 @@ import {
|
|||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import type { DataTableColumns } from 'naive-ui'
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
import ImportModal from '@/components/common/ImportModal.vue'
|
import ImportModal from '@/components/common/ImportModal.vue'
|
||||||
|
import { exportTableToExcel, type ExportColumn } from '@/utils/excelExport'
|
||||||
|
|
||||||
// 定义 props 类型
|
// 定义 props 类型
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -733,6 +734,82 @@ const handleBatchTransfer = () => {
|
|||||||
showBatchTransferModal.value = true
|
showBatchTransferModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出功能
|
||||||
|
const handleExport = () => {
|
||||||
|
try {
|
||||||
|
// 检查是否有选中的行
|
||||||
|
if (selectedRowKeys.value.length === 0) {
|
||||||
|
message.warning('请先选择要导出的学员')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前显示的数据源
|
||||||
|
const currentData = filteredData.value.length > 0 ? filteredData.value : data.value
|
||||||
|
|
||||||
|
// 根据选中的keys筛选数据
|
||||||
|
const exportData = currentData.filter((item: StudentItem) => selectedRowKeys.value.includes(item.id))
|
||||||
|
|
||||||
|
if (exportData.length === 0) {
|
||||||
|
message.warning('没有找到选中的学员数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义导出列配置
|
||||||
|
const exportColumns: ExportColumn[] = [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
key: 'studentName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '账号',
|
||||||
|
key: 'accountNumber'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '班级',
|
||||||
|
key: 'className',
|
||||||
|
render: (value: any) => {
|
||||||
|
// 处理班级名称,将多个班级用分号分隔
|
||||||
|
if (typeof value === 'string' && value.includes(',')) {
|
||||||
|
return value.split(',').join(';')
|
||||||
|
}
|
||||||
|
return value || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所在学院',
|
||||||
|
key: 'college'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '加入时间',
|
||||||
|
key: 'joinTime'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 生成文件名
|
||||||
|
const currentClassName = props.type === 'student'
|
||||||
|
? (classList.value.find(item => item.id === props.classId)?.className || '全部班级')
|
||||||
|
: '班级管理'
|
||||||
|
|
||||||
|
const filename = `${currentClassName}_选中学员信息`
|
||||||
|
|
||||||
|
// 执行导出
|
||||||
|
exportTableToExcel(exportData, exportColumns, filename)
|
||||||
|
|
||||||
|
message.success(`成功导出 ${exportData.length} 条选中学员信息`)
|
||||||
|
console.log('✅ 导出完成:', {
|
||||||
|
selectedCount: selectedRowKeys.value.length,
|
||||||
|
exportCount: exportData.length,
|
||||||
|
filename,
|
||||||
|
selectedIds: selectedRowKeys.value,
|
||||||
|
data: exportData.slice(0, 3) // 只打印前3条数据作为示例
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 导出失败:', error)
|
||||||
|
message.error('导出失败,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 确认批量调班
|
// 确认批量调班
|
||||||
const confirmBatchTransfer = async () => {
|
const confirmBatchTransfer = async () => {
|
||||||
if (!selectedTargetClass.value) {
|
if (!selectedTargetClass.value) {
|
||||||
|
@ -365,6 +365,42 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: AIAssistantDetail,
|
component: AIAssistantDetail,
|
||||||
meta: { title: '查看详情' }
|
meta: { title: '查看详情' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration',
|
||||||
|
name: 'AIOrchestration',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/index.vue'),
|
||||||
|
meta: { title: '智能体编排' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration/app-management',
|
||||||
|
name: 'AIAppManagement',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/app-management.vue'),
|
||||||
|
meta: { title: 'AI应用管理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration/knowledge-base',
|
||||||
|
name: 'AIKnowledgeBase',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/knowledge-base.vue'),
|
||||||
|
meta: { title: 'AI知识库' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration/process-design',
|
||||||
|
name: 'AIProcessDesign',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/process-design.vue'),
|
||||||
|
meta: { title: 'AI流程设计' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration/model-config',
|
||||||
|
name: 'AIModelConfig',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/model-config.vue'),
|
||||||
|
meta: { title: 'AI模型配置' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-orchestration/ocr-recognition',
|
||||||
|
name: 'AIOCRRecognition',
|
||||||
|
component: () => import('@/views/teacher/ai-orchestration/ocr-recognition.vue'),
|
||||||
|
meta: { title: 'OCR识别' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'student-management',
|
path: 'student-management',
|
||||||
name: 'StudentManagement',
|
name: 'StudentManagement',
|
||||||
|
117
src/utils/excelExport.ts
Normal file
117
src/utils/excelExport.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Excel导出工具函数
|
||||||
|
* 使用浏览器原生API实现前端Excel导出(CSV格式,可被Excel打开)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 定义导出数据的类型
|
||||||
|
export interface ExportColumn {
|
||||||
|
title: string
|
||||||
|
key: string
|
||||||
|
width?: number
|
||||||
|
render?: (value: any, row: any) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportOptions {
|
||||||
|
filename?: string
|
||||||
|
columns: ExportColumn[]
|
||||||
|
data: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将CSV字符串转换为Excel兼容的格式
|
||||||
|
* @param csvContent CSV内容
|
||||||
|
* @returns Excel兼容的Blob
|
||||||
|
*/
|
||||||
|
function csvToExcelBlob(csvContent: string): Blob {
|
||||||
|
// 添加BOM以支持中文
|
||||||
|
const BOM = '\uFEFF'
|
||||||
|
const content = BOM + csvContent
|
||||||
|
|
||||||
|
return new Blob([content], {
|
||||||
|
type: 'application/vnd.ms-excel;charset=utf-8'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义CSV字段值
|
||||||
|
* @param value 字段值
|
||||||
|
* @returns 转义后的值
|
||||||
|
*/
|
||||||
|
function escapeCsvField(value: any): string {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = String(value)
|
||||||
|
|
||||||
|
// 如果包含逗号、引号或换行符,需要用引号包围并转义引号
|
||||||
|
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
|
||||||
|
return `"${str.replace(/"/g, '""')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据导出为Excel文件(CSV格式)
|
||||||
|
* @param options 导出配置
|
||||||
|
*/
|
||||||
|
export function exportToExcel(options: ExportOptions): void {
|
||||||
|
const { filename = '导出数据', columns, data } = options
|
||||||
|
|
||||||
|
// 准备CSV内容
|
||||||
|
const csvRows: string[] = []
|
||||||
|
|
||||||
|
// 添加表头
|
||||||
|
const headers = columns.map(col => escapeCsvField(col.title))
|
||||||
|
csvRows.push(headers.join(','))
|
||||||
|
|
||||||
|
// 添加数据行
|
||||||
|
data.forEach(row => {
|
||||||
|
const rowData = columns.map(col => {
|
||||||
|
let value = row[col.key] || ''
|
||||||
|
|
||||||
|
// 如果有自定义渲染函数,使用它
|
||||||
|
if (col.render) {
|
||||||
|
value = col.render(value, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return escapeCsvField(value)
|
||||||
|
})
|
||||||
|
csvRows.push(rowData.join(','))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成CSV内容
|
||||||
|
const csvContent = csvRows.join('\n')
|
||||||
|
|
||||||
|
// 创建Excel兼容的Blob
|
||||||
|
const blob = csvToExcelBlob(csvContent)
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `${filename}_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化的导出函数,适用于表格数据
|
||||||
|
* @param data 表格数据
|
||||||
|
* @param columns 列配置
|
||||||
|
* @param filename 文件名
|
||||||
|
*/
|
||||||
|
export function exportTableToExcel(
|
||||||
|
data: any[],
|
||||||
|
columns: ExportColumn[],
|
||||||
|
filename: string = '表格数据'
|
||||||
|
): void {
|
||||||
|
exportToExcel({
|
||||||
|
filename,
|
||||||
|
columns,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
@ -11,7 +11,9 @@
|
|||||||
<div class="sidebar-container" v-if="!hideSidebar">
|
<div class="sidebar-container" v-if="!hideSidebar">
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<img :src="userStore.user?.avatar" :alt="userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username" class="avatar">
|
<img :src="userStore.user?.avatar"
|
||||||
|
:alt="userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username"
|
||||||
|
class="avatar">
|
||||||
<div class="avatar-text">
|
<div class="avatar-text">
|
||||||
{{ userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username }}
|
{{ userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username }}
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +56,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- 学员中心 - 可展开菜单 -->
|
<!-- 学员中心 - 可展开菜单 -->
|
||||||
<div class="nav-item" :class="{ active: activeNavItem === 1 }" @click="toggleStudentMenu('/teacher/student-management/student-library')">
|
<div class="nav-item" :class="{ active: activeNavItem === 1 }"
|
||||||
|
@click="toggleStudentMenu('/teacher/student-management/student-library')">
|
||||||
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt="">
|
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt="">
|
||||||
<span>学员中心</span>
|
<span>学员中心</span>
|
||||||
<n-icon class="expand-icon" :class="{ expanded: studentMenuExpanded }">
|
<n-icon class="expand-icon" :class="{ expanded: studentMenuExpanded }">
|
||||||
@ -82,7 +85,8 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/teacher/message-center" class="nav-item" :class="{ active: activeNavItem === 5 }"
|
<router-link to="/teacher/message-center" class="nav-item" :class="{ active: activeNavItem === 5 }"
|
||||||
@click="setActiveNavItem(5)">
|
@click="setActiveNavItem(5)">
|
||||||
<img :src="activeNavItem === 5 ? '/images/profile/message-active.png' : '/images/profile/message.png'" alt="">
|
<img :src="activeNavItem === 5 ? '/images/profile/message-active.png' : '/images/profile/message.png'"
|
||||||
|
alt="">
|
||||||
<span>消息中心</span>
|
<span>消息中心</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/teacher/personal-center" class="nav-item" :class="{ active: activeNavItem === 3 }"
|
<router-link to="/teacher/personal-center" class="nav-item" :class="{ active: activeNavItem === 3 }"
|
||||||
@ -101,6 +105,42 @@
|
|||||||
<span>AI助教</span>
|
<span>AI助教</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 智能体编排 - 可展开菜单 -->
|
||||||
|
<div class="nav-container orchestration-nav">
|
||||||
|
<div class="nav-item" :class="{ active: activeNavItem === 6 }" @click="toggleOrchestrationMenu">
|
||||||
|
<img :src="activeNavItem === 6 ? '/images/aiAssistant/AI助教1.png' : '/images/aiAssistant/AI助教2.png'" alt="">
|
||||||
|
<span>智能体编排</span>
|
||||||
|
<n-icon class="expand-icon" :class="{ expanded: orchestrationMenuExpanded }">
|
||||||
|
<ChevronDownOutline />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 智能体编排子菜单 -->
|
||||||
|
<div class="submenu-container" :class="{ expanded: orchestrationMenuExpanded }">
|
||||||
|
<router-link to="/teacher/ai-orchestration/app-management" class="submenu-item"
|
||||||
|
:class="{ active: activeSubNavItem === 'app-management' }" @click="setActiveSubNavItem('app-management')">
|
||||||
|
<span>AI应用管理</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/teacher/ai-orchestration/knowledge-base" class="submenu-item"
|
||||||
|
:class="{ active: activeSubNavItem === 'knowledge-base' }" @click="setActiveSubNavItem('knowledge-base')">
|
||||||
|
<span>AI知识库</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/teacher/ai-orchestration/process-design" class="submenu-item"
|
||||||
|
:class="{ active: activeSubNavItem === 'process-design' }" @click="setActiveSubNavItem('process-design')">
|
||||||
|
<span>AI流程设计</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/teacher/ai-orchestration/model-config" class="submenu-item"
|
||||||
|
:class="{ active: activeSubNavItem === 'model-config' }" @click="setActiveSubNavItem('model-config')">
|
||||||
|
<span>AI模型配置</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/teacher/ai-orchestration/ocr-recognition" class="submenu-item"
|
||||||
|
:class="{ active: activeSubNavItem === 'ocr-recognition' }"
|
||||||
|
@click="setActiveSubNavItem('ocr-recognition')">
|
||||||
|
<span>OCR识别</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧路由视图 -->
|
<!-- 右侧路由视图 -->
|
||||||
@ -146,10 +186,11 @@ const height = window.innerHeight;
|
|||||||
console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`);
|
console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`);
|
||||||
|
|
||||||
// 添加导航项激活状态管理
|
// 添加导航项激活状态管理
|
||||||
const activeNavItem = ref(0); // 0: 课程管理, 1: 学员管理, 2: 我的资源, 3: 个人中心, 4: 考试管理, 5: 消息中心
|
const activeNavItem = ref(0); // 0: 课程管理, 1: 学员管理, 2: 我的资源, 3: 个人中心, 4: 考试管理, 5: 消息中心, 6: 智能体编排
|
||||||
const activeSubNavItem = ref(''); // 子菜单激活状态
|
const activeSubNavItem = ref(''); // 子菜单激活状态
|
||||||
const examMenuExpanded = ref(false); // 考试管理菜单展开状态
|
const examMenuExpanded = ref(false); // 考试管理菜单展开状态
|
||||||
const studentMenuExpanded = ref(false); // 学员中心菜单展开状态
|
const studentMenuExpanded = ref(false); // 学员中心菜单展开状态
|
||||||
|
const orchestrationMenuExpanded = ref(false); // 智能体编排菜单展开状态
|
||||||
const showTopImage = ref(true); // 控制顶部图片显示/隐藏
|
const showTopImage = ref(true); // 控制顶部图片显示/隐藏
|
||||||
|
|
||||||
// 需要隐藏顶部图片的路由路径数组
|
// 需要隐藏顶部图片的路由路径数组
|
||||||
@ -165,7 +206,7 @@ const isAiHovered = ref(false);
|
|||||||
const isAiActive = computed(() => route.path.includes('/teacher/ai-assistant'));
|
const isAiActive = computed(() => route.path.includes('/teacher/ai-assistant'));
|
||||||
const breadcrumbDisplay = computed(() => {
|
const breadcrumbDisplay = computed(() => {
|
||||||
const currentPath = route.path;
|
const currentPath = route.path;
|
||||||
let arr = ['certificate/new', 'ai-assistant'];
|
let arr = ['certificate/new', 'ai-assistant', 'ai-orchestration'];
|
||||||
let found = arr.find(item => currentPath.includes(item));
|
let found = arr.find(item => currentPath.includes(item));
|
||||||
if (found) {
|
if (found) {
|
||||||
return false;
|
return false;
|
||||||
@ -183,8 +224,12 @@ const setActiveNavItem = (index: number) => {
|
|||||||
if (index !== 1) {
|
if (index !== 1) {
|
||||||
studentMenuExpanded.value = false;
|
studentMenuExpanded.value = false;
|
||||||
}
|
}
|
||||||
|
// 如果不是智能体编排,关闭智能体编排子菜单
|
||||||
|
if (index !== 6) {
|
||||||
|
orchestrationMenuExpanded.value = false;
|
||||||
|
}
|
||||||
// 如果切换到其他菜单,清空子菜单选中状态
|
// 如果切换到其他菜单,清空子菜单选中状态
|
||||||
if (index !== 4 && index !== 1) {
|
if (index !== 4 && index !== 1 && index !== 6) {
|
||||||
activeSubNavItem.value = '';
|
activeSubNavItem.value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,6 +267,17 @@ const toggleStudentMenu = (path: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 智能体编排菜单切换
|
||||||
|
const toggleOrchestrationMenu = () => {
|
||||||
|
orchestrationMenuExpanded.value = !orchestrationMenuExpanded.value;
|
||||||
|
activeNavItem.value = 6;
|
||||||
|
|
||||||
|
// 如果展开且没有选中子菜单,默认选中第一个
|
||||||
|
if (orchestrationMenuExpanded.value && !activeSubNavItem.value) {
|
||||||
|
activeSubNavItem.value = 'app-management';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置子菜单激活状态
|
// 设置子菜单激活状态
|
||||||
const setActiveSubNavItem = (subItem: string) => {
|
const setActiveSubNavItem = (subItem: string) => {
|
||||||
activeSubNavItem.value = subItem;
|
activeSubNavItem.value = subItem;
|
||||||
@ -235,6 +291,10 @@ const setActiveSubNavItem = (subItem: string) => {
|
|||||||
// 学员中心子菜单
|
// 学员中心子菜单
|
||||||
activeNavItem.value = 1;
|
activeNavItem.value = 1;
|
||||||
studentMenuExpanded.value = true;
|
studentMenuExpanded.value = true;
|
||||||
|
} else if (subItem === 'app-management' || subItem === 'knowledge-base' || subItem === 'process-design' || subItem === 'model-config' || subItem === 'ocr-recognition') {
|
||||||
|
// 智能体编排子菜单
|
||||||
|
activeNavItem.value = 6;
|
||||||
|
orchestrationMenuExpanded.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,6 +707,80 @@ const breadcrumbPathItems = computed(() => {
|
|||||||
return breadcrumbs;
|
return breadcrumbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 智能体编排模块的面包屑逻辑
|
||||||
|
if (currentPath.includes('ai-orchestration')) {
|
||||||
|
console.log('智能体编排页面路径:', currentPath);
|
||||||
|
let breadcrumbs: Array<{ title: string, path: string }> = [];
|
||||||
|
|
||||||
|
// 根据具体页面添加子页面标题
|
||||||
|
if (currentPath.includes('app-management')) {
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: '/teacher/ai-orchestration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI应用管理',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else if (currentPath.includes('knowledge-base')) {
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: '/teacher/ai-orchestration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI知识库',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else if (currentPath.includes('process-design')) {
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: '/teacher/ai-orchestration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI流程设计',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else if (currentPath.includes('model-config')) {
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: '/teacher/ai-orchestration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI模型配置',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else if (currentPath.includes('ocr-recognition')) {
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: '/teacher/ai-orchestration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OCR识别',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// 智能体编排主页面
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: '智能体编排',
|
||||||
|
path: currentPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('智能体编排页面面包屑:', breadcrumbs);
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
// 其他页面的面包屑逻辑
|
// 其他页面的面包屑逻辑
|
||||||
const matchedRoutes = route.matched;
|
const matchedRoutes = route.matched;
|
||||||
@ -731,6 +865,23 @@ const updateActiveNavItem = () => {
|
|||||||
activeSubNavItem.value = '';
|
activeSubNavItem.value = '';
|
||||||
examMenuExpanded.value = false;
|
examMenuExpanded.value = false;
|
||||||
studentMenuExpanded.value = false;
|
studentMenuExpanded.value = false;
|
||||||
|
} else if (path.includes('ai-orchestration')) {
|
||||||
|
// 智能体编排相关页面
|
||||||
|
activeNavItem.value = 6; // 智能体编排
|
||||||
|
orchestrationMenuExpanded.value = true;
|
||||||
|
|
||||||
|
// 根据路径设置子菜单激活状态
|
||||||
|
if (path.includes('app-management')) {
|
||||||
|
activeSubNavItem.value = 'app-management';
|
||||||
|
} else if (path.includes('knowledge-base')) {
|
||||||
|
activeSubNavItem.value = 'knowledge-base';
|
||||||
|
} else if (path.includes('process-design')) {
|
||||||
|
activeSubNavItem.value = 'process-design';
|
||||||
|
} else if (path.includes('model-config')) {
|
||||||
|
activeSubNavItem.value = 'model-config';
|
||||||
|
} else if (path.includes('ocr-recognition')) {
|
||||||
|
activeSubNavItem.value = 'ocr-recognition';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,7 +1020,17 @@ const updateActiveNavItem = () => {
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
height: calc(100vh - var(--top-height, 130px));
|
height: calc(100vh - var(--top-height, 130px));
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
/* 隐藏滚动条但保持滚动功能 */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
/* IE and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏Webkit浏览器的滚动条 */
|
||||||
|
.sidebar-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
@ -966,6 +1127,10 @@ const updateActiveNavItem = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 智能体编排菜单移除顶部外边距 */
|
||||||
|
.orchestration-nav {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
.nav-container .nav-item {
|
.nav-container .nav-item {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
|
71
src/views/teacher/ai-orchestration/app-management.vue
Normal file
71
src/views/teacher/ai-orchestration/app-management.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="development-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>AI应用管理</h1>
|
||||||
|
<p>管理和配置各种AI应用</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>功能开发中</h2>
|
||||||
|
<p>AI应用管理功能正在紧张开发中,敬请期待!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// AI应用管理页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.development-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
79
src/views/teacher/ai-orchestration/index.vue
Normal file
79
src/views/teacher/ai-orchestration/index.vue
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ai-orchestration-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>AI编排中心</h1>
|
||||||
|
<p>统一管理和配置AI相关功能</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="notice-content">
|
||||||
|
<h3>功能开发中</h3>
|
||||||
|
<p>AI编排中心功能正在开发中,敬请期待...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// AI编排中心主页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ai-orchestration-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-right: 24px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
</style>
|
71
src/views/teacher/ai-orchestration/knowledge-base.vue
Normal file
71
src/views/teacher/ai-orchestration/knowledge-base.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="development-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>AI知识库</h1>
|
||||||
|
<p>构建和管理AI知识库</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>功能开发中</h2>
|
||||||
|
<p>AI知识库功能正在紧张开发中,敬请期待!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// AI知识库页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.development-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
71
src/views/teacher/ai-orchestration/model-config.vue
Normal file
71
src/views/teacher/ai-orchestration/model-config.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="development-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>AI模型配置</h1>
|
||||||
|
<p>配置和优化AI模型参数</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>功能开发中</h2>
|
||||||
|
<p>AI模型配置功能正在紧张开发中,敬请期待!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// AI模型配置页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.development-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
71
src/views/teacher/ai-orchestration/ocr-recognition.vue
Normal file
71
src/views/teacher/ai-orchestration/ocr-recognition.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="development-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>OCR识别</h1>
|
||||||
|
<p>智能文字识别和处理</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>功能开发中</h2>
|
||||||
|
<p>OCR识别功能正在紧张开发中,敬请期待!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// OCR识别页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.development-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
71
src/views/teacher/ai-orchestration/process-design.vue
Normal file
71
src/views/teacher/ai-orchestration/process-design.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="development-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>AI流程设计</h1>
|
||||||
|
<p>可视化设计AI工作流程</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="development-notice">
|
||||||
|
<div class="notice-icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>功能开发中</h2>
|
||||||
|
<p>AI流程设计功能正在紧张开发中,敬请期待!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// AI流程设计页面
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.development-page {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 24px;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.development-notice p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,15 +15,32 @@
|
|||||||
|
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading-container">
|
||||||
|
<div class="loading-content">
|
||||||
|
<p>正在加载课程详情...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误状态 -->
|
||||||
|
<div v-else-if="error" class="error-container">
|
||||||
|
<div class="error-content">
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
<button @click="loadCourseDetail" class="retry-btn">重试</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 课程内容 -->
|
||||||
|
<template v-else>
|
||||||
<!-- 左侧课程图片 -->
|
<!-- 左侧课程图片 -->
|
||||||
<div class="course-image-section">
|
<div class="course-image-section">
|
||||||
<img src="/images/teacher/fj.png" alt="课程封面" class="course-image" />
|
<img :src="courseInfo.thumbnail || '/images/teacher/fj.png'" :alt="courseInfo.title" class="course-image" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧课程信息 -->
|
<!-- 右侧课程信息 -->
|
||||||
<div class="course-info-section">
|
<div class="course-info-section">
|
||||||
<h1 class="course-title">{{ courseInfo.title }}</h1>
|
<h1 class="course-title">{{ courseInfo.title }}</h1>
|
||||||
<p class="course-description">{{ courseInfo.description }}</p>
|
<div class="course-description" v-html="cleanHtmlContent(courseInfo.description)"></div>
|
||||||
|
|
||||||
<!-- 课程关键信息 -->
|
<!-- 课程关键信息 -->
|
||||||
<div class="course-metrics">
|
<div class="course-metrics">
|
||||||
@ -41,11 +58,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="metric-item">
|
<div class="metric-item">
|
||||||
<span class="metric-label">课程讲师:</span>
|
<span class="metric-label">课程讲师:</span>
|
||||||
<span class="metric-value">{{ courseInfo.instructor }}</span>
|
<span class="metric-value">
|
||||||
|
<span v-if="instructorsLoading">加载中...</span>
|
||||||
|
<span v-else>{{ courseInfo.instructor }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-item">
|
<div class="metric-item">
|
||||||
<span class="metric-label">教师团队:</span>
|
<span class="metric-label">教师团队:</span>
|
||||||
<span class="metric-value">{{ courseInfo.teacherCount }}人</span>
|
<span class="metric-value">
|
||||||
|
<span v-if="instructorsLoading">加载中...</span>
|
||||||
|
<span v-else>{{ courseInfo.teacherCount }}人</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-item">
|
<div class="metric-item">
|
||||||
<span class="metric-label">课程积分:</span>
|
<span class="metric-label">课程积分:</span>
|
||||||
@ -56,9 +79,11 @@
|
|||||||
<!-- 开课学期选择 -->
|
<!-- 开课学期选择 -->
|
||||||
<div class="semester-section">
|
<div class="semester-section">
|
||||||
<span class="semester-label">开课1学期</span>
|
<span class="semester-label">开课1学期</span>
|
||||||
<n-select v-model:value="selectedSemester" :options="semesterOptions" class="semester-select" size="small" />
|
<n-select v-model:value="selectedSemester" :options="semesterOptions" class="semester-select"
|
||||||
|
size="small" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部统计数据 -->
|
<!-- 底部统计数据 -->
|
||||||
@ -133,15 +158,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { NButton, NSelect } from 'naive-ui'
|
import { NButton, NSelect } from 'naive-ui'
|
||||||
|
import { CourseApi } from '@/api/modules/course'
|
||||||
|
import { TeachCourseApi } from '@/api/modules/teachCourse'
|
||||||
import CourseIntro from './tabs/CourseIntro.vue'
|
import CourseIntro from './tabs/CourseIntro.vue'
|
||||||
import TeachingTeam from './tabs/TeachingTeam.vue'
|
import TeachingTeam from './tabs/TeachingTeam.vue'
|
||||||
import ChapterList from './tabs/ChapterList.vue'
|
import ChapterList from './tabs/ChapterList.vue'
|
||||||
import CourseComments from './tabs/CourseComments.vue'
|
import CourseComments from './tabs/CourseComments.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const courseId = ref(route.params.id as string)
|
||||||
|
|
||||||
// 顶部图片控制
|
// 顶部图片控制
|
||||||
const showTopImage = ref(true) // 控制顶部图片显示/隐藏
|
const showTopImage = ref(true) // 控制顶部图片显示/隐藏
|
||||||
@ -155,9 +184,18 @@ const courseInfo = ref({
|
|||||||
duration: '4小时28分钟',
|
duration: '4小时28分钟',
|
||||||
instructor: '王建国',
|
instructor: '王建国',
|
||||||
teacherCount: 1,
|
teacherCount: 1,
|
||||||
credits: 60
|
credits: 60,
|
||||||
|
thumbnail: '/images/teacher/fj.png'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 课程数据状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
// 教师团队数据
|
||||||
|
const instructors = ref<any[]>([])
|
||||||
|
const instructorsLoading = ref(false)
|
||||||
|
|
||||||
// 课程统计数据
|
// 课程统计数据
|
||||||
const courseStats = ref({
|
const courseStats = ref({
|
||||||
views: 0,
|
views: 0,
|
||||||
@ -188,6 +226,213 @@ const goBack = () => {
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
showTopImage.value = false // 隐藏顶部图片容器
|
showTopImage.value = false // 隐藏顶部图片容器
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载课程详情
|
||||||
|
const loadCourseDetail = async () => {
|
||||||
|
console.log('🚀 开始加载教师课程详情,课程ID:', courseId.value)
|
||||||
|
|
||||||
|
if (!courseId.value || courseId.value.trim() === '') {
|
||||||
|
error.value = '课程ID无效'
|
||||||
|
console.error('❌ 课程ID无效:', courseId.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
console.log('📡 调用课程详情API...')
|
||||||
|
const response = await CourseApi.getCourseDetail(courseId.value)
|
||||||
|
console.log('📊 课程详情API响应:', response)
|
||||||
|
|
||||||
|
if (response.code === 0 || response.code === 200) {
|
||||||
|
const course = response.data
|
||||||
|
console.log('✅ 课程数据设置成功:', course)
|
||||||
|
console.log('📋 课程基本信息:', {
|
||||||
|
id: course?.id,
|
||||||
|
title: course?.title,
|
||||||
|
description: course?.description,
|
||||||
|
thumbnail: course?.thumbnail,
|
||||||
|
category: course?.category,
|
||||||
|
instructor: course?.instructor,
|
||||||
|
duration: course?.duration,
|
||||||
|
studentsCount: course?.studentsCount,
|
||||||
|
createdAt: course?.createdAt,
|
||||||
|
updatedAt: course?.updatedAt
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新课程信息
|
||||||
|
courseInfo.value = {
|
||||||
|
title: course?.title || '课程名称课程名称课',
|
||||||
|
description: course?.description || '本课程旨在带领学生系统地学习【课程核心领域】的知识。我们将从【最基础的概念】讲起,逐步深入到【高级主题或应用】。通过理论与实践相结合的方式,学生不仅能够掌握【具体的理论知识】,还能获得【具体的实践技能,如解决XX问题、开发XX应用等】。',
|
||||||
|
courseTime: formatCourseTime(course?.createdAt, course?.updatedAt),
|
||||||
|
category: course?.category?.name || '分类名称',
|
||||||
|
duration: course?.duration || '4小时28分钟',
|
||||||
|
instructor: course?.instructor?.name || '王建国',
|
||||||
|
teacherCount: 1, // 暂时固定为1
|
||||||
|
credits: 60, // 暂时固定为60
|
||||||
|
thumbnail: course?.thumbnail || '/images/teacher/fj.png'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从课程管理API获取分类信息
|
||||||
|
await loadCourseCategoryFromManagementAPI()
|
||||||
|
|
||||||
|
// 更新统计数据(这里可以根据实际API调整)
|
||||||
|
courseStats.value = {
|
||||||
|
views: course?.studentsCount || 0,
|
||||||
|
enrollments: course?.studentsCount || 0,
|
||||||
|
learners: Math.floor((course?.studentsCount || 0) * 0.8),
|
||||||
|
comments: Math.floor((course?.studentsCount || 0) * 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🎯 课程信息更新完成:', courseInfo.value)
|
||||||
|
console.log('📈 统计数据更新完成:', courseStats.value)
|
||||||
|
} else {
|
||||||
|
error.value = response.message || '获取课程详情失败'
|
||||||
|
console.error('❌ API返回错误:', response)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ 加载课程详情失败:', err)
|
||||||
|
error.value = '网络错误,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载教师团队信息
|
||||||
|
const loadCourseInstructors = async () => {
|
||||||
|
console.log('👥 开始加载课程教师团队,课程ID:', courseId.value)
|
||||||
|
|
||||||
|
if (!courseId.value || courseId.value.trim() === '') {
|
||||||
|
console.error('❌ 课程ID无效,无法加载教师团队')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instructorsLoading.value = true
|
||||||
|
console.log('📡 调用教师团队API...')
|
||||||
|
|
||||||
|
const response = await CourseApi.getCourseInstructors(courseId.value)
|
||||||
|
console.log('📊 教师团队API响应:', response)
|
||||||
|
|
||||||
|
if (response.code === 0 || response.code === 200) {
|
||||||
|
instructors.value = response.data || []
|
||||||
|
console.log('✅ 教师团队数据设置成功:', instructors.value)
|
||||||
|
console.log('👥 教师团队数量:', instructors.value.length)
|
||||||
|
|
||||||
|
// 更新课程信息中的教师团队人数
|
||||||
|
courseInfo.value.teacherCount = instructors.value.length
|
||||||
|
console.log('🎯 更新教师团队人数:', courseInfo.value.teacherCount)
|
||||||
|
|
||||||
|
// 从教师团队中获取所有教师名字(按sortOrder排序)
|
||||||
|
if (instructors.value.length > 0) {
|
||||||
|
// 按sortOrder排序,如果没有sortOrder则按原始顺序
|
||||||
|
const sortedInstructors = [...instructors.value].sort((a, b) => {
|
||||||
|
const aOrder = a.sortOrder || 0
|
||||||
|
const bOrder = b.sortOrder || 0
|
||||||
|
return aOrder - bOrder
|
||||||
|
})
|
||||||
|
// 将所有教师名字用逗号连接
|
||||||
|
const allInstructorNames = sortedInstructors.map(teacher => teacher.name).join('、')
|
||||||
|
courseInfo.value.instructor = allInstructorNames || '王建国'
|
||||||
|
console.log('👨🏫 更新所有教师:', courseInfo.value.instructor)
|
||||||
|
console.log('📋 教师团队排序:', sortedInstructors.map(t => ({ name: t.name, sortOrder: t.sortOrder })))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ 教师团队API返回错误:', response)
|
||||||
|
// 保持默认值
|
||||||
|
courseInfo.value.teacherCount = 1
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ 加载教师团队失败:', err)
|
||||||
|
// 保持默认值
|
||||||
|
courseInfo.value.teacherCount = 1
|
||||||
|
} finally {
|
||||||
|
instructorsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化课程时间
|
||||||
|
const formatCourseTime = (createdAt?: string, updatedAt?: string) => {
|
||||||
|
if (createdAt && updatedAt) {
|
||||||
|
const startDate = new Date(createdAt).toLocaleDateString('zh-CN')
|
||||||
|
const endDate = new Date(updatedAt).toLocaleDateString('zh-CN')
|
||||||
|
return `${startDate}-${endDate}`
|
||||||
|
}
|
||||||
|
return '2025-08-25-2026.08-25'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理HTML内容,确保安全显示
|
||||||
|
const cleanHtmlContent = (content: string) => {
|
||||||
|
if (!content) return ''
|
||||||
|
|
||||||
|
// 如果内容包含HTML标签,直接返回(假设后端已经处理过安全性)
|
||||||
|
// 这里可以添加更多的HTML清理逻辑,比如移除script标签等
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从课程管理API获取分类信息
|
||||||
|
const loadCourseCategoryFromManagementAPI = async () => {
|
||||||
|
try {
|
||||||
|
// 调用课程管理API获取课程信息(包含categoryId)
|
||||||
|
const response = await TeachCourseApi.getTeacherCourseList({})
|
||||||
|
|
||||||
|
if (response.data && response.data.result && response.data.result.length > 0) {
|
||||||
|
// 从所有课程中找到匹配当前课程ID的课程
|
||||||
|
const courseData = response.data.result.find(course => course.id === courseId.value)
|
||||||
|
|
||||||
|
if (!courseData) {
|
||||||
|
// 备选方案:直接获取分类列表,显示第一个分类作为示例
|
||||||
|
try {
|
||||||
|
const categoryResponse = await CourseApi.getCategories()
|
||||||
|
if (categoryResponse.code === 200 && categoryResponse.data && categoryResponse.data.length > 0) {
|
||||||
|
const firstCategory = categoryResponse.data[0]
|
||||||
|
courseInfo.value.category = firstCategory.name
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 备选方案失败:', error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (courseData.categoryId) {
|
||||||
|
// 获取分类列表
|
||||||
|
const categoryResponse = await CourseApi.getCategories()
|
||||||
|
|
||||||
|
if (categoryResponse.code === 200 && categoryResponse.data) {
|
||||||
|
// 解析categoryId(可能是逗号分隔的字符串)
|
||||||
|
const categoryIds = courseData.categoryId.toString().split(',').map((id: string) => parseInt(id.trim())).filter((id: number) => !isNaN(id))
|
||||||
|
|
||||||
|
// 根据ID匹配分类名称
|
||||||
|
const categoryNames = categoryIds.map((id: number) => {
|
||||||
|
const category = categoryResponse.data.find(cat => cat.id === id)
|
||||||
|
return category ? category.name : `未知分类${id}`
|
||||||
|
}).filter(Boolean)
|
||||||
|
|
||||||
|
// 更新课程信息中的分类
|
||||||
|
if (categoryNames.length > 0) {
|
||||||
|
courseInfo.value.category = categoryNames.join('、')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 获取分类信息失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('🎬 教师课程详情页面加载完成,课程ID:', courseId.value)
|
||||||
|
|
||||||
|
// 并行加载课程详情和教师团队信息
|
||||||
|
await Promise.all([
|
||||||
|
loadCourseDetail(),
|
||||||
|
loadCourseInstructors()
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('🎉 所有数据加载完成')
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -281,6 +526,38 @@ const handleClose = () => {
|
|||||||
margin: 25px auto;
|
margin: 25px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 加载和错误状态 */
|
||||||
|
.loading-container,
|
||||||
|
.error-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 400px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content p,
|
||||||
|
.error-content p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
background: #0C99DA;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn:hover {
|
||||||
|
background: #0A8BC7;
|
||||||
|
}
|
||||||
/* 左侧课程图片 */
|
/* 左侧课程图片 */
|
||||||
.course-image-section {
|
.course-image-section {
|
||||||
flex: 0 0 305px;
|
flex: 0 0 305px;
|
||||||
|
@ -65,8 +65,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { NBadge, NTabs, NTabPane, useMessage } from 'naive-ui'
|
import { NBadge, NTabs, NTabPane } from 'naive-ui'
|
||||||
import { ChatApi } from '@/api'
|
import { ChatApi, MessageApi } from '@/api'
|
||||||
|
|
||||||
// 导入子组件
|
// 导入子组件
|
||||||
import NotificationMessages from './components/NotificationMessages.vue'
|
import NotificationMessages from './components/NotificationMessages.vue'
|
||||||
@ -76,7 +76,6 @@ import SystemMessages from './components/SystemMessages.vue'
|
|||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const activeTab = ref('notification') // 当前激活的tab
|
const activeTab = ref('notification') // 当前激活的tab
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
// 各类消息数量(角标显示)
|
// 各类消息数量(角标显示)
|
||||||
const notificationCount = ref(0) // 即时消息数量
|
const notificationCount = ref(0) // 即时消息数量
|
||||||
@ -111,23 +110,48 @@ const loadMessageCounts = async (forceRefresh = false) => {
|
|||||||
// 检查缓存
|
// 检查缓存
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (!forceRefresh && now - cacheTimestamp.value < CACHE_DURATION) {
|
if (!forceRefresh && now - cacheTimestamp.value < CACHE_DURATION) {
|
||||||
|
console.log('📋 使用缓存的消息数量,跳过加载')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('🔄 开始加载消息数量...')
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 并行加载各类消息数量
|
// 首先尝试使用统一的未读消息数接口
|
||||||
await Promise.allSettled([
|
const unreadCountResult = await Promise.allSettled([loadTotalUnreadCount()])
|
||||||
|
console.log('🔍 统一接口结果:', unreadCountResult[0])
|
||||||
|
|
||||||
|
// 如果统一接口失败,则使用原有的分别加载方式
|
||||||
|
if (unreadCountResult[0].status === 'rejected') {
|
||||||
|
console.log('⚠️ 统一接口失败,使用分别加载方式')
|
||||||
|
// 并行加载各类消息数量,使用 Promise.allSettled 确保即使某个接口失败也不影响其他接口
|
||||||
|
const results = await Promise.allSettled([
|
||||||
loadNotificationCount(),
|
loadNotificationCount(),
|
||||||
loadCommentCount(),
|
loadCommentCount(),
|
||||||
loadFavoriteCount(),
|
loadFavoriteCount(),
|
||||||
loadSystemCount()
|
loadSystemCount()
|
||||||
])
|
])
|
||||||
|
|
||||||
|
console.log('📊 分别加载结果:', results)
|
||||||
|
// 检查是否有接口失败
|
||||||
|
const failedCount = results.filter(result => result.status === 'rejected').length
|
||||||
|
if (failedCount > 0) {
|
||||||
|
console.log(`⚠️ ${failedCount} 个接口加载失败`)
|
||||||
|
// 静默处理接口失败,不影响用户体验
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📈 最终消息数量:', {
|
||||||
|
notification: notificationCount.value,
|
||||||
|
comment: commentCount.value,
|
||||||
|
favorite: favoriteCount.value,
|
||||||
|
system: systemCount.value
|
||||||
|
})
|
||||||
|
|
||||||
cacheTimestamp.value = now
|
cacheTimestamp.value = now
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载消息数量失败:', error)
|
console.error('加载消息数量失败:', error)
|
||||||
message.error('加载消息数量失败')
|
// 不显示错误消息,因为可能只是部分接口失败
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@ -143,28 +167,49 @@ const debouncedRefresh = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载即时消息数量
|
// 加载总的未读消息数
|
||||||
const loadNotificationCount = async () => {
|
const loadTotalUnreadCount = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await ChatApi.getUnreadCount()
|
console.log('🔄 尝试调用统一未读消息数接口...')
|
||||||
if (response.data) {
|
const response = await MessageApi.getUnreadMessageCount()
|
||||||
notificationCount.value = response.data.total || 0
|
console.log('🔍 统一接口响应:', response)
|
||||||
return
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
const result = response.data || {}
|
||||||
|
console.log('📊 统一接口返回数据:', result)
|
||||||
|
|
||||||
|
// 使用统一接口返回的数据
|
||||||
|
// 注意:当前API只返回total和unread字段,我们需要分别调用各个接口获取具体数量
|
||||||
|
// 这里先设置默认值,后续会通过其他接口更新
|
||||||
|
commentCount.value = 0
|
||||||
|
favoriteCount.value = 0
|
||||||
|
systemCount.value = 0
|
||||||
|
notificationCount.value = 0
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
console.log('❌ 统一接口响应格式不正确')
|
||||||
|
return false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('获取未读消息数量失败,尝试备用方案:', error)
|
console.error('❌ 获取总未读消息数失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 备用方案:通过会话列表计算未读数量
|
// 加载即时消息数量
|
||||||
|
const loadNotificationCount = async () => {
|
||||||
|
// 由于 /aiol/aiolChat/unread-count 接口不存在,直接使用备用方案
|
||||||
try {
|
try {
|
||||||
const chatsResponse = await ChatApi.getMyChats()
|
const chatsResponse = await ChatApi.getMyChats()
|
||||||
if (chatsResponse.data?.success) {
|
if (chatsResponse.data?.success) {
|
||||||
notificationCount.value = chatsResponse.data.result.reduce((total: number, chat: any) => {
|
notificationCount.value = chatsResponse.data.result.reduce((total: number, chat: any) => {
|
||||||
return total + (chat.unreadCount || 0)
|
return total + (chat.unreadCount || 0)
|
||||||
}, 0)
|
}, 0)
|
||||||
|
} else {
|
||||||
|
notificationCount.value = 0
|
||||||
}
|
}
|
||||||
} catch (chatError) {
|
} catch (chatError) {
|
||||||
console.error('获取会话列表失败:', chatError)
|
// 静默处理错误,设置为0
|
||||||
notificationCount.value = 0
|
notificationCount.value = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,8 +217,12 @@ const loadNotificationCount = async () => {
|
|||||||
// 加载评论和@数量
|
// 加载评论和@数量
|
||||||
const loadCommentCount = async () => {
|
const loadCommentCount = async () => {
|
||||||
try {
|
try {
|
||||||
// TODO: 实现评论和@消息数量API
|
const response = await MessageApi.getCommentsAtMessageCount()
|
||||||
|
if (response.data?.success && (response.data.code === 200 || response.data.code === 0)) {
|
||||||
|
commentCount.value = response.data.result?.unread || 0
|
||||||
|
} else {
|
||||||
commentCount.value = 0
|
commentCount.value = 0
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取评论数量失败:', error)
|
console.error('获取评论数量失败:', error)
|
||||||
commentCount.value = 0
|
commentCount.value = 0
|
||||||
@ -183,10 +232,19 @@ const loadCommentCount = async () => {
|
|||||||
// 加载赞和收藏数量
|
// 加载赞和收藏数量
|
||||||
const loadFavoriteCount = async () => {
|
const loadFavoriteCount = async () => {
|
||||||
try {
|
try {
|
||||||
// TODO: 实现赞和收藏消息数量API
|
const response = await MessageApi.getLikesMessageCount()
|
||||||
|
console.log('🔍 loadFavoriteCount 响应数据:', response)
|
||||||
|
|
||||||
|
if (response.data?.success && (response.data.code === 200 || response.data.code === 0)) {
|
||||||
|
favoriteCount.value = response.data.result?.unread || 0
|
||||||
|
console.log('✅ 赞和收藏未读数量设置为:', favoriteCount.value)
|
||||||
|
} else {
|
||||||
favoriteCount.value = 0
|
favoriteCount.value = 0
|
||||||
|
console.log('❌ 赞和收藏接口响应失败,设置为0')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取收藏数量失败:', error)
|
console.error('获取收藏数量失败:', error)
|
||||||
|
// 如果接口不存在,设置为0,不显示错误消息
|
||||||
favoriteCount.value = 0
|
favoriteCount.value = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,10 +252,19 @@ const loadFavoriteCount = async () => {
|
|||||||
// 加载系统消息数量
|
// 加载系统消息数量
|
||||||
const loadSystemCount = async () => {
|
const loadSystemCount = async () => {
|
||||||
try {
|
try {
|
||||||
// TODO: 实现系统消息数量API
|
console.log('🔍 开始加载系统消息数量')
|
||||||
|
const response = await MessageApi.getSystemMessageCount()
|
||||||
|
console.log('🔍 系统消息数量API响应:', response)
|
||||||
|
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
systemCount.value = response.data.unread || 0
|
||||||
|
console.log('✅ 系统消息数量:', systemCount.value)
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ 系统消息数量API返回错误:', response)
|
||||||
systemCount.value = 0
|
systemCount.value = 0
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取系统消息数量失败:', error)
|
console.error('❌ 获取系统消息数量失败:', error)
|
||||||
systemCount.value = 0
|
systemCount.value = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span v-if="showEllipsis" class="ellipsis">...</span>
|
<span v-if="showEllipsis" class="ellipsis">...</span>
|
||||||
<button class="page-btn" @click="goToPage(totalPages)">{{ totalPages }}</button>
|
<button v-if="showLastPage" class="page-btn" @click="goToPage(totalPages)">{{ totalPages }}</button>
|
||||||
|
|
||||||
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一页</button>
|
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一页</button>
|
||||||
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(totalPages)">尾页</button>
|
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(totalPages)">尾页</button>
|
||||||
@ -140,7 +140,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useMessage, NIcon } from 'naive-ui'
|
import { useMessage, NIcon } from 'naive-ui'
|
||||||
import { ChatbubbleEllipsesOutline, TrashOutline, WarningOutline } from '@vicons/ionicons5'
|
import { ChatbubbleEllipsesOutline, TrashOutline, WarningOutline } from '@vicons/ionicons5'
|
||||||
import { CourseApi } from '@/api'
|
import { MessageApi, BackendMessageItem } from '@/api'
|
||||||
|
|
||||||
// 消息类型定义
|
// 消息类型定义
|
||||||
interface Message {
|
interface Message {
|
||||||
@ -161,17 +161,54 @@ interface Message {
|
|||||||
likeCount?: number
|
likeCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理@标记的函数
|
||||||
|
const processAtMentions = (content: string): string => {
|
||||||
|
// 将 [user:id:username] 格式转换为 @username 格式
|
||||||
|
return content.replace(/\[user:(\d+):([^\]]+)\]/g, '@$2')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据转换函数
|
||||||
|
const transformMessageData = (backendItem: BackendMessageItem): Message => {
|
||||||
|
let parsedContent: any = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsedContent = JSON.parse(backendItem.msgContent)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析消息内容失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据消息内容确定类型(评论或@)
|
||||||
|
const rawCommentContent = parsedContent?.comment?.content || ''
|
||||||
|
const hasAtMention = rawCommentContent.includes('[user:')
|
||||||
|
const type = hasAtMention ? 1 : 0 // 1=@, 0=评论
|
||||||
|
|
||||||
|
// 处理@标记,转换为友好的显示格式
|
||||||
|
const processedContent = processAtMentions(rawCommentContent)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendItem.id,
|
||||||
|
type,
|
||||||
|
username: parsedContent?.sender?.username || backendItem.sender || '未知用户',
|
||||||
|
avatar: '/images/activity/5.png', // 使用默认头像
|
||||||
|
courseInfo: parsedContent?.entity?.title || '未知课程',
|
||||||
|
content: processedContent,
|
||||||
|
timestamp: backendItem.sendTime,
|
||||||
|
isLiked: false,
|
||||||
|
isFavorited: false,
|
||||||
|
showReplyBox: false,
|
||||||
|
replyContent: '',
|
||||||
|
courseId: parsedContent?.entity?.id,
|
||||||
|
userId: parsedContent?.sender?.id,
|
||||||
|
images: [],
|
||||||
|
likeCount: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const messages = ref<Message[]>([])
|
const messages = ref<Message[]>([])
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 可用课程列表
|
|
||||||
const availableCourses = ref<Array<{ id: string, name: string }>>([
|
|
||||||
{ id: '1', name: '测试课程1' },
|
|
||||||
{ id: '2', name: '测试课程2' },
|
|
||||||
{ id: '3', name: '测试课程3' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 分页相关
|
// 分页相关
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
@ -196,6 +233,11 @@ const showEllipsis = computed(() => {
|
|||||||
return currentPage.value + 2 < totalPages.value - 1
|
return currentPage.value + 2 < totalPages.value - 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 是否显示最后一页按钮
|
||||||
|
const showLastPage = computed(() => {
|
||||||
|
return totalPages.value > 1 && currentPage.value + 2 < totalPages.value
|
||||||
|
})
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMessages()
|
loadMessages()
|
||||||
@ -205,60 +247,52 @@ onMounted(() => {
|
|||||||
const loadMessages = async () => {
|
const loadMessages = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 获取所有课程的评论数据
|
const response = await MessageApi.getCommentsAtMessages({
|
||||||
const allComments: Message[] = []
|
current: currentPage.value,
|
||||||
|
size: pageSize.value
|
||||||
|
})
|
||||||
|
|
||||||
for (const course of availableCourses.value) {
|
if (response.data?.success && (response.data.code === 200 || response.data.code === 0)) {
|
||||||
try {
|
const data = response.data.result
|
||||||
const response = await CourseApi.getCourseComments(course.id)
|
// 转换后端数据为前端格式
|
||||||
if (response.data && response.data.length > 0) {
|
messages.value = (data.records || []).map(transformMessageData)
|
||||||
// 转换评论数据为消息格式
|
totalPages.value = data.pages || 1
|
||||||
const courseComments = response.data.map((comment: any) => ({
|
totalCount.value = data.total || 0
|
||||||
id: comment.id,
|
} else {
|
||||||
type: 0, // 评论类型
|
// 静默处理响应失败,不显示错误消息
|
||||||
username: comment.userName || '匿名用户',
|
messages.value = []
|
||||||
avatar: comment.userAvatar || '',
|
}
|
||||||
courseInfo: course.name,
|
} catch (error: any) {
|
||||||
content: comment.content || '',
|
console.error('加载消息失败:', error)
|
||||||
timestamp: comment.timeAgo || comment.createTime,
|
// 静默处理错误,不显示错误消息给用户
|
||||||
|
// 如果是404错误,说明接口不存在,使用模拟数据
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
// 使用模拟数据
|
||||||
|
messages.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 1, // @消息
|
||||||
|
username: '小明',
|
||||||
|
avatar: '/images/activity/5.png',
|
||||||
|
courseInfo: '《python语言基础与应用》',
|
||||||
|
content: '老师讲得真棒!@李四 快来看这个课程!',
|
||||||
|
timestamp: '2025-09-17 04:27:25',
|
||||||
isLiked: false,
|
isLiked: false,
|
||||||
isFavorited: false,
|
isFavorited: false,
|
||||||
showReplyBox: false,
|
showReplyBox: false,
|
||||||
replyContent: '',
|
replyContent: '',
|
||||||
courseId: course.id,
|
courseId: '1954463468539371522',
|
||||||
userId: comment.userId,
|
userId: '1966804797404344321',
|
||||||
images: comment.images || [],
|
images: [],
|
||||||
likeCount: comment.likeCount || 0
|
likeCount: 0
|
||||||
}))
|
|
||||||
|
|
||||||
allComments.push(...courseComments)
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
]
|
||||||
console.warn(`获取课程 ${course.name} 评论失败:`, error)
|
totalPages.value = 1
|
||||||
|
totalCount.value = 1
|
||||||
|
} else {
|
||||||
|
// 静默处理其他错误,不显示错误消息
|
||||||
|
messages.value = []
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 按时间排序(最新的在前)
|
|
||||||
allComments.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
||||||
|
|
||||||
// 分页处理
|
|
||||||
const startIndex = (currentPage.value - 1) * pageSize.value
|
|
||||||
const endIndex = startIndex + pageSize.value
|
|
||||||
messages.value = allComments.slice(startIndex, endIndex)
|
|
||||||
|
|
||||||
// 更新分页信息
|
|
||||||
totalCount.value = allComments.length
|
|
||||||
totalPages.value = Math.ceil(totalCount.value / pageSize.value)
|
|
||||||
|
|
||||||
console.log('✅ 加载评论和@消息成功:', {
|
|
||||||
total: totalCount.value,
|
|
||||||
current: messages.value.length,
|
|
||||||
page: currentPage.value
|
|
||||||
})
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 加载评论和@消息失败:', error)
|
|
||||||
message.error('加载消息失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@ -575,6 +609,7 @@ const goToPage = (page: number) => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn i {
|
.action-btn i {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
@ -695,21 +730,21 @@ const goToPage = (page: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-btn:hover:not(:disabled) {
|
.page-btn:hover:not(:disabled) {
|
||||||
border-color: #1890ff;
|
border-color: #0089D1;
|
||||||
color: #1890ff;
|
color: #0089D1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-btn.active {
|
.page-btn.active {
|
||||||
background: #1890ff;
|
background: #0089D1;
|
||||||
border-color: #1890ff;
|
border-color: #0089D1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-btn:disabled {
|
.page-btn:disabled {
|
||||||
color: #bfbfbf;
|
color: #878787;
|
||||||
border-color: #f0f0f0;
|
border: none;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background: #fafafa;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
|
@ -1,44 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="message-center">
|
<div class="message-center">
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading-container">
|
||||||
|
<div class="loading-text">加载中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 消息列表 -->
|
<!-- 消息列表 -->
|
||||||
<div class="message-list">
|
<div v-else class="message-list">
|
||||||
<div v-for="message in messages" :key="message.id" class="message-item">
|
<div v-if="messages.length === 0" class="empty-container">
|
||||||
|
<div class="empty-text">暂无消息</div>
|
||||||
|
</div>
|
||||||
|
<div v-else v-for="messageItem in messages" :key="messageItem.id" class="message-item">
|
||||||
<!-- 用户头像 -->
|
<!-- 用户头像 -->
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<img :src="message.avatar" :alt="message.username" class="avatar" />
|
<img :src="messageItem.avatar" :alt="messageItem.username" class="avatar" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 消息内容 -->
|
<!-- 消息内容 -->
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<!-- 点赞消息 -->
|
<!-- 点赞消息 -->
|
||||||
<div class="message-header" v-if="message.type === 0">
|
<div class="message-header" v-if="messageItem.type === 0">
|
||||||
<div>
|
<div>
|
||||||
<span class="username">
|
<span class="username">
|
||||||
{{ message.username }}赞了我的评论:
|
{{ messageItem.username }}赞了我的评论:
|
||||||
</span>
|
</span>
|
||||||
<span class="content">{{ message.content }}</span>
|
<span class="content">{{ messageItem.content }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="timestamp">{{ message.timestamp }}</span>
|
<span class="timestamp">{{ messageItem.timestamp }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 收藏消息 -->
|
<!-- 收藏消息 -->
|
||||||
<div class="message-header" v-if="message.type === 1">
|
<div class="message-header" v-if="messageItem.type === 1">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<div>
|
<div>
|
||||||
<span class="username">
|
<span class="username">
|
||||||
{{ message.username }}收藏了我的课程:
|
{{ messageItem.username }}收藏了我的课程:
|
||||||
</span>
|
</span>
|
||||||
<span class="course-info">{{ message.courseInfo }}</span>
|
<span class="course-info">{{ messageItem.courseInfo }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="timestamp">{{ message.timestamp }}</span>
|
<span class="timestamp">{{ messageItem.timestamp }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 课程封面图片 - 只有收藏消息才显示 -->
|
<!-- 课程封面图片 - 只有收藏消息才显示 -->
|
||||||
<div v-if="message.type === 1 && message.courseImage" class="course-image-container">
|
<div v-if="messageItem.type === 1 && messageItem.courseImage" class="course-image-container">
|
||||||
<img :src="message.courseImage" :alt="message.courseInfo" class="course-image" />
|
<img :src="messageItem.courseImage" :alt="messageItem.courseInfo" class="course-image" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message-text" v-if="message.type === 0">课程:
|
<div class="message-text" v-if="messageItem.type === 0">课程:
|
||||||
<span class="course-info">{{ message.courseInfo }}</span>
|
<span class="course-info">{{ messageItem.courseInfo }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +63,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span v-if="showEllipsis" class="ellipsis">...</span>
|
<span v-if="showEllipsis" class="ellipsis">...</span>
|
||||||
<button class="page-btn" @click="goToPage(totalPages)">{{ totalPages }}</button>
|
<button v-if="showLastPage" class="page-btn" @click="goToPage(totalPages)">{{ totalPages }}</button>
|
||||||
|
|
||||||
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一页</button>
|
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一页</button>
|
||||||
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(totalPages)">尾页</button>
|
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(totalPages)">尾页</button>
|
||||||
@ -65,124 +73,54 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { MessageApi, type MessageItem, type BackendMessageItem } from '@/api'
|
||||||
|
|
||||||
// 消息类型定义
|
// 消息类型定义(使用API中的类型)
|
||||||
interface Message {
|
type Message = MessageItem
|
||||||
id: number
|
|
||||||
type: number // 0-点赞, 1-收藏
|
// 数据转换函数
|
||||||
username: string
|
const transformMessageData = (backendItem: BackendMessageItem): MessageItem => {
|
||||||
avatar: string
|
let parsedContent: any = null
|
||||||
courseInfo: string
|
|
||||||
content: string
|
try {
|
||||||
timestamp: string
|
parsedContent = JSON.parse(backendItem.msgContent)
|
||||||
courseImage?: string
|
} catch (error) {
|
||||||
isLiked: boolean
|
console.error('解析消息内容失败:', error)
|
||||||
isFavorited: boolean
|
}
|
||||||
showReplyBox: boolean
|
|
||||||
replyContent: string
|
// 根据action确定消息类型
|
||||||
|
const action = parsedContent?.action || 'unknown'
|
||||||
|
const type = action === 'like' ? 0 : action === 'favorite' ? 1 : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendItem.id,
|
||||||
|
type,
|
||||||
|
username: parsedContent?.sender?.username || backendItem.sender || '未知用户',
|
||||||
|
avatar: '/images/activity/5.png', // 使用默认头像
|
||||||
|
courseInfo: parsedContent?.entity?.title || '未知课程',
|
||||||
|
content: action === 'like' ? '赞了我的评论' : '收藏了我的课程',
|
||||||
|
timestamp: backendItem.sendTime,
|
||||||
|
courseImage: action === 'favorite' ? '/images/courses/course-bg.png' : undefined,
|
||||||
|
isLiked: false,
|
||||||
|
isFavorited: false,
|
||||||
|
showReplyBox: false,
|
||||||
|
replyContent: '',
|
||||||
|
readFlag: backendItem.readFlag,
|
||||||
|
action
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const messages = ref<Message[]>([
|
const messages = ref<Message[]>([])
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
type: 0, // 点赞评论 - 有评论内容,无图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '这里老师营养饮用的说明了',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
type: 1, // 收藏课程 - 无评论内容,有图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
courseImage: 'https://picsum.photos/300/200',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
type: 0, // 点赞评论 - 有评论内容,无图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '这里老师营养饮用的说明了',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
type: 1, // 收藏课程 - 无评论内容,有图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
courseImage: 'https://picsum.photos/300/200',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
type: 1, // 收藏课程 - 无评论内容,有图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
courseImage: 'https://picsum.photos/300/200',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
type: 0, // 点赞评论 - 有评论内容,无图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '这里老师营养饮用的说明了',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
type: 0, // 点赞评论 - 有评论内容,无图片
|
|
||||||
username: '王建华化学老师',
|
|
||||||
avatar: 'https://picsum.photos/200/200',
|
|
||||||
courseInfo: '《教师小学期制实验》',
|
|
||||||
content: '这里老师营养饮用的说明了',
|
|
||||||
timestamp: '7月20日\n12:41',
|
|
||||||
isLiked: false,
|
|
||||||
isFavorited: false,
|
|
||||||
showReplyBox: false,
|
|
||||||
replyContent: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 分页相关
|
// 分页相关
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const totalPages = ref(29)
|
const totalPages = ref(1)
|
||||||
|
const total = ref(0)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
// 计算显示的页码
|
// 计算显示的页码
|
||||||
const visiblePages = computed(() => {
|
const visiblePages = computed(() => {
|
||||||
@ -201,14 +139,83 @@ const showEllipsis = computed(() => {
|
|||||||
return currentPage.value + 2 < totalPages.value - 1
|
return currentPage.value + 2 < totalPages.value - 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 是否显示最后一页按钮
|
||||||
|
const showLastPage = computed(() => {
|
||||||
|
return totalPages.value > 1 && currentPage.value + 2 < totalPages.value
|
||||||
|
})
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMessages()
|
loadMessages()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 方法定义
|
// 方法定义
|
||||||
const loadMessages = () => {
|
const loadMessages = async () => {
|
||||||
// TODO: 调用API加载消息数据
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await MessageApi.getLikesMessages({
|
||||||
|
current: currentPage.value,
|
||||||
|
size: pageSize.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data?.success && (response.data.code === 200 || response.data.code === 0)) {
|
||||||
|
const data = response.data.result
|
||||||
|
// 转换后端数据为前端格式
|
||||||
|
messages.value = (data.records || []).map(transformMessageData)
|
||||||
|
totalPages.value = data.pages || 1
|
||||||
|
total.value = data.total || 0
|
||||||
|
} else {
|
||||||
|
// 静默处理响应失败,不显示错误消息
|
||||||
|
messages.value = []
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('加载消息失败:', error)
|
||||||
|
// 静默处理错误,不显示错误消息给用户
|
||||||
|
// 如果是404错误,说明接口不存在,使用模拟数据
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
// 使用模拟数据
|
||||||
|
messages.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 0,
|
||||||
|
username: '王建华化学老师',
|
||||||
|
avatar: '/images/activity/5.png',
|
||||||
|
courseInfo: '《教师小学期制实验》',
|
||||||
|
content: '这里老师营养饮用的说明了',
|
||||||
|
timestamp: '7月20日\n12:41',
|
||||||
|
isLiked: false,
|
||||||
|
isFavorited: false,
|
||||||
|
showReplyBox: false,
|
||||||
|
replyContent: '',
|
||||||
|
readFlag: 0,
|
||||||
|
action: 'like'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 1,
|
||||||
|
username: '王建华化学老师',
|
||||||
|
avatar: '/images/activity/5.png',
|
||||||
|
courseInfo: '《教师小学期制实验》',
|
||||||
|
content: '',
|
||||||
|
timestamp: '7月20日\n12:41',
|
||||||
|
courseImage: '/images/courses/course-bg.png',
|
||||||
|
isLiked: false,
|
||||||
|
isFavorited: false,
|
||||||
|
showReplyBox: false,
|
||||||
|
replyContent: '',
|
||||||
|
readFlag: 0,
|
||||||
|
action: 'favorite'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
totalPages.value = 1
|
||||||
|
total.value = 2
|
||||||
|
} else {
|
||||||
|
// 静默处理其他错误,不显示错误消息
|
||||||
|
messages.value = []
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToPage = (page: number) => {
|
const goToPage = (page: number) => {
|
||||||
@ -224,6 +231,30 @@ const goToPage = (page: number) => {
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.message-list {
|
.message-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@ -451,21 +482,21 @@ const goToPage = (page: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-btn:hover:not(:disabled) {
|
.page-btn:hover:not(:disabled) {
|
||||||
border-color: #1890ff;
|
border-color: #0089D1;
|
||||||
color: #1890ff;
|
color: #0089D1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-btn.active {
|
.page-btn.active {
|
||||||
background: #1890ff;
|
background: #0089D1;
|
||||||
border-color: #1890ff;
|
border-color: #0089D1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-btn:disabled {
|
.page-btn:disabled {
|
||||||
color: #bfbfbf;
|
color: #878787;
|
||||||
border-color: #f0f0f0;
|
border: none;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background: #fafafa;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
|
@ -66,72 +66,17 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { NIcon } from 'naive-ui'
|
import { NIcon } from 'naive-ui'
|
||||||
import { NotificationsOffOutline } from '@vicons/ionicons5'
|
import { NotificationsOffOutline } from '@vicons/ionicons5'
|
||||||
|
import { MessageApi, type SystemMessage, type BackendMessageItem } from '@/api'
|
||||||
// 系统消息类型定义
|
|
||||||
interface SystemMessage {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
content: string
|
|
||||||
timestamp: string
|
|
||||||
isRead: boolean
|
|
||||||
type: 'info' | 'warning' | 'success' | 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const messages = ref<SystemMessage[]>([
|
const messages = ref<SystemMessage[]>([])
|
||||||
{
|
const loading = ref(false)
|
||||||
id: 1,
|
const total = ref(0)
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
const pageSize = ref(10)
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
|
||||||
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
|
||||||
timestamp: '7月20日 12:41',
|
|
||||||
isRead: false,
|
|
||||||
type: 'info'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 分页相关
|
// 分页相关
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const totalPages = ref(29)
|
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
|
||||||
|
|
||||||
// 计算显示的页码
|
// 计算显示的页码
|
||||||
const visiblePages = computed(() => {
|
const visiblePages = computed(() => {
|
||||||
@ -150,14 +95,117 @@ const showEllipsis = computed(() => {
|
|||||||
return currentPage.value + 2 < totalPages.value - 1
|
return currentPage.value + 2 < totalPages.value - 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 数据转换函数
|
||||||
|
const transformSystemMessageData = (backendItem: BackendMessageItem): SystemMessage => {
|
||||||
|
let title = backendItem.titile || '系统消息'
|
||||||
|
let content = '暂无内容'
|
||||||
|
|
||||||
|
// 解析 msgContent 中的JSON数据
|
||||||
|
try {
|
||||||
|
if (backendItem.msgContent) {
|
||||||
|
const parsedContent = JSON.parse(backendItem.msgContent)
|
||||||
|
console.log('🔍 解析系统消息内容:', parsedContent)
|
||||||
|
|
||||||
|
// 根据消息类型生成标题和内容
|
||||||
|
if (parsedContent.sender && parsedContent.comment) {
|
||||||
|
// 评论消息
|
||||||
|
title = `${parsedContent.sender.username} 评论了你的课程`
|
||||||
|
content = parsedContent.comment.content || '暂无评论内容'
|
||||||
|
|
||||||
|
// 处理 @ 提及
|
||||||
|
content = processAtMentions(content)
|
||||||
|
} else if (parsedContent.sender && parsedContent.entity) {
|
||||||
|
// 其他类型的消息
|
||||||
|
title = `${parsedContent.sender.username} 的${parsedContent.entity.type}消息`
|
||||||
|
content = parsedContent.entity.title || '暂无内容'
|
||||||
|
} else {
|
||||||
|
// 直接使用原始内容
|
||||||
|
content = backendItem.msgContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ 解析系统消息内容失败:', error)
|
||||||
|
// 如果解析失败,直接使用原始内容
|
||||||
|
content = backendItem.msgContent || '暂无内容'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: backendItem.id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
timestamp: backendItem.sendTime || new Date().toLocaleString('zh-CN'),
|
||||||
|
isRead: backendItem.readFlag === 1,
|
||||||
|
type: 'info' // 默认类型,可以根据实际需求调整
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 @ 提及的函数
|
||||||
|
const processAtMentions = (content: string): string => {
|
||||||
|
if (!content) return content
|
||||||
|
|
||||||
|
// 将 [user:id:username] 格式转换为 @username
|
||||||
|
return content.replace(/\[user:(\d+):([^\]]+)\]/g, '@$2')
|
||||||
|
}
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMessages()
|
loadMessages()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 方法定义
|
// 方法定义
|
||||||
const loadMessages = () => {
|
const loadMessages = async () => {
|
||||||
// TODO: 调用API加载系统消息数据
|
try {
|
||||||
|
loading.value = true
|
||||||
|
console.log('🔍 开始加载系统消息,页码:', currentPage.value)
|
||||||
|
|
||||||
|
const response = await MessageApi.getSystemMessages({
|
||||||
|
pageNo: currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('🔍 系统消息API响应:', response)
|
||||||
|
|
||||||
|
if (response.data && response.data.records) {
|
||||||
|
const result = response.data
|
||||||
|
console.log('✅ 系统消息数据:', result)
|
||||||
|
|
||||||
|
// 转换数据格式
|
||||||
|
messages.value = result.records.map(transformSystemMessageData)
|
||||||
|
total.value = result.total || 0
|
||||||
|
|
||||||
|
console.log('✅ 转换后的系统消息:', messages.value)
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ 系统消息API返回错误:', response)
|
||||||
|
// 使用模拟数据作为备选
|
||||||
|
messages.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
||||||
|
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
||||||
|
timestamp: '7月20日 12:41',
|
||||||
|
isRead: false,
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
total.value = 1
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 加载系统消息失败:', error)
|
||||||
|
// 使用模拟数据作为备选
|
||||||
|
messages.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: '你的会员已专属权利已更新,保来享取今日内障,仅此一天!',
|
||||||
|
content: '好消息好消息!BiliBiliWorld2024年在线大学6月29日(周六)正式开课!!!',
|
||||||
|
timestamp: '7月20日 12:41',
|
||||||
|
isRead: false,
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
total.value = 1
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToPage = (page: number) => {
|
const goToPage = (page: number) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user