From fa98817aebd7a957667fd0e3a02418ceb9f1d1b2 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Sun, 14 Sep 2025 10:37:05 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90v3.8.3=E3=80=91=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E5=A4=A7=E6=94=B9=E9=80=A0=E5=85=B3=E8=81=94=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jeecgboot-vue3/src/api/common/api.ts | 28 +++ .../Form/src/jeecg/hooks/useTreeBiz.ts | 173 +++++++++++++++++- .../components/Form/src/jeecg/props/props.ts | 6 +- .../views/demo/jeecg/jeecgComponents.data.ts | 35 ++++ .../src/views/system/address/address.api.ts | 2 +- .../src/views/system/address/address.data.ts | 7 +- .../address/components/DepartLeftTree.vue | 9 +- .../src/views/system/address/index.vue | 7 +- .../system/departUser/depart.user.data.ts | 2 + .../views/system/position/PositionModal.vue | 7 +- .../system/position/TenantPositionList.vue | 133 ++++++++++++++ .../src/views/system/position/index.vue | 4 + .../views/system/position/position.data.ts | 41 ++--- 13 files changed, 422 insertions(+), 32 deletions(-) create mode 100644 jeecgboot-vue3/src/views/system/position/TenantPositionList.vue diff --git a/jeecgboot-vue3/src/api/common/api.ts b/jeecgboot-vue3/src/api/common/api.ts index 8ef60e03e..69eacadb7 100644 --- a/jeecgboot-vue3/src/api/common/api.ts +++ b/jeecgboot-vue3/src/api/common/api.ts @@ -16,6 +16,12 @@ enum Api { getCategoryData = '/sys/category/loadAllData', refreshDragCache = '/drag/page/refreshCache', refreshDefaultIndexCache = '/sys/sysRoleIndex/cleanDefaultIndexCache', + //异步获取部门和岗位 + queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync', + //查询部门岗位下的用户 + queryDepartPostUserPageList = '/sys/user/queryDepartPostUserPageList', + //查询所选部门的所有父节点ID + queryAllParentId = '/sys/sysDepart/queryAllParentId', } /** @@ -53,6 +59,13 @@ export const getRoleList = (params) => { export const queryDepartTreeSync = (params?) => { return defHttp.get({ url: Api.queryDepartTreeSync, params }); }; +/** + * 异步获取部门职位树列表 + */ +export const queryDepartAndPostTreeSync = (params?) => { + return defHttp.get({ url: Api.queryDepartAndPostTreeSync, params }); +}; + /** * 获取部门树列表 */ @@ -86,6 +99,21 @@ export const getDictItems = (dictCode) => { export const getTableList = (params) => { return defHttp.get({ url: Api.getTableList, params }); }; + +/** + * 部门岗位用户modal【查询部门岗位下的用户】 + */ +export const queryDepartPostUserPageList = (params) => { + return defHttp.get({ url: Api.queryDepartPostUserPageList, params }); +}; + +/** + * 查询所选部门的所有父节点ID + */ +export const queryAllParentId = (params) => { + return defHttp.get({ url: Api.queryAllParentId, params }); +}; + /** * 加载全部分类字典数据 */ diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useTreeBiz.ts b/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useTreeBiz.ts index ee8da4a64..8424623e2 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useTreeBiz.ts +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useTreeBiz.ts @@ -3,6 +3,8 @@ import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue'; import { TreeActionType } from '/@/components/Tree'; import { listToTree } from '/@/utils/common/compUtils'; import { isEqual } from 'lodash-es'; +import { defHttp } from "@/utils/http/axios"; +import { queryAllParentId } from "/@/api/common/api"; export function useTreeBiz(treeRef, getList, props, realProps, emit) { //接收下拉框选项 @@ -24,6 +26,10 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { // 是否是首次加载回显,只有首次加载,才会显示 loading let isFirstLoadEcho = true; let prevSelectValues = []; + // 需要展开的父节点ID列表 + const expandedKeys = ref>([]); + // 是否启用自动展开功能(可以通过props控制) + const enableAutoExpand = props.enableAutoExpand !== false; /** * 监听selectValues变化 */ @@ -55,11 +61,49 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { function getTree() { const tree = unref(treeRef); if (!tree) { - throw new Error('tree is null!'); + //throw new Error('tree is null!'); + return null; } return tree; } + /** + * 获取需要展开的父节点ID + */ + async function getParentIdsToExpand(selectedIds) { + if (!selectedIds || selectedIds.length === 0) return []; + + try { + const result = await queryAllParentId({ + departId: selectedIds.join(','), + orgCode: props.params?.orgCode + }); + + if (result) { + const allParentIds = []; + // 处理 Map 或 Object 结构 + const valuesToProcess = result instanceof Map + ? Array.from(result.values()) + : Object.values(result); + + // 遍历所有选中节点的父节点 + valuesToProcess.forEach((nodeData: any) => { + if (nodeData && nodeData.parentIds && Array.isArray(nodeData.parentIds)) { + // 添加父节点ID(不包含选中节点本身) + const parentIds = nodeData.parentIds.filter(id => !selectedIds.includes(id)); + allParentIds.push(...parentIds); + } + }); + + return [...new Set(allParentIds)]; // 去重 + } + return []; + } catch (error) { + console.warn('获取父节点ID失败:', error); + return []; + } + } + /** * 设置树展开级别 */ @@ -70,7 +114,19 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { } //设置列表默认选中 checkedKeys.value = selectValues['value']; - }).then(); + + // 如果有需要展开的父节点,则展开它们 + if (expandedKeys.value.length > 0) { + getTree().setExpandedKeys(expandedKeys.value); + } + }).then(() => { + // 再次确保展开,因为树可能还没有完全渲染 + if (expandedKeys.value.length > 0) { + setTimeout(() => { + getTree().setExpandedKeys(expandedKeys.value); + }, 100); + } + }); } /** @@ -171,8 +227,20 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { startPid = ''; params['ids'] = ids; } + + if(props.params?.departIds){ + params['departIds'] = props.params.departIds; + } let record = await getList(params); let optionData = record; + //只展示公司信息(公司+子公司) + if(props.onlyShowCompany){ + record = getCompanyData(record) + } + //是否只选择部门岗位 + if (props.izOnlySelectDepartPost) { + setCompanyDepartCheckable(record); + } if (!props.serverTreeData) { //前端处理数据为tree结构 record = listToTree(record, props, startPid); @@ -212,6 +280,14 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { } } + /** + * 获取到公司/子公司数据 + * @param record + */ + function getCompanyData(record){ + const companyData = record.filter(item=>item.orgCategory && ['1','4'].includes(item.orgCategory)); + return companyData + } /** * 异步加载时检测是否含有下级节点 * @param pid 父节点 @@ -256,6 +332,57 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { //弹出框打开时加载全部数据 openModal.value = true; await onLoadData(null, null); + + // 在数据加载完成后,如果有选中的值且启用了自动展开功能,则展开父节点 + if (enableAutoExpand && selectValues.value && selectValues.value.length > 0) { + try { + const selectedIds = selectValues.value; + const parentIds = await getParentIdsToExpand(selectedIds); + + if (parentIds.length > 0) { + expandedKeys.value = parentIds; + + // 延迟展开,确保树已经渲染完成 + nextTick(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + + // 再次确保展开 + setTimeout(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + console.log('父节点已展开:', parentIds); + // 第三次确保展开,使用更长的延迟 + setTimeout(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }, 500); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }, 200); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }); + + } + } catch (error) { + console.warn('获取父节点ID失败:', error); + } + } } else { openModal.value = false; // update-begin--author:liaozhiyang---date:20240527---for:【TV360X-414】部门设置了默认值,查询重置变成空了(同步JSelectUser组件改法) @@ -264,6 +391,46 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { } } + /** + * 设置公司部门复选框显示 + * @param record + */ + function setCompanyDepartCheckable(record) { + if (record && record.length > 0) { + for (const item of record) { + if (item.orgCategory !== '3') { + item.checkable = false; + item.selectable = false; + } else { + item.checkable = true; + item.selectable = true; + } + if (item.isLeaf) { + setCompanyDepartCheckable(item.children); + } + } + } + } + + /** + * 岗位搜索 + * + * @param value + */ + async function onSearch(value) { + if(value){ + let result = await defHttp.get({ url: "/sys/sysDepart/searchBy", params: { keyWord: value, orgCategory: "3",...props.params } }); + if (Array.isArray(result)) { + treeData.value = result; + } else { + treeData.value = []; + } + } else { + treeData.value = []; + await onLoadData(null, null) + } + } + return [ { visibleChange, @@ -279,6 +446,8 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) { treeData, getCheckStrictly, getSelectTreeData, + onSearch, + expandedKeys, }, ]; } diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/props/props.ts b/jeecgboot-vue3/src/components/Form/src/jeecg/props/props.ts index 3a5fa792b..e671c8e47 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/props/props.ts +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/props/props.ts @@ -50,7 +50,7 @@ export const treeProps = { //初始展开的层级 defaultExpandLevel: { type: [Number], - default: 0, + default: 1, }, //根pid值 startPid: { @@ -78,10 +78,14 @@ export const treeProps = { sync: propTypes.bool.def(true), //是否显示选择按钮 showButton: propTypes.bool.def(true), + //是否只显示公司 + onlyShowCompany: propTypes.bool.def(false), //是否显示复选框 checkable: propTypes.bool.def(true), //checkable 状态下节点选择完全受控(父子节点选中状态不再关联) checkStrictly: propTypes.bool.def(false), // 是否允许多选,默认 true multiple: propTypes.bool.def(true), + // 是否只选择岗位 + izOnlySelectDepartPost: propTypes.bool.def(false), }; diff --git a/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts b/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts index 3d9930396..d9c215ef4 100644 --- a/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts +++ b/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts @@ -219,6 +219,22 @@ export const schemas: FormSchema[] = [ label: '选中部门', colProps: { span: 12 }, }, + { + field: 'depart4', + component: 'JSelectDepartPost', + label: '选择岗位', + helpMessage: ['component模式'], + componentProps: { showButton: false }, + colProps: { + span: 12, + }, + }, + { + field: 'depart4', + component: 'JEllipsis', + label: '选择岗位', + colProps: { span: 12 }, + }, { field: 'user2', component: 'JSelectUser', @@ -258,6 +274,25 @@ export const schemas: FormSchema[] = [ label: '选中用户', colProps: { span: 12 }, }, + { + field: 'userPost1', + component: 'JSelectUserByDeptPost', + label: '部门岗位选择用户', + helpMessage: ['component模式'], + componentProps: { + labelKey: 'realname', + rowKey: 'username', + }, + colProps: { + span: 12, + }, + }, + { + field: 'userPost1', + component: 'JEllipsis', + label: '选中用户', + colProps: { span: 12 }, + }, { field: 'user4', component: 'JSelectUserByDepartment', diff --git a/jeecgboot-vue3/src/views/system/address/address.api.ts b/jeecgboot-vue3/src/views/system/address/address.api.ts index 4890d3434..1172d9bf1 100644 --- a/jeecgboot-vue3/src/views/system/address/address.api.ts +++ b/jeecgboot-vue3/src/views/system/address/address.api.ts @@ -3,7 +3,7 @@ import { defHttp } from '/@/utils/http/axios'; export enum Api { list = '/sys/user/queryByOrgCodeForAddressList', positionList = '/sys/position/list', - queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync', + queryDepartTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync', } /** * 获取部门树列表 diff --git a/jeecgboot-vue3/src/views/system/address/address.data.ts b/jeecgboot-vue3/src/views/system/address/address.data.ts index ddec3a863..c8c1e99a5 100644 --- a/jeecgboot-vue3/src/views/system/address/address.data.ts +++ b/jeecgboot-vue3/src/views/system/address/address.data.ts @@ -14,7 +14,12 @@ export const columns: BasicColumn[] = [ }, { title: '部门', - dataIndex: 'departName', + dataIndex: 'orgCodeTxt', + width: 200, + }, + { + title: '主岗位', + dataIndex: 'mainDepPostId_dictText', width: 200, }, { diff --git a/jeecgboot-vue3/src/views/system/address/components/DepartLeftTree.vue b/jeecgboot-vue3/src/views/system/address/components/DepartLeftTree.vue index 11ede6231..8bc77523e 100644 --- a/jeecgboot-vue3/src/views/system/address/components/DepartLeftTree.vue +++ b/jeecgboot-vue3/src/views/system/address/components/DepartLeftTree.vue @@ -14,7 +14,11 @@ :load-data="loadChildrenTreeData" v-model:expandedKeys="expandedKeys" @select="onSelect" - > + > + + @@ -26,6 +30,7 @@ import { queryDepartTreeSync } from '../address.api'; import { searchByKeywords } from '/@/views/system/departUser/depart.user.api'; import { Popconfirm } from 'ant-design-vue'; + import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue"; const prefixCls = inject('prefixCls'); // 定义props @@ -142,7 +147,7 @@ try { loading.value = true; treeData.value = []; - let result = await searchByKeywords({ keyWord: value }); + let result = await searchByKeywords({ keyWord: value, orgCategory: '1,2,3,4' }); if (Array.isArray(result)) { treeData.value = result; } diff --git a/jeecgboot-vue3/src/views/system/address/index.vue b/jeecgboot-vue3/src/views/system/address/index.vue index 6e41a6313..e26ed79c8 100644 --- a/jeecgboot-vue3/src/views/system/address/index.vue +++ b/jeecgboot-vue3/src/views/system/address/index.vue @@ -25,10 +25,12 @@ import { provide, ref, unref } from 'vue'; import { useDesign } from '/@/hooks/web/useDesign'; import DepartLeftTree from './components/DepartLeftTree.vue'; - import { BasicTable } from '/@/components/Table'; + import { BasicTable, TableAction } from '/@/components/Table'; import { useListPage } from '/@/hooks/system/useListPage'; import { columns, searchFormSchema } from './address.data'; import { list, positionList } from './address.api'; + import { getCacheByDynKey } from "@/utils/auth"; + import { JEECG_CHAT_UID } from "@/enums/cacheEnum"; const { prefixCls } = useDesign('address-list'); provide('prefixCls', prefixCls); @@ -46,14 +48,13 @@ api: list, columns, //update-begin---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错------------ - rowKey: 'userId', + rowKey: 'id', //update-end---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错-------------- showIndexColumn: true, formConfig: { schemas: searchFormSchema, }, canResize: false, - actionColumn: null, showTableSetting: false, // 请求之前对参数做处理 beforeFetch(params) { diff --git a/jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts b/jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts index 880fc180e..79e78e884 100644 --- a/jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts +++ b/jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts @@ -168,6 +168,8 @@ export function useBaseInfoForm(treeData: Ref) { return '部门'; } else if (val === '3') { return '岗位'; + } else if(val === '4'){ + return '子公司'; } return val; }, diff --git a/jeecgboot-vue3/src/views/system/position/PositionModal.vue b/jeecgboot-vue3/src/views/system/position/PositionModal.vue index 86ab21a1a..4f7a55f4a 100644 --- a/jeecgboot-vue3/src/views/system/position/PositionModal.vue +++ b/jeecgboot-vue3/src/views/system/position/PositionModal.vue @@ -34,7 +34,7 @@ } }); //设置标题 - const getTitle = computed(() => (!unref(isUpdate) ? '新增职务' : '编辑职务')); + const getTitle = computed(() => (!unref(isUpdate) ? '新增职务级别' : '编辑职务级别')); //表单提交事件 async function handleSubmit() { try { @@ -51,3 +51,8 @@ } } + \ No newline at end of file diff --git a/jeecgboot-vue3/src/views/system/position/TenantPositionList.vue b/jeecgboot-vue3/src/views/system/position/TenantPositionList.vue new file mode 100644 index 000000000..8562cb5fc --- /dev/null +++ b/jeecgboot-vue3/src/views/system/position/TenantPositionList.vue @@ -0,0 +1,133 @@ + + diff --git a/jeecgboot-vue3/src/views/system/position/index.vue b/jeecgboot-vue3/src/views/system/position/index.vue index 030bad6e9..14a85d444 100644 --- a/jeecgboot-vue3/src/views/system/position/index.vue +++ b/jeecgboot-vue3/src/views/system/position/index.vue @@ -53,6 +53,10 @@ width: 180, }, showIndexColumn: true, + defSort: { + column: "", + order: "" + } }, exportConfig: { name: '职务列表', diff --git a/jeecgboot-vue3/src/views/system/position/position.data.ts b/jeecgboot-vue3/src/views/system/position/position.data.ts index abbc9761f..bf9e2413c 100644 --- a/jeecgboot-vue3/src/views/system/position/position.data.ts +++ b/jeecgboot-vue3/src/views/system/position/position.data.ts @@ -9,22 +9,21 @@ export const columns: BasicColumn[] = [ // align: 'left', // }, { - title: '职务名称', + title: '职务级别名称', dataIndex: 'name', align: 'left' // width: 200, }, - // { - // title: '职务等级', - // dataIndex: 'postRank_dictText', - // width: 100, - // }, + { + title: '职务级别(越小级别越高)', + dataIndex: 'postLevel', + }, ]; export const searchFormSchema: FormSchema[] = [ { field: 'name', - label: '职务名称', + label: '职务级别名称', component: 'Input', colProps: { span: 8 }, }, @@ -37,25 +36,25 @@ export const formSchema: FormSchema[] = [ component: 'Input', show: false, }, - // { - // label: '职级', - // field: 'postRank', - // component: 'JDictSelectTag', - // required: true, - // componentProps: { - // dictCode: 'position_rank', - // dropdownStyle: { - // maxHeight: '100vh', - // }, - // getPopupContainer: () => document.body, - // }, - // }, { field: 'name', - label: '职务名称', + label: '职务级别名称', component: 'Input', required: true, }, + { + label: '职务级别', + field: 'postLevel', + component: 'InputNumber', + required: true, + componentProps: { + min: 1, + max: 99 + }, + dynamicRules: ({ model, schema }) => { + return [{ required: true, message: '请输入职务级别!' }]; + }, + }, // { // field: 'code', // label: '职务编码',