feat: 新增获取全量机构树的API,优化SignupModal和ManageListRemote组件的组织树交互逻辑

This commit is contained in:
huweihang
2025-12-17 18:48:29 +08:00
parent e176769174
commit a9b36884c3
3 changed files with 153 additions and 65 deletions

View File

@@ -46,6 +46,14 @@ const getUserInfoById = function(id) {
return ajax.postJson(baseURL,'/user/list',{id}); return ajax.postJson(baseURL,'/user/list',{id});
} }
/**
* 获取全量机构树
* GET /userbasic/organization/all/tree
*/
const getAllOrgTree = function () {
return ajax.get(baseURL, '/organization/all/tree');
};
/** /**
* https://u-pre.boe.com/userbasic/audience/userAudiences * https://u-pre.boe.com/userbasic/audience/userAudiences
* 【当前代码中未查询到】获取当前用户受众信息 * 【当前代码中未查询到】获取当前用户受众信息
@@ -103,14 +111,6 @@ const getUsersByIds = function(ids) {
return ajax.postJson(baseURL,'/user/getUserMessageToDai',ids); return ajax.postJson(baseURL,'/user/getUserMessageToDai',ids);
} }
/**
* 根据关键字检索用户(创建人下拉)
* @param {string} keyword
*/
const selectUser = function(keyword = '') {
return ajax.postJson(baseURL,'/user/selectuser',{ keyword });
}
export default { export default {
userParentOrg, userParentOrg,
findOrgsByKeyword, findOrgsByKeyword,
@@ -125,5 +125,5 @@ export default {
getUsersByIds, getUsersByIds,
updateUser, updateUser,
logout, logout,
selectUser getAllOrgTree
} }

View File

@@ -77,7 +77,14 @@
</el-button> </el-button>
<el-button size="small" @click="resetOrg">重置</el-button> <el-button size="small" @click="resetOrg">重置</el-button>
</div> </div>
<el-tree class="org-tree" show-checkbox node-key="id" :data="orgList" :props="orgTreeProps" lazy <el-tree
ref="orgTree"
class="org-tree"
show-checkbox
node-key="id"
:data="orgList"
:props="orgTreeProps"
lazy
:load="loadOrgNodeFull" @check-change="onOrgCheckChange" :default-checked-keys="selectedOrgKeys" /> :load="loadOrgNodeFull" @check-change="onOrgCheckChange" :default-checked-keys="selectedOrgKeys" />
</el-tab-pane> </el-tab-pane>
@@ -573,13 +580,25 @@ export default {
this.stuSelectRows = this.stuSelectRows.filter( this.stuSelectRows = this.stuSelectRows.filter(
(i) => (i.id || i.userId) !== (item.id || item.userId) (i) => (i.id || i.userId) !== (item.id || item.userId)
); );
// 同步左侧快速选人表格的勾选状态
this.$nextTick(() => {
this.syncStuTableSelection();
});
}, },
removeOrg(item) { removeOrg(item) {
this.deptList = this.deptList.filter((i) => i.id !== item.id); this.deptList = this.deptList.filter((i) => i.id !== item.id);
this.selectedOrgKeys = this.deptList.map((d) => d.id); this.selectedOrgKeys = this.deptList.map((d) => d.id);
// 同步左侧组织树的勾选状态
if (this.$refs.orgTree && this.$refs.orgTree.setCheckedKeys) {
this.$refs.orgTree.setCheckedKeys(this.selectedOrgKeys);
}
}, },
removeAudience(item) { removeAudience(item) {
this.auditSelectRows = this.auditSelectRows.filter((i) => i.id !== item.id); this.auditSelectRows = this.auditSelectRows.filter((i) => i.id !== item.id);
// 同步左侧受众表格的勾选状态
this.$nextTick(() => {
this.syncAudienceTableSelection();
});
}, },
submitAuth() { submitAuth() {
if (this.type === 2) { if (this.type === 2) {

View File

@@ -100,9 +100,12 @@
placeholder="资源归属" placeholder="资源归属"
clearable clearable
:props="resOwnerCascaderProps" :props="resOwnerCascaderProps"
:options="resOwnerOptions"
:show-all-levels="false" :show-all-levels="false"
@change="handleResOwnerChange" @change="handleResOwnerChange"
@input.native="limitResOwnerInput" @input.native="limitResOwnerInput"
@clear="handleResOwnerClear"
@visible-change="handleResOwnerVisibleChange"
filterable filterable
:filter-method="resOwnerFilterMethod" :filter-method="resOwnerFilterMethod"
></el-cascader> ></el-cascader>
@@ -227,7 +230,7 @@
<span class="common-cell common-cell-right">{{ scope.row.openCourse == 1 ? '是' : '否' }}</span> <span class="common-cell common-cell-right">{{ scope.row.openCourse == 1 ? '是' : '否' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="资源归属" prop="orgName" min-width="220" align="center" sortable="custom"> <el-table-column label="资源归属" prop="orgName" min-width="220" align="center" sortable="custom" show-overflow-tooltip>
<template slot-scope="scope"> <template slot-scope="scope">
<el-tooltip :content="scope.row.orgFullName || scope.row.orgName" placement="top" effect="dark"> <el-tooltip :content="scope.row.orgFullName || scope.row.orgName" placement="top" effect="dark">
<span class="common-cell common-cell-right">{{ scope.row.orgName }}</span> <span class="common-cell common-cell-right">{{ scope.row.orgName }}</span>
@@ -464,14 +467,23 @@ export default {
computed: { computed: {
...mapGetters(['resOwnerMap', 'sysTypeMap', 'userInfo', 'identity']), ...mapGetters(['resOwnerMap', 'sysTypeMap', 'userInfo', 'identity']),
resOwnerCascaderProps() { resOwnerCascaderProps() {
// 搜索模式:关闭懒加载,直接使用全量 options
if (this.resOwnerSearchMode) {
return {
value: 'id',
label: 'name',
children: 'children',
checkStrictly: true, // 允许选择任意一级选项
};
}
// 非搜索模式:开启前端“伪懒加载”,数据从本地缓存树中按需取
return { return {
value: 'id', value: 'id',
label: 'name', label: 'name',
children: 'children', children: 'children',
lazy: true, lazy: true,
lazyLoad: this.loadResOwnerNode, lazyLoad: this.loadResOwnerNodeFromCache,
leaf: 'leaf', checkStrictly: true, // 允许选择任意一级选项
checkStrictly: true // 允许选择任意一级选项
}; };
}, },
// 动态计算“授课教师”列的最小宽度,避免固定 260px // 动态计算“授课教师”列的最小宽度,避免固定 260px
@@ -496,7 +508,11 @@ export default {
sysTypeList: [], sysTypeList: [],
resOwnerListMap: [], resOwnerListMap: [],
resOwnerSelected: [], resOwnerSelected: [],
resOwnerCascaderOptions: [], // 资源归属全量树 & 级联选项
resOwnerTreeAll: [],
resOwnerOptions: [],
// 资源归属搜索模式开关(有关键字时为 true
resOwnerSearchMode: false,
showSetTopFeature: false, showSetTopFeature: false,
page: { page: {
pageIndex: 1,//第几页 pageIndex: 1,//第几页
@@ -628,6 +644,8 @@ export default {
//已经加载tree的情况下不需要再单独的加载一次 //已经加载tree的情况下不需要再单独的加载一次
this.loadResOwners(); this.loadResOwners();
this.loadSysTypes(); this.loadSysTypes();
// 加载资源归属全量树,供级联和搜索使用
this.loadAllResOwnerTree();
document.querySelector('#app').style.overflowX = 'hidden'; document.querySelector('#app').style.overflowX = 'hidden';
this.applyAppScrollbarStyle(); this.applyAppScrollbarStyle();
@@ -838,8 +856,36 @@ export default {
if (event && event.target && event.target.value !== limited) { if (event && event.target && event.target.value !== limited) {
event.target.value = limited; event.target.value = limited;
} }
if (this.$refs.resOwnerCascader) { const keyword = (limited || '').trim();
this.$refs.resOwnerCascader.inputValue = limited; // 根据是否有关键字切换搜索模式
this.resOwnerSearchMode = !!keyword;
if (this.resOwnerSearchMode) {
// 搜索模式:使用全量树作为 options交给 filter-method 过滤
this.resOwnerOptions = this.resOwnerTreeAll;
} else {
// 非搜索模式:清空 options交给 lazyLoad 从缓存树按需“懒加载”
this.resOwnerOptions = [];
}
},
// 清空选择时,恢复到懒加载模式
handleResOwnerClear() {
this.resOwnerSearchMode = false;
this.resOwnerOptions = [];
// 清空输入框的文字,避免残留关键字
this.$nextTick(() => {
if (this.$refs.resOwnerCascader && this.$refs.resOwnerCascader.inputValue !== undefined) {
this.$refs.resOwnerCascader.inputValue = '';
}
});
},
// 下拉面板打开时,如果当前没有关键字,也确保是懒加载模式
handleResOwnerVisibleChange(visible) {
if (!visible) return;
// 如果没有关键字,就强制回到懒加载模式
const keyword = (this.$refs.resOwnerCascader && this.$refs.resOwnerCascader.inputValue) || '';
if (!keyword) {
this.resOwnerSearchMode = false;
this.resOwnerOptions = [];
} }
}, },
resOwnerFilterMethod(node, keyword) { resOwnerFilterMethod(node, keyword) {
@@ -1460,59 +1506,49 @@ export default {
sessionStorage.setItem('courseDetail', JSON.stringify(row)); sessionStorage.setItem('courseDetail', JSON.stringify(row));
this.$router.push({ path: '/iframe/course/coursemanage-remote' }); this.$router.push({ path: '/iframe/course/coursemanage-remote' });
}, },
async loadResOwnerNode(node, resolve) { // 前端“伪懒加载”:根据本地全量机构树按需返回子节点
const { level } = node; // 为了让 el-cascader 的 loading 动画有可见效果,这里故意加入一个很小的延时再 resolve
let parentId = ''; async loadResOwnerNodeFromCache(node, resolve) {
try {
if (level === 0) { const delayResolve = (nodes) => {
// 第一层:获取根节点 // 适当延时 150ms让 loading 动画可见,但又不会让用户感觉明显卡顿
try { setTimeout(() => resolve(nodes), 150);
const res = await apiUserbasic.findOrgsByKeyword(''); };
if (res && res.result) {
const nodes = res.result.map(item => ({ // 根节点level === 0返回所有一级机构
id: item.id, if (node.level === 0) {
name: item.name, return delayResolve(this.resOwnerTreeAll || []);
hrbpId: item.hrbpId,
leaf: false, // 懒加载模式下,先假设所有节点都可能有子节点
children: [] // 空数组表示需要懒加载
}));
resolve(nodes);
} else {
resolve([]);
}
} catch (error) {
console.error('加载资源归属失败:', error);
resolve([]);
} }
} else {
// 子节点根据父节点ID加载 // 其他层级:根据当前节点 id 在树中查找对应节点,再返回其 children
parentId = node.value || (node.data && node.data.id); const currentId = node.value || (node.data && node.data.id);
if (!parentId) { if (!currentId) {
resolve([]); return delayResolve([]);
return;
} }
try {
const res = await apiUserbasic.getOrgInfo(parentId); const findNodeById = (list = [], id) => {
if (res && res.status === 200 && res.result) { for (let i = 0; i < list.length; i++) {
let treeList = []; const item = list[i];
if (res.result.directChildList && res.result.directChildList.length > 0) { if (item.id === id) {
treeList = res.result.directChildList.map(item => ({ return item;
id: item.id, }
name: item.name, if (Array.isArray(item.children) && item.children.length > 0) {
hrbpId: item.hrbpId, const found = findNodeById(item.children, id);
leaf: false, // 懒加载模式下,先假设所有节点都可能有子节点 if (found) return found;
children: [] // 空数组表示需要懒加载
}));
} }
resolve(treeList);
} else {
// 如果没有子节点,返回空数组,级联选择器会自动将其视为叶子节点
resolve([]);
} }
} catch (error) { return null;
console.error('加载资源归属子节点失败:', error); };
resolve([]);
const target = findNodeById(this.resOwnerTreeAll || [], currentId);
if (target && Array.isArray(target.children)) {
delayResolve(target.children);
} else {
delayResolve([]);
} }
} catch (e) {
console.error('本地懒加载资源归属节点失败:', e);
resolve([]);
} }
}, },
handleResOwnerChange(value) { handleResOwnerChange(value) {
@@ -1536,6 +1572,39 @@ export default {
} }
}); });
}, },
// 将后端机构节点转换为级联组件所需结构
mapOrgToCascaderNode(node = {}) {
const children = Array.isArray(node.childList)
? node.childList.map(child => this.mapOrgToCascaderNode(child))
: [];
return {
id: node.organizationId,
name: node.orgName,
children,
};
},
// 加载资源归属全量机构树
async loadAllResOwnerTree() {
try {
const res = await apiUserbasic.getAllOrgTree();
if (res && res.status === 200 && res.result && Array.isArray(res.result.orgTreeList)) {
this.resOwnerTreeAll = res.result.orgTreeList.map(item => this.mapOrgToCascaderNode(item));
// 默认进入非搜索模式,由懒加载从本地树按需返回节点
this.resOwnerOptions = [];
} else if (res && res.result && Array.isArray(res.result.orgTreeList)) {
// 兼容没有 status 字段但有 result 的情况
this.resOwnerTreeAll = res.result.orgTreeList.map(item => this.mapOrgToCascaderNode(item));
this.resOwnerOptions = [];
} else {
this.resOwnerTreeAll = [];
this.resOwnerOptions = [];
}
} catch (error) {
console.error('加载资源归属全量树失败:', error);
this.resOwnerTreeAll = [];
this.resOwnerOptions = [];
}
},
showChooseCourse() { showChooseCourse() {
this.courseChooseShow = true; this.courseChooseShow = true;
}, },