feat:bug统一修复
This commit is contained in:
parent
6d6ded74b4
commit
bf6496b755
@ -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
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 阅卷中心相关接口 ==========
|
// ========== 阅卷中心相关接口 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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', {
|
||||||
|
@ -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高度
|
// 自动调整textarea高度(但不影响固定高度的textarea)
|
||||||
const adjustTextareaHeight = (event: Event) => {
|
const adjustTextareaHeight = (event: Event) => {
|
||||||
const textarea = event.target as HTMLTextAreaElement
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
// 如果textarea有comment-textarea类且在发布评论区域,保持固定高度36px
|
||||||
|
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
|
||||||
// 如果当前高度是40px,则调整到60px
|
// 如果textarea有comment-textarea类且在发布评论区域,保持固定高度36px
|
||||||
|
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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置编辑模式
|
// 检查权限:比较当前用户username和题库的createBy
|
||||||
|
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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user