【更新】机构大数据优化(待完善)

This commit is contained in:
xuyuxiang
2026-03-03 00:15:41 +08:00
parent 32d9e60fe1
commit a5f313fd00
27 changed files with 309 additions and 317 deletions

View File

@@ -34,6 +34,7 @@
<a-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
@@ -57,11 +58,7 @@
placeholder="请选择上级组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
selectable="false"
tree-line
/>
@@ -243,6 +240,7 @@
const slots = useSlots()
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id

View File

@@ -27,11 +27,7 @@ XnPageSelect 分页下拉选择器
tree-default-expand-all
:tree-data="treeData"
:tree-default-expanded-keys="treeDefaultExpandedKeys"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
@change="selePositionData(formData.orgId, 0)"
></a-tree-select>
</a-form-item>
@@ -56,6 +52,7 @@ import bizUserApi from '@/api/biz/bizUserApi'
// 表单数据
const formData = ref({})
const xnPositionPageSelectRef = ref()
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 机构选择后查询对应的职位
const selePositionData = (orgId, type) => {

View File

@@ -5,7 +5,7 @@
v-if="props.showSearch"
v-model:value="modelValue"
:options="options"
:field-names="{ label: 'name', value: 'id' }"
:field-names="selectFieldNames"
class="xn-wd"
:placeholder="props.placeholder"
:allow-clear="props.allowClear"
@@ -20,7 +20,7 @@
v-else
v-model:value="modelValue"
:options="options"
:field-names="{ label: 'name', value: 'id' }"
:field-names="selectFieldNames"
class="xn-wd"
:placeholder="props.placeholder"
:allow-clear="props.allowClear"
@@ -40,6 +40,7 @@
const initParams = ref({})
const options = ref([])
const spinning = ref(false)
const selectFieldNames = { label: 'name', value: 'id' }
const emit = defineEmits({ change: null, 'update:value': null, search: null })
const props = defineProps({
value: {

View File

@@ -34,6 +34,7 @@
<a-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
@@ -57,11 +58,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
selectable="false"
tree-line
/>
@@ -239,6 +236,7 @@
const slots = useSlots()
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 获取职位树数据
const treeData = ref()
// 默认展开二级树的节点id

View File

@@ -34,6 +34,7 @@
<a-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
@@ -57,11 +58,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
selectable="false"
tree-line
@change="onCategoryOrOrgIdSelect"
@@ -246,6 +243,7 @@
const slots = useSlots()
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id
@@ -323,7 +321,7 @@
}
}
const openModal = () => {
if (typeof props.orgTreeApi !== 'function' || typeof props.rolePageApi !== 'function') {
if ((typeof props.orgTreeApi !== 'function' && typeof props.orgTreeLazyApi !== 'function') || typeof props.rolePageApi !== 'function') {
message.warning('未配置角色选择器API')
return
}

View File

@@ -41,6 +41,7 @@
<a-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="onLoadData"
@@ -64,11 +65,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
selectable="false"
tree-line
/>
@@ -253,6 +250,7 @@
const selectedTableListLoading = ref(false)
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id

View File

@@ -11,11 +11,7 @@
placeholder="请选择上级字典"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
treeLine
/>
@@ -52,7 +48,7 @@
const treeData = ref([])
// 默认展开的节点(顶级)
const defaultExpandedKeys = ref([0])
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 打开抽屉
const onOpen = (record, parentId) => {
visible.value = true

View File

@@ -22,11 +22,7 @@
placeholder="请选择上级字典"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
selectable="false"
tree-line
/>
@@ -119,6 +115,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'dictLabel', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
// 表格查询 返回 Promise 对象

View File

@@ -10,11 +10,7 @@
allow-clear
tree-default-expand-all
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
tree-line
/>
@@ -48,6 +44,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 选中的ID列表
const ids = ref([])

View File

@@ -15,11 +15,7 @@
placeholder="请选择上级机构"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -71,7 +67,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
placeholder="请选择上级机构"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -184,6 +181,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)

View File

@@ -15,11 +15,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
tree-line
:load-data="onLoadData"
></a-tree-select>
@@ -63,7 +59,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
placeholder="请选择机构"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -175,6 +172,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)

View File

@@ -45,6 +45,7 @@
<a-row :gutter="16">
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择机构:" name="orgId">
<a-spin :spinning="treeLoading">
<a-tree-select
v-model:value="formData.orgId"
class="xn-wd"
@@ -53,15 +54,17 @@
allow-clear
tree-line
:tree-data="treeData"
:tree-default-expanded-keys="treeDefaultExpandedKeys"
v-model:treeExpandedKeys="treeDefaultExpandedKeys"
:field-names="treeFieldNames"
:load-data="onLoadData"
:load-data="isEditMode ? undefined : onLoadData"
@change="selePositionData(formData.orgId, 0)"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择岗位:" name="positionId">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnPositionPageSelectRef"
v-model:value="formData.positionId"
@@ -70,10 +73,12 @@
:page-function="selectApiFunction.positionSelector"
:echo-function="selectApiFunction.echoPosition"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择主管:" name="directorId">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnUserPageSelectRef"
v-model:value="formData.directorId"
@@ -82,6 +87,7 @@
:page-function="selectApiFunction.userSelector"
:echo-function="selectApiFunction.echoUser"
/>
</a-spin>
</a-form-item>
</a-col>
</a-row>
@@ -122,6 +128,7 @@
:name="['positionJson', index, 'orgId']"
:rules="{ required: true, message: '请选择机构' }"
>
<a-spin :spinning="treeLoading">
<a-tree-select
v-model:value="positionInfo.orgId"
class="xn-wd"
@@ -130,11 +137,12 @@
allow-clear
tree-line
:tree-data="treeData"
:tree-default-expanded-keys="treeDefaultExpandedKeys"
v-model:treeExpandedKeys="childTreeExpandedKeys[index]"
:field-names="treeFieldNames"
:load-data="onLoadData"
:load-data="isEditMode ? undefined : onLoadData"
@change="childOrgSelect(positionInfo, 0, index)"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
@@ -142,6 +150,7 @@
:name="['positionJson', index, 'positionId']"
:rules="{ required: true, message: '请选择岗位' }"
>
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnChildPositionPageSelectRef"
v-model:value="positionInfo.positionId"
@@ -150,10 +159,12 @@
:page-function="selectApiFunction.childPositionSelector"
:echo-function="selectApiFunction.echoPosition"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
<a-form-item :name="['positionJson', index, 'directorId']">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnChildUserPageSelectRef"
v-model:value="positionInfo.directorId"
@@ -162,6 +173,7 @@
:page-function="selectApiFunction.childUserSelector"
:echo-function="selectApiFunction.echoUser"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="3" :lg="3" :xl="3">
@@ -308,8 +320,10 @@
const activeTabsKey = ref('1')
const emit = defineEmits({ successful: null })
const formLoading = ref(false)
const treeLoading = ref(false)
const treeData = ref([])
const treeDefaultExpandedKeys = ref([])
const childTreeExpandedKeys = ref([])
// 分页select组件dom定义
const xnPositionPageSelectRef = ref()
const xnUserPageSelectRef = ref()
@@ -319,33 +333,12 @@
const formData = ref({})
const treeFieldNames = { children: 'children', label: 'name', key: 'id', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgsInTree = (orgIds) => {
const missingIds = orgIds.filter((id) => id && !findNodeInTree(treeData.value, id))
if (missingIds.length === 0) return
userCenterApi.userCenterGetOrgListByIdList({ idList: missingIds }).then((data) => {
if (data && data.length > 0) {
data.forEach((org) => {
treeData.value.push({ ...org, isLeaf: true })
})
treeData.value = [...treeData.value]
}
})
}
// 树加载Promise用于协调回显时序
let treeLoadPromise = null
// 是否为编辑模式(编辑时加载全量树,新增时懒加载)
const isEditMode = ref(false)
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
isEditMode.value = !!record
formData.value = {
gender: '男',
positionJson: []
@@ -357,25 +350,42 @@
selePositionData(orgId)
})
}
if (record) {
convertFormData(record)
}
nextTick(() => {
// 机构选择器数据
treeLoadPromise = bizUserApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
if (isEditMode.value) {
// 编辑模式:加载全量树 + 详情,等都完成后展开到选中节点
treeLoading.value = true
const treePromise = bizUserApi.userOrgTreeLazySelector({ searchKey: '' }).then((res) => {
if (res !== null) {
treeData.value = res
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
})
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
}
})
})
const detailPromise = convertFormData(record)
Promise.all([treePromise, detailPromise]).then(() => {
expandToSelectedOrgs()
}).finally(() => {
treeLoading.value = false
})
} else {
// 新增模式:懒加载树
bizUserApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
}
})
}
})
}
// 懒加载子节点
@@ -405,15 +415,51 @@
const onClose = () => {
treeData.value = []
treeDefaultExpandedKeys.value = []
childTreeExpandedKeys.value = []
visible.value = false
}
// 在全量树中查找目标节点的所有祖先ID用于展开树到选中节点
const collectAncestorKeys = (nodes, targetId, path = []) => {
if (!nodes) return null
for (const node of nodes) {
if (node.id === targetId) return path
if (node.children) {
const found = collectAncestorKeys(node.children, targetId, [...path, node.id])
if (found) return found
}
}
return null
}
// 展开树到所有选中的机构节点
const expandToSelectedOrgs = () => {
// 主选择机构只展开主orgId的祖先
if (formData.value.orgId) {
const ancestors = collectAncestorKeys(treeData.value, formData.value.orgId)
if (ancestors) {
ancestors.forEach((id) => {
if (!treeDefaultExpandedKeys.value.includes(id)) {
treeDefaultExpandedKeys.value.push(id)
}
})
}
}
// 任职信息:每行独立展开
if (formData.value.positionJson) {
formData.value.positionJson.forEach((item, index) => {
if (item.orgId) {
const ancestors = collectAncestorKeys(treeData.value, item.orgId)
childTreeExpandedKeys.value[index] = ancestors ? [...ancestors] : []
}
})
}
}
// 回显数据
const convertFormData = (record) => {
const param = {
id: record.id
}
// 查询详情
const detailPromise = bizUserApi.userDetail(param).then((data) => {
return bizUserApi.userDetail(param).then((data) => {
if (data.positionJson) {
// 替换表单中的格式与后端查到的
data.positionJson = JSON.parse(data.positionJson)
@@ -428,24 +474,6 @@
})
}
selePositionData(formData.value.orgId)
return data
})
// 等待树和详情都加载完成后,确保机构节点可回显
nextTick(() => {
if (treeLoadPromise) {
Promise.all([treeLoadPromise, detailPromise]).then(([_, detail]) => {
const orgIds = [detail.orgId]
if (detail.positionJson) {
const positions = typeof detail.positionJson === 'string'
? JSON.parse(detail.positionJson)
: detail.positionJson
positions.forEach((item) => {
if (item.orgId) orgIds.push(item.orgId)
})
}
ensureOrgsInTree(orgIds)
})
}
})
}
// 默认要校验的
@@ -518,10 +546,12 @@
positionId: undefined,
directorId: undefined
})
childTreeExpandedKeys.value.push([])
}
// 删减行
const delDomains = (index) => {
formData.value.positionJson.splice(index, 1)
childTreeExpandedKeys.value.splice(index, 1)
}
// 子表行内选择机构
const childOrgSelect = async (data, type, index) => {

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
placeholder="请选择所属机构"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -173,7 +170,6 @@
<Form ref="formRef" @successful="tableRef.refresh()" />
<xn-role-selector
ref="RoleSelectorPlusRef"
:org-tree-api="selectorApiFunction.orgTreeApi"
:org-tree-lazy-api="selectorApiFunction.orgTreeLazyApi"
:role-page-api="selectorApiFunction.rolePageApi"
:add-show="false"
@@ -247,6 +243,7 @@
const treeData = ref([])
const selectedRowKeys = ref([])
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
const formRef = ref(null)
const RoleSelectorPlusRef = ref()
const selectedRecord = ref({})

View File

@@ -16,11 +16,7 @@
placeholder="请选择上级字典"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
treeLine
/>
@@ -69,6 +65,7 @@
const treeData = ref([])
// 默认展开的节点(顶级)
const defaultExpandedKeys = ref([0])
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 定义字典颜色
const dictColorList = [
'default',

View File

@@ -66,11 +66,7 @@
allow-clear
tree-default-expand-all
:tree-data="menuTreeData"
:field-names="{
children: 'children',
label: 'title',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
tree-line
/>
@@ -192,6 +188,7 @@
const tableList = ref([])
const tableColumns = ref([])
const menuTreeData = ref([])
const treeFieldNames = { children: 'children', label: 'title', value: 'id' }
const submitLoading = ref(false)
const moduleOptions = ref()
const generateTypeOptions = ref([

View File

@@ -19,11 +19,7 @@
allow-clear
tree-default-expand-all
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'title',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
tree-line
@change="parentChange(formData.parentId)"
@@ -111,6 +107,7 @@
// 默认展开的节点(顶级)
const defaultExpandedKeys = ref([0])
const treeData = ref([])
const treeFieldNames = { children: 'children', label: 'title', value: 'id' }
const formData = ref({})
// 类别
const moduleId = ref('')

View File

@@ -10,11 +10,7 @@
allow-clear
tree-default-expand-all
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
tree-line
/>
@@ -48,6 +44,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 选中的ID列表
const ids = ref([])

View File

@@ -15,11 +15,7 @@
placeholder="请选择上级组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -71,7 +67,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
placeholder="请选择上级组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -181,6 +178,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)

View File

@@ -15,11 +15,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
tree-line
:load-data="onLoadData"
></a-tree-select>
@@ -62,7 +58,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -172,6 +169,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 树容器高度自适应
const treeContainerRef = ref(null)
const treeHeight = ref(0)

View File

@@ -30,11 +30,7 @@
allow-clear
tree-default-expand-all
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeFieldNames"
selectable="false"
tree-line
/>
@@ -65,7 +61,7 @@
// 定义机构元素
const treeData = ref([])
const submitLoading = ref(false)
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeFieldNames = { children: 'children', label: 'name', value: 'id' }
// 打开抽屉
const onOpen = (record, category, orgId) => {
visible.value = true

View File

@@ -38,11 +38,7 @@
placeholder="请选择组织"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
@change="onCategoryOrOrgIdSelect"
@@ -216,6 +212,7 @@
const treeData = ref([])
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
// 记录数据
const recordCacheData = ref({})
// 树容器高度自适应

View File

@@ -45,6 +45,7 @@
<a-row :gutter="16">
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择组织:" name="orgId">
<a-spin :spinning="treeLoading">
<a-tree-select
v-model:value="formData.orgId"
class="xn-wd"
@@ -53,15 +54,17 @@
allow-clear
tree-line
:tree-data="treeData"
:tree-default-expanded-keys="treeDefaultExpandedKeys"
v-model:treeExpandedKeys="treeDefaultExpandedKeys"
:field-names="treeFieldNames"
:load-data="onLoadData"
:load-data="isEditMode ? undefined : onLoadData"
@change="selePositionData(formData.orgId, 0)"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择职位:" name="positionId">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnPositionPageSelectRef"
v-model:value="formData.positionId"
@@ -70,10 +73,12 @@
:page-function="selectApiFunction.positionSelector"
:echo-function="selectApiFunction.echoPosition"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-form-item label="选择主管:" name="directorId">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnUserPageSelectRef"
v-model:value="formData.directorId"
@@ -82,6 +87,7 @@
:page-function="selectApiFunction.userSelector"
:echo-function="selectApiFunction.echoUser"
/>
</a-spin>
</a-form-item>
</a-col>
</a-row>
@@ -122,6 +128,7 @@
:name="['positionJson', index, 'orgId']"
:rules="{ required: true, message: '请选择组织' }"
>
<a-spin :spinning="treeLoading">
<a-tree-select
v-model:value="positionInfo.orgId"
class="xn-wd"
@@ -130,11 +137,12 @@
allow-clear
tree-line
:tree-data="treeData"
:tree-default-expanded-keys="treeDefaultExpandedKeys"
v-model:treeExpandedKeys="childTreeExpandedKeys[index]"
:field-names="treeFieldNames"
:load-data="onLoadData"
:load-data="isEditMode ? undefined : onLoadData"
@change="childOrgSelect(positionInfo, 0, index)"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
@@ -142,6 +150,7 @@
:name="['positionJson', index, 'positionId']"
:rules="{ required: true, message: '请选择职位' }"
>
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnChildPositionPageSelectRef"
v-model:value="positionInfo.positionId"
@@ -150,10 +159,12 @@
:page-function="selectApiFunction.childPositionSelector"
:echo-function="selectApiFunction.echoPosition"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
<a-form-item :name="['positionJson', index, 'directorId']">
<a-spin :spinning="treeLoading">
<xn-page-select
ref="xnChildUserPageSelectRef"
v-model:value="positionInfo.directorId"
@@ -162,6 +173,7 @@
:page-function="selectApiFunction.childUserSelector"
:echo-function="selectApiFunction.echoUser"
/>
</a-spin>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="3" :lg="3" :xl="3">
@@ -308,8 +320,10 @@
const activeTabsKey = ref('1')
const emit = defineEmits({ successful: null })
const formLoading = ref(false)
const treeLoading = ref(false)
const treeData = ref([])
const treeDefaultExpandedKeys = ref([])
const childTreeExpandedKeys = ref([])
// 分页select组件dom定义
const xnPositionPageSelectRef = ref()
const xnUserPageSelectRef = ref()
@@ -319,33 +333,12 @@
const formData = ref({})
const treeFieldNames = { children: 'children', label: 'name', key: 'id', value: 'id' }
// 在树中递归查找节点
const findNodeInTree = (nodes, id) => {
if (!nodes) return false
for (const node of nodes) {
if (node.id === id) return true
if (node.children && findNodeInTree(node.children, id)) return true
}
return false
}
// 确保选中的机构节点在树中可回显名称
const ensureOrgsInTree = (orgIds) => {
const missingIds = orgIds.filter((id) => id && !findNodeInTree(treeData.value, id))
if (missingIds.length === 0) return
userCenterApi.userCenterGetOrgListByIdList({ idList: missingIds }).then((data) => {
if (data && data.length > 0) {
data.forEach((org) => {
treeData.value.push({ ...org, isLeaf: true })
})
treeData.value = [...treeData.value]
}
})
}
// 树加载Promise用于协调回显时序
let treeLoadPromise = null
// 是否为编辑模式(编辑时加载全量树,新增时懒加载)
const isEditMode = ref(false)
// 打开抽屉
const onOpen = (record, orgId) => {
visible.value = true
isEditMode.value = !!record
formData.value = {
gender: '男',
positionJson: []
@@ -357,25 +350,42 @@
selePositionData(orgId)
})
}
if (record) {
convertFormData(record)
}
nextTick(() => {
// 机构选择器数据
treeLoadPromise = userApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
if (isEditMode.value) {
// 编辑模式:加载全量树 + 详情,等都完成后展开到选中节点
treeLoading.value = true
const treePromise = userApi.userOrgTreeLazySelector({ searchKey: '' }).then((res) => {
if (res !== null) {
treeData.value = res
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
})
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
}
})
})
const detailPromise = convertFormData(record)
Promise.all([treePromise, detailPromise]).then(() => {
expandToSelectedOrgs()
}).finally(() => {
treeLoading.value = false
})
} else {
// 新增模式:懒加载树
userApi.userOrgTreeLazySelector().then((res) => {
if (res !== null) {
treeData.value = res.map((item) => {
return {
...item,
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
}
})
// 只有一个根节点时才自动展开
if (treeData.value.length === 1) {
treeDefaultExpandedKeys.value.push(treeData.value[0].id)
}
}
})
}
})
}
// 懒加载子节点
@@ -405,15 +415,51 @@
const onClose = () => {
treeData.value = []
treeDefaultExpandedKeys.value = []
childTreeExpandedKeys.value = []
visible.value = false
}
// 在全量树中查找目标节点的所有祖先ID用于展开树到选中节点
const collectAncestorKeys = (nodes, targetId, path = []) => {
if (!nodes) return null
for (const node of nodes) {
if (node.id === targetId) return path
if (node.children) {
const found = collectAncestorKeys(node.children, targetId, [...path, node.id])
if (found) return found
}
}
return null
}
// 展开树到所有选中的机构节点
const expandToSelectedOrgs = () => {
// 主选择组织只展开主orgId的祖先
if (formData.value.orgId) {
const ancestors = collectAncestorKeys(treeData.value, formData.value.orgId)
if (ancestors) {
ancestors.forEach((id) => {
if (!treeDefaultExpandedKeys.value.includes(id)) {
treeDefaultExpandedKeys.value.push(id)
}
})
}
}
// 任职信息:每行独立展开
if (formData.value.positionJson) {
formData.value.positionJson.forEach((item, index) => {
if (item.orgId) {
const ancestors = collectAncestorKeys(treeData.value, item.orgId)
childTreeExpandedKeys.value[index] = ancestors ? [...ancestors] : []
}
})
}
}
// 回显数据
const convertFormData = (record) => {
const param = {
id: record.id
}
// 查询详情
const detailPromise = userApi.userDetail(param).then((data) => {
return userApi.userDetail(param).then((data) => {
if (data.positionJson) {
// 替换表单中的格式与后端查到的
data.positionJson = JSON.parse(data.positionJson)
@@ -428,24 +474,6 @@
})
}
selePositionData(formData.value.orgId)
return data
})
// 等待树和详情都加载完成后,确保机构节点可回显
nextTick(() => {
if (treeLoadPromise) {
Promise.all([treeLoadPromise, detailPromise]).then(([_, detail]) => {
const orgIds = [detail.orgId]
if (detail.positionJson) {
const positions = typeof detail.positionJson === 'string'
? JSON.parse(detail.positionJson)
: detail.positionJson
positions.forEach((item) => {
if (item.orgId) orgIds.push(item.orgId)
})
}
ensureOrgsInTree(orgIds)
})
}
})
}
@@ -519,10 +547,12 @@
positionId: undefined,
directorId: undefined
})
childTreeExpandedKeys.value.push([])
}
// 删减行
const delDomains = (index) => {
formData.value.positionJson.splice(index, 1)
childTreeExpandedKeys.value.splice(index, 1)
}
// 子表行内选择机构

