Merge remote-tracking branch 'websoft/master'

# Conflicts:
#	src/views/settlement/receivable-payable-detail.vue
This commit is contained in:
2026-08-31 19:30:16 +08:00
25 changed files with 493 additions and 187 deletions
+51 -19
View File
@@ -216,18 +216,14 @@
</div>
</el-form-item>
<el-form-item label="所属组织" prop="organizationName">
<el-tree-select
v-model="selectedOrganizationId"
:data="organizationOptions"
:props="organizationTreeSelectProps"
node-key="id"
<el-cascader
v-model="organizationCascaderValue"
:options="organizationOptions"
:props="organizationCascaderProps"
placeholder="请选择"
check-strictly
clearable
filterable
:render-after-expand="false"
@visible-change="visible => visible && loadOrganizationOptions()"
@change="handleOrganizationChange"
/>
</el-form-item>
<el-form-item label="经办人">
@@ -794,7 +790,7 @@ import { getToken } from '@/utils/auth';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import BillingPlanEditor from './components/billing-plan-editor.vue';
import PdfPreview from '@/components/pdf-preview/main.vue';
import { ElImageViewer, ElTreeSelect } from 'element-plus';
import { ElImageViewer, ElCascader } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue';
import { fallbackPlugin, imagePlugin, officePlugin, textPlugin } from '@open-file-viewer/core';
import '@open-file-viewer/core/style.css';
@@ -1230,13 +1226,32 @@ export default {
contractFormatOptions() {
return this.columnOptions('contractFormat');
},
organizationTreeSelectProps() {
organizationCascaderProps() {
return {
label: 'label',
value: 'id',
children: 'children',
emitPath: true,
checkStrictly: true,
expandTrigger: 'click',
};
},
// 所属组织级联:selectedOrganizationId 存部门 id,级联控件用根到节点的 id 路径,
// 两者在此双向转换;依赖 organizationOptions,树加载完成后回显自动刷新
organizationCascaderValue: {
get() {
const id = this.selectedOrganizationId || this.form.organizationId;
if (!id) return [];
return this.findOrganizationPath(id) || [String(id)];
},
set(path) {
const value = Array.isArray(path)
? path.filter(item => item !== undefined && item !== null && item !== '')
: [];
this.selectedOrganizationId = value.length ? String(value.at(-1)) : '';
this.handleOrganizationChange(value);
},
},
settlementRuleEnabled() {
return Number(this.settlementRuleForm.autoGenerate) === 1;
},
@@ -1728,6 +1743,18 @@ export default {
const value = Array.isArray(id) ? id.at(-1) : id;
return this.organizationFlatOptions.find(item => String(item.id) === String(value));
},
// 由部门 id 反查「根 → 该节点」的 id 路径,供级联回显(找不到返回 null)
findOrganizationPath(id, tree = this.organizationOptions, 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, String(node.id)];
if (String(node.id) === String(target)) return path;
const matched = this.findOrganizationPath(target, node.children, path);
if (matched) return matched;
}
return null;
},
handleOrganizationChange(id) {
const organization = this.findOrganization(id);
Object.assign(
@@ -1752,21 +1779,26 @@ export default {
this.organizationFlatOptions.unshift(item);
},
renderOrganizationSearch(scope) {
// 搜索区所属组织:树形下拉(与客户档案统一
return h(ElTreeSelect, {
modelValue: scope.row?.organizationName || '',
// 搜索区所属组织:单选级联(与新增/编辑表单一致
// 级联返回 id 路径数组,提交时由 normalizeSearch 取末级 id 转组织名称
return h(ElCascader, {
modelValue: Array.isArray(scope.row?.organizationName) ? scope.row.organizationName : [],
'onUpdate:modelValue': value => {
if (scope.row) scope.row.organizationName = value;
// 级联单选:modelValue 必须是 id 路径数组,否则控件内部状态会错乱导致无法再次选中
if (scope.row) scope.row.organizationName = Array.isArray(value) ? value : [];
},
options: this.organizationOptions,
props: {
label: 'label',
value: 'id',
children: 'children',
checkStrictly: true,
expandTrigger: 'click',
},
data: this.organizationOptions,
'node-key': 'id',
'check-strictly': true,
filterable: true,
clearable: true,
'render-after-expand': false,
style: 'width: 100%',
placeholder: '请选择 所属组织',
props: { label: 'label', value: 'id', children: 'children' },
});
},
integerInput(prop, value) {
+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');
+1
View File
@@ -48,6 +48,7 @@ import BillPaymentTable from './components/bill-payment-table.vue';
const emptyQuery = () => ({
paymentNo: '',
deptName: '',
deptPath: [],
paymentDateRange: [],
approvalStatus: '',
});
@@ -6,18 +6,15 @@
<el-input v-model="query.paymentNo" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="使用部门">
<el-tree-select
:model-value="query.deptName"
:data="organizationTreeOptions"
node-key="id"
check-strictly
<el-cascader
v-model="query.deptPath"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
render-after-expand
style="width: 100%"
placeholder="请选择 所属组织"
:props="{ label: 'label', value: 'id', children: 'children' }"
@update:model-value="val => { query.deptName = findOrgById(val)?.rawLabel || val }"
@change="val => { query.deptName = findOrgById(val)?.rawLabel || '' }"
/>
</el-form-item>
<el-form-item label="付款日期">
+6 -8
View File
@@ -10,18 +10,15 @@
<el-input v-model="query.projectName" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="所属组织">
<el-tree-select
:model-value="query.deptName"
:data="organizationTreeOptions"
node-key="id"
check-strictly
<el-cascader
v-model="query.deptPath"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
render-after-expand
style="width: 100%"
placeholder="请选择 所属组织"
:props="{ label: 'label', value: 'id', children: 'children' }"
@update:model-value="val => { query.deptName = findOrgById(val)?.rawLabel || val }"
@change="val => { query.deptName = findOrgById(val)?.rawLabel || '' }"
/>
</el-form-item>
<el-form-item class="invoice-kingdee-item" label="金蝶单据状态">
@@ -252,6 +249,7 @@ const emptyQuery = () => ({
applicationNo: '',
projectName: '',
deptName: '',
deptPath: [],
kingdeeStatus: '',
approvalStatus: '',
});
+6 -8
View File
@@ -19,18 +19,15 @@
<el-input v-model="query.projectName" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="所属组织">
<el-tree-select
:model-value="query.deptName"
:data="organizationTreeOptions"
node-key="id"
check-strictly
<el-cascader
v-model="query.deptPath"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
render-after-expand
style="width: 100%"
placeholder="请选择 所属组织"
:props="{ label: 'label', value: 'id', children: 'children' }"
@update:model-value="val => { query.deptName = findOrgById(val)?.rawLabel || val }"
@change="val => { query.deptName = findOrgById(val)?.rawLabel || '' }"
/>
</el-form-item>
<el-form-item v-if="searchExpanded" label="审核状态">
@@ -245,6 +242,7 @@ const emptyQuery = () => ({
invoiceDate: '',
projectName: '',
deptName: '',
deptPath: [],
approvalStatus: '',
kingdeeStatus: '',
});
+6 -8
View File
@@ -23,18 +23,15 @@
/></el-form-item>
<template v-if="searchExpanded">
<el-form-item label="所属组织"
><el-tree-select
:model-value="query.deptName"
:data="organizationTreeOptions"
node-key="id"
check-strictly
><el-cascader
v-model="query.deptPath"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
render-after-expand
style="width: 100%"
placeholder="请选择 所属组织"
:props="{ label: 'label', value: 'id', children: 'children' }"
@update:model-value="val => { query.deptName = findOrgById(val)?.rawLabel || val }"
@change="val => { query.deptName = findOrgById(val)?.rawLabel || '' }"
/></el-form-item>
<el-form-item label="关联结算单"
><el-input v-model="query.settlementNo" clearable
@@ -243,6 +240,7 @@ const emptyQuery = () => ({
payeeName: '',
projectName: '',
deptName: '',
deptPath: [],
settlementNo: '',
paymentType: '',
approvalStatus: '',
+16 -2
View File
@@ -27,6 +27,16 @@
:value="item.value"
/>
</el-select>
<el-cascader
v-else-if="field.type === 'cascader'"
v-model="query[field.prop]"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
clearable
filterable
style="width: 100%"
placeholder="请选择"
/>
<el-input v-else v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
<div class="formal-page__search-actions">
@@ -168,6 +178,7 @@ import {
formalSettlementSearchFields,
paymentStatusOptions,
} from '@/option/settlement/formalSettlementSearch';
import organizationSearch from '@/mixins/organization-search';
import { formalSettlementTableColumns } from '@/option/settlement/formalSettlementTable';
import FormalSettlementEditor from './components/formal-settlement-editor.vue';
import FormalSettlementTablePanel from './components/formal-settlement-table-panel.vue';
@@ -176,7 +187,7 @@ const emptyQuery = () => ({
formalSettlementNo: '',
preSettlementNo: '',
projectName: '',
deptName: '',
deptName: [],
contractNo: '',
payerName: '',
payeeName: '',
@@ -190,6 +201,7 @@ const emptyQuery = () => ({
export default {
name: 'FormalSettlement',
components: { FormalSettlementEditor, FormalSettlementTablePanel },
mixins: [organizationSearch],
data() {
return {
ArrowDown,
@@ -231,6 +243,7 @@ export default {
},
},
mounted() {
this.loadOrganizationOptions();
this.loadTable();
this.openRouteDetail();
},
@@ -517,7 +530,8 @@ export default {
XLSX.writeFile(workbook, `正式结算单${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`);
},
buildQueryParams() {
const params = { ...this.query };
// 所属组织级联返回 id 路径,转成组织名称传给后端(用副本,避免污染搜索框回显)
const params = this.normalizeOrganizationSearch({ ...this.query }, 'deptName');
params.settlementType = this.activeSettlementType;
const range = params.createDateRange || [];
delete params.createDateRange;
+17 -2
View File
@@ -29,6 +29,16 @@
:value="item.value"
/>
</el-select>
<el-cascader
v-else-if="field.type === 'cascader'"
v-model="query[field.prop]"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
clearable
filterable
style="width: 100%"
placeholder="请选择"
/>
<el-input v-else v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
</template>
@@ -283,6 +293,7 @@ import {
voidBill,
} from '@/api/settlement/preSettlement';
import { preSettlementSearchFields } from '@/option/settlement/preSettlementSearch';
import organizationSearch from '@/mixins/organization-search';
import { preSettlementTableColumns } from '@/option/settlement/preSettlementTable';
import { createSettlementTransfer } from '@/utils/settlement-transfer';
import { downloadFile } from '@/utils/util';
@@ -292,7 +303,7 @@ const emptyQuery = () => ({
preSettlementNo: '',
advanceNo: '',
projectName: '',
deptName: '',
deptName: [],
contractName: '',
contractNo: '',
payeeName: '',
@@ -304,6 +315,7 @@ const emptyQuery = () => ({
export default {
name: 'PreSettlement',
components: { PreSettlementEditor },
mixins: [organizationSearch],
data() {
return {
ArrowDown,
@@ -357,6 +369,7 @@ export default {
},
},
created() {
this.loadOrganizationOptions();
this.loadTable();
this.openRouteDetail();
},
@@ -377,8 +390,10 @@ export default {
},
buildQuery() {
const range = this.query.createDateRange || [];
// 所属组织级联返回 id 路径,转成组织名称传给后端(用副本,避免污染搜索框回显)
const query = this.normalizeOrganizationSearch({ ...this.query }, 'deptName');
return {
...this.query,
...query,
createDateRange: undefined,
createStartDate: range[0],
createEndDate: range[1],
+16 -2
View File
@@ -25,6 +25,16 @@
:label="item.label"
:value="item.value"
/></el-select>
<el-cascader
v-else-if="field.type === 'cascader'"
v-model="query[field.prop]"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
clearable
filterable
style="width: 100%"
placeholder="请选择"
/>
<el-input v-else v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
<div class="settlement-adjustment-page__search-actions">
@@ -159,6 +169,7 @@ import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import * as api from '@/api/settlement/settlementAdjustment';
import { settlementAdjustmentSearchFields } from '@/option/settlement/settlementAdjustmentSearch';
import organizationSearch from '@/mixins/organization-search';
import { settlementAdjustmentTableColumns } from '@/option/settlement/settlementAdjustmentTable';
import SettlementAdjustmentEditor from './components/settlement-adjustment-editor.vue';
@@ -167,7 +178,7 @@ const emptyQuery = () => ({
createDateRange: [],
customerName: '',
projectName: '',
deptName: '',
deptName: [],
formalSettlementNo: '',
settlementType: '',
approvalStatus: '',
@@ -175,6 +186,7 @@ const emptyQuery = () => ({
export default {
name: 'SettlementAdjustment',
components: { SettlementAdjustmentEditor },
mixins: [organizationSearch],
data: () => ({
ArrowDown,
ArrowUp,
@@ -199,6 +211,7 @@ export default {
},
},
created() {
this.loadOrganizationOptions();
this.loadTable();
},
methods: {
@@ -210,7 +223,8 @@ export default {
try {
const range = this.query.createDateRange || [];
const { data } = await api.getList(this.page.current, this.page.size, {
...this.query,
// 所属组织级联返回 id 路径,转成组织名称传给后端(用副本,避免污染搜索框回显)
...this.normalizeOrganizationSearch({ ...this.query }, 'deptName'),
createDateRange: undefined,
createStartDate: range[0],
createEndDate: range[1],
@@ -17,6 +17,16 @@
:value="item.value"
/>
</el-select>
<el-cascader
v-else-if="field.type === 'cascader'"
v-model="query[field.prop]"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
clearable
filterable
style="width: 100%"
placeholder="请选择"
/>
<el-input v-else v-model="query[field.prop]" clearable placeholder="请输入" />
</el-form-item>
</div>
@@ -87,6 +97,7 @@ import * as XLSX from 'xlsx';
import * as api from '@/api/settlement/transportReconciliation';
import { transportReconciliationSearchFields } from '@/option/settlement/transportReconciliationSearch';
import { transportReconciliationTableColumns } from '@/option/settlement/transportReconciliationTable';
import organizationSearch from '@/mixins/organization-search';
import TransportReconciliationEditor from './components/transport-reconciliation-editor.vue';
import TransportReconciliationTablePanel from './components/transport-reconciliation-table-panel.vue';
@@ -94,7 +105,7 @@ const emptyQuery = () => ({
reconciliationNo: '',
preSettlementNos: '',
projectName: '',
deptName: '',
deptName: [],
contractNo: '',
payerName: '',
payeeName: '',
@@ -105,6 +116,7 @@ const emptyQuery = () => ({
export default {
name: 'TransportReconciliation',
components: { TransportReconciliationEditor, TransportReconciliationTablePanel },
mixins: [organizationSearch],
data() {
return {
ArrowDown,
@@ -129,6 +141,7 @@ export default {
},
},
mounted() {
this.loadOrganizationOptions();
this.loadTable();
},
methods: {
@@ -139,7 +152,8 @@ export default {
this.loading = true;
try {
const response = await api.getList(this.page.current, this.page.size, {
...this.query,
// 所属组织级联返回 id 路径,转成组织名称传给后端(用副本,避免污染搜索框回显)
...this.normalizeOrganizationSearch({ ...this.query }, 'deptName'),
settlementType: this.settlementType,
});
const data = response?.data?.data || response?.data || response || {};
+43 -1
View File
@@ -195,7 +195,7 @@
<el-col :span="6"><el-form-item label="邮箱"><el-input v-model="form.email" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="性别"><el-select v-model="form.sex"><el-option label="男" :value="1" /><el-option label="女" :value="2" /><el-option label="未知" :value="3" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="岗位" prop="postId"><el-tree-select v-model="form.postId" :data="postList" :props="{ label: 'postName', value: 'id' }" check-strictly /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="所属组织" prop="deptId"><el-tree-select v-model="form.deptId" :data="permissionDeptTree" :props="{ label: 'title', value: 'id' }" multiple check-strictly show-checkbox /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="所属组织" prop="deptId"><el-cascader v-model="deptCascaderValue" :options="permissionDeptTree" :props="deptCascaderProps" clearable filterable placeholder="请选择 所属组织" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="工号"><el-input v-model="form.code" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="人员类别"><el-select v-model="form.personCategory"><el-option label="内部员工" :value="1" /><el-option label="承运商" :value="2" /></el-select></el-form-item></el-col>
<el-col :span="18"><el-form-item label="数据权限范围"><el-radio-group v-model="form.dataScopeRange"><el-radio :value="1">仅所属组织</el-radio><el-radio :value="2">全部组织</el-radio><el-radio :value="3">自定义</el-radio></el-radio-group></el-form-item></el-col>
@@ -443,6 +443,36 @@ export default {
},
computed: {
...mapGetters(['userInfo', 'permission']),
// 所属组织级联配置:checkStrictly 允许选中任意层级,不必选到末级
deptCascaderProps() {
return {
label: 'title',
value: 'id',
children: 'children',
emitPath: true,
checkStrictly: true,
expandTrigger: 'click',
};
},
// 所属组织为单选级联:form.deptId 仍为数组(提交时 join),此处与 id 路径双向转换
deptCascaderValue: {
get() {
const ids = Array.isArray(this.form.deptId)
? this.form.deptId
: this.form.deptId
? [this.form.deptId]
: [];
const id = ids.find(item => item !== undefined && item !== null && item !== '');
return id === undefined ? [] : this.findDeptPath(id) || [id];
},
set(path) {
const value = Array.isArray(path)
? path.filter(item => item !== undefined && item !== null && item !== '')
: [];
this.form.deptId = value.length ? [value.at(-1)] : [];
this.$refs.userFormRef?.validateField('deptId');
},
},
permissionList() {
return {
addBtn: this.validData(this.permission.user_add, false),
@@ -565,6 +595,18 @@ export default {
this.customerList = res.data.data || [];
});
},
// 由部门 id 反查「根 → 该节点」的 id 路径,供级联回显(找不到返回 null)
findDeptPath(id, tree = this.permissionDeptTree, 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.id];
if (String(node.id) === String(target)) return path;
const matched = this.findDeptPath(target, node.children, path);
if (matched) return matched;
}
return null;
},
submitRole() {
const roleList = this.$refs.treeRole.getCheckedKeys().join(',');
if (this.roleFormMode) {
+19 -8
View File
@@ -490,14 +490,14 @@
</el-col>
<el-col :span="6">
<el-form-item label="所属组织" prop="organizationName">
<el-select v-model="driverForm.organizationName" clearable filterable>
<el-option
v-for="item in organizationOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-cascader
v-model="organizationCascaderValue"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
placeholder="请选择 所属组织"
/>
</el-form-item>
</el-col>
</el-row>
@@ -815,6 +815,15 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
// 所属组织级联:表单存组织名称,级联控件用 id 路径,两者在此双向转换
organizationCascaderValue: {
get() {
return this.resolveOrgPath(this.driverForm.organizationName);
},
set(path) {
this.driverForm.organizationName = this.resolveOrgName(path);
},
},
isAdmin() {
const authority = this.userInfo.authority || '';
return authority.includes('admin');
@@ -1613,6 +1622,8 @@ export default {
this.onLoad(this.page);
},
searchChange(params, done) {
// 所属组织级联返回的是 id 路径,转成组织名称传给后端
this.normalizeOrganizationSearch(params);
this.searchForm = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
+17 -11
View File
@@ -145,19 +145,14 @@
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="船舶所属组织" prop="organizationName">
<el-select
v-model="shipForm.organizationName"
<el-cascader
v-model="organizationCascaderValue"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
placeholder="请选择"
>
<el-option
v-for="item in organizationOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
placeholder="请选择 船舶所属组织"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -707,6 +702,15 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
// 船舶所属组织级联:表单存组织名称,级联控件用 id 路径,两者在此双向转换
organizationCascaderValue: {
get() {
return this.resolveOrgPath(this.shipForm.organizationName);
},
set(path) {
this.shipForm.organizationName = this.resolveOrgName(path);
},
},
isAdmin() {
const authority = this.userInfo.authority || '';
return authority.includes('admin');
@@ -1100,6 +1104,8 @@ export default {
this.onLoad(this.page);
},
searchChange(params, done) {
// 所属组织级联返回的是 id 路径,转成组织名称传给后端
this.normalizeOrganizationSearch(params);
this.searchForm = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
+17 -11
View File
@@ -159,19 +159,14 @@
<el-row :gutter="18">
<el-col :span="24">
<el-form-item label="所有组织" prop="organizationName">
<el-select
v-model="vehicleForm.organizationName"
<el-cascader
v-model="organizationCascaderValue"
:options="organizationTreeOptions"
:props="organizationCascaderProps"
filterable
clearable
placeholder="请选择"
>
<el-option
v-for="item in organizationOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
placeholder="请选择 所属组织"
/>
</el-form-item>
</el-col>
</el-row>
@@ -709,6 +704,15 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
// 所属组织级联:表单存组织名称,级联控件用 id 路径,两者在此双向转换
organizationCascaderValue: {
get() {
return this.resolveOrgPath(this.vehicleForm.organizationName);
},
set(path) {
this.vehicleForm.organizationName = this.resolveOrgName(path);
},
},
isAdmin() {
const authority = this.userInfo.authority || '';
return authority.includes('admin');
@@ -1275,6 +1279,8 @@ export default {
this.onLoad(this.page);
},
searchChange(params, done) {
// 所属组织级联返回的是 id 路径,转成组织名称传给后端
this.normalizeOrganizationSearch(params);
this.searchForm = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
+87 -39
View File
@@ -335,17 +335,13 @@
</el-col>
<el-col :span="6">
<el-form-item label="所属组织" prop="deptName">
<el-tree-select
v-model="archiveForm.deptId"
:data="deptTree"
:props="{ label: 'label', value: 'value', children: 'children' }"
multiple
node-key="value"
check-strictly
filterable
<el-cascader
v-model="deptCascaderValue"
:options="deptTree"
:props="deptCascaderProps"
clearable
:render-after-expand="false"
@change="onDeptChange"
filterable
placeholder="请选择 所属组织"
/>
</el-form-item>
</el-col>
@@ -1367,7 +1363,7 @@
<script>
import { h } from 'vue';
import { ElTreeSelect } from 'element-plus';
import { ElCascader } from 'element-plus';
import {
getList,
getDetail,
@@ -1875,6 +1871,34 @@ export default {
emitPath: true,
};
},
deptCascaderProps() {
return {
value: 'value',
label: 'label',
children: 'children',
emitPath: true,
checkStrictly: true,
expandTrigger: 'click',
};
},
// 所属组织级联值:archiveForm.deptId 只存选中的部门 ID(单选),
// 级联控件需要根到选中节点的完整路径,两者在此做双向转换——
// 部门树是异步加载的,依赖 deptTree 可保证树加载完成后回显自动刷新。
deptCascaderValue: {
get() {
const ids = Array.isArray(this.archiveForm.deptId)
? this.archiveForm.deptId
: this.archiveForm.deptId
? [this.archiveForm.deptId]
: [];
const currentId = ids.length ? String(ids[ids.length - 1]) : '';
if (!currentId) return [];
return this.findDeptPath(currentId) || [currentId];
},
set(path) {
this.onDeptChange(path);
},
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
@@ -2645,38 +2669,57 @@ export default {
this.onDeptChange([firstTopLevelDept.value]);
},
onDeptChange(value) {
const deptIds = Array.isArray(value) ? value : value ? [value] : [];
const deptNames = deptIds
.map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel)
.filter(Boolean);
this.archiveForm.deptId = deptIds;
this.archiveForm.deptIds = deptIds.join(',');
this.archiveForm.deptName = deptNames.join(',');
// 级联单选:value 为根到选中节点的 ID 路径,取末级作为客商所属组织
const path = Array.isArray(value)
? value.filter(item => item !== undefined && item !== null && item !== '')
: [];
const deptId = path.length ? String(path[path.length - 1]) : '';
this.archiveForm.deptId = deptId ? [deptId] : [];
this.archiveForm.deptIds = deptId;
this.archiveForm.deptName = deptId ? this.findDeptLabel(deptId) : '';
this.$refs.archiveForm?.validateField('deptName');
},
findDeptPath(id, tree = this.deptTree, parents = []) {
for (const node of tree || []) {
const path = [...parents, String(node.value)];
if (String(node.value) === String(id)) return path;
const children = node.children || [];
if (children.length) {
const matched = this.findDeptPath(id, children, path);
if (matched) return matched;
}
}
return null;
},
findDeptLabel(id, tree = this.deptTree) {
for (const node of tree || []) {
if (String(node.value) === String(id)) return node.label;
const matched = this.findDeptLabel(id, node.children || []);
if (matched) return matched;
}
return '';
},
renderDeptSearch(scope) {
// 搜索区所属组织:多选树形下拉(与新增/编辑表单一致)
// 多选返回 ID 数组,转换为部门名称按逗号拼接写入 searchForm.deptName
return h(ElTreeSelect, {
// 搜索区所属组织:单选级联(与新增/编辑表单一致)
// 级联返回的是根到选中节点的 ID 路径数组,提交时取末级 ID 转部门名称
return h(ElCascader, {
modelValue: Array.isArray(scope.row?.deptName) ? scope.row.deptName : [],
'onUpdate:modelValue': value => {
// 多选模式modelValue 必须是 ID 数组,否则 el-tree-select 内部状态会错乱导致无法再次选中
const ids = Array.isArray(value) ? value : value ? [value] : [];
if (scope.row) scope.row.deptName = ids;
// 级联单选modelValue 必须是 ID 路径数组,否则控件内部状态会错乱导致无法再次选中
if (scope.row) scope.row.deptName = Array.isArray(value) ? value : [];
},
options: this.deptTree,
props: {
label: 'label',
value: 'value',
children: 'children',
checkStrictly: true,
expandTrigger: 'click',
},
data: this.deptTree,
'node-key': 'value',
'check-strictly': true,
multiple: true,
filterable: true,
clearable: true,
'render-after-expand': false,
collapseTags: true,
'collapse-tags-tooltip': true,
'default-expand-all': false,
style: 'width: 100%',
placeholder: '请选择 所属组织',
props: { label: 'label', value: 'value', children: 'children' },
});
},
handleNoFixedTermChange(checked) {
@@ -2717,13 +2760,15 @@ export default {
: [];
},
normalizeDeptIds(detail) {
// 所属组织为单选级联,只保留第一个部门 ID(历史多选数据自动收敛)
const value = detail.deptIds || detail.deptId;
return value
const ids = value
? String(value)
.split(',')
.map(item => item.trim())
.filter(item => item !== '')
: [];
return ids.length ? [ids[0]] : [];
},
formatArchiveAddress(archive = {}) {
return [archive.registeredRegionName, archive.registeredDetailAddress]
@@ -4539,12 +4584,15 @@ export default {
this.onLoad(this.page);
},
searchChange(params, done) {
// 搜索区所属组织多选返回的是部门 ID 数组,转成部门名称逗号串传给后端(与列表接口 deptName 字段一致)
// 搜索区所属组织级联返回的是 ID 路径数组,取末级 ID 转成部门名称传给后端(与列表接口 deptName 字段一致)
if (Array.isArray(params.deptName)) {
params.deptName = params.deptName
.map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel)
.filter(Boolean)
.join(',');
const path = params.deptName.filter(
item => item !== undefined && item !== null && item !== ''
);
const deptId = path.length ? path[path.length - 1] : '';
params.deptName = deptId
? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || ''
: '';
}
this.query = this.buildQuery(params);
this.page.currentPage = 1;