feat: 完善班级管理,消息中心对接接口,添加样式,修复打包报错

This commit is contained in:
QDKF 2025-09-23 17:24:16 +08:00
parent 254fb72d0d
commit 5c14a13f5e
14 changed files with 1434 additions and 367 deletions

View File

@ -77,7 +77,7 @@ export class AuthApi {
message: actualMessage || '登录成功', message: actualMessage || '登录成功',
data: { data: {
user: { user: {
id: 1, // 真实API没有返回用户ID使用默认值 id: "1", // 真实API没有返回用户ID使用默认值
email: data.email || '', email: data.email || '',
phone: data.phone || '', phone: data.phone || '',
username: data.email || data.phone || '', username: data.email || data.phone || '',
@ -129,7 +129,7 @@ export class AuthApi {
message: actualMessage || '登录成功', message: actualMessage || '登录成功',
data: { data: {
user: { user: {
id: 1, id: "1",
email: data.email || '', email: data.email || '',
phone: data.phone || '', phone: data.phone || '',
username: data.phone || data.email?.split('@')[0] || 'user', username: data.phone || data.email?.split('@')[0] || 'user',
@ -232,7 +232,7 @@ export class AuthApi {
code: 200, code: 200,
message: actualMessage || '注册成功', message: actualMessage || '注册成功',
data: { data: {
id: Date.now(), // 临时ID id: Date.now().toString(), // 临时ID
username: registerData.studentNumber, username: registerData.studentNumber,
email: data.email || '', email: data.email || '',
phone: data.phone || '', phone: data.phone || '',
@ -339,7 +339,7 @@ export class AuthApi {
} }
const convertedUser = { const convertedUser = {
id: parseInt(baseInfo.id) || 0, id: baseInfo.id, // 直接使用字符串ID避免parseInt精度问题
username: baseInfo.username, username: baseInfo.username,
email: baseInfo.email, email: baseInfo.email,
phone: baseInfo.phone, phone: baseInfo.phone,

View File

@ -33,7 +33,7 @@ export interface ChatMessage {
senderName: string senderName: string
senderAvatar?: string senderAvatar?: string
content: string content: string
messageType: 'text' | 'image' | 'file' | 'system' messageType: number // 0=文本, 1=图片, 2=文件, 3=系统消息
timestamp: string timestamp: string
isRead: boolean isRead: boolean
replyTo?: string replyTo?: string
@ -42,6 +42,8 @@ export interface ChatMessage {
fileUrl?: string // 文件URL fileUrl?: string // 文件URL
fileSize?: number // 文件大小 fileSize?: number // 文件大小
fileType?: string // 文件类型 fileType?: string // 文件类型
fileName?: string // 文件名
createTime?: string // 创建时间
} }
// 群聊成员接口类型定义 // 群聊成员接口类型定义
@ -56,11 +58,13 @@ export interface ChatMember {
username: string username: string
// 可选字段 // 可选字段
chatId?: string chatId?: string
role?: 'admin' | 'member' role: 0 | 1 | 2 // 0=群主, 1=管理员, 2=成员
joinTime?: string joinTime?: string
isOnline?: boolean isOnline?: boolean
status?: number // 成员状态 status?: number // 成员状态
lastActiveTime?: string // 最后活跃时间 lastActiveTime?: string // 最后活跃时间
izMuted: 0 | 1 // 是否被禁言: 0=未禁言, 1=已禁言
izNotDisturb?: 0 | 1 // 是否免打扰: 0=未设置, 1=已设置
} }
// 我的会话列表响应类型 // 我的会话列表响应类型
@ -142,19 +146,32 @@ export const ChatApi = {
/** /**
* *
* POST /aiol/aiolChat/send * POST /aiol/aiolChatMessage/send
* *
*/ */
sendMessage: (data: { sendMessage: (data: {
chatId: string chat_id: string
content: string content: string
messageType: 'text' | 'image' | 'file' messageType: number // 0=文本1=图片2=文件
replyTo?: string replyTo?: string
fileUrl?: string fileUrl?: string
fileSize?: number fileSize?: number
fileType?: string fileType?: string
fileName?: string
}): Promise<ApiResponse<any>> => { }): Promise<ApiResponse<any>> => {
return ApiRequest.post('/aiol/aiolChat/send', data) // 转换参数名以匹配服务器期望的格式
const serverData = {
chat_id: data.chat_id,
content: data.content,
message_type: data.messageType, // 服务器可能期望 message_type
replyTo: data.replyTo,
fileUrl: data.fileUrl,
fileSize: data.fileSize,
fileType: data.fileType,
fileName: data.fileName
}
console.log('🔄 转换后的服务器数据:', serverData)
return ApiRequest.post('/aiol/aiolChatMessage/send', serverData)
}, },
@ -233,5 +250,46 @@ export const ChatApi = {
*/ */
unmuteMember: (chatId: string, userId: string): Promise<ApiResponse<any>> => { unmuteMember: (chatId: string, userId: string): Promise<ApiResponse<any>> => {
return ApiRequest.post(`/aiol/aiolChat/${chatId}/unmute_member/${userId}`) return ApiRequest.post(`/aiol/aiolChat/${chatId}/unmute_member/${userId}`)
},
/**
*
* POST /aiol/aiolChat/{chatId}/enable_not_disturb/{userId}
*/
enableNotDisturb: (chatId: string, userId: string): Promise<ApiResponse<any>> => {
return ApiRequest.post(`/aiol/aiolChat/${chatId}/enable_not_disturb/${userId}`)
},
/**
*
* POST /aiol/aiolChat/{chatId}/disable_not_disturb/{userId}
*/
disableNotDisturb: (chatId: string, userId: string): Promise<ApiResponse<any>> => {
return ApiRequest.post(`/aiol/aiolChat/${chatId}/disable_not_disturb/${userId}`)
},
/**
* ID
* POST /aiol/aiolChat/{chatId}/update_last_read/{messageId}
* ID
*/
updateLastRead: (chatId: string, messageId: string): Promise<ApiResponse<any>> => {
return ApiRequest.post(`/aiol/aiolChat/${chatId}/update_last_read/${messageId}`)
},
/**
*
* POST /sys/common/upload
* URL
*/
uploadFile: (file: File): Promise<ApiResponse<{ success: boolean; message: string; result?: { url: string }; data?: { url: string } }>> => {
const formData = new FormData()
formData.append('file', file)
return ApiRequest.post('/sys/common/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
} }
} }

View File

@ -29,7 +29,7 @@ export interface PaginationResponse<T> {
// 用户相关类型 // 用户相关类型
export interface User { export interface User {
id: number id: string // 改为字符串类型,避免大整数精度问题
username: string username: string
email: string email: string
phone?: string phone?: string
@ -218,12 +218,12 @@ export interface CourseListRequest {
} }
export interface CourseCategory { export interface CourseCategory {
id: number id: string
name: string name: string
slug: string slug: string
description?: string description?: string
icon?: string icon?: string
parentId?: number parentId?: string
children?: CourseCategory[] children?: CourseCategory[]
} }
@ -359,7 +359,7 @@ export interface BackendCourseDetailResponse {
} }
export interface Instructor { export interface Instructor {
id: number id: string
name: string name: string
title: string title: string
bio: string bio: string

View File

@ -288,6 +288,57 @@
:radio-options="importRadioOptions" radio-field="updateMode" import-type="student" :radio-options="importRadioOptions" radio-field="updateMode" import-type="student"
template-name="student_import_template.xlsx" @success="handleImportSuccess" template-name="student_import_template.xlsx" @success="handleImportSuccess"
@template-download="handleTemplateDownload" /> @template-download="handleTemplateDownload" />
<!-- 学员库选择弹窗 -->
<n-modal v-model:show="showStudentLibraryModal" title="从学员库添加学员" :mask-closable="false">
<n-card style="width: 900px; max-height: 85vh" title="从学员库添加学员" :bordered="false" size="huge" role="dialog"
aria-modal="true">
<!-- 搜索区域 -->
<div class="search-section">
<n-input v-model:value="librarySearchKeyword" placeholder="请输入姓名/账号" style="width: 300px" clearable
@keyup.enter="handleLibrarySearch">
<template #prefix>
<n-icon>
<SearchOutline />
</n-icon>
</template>
</n-input>
<n-button type="primary" @click="handleLibrarySearch" style="margin-left: 12px">
搜索
</n-button>
<n-button @click="clearLibrarySearch" style="margin-left: 8px">
清空
</n-button>
</div>
<n-divider />
<!-- 学员列表 -->
<div class="student-list-section">
<div class="list-header">
<span class="total-count">共找到 {{ filteredLibraryStudents.length }} 名学员</span>
<span class="selected-count" v-if="selectedLibraryStudents.length > 0">
已选择 {{ selectedLibraryStudents.length }}
</span>
</div>
<n-data-table :columns="libraryColumns" :data="filteredLibraryStudents" :loading="libraryLoading"
:row-key="(row: LibraryStudentItem) => row.id"
v-model:checked-row-keys="selectedLibraryStudents" :pagination="libraryPagination" striped
size="small" :max-height="400" />
</div>
<template #footer>
<div class="modal-footer">
<n-button @click="closeStudentLibraryModal">取消</n-button>
<n-button type="primary" @click="handleConfirmLibrarySelection"
:disabled="selectedLibraryStudents.length === 0">
确认添加 ({{ selectedLibraryStudents.length }})
</n-button>
</div>
</template>
</n-card>
</n-modal>
</div> </div>
</template> </template>
@ -295,7 +346,7 @@
import { ref, onMounted, h, computed, watch } from 'vue' import { ref, onMounted, h, computed, watch } from 'vue'
import TeachCourseApi, { ClassApi } from '@/api/modules/teachCourse' import TeachCourseApi, { ClassApi } from '@/api/modules/teachCourse'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { AddCircleOutline, SettingsOutline, QrCode } from '@vicons/ionicons5' import { AddCircleOutline, SettingsOutline, QrCode, SearchOutline } from '@vicons/ionicons5'
import { import {
NDataTable, NDataTable,
NButton, NButton,
@ -359,6 +410,16 @@ interface ClassItem {
createTime: string createTime: string
} }
//
interface LibraryStudentItem {
id: string
realName: string
studentNumber: string
className: string
school: string
createTime: string
}
// //
interface FormData { interface FormData {
studentName: string studentName: string
@ -386,8 +447,15 @@ const showTransferModal = ref(false)
const showAddClassModal = ref(false) const showAddClassModal = ref(false)
const showManageClassModal = ref(false) const showManageClassModal = ref(false)
const showImportModal = ref(false) const showImportModal = ref(false)
const showStudentLibraryModal = ref(false)
const selectedTargetClass = ref('') const selectedTargetClass = ref('')
const currentTransferStudent = ref<StudentItem | null>(null) const currentTransferStudent = ref<StudentItem | null>(null)
//
const librarySearchKeyword = ref('')
const libraryStudents = ref<LibraryStudentItem[]>([])
const selectedLibraryStudents = ref<string[]>([])
const libraryLoading = ref(false)
const formRef = ref<FormInst | null>(null) const formRef = ref<FormInst | null>(null)
const classFormRef = ref<FormInst | null>(null) const classFormRef = ref<FormInst | null>(null)
const isEditMode = ref(false) const isEditMode = ref(false)
@ -525,6 +593,22 @@ const classSelectOptions = computed(() =>
// 使 // 使
const classList = computed(() => masterClassList.value) const classList = computed(() => masterClassList.value)
//
const filteredLibraryStudents = computed(() => {
if (!librarySearchKeyword.value.trim()) {
return libraryStudents.value
}
const keyword = librarySearchKeyword.value.trim().toLowerCase()
return libraryStudents.value.filter(student =>
student.realName.toLowerCase().includes(keyword) ||
student.studentNumber.toLowerCase().includes(keyword) ||
student.school.toLowerCase().includes(keyword) ||
student.className.toLowerCase().includes(keyword)
)
})
// //
const columns: DataTableColumns<StudentItem> = [ const columns: DataTableColumns<StudentItem> = [
{ {
@ -664,6 +748,56 @@ const columns: DataTableColumns<StudentItem> = [
} }
] ]
//
const libraryColumns: DataTableColumns<LibraryStudentItem> = [
{
type: 'selection'
},
{
title: '姓名',
key: 'realName',
width: 120,
align: 'center'
},
{
title: '账号',
key: 'studentNumber',
width: 140,
align: 'center'
},
{
title: '班级',
key: 'className',
width: 150,
align: 'center',
render: (row: LibraryStudentItem) => {
// 使
const classNames = formatClassNames(row.className || '')
//
return h('div', {
class: 'class-cell'
}, classNames.map((name, index) =>
h('div', {
key: index,
class: 'class-cell-item'
}, name)
))
}
},
{
title: '所在学院',
key: 'school',
width: 200,
align: 'center'
},
{
title: '加入时间',
key: 'createTime',
width: 140,
align: 'center'
}
]
// //
const data = ref<StudentItem[]>([]) const data = ref<StudentItem[]>([])
const loading = ref(false) const loading = ref(false)
@ -686,6 +820,22 @@ const pagination = ref({
} }
}) })
//
const libraryPagination = ref({
page: 1,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 20, 50],
itemCount: computed(() => filteredLibraryStudents.value.length),
onChange: (page: number) => {
libraryPagination.value.page = page
},
onUpdatePageSize: (pageSize: number) => {
libraryPagination.value.pageSize = pageSize
libraryPagination.value.page = 1
}
})
// //
const handleTransfer = (row: StudentItem) => { const handleTransfer = (row: StudentItem) => {
currentTransferStudent.value = row currentTransferStudent.value = row
@ -871,7 +1021,34 @@ const confirmBatchTransfer = async () => {
} }
const handleDelete = (row: StudentItem) => { const handleDelete = (row: StudentItem) => {
message.warning(`移除学员:${row.studentName}`) dialog.warning({
title: '确认移除',
content: `确定要移除学员"${row.studentName}"吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
const currentClassId = props.classId || selectedDepartment.value
if (!currentClassId) {
message.error('班级ID为空无法移除学员')
return
}
const response = await ClassApi.removeStudent(String(currentClassId), row.id)
if (response.data && (response.data.success || response.data.code === 200)) {
message.success(`已移除学员"${row.studentName}"`)
//
loadData(currentClassId)
} else {
message.error(response.data?.message || '移除失败')
}
} catch (error) {
console.error('移除学员失败:', error)
message.error('移除学员失败,请重试')
}
}
})
} }
// student // student
@ -1117,9 +1294,8 @@ const handleAddStudentSelect = (key: string) => {
// //
openAddModal() openAddModal()
} else if (key === 'library') { } else if (key === 'library') {
// //
message.info('学员库添加功能待开发,敬请期待') openStudentLibraryModal()
console.log('学员库添加功能将在后续版本中实现')
} }
} }
@ -1278,7 +1454,7 @@ const loadData = async (classId?: string | number | null) => {
// API // API
const studentsData = response.data.result || [] const studentsData = response.data.result || []
const transformedData: StudentItem[] = studentsData.map((student: any) => ({ const transformedData: StudentItem[] = studentsData.map((student: any) => ({
id: student.id || '', id: student.studentId || student.id || '', // 使studentIdID
studentName: student.realname || student.username || '未知姓名', studentName: student.realname || student.username || '未知姓名',
accountNumber: student.studentId || student.username || '', accountNumber: student.studentId || student.username || '',
className: student.classId || '未分配班级', // ID className: student.classId || '未分配班级', // ID
@ -1342,6 +1518,158 @@ const clearSearch = () => {
pagination.value.page = 1 pagination.value.page = 1
} }
//
const openStudentLibraryModal = async () => {
showStudentLibraryModal.value = true
selectedLibraryStudents.value = []
librarySearchKeyword.value = ''
await loadLibraryStudents()
}
const closeStudentLibraryModal = () => {
showStudentLibraryModal.value = false
selectedLibraryStudents.value = []
librarySearchKeyword.value = ''
libraryStudents.value = []
}
const loadLibraryStudents = async () => {
libraryLoading.value = true
try {
console.log('🚀 开始加载学员库数据...')
//
await loadClassList()
if (masterClassList.value.length === 0) {
libraryStudents.value = []
return
}
//
const allStudents: LibraryStudentItem[] = []
for (const classItem of masterClassList.value) {
try {
const response = await ClassApi.getClassStudents(classItem.id)
const studentsData = response.data.result || []
const transformedStudents: LibraryStudentItem[] = studentsData.map((student: any) => ({
id: student.id || student.studentId || '',
realName: student.realname || student.username || '未知姓名',
studentNumber: student.studentId || student.username || '',
className: classItem.id, // 使ID
school: student.college || student.department || '未分配学院',
createTime: student.createTime ? new Date(student.createTime).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).replace(/\//g, '.').replace(',', '') : '未知时间'
}))
allStudents.push(...transformedStudents)
} catch (error) {
console.error(`❌ 加载班级 ${classItem.className} 的学员数据失败:`, error)
}
}
libraryStudents.value = allStudents
console.log(`✅ 成功加载学员库数据,共 ${allStudents.length} 名学员`)
} catch (error) {
console.error('❌ 加载学员库数据失败:', error)
message.error('加载学员库数据失败,请重试')
libraryStudents.value = []
} finally {
libraryLoading.value = false
}
}
const handleLibrarySearch = () => {
libraryPagination.value.page = 1
}
const clearLibrarySearch = () => {
librarySearchKeyword.value = ''
libraryPagination.value.page = 1
}
const handleConfirmLibrarySelection = async () => {
if (selectedLibraryStudents.value.length === 0) {
message.warning('请先选择要添加的学员')
return
}
try {
const currentClassId = props.classId || selectedDepartment.value
if (!currentClassId) {
message.error('班级ID为空无法添加学员')
return
}
//
const selectedStudents = libraryStudents.value.filter(student =>
selectedLibraryStudents.value.includes(student.id)
)
console.log('🚀 开始添加学员到班级:', {
目标班级ID: currentClassId,
选中学员数量: selectedStudents.length,
选中学员: selectedStudents.map(s => ({ id: s.id, name: s.realName, studentNumber: s.studentNumber }))
})
//
let successCount = 0
let failCount = 0
for (const student of selectedStudents) {
try {
// API
const payload = {
realName: student.realName,
studentNumber: student.studentNumber,
password: '123456', //
school: student.school,
classId: String(currentClassId)
}
console.log('📝 添加学员API请求参数:', payload)
// API
const response = await ClassApi.createdStudents(payload)
if (response.data && (response.data.success || response.data.code === 200)) {
successCount++
console.log(`✅ 成功添加学员: ${student.realName}`)
} else {
failCount++
console.error(`❌ 添加学员失败: ${student.realName}`, response.data?.message)
}
} catch (error) {
failCount++
console.error(`❌ 添加学员异常: ${student.realName}`, error)
}
}
if (successCount > 0) {
message.success(`成功添加 ${successCount} 名学员到班级${failCount > 0 ? `${failCount} 名添加失败` : ''}`)
} else {
message.error('添加学员失败,请重试')
return
}
//
closeStudentLibraryModal()
//
loadData(currentClassId)
} catch (error) {
console.error('添加学员失败:', error)
message.error('添加学员失败,请重试')
}
}
// ID // ID
watch( watch(
() => props.classId, () => props.classId,
@ -1759,4 +2087,35 @@ defineExpose({
.custom-empty .n-empty { .custom-empty .n-empty {
margin: 0; margin: 0;
} }
/* 学员库弹窗样式 */
.search-section {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 0;
}
.student-list-section {
margin-top: 16px;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 0 4px;
}
.total-count {
font-size: 14px;
color: #666;
}
.selected-count {
font-size: 14px;
color: #1890ff;
font-weight: 500;
}
</style> </style>

View File

@ -0,0 +1 @@

View File

@ -1210,7 +1210,7 @@ const loadCourseDetail = async () => {
if (course.value) { if (course.value) {
if (!course.value.instructor?.name) { if (!course.value.instructor?.name) {
course.value.instructor = { course.value.instructor = {
id: 1, id: "1",
name: 'DeepSeek技术学院', name: 'DeepSeek技术学院',
title: '讲师', title: '讲师',
bio: '', bio: '',
@ -1292,7 +1292,7 @@ const loadMockCourseData = () => {
title: 'DeepSeek办公自动化职业岗位标准课程', title: 'DeepSeek办公自动化职业岗位标准课程',
description: '本课程将帮助您掌握DeepSeek的基本使用方法了解办公自动化职业岗位标准提高教学质量和效率获得实际工作技能。', description: '本课程将帮助您掌握DeepSeek的基本使用方法了解办公自动化职业岗位标准提高教学质量和效率获得实际工作技能。',
instructor: { instructor: {
id: 1, id: "1",
name: 'DeepSeek技术学院', name: 'DeepSeek技术学院',
title: '讲师', title: '讲师',
bio: '专注于AI技术应用与教学', bio: '专注于AI技术应用与教学',
@ -1311,7 +1311,7 @@ const loadMockCourseData = () => {
price: 0, price: 0,
originalPrice: 299, originalPrice: 299,
category: { category: {
id: 1, id: "1",
name: 'AI技术', name: 'AI技术',
slug: 'ai-technology', slug: 'ai-technology',
description: 'AI技术', description: 'AI技术',
@ -1679,7 +1679,7 @@ const initializeMockState = () => {
// //
if (!userStore.isLoggedIn) { if (!userStore.isLoggedIn) {
userStore.user = { userStore.user = {
id: 1, id: "1",
username: 'testuser', username: 'testuser',
email: 'test@example.com', email: 'test@example.com',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80', avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',

View File

@ -793,19 +793,19 @@ const activeTab = ref('intro')
// //
const instructors = ref([ const instructors = ref([
{ {
id: 1, id: "1",
name: '汪波', name: '汪波',
title: '教授', title: '教授',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80' avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80'
}, },
{ {
id: 2, id: "2",
name: '汪波', name: '汪波',
title: '教授', title: '教授',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80' avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80'
}, },
{ {
id: 3, id: "3",
name: '汪波', name: '汪波',
title: '教授', title: '教授',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80' avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80'
@ -869,7 +869,7 @@ const loadCourseDetail = async () => {
if (course.value) { if (course.value) {
if (!course.value.instructor?.name) { if (!course.value.instructor?.name) {
course.value.instructor = { course.value.instructor = {
id: 1, id: "1",
name: 'DeepSeek技术学院', name: 'DeepSeek技术学院',
title: '讲师', title: '讲师',
bio: '', bio: '',
@ -1408,7 +1408,7 @@ const initializeMockState = () => {
// //
if (!userStore.isLoggedIn) { if (!userStore.isLoggedIn) {
userStore.user = { userStore.user = {
id: 1, id: "1",
username: 'testuser', username: 'testuser',
email: 'test@example.com', email: 'test@example.com',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80', avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',

View File

@ -1247,7 +1247,7 @@ const initializeEnrolledState = () => {
// //
if (!userStore.isLoggedIn) { if (!userStore.isLoggedIn) {
userStore.user = { userStore.user = {
id: 1, id: "1",
username: 'testuser', username: 'testuser',
email: 'test@example.com', email: 'test@example.com',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80', avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',

View File

@ -2077,7 +2077,7 @@ const loadCourseDetail = async () => {
if (course.value) { if (course.value) {
if (!course.value.instructor?.name) { if (!course.value.instructor?.name) {
course.value.instructor = { course.value.instructor = {
id: 1, id: "1",
name: 'DeepSeek技术学院', name: 'DeepSeek技术学院',
title: '讲师', title: '讲师',
bio: '', bio: '',

View File

@ -593,13 +593,13 @@ const setDefaultCourseInfo = () => {
description: '<p>本课程将带你从零开始学习C语言程序设计掌握编程基础知识。</p>', description: '<p>本课程将带你从零开始学习C语言程序设计掌握编程基础知识。</p>',
content: '详细的C语言课程内容', content: '详细的C语言课程内容',
category: { category: {
id: 3, id: "3",
name: '信息技术', name: '信息技术',
slug: 'it', slug: 'it',
description: '信息技术相关课程' description: '信息技术相关课程'
}, },
instructor: { instructor: {
id: 4, id: "4",
name: '教师', name: '教师',
title: 'C语言专家', title: 'C语言专家',
bio: '资深C语言开发工程师', bio: '资深C语言开发工程师',

View File

@ -285,7 +285,7 @@ const handleLogin = async () => {
// //
console.warn('⚠️ 获取用户信息失败,使用基本信息') console.warn('⚠️ 获取用户信息失败,使用基本信息')
const basicUser = { const basicUser = {
id: 1, id: "1",
email: isPhone ? '' : formData.studentId, email: isPhone ? '' : formData.studentId,
phone: isPhone ? formData.studentId : '', phone: isPhone ? formData.studentId : '',
username: formData.studentId, username: formData.studentId,
@ -303,7 +303,7 @@ const handleLogin = async () => {
// //
console.warn('⚠️ 获取用户信息异常,使用基本信息:', userInfoError) console.warn('⚠️ 获取用户信息异常,使用基本信息:', userInfoError)
const basicUser = { const basicUser = {
id: 1, id: "1",
email: isPhone ? '' : formData.studentId, email: isPhone ? '' : formData.studentId,
phone: isPhone ? formData.studentId : '', phone: isPhone ? formData.studentId : '',
username: formData.studentId, username: formData.studentId,

View File

@ -219,13 +219,13 @@ const loadCourses = async () => {
totalLessons: 54, totalLessons: 54,
level: 'beginner', level: 'beginner',
language: 'zh-CN', language: 'zh-CN',
category: { id: 1, name: '教育培训', slug: 'education-training' }, category: { id: "1", name: '教育培训', slug: 'education-training' },
tags: ['心理学', '教育', '基础'], tags: ['心理学', '教育', '基础'],
skills: ['心理分析', '教育理论'], skills: ['心理分析', '教育理论'],
requirements: ['无特殊要求'], requirements: ['无特殊要求'],
objectives: ['掌握教育心理学基础理论'], objectives: ['掌握教育心理学基础理论'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',
@ -255,13 +255,13 @@ const loadCourses = async () => {
totalLessons: 42, totalLessons: 42,
level: 'intermediate', level: 'intermediate',
language: 'zh-CN', language: 'zh-CN',
category: { id: 2, name: '技术应用', slug: 'tech-application' }, category: { id: "2", name: '技术应用', slug: 'tech-application' },
tags: ['教育技术', '多媒体', '在线教育'], tags: ['教育技术', '多媒体', '在线教育'],
skills: ['多媒体制作', '在线教学'], skills: ['多媒体制作', '在线教学'],
requirements: ['基础计算机操作'], requirements: ['基础计算机操作'],
objectives: ['掌握现代教育技术应用'], objectives: ['掌握现代教育技术应用'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',
@ -291,13 +291,13 @@ const loadCourses = async () => {
totalLessons: 68, totalLessons: 68,
level: 'advanced', level: 'advanced',
language: 'zh-CN', language: 'zh-CN',
category: { id: 3, name: '课程设计', slug: 'course-design' }, category: { id: "3", name: '课程设计', slug: 'course-design' },
tags: ['课程设计', '教学开发', '教育'], tags: ['课程设计', '教学开发', '教育'],
skills: ['课程规划', '教学设计'], skills: ['课程规划', '教学设计'],
requirements: ['教育理论基础'], requirements: ['教育理论基础'],
objectives: ['掌握课程设计方法'], objectives: ['掌握课程设计方法'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',
@ -327,13 +327,13 @@ const loadCourses = async () => {
totalLessons: 36, totalLessons: 36,
level: 'intermediate', level: 'intermediate',
language: 'zh-CN', language: 'zh-CN',
category: { id: 4, name: '研究方法', slug: 'research-methods' }, category: { id: "4", name: '研究方法', slug: 'research-methods' },
tags: ['研究方法', '教育', '学术'], tags: ['研究方法', '教育', '学术'],
skills: ['研究设计', '数据分析'], skills: ['研究设计', '数据分析'],
requirements: ['统计学基础'], requirements: ['统计学基础'],
objectives: ['掌握教育研究方法'], objectives: ['掌握教育研究方法'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',
@ -363,13 +363,13 @@ const loadCourses = async () => {
totalLessons: 28, totalLessons: 28,
level: 'beginner', level: 'beginner',
language: 'zh-CN', language: 'zh-CN',
category: { id: 5, name: '心理辅导', slug: 'psychological-counseling' }, category: { id: "5", name: '心理辅导', slug: 'psychological-counseling' },
tags: ['心理辅导', '学生', '技巧'], tags: ['心理辅导', '学生', '技巧'],
skills: ['心理咨询', '沟通技巧'], skills: ['心理咨询', '沟通技巧'],
requirements: ['心理学基础'], requirements: ['心理学基础'],
objectives: ['掌握心理辅导技巧'], objectives: ['掌握心理辅导技巧'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',
@ -399,13 +399,13 @@ const loadCourses = async () => {
totalLessons: 40, totalLessons: 40,
level: 'advanced', level: 'advanced',
language: 'zh-CN', language: 'zh-CN',
category: { id: 6, name: '教育评估', slug: 'education-assessment' }, category: { id: "6", name: '教育评估', slug: 'education-assessment' },
tags: ['教育评估', '测量', '技术'], tags: ['教育评估', '测量', '技术'],
skills: ['评估设计', '测量技术'], skills: ['评估设计', '测量技术'],
requirements: ['教育统计学'], requirements: ['教育统计学'],
objectives: ['掌握教育评估方法'], objectives: ['掌握教育评估方法'],
instructor: { instructor: {
id: 1, id: "1",
name: '汪波', name: '汪波',
avatar: '/images/Teachers/师资力量1.png', avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授', title: '云南师范大学教授',

View File

@ -405,7 +405,7 @@ const loadCourseCategoryFromManagementAPI = async () => {
// ID // ID
const categoryNames = categoryIds.map((id: number) => { const categoryNames = categoryIds.map((id: number) => {
const category = categoryResponse.data.find(cat => cat.id === id) const category = categoryResponse.data.find(cat => cat.id === String(id))
return category ? category.name : `未知分类${id}` return category ? category.name : `未知分类${id}`
}).filter(Boolean) }).filter(Boolean)

File diff suppressed because it is too large Load Diff