diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index a39b8336..9f58b221 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -651,7 +651,8 @@ li{ color: #000000; margin-left: 4px; .el-input__inner { - width: 28px; + width: 100%; + min-width: 28px; height: 28px; background: #F5F9FF; border-radius: 4px; diff --git a/src/views/course/CourseManage.vue b/src/views/course/CourseManage.vue index b42abb8c..206e2892 100644 --- a/src/views/course/CourseManage.vue +++ b/src/views/course/CourseManage.vue @@ -1410,7 +1410,8 @@ export default { color: #000000; margin-left: 4px; .el-input__inner { - width: 28px; + width: 100%; + min-width: 28px; height: 28px; background: #f5f9ff; border-radius: 4px; diff --git a/src/views/course/ManageListRemote.vue b/src/views/course/ManageListRemote.vue index 12d18ea1..2a7ce99b 100644 --- a/src/views/course/ManageListRemote.vue +++ b/src/views/course/ManageListRemote.vue @@ -758,6 +758,7 @@ export default { }, handleTeacherChange(value = []) { this.params.teacherId = (value || []).join(','); + this.adjustTeacherWidth(); }, handleTeacherClear() { this.teacherSelected = []; @@ -821,10 +822,12 @@ export default { }, handleCreatorChange(value = []) { this.params.createUserId = (value || []).slice(0, 5).join(','); + this.adjustCreatorWidth(); }, handleCreatorClear() { this.creatorSelected = []; this.params.createUserId = ''; + this.adjustCreatorWidth(); }, // 创建人下拉展开时,如果当前没有关键字,则清空上一次的查询结果 handleCreatorVisibleChange(visible) { @@ -843,6 +846,7 @@ export default { if (this.$refs.teacherSelect) { this.$refs.teacherSelect.query = limited; } + this.adjustTeacherWidth(); }, limitCreatorInput(event) { const limited = (event && event.target && event.target.value ? event.target.value : '').slice(0, 50); @@ -852,6 +856,7 @@ export default { if (this.$refs.creatorSelect) { this.$refs.creatorSelect.query = limited; } + this.adjustCreatorWidth(); }, limitResOwnerInput(event) { const limited = (event && event.target && event.target.value ? event.target.value : '').slice(0, 200); @@ -892,6 +897,9 @@ export default { }, resOwnerFilterMethod(node, keyword) { if (!keyword) return true; + // 只对叶子节点做匹配,避免同一个名称在父/子层级出现时,看起来像“重复数据” + const isLeaf = node.isLeaf || (node.data && node.data.leaf); + if (!isLeaf) return false; const text = (node.label || (node.data && node.data.name) || '').toString().toLowerCase(); const kw = keyword.toString().toLowerCase(); return text.includes(kw); @@ -1508,6 +1516,14 @@ export default { sessionStorage.setItem('courseDetail', JSON.stringify(row)); this.$router.push({ path: '/iframe/course/coursemanage-remote' }); }, + // 克隆一个用于懒加载的节点:只保留当前层必要字段,不预置 children,避免 Element Cascader 出现重复节点 + cloneOrgNodeForLazy(node = {}) { + return { + id: node.id, + name: node.name, + leaf: node.leaf, + }; + }, // 前端“伪懒加载”:根据本地全量机构树按需返回子节点 // 为了让 el-cascader 的 loading 动画有可见效果,这里故意加入一个很小的延时再 resolve async loadResOwnerNodeFromCache(node, resolve) { @@ -1517,9 +1533,15 @@ export default { setTimeout(() => resolve(nodes), 150); }; - // 根节点(level === 0):返回所有一级机构 + // 如果当前节点在数据上被标记为叶子节点,直接返回空数组,不再触发“懒加载” + if (node && node.data && node.data.leaf) { + return resolve([]); + } + + // 根节点(level === 0):返回所有一级机构(只返回当前层的浅拷贝,不带 children,避免重复) if (node.level === 0) { - return delayResolve(this.resOwnerTreeAll || []); + const roots = (this.resOwnerTreeAll || []).map(item => this.cloneOrgNodeForLazy(item)); + return delayResolve(roots); } // 其他层级:根据当前节点 id 在树中查找对应节点,再返回其 children @@ -1543,11 +1565,25 @@ export default { }; const target = findNodeById(this.resOwnerTreeAll || [], currentId); - if (target && Array.isArray(target.children)) { - delayResolve(target.children); - } else { - delayResolve([]); + if (!target) { + // 未找到对应节点,直接返回空 + return resolve([]); } + + // 如果在树里这个节点本身就是叶子节点,同样直接返回空,不展示 loading + if (target.leaf) { + return resolve([]); + } + + if (Array.isArray(target.children) && target.children.length > 0) { + // 有子节点时,只返回当前层的浅拷贝,不预置更深 children,避免重复 + const children = target.children.map(child => this.cloneOrgNodeForLazy(child)); + // 做一个轻微延迟,让 loading 有一点点反馈 + return delayResolve(children); + } + + // 没有子节点,直接返回空 + return resolve([]); } catch (e) { console.error('本地懒加载资源归属节点失败:', e); resolve([]); @@ -1557,6 +1593,10 @@ export default { if (!value || value.length === 0) { this.orgId = ''; this.orgName = ''; + // 清空选择时,直接收起下拉 + if (this.$refs.resOwnerCascader) { + this.$refs.resOwnerCascader.dropDownVisible = false; + } return; } // value 是选中的路径数组,最后一个元素是选中的节点ID @@ -1571,31 +1611,49 @@ export default { const lastNode = checkedNodes[checkedNodes.length - 1]; this.orgName = lastNode.label || lastNode.name || ''; } + // 选中后自动收起下拉 + this.$refs.resOwnerCascader.dropDownVisible = false; } }); }, // 将后端机构节点转换为级联组件所需结构 mapOrgToCascaderNode(node = {}) { - const children = Array.isArray(node.childList) + const hasChildren = Array.isArray(node.childList) && node.childList.length > 0; + const children = hasChildren ? node.childList.map(child => this.mapOrgToCascaderNode(child)) : []; return { id: node.organizationId, name: node.orgName, children, + // childList 为空就标记为叶子节点,去掉右侧箭头,且不再触发懒加载 + leaf: !hasChildren, }; }, // 加载资源归属全量机构树 async loadAllResOwnerTree() { try { const res = await apiUserbasic.getAllOrgTree(); + // 调试日志:打印原始机构树与映射后的树结构,用于排查“重复节点”问题 + try { + const raw = res && res.result && res.result.orgTreeList + ? JSON.parse(JSON.stringify(res.result.orgTreeList)) + : []; + // 注意:这里日志仅用于前端调试,确认后可删除 + console.log('[资源归属] getAllOrgTree 原始 orgTreeList =>', raw); + } catch (e) { + console.warn('[资源归属] 打印 orgTreeList 日志失败:', e); + } + if (res && res.status === 200 && res.result && Array.isArray(res.result.orgTreeList)) { this.resOwnerTreeAll = res.result.orgTreeList.map(item => this.mapOrgToCascaderNode(item)); + console.log('[资源归属] 映射后的 resOwnerTreeAll =>', this.resOwnerTreeAll); // 默认进入非搜索模式,由懒加载从本地树按需返回节点 this.resOwnerOptions = []; } else if (res && res.result && Array.isArray(res.result.orgTreeList)) { // 兼容没有 status 字段但有 result 的情况 this.resOwnerTreeAll = res.result.orgTreeList.map(item => this.mapOrgToCascaderNode(item)); + console.log('[资源归属] 映射后的 resOwnerTreeAll(无status) =>', this.resOwnerTreeAll); this.resOwnerOptions = []; } else { this.resOwnerTreeAll = []; @@ -1718,6 +1776,9 @@ export default { buildActions(row) { const actions = []; // 优先级按原有展示顺序 + if (row.isPermission && !this.forChoose && row.status == 2) { + actions.push({ key: 'withdraw', label: '撤回', className: 'action-link--primary' }); + } if (row.isPermission && row.status != 2) { actions.push({ key: 'edit', label: '编辑', className: 'action-link--primary' }); } @@ -1727,9 +1788,6 @@ export default { if (row.isPermission && !this.forChoose && row.published) { actions.push({ key: 'manage', label: '管理', className: 'action-link--primary' }); } - if (row.isPermission && !this.forChoose && row.status == 2) { - actions.push({ key: 'withdraw', label: '撤回', className: 'action-link--primary' }); - } if ((row.isPermission && row.status != 2 && !row.published) || (row.isPermission && !row.enabled)) { actions.push({ key: 'delete', label: '删除', className: 'action-link--danger' }); } @@ -1751,6 +1809,35 @@ export default { const actions = this.buildActions(row); return actions.length > 4 ? actions.slice(4) : []; } + , + // 按已选标签宽度动态调整“授课教师”选择框宽度 + adjustTeacherWidth() { + this.$nextTick(() => { + const select = this.$refs.teacherSelect; + this.adjustSelectWidth(select, 200, 800, 60); + }); + }, + // 按已选标签宽度动态调整“创建人”选择框宽度 + adjustCreatorWidth() { + this.$nextTick(() => { + const select = this.$refs.creatorSelect; + this.adjustSelectWidth(select, 200, 800, 60); + }); + }, + // 通用:累加 el-select__tags 内所有 tag 的宽度,设置选择框宽度 + adjustSelectWidth(selectRef, min = 200, max = 800, extra = 0) { + if (!selectRef || !selectRef.$el) return; + const el = selectRef.$el; + const tags = Array.from(el.querySelectorAll('.el-select__tags .el-tag')); + const inputInner = el.querySelector('.el-input__inner'); + const padding = 24; // 预留箭头/输入余量 + const tagsWidth = tags.reduce((sum, tag) => sum + (tag.getBoundingClientRect().width || 0), 0); + const target = Math.max(min, Math.min(tagsWidth + padding + extra, max)); + el.style.width = `${target}px`; + if (inputInner) { + inputInner.style.width = `${target}px`; + } + } } }; @@ -2065,7 +2152,7 @@ export default { color: #000000; margin-left: 4px; .el-input__inner { - width: 28px; + min-width: 28px; height: 28px; background: #F5F9FF; border-radius: 4px; @@ -2149,7 +2236,12 @@ export default { background-color: #4284F7 } - +::v-deep .el-table__empty-block { + width: 100% !important; + display: flex; + justify-content: center; + align-items: center; +} ::v-deep.el-table { border-radius: 6px 6px 0 0; @@ -2292,7 +2384,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &:focus { border-color: #4284F7; } @@ -2309,7 +2401,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; color: #000000; &:focus { border: 1px solid #4284F7; @@ -2341,13 +2433,14 @@ export default { margin-left: 10px; } .el-input__inner { - width: 180px; + min-width: 180px; + width: auto; height: 32px; background: #FFFFFF; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; } .el-input { &.is-focus .el-input__inner{ @@ -2370,7 +2463,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2395,7 +2488,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2420,7 +2513,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2446,7 +2539,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2460,16 +2553,18 @@ export default { .filter-field--resowner { .el-cascader { - width: 180px; + min-width: 200px; + width: auto; line-height: 32px; .el-input__inner { - width: 180px; + min-width: 200px; + width: auto; height: 32px; background: #FFFFFF; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &:focus { border-color: #4284F7; } @@ -2480,31 +2575,35 @@ export default { } .el-input { .el-input__inner { - width: 180px; + min-width: 200px; + width: auto; height: 32px; background: #FFFFFF; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; } } } .filter-field--creator { .el-select { + min-width: 180px; + width: auto; .el-select__input { margin-left: 10px; } .el-input__inner { - width: 136px; + min-width: 180px; + width: auto; height: 32px; background: #FFFFFF; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2520,13 +2619,13 @@ export default { } .el-input__inner { - width: 180px; + width: 136px; height: 32px; background: #FFFFFF; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; &.is-focus .el-input__inner{ border-color: #4284F7; } @@ -2547,7 +2646,7 @@ export default { border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); line-height: 32px; - padding: 0 10px; + padding: 0 15px 0 10px; .el-input__icon { line-height: 32px;