| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- <template>
- <div class="app-container">
- <el-form ref="queryRef" :inline="true" :model="searchForm" v-show="showSearch" label-width="80px">
- <el-form-item label="隐患类型" prop="hazardType">
- <el-select v-model="searchForm.hazardType" placeholder="请选择隐患类型" clearable style="width: 160px">
- <el-option v-for="d in hazardTypeDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- <el-form-item label="隐患等级" prop="hazardLevel">
- <el-select v-model="searchForm.hazardLevel" placeholder="请选择隐患等级" clearable style="width: 160px">
- <el-option v-for="d in hazardLevelDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- <el-form-item label="状态" prop="hazardStatus">
- <el-select v-model="searchForm.hazardStatus" placeholder="请选择状态" clearable style="width: 160px">
- <el-option v-for="d in hazardStatusDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- <el-form-item label="报送状态" prop="isSubmitted">
- <el-select v-model="searchForm.isSubmitted" placeholder="请选择报送状态" clearable style="width: 160px">
- <el-option label="未报送" :value="0" />
- <el-option label="已报送" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="关联管网" prop="networkId">
- <el-select-v2 v-model="searchForm.networkId" placeholder="请选择关联管网" clearable filterable style="width: 200px" :options="networkSelectOptions" @focus="loadNetworkOptions" />
- </el-form-item>
- <el-form-item label="关联设备" prop="equipmentId">
- <el-select-v2 v-model="searchForm.equipmentId" placeholder="请选择关联设备" clearable filterable style="width: 200px" :options="equipmentSelectOptions" @focus="loadEquipmentOptions" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
- <el-button icon="Refresh" @click="handleReset">重置</el-button>
- </el-form-item>
- </el-form>
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
- </el-col>
- <right-toolbar v-model:showSearch="showSearch" @queryTable="loadData" />
- </el-row>
- <el-table :data="tableData" v-loading="loading">
- <el-table-column prop="hazardNo" label="隐患编号" width="130" show-overflow-tooltip />
- <el-table-column prop="hazardType" label="隐患类型" width="100" align="center">
- <template #default="{ row }">{{ getDictLabel(hazardTypeDict, row.hazardType) }}</template>
- </el-table-column>
- <el-table-column prop="hazardLevel" label="隐患等级" width="100" align="center">
- <template #default="{ row }">
- <el-tag :type="levelTag(row.hazardLevel)">{{ getDictLabel(hazardLevelDict, row.hazardLevel) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="hazardDesc" label="隐患描述" min-width="180" show-overflow-tooltip />
- <el-table-column prop="hazardSource" label="隐患来源" width="110" align="center">
- <template #default="{ row }">{{ getDictLabel(hazardSourceDict, row.hazardSource) }}</template>
- </el-table-column>
- <el-table-column prop="hazardStatus" label="状态" width="100" align="center">
- <template #default="{ row }">
- <el-tag :type="statusTag(row.hazardStatus)">{{ getDictLabel(hazardStatusDict, row.hazardStatus) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="隐患图片" width="90" align="center">
- <template #default="{ row }">
- <template v-if="getRowImageUrls(row).length">
- <el-image
- :src="getRowImageUrls(row)[0]"
- fit="contain"
- lazy
- :preview-src-list="getRowImageUrls(row)"
- preview-teleported
- hide-on-click-modal
- class="table-image"
- />
- </template>
- <el-tag v-else type="info" size="small">无</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="关联管网" width="140" show-overflow-tooltip>
- <template #default="{ row }">{{ getNetworkName(row.networkId) }}</template>
- </el-table-column>
- <el-table-column label="关联设备" width="140" show-overflow-tooltip>
- <template #default="{ row }">{{ getEquipmentName(row.equipmentId) }}</template>
- </el-table-column>
- <el-table-column prop="reportUnit" label="上报单位" width="130" show-overflow-tooltip />
- <el-table-column prop="reportTime" label="上报时间" width="170" align="center" />
- <el-table-column label="操作" width="200" fixed="right" align="center" class-name="small-padding fixed-width">
- <template #default="{ row }">
- <el-button link type="primary" icon="Edit" @click="handleEdit(row)">修改</el-button>
- <el-button link type="primary" icon="View" @click="handleDetail(row)">详情</el-button>
- <el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="pageNum"
- v-model:limit="pageSize"
- :page-sizes="[10, 20, 50]"
- @pagination="loadData"
- />
- <!-- 新增/编辑对话框 -->
- <el-dialog :title="dialogTitle" v-model="dialogVisible" width="750px" append-to-body destroy-on-close>
- <el-form :model="formData" ref="formRef" label-width="100px" :rules="rules">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="隐患编号" prop="hazardNo">
- <el-input v-model="formData.hazardNo" placeholder="保存时自动生成" readonly />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="隐患类型" prop="hazardType">
- <el-select v-model="formData.hazardType" style="width: 100%">
- <el-option v-for="d in hazardTypeDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="隐患等级" prop="hazardLevel">
- <el-select v-model="formData.hazardLevel" style="width: 100%">
- <el-option v-for="d in hazardLevelDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="隐患来源" prop="hazardSource">
- <el-select v-model="formData.hazardSource" style="width: 100%" placeholder="请选择隐患来源">
- <el-option v-for="d in hazardSourceDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="关联管网">
- <el-select-v2 v-model="formData.networkId" placeholder="请选择" clearable filterable style="width: 100%" :options="networkSelectOptions" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="关联设备" prop="equipmentId">
- <el-select-v2 v-model="formData.equipmentId" placeholder="请选择" clearable filterable style="width: 100%" :options="equipmentSelectOptions" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="上报单位">
- <el-input v-model="formData.reportUnit" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="上报时间" prop="reportTime">
- <el-date-picker v-model="formData.reportTime" type="datetime" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" placeholder="请选择上报时间" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="隐患描述" prop="hazardDesc">
- <el-input v-model="formData.hazardDesc" type="textarea" :rows="3" placeholder="请输入隐患描述" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="整改方案">
- <el-input v-model="formData.rectifyPlan" type="textarea" :rows="3" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="整改期限">
- <el-date-picker v-model="formData.rectifyDeadline" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="整改责任人">
- <el-input v-model="formData.rectifyPerson" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="经度">
- <el-input-number v-model="formData.longitude" :precision="10" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="纬度">
- <el-input-number v-model="formData.latitude" :precision="10" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="隐患图片" class="image-form-item">
- <div class="image-upload-content">
- <el-upload
- action="#"
- :auto-upload="false"
- :show-file-list="false"
- :http-request="() => {}"
- :on-change="handleImageChange"
- accept="image/jpeg,image/png,image/jpg"
- multiple
- >
- <el-button type="primary" plain :loading="imageUploading">
- {{ imageUploading ? '上传中...' : '选择图片' }}
- </el-button>
- <template #tip>
- <div class="el-upload__tip">支持 JPG/PNG 格式,单张不超过 5MB,可多选</div>
- </template>
- </el-upload>
- <div v-if="imageList.length" class="image-list">
- <div v-for="(img, index) in imageList" :key="index" class="image-item">
- <img :src="img" class="image-thumb" @click="previewImage(index)" />
- <el-icon class="image-remove" @click="removeImage(index)"><Close /></el-icon>
- </div>
- </div>
- </div>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="handleSave">确 定</el-button>
- <el-button @click="dialogVisible = false">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 详情对话框 -->
- <el-drawer title="隐患详情" v-model="detailVisible" size="900px" class="detail-drawer" direction="rtl">
- <el-descriptions v-if="detailData" :column="2" border>
- <el-descriptions-item label="隐患编号">{{ detailData.hazardNo }}</el-descriptions-item>
- <el-descriptions-item label="隐患类型">{{ getDictLabel(hazardTypeDict, detailData.hazardType) }}</el-descriptions-item>
- <el-descriptions-item label="隐患等级">
- <el-tag :type="levelTag(detailData.hazardLevel)">{{ getDictLabel(hazardLevelDict, detailData.hazardLevel) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="隐患来源">{{ getDictLabel(hazardSourceDict, detailData.hazardSource) }}</el-descriptions-item>
- <el-descriptions-item label="状态">
- <el-tag :type="statusTag(detailData.hazardStatus)">{{ getDictLabel(hazardStatusDict, detailData.hazardStatus) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="报送状态">
- <el-tag :type="detailData.isSubmitted === 1 ? 'success' : 'info'">{{ detailData.isSubmitted === 1 ? '已报送' : '未报送' }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="关联管网">{{ getNetworkName(detailData.networkId) }}</el-descriptions-item>
- <el-descriptions-item label="关联设备">{{ getEquipmentName(detailData.equipmentId) }}</el-descriptions-item>
- <el-descriptions-item label="上报单位">{{ detailData.reportUnit || '-' }}</el-descriptions-item>
- <el-descriptions-item label="上报时间">{{ detailData.reportTime || '-' }}</el-descriptions-item>
- <el-descriptions-item label="隐患描述" :span="2">{{ detailData.hazardDesc || '-' }}</el-descriptions-item>
- <el-descriptions-item label="整改方案" :span="2">{{ detailData.rectifyPlan || '-' }}</el-descriptions-item>
- <el-descriptions-item label="整改期限">{{ detailData.rectifyDeadline || '-' }}</el-descriptions-item>
- <el-descriptions-item label="整改责任人">{{ detailData.rectifyPerson || '-' }}</el-descriptions-item>
- <el-descriptions-item label="经度">{{ detailData.longitude || '-' }}</el-descriptions-item>
- <el-descriptions-item label="纬度">{{ detailData.latitude || '-' }}</el-descriptions-item>
- <el-descriptions-item label="隐患图片" :span="2">
- <div v-if="detailImageUrls.length" class="detail-image-list">
- <el-image
- v-for="(img, index) in detailImageUrls"
- :key="index"
- :src="img"
- fit="contain"
- lazy
- :preview-src-list="detailImageUrls"
- :initial-index="index"
- preview-teleported
- hide-on-click-modal
- class="detail-image"
- />
- </div>
- <span v-else>-</span>
- </el-descriptions-item>
- <el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
- </el-descriptions>
- </el-drawer>
- <!-- 图片预览 -->
- <el-image-viewer
- v-if="previewVisible"
- :url-list="previewList"
- :initial-index="previewIndex"
- hide-on-click-modal
- @close="previewVisible = false"
- />
- </div>
- </template>
- <script setup>
- import { computed, ref, reactive, shallowRef, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { Close } from '@element-plus/icons-vue'
- import request from '@/utils/request'
- import { createMinioBatchUploader } from '@/utils/minioBatchUpload'
- import { getHazardPage, getHazardById, addHazard, updateHazard, deleteHazard } from '@/api/pipeNetwork/hazard'
- import { getDicts } from '@/api/system/dict/data'
- import { buildMinioAttachmentUrl } from '@/utils/minioAttachment'
- import { getPipeNetworkOptions, getEquipmentByTopLevelType } from '@/api/pipeNetwork/basic'
- const loading = ref(false)
- const showSearch = ref(true)
- const queryRef = ref(null)
- const tableData = ref([])
- const pageNum = ref(1)
- const pageSize = ref(10)
- const total = ref(0)
- const searchForm = reactive({ hazardType: '', hazardLevel: '', hazardStatus: '', isSubmitted: null, networkId: '', equipmentId: '' })
- const dialogVisible = ref(false)
- const dialogTitle = ref('新增')
- const formRef = ref(null)
- // 表单校验规则
- const rules = {
- hazardType: [{ required: true, message: '请选择隐患类型', trigger: 'change' }],
- hazardLevel: [{ required: true, message: '请选择隐患等级', trigger: 'change' }],
- hazardSource: [{ required: true, message: '请选择隐患来源', trigger: 'change' }],
- equipmentId: [{ required: true, message: '请选择关联设备', trigger: 'change' }],
- reportTime: [{ required: true, message: '请选择上报时间', trigger: 'change' }],
- hazardDesc: [{ required: true, message: '请输入隐患描述', trigger: 'blur' }]
- }
- const formData = reactive({
- hazardId: '', hazardNo: '', hazardType: '', hazardLevel: '', hazardDesc: '',
- hazardSource: '', hazardStatus: '待核查', reportUnit: '', reportTime: '',
- rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '',
- networkId: '', equipmentId: '', longitude: null, latitude: null,
- hazardImages: ''
- })
- const hazardTypeDict = ref([])
- const hazardLevelDict = ref([])
- const hazardStatusDict = ref([])
- const hazardSourceDict = ref([])
- // 选项列表可能较大,使用浅层响应式并缓存请求,避免每次编辑都触发整棵数组深度更新。
- const networkOptions = shallowRef([])
- const equipmentOptions = shallowRef([])
- const networkNameCache = shallowRef(new Map())
- const equipmentNameCache = shallowRef(new Map())
- const rowImageCache = new WeakMap()
- const networkSelectOptions = computed(() => networkOptions.value.map(item => ({ value: item.networkId, label: item.networkName })))
- const equipmentSelectOptions = computed(() => equipmentOptions.value.map(item => ({ value: item.equipmentId, label: item.equipmentName })))
- let networkOptionsRequest = null
- let equipmentOptionsRequest = null
- // 详情相关
- const detailVisible = ref(false)
- const detailData = ref(null)
- const detailImageUrls = computed(() => parseImageUrls(detailData.value?.hazardImages))
- // 图片相关
- const imageList = ref([])
- const imageUrls = ref([])
- const imageUploading = ref(false)
- const previewVisible = ref(false)
- const previewList = ref([])
- const previewIndex = ref(0)
- function getDictLabel(dict, value) {
- const item = dict.find(d => d.dictValue === value)
- return item ? item.dictLabel : value
- }
- function levelTag(value) {
- const map = { '0': 'danger', '1': 'warning', '2': 'info', '3': '' }
- return map[value] || 'info'
- }
- function statusTag(value) {
- const map = { '待核查': 'info', '整改中': 'warning', '已整改': 'success', '已闭环': '' }
- return map[value] || 'info'
- }
- /** 将存储的 hazardImages 字符串转为 URL 数组 */
- function parseImageUrls(hazardImages) {
- if (!hazardImages) return []
- let values = hazardImages
- if (typeof values === 'string') {
- const text = values.trim()
- if (text.startsWith('[') || text.startsWith('{')) {
- try {
- values = JSON.parse(text)
- } catch (e) {
- values = text.split(',')
- }
- } else {
- values = text.split(/[,;\n]/)
- }
- }
- if (!Array.isArray(values)) values = [values]
- return values
- .map(item => {
- const source = typeof item === 'object' ? (item.url || item.fileName || item.name) : item
- return normalizeImageUrl(source)
- })
- .filter(Boolean)
- }
- function getRowImageUrls(row) {
- if (!row || typeof row !== 'object') return []
- if (!rowImageCache.has(row)) rowImageCache.set(row, parseImageUrls(row.hazardImages))
- return rowImageCache.get(row)
- }
- /** 兼容 MinIO 完整地址、upload 相对路径和历史仅文件名数据 */
- function normalizeImageUrl(value) {
- const url = String(value || '').trim()
- if (!url || /^(blob:|data:)/i.test(url)) return url
- const minioBase = String(import.meta.env.VITE_APP_MINIO_BASE_URL || '').trim().replace(/\/+$/, '')
- if (/^https?:\/\//i.test(url)) return buildMinioAttachmentUrl(url)
- if (minioBase) {
- if (url.startsWith('/')) return buildMinioAttachmentUrl(url)
- if (url.startsWith('upload/')) return `${minioBase}/${url}`
- if (!url.includes('/')) return `${minioBase}/upload/${url}`
- }
- const baseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim().replace(/\/+$/, '')
- return url.startsWith('/') ? `${baseApi}${url}` : `${baseApi}/${url}`
- }
- /** 图片选择后立即上传到 MinIO(同一批次多张合并为一次 /minio/uploads 请求) */
- const hazardImageUploader = createMinioBatchUploader('hazard')
- hazardImageUploader.onBatchSettled(() => { imageUploading.value = false })
- async function handleImageChange(file) {
- if (!file.raw) return
- const isImage = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png' || file.raw.type === 'image/jpg'
- if (!isImage) { ElMessage.error('只能上传图片文件 (JPG/PNG)!'); return }
- if (file.raw.size / 1024 / 1024 >= 5) { ElMessage.error('图片大小不能超过 5MB!'); return }
- imageUploading.value = true
- try {
- const info = await hazardImageUploader(file)
- const url = normalizeImageUrl(info.relativePath || info.url || info.fileName)
- if (!url) throw new Error('上传接口未返回图片地址')
- imageUrls.value.push(url)
- imageList.value.push(url)
- } catch (e) {
- ElMessage.error('图片上传失败')
- }
- }
- /** 删除图片 */
- function removeImage(index) {
- imageUrls.value.splice(index, 1)
- imageList.value.splice(index, 1)
- }
- /** 预览大图 */
- function previewImage(index) {
- previewList.value = [...imageList.value]
- previewIndex.value = index
- previewVisible.value = true
- }
- async function loadDicts() {
- const [typeRes, levelRes, statusRes, sourceRes] = await Promise.all([
- getDicts('hazard_type'),
- getDicts('hazard_level'),
- getDicts('hazard_status'),
- getDicts('hazard_source')
- ])
- hazardTypeDict.value = typeRes.data
- hazardLevelDict.value = levelRes.data
- hazardStatusDict.value = statusRes.data
- hazardSourceDict.value = sourceRes.data
- }
- async function loadData() {
- loading.value = true
- try {
- const res = await getHazardPage(pageNum.value, pageSize.value, searchForm)
- tableData.value = res.data.records || []
- total.value = res.data.total || 0
- } catch (e) { console.error(e) } finally { loading.value = false }
- }
- function handleSearch() { pageNum.value = 1; loadData() }
- function handleReset() {
- queryRef.value?.resetFields()
- Object.assign(searchForm, { hazardType: '', hazardLevel: '', hazardStatus: '', isSubmitted: null, networkId: '', equipmentId: '' })
- handleSearch()
- }
- async function loadNetworkOptions() {
- if (Array.isArray(networkOptions.value) && networkOptions.value.length > 0) return
- if (networkOptionsRequest) return networkOptionsRequest
- networkOptionsRequest = getPipeNetworkOptions()
- .then(res => {
- networkOptions.value = Array.isArray(res.data) ? res.data : []
- networkNameCache.value = new Map(networkOptions.value.map(item => [String(item.networkId), item.networkName]))
- return networkOptions.value
- })
- .catch(e => { console.error(e); return [] })
- .finally(() => { networkOptionsRequest = null })
- return networkOptionsRequest
- }
- async function loadEquipmentOptions() {
- if (Array.isArray(equipmentOptions.value) && equipmentOptions.value.length > 0) return
- if (equipmentOptionsRequest) return equipmentOptionsRequest
- equipmentOptionsRequest = getEquipmentByTopLevelType('燃气')
- .then(res => {
- equipmentOptions.value = Array.isArray(res.data) ? res.data : []
- equipmentNameCache.value = new Map(equipmentOptions.value.map(item => [String(item.equipmentId), item.equipmentName]))
- return equipmentOptions.value
- })
- .catch(e => { console.error(e); return [] })
- .finally(() => { equipmentOptionsRequest = null })
- return equipmentOptionsRequest
- }
- function getNetworkName(id) {
- return networkNameCache.value.get(String(id)) || id || '-'
- }
- function getEquipmentName(id) {
- return equipmentNameCache.value.get(String(id)) || id || '-'
- }
- /** 自动生成隐患编号:YH-YYYYMMDDHHmmss */
- function generateHazardNo() {
- const now = new Date()
- const pad = (n) => String(n).padStart(2, '0')
- const dateStr = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
- const timeStr = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
- return `YH-${dateStr}${timeStr}`
- }
- function handleAdd() {
- dialogTitle.value = '新增'
- Object.assign(formData, { hazardId: '', hazardNo: generateHazardNo(), hazardType: '', hazardLevel: '', hazardDesc: '', hazardSource: '', hazardStatus: '待核查', reportUnit: '', reportTime: '', rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '', networkId: '', equipmentId: '', longitude: null, latitude: null, hazardImages: '', isSubmitted: null, submitTime: null, verifyPerson: null, verifyTime: null })
- imageList.value = []
- imageUrls.value = []
- dialogVisible.value = true
- loadNetworkOptions(); loadEquipmentOptions()
- }
- function handleEdit(row) {
- dialogTitle.value = '编辑'
- // 只回填表单字段,避免把接口返回的分页/审计字段直接写入响应式表单。
- // 直接 Object.assign(row) 在部分数据返回结构下会导致弹窗渲染持续触发更新。
- const editableFields = [
- 'hazardId', 'hazardNo', 'hazardType', 'hazardLevel', 'hazardDesc',
- 'hazardSource', 'hazardStatus', 'reportUnit', 'reportTime',
- 'rectifyPlan', 'rectifyDeadline', 'rectifyPerson', 'networkId',
- 'equipmentId', 'longitude', 'latitude', 'hazardImages'
- ]
- editableFields.forEach(key => {
- const value = row?.[key]
- if (key === 'longitude' || key === 'latitude') {
- formData[key] = value === null || value === undefined || value === '' ? null : Number(value)
- } else if (key === 'hazardImages') {
- formData[key] = parseImageUrls(value).join(',')
- } else {
- formData[key] = value ?? ''
- }
- })
- // 回显已有图片
- const urls = getRowImageUrls(row)
- imageUrls.value = [...urls]
- imageList.value = [...urls]
- dialogVisible.value = true
- // 选项加载不阻塞弹窗打开,且函数内部会避免重复请求。
- void Promise.allSettled([loadNetworkOptions(), loadEquipmentOptions()])
- }
- async function handleSave() {
- if (!formRef.value) return
- try {
- await formRef.value.validate()
- if (imageUploading.value) { ElMessage.warning('图片正在上传,请稍候'); return }
- // 将图片URL列表拼接为逗号分隔字符串
- formData.hazardImages = imageUrls.value.join(',')
- if (formData.hazardId) { await updateHazard(formData); ElMessage.success('修改成功') }
- else { await addHazard(formData); ElMessage.success('新增成功') }
- dialogVisible.value = false; loadData()
- } catch (e) {
- const msg = e?.response?.data?.msg || e?.message || '操作失败'
- ElMessage.error(msg)
- }
- }
- function handleDelete(row) {
- ElMessageBox.confirm('确认删除该隐患记录?', '提示', { type: 'warning' }).then(async () => {
- await deleteHazard(row.hazardId); ElMessage.success('删除成功'); loadData()
- }).catch(() => {})
- }
- async function handleDetail(row) {
- try {
- const res = await getHazardById(row.hazardId)
- detailData.value = res.data || row
- detailVisible.value = true
- } catch (e) {
- // 接口失败时降级使用表格行数据
- detailData.value = row
- detailVisible.value = true
- }
- }
- onMounted(async () => {
- await loadDicts()
- void loadData()
- })
- </script>
- <style scoped>
- /* 表格图片样式 */
- .table-image {
- width: 40px;
- height: 40px;
- border-radius: 4px;
- cursor: pointer;
- }
- /* 多图上传列表样式 */
- .image-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 8px;
- }
- .image-form-item :deep(.el-form-item__content) {
- display: block;
- }
- .image-upload-content {
- width: 100%;
- }
- .image-item {
- position: relative;
- width: 80px;
- height: 80px;
- border-radius: 4px;
- overflow: visible;
- }
- .image-thumb {
- width: 80px;
- height: 80px;
- object-fit: contain;
- background: #f5f7fa;
- border-radius: 4px;
- border: 1px solid #dcdfe6;
- cursor: pointer;
- }
- .image-thumb:hover {
- border-color: #409eff;
- }
- .image-remove {
- position: absolute;
- top: -6px;
- right: -6px;
- width: 18px;
- height: 18px;
- background: #f56c6c;
- color: #fff;
- border-radius: 50%;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- z-index: 1;
- }
- .image-remove:hover {
- background: #e64a4a;
- }
- /* 详情图片样式 */
- .detail-image-list {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
- .detail-image {
- width: 120px;
- height: 120px;
- border-radius: 6px;
- cursor: pointer;
- object-fit: contain;
- background: #f5f7fa;
- }
- </style>
|