672 lines
21 KiB
Vue
Raw Normal View History

2025-02-26 13:36:43 +08:00
<template>
2025-03-27 15:56:05 +08:00
<section class="custom-page">
2025-06-05 17:40:58 +08:00
<avue-crud ref="crudRef" v-model:page="pageData" :data="data" :option="option" :table-loading="loading">
<template #search>
<div class="custom-search">
<AreaCascader v-model:region-code="query.regionCode" v-model:grid-id="query.gridId" :width="500" />
<!-- <url-select
v-model="query.landType"
placeholder="土地类型"
url="/land-resource/landManage/page"
style="margin-left: 8px; width: 200px"
label-key="landName"
value-key="landType"
:clearable="true"
/> -->
<el-select v-model="query.landType" placeholder="选择土地类型" clearable style="width: 200px; margin-left: 8px">
<el-option v-for="item in landTypeOptions" :key="item.id" :label="item.landType" :value="item.id" />
</el-select>
2025-06-05 17:40:58 +08:00
<el-input v-model="query.keyword" placeholder="关键词搜索" style="margin-left: 8px; width: 200px" @keyup.enter="handleSearch" />
<el-button type="primary" @click="handleSearch"> 搜索 </el-button>
<el-button @click="resetSearch"> 重置 </el-button>
</div>
</template>
2025-06-05 17:40:58 +08:00
<template #menu-left>
<el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
2025-03-03 16:28:34 +08:00
</template>
2025-03-25 08:45:53 +01:00
<template #menu="scope">
<custom-table-operate :actions="option.actions" :data="scope" />
</template>
2025-03-03 16:28:34 +08:00
</avue-crud>
2025-06-05 17:40:58 +08:00
<!-- 新增弹窗 -->
<el-dialog v-model="addDialogVisible" title="新增" width="800px" top="8vh">
<el-tabs v-model="activeTab">
<el-tab-pane label="土地基本信息" name="basic">
<el-form ref="basicFormRef" :model="formDataBasic" :rules="basicRules" label-width="120px" class="form-container">
<el-form-item label="地块名称" prop="landName">
<el-input v-model="formDataBasic.landName" placeholder="请输入地块名称" />
</el-form-item>
<el-form-item label="面积(亩)" prop="area">
<el-input-number v-model="formDataBasic.area" :min="0" :precision="2" :step="0.1" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="土地类型" prop="landType">
<el-select v-model="formDataBasic.landType" placeholder="选择土地类型" clearable>
<el-option v-for="item in landTypeOptions" :key="item.id" :label="item.landType" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="具体位置" prop="address">
<el-input v-model="formDataBasic.address" placeholder="请输入具体位置" />
</el-form-item>
<el-form-item label="所属网格" prop="gridId">
<AreaCascader v-model:region-code="formDataBasic.regionCode" v-model:grid-id="formDataBasic.gridId" label="" :width="500" />
</el-form-item>
<el-form-item label="土壤类型" prop="soilTypeId">
<url-select
v-model="formDataBasic.soilTypeId"
placeholder="选择土壤类型"
url="/land-resource/baseInfo/soilTypePage"
:params="{ current: 1, size: 100 }"
label-key="soilType"
value-key="id"
:clearable="true"
/>
</el-form-item>
<el-form-item label="土地照片" prop="landUrl">
<el-upload
:http-request="customUploadRequest"
:on-success="(res, file) => handleUploadSuccess(res, file, 'basic')"
multiple
:show-file-list="true"
>
<template #trigger>
<el-button type="primary">点击上传</el-button>
</template>
</el-upload>
</el-form-item>
<el-form-item label="土地范围" prop="scope">
<el-input v-model="formDataBasic.scope" placeholder="请输入土地范围" />
<!-- <Attrs v-model:attrs="formDataBasic.scope" type="add" accept="image/*" /> -->
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="土地产权信息" name="property">
2025-06-12 19:21:26 +08:00
<el-form ref="propertyFormRef" :model="formDataProperty" :rules="propertyRules" label-width="120px" class="form-container">
2025-06-05 17:40:58 +08:00
<el-form-item label="地块名称">
<el-input v-model="formDataProperty.landName" disabled />
</el-form-item>
<el-form-item label="产权人姓名" prop="propertyName">
<el-input v-model="formDataProperty.propertyName" placeholder="请输入产权人姓名" />
</el-form-item>
<el-form-item label="联系方式" prop="propertyPhone">
<el-input v-model="formDataProperty.propertyPhone" placeholder="请输入产权人联系方式" />
</el-form-item>
<el-form-item label="产权编号" prop="landCode">
<el-input v-model="formDataProperty.landCode" placeholder="请输入产权编号" />
</el-form-item>
<el-form-item label="产权证书" prop="propertyCertificateUrl">
<FileUploader v-model="formDataProperty.propertyCertificateUrl" :limit="1" />
2025-06-05 17:40:58 +08:00
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
2025-06-12 19:21:26 +08:00
<el-button v-if="activeTab === 'basic'" type="primary" @click="handleAddSubmit('basic')">提交</el-button>
2025-06-05 17:40:58 +08:00
<el-button v-if="activeTab === 'basic'" :disabled="disabledProperty" @click="activeTab = 'property'">下一步</el-button>
<el-button v-else @click="activeTab = 'basic'">上一步</el-button>
<el-button v-if="activeTab === 'property'" type="primary" @click="handleAddSubmit('property')"> 提交 </el-button>
</span>
</template>
</el-dialog>
<!-- 查看和编辑共用弹窗 -->
<el-dialog v-model="viewEditDialogVisible" :title="viewEditTitle" width="800px" top="8vh">
<el-form
ref="viewEditFormRef"
:model="viewEditFormData"
:rules="isView ? {} : editRules"
label-width="120px"
class="view-edit-form"
:disabled="isView"
>
<h3 class="section-title">土地基本信息</h3>
<el-form-item label="地块名称" prop="landName">
<el-input v-model="viewEditFormData.landName" />
</el-form-item>
<el-form-item label="面积(亩)" prop="area">
<el-input-number v-model="viewEditFormData.area" :min="0" :precision="2" :step="0.1" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="土地类型" prop="landType">
<el-select v-model="viewEditFormData.landType" clearable>
<el-option v-for="item in landTypeOptions" :key="item.id" :label="item.landType" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="具体位置" prop="address">
<el-input v-model="viewEditFormData.address" />
</el-form-item>
<el-form-item label="所属网格" prop="gridId">
<AreaCascader v-model="viewEditFormData.regionCode" v-model:grid-id="viewEditFormData.gridId" :width="500" label="" />
2025-06-05 17:40:58 +08:00
</el-form-item>
<el-form-item label="土壤类型" prop="soilTypeId">
<url-select
v-model="viewEditFormData.soilTypeId"
url="/land-resource/baseInfo/soilTypePage"
:params="{ current: 1, size: 100 }"
label-key="soilType"
value-key="id"
:clearable="true"
/>
</el-form-item>
<el-form-item label="土地照片" prop="landUrl">
<FileUploader v-model="viewEditFormData.landUrl" :limit="1" />
2025-06-05 17:40:58 +08:00
</el-form-item>
<el-form-item label="土地范围" prop="scope">
<el-input v-model="viewEditFormData.scope" />
</el-form-item>
<h3 class="section-title">土地产权信息</h3>
<el-form-item label="产权人姓名" prop="propertyName">
<el-input v-model="viewEditFormData.propertyName" />
</el-form-item>
<el-form-item label="联系方式" prop="propertyPhone">
<el-input v-model="viewEditFormData.propertyPhone" />
</el-form-item>
<el-form-item label="产权编号" prop="landCode">
<el-input v-model="viewEditFormData.landCode" />
</el-form-item>
<el-form-item label="产权证书" prop="propertyCertificateUrl">
<FileUploader v-model="viewEditFormData.propertyCertificateUrl" :limit="1" />
2025-06-05 17:40:58 +08:00
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="viewEditDialogVisible = false">{{ isView ? '关闭' : '取消' }}</el-button>
<el-button v-if="isView" type="primary" @click="handleEdit">编辑</el-button>
<el-button v-if="!isView" type="primary" @click="handleView">查看</el-button>
<el-button v-if="!isView" type="primary" @click="handleViewEditSubmit"> 提交 </el-button>
</span>
</template>
</el-dialog>
2025-03-27 15:56:05 +08:00
</section>
2025-02-26 13:36:43 +08:00
</template>
<script setup>
2025-06-05 17:40:58 +08:00
import { ref, reactive, onMounted, computed, nextTick } from 'vue';
2025-03-03 16:28:34 +08:00
import { CRUD_OPTIONS } from '@/config';
2025-06-05 17:40:58 +08:00
import { getLandList, saveBaseInfo, saveProperty, editLand, deleteLand } from '@/apis/landResourceManagement/landManagement';
import { CommonUpload } from '@/apis/index';
import request from '@/utils/axios';
2025-04-03 17:23:48 +08:00
2025-06-05 17:40:58 +08:00
// ==============================
// 状态管理
// ==============================
2025-03-05 17:30:00 +08:00
const loading = ref(false);
2025-03-03 16:28:34 +08:00
const crudRef = ref();
2025-06-05 17:40:58 +08:00
const addDialogVisible = ref(false);
const activeTab = ref('basic');
const viewEditDialogVisible = ref(false);
const isView = ref(false);
// 控制是否启用验证规则
const isValidationEnabled = ref(false);
// 表单引用
const basicFormRef = ref(null);
const propertyFormRef = ref(null);
const viewEditFormRef = ref(null);
// 分页数据
2025-03-05 17:30:00 +08:00
const pageData = ref({
2025-03-03 16:28:34 +08:00
currentPage: 1,
pageSize: 10,
2025-02-26 13:36:43 +08:00
total: 0,
});
2025-06-05 17:40:58 +08:00
// 查询参数
const query = ref({
landType: null,
current: 1,
size: 10,
regionCode: null,
keyword: '',
gridId: null,
});
// 表格数据
2025-03-03 16:28:34 +08:00
const data = ref([]);
2025-06-05 17:40:58 +08:00
const landTypeOptions = ref([]);
// 表单数据
const formDataBasic = ref({
landName: '',
regionCode: '',
gridId: '',
area: 0,
landType: '',
address: '',
soilTypeId: '',
landUrl: '',
scope: '',
});
const formDataProperty = ref({
id: '',
landName: '',
propertyName: '',
propertyPhone: '',
landCode: '',
propertyCertificateUrl: '',
});
const viewEditFormData = ref({
id: '',
landName: '',
gridId: '',
gridName: '',
area: 0,
landType: '',
landTypeId: '',
landTypeName: '',
address: '',
detailAddress: '',
fullLandType: '',
fullRegionName: '',
soilTypeId: '',
soilType: '',
landUrl: '',
scope: '',
propertyName: '',
propertyPhone: '',
landCode: '',
propertyCertificateUrl: '',
updateTime: '',
});
// OSS URL
const ossUrl = 'http://gov-cloud.oss-cn-chengdu.aliyuncs.com/';
// ==============================
// 计算属性
// ==============================
const viewEditTitle = computed(() => {
return isView.value ? '查看' : '编辑';
});
const disabledProperty = computed(() => {
return formDataBasic.value.id ? false : true;
});
// ==============================
// 表单验证规则
// ==============================
const basicRules = reactive(
isValidationEnabled.value
? {
landName: [{ required: true, message: '请输入地块名称', trigger: 'blur' }],
area: [
{ required: true, message: '请输入面积', trigger: 'blur' },
{
type: 'number',
min: 0,
message: '面积必须大于0',
trigger: 'blur',
},
],
landType: [{ required: true, message: '请选择土地类型', trigger: 'change' }],
address: [{ required: true, message: '请输入具体位置', trigger: 'blur' }],
gridId: [{ required: true, message: '请选择所属网格', trigger: 'change' }],
soilTypeId: [{ required: true, message: '请选择土壤类型', trigger: 'change' }],
landUrl: [{ required: true, message: '请上传土地照片', trigger: 'change' }],
scope: [{ required: true, message: '请输入土地范围', trigger: 'blur' }],
}
: {}
);
const propertyRules = reactive(
isValidationEnabled.value
? {
propertyName: [{ required: true, message: '请输入产权人姓名', trigger: 'blur' }],
propertyPhone: [
{ required: true, message: '请输入联系方式', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur',
},
],
landCode: [{ required: true, message: '请输入产权编号', trigger: 'blur' }],
propertyCertificateUrl: [{ required: true, message: '请上传产权证书', trigger: 'change' }],
}
: {}
);
const editRules = reactive(
isValidationEnabled.value
? {
...basicRules,
...propertyRules,
}
: {}
);
// ==============================
// CRUD 配置
// ==============================
2025-03-03 16:28:34 +08:00
const option = reactive({
...CRUD_OPTIONS,
2025-06-05 17:40:58 +08:00
addBtn: false,
searchBtn: false,
emptyBtn: false,
2025-03-03 16:28:34 +08:00
column: [
2025-06-05 17:40:58 +08:00
{ label: '地块名称', prop: 'landName' },
{ label: '所属网格', prop: 'gridName' },
2025-06-12 19:21:26 +08:00
{ label: '面积', prop: 'area', formatter: (row, column, cellValue) => `${cellValue}` },
2025-06-05 17:40:58 +08:00
{ label: '土地类型', prop: 'landTypeName' },
{ label: '所属行政区域', prop: 'fullRegionName' },
{ label: '具体位置', prop: 'address' },
{ label: '产权人姓名', prop: 'propertyName' },
{ label: '产权人联系方式', prop: 'propertyPhone' },
{ label: '产权编号', prop: 'landCode' },
{ label: '信息录入时间', prop: 'createTime' },
{ label: '信息更新时间', prop: 'updateTime' },
2025-03-03 16:28:34 +08:00
],
2025-03-25 08:45:53 +01:00
actions: [
{
name: '查看',
icon: 'view',
event: ({ row }) => handleView(row),
},
2025-04-03 17:23:48 +08:00
{
name: '编辑',
icon: 'edit',
2025-06-05 17:40:58 +08:00
event: ({ row }) => handleEdit(row),
2025-04-03 17:23:48 +08:00
},
2025-03-25 08:45:53 +01:00
{
type: 'danger',
name: '删除',
icon: 'delete',
event: ({ row }) => handleDelete(row.id),
},
],
2025-03-03 16:28:34 +08:00
});
2025-06-05 17:40:58 +08:00
// ==============================
// 方法 - 数据获取
// ==============================
const getData = async () => {
2025-03-05 17:30:00 +08:00
loading.value = true;
2025-06-05 17:40:58 +08:00
try {
const res = await getLandList(query.value);
const { current, size, total, records } = res.data;
2025-03-05 17:30:00 +08:00
data.value = records;
2025-06-05 17:40:58 +08:00
pageData.value = {
currentPage: current || 1,
pageSize: size || 10,
total: total,
};
} catch (error) {
console.error('获取数据失败', error);
} finally {
loading.value = false;
2025-02-27 11:34:43 +08:00
}
2025-06-05 17:40:58 +08:00
};
const fetchLandTypeData = async () => {
try {
const response = await request.get('/land-resource/baseInfo/landTree', { params: { status: '1' } });
if (response.code === 200) {
landTypeOptions.value = extractLeafNodes(response.data);
}
} catch (error) {
console.error('获取土地类型数据失败', error);
}
2025-06-05 17:40:58 +08:00
};
2025-06-05 17:40:58 +08:00
// ==============================
// 方法 - 表单操作
// ==============================
const handleAdd = () => {
// 重置表单
formDataBasic.value = {
landName: '',
gridId: '',
area: 0,
landType: '',
address: '',
soilTypeId: '',
landUrl: '',
scope: '',
};
formDataProperty.value = {
id: '',
landName: '',
propertyName: '',
propertyPhone: '',
landCode: '',
propertyCertificateUrl: '',
};
addDialogVisible.value = true;
activeTab.value = 'basic';
// 重置表单验证
nextTick(() => {
if (basicFormRef.value) {
basicFormRef.value.resetFields();
}
if (propertyFormRef.value) {
propertyFormRef.value.resetFields();
}
2025-02-27 17:30:06 +08:00
});
2025-06-05 17:40:58 +08:00
};
2025-04-03 17:23:48 +08:00
2025-06-05 17:40:58 +08:00
const handleView = (row) => {
isView.value = true;
viewEditFormData.value = { ...row };
viewEditDialogVisible.value = true;
};
const handleEdit = (row) => {
isView.value = false;
viewEditFormData.value = { ...row };
viewEditDialogVisible.value = true;
// 重置表单验证
// nextTick(() => {
// if (viewEditFormRef.value) {
// viewEditFormRef.value.resetFields();
// }
// });
};
const handleDelete = (id) => {
// 删除逻辑
deleteLand(id).then(() => {
getData();
});
};
const handleSearch = () => {
query.value.current = 1;
getData();
};
const resetSearch = () => {
query.value = {
landType: null,
current: 1,
size: 10,
regionCode: null,
keyword: '',
gridId: null,
};
getData();
};
// ==============================
// 方法 - 表单提交
// ==============================
const handleAddSubmit = async (tab) => {
try {
if (tab === 'basic') {
await basicFormRef.value.validate();
const res = await saveBaseInfo(formDataBasic.value);
if (res.code === 200) {
activeTab.value = 'property';
formDataProperty.value.id = res.data.id;
formDataProperty.value.landName = formDataBasic.value.landName;
}
} else if (tab === 'property') {
await propertyFormRef.value.validate();
const res = await saveProperty(formDataProperty.value);
if (res.code === 200) {
addDialogVisible.value = false;
getData();
}
}
} catch (error) {
console.error('表单提交失败', error);
2025-04-03 17:23:48 +08:00
}
2025-06-05 17:40:58 +08:00
};
const handleViewEditSubmit = async () => {
try {
await viewEditFormRef.value.validate();
// 合并提交逻辑
const { id, ...formData } = viewEditFormData.value;
if (id) {
// 更新逻辑
// await updateLandInfo({ id, ...formData });
await editLand({ id, ...formData });
viewEditDialogVisible.value = false;
getData();
}
} catch (error) {
console.error('表单提交失败', error);
2025-04-03 17:23:48 +08:00
}
2025-03-14 17:33:44 +08:00
};
2025-06-05 17:40:58 +08:00
// ==============================
// 方法 - 文件上传
// ==============================
const customUploadRequest = async (options) => {
const formData = new FormData();
formData.append('file', options.file);
try {
const response = await CommonUpload(formData);
options.onSuccess(response, options.file);
return response;
} catch (err) {
console.error('上传失败', err);
options.onError(err);
throw err;
}
2025-03-14 17:33:44 +08:00
};
2025-06-05 17:40:58 +08:00
const handleUploadSuccess = (res, file, type) => {
if (res?.data?.url) {
const url = res.data.url;
switch (type) {
case 'basic':
formDataBasic.value.landUrl = url;
break;
case 'property':
formDataProperty.value.propertyCertificateUrl = url;
break;
case 'viewEditBasic':
viewEditFormData.value.landUrl = url;
break;
case 'viewEditProperty':
viewEditFormData.value.propertyCertificateUrl = url;
break;
}
// 手动触发验证
nextTick(() => {
if (type === 'basic' && basicFormRef.value) {
basicFormRef.value.validateField('landUrl');
} else if (type === 'property' && propertyFormRef.value) {
propertyFormRef.value.validateField('propertyCertificateUrl');
} else if (type.includes('viewEdit') && viewEditFormRef.value) {
const field = type === 'viewEditBasic' ? 'landUrl' : 'propertyCertificateUrl';
viewEditFormRef.value.validateField(field);
2025-03-14 17:33:44 +08:00
}
});
2025-06-05 17:40:58 +08:00
}
};
// ==============================
// 工具函数
// ==============================
const extractLeafNodes = (nodes) => {
let result = [];
for (const node of nodes) {
if (node.children && node.children.length > 0) {
result = result.concat(extractLeafNodes(node.children));
} else if (node.status === '1') {
result.push(node);
}
}
return result;
2025-03-14 17:33:44 +08:00
};
2025-04-03 17:23:48 +08:00
2025-06-05 17:40:58 +08:00
// ==============================
// 生命周期
// ==============================
onMounted(() => {
getData();
fetchLandTypeData();
});
2025-02-26 13:36:43 +08:00
</script>
<style lang="scss" scoped>
2025-06-05 17:40:58 +08:00
.custom-page {
padding: 20px;
height: calc(100vh - 150px);
overflow-y: auto;
// background-color: #409eff;
.custom-search {
display: flex;
align-items: center;
margin-bottom: 16px;
.el-button {
margin-left: 12px;
}
}
}
2025-06-05 17:40:58 +08:00
.form-container,
.view-edit-form {
padding: 0 20px;
max-height: calc(100vh - 300px);
overflow-y: auto;
.el-form-item {
margin-bottom: 22px;
}
}
.view-edit-form {
.section-title {
color: #409eff;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin: 20px 0;
}
.preview-image {
max-width: 100%;
max-height: 200px;
border: 1px solid #eee;
border-radius: 4px;
display: block;
margin-top: 8px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
padding: 10px 20px 0;
border-top: 1px solid #eee;
}
2025-02-26 13:36:43 +08:00
</style>