View File

@@ -12,16 +12,17 @@
/>
<div style="flex: 1; overflow: hidden">
<xn-tree-skeleton v-if="treeLoading && treeData.length === 0" />
<a-tree
v-else-if="treeData.length > 0"
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
<a-spin v-else-if="treeData.length > 0" :spinning="treeLoading">
<a-tree
v-model:expandedKeys="defaultExpandedKeys"
:show-line="{ showLeafIcon: false }"
:tree-data="treeData"
:field-names="treeFieldNames"
:load-data="searchMode ? undefined : onLoadData"
:height="treeHeight"
@select="treeSelect"
/>
</a-spin>
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
@@ -38,11 +39,7 @@
:placeholder="$t('user.placeholderUserOrg')"
allow-clear
:tree-data="treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id'
}"
:field-names="treeSelectFieldNames"
tree-line
:load-data="onLoadData"
/>
@@ -187,7 +184,7 @@
<Form ref="formRef" @successful="tableRef.refresh()" />
<xn-role-selector
ref="RoleSelectorPlusRef"
:org-tree-api="selectorApiFunction.orgTreeApi"
:org-tree-lazy-api="selectorApiFunction.orgTreeLazyApi"
:role-page-api="selectorApiFunction.rolePageApi"
:add-show="false"
:show="false"
@@ -265,6 +262,7 @@
const treeData = ref([])
const selectedRowKeys = ref([])
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
const treeSelectFieldNames = { children: 'children', label: 'name', value: 'id' }
const formRef = ref(null)
const RoleSelectorPlusRef = ref()
const selectedRecord = ref({})