Files
td_official/src/views/project/landTransfer/BasicData/landBlock/index.vue
2025-09-06 18:51:05 +08:00

599 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="110px">
<el-form-item label="地块编号" prop="landCode">
<el-input v-model="queryParams.landCode" placeholder="请输入地块编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="地块名称" prop="landName">
<el-input v-model="queryParams.landName" placeholder="请输入地块名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="所属村委会" prop="villageCommittee">
<el-input v-model="queryParams.villageCommittee" placeholder="请输入所属村委会" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设计面积(亩)" prop="designArea">
<el-input v-model="queryParams.designArea" type="number" placeholder="请输入设计面积" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="地块数(块)" prop="blockCount">
<el-input v-model="queryParams.blockCount" type="number" placeholder="请输入地块数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="农户数(户)" prop="farmerCount">
<el-input v-model="queryParams.farmerCount" type="number" placeholder="请输入农户数" clearable @keyup.enter="handleQuery" />
</el-form-item> -->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:landBlock:add']">新增</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button>
</el-col> -->
<el-col :span="1.5">
<el-upload ref="uploadRef" class="upload-demo" :http-request="handleImport" :show-file-list="false">
<template #trigger>
<el-button plain type="primary" icon="upload">导入excel</el-button>
</template>
</el-upload>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Download" @click="exportFile">导出模版</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table draggable v-loading="loading" :data="landBlockList" @selection-change="handleSelectionChange">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="地块编号" align="center" prop="landCode" />
<el-table-column label="地块名称" align="center" prop="landName" />
<el-table-column label="方阵" align="center" prop="unit" />
<el-table-column label="所属村委会" align="center" prop="villageCommittee" />
<el-table-column label="设计面积(亩)" align="center" prop="designArea" />
<el-table-column label="地块数(块)" align="center" prop="blockCount" />
<el-table-column label="农户数(户)" align="center" prop="farmerCount" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['land:landBlock:edit']">编辑</el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['land:landBlock:remove']">删除</el-button>
</el-tooltip>
<el-tooltip content="关联方阵" placement="top">
<el-button type="primary" link @click="handleView(scope.row)">关联方阵</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 地块表单弹窗 -->
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="landBlockFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="地块名称" prop="landName">
<el-input v-model="form.landName" placeholder="请输入地块名称" />
</el-form-item>
<el-form-item label="所属村委会" prop="villageCommittee">
<el-input v-model="form.villageCommittee" placeholder="请输入所属村委会" />
</el-form-item>
<el-form-item label="设计面积(亩)" prop="designArea">
<el-input type="number" v-model="form.designArea" placeholder="请输入设计面积" />
</el-form-item>
<el-form-item label="地块数(块)" prop="blockCount">
<el-input type="number" v-model="form.blockCount" placeholder="请输入地块数" />
</el-form-item>
<el-form-item label="农户数(户)" prop="farmerCount">
<el-input type="number" v-model="form.farmerCount" placeholder="请输入农户数" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm" v-hasPermi="['land:landBlock:add']"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 关联方阵弹窗核心修复区域 -->
<el-dialog draggable :title="dialogMatrix.title" v-model="dialogMatrix.visible" width="900px" append-to-body>
<el-button type="primary" plain icon="Plus" @click="addUnitBoItem" style="margin-bottom: 15px">添加</el-button>
<!-- 修复1表单模型绑定formM根模型确保嵌套字段校验生效 -->
<el-form ref="landBlockFormMatrixRef" :model="formM" label-width="100px">
<!-- 修复2循环formM.unitBoList而非独立ref保证数据双向绑定 -->
<el-row v-for="(item, i) of formM.unitBoList" :key="i" class="mb-4">
<!-- 方阵选择修复校验规则移除min:2改为min:1适配单层级选择 -->
<el-col :span="8">
<el-form-item
label="方阵"
:prop="`unitBoList[${i}].unitProjectId`"
:rules="[
{ required: true, message: '请选择方阵', trigger: 'change' },
{ type: 'array', min: 1, message: '请选择完整的方阵', trigger: 'change' }
]"
>
<el-cascader
:options="fangzhenList"
placeholder="请选择方阵"
filterable
:props="{ value: 'matrixId', label: 'name' }"
v-model="item.unitProjectId"
clearable
/>
</el-form-item>
</el-col>
<!-- 所属工区保留原有规则 -->
<el-col :span="8">
<el-form-item
label="所属工区"
:prop="`unitBoList[${i}].unitProjectArea`"
:rules="{ required: true, message: '请输入所属工区', trigger: 'blur' }"
>
<el-input v-model="item.unitProjectArea" placeholder="请输入所属工区" />
</el-form-item>
</el-col>
<!-- 方阵状态保留原有规则 -->
<el-col :span="6">
<el-form-item
label="方阵状态"
:prop="`unitBoList[${i}].unitProjectStatus`"
:rules="{ required: true, message: '请输入方阵状态', trigger: 'blur' }"
>
<el-input v-model="item.unitProjectStatus" placeholder="请输入方阵状态" />
</el-form-item>
</el-col>
<!-- 删除按钮禁用逻辑优化至少保留1项 -->
<el-col :span="1" style="margin-left: 15px; display: flex; align-items: flex-end">
<el-button
style="margin-bottom: 18px"
type="danger"
icon="Delete"
@click="removeUnitBoItem(i)"
:disabled="formM.unitBoList.length <= 1"
></el-button>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFormMatrix"> </el-button>
<el-button @click="cancelMatrix"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="LandBlock" lang="ts">
import {
listLandBlock,
getLandBlock,
delLandBlock,
LandUnit,
addLandBlock,
updateLandBlock,
subMatrix,
importLandBlock
} from '@/api/system/landTransfer/landBlock';
import { LandBlockVO, LandBlockQuery, LandBlockForm } from '@/api/system/landTransfer/landBlock/types';
import { useUserStoreHook } from '@/store/modules/user';
import { getCurrentInstance, ComponentInternalInstance, onMounted, onUnmounted, watch, reactive, ref, toRefs, computed } from 'vue';
import { ElFormInstance } from 'element-plus';
// 类型定义补充
interface DialogOption {
visible: boolean;
title: string;
}
// 方阵级联选择器选项类型
interface FangzhenOption {
matrixId: string | number;
name: string;
children?: FangzhenOption[];
}
// 方阵表单项类型
interface UnitBoItem {
unitProjectArea: string;
unitProjectStatus: string;
unitProjectId: (string | number)[]; // 级联选择值(数组)
}
// 方阵表单根模型类型关键显式声明unitBoList
interface MatrixForm {
landId?: string | number; // 关联的地块ID
unitBoList: UnitBoItem[]; // 方阵列表
}
// 基础实例与Store
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
// 响应式数据
const landBlockList = ref<LandBlockVO[]>([]);
const fangzhenList = ref<FangzhenOption[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const uploadRef = ref<any>(null);
// 方阵表单模型核心修复使用reactive并显式声明结构
const formM = ref<MatrixForm>({
landId: undefined,
unitBoList: [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }]
});
// 表格选择相关
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
// 表单Ref
const queryFormRef = ref<ElFormInstance>();
const landBlockFormRef = ref<ElFormInstance>();
const landBlockFormMatrixRef = ref<ElFormInstance>();
// 弹窗配置
const dialog = reactive<DialogOption>({ visible: false, title: '' });
const dialogMatrix = reactive<DialogOption>({ visible: false, title: '选择方阵' });
// 初始表单数据
const initFormData: LandBlockForm = {
id: undefined,
projectId: currentProject.value?.id,
landCode: undefined,
landName: undefined,
villageCommittee: undefined,
designArea: undefined,
blockCount: undefined,
farmerCount: undefined,
remark: undefined
};
// 核心数据(含表单规则)
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
landCode: undefined,
landName: undefined,
villageCommittee: undefined,
designArea: undefined,
blockCount: undefined,
farmerCount: undefined,
params: {}
},
// 地块表单规则
rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
landCode: [{ required: true, message: '地块编号不能为空', trigger: 'blur' }],
landName: [{ required: true, message: '地块名称不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询地块列表 */
const getList = async () => {
loading.value = true;
try {
const res = await listLandBlock(queryParams.value);
landBlockList.value = res.rows;
total.value = res.total;
} catch (err) {
proxy?.$modal.msgError('获取地块列表失败');
} finally {
loading.value = false;
}
};
/** 地块表单取消 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 地块表单重置 */
const reset = () => {
form.value = { ...initFormData };
landBlockFormRef.value?.resetFields();
};
/** 搜索提交 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 搜索重置 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 表格选择变化 */
const handleSelectionChange = (selection: LandBlockVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = selection.length === 0;
};
/** 新增地块 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加地块信息';
};
/** 编辑地块 */
const handleUpdate = async (row?: LandBlockVO) => {
reset();
const _id = row?.id || ids.value[0];
if (!_id) return proxy?.$modal.msgWarning('请选择要编辑的地块');
try {
const res = await getLandBlock(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改地块信息';
} catch (err) {
proxy?.$modal.msgError('获取地块详情失败');
}
};
/** 提交地块表单 */
const submitForm = () => {
landBlockFormRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
buttonLoading.value = true;
try {
if (form.value.id) {
await updateLandBlock(form.value);
} else {
await addLandBlock(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} catch (err) {
proxy?.$modal.msgError(form.value.id ? '修改失败' : '新增失败');
} finally {
buttonLoading.value = false;
}
});
};
/** 删除地块 */
const handleDelete = async (row?: LandBlockVO) => {
const _ids = row?.id || ids.value;
if (!_ids.length) return proxy?.$modal.msgWarning('请选择要删除的地块');
try {
await proxy?.$modal.confirm(`是否确认删除地块信息编号为"${_ids}"的数据项?`);
await delLandBlock(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (err) {
// 取消确认时不提示错误
if (err !== 'cancel') proxy?.$modal.msgError('删除失败');
}
};
/** 获取方阵列表 */
const getfangzhenList = async () => {
if (!currentProject.value?.id) return;
loading.value = true;
try {
const res = await subMatrix(currentProject.value.id);
// 处理方阵数据(级联选择需父子结构)
res.data.forEach((item: any) => {
item.children?.forEach((item2: any) => {
item2.matrixId = `${item2.name}_${item2.matrixId}`;
});
});
fangzhenList.value = res.data;
} catch (err) {
proxy?.$modal.msgError('获取方阵列表失败');
} finally {
loading.value = false;
}
};
/** 关联方阵 */
const handleView = async (row: LandBlockVO) => {
if (!row?.id) return proxy?.$modal.msgWarning('请选择有效的地块');
console.log('🚀 ~ handleView ~ row:', row);
// 重置方阵表单
resetMatrix();
// 绑定当前地块ID
formM.value.landId = row.id;
// 打开弹窗
dialogMatrix.visible = true;
dialogMatrix.title = `关联方阵(地块:${row.landName || row.landCode}`;
};
/** 新增方阵表单项 */
const addUnitBoItem = () => {
formM.value.unitBoList.push({
unitProjectArea: '',
unitProjectStatus: '',
unitProjectId: []
});
// 重置校验状态
landBlockFormMatrixRef.value?.clearValidate();
};
/** 删除方阵表单项 */
const removeUnitBoItem = (index: number) => {
if (formM.value.unitBoList.length <= 1) {
return proxy?.$modal.msgWarning('至少保留一项方阵配置');
}
formM.value.unitBoList.splice(index, 1);
landBlockFormMatrixRef.value?.clearValidate();
};
/** 提交方阵关联表单(核心修复:数据处理逻辑) */
const submitFormMatrix = () => {
landBlockFormMatrixRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
if (!formM.value.landId) return proxy?.$modal.msgWarning('地块ID异常请重新选择地块');
try {
// 处理方阵数据修复ID拆分逻辑
const unitBoListParams = formM.value.unitBoList.map((item) => {
// 取级联选择的最后一层值
const lastLevelValue = item.unitProjectId[item.unitProjectId.length - 1];
if (!lastLevelValue) throw new Error('请选择完整的方阵');
// 拆分名称和ID
const [unitProjectName, unitProjectId] = String(lastLevelValue).split('_');
if (!unitProjectId) throw new Error('方阵ID解析失败请重新选择');
return {
unitProjectArea: item.unitProjectArea.trim(),
unitProjectStatus: item.unitProjectStatus.trim(),
unitProjectId: unitProjectId,
unitProjectName: unitProjectName
};
});
// 调用关联接口
const res = await LandUnit({
landId: formM.value.landId,
unitBoList: unitBoListParams
});
if (res.code === 200) {
proxy?.$modal.msgSuccess('关联方阵成功');
dialogMatrix.visible = false;
await getList();
} else {
proxy?.$modal.msgError(res.msg || '关联失败');
}
} catch (err: any) {
proxy?.$modal.msgError(err.msg || '关联过程异常');
}
});
};
/** 方阵表单取消 */
const cancelMatrix = () => {
resetMatrix();
dialogMatrix.visible = false;
};
/** 方阵表单重置 */
const resetMatrix = () => {
if (landBlockFormMatrixRef.value) {
landBlockFormMatrixRef.value.resetFields();
landBlockFormMatrixRef.value.clearValidate();
}
formM.value.landId = undefined;
formM.value.unitBoList = [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }];
};
/** 监听项目变化,刷新数据 */
const listeningProject = watch(
() => currentProject.value?.id,
(newId) => {
if (newId) {
queryParams.value.projectId = newId;
getfangzhenList();
getList();
}
},
{ immediate: true }
);
/** 导入Excel */
const handleImport = (options: { file: File }) => {
if (!currentProject.value?.id) return proxy?.$modal.msgWarning('请先选择项目');
if (!options.file) return proxy?.$modal.msgWarning('请选择Excel文件');
loading.value = true;
const formData = new FormData();
formData.append('file', options.file);
importLandBlock(currentProject.value.id, formData)
.then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess(res.msg || '导入成功');
getList();
getfangzhenList();
} else {
proxy?.$modal.msgError(res.msg || '导入失败');
}
})
.catch((err) => {
proxy?.$modal.msgError(err.msg || '导入接口异常');
})
.finally(() => {
loading.value = false;
});
};
/** 导出模板 */
const exportFile = () => {
try {
const link = document.createElement('a');
link.href = '/dikuai.xlsx';
link.download = '地块信息导入模版.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
alert('下载失败,请重试');
}
};
/** 下载导入模板 */
const downloadTemplate = () => {
try {
const link = document.createElement('a');
link.href = '/landBlock.xlsx';
link.download = '地块信息导入模板.xlsx';
document.body.appendChild(link);
link.click();
} catch (err) {
proxy?.$modal.msgError('模板下载失败,请重试');
} finally {
const link = document.querySelector('a[download="地块信息导入模板.xlsx"]');
if (link) document.body.removeChild(link);
}
};
/** 生命周期:组件卸载时清理监听 */
onUnmounted(() => {
listeningProject();
});
/** 生命周期:组件挂载时初始化数据 */
onMounted(() => {
getList();
getfangzhenList();
});
</script>