<template>
|
<basicContainer>
|
<el-tabs v-model="activeAccountSet" @tab-click="handleTabClick">
|
<el-tab-pane label="珠海汇畅" name="0"></el-tab-pane>
|
<el-tab-pane label="广珠物流贸易" name="1"></el-tab-pane>
|
</el-tabs>
|
<avue-crud :option="option" :table-loading="pageF.loading" :data="tableData" :page="page"
|
:permission="permissionList" :before-open="beforeOpen" v-model="form" ref="crudRef" @row-update="rowUpdate"
|
@row-save="rowSave" @refresh-change="refreshChange" @row-del="rowDel" @search-change="searchChange"
|
@search-reset="searchReset" @selection-change="selectionChange" @current-change="currentChange"
|
@size-change="sizeChange" @on-load="onLoad">
|
<template #menu-left>
|
<!-- <el-button type="success" icon="Edit" :disabled="pageF.single" v-hasPermi="['cwgl:voucherSubjectSetting:edit']"
|
@click="handleUpdate">修改
|
</el-button>
|
<el-button type="danger" icon="Delete" :disabled="pageF.multiple" @click="handleDelete"
|
v-hasPermi="['cwgl:voucherSubjectSetting:remove']">删除
|
</el-button> -->
|
<el-button type="warning" plain icon="Download" @click="handleExport"
|
v-hasPermi="['cwgl:voucherSubjectSetting:export']">导出
|
</el-button>
|
</template>
|
<template #menu="{ row, index, size }">
|
<el-button type="primary" text icon="Plus" @click="handleRowAdd(row)">新增子项</el-button>
|
</template>
|
</avue-crud>
|
</basicContainer>
|
</template>
|
|
<script setup name="voucherSubjectSetting" lang="ts">
|
import { VoucherSubjectSettingI, addVoucherSubjectSetting, delVoucherSubjectSetting, exportVoucherSubjectSetting, getVoucherSubjectSetting, listVoucherSubjectSetting, updateVoucherSubjectSetting } from "@/api/cwgl/voucherSubjectSetting";
|
import useCurrentInstance from "@/utils/useCurrentInstance";
|
import { computed, reactive, ref, toRefs } from "vue";
|
import { PagesInterface, PageQueryInterface } from "@/utils/globalInterface";
|
import { usePagePlus } from "@/hooks/usePagePlus";
|
import { hasPermission } from "@/utils/permissionUtils";
|
|
const { proxy } = useCurrentInstance();
|
const crudRef = ref();
|
|
const permissionList = computed(() => {
|
return {
|
addBtn: hasPermission(["cwgl:voucherSubjectSetting:add"]),
|
delBtn: hasPermission(["cwgl:voucherSubjectSetting:remove"]),
|
editBtn: hasPermission(["cwgl:voucherSubjectSetting:edit"]),
|
viewBtn: hasPermission(["cwgl:voucherSubjectSetting:query"]),
|
}
|
})
|
// 记录是否来自行内操作
|
const isFromRow = ref(false);
|
const activeAccountSet = ref("0");
|
const data = reactive({
|
form: <VoucherSubjectSettingI>{},
|
queryParams: <VoucherSubjectSettingI & PageQueryInterface>{
|
type: "0",
|
accountingItemsContains: [], // 必须初始化为数组
|
},
|
page: <PagesInterface>{
|
pageSize: 10,
|
total: 0,
|
currentPage: 1,
|
},
|
selectionList: [],
|
})
|
const { queryParams, form, page, selectionList } = toRefs(data);
|
const option = ref({
|
pageKey: 'VoucherSubjectSetting',
|
rowKey: 'id',
|
tree: true, // 开启树形表格模式
|
childrenHandler: 'children', // 指定子节点字段名,默认为 children
|
expandAll: true, // 是否默认展开所有行
|
viewBtn: false,
|
searchSpan: 5,
|
labelWidth: 150,
|
searchLabelWidth: 120,
|
column: {
|
// id: {
|
// label: '科目ID11',
|
// },
|
// parentId: {
|
// label: '父科目ID',
|
// },
|
// ancestors: {
|
// label: '祖级列表',
|
// type: 'textarea', minRows: 3, maxRows: 5,
|
// },
|
// type: {
|
// label: '帐套类型',
|
// search: true,
|
// minWidth: 120,
|
// type: 'select', dataType: 'string', dicUrl: '/system/dict/data/type/sys_accounting_type',
|
// rules: [
|
// {
|
// required: true,
|
// message: "帐套类型不能为空", trigger: "change"
|
// }
|
// ],
|
// },
|
|
accountSet: {
|
label: '账套',
|
minWidth: 220,
|
addDisabled: true,
|
editDisabled: true,
|
type: 'select', dataType: 'string', dicUrl: '/system/dict/data/type/sys_accounting_type',
|
rules: [
|
{
|
required: true,
|
message: "帐套类型不能为空", trigger: "change"
|
}
|
],
|
},
|
// parentSubjectName: {
|
// label: '上级科目名称2',
|
// minWidth: 150,
|
|
// search: true,
|
// rules: [
|
// {
|
// required: true,
|
// message: "科目名称不能为空", trigger: "blur"
|
// }
|
// ],
|
// },
|
parentId: {
|
label: '上级科目名称',
|
prop: 'parentId',
|
type: 'tree',
|
hide: true,
|
dicData: [],
|
props: {
|
label: 'fullName', // 关键点:绑定我们预处理好的全路径字段
|
value: 'id'
|
},
|
addDisabled: true,
|
editDisabled: true,
|
rules: [
|
{ required: true, message: "上级科目不能为空", trigger: "change" }
|
]
|
},
|
parentSubjectCode: {
|
label: '上级科目代码',
|
minWidth: 150,
|
disabled: true, // 设置为禁用,仅供查看
|
// 如果希望在表格也显示,这里不要写 hide: true
|
},
|
parentSubjectCode: {
|
label: '上级科目代码',
|
minWidth: 150,
|
hide: true,
|
// rules: [
|
// {
|
// required: true,
|
// message: "科目名称不能为空", trigger: "blur"
|
// }
|
// ],
|
},
|
// parentSubjectCode: {
|
// label: '上级科目代码',
|
// minWidth: 150,
|
// display: true,
|
// hide: true, // 在表格列中隐藏
|
// search: false, // 在搜索栏中隐藏
|
// display: true, // 在新增/修改弹窗中显示(默认为true)
|
// addDisabled: true, // 新增时置灰,不可编辑
|
// editDisabled: true, // 修改时置灰,不可编辑
|
// // readonly: true, // 或者使用只读属性(取决于 UI 需求)
|
// // rules: [
|
// // {
|
// // required: true,
|
// // message: "上级科目代码不能为空", trigger: "blur"
|
// // }
|
// // ],
|
// },
|
subjectName: {
|
label: '科目名称',
|
minWidth: 150,
|
|
search: true,
|
rules: [
|
{
|
required: true,
|
message: "科目名称不能为空", trigger: "blur"
|
}
|
],
|
},
|
subjectCode: {
|
label: '科目代码',
|
minWidth: 150,
|
search: true,
|
rules: [
|
{
|
required: true,
|
message: "科目代码不能为空", trigger: "blur"
|
}
|
],
|
},
|
balanceDirection: {
|
label: '余额方向',
|
search: true,
|
minWidth: 120,
|
type: 'radio',
|
dicUrl: '/system/dict/data/type/sys_balance_direction',
|
rules: [
|
{
|
required: true,
|
message: "余额方向不能为空", trigger: "blur"
|
}
|
],
|
},
|
enabled: {
|
label: '是否启用',
|
minWidth: 120,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_whether_type',
|
rules: [
|
{
|
required: true,
|
message: "是否启用不能为空", trigger: "change"
|
}
|
],
|
search: true,
|
},
|
accountingItemsDesc: {
|
label: '核算项目',
|
minWidth: 150,
|
addDisplay: false,
|
editDisplay: false,
|
},
|
accountingItemsContains: {
|
label: '核算项目',
|
minWidth: 150,
|
search: true,
|
type: 'select', // 确保类型为 select
|
multiple: true, // 开启多选
|
addDisplay: false, // 在新增时隐藏
|
editDisplay: false,
|
hide: true,
|
dataType: 'string',
|
dicUrl: '/system/dict/data/type/sys_accounting_item_name',
|
placeholder: '请选择核算项目(可多选)',
|
props: {
|
label: 'dictLabel',
|
value: 'dictValue'
|
}
|
},
|
accountingItems: {
|
label: '核算项目',
|
minWidth: 150,
|
type: 'select',
|
multiple: true,
|
dicUrl: '/system/dict/data/type/sys_accounting_item_name',
|
placeholder: '请选择核算项目(可多选)',
|
props: {
|
label: 'dictLabel',
|
value: 'dictValue'
|
},
|
// --- 关键修改 ---
|
hide: true, // 在表格列中隐藏
|
search: false, // 在搜索栏中隐藏
|
display: true, // 确保在弹窗表单中依然显示(默认即为 true)
|
// ----------------
|
minWidth: 150
|
},
|
subjectType: {
|
label: '科目类别',
|
minWidth: 150,
|
minWidth: 120,
|
type: 'select', dicUrl: '/system/dict/data/type/sys_subject_category',
|
rules: [
|
{
|
required: true,
|
message: "科目类别不能为空", trigger: "change"
|
}
|
],
|
search: true,
|
},
|
quantityAmountAccounting: {
|
label: '数量金额核算',
|
minWidth: 120,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
rules: [
|
{
|
required: true,
|
message: "数量金额核算不能为空", trigger: "change"
|
}
|
],
|
},
|
unitOfMeasurement: {
|
label: '计量单位',
|
minWidth: 150,
|
|
},
|
expenseName: {
|
label: '费用名称',
|
minWidth: 120,
|
type: 'select', dicUrl: '/system/dict/data/type/sys_name_harge',
|
rules: [
|
{
|
required: true,
|
message: "费用名称不能为空", trigger: "change"
|
}
|
],
|
search: true,
|
|
},
|
mnemonicCode: {
|
label: '助记码',
|
minWidth: 150,
|
|
},
|
foreignCurrencyAccounting: {
|
label: '外币核算',
|
minWidth: 150,
|
type: 'select', dicUrl: '/system/dict/data/type/sys_foreign_verification',
|
},
|
cashSubject: {
|
label: '现金科目',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
|
},
|
bankSubject: {
|
label: '银行科目',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
},
|
cashFlowSubject: {
|
label: '现金流量科目',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
},
|
contactUnit: {
|
label: '往来单位',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
},
|
department: {
|
label: '部门',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
},
|
productName: {
|
label: '品名',
|
minWidth: 150,
|
type: 'radio', dicUrl: '/system/dict/data/type/sys_right_wrong',
|
|
},
|
|
|
// orderNum: {
|
// label: '显示顺序',
|
// },
|
// status: {
|
// label: '状态',
|
// },
|
// delFlag: {
|
// label: '删除标志',
|
// },
|
// createBy: {
|
// label: '创建者',
|
// },
|
// createTime: {
|
// label: '创建时间',
|
// },
|
// updateBy: {
|
// label: '更新者',
|
// },
|
// updateTime: {
|
// label: '更新时间',
|
// },
|
// remark: {
|
// label: '备注',
|
// type: 'textarea', minRows: 3, maxRows: 5,
|
// },
|
|
}
|
})
|
// sys_accounting_type
|
// listVoucherSubjectSetting
|
const { tableData, pageF, rowSave, rowUpdate, rowDel, beforeOpen, searchChange,
|
searchReset, selectionChange, onLoad, currentChange, sizeChange, handleDelete, handleExport, handleUpdate, refreshChange } = usePagePlus({
|
form: form,
|
option: option,
|
queryParams: queryParams,
|
idKey: 'id',
|
page: page.value,
|
getListApi: listVoucherSubjectSetting,
|
getDetailApi: getVoucherSubjectSetting,
|
// 【关键修复点】:拦截详情接口,在数据进入 form 之前完成格式化
|
getDetailApi: (id: any) => {
|
return getVoucherSubjectSetting(id).then((res: any) => {
|
console.log(res);
|
|
if (res.code === 200 && res.data) {
|
// 1. 处理 accountingItems 数字转数组
|
// 1. 如果 accountSet 是数字,转为字符串
|
res.data.accountSet = res.data.accountSet ? String(res.data.accountSet) : "0";
|
|
if (typeof res.data.accountingItems === 'number') {
|
res.data.accountingItems = decomposeAccountingItems(res.data.accountingItems);
|
} else if (!res.data.accountingItems) {
|
res.data.accountingItems = [];
|
}
|
|
// 2. 处理 accountingItemsContains 确保是数组
|
if (!Array.isArray(res.data.accountingItemsContains)) {
|
res.data.accountingItemsContains = [];
|
}
|
|
|
}
|
return res;
|
});
|
},
|
exportApi: exportVoucherSubjectSetting,
|
deleteApi: delVoucherSubjectSetting,
|
addApi: addVoucherSubjectSetting,
|
updateApi: updateVoucherSubjectSetting,
|
getListFunc: (res: any) => {
|
// 1. 转换树结构
|
tableData.value = proxy.handleTree(res.rows, "id");
|
|
// 2. 使用递归函数展开所有层级
|
nextTick(() => {
|
if (crudRef.value) {
|
const tableMethods = crudRef.value.$refs.table;
|
if (tableMethods && tableData.value) {
|
|
// --- 定义递归展开函数 ---
|
const expandAllNodes = (list: any[]) => {
|
list.forEach(row => {
|
// 只要有子节点,就执行展开动作
|
if (row.children && row.children.length > 0) {
|
tableMethods.toggleRowExpansion(row, true);
|
// 继续递归展开子节点的子节点
|
expandAllNodes(row.children);
|
}
|
});
|
};
|
|
// 执行递归
|
expandAllNodes(tableData.value);
|
}
|
}
|
});
|
},
|
handleUpdateFunc: () => {
|
isFromRow.value = true; // 修改操作也视为禁用上级科目
|
crudRef.value.rowEdit(selectionList.value[0]);
|
},
|
handleSelectionChangeFunc: (selection: any) => {
|
selectionList.value = selection;
|
},
|
getBeginListFunc: (params = {}) => {
|
let newParams = { ...params };
|
newParams.type = activeAccountSet.value;
|
|
// 处理 accountingItems 参数
|
if (newParams.accountingItemsContains) {
|
if (Array.isArray(newParams.accountingItemsContains)) {
|
// 如果是数组,按原逻辑处理
|
newParams.accountingItemsContains = newParams.accountingItemsContains.join(',');
|
} else {
|
// 如果是字符串,检查是否为逗号分隔的数字
|
const itemsStr = String(newParams.accountingItemsContains).trim();
|
if (itemsStr && itemsStr.includes(',')) {
|
// 分割字符串,转换为数字并相加
|
const sum = itemsStr
|
.split(',')
|
.map(item => parseInt(item.trim(), 10))
|
.filter(num => !isNaN(num))
|
.reduce((acc, curr) => acc + curr, 0);
|
|
// 使用相加后的结果
|
newParams.accountingItemsContains = sum > 0 ? sum.toString() : '';
|
}
|
}
|
}
|
|
queryParams.value = newParams;
|
return newParams;
|
},
|
|
handleBeforeOpenFunc: (type: string) => {
|
if (!option.value?.column) return;
|
|
const codeCol = option.value.column.parentSubjectCode;
|
const parentIdCol = option.value.column.parentId;
|
|
// --- 1. 定义名称递归处理器 ---
|
const formatTreeData = (list: any[], parentName = '') => {
|
return list.map(item => {
|
// 拼接全路径:父级名称 > 当前名称
|
const currentFullName = parentName ? `${parentName} > ${item.subjectName}` : item.subjectName;
|
const newItem = {
|
...item,
|
fullName: currentFullName
|
};
|
// 如果有子节点,继续递归
|
if (newItem.children && newItem.children.length > 0) {
|
newItem.children = formatTreeData(newItem.children, currentFullName);
|
}
|
return newItem;
|
});
|
};
|
|
// --- 2. 加载并处理字典数据 ---
|
listVoucherSubjectSetting({ type: activeAccountSet.value }).then(res => {
|
// 将扁平数据转为树结构 (proxy.handleTree 是 Ruoyi 常用工具)
|
const treeData = proxy.handleTree(res.rows, "id", "parentId");
|
// 执行全路径拼接
|
const fullPathTree = formatTreeData(treeData);
|
|
// 组合最终字典,加入“顶级”选项
|
const fullDic = [
|
{
|
id: 0,
|
subjectName: '顶级科目',
|
fullName: '顶级科目',
|
children: fullPathTree
|
}
|
];
|
if (parentIdCol) parentIdCol.dicData = fullDic;
|
|
// 异步回调中再次确保代码字段显示
|
if (String(form.value.parentId) === "0" || !form.value.parentId) {
|
form.value.parentSubjectCode = "顶级科目代码";
|
}
|
});
|
|
// --- 3. 处理不同模式下的显示逻辑 ---
|
if (type !== 'add') {
|
// 【修改/查看】
|
if (String(form.value.parentId) === "0" || !form.value.parentId) {
|
form.value.parentSubjectCode = "顶级科目代码";
|
}
|
if (parentIdCol) { parentIdCol.display = true; parentIdCol.disabled = true; }
|
if (codeCol) { codeCol.display = true; codeCol.disabled = true; }
|
} else {
|
// 【新增】
|
if (isFromRow.value) {
|
// 行内新增子项
|
if (parentIdCol) { parentIdCol.display = true; parentIdCol.disabled = true; }
|
if (codeCol) { codeCol.display = true; codeCol.disabled = true; }
|
} else {
|
// 顶部新增 (默认是顶级科目)
|
if (parentIdCol) parentIdCol.display = false;
|
if (codeCol) codeCol.display = false;
|
|
const currentSet = activeAccountSet.value;
|
Object.assign(form.value, {
|
id: undefined,
|
parentId: 0,
|
parentSubjectCode: "无",
|
accountSet: currentSet,
|
type: currentSet,
|
accountingItems: [],
|
enabled: "1"
|
});
|
}
|
}
|
|
// --- 4. 格式化多选字段 ---
|
const fieldsToFix = ['accountingItems', 'accountingItemsContains'];
|
fieldsToFix.forEach(field => {
|
const val = form.value[field];
|
if (typeof val === 'number') {
|
form.value[field] = decomposeAccountingItems(val);
|
} else if (!Array.isArray(val)) {
|
form.value[field] = [];
|
}
|
});
|
|
nextTick(() => {
|
isFromRow.value = false;
|
});
|
},
|
|
// 新增保存前的逻辑
|
rowSaveBegin: (row: any, done: any, loading: any) => {
|
processAccountingItems(row);
|
delete row.accountingItemsContains;
|
|
// --- 核心逻辑开始 ---
|
if (!isFromRow.value) {
|
// 1. 确保没有 ID(防止误操作)
|
delete row.id;
|
|
// 2. 如果是顶级科目(parentId 为 0 或 无)
|
if (row.parentId === 0 || !row.parentId) {
|
// 将上级科目代码设置为当前填写的科目代码
|
row.parentSubjectCode = row.subjectCode;
|
}
|
} else {
|
// 如果是行内新增,parentId 已经是父节点的 ID
|
}
|
// --- 核心逻辑结束 ---
|
|
done(row);
|
},
|
|
// 修改保存前的逻辑
|
rowUpdateBegin: (row: any, done: any, loading: any) => {
|
processAccountingItems(row);
|
delete row.accountingItemsContains;
|
if (row.parentSubjectCode == "顶级科目代码") {
|
row.parentSubjectCode = row.subjectCode;
|
}
|
done(row); // 同理
|
},
|
})
|
|
const processAccountingItems = (row: any) => {
|
if (!row.accountingItems) {
|
row.accountingItems = 0;
|
return;
|
}
|
|
// 1. 如果已经是数组(正常多选组件返回的结果)
|
if (Array.isArray(row.accountingItems)) {
|
row.accountingItems = row.accountingItems
|
.map(item => parseInt(String(item), 10))
|
.filter(num => !isNaN(num))
|
.reduce((acc, curr) => acc + curr, 0);
|
}
|
// 2. 如果是逗号分隔的字符串
|
else if (typeof row.accountingItems === 'string' && row.accountingItems.includes(',')) {
|
row.accountingItems = row.accountingItems
|
.split(',')
|
.map(item => parseInt(item.trim(), 10))
|
.filter(num => !isNaN(num))
|
.reduce((acc, curr) => acc + curr, 0);
|
}
|
// 3. 如果已经是数字,保持不变
|
console.log('转换后的提交值:', row.accountingItems);
|
};
|
const handleTabClick = (tab: any) => {
|
const selectedTabName = tab.props.name;
|
console.log("Tab点击的值:", selectedTabName);
|
activeAccountSet.value = selectedTabName;
|
queryParams.value.type = selectedTabName;
|
const nextParams = {
|
...queryParams.value,
|
type: selectedTabName
|
};
|
|
// 4. 重置分页并加载
|
page.value.currentPage = 1;
|
onLoad(page.value, nextParams);
|
}
|
|
/**
|
* 拆分函数:将 3 转换为 ['1', '2']
|
*/
|
function decomposeAccountingItems(num: number): string[] {
|
const result: string[] = [];
|
let bit = 1;
|
while (bit <= num) {
|
if ((num & bit) === bit) {
|
result.push(bit.toString());
|
}
|
bit <<= 1; // 位移运算,等同于 bit *= 2
|
}
|
return result;
|
}
|
|
|
/**
|
* 处理行内“新增子项”点击
|
*/
|
const handleRowAdd = (row: any) => {
|
isFromRow.value = true;
|
|
// 1. 先触发新增动作,这会弹出窗口并初始化 form
|
crudRef.value.rowAdd();
|
|
// 2. 在 DOM 更新后的下一次循环中强制赋值
|
nextTick(() => {
|
// 确保赋值给 data 响应式对象中的 form
|
form.value.parentId = row.id;
|
form.value.parentSubjectCode = row.subjectCode;
|
form.value.accountSet = row.accountSet;
|
form.value.type = row.accountSet;
|
form.value.enabled = "1";
|
form.value.accountingItems = [];
|
});
|
};
|
</script>
|