Files
td_official/src/views/project/landTransfer/BasicData/landBlock/index.vue
2025-08-30 08:18:27 +08:00

571 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>
<!-- 方阵表单绑定unitBoList的索引实现动态校验 -->
<el-form ref="landBlockFormMatrixRef" :model="formM" label-width="100px">
<el-row v-for="(item, i) of unitBoList" :key="i" class="mb-4">
<!-- 方阵选择必填校验 -->
<el-col :span="8">
<el-form-item
label="方阵"
:prop="`unitBoList[${i}].unitProjectId`"
:rules="[
{ required: true, message: '请选择方阵', trigger: 'change' },
{ type: 'array', min: 2, 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>
<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)"></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 } from 'vue';
import { ElFormInstance, ElMessage } from 'element-plus';
// 类型定义补充避免any
interface DialogOption {
visible: boolean;
title: string;
}
interface UnitBoItem {
unitProjectArea: string;
unitProjectStatus: string;
unitProjectId: (string | number)[]; // 级联选择值(数组)
}
// 基础实例与Store
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
// 响应式数据
const landBlockList = ref<LandBlockVO[]>([]);
const fangzhenList = ref<any[]>([]); // 方阵列表(实际项目建议定义具体类型)
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const uploadRef = ref<any>(null); // upload组件ref
// 方阵表单数据(核心修改:初始值为空,避免默认填充无效数据)
const unitBoList = ref<UnitBoItem[]>([{ 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 },
formM: { landId: undefined }, // 方阵关联表单仅存地块ID
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, formM } = 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}`; // 拼接名称+ID便于后续拆分
});
});
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('请选择有效的地块');
// 1. 重置方阵表单(清空历史数据)
resetMatrix();
// 2. 绑定当前地块ID
formM.value.landId = row.id;
// 3. 打开弹窗
dialogMatrix.visible = true;
dialogMatrix.title = `关联方阵(地块:${row.landName || row.landCode}`;
};
/** 新增方阵表单项 */
const addUnitBoItem = () => {
unitBoList.value.push({
unitProjectArea: '',
unitProjectStatus: '',
unitProjectId: [] // 级联选择初始为空数组
});
};
/** 删除方阵表单项 */
const removeUnitBoItem = (index: number) => {
if (unitBoList.value.length <= 1) {
return proxy?.$modal.msgWarning('至少保留一项方阵配置');
}
unitBoList.value.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 = unitBoList.value.map((item) => {
const [unitProjectName, unitProjectId] = item.unitProjectId[1]?.split('_') || [];
if (!unitProjectId) throw new Error('方阵ID解析失败请重新选择方阵');
return {
unitProjectArea: item.unitProjectArea.trim(),
unitProjectStatus: item.unitProjectStatus.trim(),
unitProjectId: unitProjectId, // 纯ID后端需要
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 = () => {
// 1. 清空地块ID
formM.value.landId = undefined;
// 2. 重置方阵列表(仅保留一个空项)
unitBoList.value = [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }];
// 3. 重置表单校验状态
if (landBlockFormMatrixRef.value) {
landBlockFormMatrixRef.value.resetFields();
landBlockFormMatrixRef.value.clearValidate();
}
};
/** 监听项目变化,刷新数据 */
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 {
// 创建a标签
const link = document.createElement('a');
// 设置PDF文件路径 - 相对于public目录
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'; // 模板路径需确保public目录下存在该文件
link.download = '地块信息导入模板.xlsx'; // 下载后文件名
document.body.appendChild(link);
link.click(); // 触发下载
} catch (err) {
proxy?.$modal.msgError('模板下载失败,请重试');
} finally {
document.body.removeChild(link); // 清理DOM
}
};
/** 生命周期:组件卸载时清理监听 */
onUnmounted(() => {
listeningProject();
});
/** 生命周期:组件挂载时初始化数据 */
onMounted(() => {
getList();
getfangzhenList();
});
</script>