feat: 完善班级管理,消息中心对接接口,添加样式,修复打包报错
This commit is contained in:
parent
254fb72d0d
commit
5c14a13f5e
@ -77,7 +77,7 @@ export class AuthApi {
|
||||
message: actualMessage || '登录成功',
|
||||
data: {
|
||||
user: {
|
||||
id: 1, // 真实API没有返回用户ID,使用默认值
|
||||
id: "1", // 真实API没有返回用户ID,使用默认值
|
||||
email: data.email || '',
|
||||
phone: data.phone || '',
|
||||
username: data.email || data.phone || '',
|
||||
@ -129,7 +129,7 @@ export class AuthApi {
|
||||
message: actualMessage || '登录成功',
|
||||
data: {
|
||||
user: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
email: data.email || '',
|
||||
phone: data.phone || '',
|
||||
username: data.phone || data.email?.split('@')[0] || 'user',
|
||||
@ -232,7 +232,7 @@ export class AuthApi {
|
||||
code: 200,
|
||||
message: actualMessage || '注册成功',
|
||||
data: {
|
||||
id: Date.now(), // 临时ID
|
||||
id: Date.now().toString(), // 临时ID
|
||||
username: registerData.studentNumber,
|
||||
email: data.email || '',
|
||||
phone: data.phone || '',
|
||||
@ -339,7 +339,7 @@ export class AuthApi {
|
||||
}
|
||||
|
||||
const convertedUser = {
|
||||
id: parseInt(baseInfo.id) || 0,
|
||||
id: baseInfo.id, // 直接使用字符串ID,避免parseInt精度问题
|
||||
username: baseInfo.username,
|
||||
email: baseInfo.email,
|
||||
phone: baseInfo.phone,
|
||||
|
@ -33,7 +33,7 @@ export interface ChatMessage {
|
||||
senderName: string
|
||||
senderAvatar?: string
|
||||
content: string
|
||||
messageType: 'text' | 'image' | 'file' | 'system'
|
||||
messageType: number // 0=文本, 1=图片, 2=文件, 3=系统消息
|
||||
timestamp: string
|
||||
isRead: boolean
|
||||
replyTo?: string
|
||||
@ -42,6 +42,8 @@ export interface ChatMessage {
|
||||
fileUrl?: string // 文件URL
|
||||
fileSize?: number // 文件大小
|
||||
fileType?: string // 文件类型
|
||||
fileName?: string // 文件名
|
||||
createTime?: string // 创建时间
|
||||
}
|
||||
|
||||
// 群聊成员接口类型定义
|
||||
@ -56,11 +58,13 @@ export interface ChatMember {
|
||||
username: string
|
||||
// 可选字段
|
||||
chatId?: string
|
||||
role?: 'admin' | 'member'
|
||||
role: 0 | 1 | 2 // 0=群主, 1=管理员, 2=成员
|
||||
joinTime?: string
|
||||
isOnline?: boolean
|
||||
status?: number // 成员状态
|
||||
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: {
|
||||
chatId: string
|
||||
chat_id: string
|
||||
content: string
|
||||
messageType: 'text' | 'image' | 'file'
|
||||
messageType: number // 0=文本,1=图片,2=文件
|
||||
replyTo?: string
|
||||
fileUrl?: string
|
||||
fileSize?: number
|
||||
fileType?: string
|
||||
fileName?: string
|
||||
}): 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>> => {
|
||||
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'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export interface PaginationResponse<T> {
|
||||
|
||||
// 用户相关类型
|
||||
export interface User {
|
||||
id: number
|
||||
id: string // 改为字符串类型,避免大整数精度问题
|
||||
username: string
|
||||
email: string
|
||||
phone?: string
|
||||
@ -218,12 +218,12 @@ export interface CourseListRequest {
|
||||
}
|
||||
|
||||
export interface CourseCategory {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
description?: string
|
||||
icon?: string
|
||||
parentId?: number
|
||||
parentId?: string
|
||||
children?: CourseCategory[]
|
||||
}
|
||||
|
||||
@ -359,7 +359,7 @@ export interface BackendCourseDetailResponse {
|
||||
}
|
||||
|
||||
export interface Instructor {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
bio: string
|
||||
|
@ -288,6 +288,57 @@
|
||||
:radio-options="importRadioOptions" radio-field="updateMode" import-type="student"
|
||||
template-name="student_import_template.xlsx" @success="handleImportSuccess"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
@ -295,7 +346,7 @@
|
||||
import { ref, onMounted, h, computed, watch } from 'vue'
|
||||
import TeachCourseApi, { ClassApi } from '@/api/modules/teachCourse'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { AddCircleOutline, SettingsOutline, QrCode } from '@vicons/ionicons5'
|
||||
import { AddCircleOutline, SettingsOutline, QrCode, SearchOutline } from '@vicons/ionicons5'
|
||||
import {
|
||||
NDataTable,
|
||||
NButton,
|
||||
@ -359,6 +410,16 @@ interface ClassItem {
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// 学员库数据类型定义
|
||||
interface LibraryStudentItem {
|
||||
id: string
|
||||
realName: string
|
||||
studentNumber: string
|
||||
className: string
|
||||
school: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// 表单数据类型
|
||||
interface FormData {
|
||||
studentName: string
|
||||
@ -386,8 +447,15 @@ const showTransferModal = ref(false)
|
||||
const showAddClassModal = ref(false)
|
||||
const showManageClassModal = ref(false)
|
||||
const showImportModal = ref(false)
|
||||
const showStudentLibraryModal = ref(false)
|
||||
const selectedTargetClass = ref('')
|
||||
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 classFormRef = ref<FormInst | null>(null)
|
||||
const isEditMode = ref(false)
|
||||
@ -525,6 +593,22 @@ const classSelectOptions = computed(() =>
|
||||
// 班级管理列表(使用主数据源)
|
||||
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> = [
|
||||
{
|
||||
@ -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 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) => {
|
||||
currentTransferStudent.value = row
|
||||
@ -871,7 +1021,34 @@ const confirmBatchTransfer = async () => {
|
||||
}
|
||||
|
||||
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 模式)
|
||||
@ -1117,9 +1294,8 @@ const handleAddStudentSelect = (key: string) => {
|
||||
// 手动添加,打开现有的添加学员弹窗
|
||||
openAddModal()
|
||||
} else if (key === 'library') {
|
||||
// 学员库添加,先保留功能,后续实现
|
||||
message.info('学员库添加功能待开发,敬请期待')
|
||||
console.log('学员库添加功能将在后续版本中实现')
|
||||
// 学员库添加,打开学员库选择弹窗
|
||||
openStudentLibraryModal()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1278,7 +1454,7 @@ const loadData = async (classId?: string | number | null) => {
|
||||
// 转换API响应数据为组件需要的格式
|
||||
const studentsData = response.data.result || []
|
||||
const transformedData: StudentItem[] = studentsData.map((student: any) => ({
|
||||
id: student.id || '',
|
||||
id: student.studentId || student.id || '', // 使用studentId作为主要ID
|
||||
studentName: student.realname || student.username || '未知姓名',
|
||||
accountNumber: student.studentId || student.username || '',
|
||||
className: student.classId || '未分配班级', // 班级ID,后续转换为班级名称
|
||||
@ -1342,6 +1518,158 @@ const clearSearch = () => {
|
||||
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变化,重新加载数据
|
||||
watch(
|
||||
() => props.classId,
|
||||
@ -1759,4 +2087,35 @@ defineExpose({
|
||||
.custom-empty .n-empty {
|
||||
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>
|
1
src/components/teacher/StudentLibraryModal.vue
Normal file
1
src/components/teacher/StudentLibraryModal.vue
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1210,7 +1210,7 @@ const loadCourseDetail = async () => {
|
||||
if (course.value) {
|
||||
if (!course.value.instructor?.name) {
|
||||
course.value.instructor = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: 'DeepSeek技术学院',
|
||||
title: '讲师',
|
||||
bio: '',
|
||||
@ -1292,7 +1292,7 @@ const loadMockCourseData = () => {
|
||||
title: 'DeepSeek办公自动化职业岗位标准课程',
|
||||
description: '本课程将帮助您掌握DeepSeek的基本使用方法,了解办公自动化职业岗位标准,提高教学质量和效率,获得实际工作技能。',
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: 'DeepSeek技术学院',
|
||||
title: '讲师',
|
||||
bio: '专注于AI技术应用与教学',
|
||||
@ -1311,7 +1311,7 @@ const loadMockCourseData = () => {
|
||||
price: 0,
|
||||
originalPrice: 299,
|
||||
category: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: 'AI技术',
|
||||
slug: 'ai-technology',
|
||||
description: 'AI技术',
|
||||
@ -1679,7 +1679,7 @@ const initializeMockState = () => {
|
||||
// 模拟用户已登录
|
||||
if (!userStore.isLoggedIn) {
|
||||
userStore.user = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
username: 'testuser',
|
||||
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',
|
||||
|
@ -793,19 +793,19 @@ const activeTab = ref('intro')
|
||||
// 讲师数据
|
||||
const instructors = ref([
|
||||
{
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
title: '教授',
|
||||
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: '汪波',
|
||||
title: '教授',
|
||||
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: '汪波',
|
||||
title: '教授',
|
||||
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.instructor?.name) {
|
||||
course.value.instructor = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: 'DeepSeek技术学院',
|
||||
title: '讲师',
|
||||
bio: '',
|
||||
@ -1408,7 +1408,7 @@ const initializeMockState = () => {
|
||||
// 模拟用户已登录
|
||||
if (!userStore.isLoggedIn) {
|
||||
userStore.user = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
username: 'testuser',
|
||||
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',
|
||||
|
@ -1247,7 +1247,7 @@ const initializeEnrolledState = () => {
|
||||
// 模拟用户已登录
|
||||
if (!userStore.isLoggedIn) {
|
||||
userStore.user = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
username: 'testuser',
|
||||
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',
|
||||
|
@ -2077,7 +2077,7 @@ const loadCourseDetail = async () => {
|
||||
if (course.value) {
|
||||
if (!course.value.instructor?.name) {
|
||||
course.value.instructor = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: 'DeepSeek技术学院',
|
||||
title: '讲师',
|
||||
bio: '',
|
||||
|
@ -593,13 +593,13 @@ const setDefaultCourseInfo = () => {
|
||||
description: '<p>本课程将带你从零开始学习C语言程序设计,掌握编程基础知识。</p>',
|
||||
content: '详细的C语言课程内容',
|
||||
category: {
|
||||
id: 3,
|
||||
id: "3",
|
||||
name: '信息技术',
|
||||
slug: 'it',
|
||||
description: '信息技术相关课程'
|
||||
},
|
||||
instructor: {
|
||||
id: 4,
|
||||
id: "4",
|
||||
name: '教师',
|
||||
title: 'C语言专家',
|
||||
bio: '资深C语言开发工程师',
|
||||
|
@ -285,7 +285,7 @@ const handleLogin = async () => {
|
||||
// 如果获取用户信息失败,创建基本用户信息
|
||||
console.warn('⚠️ 获取用户信息失败,使用基本信息')
|
||||
const basicUser = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
email: isPhone ? '' : formData.studentId,
|
||||
phone: isPhone ? formData.studentId : '',
|
||||
username: formData.studentId,
|
||||
@ -303,7 +303,7 @@ const handleLogin = async () => {
|
||||
// 如果获取用户信息异常,创建基本用户信息
|
||||
console.warn('⚠️ 获取用户信息异常,使用基本信息:', userInfoError)
|
||||
const basicUser = {
|
||||
id: 1,
|
||||
id: "1",
|
||||
email: isPhone ? '' : formData.studentId,
|
||||
phone: isPhone ? formData.studentId : '',
|
||||
username: formData.studentId,
|
||||
|
@ -219,13 +219,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 54,
|
||||
level: 'beginner',
|
||||
language: 'zh-CN',
|
||||
category: { id: 1, name: '教育培训', slug: 'education-training' },
|
||||
category: { id: "1", name: '教育培训', slug: 'education-training' },
|
||||
tags: ['心理学', '教育', '基础'],
|
||||
skills: ['心理分析', '教育理论'],
|
||||
requirements: ['无特殊要求'],
|
||||
objectives: ['掌握教育心理学基础理论'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
@ -255,13 +255,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 42,
|
||||
level: 'intermediate',
|
||||
language: 'zh-CN',
|
||||
category: { id: 2, name: '技术应用', slug: 'tech-application' },
|
||||
category: { id: "2", name: '技术应用', slug: 'tech-application' },
|
||||
tags: ['教育技术', '多媒体', '在线教育'],
|
||||
skills: ['多媒体制作', '在线教学'],
|
||||
requirements: ['基础计算机操作'],
|
||||
objectives: ['掌握现代教育技术应用'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
@ -291,13 +291,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 68,
|
||||
level: 'advanced',
|
||||
language: 'zh-CN',
|
||||
category: { id: 3, name: '课程设计', slug: 'course-design' },
|
||||
category: { id: "3", name: '课程设计', slug: 'course-design' },
|
||||
tags: ['课程设计', '教学开发', '教育'],
|
||||
skills: ['课程规划', '教学设计'],
|
||||
requirements: ['教育理论基础'],
|
||||
objectives: ['掌握课程设计方法'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
@ -327,13 +327,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 36,
|
||||
level: 'intermediate',
|
||||
language: 'zh-CN',
|
||||
category: { id: 4, name: '研究方法', slug: 'research-methods' },
|
||||
category: { id: "4", name: '研究方法', slug: 'research-methods' },
|
||||
tags: ['研究方法', '教育', '学术'],
|
||||
skills: ['研究设计', '数据分析'],
|
||||
requirements: ['统计学基础'],
|
||||
objectives: ['掌握教育研究方法'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
@ -363,13 +363,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 28,
|
||||
level: 'beginner',
|
||||
language: 'zh-CN',
|
||||
category: { id: 5, name: '心理辅导', slug: 'psychological-counseling' },
|
||||
category: { id: "5", name: '心理辅导', slug: 'psychological-counseling' },
|
||||
tags: ['心理辅导', '学生', '技巧'],
|
||||
skills: ['心理咨询', '沟通技巧'],
|
||||
requirements: ['心理学基础'],
|
||||
objectives: ['掌握心理辅导技巧'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
@ -399,13 +399,13 @@ const loadCourses = async () => {
|
||||
totalLessons: 40,
|
||||
level: 'advanced',
|
||||
language: 'zh-CN',
|
||||
category: { id: 6, name: '教育评估', slug: 'education-assessment' },
|
||||
category: { id: "6", name: '教育评估', slug: 'education-assessment' },
|
||||
tags: ['教育评估', '测量', '技术'],
|
||||
skills: ['评估设计', '测量技术'],
|
||||
requirements: ['教育统计学'],
|
||||
objectives: ['掌握教育评估方法'],
|
||||
instructor: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: '汪波',
|
||||
avatar: '/images/Teachers/师资力量1.png',
|
||||
title: '云南师范大学教授',
|
||||
|
@ -405,7 +405,7 @@ const loadCourseCategoryFromManagementAPI = async () => {
|
||||
|
||||
// 根据ID匹配分类名称
|
||||
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}`
|
||||
}).filter(Boolean)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user