2025-07-02 12:00:34 +08:00

641 lines
22 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="custom-page">
<avue-crud
ref="crudRef"
v-model:page="pageData"
:data="crudData"
:option="crudOptions"
:table-loading="loading"
@refresh-change="handleRefresh"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
>
<template #menu-left>
<el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="success" icon="download" @click="handleExport">导出</el-button>
</template>
<template #menu="scope">
<custom-table-operate :actions="getActions(scope.row)" :data="scope" />
</template>
</avue-crud>
<el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true">
<p class="form-group">任务信息</p>
<el-form :model="taskForm" :disabled="isReadonlyInfo" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任务编号" prop="taskCode">
<el-input v-model="taskForm.taskCode" placeholder="请输入任务编号" />
</el-form-item>
<el-form-item label="任务成员" prop="taskUserIds">
<!-- /land-resource/grid-member/current-user -->
<url-select
v-model="taskForm.taskUserIds"
url="/land-resource/grid-member/current-user"
placeholder="请选择任务成员"
:multiple="true"
label-key="memberName"
value-key="id"
:response-parser="(res) => res.data.memberList"
/>
</el-form-item>
<el-form-item label="注意事项" prop="notes">
<el-input v-model="taskForm.notes" type="textarea" :autosize="{ minRows: 2, maxRows: 6 }" placeholder="请输入注意事项" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务名称" prop="taskName">
<el-input v-model="taskForm.taskName" placeholder="请输入任务名称" />
</el-form-item>
<el-form-item label="巡查类型" prop="inspectionTypeCode">
<url-select
v-model="taskForm.inspectionTypeCode"
url="/system/dict/data/type/land_res_patrol_type"
placeholder="请选择巡查类型"
label-key="dictLabel"
value-key="dictValue"
:response-parser="(res) => res.data"
/>
</el-form-item>
<el-form-item label="巡查对象" prop="inspectionTarget">
<el-input v-model="taskForm.inspectionTarget" placeholder="请输入巡查对象" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 种植信息登记 -->
<div v-if="dialogTitle !== '新增'">
<p class="form-group">种植信息登记</p>
<el-form :model="plantingForm" disabled label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="备案种植作物" prop="recordedCrop">
<el-input v-model="plantingForm.recordedCrop" placeholder="请输入备案种植作物" />
</el-form-item>
<el-form-item label="实际种植作物" prop="actualCrop">
<el-input v-model="plantingForm.actualCrop" placeholder="请输入实际种植作物" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="与备案相符" prop="isCropMatch">
<el-radio-group v-model="plantingForm.isCropMatch">
<el-radio value="1">是</el-radio>
<el-radio value="0">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 巡查信息登记,只有进行过结果登记的才显示 -->
<template v-if="dialogTitle !== '查看' && dialogTitle !== '详情' && dialogTitle !== '新增' && dialogTitle !== '编辑'">
<p class="form-group">巡查信息登记</p>
<!-- 循环渲染多个表单 -->
<div v-for="(form, index) in illegalForms" :key="index" class="form-container">
<div class="form-header">
<span>记录 #{{ index + 1 }}</span>
<el-button v-if="!isReadonlyRegist" type="danger" icon="el-icon-delete" circle size="mini" @click="handleRemoveForm(index)" />
</div>
<el-form :model="form" :disabled="isReadonlyRegist" label-width="100px" class="form-item">
<el-row :gutter="20">
<el-col :span="12">
<!-- 选择地块 -->
<el-form-item label="选择地块" prop="landId">
<url-select
v-model="form.landId"
url="/land-resource/landManage/page"
placeholder="请选择地块"
label-key="landName"
value-key="id"
:params="{ current: 1, size: 999 }"
/>
</el-form-item>
<el-form-item label="是否违法" prop="illegalFlag">
<el-radio-group v-model="form.illegalFlag">
<el-radio value="1">是</el-radio>
<el-radio value="0">否</el-radio>
</el-radio-group>
</el-form-item>
<!-- 违法时间 -->
<el-form-item v-if="form.illegalFlag === '1'" label="违法时间" prop="illegalDate">
<el-date-picker
v-model="form.illegalDate"
type="datetime"
placeholder="选择违法时间"
format="YYYY年MM月DD日"
value-format="YYYY-MM-DD"
:disabled="isReadonlyRegist || form.illegalFlag === 0"
/>
</el-form-item>
<!-- 违法图片 -->
<el-form-item v-if="form.illegalFlag === '1'" label="违法图片" prop="illegalImages">
<file-uploader v-model="form.illegalImages" :limit="1" :readonly="isReadonlyRegist || form.illegalFlag === 0" />
</el-form-item>
</el-col>
<el-col v-if="form.illegalFlag === '1'" :span="12">
<el-form-item label="违法类型" prop="illegalType">
<url-select
v-model="form.illegalTypeCode"
url="/system/dict/data/type/land_inspection_illegal_type"
placeholder="请选择违法类型"
label-key="dictLabel"
value-key="dictValue"
:response-parser="(res) => res.data"
:disabled="isReadonlyRegist || form.illegalFlag === 0"
/>
</el-form-item>
<el-form-item v-if="form.illegalFlag === '1'" label="违法行为描述" prop="desc">
<el-input
v-model="form.desc"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"
placeholder="请输入违法行为描述"
:disabled="isReadonlyRegist || form.illegalFlag === 0"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div v-if="!isReadonlyRegist" class="form-footer">
<el-button type="primary" @click="handleAddForm"> 添加记录 </el-button>
</div>
</template>
<template #footer>
<div class="dialog-footer">
<!-- <el-button @click="infoCancel">取消</el-button> -->
<el-button type="primary" @click="handleSubmit()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, watch, onMounted, computed, nextTick } from 'vue';
import { CRUD_OPTIONS } from '@/config';
import { ElMessage } from 'element-plus';
import { useActionPermissions } from '@/hooks/useActionPermissions';
import { useUserStore } from '@/store/modules/user';
import {
createLandInspection,
deleteLandInspection,
updateLandInspection,
updateLandInspectionStatus,
getLandInspectionDetail,
fetchLandInspectionList,
exportLandInspection,
createInspectionResult,
createInspectionResultBatch,
deleteInspectionResult,
getInspectionResultDetail,
} from '@/apis/landResourceManagement/landInspection/index';
const dialogTitle = ref('新增');
const visible = ref(false);
const isReadonlyInfo = ref(false);
const isReadonlyRegist = ref(false);
const UserStore = useUserStore();
const user = UserStore.getUserInfo();
console.log('admin 属性:', user.admin);
const pageData = ref({
currentPage: 1,
pageSize: 10,
total: 0,
});
const searchForm = ref({
taskCode: '',
taskName: '',
userId: '',
inspectionTypeCode: '',
gridId: '',
});
const taskForm = ref({
id: '', // 任务ID
taskCode: '', // 任务编号
taskName: '', // 任务名称
taskUserIds: [], // 任务成员ID列表
taskMembers: '', // 任务成员
inspectionTypeCode: '', // 巡查类型代码
inspectionType: '', // 巡查类型
inspectionTarget: '', // 巡查对象
inspectionStatus: '', // 巡查状态
notes: '', // 注意事项
inspectionResults: [], // 巡查结果列表
});
const illegalForm = ref({
id: '', // 登记ID
inspectionId: '', // 任务ID
inspectionTaskName: '', // 巡查任务名称
landId: '', // 地块ID
landName: '', // 地块名称
illegalFlag: 0, // 是否违法
illegalDate: '', // 违法时间
illegalTypeCode: '', // 违法类型代码
illegalTypeName: '', // 违法类型名称
desc: '', // 违法描述
img: '', // 违法图片
status: '', // 状态
});
const plantingForm = ref({
id: '',
inspectionId: '', // 任务ID
inspectionTaskName: '', // 巡查任务名称
recordedCrop: '甘蔗', // 备案种植作物
isCropMatch: '1', // 种植作物与备案作物是否一致
actualCrop: '甘蔗', // 实际种植作物
});
const illegalForms = ref([{ ...illegalForm.value }]);
const crudData = ref([]);
const loading = ref(false);
const crudOptions = reactive({
...CRUD_OPTIONS,
addBtn: false,
searchBtn: false,
emptyBtn: false,
column: [
{ label: '任务编号', prop: 'taskCode' },
{ label: '任务名称', prop: 'taskName' },
{ label: '任务成员', prop: 'taskMembers' },
{ label: '巡查类型', prop: 'inspectionType' },
{ label: '巡查对象', prop: 'inspectionTarget' },
{ label: '注意事项', prop: 'notes' },
{ label: '是否违法', prop: 'isIllegal' },
{ label: '状态', prop: 'inspectionStatusName' },
],
actions: [
{
name: '查看',
icon: 'view',
event: ({ row }) => handleView(row.id),
},
{
name: '编辑',
icon: 'edit',
event: ({ row }) => handleEdit(row.id),
},
{
type: 'danger',
name: '删除',
icon: 'delete',
event: ({ row }) => handleDelete(row.id),
},
// 结果登记
{
name: '结果登记',
icon: 'result',
event: ({ row }) => handleResult(row.id),
},
// 收回任务
{
name: '收回任务',
icon: 'back',
event: ({ row }) => handleBack(row.id),
},
// 发布任务
{
name: '发布任务',
icon: 'publish',
event: ({ row }) => handlePublish(row.id),
},
// 重新发布
{
name: '重新发布',
icon: 're-publish',
event: ({ row }) => handleRePublish(row.id),
},
// 查看登记结果
{
name: '查看登记结果',
icon: 'view-result',
event: ({ row }) => handleViewResult(row),
},
],
});
onMounted(() => {
getData();
});
const getData = async () => {
loading.value = true;
try {
const response = await fetchLandInspectionList({
current: pageData.value.currentPage,
size: pageData.value.pageSize,
...searchForm.value,
});
crudData.value = response.data.records;
pageData.value.total = response.data.total;
pageData.value.currentPage = response.data.current;
pageData.value.pageSize = response.data.size;
} catch (error) {
ElMessage.error('加载数据失败');
} finally {
loading.value = false;
}
};
const handleAdd = () => {
isReadonlyInfo.value = false;
// resetForm();
dialogTitle.value = '新增';
visible.value = true;
};
const handleEdit = async (id) => {
console.log('编辑行:', id);
isReadonlyInfo.value = false;
const response = await getLandInspectionDetail(id);
taskForm.value = response.data;
// 处理返回的任务成员数组inspectionUsers[]数据为选择器需要的id数组taskUserIds[]
if (taskForm.value.inspectionUsers && Array.isArray(taskForm.value.inspectionUsers)) {
taskForm.value.taskUserIds = taskForm.value.inspectionUsers.map((user) => user.userId);
} else {
taskForm.value.taskUserIds = [];
}
dialogTitle.value = '编辑';
visible.value = true;
};
const handleView = async (id) => {
console.log('查看行:', id);
const response = await getLandInspectionDetail(id);
taskForm.value = response.data;
const arr = response.data.inspectionResults;
illegalForms.value = arr ? arr : [];
illegalForms.value.forEach((item) => {
// 如果后端只给了一个 img 字符串
if (item.img) {
// 直接包成数组
item.illegalImages = [item.img];
} else {
// 没图时给空数组
item.illegalImages = [];
}
});
isReadonlyInfo.value = true;
if (taskForm.value.inspectionUsers && Array.isArray(taskForm.value.inspectionUsers)) {
taskForm.value.taskUserIds = taskForm.value.inspectionUsers.map((user) => user.userId);
} else {
taskForm.value.taskUserIds = [];
}
dialogTitle.value = '查看';
visible.value = true;
};
const handleDelete = async (id) => {
console.log('删除ID:', id);
const response = await deleteLandInspection(id);
if (response?.code === 200) {
ElMessage.success(response?.msg || '删除成功');
getData(); // 刷新数据
}
};
const handleResult = async (id) => {
console.log('结果登记ID:', id);
await handleView(id);
isReadonlyRegist.value = false;
dialogTitle.value = '结果登记';
illegalForm.value.inspectionId = id;
nextTick(() => {
visible.value = true;
});
};
const handleBack = async (id) => {
console.log('收回任务ID:', id);
const response = await updateLandInspectionStatus({
id,
status: '02',
});
ElMessage.success(response?.msg || '发布成功');
await getData();
};
const handlePublish = async (id) => {
console.log('发布任务ID:', id);
// updateLandInspectionStatus : status -1 => 00
const response = await updateLandInspectionStatus({
id,
status: '00',
});
ElMessage.success(response?.msg || '发布成功');
await getData();
};
const handleRePublish = async (id) => {
console.log('重新发布ID:', id);
const response = await updateLandInspectionStatus({
id,
status: '00',
});
ElMessage.success(response?.msg || '发布成功');
await getData();
};
const handleViewResult = async (row) => {
console.log('查看登记结果ID:', row.id);
await handleView(row.id);
isReadonlyRegist.value = true;
// const response = await getInspectionResultDetail(row.id);
// illegalForm.value = response.data;
dialogTitle.value = '查看登记结果';
nextTick(() => {
visible.value = true;
});
};
// 删除表单
const handleRemoveForm = async (index) => {
if (illegalForms.value[index].id) {
await deleteInspectionResult(illegalForms.value[index].id);
} else {
illegalForms.value.splice(index, 1);
}
};
const handleSubmit = async () => {
try {
console.log('表单数据:', taskForm.value);
console.log('表单数据:', illegalForm.value);
// 1. 表单验证如果有refName
loading.value = true;
let response;
// 2. 提交数据
if (dialogTitle.value === '新增') {
taskForm.value.id = '';
response = await createLandInspection(taskForm.value);
// 3. 处理成功响应拦截器已处理200状态码
ElMessage.success(response?.msg || '新增成功');
visible.value = false;
await getData();
} else if (dialogTitle.value === '编辑') {
response = await updateLandInspection(taskForm.value);
// 3. 处理成功响应拦截器已处理200状态码
ElMessage.success(response?.msg || '编辑成功');
visible.value = false;
await getData();
} else if (dialogTitle.value === '结果登记') {
// 1. 只保留没有 id 的新记录,并给它们补全 img 字段
const payload = illegalForms.value
.filter((item) => !item.id)
.map((item) => {
// 安全取第一张图
const firstImage = Array.isArray(item.illegalImages) && item.illegalImages.length ? item.illegalImages[0] : '';
return {
...item,
img: firstImage,
};
});
// 2. 如果没有新记录,直接提示并返回
if (payload.length === 0) {
ElMessage.warning('没有需要登记的新违法记录');
return;
}
// 3. 调用批量创建接口 —— 按后端期待的结构传参
response = await createInspectionResultBatch(payload);
ElMessage.success(response?.msg || '结果登记成功');
// 4. 完成后重新拉列表、关闭弹窗
await getData();
}
// 可刷新表格数据
} catch (e) {
ElMessage.error('保存失败');
} finally {
loading.value = false;
}
};
const handleExport = async () => {
try {
const response = await exportLandInspection(searchForm.value);
if (response?.code === 200) {
// 假设返回的是文件下载链接
const link = document.createElement('a');
link.href = response.data; // 假设data是下载链接
link.download = 'land_inspection_export.xlsx'; // 设置下载文件名
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success('导出成功');
} else {
ElMessage.error(response?.msg || '导出失败');
}
} catch (error) {
ElMessage.error('导出失败,请稍后重试');
}
};
function handleRefresh() {
console.log('刷新数据');
}
function handleAddForm() {
console.log('添加记录');
illegalForms.value.push({ ...illegalForm.value });
}
function handleCurrentChange(val) {
console.log('当前页:', val);
}
function handleSizeChange(val) {
console.log('每页显示条数:', val);
}
// ---------------------------------------------------------------------
// 页面权限控制相关
// ---------------------------------------------------------------------
// 如果user.admin是true就是operator否则是inspector
const role = ref('operator');
if (user.admin) {
role.value = 'operator';
} else {
role.value = 'inspector';
}
// 所有操作项
const allActions = [
{ name: '查看', icon: 'view', key: 'view', event: ({ row }) => handleView(row.id) },
{ name: '编辑', icon: 'edit', key: 'edit', event: ({ row }) => handleEdit(row.id) },
{ name: '删除', icon: 'delete', key: 'delete', type: 'danger', event: ({ row }) => handleDelete(row.id) },
{ name: '结果登记', icon: 'result', key: 'result', event: ({ row }) => handleResult(row.id) },
{ name: '收回任务', icon: 'back', key: 'back', event: ({ row }) => handleBack(row.id) },
{ name: '发布任务', icon: 'publish', key: 'publish', event: ({ row }) => handlePublish(row.id) },
{ name: '重新发布', icon: 're-publish', key: 'rePublish', event: ({ row }) => handleRePublish(row.id) },
{ name: '查看登记结果', icon: 'view-result', key: 'viewResult', event: ({ row }) => handleViewResult(row) },
];
// 权限表(角色 -> 可操作项)
const permissionMap = {
admin: ['operator', 'inspector'], // 继承 operator 和 inspector 的权限
operator: ['view', 'edit', 'delete', 'publish', 'back', 'rePublish', 'viewResult'],
inspector: ['result', 'viewResult'],
};
// 状态控制表(状态码 -> 可操作项)
const statusMap = {
'-1': ['view', 'edit', 'delete', 'publish'],
'00': ['result', 'back', 'viewResult'],
'01': ['viewResult'],
'02': ['view', 'edit', 'delete', 'rePublish'],
};
function getActions(row) {
// 1. 先拿到当前角色对应的“原始权限”列表(可能是 action.key 也可能是继承的子角色名)
let raw = permissionMap[role.value] || [];
// 2. 如果 raw 里有子角色(即 permissionMap 中也存在该 key则把它平铺展开
const expanded = raw.reduce((acc, key) => {
if (permissionMap[key]) {
// key 是个角色,取它的 action.key 列表
acc.push(...permissionMap[key]);
} else {
// key 不是角色,直接当作 action.key
acc.push(key);
}
return acc;
}, []);
// 去重
const roleAllowed = Array.from(new Set(expanded));
// 3. 根据当前行状态拿到状态允许的 action.key
// 假设 row.inspectionStatus 或 row.status 存储了状态码
const statusKey = row.inspectionStatus ?? row.status;
const statusAllowed = statusMap[statusKey] || [];
// 4. 最终只取角色和状态都允许的 key
const finalKeys = roleAllowed.filter((key) => statusAllowed.includes(key));
// 5. 根据 finalKeys 过滤 allActions并保持原始顺序
return allActions.filter((a) => finalKeys.includes(a.key));
}
</script>
<style scoped lang="scss">
:deep(.el-dialog__body) {
padding: 20px;
height: calc(100vh - 300px);
overflow-y: auto;
}
.form-container {
position: relative;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.form-footer {
margin-top: 20px;
text-align: center;
}
.form-group {
font-size: 16px;
font-weight: 500;
margin: 30px 0;
color: #333333;
}
</style>