refactor(organization): 将所属组织搜索组件统一替换为级联选择器

- 全站搜索栏所属组织由树形下拉 (el-tree-select) 统一替换为级联选择器 (el-cascader)
- 修改多个视图和组件中所属组织相关表单与搜索逻辑,适配级联选择器数据结构
- 组织数据结构调整及新增 findOrgPath、findDeptPath 等方法支持路径反查与回显
- 表单模型与搜索参数中所属组织字段改用 ID 路径数组,提交时转换为名称
- 优化所属组织搜索UI,统一清除、过滤、提示等功能样式和行为
- 引入 organization-search mixin 支持组织数据加载、路径转换及搜索参数格式化
- 更新相关依赖组件 Element Plus 引入 ElCascader 替换 ElTreeSelect
- 修正搜索表单CSS,新增el-cascader全宽样式支持搜索栏布局一致性
- 兼容老数据,支持多种部门ID格式自动转换到单选路径数组格式
- 业务部门与承办部门表单同样应用级联选择器及路径转换逻辑
- 相关搜索配置项所属组织字段类型由 input 改为 cascader,提升搜索交互体验
This commit is contained in:
2026-08-31 12:19:18 +08:00
parent 3a08e12c65
commit 9b9e652afd
26 changed files with 515 additions and 192 deletions
+64 -23
View File
@@ -166,33 +166,25 @@
/>
</el-form-item>
<el-form-item label="业务部门" prop="businessDeptId">
<el-tree-select
v-model="form.businessDeptId"
:data="businessDeptTreeOptions"
:props="deptTreeSelectProps"
node-key="value"
<el-cascader
v-model="businessDeptCascaderValue"
:options="businessDeptTreeOptions"
:props="deptCascaderProps"
placeholder="请选择业务部门"
check-strictly
filterable
clearable
:render-after-expand="false"
:disabled="isBasicInfoReadonly"
@change="handleBusinessDeptChange"
/>
</el-form-item>
<el-form-item label="承办部门" prop="undertakeDeptId">
<el-tree-select
v-model="form.undertakeDeptId"
:data="deptTreeOptions"
:props="deptTreeSelectProps"
node-key="value"
<el-cascader
v-model="undertakeDeptCascaderValue"
:options="deptTreeOptions"
:props="deptCascaderProps"
placeholder="请选择承办部门"
check-strictly
clearable
filterable
:render-after-expand="false"
:disabled="isBasicInfoReadonly"
@change="handleUndertakeDeptChange"
/>
</el-form-item>
<el-form-item label="项目由来" prop="projectSource">
@@ -1047,11 +1039,6 @@ export default {
deptOptions: [],
businessDeptTreeOptions: [],
deptTreeOptions: [],
deptTreeSelectProps: {
label: 'label',
value: 'value',
children: 'children',
},
cargoTypeOptions: [],
selectedCustomerId: [],
selectedCarrierIds: [],
@@ -1110,6 +1097,45 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
// 部门级联配置:checkStrictly 允许选中任意层级,不必选到末级
deptCascaderProps() {
return {
label: 'label',
value: 'value',
children: 'children',
emitPath: true,
checkStrictly: true,
expandTrigger: 'click',
};
},
// 业务部门:form 存部门 id,级联控件用根到节点的 id 路径,两者在此双向转换
businessDeptCascaderValue: {
get() {
const id = this.form.businessDeptId;
return id ? this.findDeptPath(id, this.businessDeptTreeOptions) || [id] : [];
},
set(path) {
const value = Array.isArray(path)
? path.filter(item => item !== undefined && item !== null && item !== '')
: [];
this.form.businessDeptId = value.length ? value.at(-1) : '';
this.handleBusinessDeptChange(this.form.businessDeptId);
},
},
// 承办部门:同上
undertakeDeptCascaderValue: {
get() {
const id = this.form.undertakeDeptId;
return id ? this.findDeptPath(id, this.deptTreeOptions) || [id] : [];
},
set(path) {
const value = Array.isArray(path)
? path.filter(item => item !== undefined && item !== null && item !== '')
: [];
this.form.undertakeDeptId = value.length ? value.at(-1) : '';
this.handleUndertakeDeptChange(this.form.undertakeDeptId);
},
},
permissionList() {
return {
addBtn: this.canCreate,
@@ -1843,15 +1869,30 @@ export default {
return `${base}-BG${this.$dayjs().format('YYYYMMDD')}`;
},
handleBusinessDeptChange(value) {
const dept = this.deptOptions.find(item => item.value === value);
// 兼容级联路径数组(取末级)与单个部门 id
const id = Array.isArray(value) ? value.at(-1) : value;
const dept = this.deptOptions.find(item => String(item.value) === String(id));
this.form.businessDeptName = dept ? dept.rawLabel : '';
this.$refs.projectForm?.validateField('businessDeptId');
},
handleUndertakeDeptChange(value) {
const dept = this.deptOptions.find(item => item.value === value);
const id = Array.isArray(value) ? value.at(-1) : value;
const dept = this.deptOptions.find(item => String(item.value) === String(id));
this.form.undertakeDeptName = dept ? dept.rawLabel : '';
this.$refs.projectForm?.validateField('undertakeDeptId');
},
// 由部门 id 反查「根 → 该节点」的 id 路径,供级联回显(找不到返回 null)
findDeptPath(id, tree = [], parents = []) {
const target = Array.isArray(id) ? id.at(-1) : id;
if (target === undefined || target === null || target === '') return null;
for (const node of tree || []) {
const path = [...parents, node.value];
if (String(node.value) === String(target)) return path;
const matched = this.findDeptPath(target, node.children, path);
if (matched) return matched;
}
return null;
},
handleProjectTypeChange() {
this.normalizeAttachmentFileTypes();
this.$refs.projectForm?.validateField('projectType');