feat:bug统一修复

This commit is contained in:
小张 2025-09-21 04:12:09 +08:00
parent 6d6ded74b4
commit bf6496b755
5 changed files with 711 additions and 72 deletions

View File

@ -1305,6 +1305,120 @@ export class ExamApi {
return response return response
} }
// ========== 题库权限管理相关接口 ==========
/**
*
*/
static async searchUsers(params: {
repoId: string // 必需参数
employeeNumber?: string
username?: string
}): Promise<ApiResponse<{
success: boolean
code: number
message: string
result: {
userId: string
userName: string
userAvatar: string
permission: boolean
}[]
timestamp: number
}>> {
console.log('🚀 搜索用户列表:', params)
// 构建查询参数,确保参数格式正确
const queryParams: Record<string, string> = {
repoId: params.repoId
}
if (params.employeeNumber) {
queryParams.employeeNumber = params.employeeNumber
}
if (params.username) {
queryParams.username = params.username
}
console.log('📤 发送请求参数:', queryParams)
const response = await ApiRequest.get<any>('/aiol/aiolRepo/userList', queryParams)
console.log('✅ 搜索用户列表成功:', response)
return response as ApiResponse<{
success: boolean
code: number
message: string
result: {
userId: string
userName: string
userAvatar: string
permission: boolean
}[]
timestamp: number
}>
}
/**
*
*/
static async getUsersByRepoId(repoId: string): Promise<ApiResponse<{
success: boolean
code: number
message: string
result: {
userId: string
userName: string
userAvatar: string
permission: boolean
}[]
timestamp: number
}>> {
console.log('🚀 获取题库有权限的用户列表:', { repoId })
const response = await ApiRequest.get<any>(`/aiol/aiolRepo/userListByRepoId/${repoId}`)
console.log('✅ 获取题库有权限的用户列表成功:', response)
return response as ApiResponse<{
success: boolean
code: number
message: string
result: {
userId: string
userName: string
userAvatar: string
permission: boolean
}[]
timestamp: number
}>
}
/**
*
*/
static async addOrDeletePermission(params: {
repoId: string
type: 'add' | 'delete'
userId: string
}): Promise<ApiResponse<{
success: boolean
code: number
message: string
result: string
timestamp: number
}>> {
console.log('🚀 添加或删除题库权限:', params)
// 构建路径参数的URL
const url = `/aiol/aiolRepo/addOrDeletePermission?repoId=${params.repoId}&type=${params.type}&userId=${params.userId}`
const response = await ApiRequest.post<any>(url)
console.log('✅ 添加或删除题库权限成功:', response)
return response as ApiResponse<{
success: boolean
code: number
message: string
result: string
timestamp: number
}>
}
// ========== 阅卷中心相关接口 ========== // ========== 阅卷中心相关接口 ==========
/** /**

View File

@ -179,6 +179,12 @@ watch(() => userStore.user?.avatar, (newAvatar) => {
console.log('🖼️ AppHeader - 头像URL变化:', newAvatar) console.log('🖼️ AppHeader - 头像URL变化:', newAvatar)
}, { immediate: true }) }, { immediate: true })
//
watch(() => userStore.isTeacher, (isTeacher) => {
console.log('👨‍🏫 AppHeader - 用户是否为教师:', isTeacher)
console.log('👤 AppHeader - 用户角色:', userStore.user?.role)
}, { immediate: true })
// //
const mobileMenuOpen = ref(false) const mobileMenuOpen = ref(false)
@ -548,13 +554,13 @@ const userMenuOptions = computed(() => [
}, [ }, [
h('img', { h('img', {
src: '/images/personal/用户_user备份@2x.png', src: '/images/personal/用户_user备份@2x.png',
alt: '个人资料', alt: '个人中心',
style: 'width: 18px; height: 18px; object-fit: contain;' style: 'width: 18px; height: 18px; object-fit: contain;'
}) })
]), ]),
h('span', { h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;' style: 'font-size: 14px; color: #333; font-weight: 500;'
}, '个人资料') }, '个人中心')
]) ])
]), ]),
@ -563,8 +569,8 @@ const userMenuOptions = computed(() => [
class: 'user-menu-footer', class: 'user-menu-footer',
style: 'display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f0f0;' style: 'display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f0f0;'
}, [ }, [
// // - teacher
h('div', { ...(userStore.isTeacher ? [h('div', {
class: 'footer-action', class: 'footer-action',
style: 'display: flex; align-items: center; gap: 4px; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;', style: 'display: flex; align-items: center; gap: 4px; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;',
onClick: () => handleUserMenuSelect('teacher') onClick: () => handleUserMenuSelect('teacher')
@ -577,7 +583,7 @@ const userMenuOptions = computed(() => [
h('span', { h('span', {
style: 'font-size: 12px; color: #666;' style: 'font-size: 12px; color: #666;'
}, '切换教师端') }, '切换教师端')
]), ])] : []),
// 退 // 退
h('div', { h('div', {

View File

@ -64,7 +64,7 @@
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<div class="breadcrumb-section"> <div class="breadcrumb-section">
<div class="breadcrumb"> <div class="breadcrumb">
<span class="breadcrumb-course">{{ course?.title || '课程' }}</span> <span class="breadcrumb-course clickable" @click="goBackToVideo">{{ course?.title || '课程' }}</span>
<span class="breadcrumb-separator"> > </span> <span class="breadcrumb-separator"> > </span>
<span class="breadcrumb-current">{{ practiceMode ? currentPracticeSection?.name || '练习' : '讨论' }}</span> <span class="breadcrumb-current">{{ practiceMode ? currentPracticeSection?.name || '练习' : '讨论' }}</span>
</div> </div>
@ -577,15 +577,12 @@
<span class="comment-username">{{ comment.username }}</span> <span class="comment-username">{{ comment.username }}</span>
</div> </div>
<div class="comment-text">{{ comment.content }}</div> <div class="comment-text">{{ comment.content }}</div>
<div class="comment-actions"> <div class="comment-footer">
<button class="action-btn"> <span class="comment-time">2025.07.23 16:28</span>
<span>2025.07.23 16:28</span> <div class="discussion-comment-actions">
</button>
<button v-if="comment.type === 'note'" class="action-btn"> <span class="discussion-reply-text" @click="startReply(comment.id, comment.username)">回复</span>
<span class="top">置顶评论</span> </div>
</button>
<button v-else class="action-btn"
@click="startReply(comment.id, comment.username)">回复</button>
</div> </div>
<!-- 回复输入区域 --> <!-- 回复输入区域 -->
@ -689,13 +686,14 @@
<span class="more-images-text">+6</span> <span class="more-images-text">+6</span>
</div> </div>
</div> </div>
<div class="comment-actions"> <div class="comment-footer">
<div class="note-icon-container">
<img src="/images/courses/comments-note.png" alt="笔记" class="note-icon">
<span>笔记</span>
</div>
<span class="comment-time">2025.07.23 16:28</span> <span class="comment-time">2025.07.23 16:28</span>
<button class="action-btn" @click="startReply(1, '张老师')">回复</button> <div class="discussion-comment-actions">
<button class="discussion-like-btn">
👍 0
</button>
<span class="discussion-reply-text" @click="startReply(1, '张老师')">回复</span>
</div>
</div> </div>
<!-- 回复输入区域 --> <!-- 回复输入区域 -->
@ -1662,9 +1660,15 @@ const commentsCount = computed(() => {
return comments.value.length return comments.value.length
}) })
// textarea // textareatextarea
const adjustTextareaHeight = (event: Event) => { const adjustTextareaHeight = (event: Event) => {
const textarea = event.target as HTMLTextAreaElement const textarea = event.target as HTMLTextAreaElement
// textareacomment-textarea36px
if (textarea.classList.contains('comment-textarea') && textarea.closest('.post-comment-section')) {
textarea.style.height = '36px'
return
}
// textarea
textarea.style.height = 'auto' textarea.style.height = 'auto'
textarea.style.height = textarea.scrollHeight + 'px' textarea.style.height = textarea.scrollHeight + 'px'
} }
@ -1672,7 +1676,12 @@ const adjustTextareaHeight = (event: Event) => {
// textarea // textarea
const handleTextareaClick = (event: MouseEvent) => { const handleTextareaClick = (event: MouseEvent) => {
const textarea = event.target as HTMLTextAreaElement const textarea = event.target as HTMLTextAreaElement
// 40px60px // textareacomment-textarea36px
if (textarea.classList.contains('comment-textarea') && textarea.closest('.post-comment-section')) {
textarea.style.height = '36px'
return
}
// textarea
if (textarea.style.height === '40px' || textarea.style.height === '') { if (textarea.style.height === '40px' || textarea.style.height === '') {
textarea.style.height = '60px' textarea.style.height = '60px'
} }
@ -1773,6 +1782,22 @@ const cancelReply = () => {
replyText.value = '' replyText.value = ''
} }
//
const goBackToVideo = () => {
// 退
practiceMode.value = false
discussionMode.value = false
practiceStarted.value = false
practiceFinished.value = false
//
currentPracticeSection.value = null
currentQuestionIndex.value = 0
console.log('🔙 返回到视频页面')
}
const submitReply = () => { const submitReply = () => {
if (replyText.value.trim() && replyingTo.value) { if (replyText.value.trim() && replyingTo.value) {
const newReplyObj = { const newReplyObj = {
@ -3252,7 +3277,7 @@ onActivated(() => {
width: 300px; width: 300px;
flex-shrink: 0; flex-shrink: 0;
align-self: flex-start; align-self: flex-start;
margin-top: 72px; /* 与提示区域顶部对齐 */ margin-top: 54px; /* 与广告区域齐平 */
} }
/* 面包屑导航样式 */ /* 面包屑导航样式 */
@ -3279,6 +3304,15 @@ onActivated(() => {
cursor: pointer; cursor: pointer;
white-space: nowrap; /* 一行展示,不换行 */ white-space: nowrap; /* 一行展示,不换行 */
/* 移除固定宽度,让内容完全展示 */ /* 移除固定宽度,让内容完全展示 */
transition: color 0.3s ease;
}
.breadcrumb-course.clickable {
color: #333333; /* 保持原来的颜色 */
}
.breadcrumb-course.clickable:hover {
/* 悬停时保持原样,不改变颜色和样式 */
} }
.breadcrumb-separator { .breadcrumb-separator {
@ -6926,11 +6960,15 @@ onActivated(() => {
/* 发布评论区域 */ /* 发布评论区域 */
.post-comment-section { .post-comment-section {
margin-bottom: 10px; margin-bottom: 10px;
background: transparent; /* 无背景 */
border: none; /* 无边框 */
} }
.comment-input-wrapper { .comment-input-wrapper {
display: flex; display: flex;
gap: 12px; gap: 12px;
background: transparent; /* 无背景 */
border: none; /* 无边框 */
} }
.user-avatar img { .user-avatar img {
@ -6943,20 +6981,47 @@ onActivated(() => {
.comment-input-area { .comment-input-area {
flex: 1; flex: 1;
background: transparent; /* 无背景 */
border: none; /* 无边框 */
} }
.comment-textarea { .comment-textarea {
width: 100%; width: 100% !important;
border: 1px solid #E6E6E6; border: 1px solid #E6E6E6 !important;
padding: 10px; border-radius: 2px !important;
font-size: 14px; padding: 8px 12px !important;
resize: none; font-size: 14px !important;
font-family: inherit; resize: none !important; /* 强制禁用调整大小功能 */
height: 40px; font-family: PingFangSC, PingFang SC !important;
min-height: 40px; height: 36px !important; /* 强制设置高度为36px */
box-sizing: border-box; min-height: 36px !important;
overflow: hidden; max-height: 36px !important;
transition: height 0.3s ease; box-sizing: border-box !important;
overflow: hidden !important;
background: transparent !important; /* 透明背景 */
outline: none !important;
line-height: 20px !important;
/* 完全移除 resize handle */
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
}
/* 针对外面评论区域的 textarea 强制移除 resize handle */
.comment-textarea::-webkit-resizer {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
}
.comment-textarea::-moz-resizer {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
} }
.comment-textarea:focus { .comment-textarea:focus {
@ -6973,6 +7038,8 @@ onActivated(() => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-top: 1px; /* 与讨论区域一致的间距 */
} }
.toolbar-left { .toolbar-left {
@ -6981,10 +7048,11 @@ onActivated(() => {
} }
.toolbar-btn { .toolbar-btn {
background: none;
border: none;
width: 24px; width: 24px;
height: 20px; height: 20px;
border-radius: 2px;
border: 1px solid #E6E6E6;
background: transparent; /* 透明背景 */
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -6997,9 +7065,9 @@ onActivated(() => {
} }
.toolbar-icon { .toolbar-icon {
width: 12px; width: 16px; /* 调整图标大小 */
height: 12px; height: 16px;
object-fit: contain; /* 移除蓝色边框 */
} }
.toolbar-right { .toolbar-right {
@ -7011,11 +7079,17 @@ onActivated(() => {
background: #0088D1; background: #0088D1;
color: white; color: white;
border: none; border: none;
border-radius: 2px; /* 添加圆角 */
padding: 3px 10px; padding: 3px 10px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
transition: background-color 0.3s; transition: background-color 0.3s;
font-weight: 500; font-weight: 500;
width: 48px; /* 与讨论区域一致的宽度 */
height: 24px; /* 与讨论区域一致的高度 */
font-family: PingFangSC, PingFang SC;
text-align: center;
line-height: 20px;
} }
.btn-submit:hover:not(:disabled) { .btn-submit:hover:not(:disabled) {
@ -7079,7 +7153,7 @@ onActivated(() => {
border: none; border: none;
padding: 10px; padding: 10px;
font-size: 14px; font-size: 14px;
resize: none; resize: none !important; /* 强制禁用调整大小功能 */
font-family: inherit; font-family: inherit;
height: 40px; height: 40px;
min-height: 40px; min-height: 40px;
@ -7087,6 +7161,10 @@ onActivated(() => {
overflow: hidden; overflow: hidden;
transition: height 0.3s ease; transition: height 0.3s ease;
background: transparent; background: transparent;
/* 完全移除 resize handle */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
} }
.reply-textarea:focus { .reply-textarea:focus {
@ -7099,6 +7177,23 @@ onActivated(() => {
color: #999; color: #999;
} }
/* 针对回复区域的 textarea 强制移除 resize handle */
.reply-textarea::-webkit-resizer {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
}
.reply-textarea::-moz-resizer {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
}
.reply-toolbar { .reply-toolbar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -8374,28 +8469,9 @@ onActivated(() => {
margin-bottom: 32px; margin-bottom: 32px;
} }
.comment-input-wrapper { /* 移除重复的 comment-input-wrapper 样式,使用上面的定义 */
border: 1px solid #d9d9d9;
border-radius: 8px;
overflow: hidden;
background: white;
}
.comment-textarea { /* 移除重复的 comment-textarea 样式,使用上面的定义 */
width: 100%;
padding: 16px;
border: none;
outline: none;
resize: vertical;
min-height: 80px;
font-size: 14px;
line-height: 1.6;
font-family: inherit;
}
.comment-textarea::placeholder {
color: #bfbfbf;
}
.comment-actions { .comment-actions {
display: flex; display: flex;

View File

@ -503,7 +503,7 @@
<!-- 固定按钮组 --> <!-- 固定按钮组 -->
<div class="fixed-buttons-group" :class="{ 'show': showFixedButtons }"> <div class="fixed-buttons-group" :class="{ 'show': showFixedButtons }">
<div class="fixed-button customer-btn" title="客服"> <div class="fixed-button customer-btn" title="客服" @click="goToMessageCenter">
<img src="/images/icon/customer.jpg" alt="客服" /> <img src="/images/icon/customer.jpg" alt="客服" />
</div> </div>
<!-- <div class="fixed-button phone-btn" title="电话"> <!-- <div class="fixed-button phone-btn" title="电话">
@ -556,8 +556,8 @@ const goToCoursesPage = () => {
// //
router.push('/profile') router.push('/profile')
} else { } else {
// //
loginModalVisible.value = true router.push('/login')
} }
} }
@ -674,6 +674,32 @@ const scrollToTop = () => {
}) })
} }
//
const goToMessageCenter = () => {
console.log('🔍 点击客服按钮,用户状态:', {
isLoggedIn: userStore.isLoggedIn,
isTeacher: userStore.isTeacher,
userRole: userStore.user?.role
})
if (!userStore.isLoggedIn) {
//
console.log('👤 用户未登录,跳转到登录页面')
router.push('/login')
return
}
if (userStore.isTeacher) {
//
console.log('👨‍🏫 教师用户,跳转到教师端消息中心')
router.push('/teacher/message-center')
} else {
//
console.log('👨‍🎓 学生用户,跳转到学员端消息中心')
router.push('/profile/message')
}
}
// //
const handleScroll = () => { const handleScroll = () => {
try { try {

View File

@ -43,6 +43,17 @@
<n-input v-model:value="createForm.description" type="textarea" placeholder="请输入题库描述" <n-input v-model:value="createForm.description" type="textarea" placeholder="请输入题库描述"
style="width: 100%;" :rows="3" /> style="width: 100%;" :rows="3" />
</div> </div>
<!-- 权限管理区域 - 仅在编辑模式下显示 -->
<div v-if="isEditMode && currentRepoId" class="permission-section">
<div class="section-divider"></div>
<div class="section-title">权限管理</div>
<div class="permission-actions">
<n-button type="info" @click="showPermissionManagement(currentRepoId)">
管理用户权限
</n-button>
</div>
</div>
</div> </div>
<template #action> <template #action>
@ -100,15 +111,121 @@
</n-space> </n-space>
</template> </template>
</n-modal> </n-modal>
<!-- 权限管理弹窗 -->
<n-modal
v-model:show="showPermissionModal"
preset="dialog"
title="权限管理"
style="width: 700px;"
>
<div class="permission-modal-content">
<n-tabs default-value="authorized" type="line">
<!-- 已授权用户标签页 -->
<n-tab-pane name="authorized" tab="已授权用户">
<div v-if="authorizedUsers.length > 0" class="permission-user-list">
<div
v-for="user in authorizedUsers"
:key="user.userId"
class="permission-user-item"
>
<div class="user-info">
<img
:src="user.userAvatar || '/images/default-avatar.png'"
:alt="user.userName"
class="user-avatar"
/>
<span class="user-name">{{ user.userName }}</span>
<span class="permission-badge authorized">已授权</span>
</div>
<div class="permission-actions">
<n-button
type="error"
size="small"
@click="toggleUserPermission(user.userId, true)"
>
移除权限
</n-button>
</div>
</div>
</div>
<div v-else class="no-users">
<n-empty description="暂无已授权用户" />
</div>
</n-tab-pane>
<!-- 搜索用户标签页 -->
<n-tab-pane name="search" tab="搜索用户">
<div class="permission-search">
<n-space>
<n-input
v-model:value="permissionSearchKeyword"
placeholder="搜索用户名"
style="width: 300px;"
@keyup.enter="searchPermissionUsers"
/>
<n-button type="primary" @click="searchPermissionUsers">
搜索
</n-button>
</n-space>
</div>
<div v-if="searchResults.length > 0" class="permission-user-list">
<div
v-for="user in searchResults"
:key="user.userId"
class="permission-user-item"
>
<div class="user-info">
<img
:src="user.userAvatar || '/images/default-avatar.png'"
:alt="user.userName"
class="user-avatar"
/>
<span class="user-name">{{ user.userName }}</span>
<span
class="permission-badge"
:class="user.permission ? 'authorized' : 'unauthorized'"
>
{{ user.permission ? '已授权' : '未授权' }}
</span>
</div>
<div class="permission-actions">
<n-button
:type="user.permission ? 'error' : 'primary'"
size="small"
@click="toggleUserPermission(user.userId, user.permission)"
>
{{ user.permission ? '移除权限' : '添加权限' }}
</n-button>
</div>
</div>
</div>
<div v-else-if="permissionSearchKeyword" class="no-users">
<n-empty description="未找到匹配的用户" />
</div>
<div v-else class="search-tip">
<n-empty description="请输入用户名进行搜索" />
</div>
</n-tab-pane>
</n-tabs>
</div>
<template #action>
<n-space>
<n-button @click="closePermissionModal">关闭</n-button>
</n-space>
</template>
</n-modal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, h, VNode, watch, nextTick } from 'vue'; import { ref, reactive, onMounted, h, VNode, watch, nextTick, computed } from 'vue';
import { NButton, NSpace, NSelect, NIcon, NText, NP, NAlert, NUpload, NUploadDragger, useMessage, useDialog } from 'naive-ui'; import { NButton, NSpace, NSelect, NIcon, NText, NP, NAlert, NUpload, NUploadDragger, NInput, useMessage, useDialog } from 'naive-ui';
import { CloudUploadOutline } from '@vicons/ionicons5'; import { CloudUploadOutline } from '@vicons/ionicons5';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { ExamApi } from '@/api'; import { ExamApi, AuthApi } from '@/api';
import type { Repo } from '@/api/types'; import type { Repo } from '@/api/types';
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui'; import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
@ -169,6 +286,28 @@ const createForm = reactive({
description: '', description: '',
}); });
//
const showPermissionModal = ref(false);
const currentRepoId = ref('');
const permissionSearchKeyword = ref('');
const permissionUserList = ref<{
userId: string;
userName: string;
userAvatar: string;
permission: boolean;
}[]>([]);
//
const authorizedUsers = computed(() =>
permissionUserList.value.filter(user => user.permission)
);
const searchResults = ref<{
userId: string;
userName: string;
userAvatar: string;
permission: boolean;
}[]>([]);
// //
const showImportModal = ref(false); const showImportModal = ref(false);
const importFileList = ref<UploadFileInfo[]>([]); const importFileList = ref<UploadFileInfo[]>([]);
@ -411,8 +550,9 @@ const loadQuestionBanks = async () => {
creator: repo.createBy || '未知', creator: repo.createBy || '未知',
createTime: repo.createTime || '', createTime: repo.createTime || '',
lastModified: repo.updateTime || '', lastModified: repo.updateTime || '',
courseName: repo.courseName || '暂无课程' courseName: repo.courseName || '暂无课程',
})); createBy: repo.createBy // createBy
} as QuestionBank & { createBy: string }));
// //
let filteredData = apiData; let filteredData = apiData;
@ -581,7 +721,7 @@ const enterQuestionBank = (bankId: string, bankTitle: string) => {
} }
}; };
const editQuestionBank = (id: string) => { const editQuestionBank = async (id: string) => {
console.log('编辑题库:', id); console.log('编辑题库:', id);
// //
@ -591,9 +731,33 @@ const editQuestionBank = (id: string) => {
return; return;
} }
// // usernamecreateBy
try {
const userInfo = await AuthApi.getUserInfo();
if (userInfo.success && userInfo.result) {
const currentUserUsername = userInfo.result.baseInfo.username;
console.log('当前用户username:', currentUserUsername);
console.log('题库createBy:', (questionBank as any).createBy);
//
if (currentUserUsername !== (questionBank as any).createBy) {
console.log('❌ 用户无权限编辑此题库');
message.error('当前用户无权限编辑此题库');
return;
}
console.log('✅ 用户有权限编辑此题库');
}
} catch (error) {
console.error('获取用户信息失败:', error);
message.error('获取用户信息失败');
return;
}
// -
isEditMode.value = true; isEditMode.value = true;
editingId.value = id; editingId.value = id;
currentRepoId.value = id; // ID
// //
createForm.name = questionBank.name; createForm.name = questionBank.name;
@ -603,6 +767,141 @@ const editQuestionBank = (id: string) => {
showCreateModal.value = true; showCreateModal.value = true;
}; };
//
const showPermissionManagement = (repoId: string) => {
currentRepoId.value = repoId;
showPermissionModal.value = true;
//
setTimeout(() => {
loadInitialPermissionUsers();
}, 50);
};
//
const loadInitialPermissionUsers = async () => {
if (!currentRepoId.value) return;
try {
console.log('🔍 初始加载权限用户列表题库ID:', currentRepoId.value);
//
const response = await ExamApi.getUsersByRepoId(currentRepoId.value);
console.log('🔍 获取有权限用户响应:', response);
// response.data {success, code, message, result}
const apiData = response.data;
if (apiData.success && apiData.code === 200 && apiData.result) {
permissionUserList.value = apiData.result;
console.log('✅ 获取有权限用户列表成功,共', apiData.result.length, '个用户');
} else {
//
console.log('⚠️ 获取有权限用户失败,尝试搜索所有用户');
console.log('⚠️ 失败原因:', { success: apiData.success, code: apiData.code, message: apiData.message });
await searchPermissionUsers();
}
} catch (error: any) {
console.error('❌ 初始加载权限用户列表失败:', error);
//
console.log('⚠️ 获取有权限用户失败,尝试搜索所有用户');
await searchPermissionUsers();
}
};
//
const searchPermissionUsers = async () => {
if (!currentRepoId.value) {
message.error('题库ID不能为空');
return;
}
try {
console.log('🔍 搜索参数:', {
repoId: currentRepoId.value,
username: permissionSearchKeyword.value
});
const response = await ExamApi.searchUsers({
repoId: currentRepoId.value,
username: permissionSearchKeyword.value || undefined
});
console.log('🔍 完整响应数据:', response);
// response.data {success, code, message, result}
const apiData = response.data;
console.log('🔍 API数据结构:', {
success: apiData.success,
code: apiData.code,
message: apiData.message,
result: apiData.result?.length || 0
});
//
if (apiData.success && apiData.code === 200 && apiData.result) {
searchResults.value = apiData.result;
console.log('✅ 搜索用户列表成功,共', apiData.result.length, '个用户');
} else {
const errorMessage = apiData.message || '获取用户列表失败';
message.error(errorMessage);
console.error('❌ API返回错误:', {
success: apiData.success,
businessCode: apiData.code,
message: apiData.message
});
}
} catch (error: any) {
console.error('❌ 获取权限用户列表失败:', error);
const errorMessage = error.response?.data?.message || error.message || '获取用户列表失败';
message.error(errorMessage);
}
};
//
const toggleUserPermission = async (userId: string, currentPermission: boolean) => {
if (!currentRepoId.value) return;
try {
const response = await ExamApi.addOrDeletePermission({
repoId: currentRepoId.value,
userId: userId,
type: currentPermission ? 'delete' : 'add'
});
console.log('🔍 权限操作响应:', response);
// response.data {success, code, message, result}
const apiData = response.data;
if (apiData.success && apiData.code === 200) {
message.success(currentPermission ? '权限已移除' : '权限已添加');
//
await loadInitialPermissionUsers();
if (permissionSearchKeyword.value) {
await searchPermissionUsers();
}
} else {
const errorMessage = apiData.message || '权限操作失败';
message.error(errorMessage);
console.error('❌ 权限操作失败:', { success: apiData.success, code: apiData.code, message: apiData.message });
}
} catch (error) {
console.error('❌ 权限操作失败:', error);
message.error('权限操作失败');
}
};
//
const closePermissionModal = () => {
showPermissionModal.value = false;
currentRepoId.value = '';
permissionSearchKeyword.value = '';
permissionUserList.value = [];
searchResults.value = [];
};
const deleteQuestionBank = (id: string) => { const deleteQuestionBank = (id: string) => {
// //
const questionBank = questionBankList.value.find(item => item.id === id); const questionBank = questionBankList.value.find(item => item.id === id);
@ -645,6 +944,7 @@ const closeCreateModal = () => {
showCreateModal.value = false; showCreateModal.value = false;
isEditMode.value = false; isEditMode.value = false;
editingId.value = ''; editingId.value = '';
currentRepoId.value = ''; // ID
createForm.name = ''; createForm.name = '';
createForm.description = ''; createForm.description = '';
}; };
@ -957,4 +1257,121 @@ onMounted(() => {
.import-result { .import-result {
margin-top: 16px; margin-top: 16px;
} }
/* 权限管理样式 */
.permission-modal-content {
padding: 16px 0;
}
.permission-search {
margin-bottom: 20px;
}
.permission-user-list {
max-height: 400px;
overflow-y: auto;
}
.permission-user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border: 1px solid #e6e6e6;
border-radius: 6px;
margin-bottom: 8px;
background: #fafafa;
}
.permission-user-item:hover {
background: #f0f0f0;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.user-name {
font-size: 14px;
font-weight: 500;
color: #333;
}
.permission-actions {
display: flex;
gap: 8px;
}
.no-users {
text-align: center;
padding: 40px 0;
color: #999;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0;
}
/* 编辑弹窗中的权限管理区域样式 */
.permission-section {
margin-top: 20px;
}
.section-divider {
height: 1px;
background: #e6e6e6;
margin: 16px 0;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 12px;
}
.permission-actions {
display: flex;
gap: 8px;
}
/* 权限徽章样式 */
.permission-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
margin-left: 8px;
}
.permission-badge.authorized {
background: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.permission-badge.unauthorized {
background: #fff2e8;
color: #fa8c16;
border: 1px solid #ffd591;
}
/* 搜索提示样式 */
.search-tip {
text-align: center;
padding: 40px 0;
color: #999;
}
</style> </style>