Files
threeonecheck_web/pages/hiddendanger/Inspection.vue
2026-02-08 09:30:43 +08:00

901 lines
23 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>
<view class=" page padding">
<view class="padding radius bg-white list-list margin-bottom" v-for="item in hiddenDangerList"
:key="item.hazardId">
<view class="flex justify-between margin-bottom">
<view class="text-bold text-black">{{item.title}}</view>
<view class="text-blue">{{item.statusName}}</view>
</view>
<view class="flex margin-bottom">
<view class="text-gray">隐患等级</view>
<view class="level-tag" :class="{
'level-minor': item.levelName === '轻微隐患',
'level-normal': item.levelName === '一般隐患',
'level-major': item.levelName === '重大隐患'
}">{{item.levelName}}</view>
</view>
<view class="flex margin-bottom">
<view class="text-gray" style="white-space: nowrap;">隐患位置</view>
<view class="text-black">{{item.address}}</view>
</view>
<view class="flex margin-bottom">
<view class="text-gray">创建时间</view>
<view class="text-black">{{item.createdAt}}</view>
</view>
<view class="flex justify-end" style="gap: 10rpx;">
<!-- 所有状态都显示查看详情 -->
<button class="round cu-btn lg light bg-blue" @click="details(item)">查看详情</button>
<!-- 待整改状态canEdit为true时显示隐患交办和立即整改为false时不显示 -->
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn lg light bg-blue" @click="assignHazard(item)">隐患交办</button>
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn lg bg-blue" @click="Rectification(item)">立即整改</button>
<!-- 待验收显示编辑整改信息和立即验收 -->
<button v-if="item.statusName === '待验收' && item.canEdit"
class="round cu-btn lg light bg-blue" @click="editRectification(item)">编辑整改信息</button>
<button v-if="item.statusName === '待验收' && canAcceptance"
class="round cu-btn lg bg-blue" @click="acceptance(item)">立即验收</button>
<!-- 待交办显示隐患交办 -->
<button v-if="item.statusName === '待交办'"
class="round cu-btn lg bg-blue" @click="assignHazard(item)">隐患交办</button>
</view>
</view>
<!-- 固定在底部的悬浮新增按钮 -->
<view class="fixed-add-btn" @click="showAddPopup = true">
<text class="cuIcon-add"></text>
<text>新增</text>
</view>
<!-- 新增弹窗 -->
<u-popup :show="showAddPopup" mode="center" round="20" @close="showAddPopup = false">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title text-bold">新增隐患排查</view>
<view class="popup-close" @click="showAddPopup = false">×</view>
</view>
<scroll-view class="popup-body" scroll-y>
<view class="flex margin-bottom">
<view class="text-gray">隐患图片/视频</view>
<view class="text-red">*</view>
</view>
<up-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple
:maxCount="10"></up-upload>
<view class="text-gray text-sm">必填请上传现场照片或者视频作为隐患证据</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患标题</view>
<view class="text-red">*</view>
</view>
<up-input placeholder="请输入内容" border="surround" v-model="formData.title"></up-input>
<view class="text-sm text-gray margin-top-xs">请用简洁的语言概括隐患要点</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患等级</view>
<view class="text-red">*</view>
</view>
<up-choose v-model="formData.level" :options="levelOptions" :wrap="false" item-width="183rpx"
item-height="72rpx"></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患来源</view>
<view class="text-red">*</view>
</view>
<up-choose v-model="formData.source" :options="sourceOptions" :wrap="false" item-width="183rpx"
item-height="72rpx"></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患位置</view>
<view class="text-red">*</view>
</view>
<view class="address-box">
<up-input class="address-input-wrapper" v-model="selectedAddress" placeholder="请输入地址" border="surround"></up-input>
<button class="btn-address bg-blue" @tap.stop="chooseLocation">选择地址</button>
</view>
<view class="text-gray text-sm margin-top-xs">办公楼3层东侧消防通道生产车间A区设备旁等或点击"选择地址"按钮在地图上选择</view>
<!-- 隐患区域选择 -->
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患区域</view>
</view>
<view class="select-trigger" @click="showAreaPicker = true">
<view class="select-value" :class="{ 'placeholder': !selectedAreaName }">
{{ selectedAreaName || '请选择隐患区域' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患描述</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="formData.description" placeholder="请输入内容"></up-textarea>
<view class="text-gray text-sm margin-top-xs">请详细说明隐患现状潜在风险及影响范围</view>
<view class="text-gray margin-bottom margin-top">隐患标签</view>
<up-choose v-model="formData.tagIndex" :options="tagOptions"></up-choose>
<view class="text-gray text-sm">可选择多个相关标签对隐患进行分类</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showAddPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleAdd">确定</button>
</view>
</view>
</u-popup>
<!-- 区域选择弹窗 -->
<u-popup :show="showAreaPicker" mode="bottom" round="20" @close="showAreaPicker = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showAreaPicker = false">取消</view>
<view class="picker-title">选择隐患区域</view>
<view class="picker-confirm" @click="confirmAreaSelect">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
v-for="item in areaList"
:key="item.id"
class="picker-item"
:class="{ 'picker-item-active': tempAreaId === item.id }"
@click="tempAreaId = item.id"
>
<view class="flex align-center">
<view class="area-color-dot" :style="{ backgroundColor: item.color }"></view>
<text>{{ item.name }}</text>
</view>
<text v-if="tempAreaId === item.id" class="cuIcon-check text-blue"></text>
</view>
<view v-if="areaList.length === 0" class="text-gray text-center padding">
暂无区域数据
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import {
ref,
computed,
reactive,
watch
} from 'vue'
import {
onLoad,
onShow
} from '@dcloudio/uni-app'
import {
addHiddenDanger,
enterCheckPlan,
getCheckTaskDetail,
getMyHiddenDangerList,
getHiddenDangerLabelList
} from '@/request/api.js'
import { getAreaList } from '@/request/three_one_api/area.js'
import {
baseUrl,
getToken
} from '@/request/request.js'
// 弹窗控制
const showAddPopup = ref(false);
// 获取用户角色判断是否有验收权限admin或manage才能验收
const userRole = ref('');
const canAcceptance = computed(() => {
return userRole.value === 'admin' || userRole.value === 'manage';
});
// 从storage获取用户信息
const getUserRole = () => {
try {
const userInfoStr = uni.getStorageSync('userInfo');
if (userInfoStr) {
const userInfo = JSON.parse(userInfoStr);
userRole.value = userInfo.role || '';
console.log('当前用户角色:', userRole.value);
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
};
getUserRole();
// 任务相关ID从接口获取
const taskId = ref('');
const checkPointId = ref('');
// 获取任务详情
const fetchTaskInfo = async (oneTableId) => {
try {
// 第一步:调用 start 接口获取 taskId
const startRes = await enterCheckPlan(oneTableId);
if (startRes.code === 0 && startRes.data) {
const tid = startRes.data.taskId;
// 第二步:用 taskId 获取任务详情
const detailRes = await getCheckTaskDetail(tid);
if (detailRes.code === 0 && detailRes.data) {
taskId.value = detailRes.data.taskId;
checkPointId.value = detailRes.data.checkPointId;
}
}
} catch (error) {
console.error('获取任务信息失败:', error);
}
};
onLoad((options) => {
if (options.id) {
fetchTaskInfo(options.id);
}
});
// 表单数据
const formData = reactive({
title: '', // 隐患标题
level: 0, // 隐患等级索引
description: '', // 隐患描述
tagIndex: 0, // 隐患标签索引
source: '', // 隐患来源
});
// 经纬度
const lng = ref(0);
const lat = ref(0);
// 地址选择 - 调用腾讯地图
const selectedAddress = ref('');
// 区域选择相关
const showAreaPicker = ref(false);
const areaList = ref([]);
const selectedAreaId = ref('');
const selectedAreaName = ref('');
const tempAreaId = ref('');
// 获取区域列表
const fetchAreaList = async () => {
try {
const res = await getAreaList();
if (res.code === 0 && res.data && res.data.records) {
areaList.value = res.data.records;
}
} catch (error) {
console.error('获取区域列表失败:', error);
}
};
fetchAreaList();
// 确认区域选择
const confirmAreaSelect = () => {
if (tempAreaId.value) {
selectedAreaId.value = tempAreaId.value;
const selected = areaList.value.find(item => item.id === tempAreaId.value);
selectedAreaName.value = selected ? selected.name : '';
}
showAreaPicker.value = false;
};
const chooseLocation = () => {
console.log('chooseLocation called');
// 先关闭弹窗,避免在弹窗中调用地图选择出现问题
showAddPopup.value = false;
setTimeout(() => {
// 先获取当前位置,再打开地图选择
uni.getLocation({
type: 'gcj02', // 使用国测局坐标系(腾讯地图使用)
success: (locationRes) => {
console.log('获取当前位置成功:', locationRes);
// 使用当前位置作为地图初始中心点
uni.chooseLocation({
latitude: locationRes.latitude,
longitude: locationRes.longitude,
success: (res) => {
console.log('选择位置成功:', res);
// 获取选择的位置信息
selectedAddress.value = res.address + (res.name ? `(${res.name})` : '');
lng.value = res.longitude;
lat.value = res.latitude;
// 重新打开弹窗
showAddPopup.value = true;
},
fail: (err) => {
console.error('选择位置失败:', err);
// 重新打开弹窗
showAddPopup.value = true;
// 用户取消选择不提示
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({
title: '选择位置失败',
icon: 'none'
});
}
}
});
},
fail: (err) => {
console.error('获取当前位置失败:', err);
// 即使获取位置失败,也打开地图选择(不带初始位置)
uni.chooseLocation({
success: (res) => {
console.log('选择位置成功:', res);
selectedAddress.value = res.address + (res.name ? `(${res.name})` : '');
lng.value = res.longitude;
lat.value = res.latitude;
showAddPopup.value = true;
},
fail: (chooseErr) => {
console.error('选择位置失败:', chooseErr);
showAddPopup.value = true;
if (chooseErr.errMsg && chooseErr.errMsg.indexOf('cancel') === -1) {
uni.showToast({
title: '选择位置失败',
icon: 'none'
});
}
}
});
}
});
}, 300);
};
// 确定新增
const handleAdd = async () => {
// 表单验证
if (!formData.title) {
uni.showToast({
title: '请输入隐患标题',
icon: 'none'
});
return;
}
if (fileList1.value.length === 0) {
uni.showToast({
title: '请上传隐患图片/视频',
icon: 'none'
});
return;
}
// 构建附件列表 - 从上传返回的url中提取文件名
const attachments = fileList1.value.map(file => {
// 确保 url 是字符串
let url = '';
if (typeof file.url === 'string') {
url = file.url;
} else if (file.url && typeof file.url === 'object') {
url = file.url.url || file.url.path || '';
}
// 从url中提取文件名
const fileName = (typeof url === 'string' && url) ? url.split('/').pop() : (file.name || '');
return {
fileName: fileName || '',
filePath: url || '',
fileType: file.type || 'image/png',
fileSize: file.size || 0
};
});
// 获取隐患标签ID
const selectedTag = tagOptions.value[formData.tagIndex];
const tagId = selectedTag ? selectedTag.id : null;
console.log("innnn",sourceOptions);
// 构建请求参数
const params = {
title: formData.title, //标题
level: formData.level + 1, // 1.轻微隐患 2.一般隐患 3.重大隐患
lng: lng.value || 0, //经度
lat: lat.value || 0, //纬度
address: selectedAddress.value || '', //详细地址
areaId: selectedAreaId.value || null, //隐患区域ID
description: formData.description || '', //隐患描述
tagId: tagId, //隐患标签ID
taskId: taskId.value, //关联任务ID
checkPointId: checkPointId.value, //关联检查点ID
source: sourceOptions.value[formData.source]?.title || '', //隐患来源(随手拍、企业自查、行业互查、专家诊查)
attachments: attachments, //附件列表(图片/视频)
};
console.log('提交的参数:', params);
//
try {
const res = await addHiddenDanger(params);
if (res.code === 0) {
uni.showToast({
title: '新增成功',
icon: 'success'
});
showAddPopup.value = false;
// 重置表单
formData.title = '';
formData.level = 0;
formData.description = '';
formData.tagIndex = 0;
selectedAddress.value = '';
selectedAreaId.value = '';
selectedAreaName.value = '';
fileList1.value = [];
// 刷新隐患列表
fetchHiddenDangerList();
} else {
uni.showToast({
title: res.msg || '新增失败',
icon: 'none'
});
}
} catch (error) {
console.error(error);
uni.showToast({
title: '请求失败',
icon: 'none'
});
}
};
//获取隐患列表
const hiddenDangerList = ref([]);
const fetchHiddenDangerList = async () => {
try {
const res = await getMyHiddenDangerList();
if (res.code === 0) {
hiddenDangerList.value = res.data.records;
} else {
uni.showToast({
title: res.msg || '获取隐患列表失败',
icon: 'none'
});
}
} catch (error) {
console.error(error);
uni.showToast({
title: '请求失败',
icon: 'none'
});
}
};
// 页面显示时刷新列表(从交办、验收页面返回时自动刷新)
onShow(() => {
fetchHiddenDangerList();
});
const details = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/view?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
const Rectification = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/rectification?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
// 编辑整改信息(待验收状态)
const editRectification = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/rectification?rectifyId=${item.rectifyId}&isEdit=1`
})
}
const acceptance = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/acceptance?hazardId=${item.hazardId}&assignId=${item.assignId}&rectifyId=${item.rectifyId}`
})
}
// 隐患交办
const assignHazard = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/assignment?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
const fileList1 = ref([]);
// 删除图片
const deletePic = (event) => {
fileList1.value.splice(event.index, 1);
};
// 新增图片
const afterRead = async (event) => {
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
let lists = [].concat(event.file);
let fileListLen = fileList1.value.length;
lists.map((item) => {
fileList1.value.push({
...item,
status: 'uploading',
message: '上传中',
});
});
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url);
let item = fileList1.value[fileListLen];
fileList1.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: result,
});
fileListLen++;
}
};
const uploadFilePromise = (filePath) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (res) => {
const data = JSON.parse(res.data);
if (data.code === 0) {
resolve(data.data);
} else {
reject(data.msg || '上传失败');
}
},
fail: (err) => {
console.error('上传失败:', err);
reject(err);
}
});
});
};
const tagOptions = ref([]);
const fetchTagOptions = async () => {
try {
const res = await getHiddenDangerLabelList();
if (res.code === 0) {
// 将接口返回的 name 字段映射为 title 字段,供 up-choose 组件使用
tagOptions.value = res.data.map(item => ({
id: item.id,
title: item.name
}));
} else {
uni.showToast({
title: res.msg || '获取标签列表失败',
icon: 'none'
});
}
} catch (error) {
console.error(error);
uni.showToast({
title: '请求失败',
icon: 'none'
});
}
};
fetchTagOptions();
//
// 隐患等级选项
const levelOptions = ref([{
id: 1,
title: '轻微隐患'
},
{
id: 2,
title: '一般隐患'
},
{
id: 3,
title: '重大隐患'
}
])
//隐患来源
const sourceOptions = ref([{
id: 1,
title: '随手拍'
},
{
id: 2,
title: '企业自查'
},
{
id: 3,
title: '行业互查'
},
{
id: 4,
title: '专家诊查'
}
])
// 监听隐患来源选择变化
watch(() => formData.source, (newVal) => {
const selected = sourceOptions.value[newVal];
console.log('隐患来源选择结果:', {
索引: newVal,
选中项: selected,
id: selected?.id,
title: selected?.title
});
});
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 120rpx; // 给底部按钮留出空间
}
// 固定在底部的悬浮新增按钮
.fixed-add-btn {
position: fixed;
bottom: 40rpx;
left: 30rpx;
right: 30rpx;
height: 88rpx;
background: linear-gradient(135deg, #667eea 0%, #2668EA 100%);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 32rpx;
font-weight: 500;
box-shadow: 0 8rpx 24rpx rgba(38, 104, 234, 0.4);
z-index: 100;
.cuIcon-add {
margin-right: 10rpx;
font-size: 36rpx;
}
}
.list-list {
background: #FFFFFF;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-left: 5px solid #2667E9;
border-radius: 20rpx;
padding: 20rpx;
}
// 隐患等级标签样式
.level-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
}
// 轻微隐患
.level-minor {
background: #F6FFED;
border: 2rpx solid #B7EB8F;
color: #52C41A;
}
// 一般隐患
.level-normal {
background: #FFF7E6;
border: 2rpx solid #FFD591;
color: #FA8C16;
}
// 重大隐患
.level-major {
background: #FFF1F0;
border: 2rpx solid #FFA39E;
color: #F5222D;
}
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
}
.popup-body {
padding: 30rpx;
max-height: 900rpx;
overflow-y: auto;
}
.popup-footer {
display: flex;
border-top: 1rpx solid #eee;
button {
flex: 1;
height: 90rpx;
line-height: 90rpx;
border-radius: 0;
font-size: 30rpx;
&::after {
border: none;
}
}
.btn-cancel {
background: #fff;
color: #666;
}
.btn-confirm {
color: #fff;
}
}
.address-box {
display: flex;
align-items: center;
gap: 20rpx;
.address-input-wrapper {
flex: 1;
}
.btn-address {
flex-shrink: 0;
height: 70rpx;
line-height: 70rpx;
padding: 0 30rpx;
font-size: 26rpx;
border-radius: 8rpx;
color: #fff;
&::after {
border: none;
}
}
}
// 选择器触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
.select-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.placeholder {
color: #c0c4cc;
}
}
}
// 选择弹窗样式
.picker-popup {
background: #fff;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.picker-cancel {
font-size: 28rpx;
color: #999;
}
.picker-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.picker-confirm {
font-size: 28rpx;
color: #2667E9;
}
}
.picker-body {
max-height: 600rpx;
padding: 0 30rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.picker-item-active {
color: #2667E9;
}
}
}
// 区域颜色圆点
.area-color-dot {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
margin-right: 16rpx;
}
.address-popup {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.address-popup-body {
padding: 30rpx;
max-height: 500rpx;
.search-box {
margin-bottom: 20rpx;
.search-input {
width: 100%;
background: #f5f5f5;
border-radius: 8rpx;
padding: 16rpx 20rpx;
font-size: 28rpx;
}
}
.address-list {
max-height: 350rpx;
overflow-y: auto;
}
.address-item {
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #eee;
font-size: 26rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.address-item-active {
background: #EBF2FC;
color: #2667E9;
}
}
}
</style>
<style lang="scss">
/* 全局样式覆盖 up-tag 文字居中 */
.u-tag {
justify-content: center !important;
}
</style>