| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215 |
- <!-- 预警发布 -->
- <template>
- <div class="app-container warning-release-page">
- <el-card shadow="never" class="page-card header-card">
- <div class="page-header">
- <div>
- <div class="page-title">预警发布</div>
- <div class="page-subtitle">支持按预警类型、预警级别、发布时间查询,并对预警信息进行新增、编辑、查看、发布等操作</div>
- </div>
- <div class="header-actions">
- <el-button v-hasPermi="['warning:warning:add']" type="primary" icon="Plus" @click="handleAdd">新增预警</el-button>
- <el-button icon="Refresh" @click="loadList">刷新</el-button>
- </div>
- </div>
- </el-card>
- <el-card shadow="never" class="page-card">
- <el-form :model="queryParams" :inline="true">
- <el-form-item label="预警名称">
- <el-input
- v-model="queryParams.warningName"
- placeholder="请输入预警名称"
- clearable
- style="width: 220px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="预警类型">
- <el-select v-model="queryParams.warningType" placeholder="请选择预警类型" clearable style="width: 180px">
- <el-option
- v-for="item in warningTypeOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="预警级别">
- <el-select v-model="queryParams.warningLevel" placeholder="请选择预警级别" clearable style="width: 180px">
- <el-option
- v-for="item in warningLevelOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="发布时间">
- <el-date-picker
- v-model="queryParams.dateRange"
- type="datetimerange"
- value-format="YYYY-MM-DD HH:mm:ss"
- range-separator="至"
- start-placeholder="开始时间"
- end-placeholder="结束时间"
- style="width: 360px"
- />
- </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>
- <el-card shadow="never" class="page-card">
- <el-table v-loading="loading" :data="warningList">
- <el-table-column label="序号" width="70" align="center">
- <template #default="scope">
- {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
- </template>
- </el-table-column>
- <el-table-column label="预警编号" prop="warningNo" width="180" />
- <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
- <el-table-column label="预警类型" prop="warningTypeText" width="120" />
- <el-table-column label="预警级别" width="110" align="center">
- <template #default="scope">
- <el-tag :type="getLevelTagType(scope.row.warningLevel)">
- {{ scope.row.warningLevelText || scope.row.warningLevel || "-" }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="预警位置" prop="location" min-width="180" show-overflow-tooltip />
- <el-table-column label="发布时间" width="180">
- <template #default="scope">{{ formatDateTime(scope.row.publishTime) }}</template>
- </el-table-column>
- <el-table-column label="发布人" prop="publisher" width="120" />
- <el-table-column label="状态" width="120" align="center">
- <template #default="scope">
- <el-tag :type="getStatusTagType(scope.row.status)">
- {{ getStatusText(scope.row.status) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" fixed="right" width="280" align="center">
- <template #default="scope">
- <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
- <el-button v-if="scope.row.status === 'DRAFT'" v-hasPermi="['warning:warning:edit']" link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
- <el-button v-if="scope.row.status === 'DRAFT'" v-hasPermi="['warning:warning:publish']" link type="success" @click="handlePublish(scope.row)">发布</el-button>
- <el-button v-if="scope.row.status === 'DRAFT'" v-hasPermi="['warning:warning:remove']" link type="danger" @click="handleDelete(scope.row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- @pagination="loadList"
- />
- </el-card>
- <el-dialog v-model="editVisible" :title="dialogTitle" width="860px" append-to-body @close="resetForm">
- <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="预警类型" prop="warningType">
- <el-select v-model="formData.warningType" placeholder="请选择预警类型" style="width: 100%" @change="handleTypeLevelChange">
- <el-option
- v-for="item in warningTypeOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="预警级别" prop="warningLevel">
- <el-select v-model="formData.warningLevel" placeholder="请选择预警级别" style="width: 100%" @change="handleTypeLevelChange">
- <el-option
- v-for="item in warningLevelOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="预警专项">
- <el-input v-model="formData.warningSpecial" placeholder="请输入预警专项" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="权属单位">
- <el-input v-model="formData.ownershipUnit" placeholder="请输入权属单位" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="发布人">
- <el-input :model-value="formData.publisher || '发布时确定'" disabled />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="处置人">
- <el-input v-model="formData.handler" placeholder="请选择处置人" readonly @click="openUserPicker">
- <template #suffix>
- <el-icon style="cursor: pointer;" @click="openUserPicker"><Search /></el-icon>
- </template>
- </el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="核查人">
- <el-input v-model="formData.reviewer" placeholder="默认发布人,可选择其他核查人" readonly clearable @click="openReviewerPicker">
- <template #suffix><el-icon style="cursor:pointer" @click="openReviewerPicker"><Search /></el-icon></template>
- </el-input>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="预警名称" prop="warningName">
- <el-input v-model="formData.warningName" placeholder="请输入预警名称" maxlength="100" show-word-limit />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="预警位置" prop="location">
- <el-input v-model="formData.location" placeholder="请输入预警位置" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="关联设备">
- <el-input v-model="formData.deviceName" placeholder="点击选择关联设备" readonly @click="openDevicePicker">
- <template #append>
- <el-button :icon="Search" @click="openDevicePicker">选择</el-button>
- </template>
- </el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="经度">
- <el-input v-model="formData.longitude" placeholder="请输入经度" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="纬度">
- <el-input v-model="formData.latitude" placeholder="请输入纬度" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="内容描述" prop="warningContent">
- <el-input
- v-model="formData.warningContent"
- type="textarea"
- :rows="4"
- maxlength="500"
- show-word-limit
- placeholder="请输入预警内容描述"
- />
- </el-form-item>
- </el-col>
- <el-col :span="24" v-if="reasonOptions.length">
- <el-form-item label="推荐原因">
- <div class="reason-wrap">
- <el-tag
- v-for="item in reasonOptions"
- :key="item.reasonId"
- class="reason-tag"
- effect="plain"
- @click="appendReason(item)"
- >
- {{ item.reasonName }}
- </el-tag>
- </div>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="备注">
- <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="附件上传">
- <div class="attachment-panel">
- <el-upload
- ref="uploadRef"
- action="#"
- :auto-upload="false"
- :show-file-list="false"
- multiple
- :on-change="handleUploadChange"
- :on-exceed="handleUploadExceed"
- :limit="9"
- accept=".pdf,.doc,.docx,.xls,.xlsx,.zip,.jpg,.jpeg,.png"
- >
- <el-button type="primary">选择附件</el-button>
- </el-upload>
- <div class="attachment-tip">支持常见文档、压缩包和图片,新增或编辑时会随保存接口一起上传。</div>
- <div v-if="formAttachments.length" class="attachment-list">
- <div v-for="item in formAttachments" :key="item.uid || item.attachmentId || item.name" class="attachment-item">
- <el-link
- v-if="item.url"
- :href="item.url"
- target="_blank"
- type="primary"
- class="attachment-link"
- >
- {{ item.name }}
- </el-link>
- <span v-else class="attachment-link">{{ item.name }}</span>
- <span class="attachment-meta">{{ item.sizeText || item.createTime || "" }}</span>
- <el-button link type="danger" @click="removeAttachment(item)">移除</el-button>
- </div>
- </div>
- </div>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="editVisible = false">取 消</el-button>
- <el-button type="primary" :loading="submitLoading" @click="submitForm">保 存</el-button>
- </div>
- </template>
- </el-dialog>
- <el-drawer v-model="detailVisible" title="预警详情" size="900px" append-to-body class="detail-drawer" direction="rtl">
- <template v-if="detailData.basicInfo">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="预警编号">{{ detailData.basicInfo.warningNo || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警名称">{{ detailData.basicInfo.warningName || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警类型">{{ formatTypeText(detailData.basicInfo.warningType) }}</el-descriptions-item>
- <el-descriptions-item label="预警级别">
- <el-tag :type="getLevelTagType(detailData.basicInfo.warningLevel)">
- {{ formatLevelText(detailData.basicInfo.warningLevel) }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="预警专项">{{ detailData.basicInfo.warningSpecial || "-" }}</el-descriptions-item>
- <el-descriptions-item label="权属单位">{{ detailData.basicInfo.ownershipUnit || "-" }}</el-descriptions-item>
- <el-descriptions-item label="发布人">{{ detailData.basicInfo.publisher || "-" }}</el-descriptions-item>
- <el-descriptions-item label="处置人">{{ detailData.basicInfo.handler || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警位置" :span="2">{{ detailData.basicInfo.location || "-" }}</el-descriptions-item>
- <el-descriptions-item label="关联设备" :span="2">
- <template v-if="detailData.basicInfo.deviceCode || detailData.basicInfo.deviceName">
- {{ detailData.basicInfo.deviceName || "-" }}
- <span v-if="detailData.basicInfo.deviceCode" style="color: #909399;">({{ detailData.basicInfo.deviceCode }})</span>
- </template>
- <span v-else>-</span>
- </el-descriptions-item>
- <el-descriptions-item label="发布时间">{{ formatDateTime(detailData.basicInfo.publishTime) }}</el-descriptions-item>
- <el-descriptions-item label="状态">{{ getStatusText(detailData.basicInfo.status) }}</el-descriptions-item>
- <el-descriptions-item label="内容描述" :span="2">{{ detailData.basicInfo.warningContent || "-" }}</el-descriptions-item>
- <el-descriptions-item label="备注" :span="2">{{ detailData.basicInfo.remark || "-" }}</el-descriptions-item>
- </el-descriptions>
- <el-divider content-position="left">关联报警</el-divider>
- <el-table :data="detailData.alarmList || []" border size="small">
- <el-table-column label="报警ID" prop="alarmId" min-width="160" />
- <el-table-column label="报警名称" prop="alarmName" min-width="180" show-overflow-tooltip />
- <el-table-column label="报警类型" min-width="120">
- <template #default="scope">
- {{ formatTypeText(scope.row.alarmType || scope.row.warningType) }}
- </template>
- </el-table-column>
- </el-table>
- <el-divider content-position="left">处置记录</el-divider>
- <el-table :data="detailData.disposalHistory || detailData.disposalList || []" border size="small">
- <el-table-column label="处置类型" prop="disposalTypeName" min-width="120" />
- <el-table-column label="处置人" prop="disposalUser" min-width="120" />
- <el-table-column label="处置内容" prop="disposalContent" min-width="220" show-overflow-tooltip />
- <el-table-column label="处置时间" prop="createTime" min-width="180" />
- </el-table>
- <el-divider content-position="left">附件信息</el-divider>
- <div v-if="(detailData.attachmentList || []).length" class="detail-attachment-list">
- <div v-for="item in detailData.attachmentList" :key="item.attachmentId || item.attachmentName" class="detail-attachment-item">
- <el-link :href="buildAttachmentUrl(item.attachmentUrl)" target="_blank" type="primary">
- {{ item.attachmentName || "附件" }}
- </el-link>
- <span>{{ item.createTime || "-" }}</span>
- </div>
- </div>
- <el-empty v-else description="暂无附件信息" />
- </template>
- </el-drawer>
- <!-- 关联设备选择对话框 -->
- <el-dialog v-model="devicePickerVisible" title="选择关联设备" width="860px" append-to-body @open="loadDevicePickerData">
- <div class="device-picker-container">
- <div class="device-picker-left">
- <div class="picker-title">设备类型</div>
- <button
- :class="{ active: deviceRootTypeId === '' }"
- class="device-type-item"
- @click="selectDeviceRootType('')"
- >
- 全部类型
- </button>
- <button
- v-for="item in deviceRootTypes"
- :key="item.id"
- :class="{ active: String(deviceRootTypeId) === String(item.id) }"
- class="device-type-item"
- @click="selectDeviceRootType(item.id)"
- >
- {{ item.typeName }}
- </button>
- </div>
- <div class="device-picker-right">
- <el-form :inline="true" @submit.prevent>
- <el-form-item>
- <el-input v-model="deviceQuery.equipmentCode" placeholder="设备编码" clearable style="width: 180px" @keyup.enter="loadDeviceList" />
- </el-form-item>
- <el-form-item>
- <el-input v-model="deviceQuery.equipmentName" placeholder="设备名称" clearable style="width: 180px" @keyup.enter="loadDeviceList" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadDeviceList">查询</el-button>
- </el-form-item>
- </el-form>
- <el-table v-loading="deviceLoading" :data="deviceList" border size="small" height="320">
- <el-table-column prop="equipmentCode" label="设备编码" min-width="180" show-overflow-tooltip />
- <el-table-column prop="equipmentName" label="设备名称" min-width="180" show-overflow-tooltip />
- <el-table-column label="设备类型" min-width="140">
- <template #default="{ row }">{{ deviceTypeNameMap[row.equipmentTypeId] || row.equipmentTypeName || "-" }}</template>
- </el-table-column>
- <el-table-column label="操作" width="90" align="center">
- <template #default="{ row }">
- <el-button link type="primary" @click="confirmDevice(row)">选择</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-pagination
- v-model:current-page="deviceQuery.pageNum"
- v-model:page-size="deviceQuery.pageSize"
- :total="deviceTotal"
- :page-sizes="[10, 20, 50]"
- layout="total, sizes, prev, pager, next"
- style="margin-top: 12px; justify-content: flex-end"
- @size-change="loadDeviceList"
- @current-change="loadDeviceList"
- />
- </div>
- </div>
- </el-dialog>
- <!-- 处置人选择对话框 -->
- <el-dialog v-model="userPickerVisible" :title="pickerTarget === 'reviewer' ? '选择核查人' : '选择处置人'" width="900px" append-to-body @open="loadUserPickerData">
- <div class="user-picker-container">
- <!-- 左侧部门树 -->
- <div class="user-picker-left">
- <div class="picker-title">部门列表</div>
- <el-input v-model="deptFilterText" placeholder="搜索部门" clearable style="margin-bottom: 12px;" />
- <el-tree
- ref="deptTreeRef"
- :data="deptTree"
- :props="{ label: 'label', children: 'children' }"
- :filter-node-method="filterDeptNode"
- :highlight-current="true"
- default-expand-all
- @node-click="handleDeptClick"
- />
- </div>
- <!-- 右侧用户列表 -->
- <div class="user-picker-right">
- <div class="picker-title">
- 用户列表
- <span v-if="currentDeptName" class="current-dept">({{ currentDeptName }})</span>
- </div>
- <el-input v-model="userFilterText" placeholder="搜索用户" clearable style="margin-bottom: 12px;" @input="filterUserList" />
- <el-table
- v-loading="userPickerLoading"
- :data="filteredUserList"
- highlight-current-row
- @current-change="handleUserCurrentChange"
- height="400"
- >
- <el-table-column label="用户名称" prop="userName" min-width="120" show-overflow-tooltip />
- <el-table-column label="用户昵称" prop="nickName" min-width="120" show-overflow-tooltip />
- <el-table-column label="部门" prop="deptName" min-width="140" show-overflow-tooltip />
- <el-table-column label="手机号码" prop="phonenumber" width="130" />
- </el-table>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="userPickerVisible = false">取 消</el-button>
- <el-button type="primary" :disabled="!selectedUser" @click="confirmSelectUser">确 定</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import {computed, onMounted, reactive, ref, watch} from "vue";
- import {ElMessage, ElMessageBox} from "element-plus";
- import { Search } from "@element-plus/icons-vue";
- import { parseTime } from "@/utils/ruoyi";
- import { warningStatusLabels } from "../warningWorkflow";
- import { deptTreeSelect, listUser } from "@/api/system/user";
- import { listEquipment, listEquipmentTypeAll } from "@/api/pipeNetwork/pdeviceInform";
- import {
- deleteYjxxData,
- getAlertTypeDictionaryData,
- getdbyjlistData,
- getWarningDetailData,
- getWarningReasonListData,
- releaseYjxxData,
- submitYjxxData,
- updateYjxxData
- } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
- const loading = ref(false);
- const submitLoading = ref(false);
- const total = ref(0);
- const warningList = ref([]);
- const warningTypeOptions = ref([]);
- const warningLevelOptions = ref([]);
- const reasonOptions = ref([]);
- const uploadRef = ref();
- const formAttachments = ref([]);
- const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
- const editVisible = ref(false);
- const detailVisible = ref(false);
- const userPickerVisible = ref(false);
- const pickerTarget = ref('handler');
- const dialogTitle = ref("新增预警");
- const isEdit = ref(false);
- const formRef = ref();
- const detailData = ref({});
- // 关联设备选择器
- const devicePickerVisible = ref(false);
- const deviceRootTypeId = ref("");
- const deviceRootTypes = ref([]);
- const deviceList = ref([]);
- const deviceTotal = ref(0);
- const deviceLoading = ref(false);
- const deviceTypeNameMap = ref({});
- const deviceQuery = reactive({
- pageNum: 1,
- pageSize: 10,
- equipmentCode: "",
- equipmentName: ""
- });
- // 用户选择器相关
- const deptTree = ref([]);
- const userList = ref([]);
- const filteredUserList = ref([]);
- const selectedDeptId = ref("");
- const currentDeptName = ref("");
- const selectedUser = ref(null);
- const userPickerLoading = ref(false);
- const deptFilterText = ref("");
- const userFilterText = ref("");
- const deptTreeRef = ref();
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- warningName: "",
- warningType: "",
- warningLevel: "",
- dateRange: []
- });
- const formData = reactive(createEmptyForm());
- const formRules = {
- warningType: [{ required: true, message: "请选择预警类型", trigger: "change" }],
- warningLevel: [{ required: true, message: "请选择预警级别", trigger: "change" }],
- warningName: [{ required: true, message: "请输入预警名称", trigger: "blur" }],
- location: [{ required: true, message: "请输入预警位置", trigger: "blur" }],
- warningContent: [{ required: true, message: "请输入预警内容描述", trigger: "blur" }]
- };
- const warningTypeLabelMap = computed(() => {
- return warningTypeOptions.value.reduce((acc, item) => {
- acc[item.dictValue] = item.dictLabel;
- return acc;
- }, {});
- });
- const warningLevelLabelMap = computed(() => {
- return warningLevelOptions.value.reduce((acc, item) => {
- acc[item.dictValue] = item.dictLabel;
- return acc;
- }, {});
- });
- function createEmptyForm() {
- return {
- warningId: "",
- warningNo: "",
- warningName: "",
- warningType: "",
- warningLevel: "",
- warningSpecial: "",
- deviceCode: "",
- deviceName: "",
- location: "",
- longitude: "",
- latitude: "",
- ownershipUnit: "",
- publisher: "",
- publishTime: "",
- handler: "",
- reviewer: "",
- status: "DRAFT",
- warningContent: "",
- remark: ""
- };
- }
- function resetForm() {
- Object.assign(formData, createEmptyForm());
- reasonOptions.value = [];
- formAttachments.value = [];
- uploadRef.value?.clearFiles?.();
- formRef.value?.clearValidate();
- }
- function formatFileSize(bytes) {
- if (!bytes) return "";
- const units = ["B", "KB", "MB", "GB"];
- const index = Math.floor(Math.log(bytes) / Math.log(1024));
- return `${(bytes / 1024 ** index).toFixed(2)} ${units[index]}`;
- }
- function buildAttachmentUrl(path) {
- if (!path) return "";
- if (/^https?:\/\//i.test(path)) {
- return path;
- }
- const base = minioBaseUrl.endsWith("/") ? minioBaseUrl : `${minioBaseUrl}/`;
- return `${base}${String(path).replace(/^\/+/, "")}`;
- }
- function normalizeExistingAttachment(item) {
- return {
- uid: item.attachmentId || item.attachmentName,
- name: item.attachmentName || "附件",
- url: buildAttachmentUrl(item.attachmentUrl),
- attachmentId: item.attachmentId,
- attachmentUrl: item.attachmentUrl,
- createTime: item.createTime || "",
- raw: null,
- sizeText: item.attachmentSize ? formatFileSize(item.attachmentSize) : ""
- };
- }
- function handleUploadChange(file) {
- if (!file?.raw) return;
- formAttachments.value.push({
- uid: file.uid,
- name: file.name,
- raw: file.raw,
- url: "",
- sizeText: formatFileSize(file.size)
- });
- }
- function handleUploadExceed() {
- ElMessage.warning("最多上传 9 个附件");
- }
- function removeAttachment(item) {
- formAttachments.value = formAttachments.value.filter((current) => current.uid !== item.uid);
- }
- function formatTypeText(value) {
- return warningTypeLabelMap.value[value] || value || "-";
- }
- function formatLevelText(value) {
- return warningLevelLabelMap.value[value] || value || "-";
- }
- function formatDateTime(value) {
- return value ? parseTime(value) : "-";
- }
- function getLevelTagType(level) {
- return {
- "0": "danger",
- "1": "danger",
- "2": "warning",
- "3": "success",
- "4": "info"
- }[level] || "info";
- }
- function getStatusText(status) {
- return warningStatusLabels[status] || status || "-";
- }
- function getStatusTagType(status) {
- // 按照预警状态语义设置 tag 颜色
- return {
- DRAFT: "info", // 草稿 - 灰色(未发布)
- PENDING: "warning", // 待办 - 黄色(等待处置)
- RECEIVED: "warning", // 已接收 - 黄色
- PROCESSING: "warning", // 处置中 - 黄色(进行中)
- HANDLED: "success", // 已处置 - 绿色(已完成)
- CLOSED: "info" // 已解除 - 灰色
- }[status] || "info";
- }
- function normalizeWarningRow(item) {
- const row = { ...item };
- row.warningId = item.warningId || item.warning_id || "";
- row.warningNo = item.warningNo || item.warning_no || "";
- row.warningName = item.warningName || item.warning_name || "";
- row.warningType = item.warningType || item.warning_type || "";
- row.warningLevel = item.warningLevel || item.warning_level || "";
- row.warningSpecial = item.warningSpecial || item.warning_special || "";
- row.location = item.location || "";
- row.longitude = item.longitude || "";
- row.latitude = item.latitude || "";
- row.ownershipUnit = item.ownershipUnit || item.ownership_unit || "";
- row.publisher = item.publisher || "";
- row.publishTime = item.publishTime || item.publish_time || "";
- row.handler = item.handler || "";
- row.reviewer = item.reviewer || "";
- row.status = item.status || "";
- row.warningContent = item.warningContent || item.warning_content || "";
- row.deviceCode = item.deviceCode || item.device_code || "";
- row.deviceName = item.deviceName || item.device_name || "";
- row.remark = item.remark || "";
- row.warningTypeText = formatTypeText(row.warningType);
- row.warningLevelText = formatLevelText(row.warningLevel);
- return row;
- }
- function normalizeAlarmRow(item) {
- return {
- ...item,
- alarmId: item.alarmId || item.alarm_id || "",
- alarmName: item.alarmName || item.alarm_name || item.deviceName || item.device_name || "",
- alarmType: item.alarmType || item.alarm_type || item.warningType || item.warning_type || ""
- };
- }
- function normalizeDisposalRow(item) {
- return {
- ...item,
- disposalTypeName: item.disposalTypeName || item.disposal_type_name || item.disposalType || item.disposal_type || "",
- disposalUser: item.disposalUser || item.disposal_user || "",
- disposalContent: item.disposalContent || item.disposal_content || "",
- createTime: item.createTime || item.create_time || ""
- };
- }
- async function loadDicts() {
- const [typeResult, levelResult] = await Promise.allSettled([
- getAlertTypeDictionaryData({ dictType: "alert_type" }),
- getAlertTypeDictionaryData({ dictType: "alert_level" })
- ]);
- if (typeResult.status === "fulfilled") {
- warningTypeOptions.value = typeResult.value.rows || [];
- }
- if (levelResult.status === "fulfilled") {
- warningLevelOptions.value = levelResult.value.rows || [];
- }
- }
- async function loadReasonOptions() {
- if (!formData.warningType || !formData.warningLevel) {
- reasonOptions.value = [];
- return;
- }
- const res = await getWarningReasonListData({
- warningType: formData.warningType,
- warningLevel: formData.warningLevel
- });
- reasonOptions.value = res.data || [];
- }
- function handleTypeLevelChange() {
- loadReasonOptions();
- }
- function appendReason(item) {
- const content = item.description ? `${item.reasonName}:${item.description}` : item.reasonName;
- formData.warningContent = formData.warningContent
- ? `${formData.warningContent}\n${content}`
- : content;
- }
- async function loadList() {
- loading.value = true;
- try {
- const params = {
- pageNum: queryParams.pageNum,
- pageSize: queryParams.pageSize,
- warningName: queryParams.warningName,
- warningType: queryParams.warningType,
- warningLevel: queryParams.warningLevel,
- startTime: queryParams.dateRange?.[0] || "",
- endTime: queryParams.dateRange?.[1] || ""
- };
- const res = await getdbyjlistData(params);
- warningList.value = (res.data?.records || []).map(normalizeWarningRow);
- total.value = res.data?.total || 0;
- } catch {
- warningList.value = [];
- total.value = 0;
- } finally {
- loading.value = false;
- }
- }
- function handleQuery() {
- queryParams.pageNum = 1;
- loadList();
- }
- function resetQuery() {
- queryParams.pageNum = 1;
- queryParams.pageSize = 10;
- queryParams.warningName = "";
- queryParams.warningType = "";
- queryParams.warningLevel = "";
- queryParams.dateRange = [];
- loadList();
- }
- // 部门树过滤
- watch(deptFilterText, (val) => {
- deptTreeRef.value?.filter(val);
- });
- function filterDeptNode(value, data) {
- if (!value) return true;
- return data.label.indexOf(value) !== -1;
- }
- // 用户选择器相关函数
- async function loadUserPickerData() {
- userPickerLoading.value = true;
- try {
- await loadDeptTree();
- // 默认加载第一个部门的用户
- if (deptTree.value.length > 0) {
- const firstDept = deptTree.value[0];
- selectedDeptId.value = firstDept.id;
- currentDeptName.value = firstDept.label;
- await loadUserList(firstDept.id);
- }
- } finally {
- userPickerLoading.value = false;
- }
- }
- async function loadDeptTree() {
- const res = await deptTreeSelect();
- deptTree.value = res.data || [];
- }
- async function loadUserList(deptId) {
- userPickerLoading.value = true;
- try {
- const res = await listUser({ deptId, pageNum: 1, pageSize: 100 });
- userList.value = res.rows || [];
- filteredUserList.value = userList.value;
- } finally {
- userPickerLoading.value = false;
- }
- }
- function handleDeptClick(data) {
- selectedDeptId.value = data.id;
- currentDeptName.value = data.label;
- loadUserList(data.id);
- selectedUser.value = null;
- userFilterText.value = "";
- }
- function filterUserList() {
- const keyword = userFilterText.value.toLowerCase();
- if (!keyword) {
- filteredUserList.value = userList.value;
- return;
- }
- filteredUserList.value = userList.value.filter((user) => {
- const userName = (user.userName || "").toLowerCase();
- const nickName = (user.nickName || "").toLowerCase();
- const deptName = (user.deptName || "").toLowerCase();
- return userName.includes(keyword) || nickName.includes(keyword) || deptName.includes(keyword);
- });
- }
- function handleUserCurrentChange(row) {
- selectedUser.value = row;
- }
- function openUserPicker() {
- pickerTarget.value = 'handler';
- selectedUser.value = null;
- userFilterText.value = "";
- deptFilterText.value = "";
- userPickerVisible.value = true;
- }
- function openReviewerPicker() {
- pickerTarget.value = 'reviewer';
- selectedUser.value = null;
- userFilterText.value = '';
- deptFilterText.value = '';
- userPickerVisible.value = true;
- }
- function confirmSelectUser() {
- if (!selectedUser.value) {
- ElMessage.warning("请选择用户");
- return;
- }
- formData[pickerTarget.value] = selectedUser.value.userName;
- userPickerVisible.value = false;
- }
- function openDevicePicker() {
- devicePickerVisible.value = true;
- }
- async function loadDevicePickerData() {
- deviceQuery.pageNum = 1;
- deviceQuery.equipmentCode = "";
- deviceQuery.equipmentName = "";
- try {
- const typeRes = await listEquipmentTypeAll();
- const allTypes = typeRes.data || [];
- deviceRootTypes.value = allTypes.filter((t) => String(t.parentId) === "0" || t.parentId === 0 || t.parentId === null || t.parentId === undefined || t.parentId === "");
- deviceTypeNameMap.value = allTypes.reduce((acc, t) => {
- acc[t.id] = t.typeName;
- return acc;
- }, {});
- } catch (e) {
- deviceRootTypes.value = [];
- }
- await loadDeviceList();
- }
- function selectDeviceRootType(rootTypeId) {
- deviceRootTypeId.value = rootTypeId === "" ? "" : String(rootTypeId);
- deviceQuery.pageNum = 1;
- loadDeviceList();
- }
- async function loadDeviceList() {
- deviceLoading.value = true;
- try {
- const res = await listEquipment({
- pageNum: deviceQuery.pageNum,
- pageSize: deviceQuery.pageSize,
- equipmentCode: deviceQuery.equipmentCode || undefined,
- equipmentName: deviceQuery.equipmentName || undefined,
- rootTypeId: deviceRootTypeId.value || undefined
- });
- deviceList.value = res.records || [];
- deviceTotal.value = Number(res.total || 0);
- } catch (e) {
- ElMessage.error(e?.msg || e?.message || "加载设备失败");
- } finally {
- deviceLoading.value = false;
- }
- }
- function confirmDevice(row) {
- formData.deviceCode = row.equipmentCode || "";
- formData.deviceName = row.equipmentName || "";
- devicePickerVisible.value = false;
- }
- function handleAdd() {
- isEdit.value = false;
- dialogTitle.value = "新增预警";
- resetForm();
- editVisible.value = true;
- }
- async function handleEdit(row) {
- isEdit.value = true;
- dialogTitle.value = "编辑预警";
- resetForm();
- Object.assign(formData, { ...row });
- const detailRes = await getWarningDetailData(row.warningId);
- const detail = detailRes.data || {};
- formData.reviewer = detail.basicInfo?.reviewer || "";
- formAttachments.value = (detail.attachmentList || []).map(normalizeExistingAttachment);
- await loadReasonOptions();
- editVisible.value = true;
- }
- async function submitForm() {
- const valid = await formRef.value?.validate().catch(() => false);
- if (!valid) return;
- submitLoading.value = true;
- try {
- const payload = {
- warning: {
- warningId: formData.warningId,
- warningNo: formData.warningNo,
- warningName: formData.warningName,
- warningType: formData.warningType,
- warningLevel: formData.warningLevel,
- warningSpecial: formData.warningSpecial,
- location: formData.location,
- longitude: formData.longitude,
- latitude: formData.latitude,
- ownershipUnit: formData.ownershipUnit,
- handler: formData.handler,
- reviewer: formData.reviewer,
- warningContent: formData.warningContent,
- deviceCode: formData.deviceCode || "",
- deviceName: formData.deviceName || "",
- remark: formData.remark
- },
- files: formAttachments.value.map((item) => item.raw).filter(Boolean)
- };
- const res = isEdit.value ? await updateYjxxData(payload) : await submitYjxxData(payload);
- if (res.code !== 200) {
- ElMessage.error(res.msg || "保存失败");
- return;
- }
- ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
- editVisible.value = false;
- loadList();
- } finally {
- submitLoading.value = false;
- }
- }
- async function handleView(row) {
- const res = await getWarningDetailData(row.warningId);
- const detail = res.data || {};
- const basicSource = detail.basicInfo || detail;
- detailData.value = {
- ...detail,
- basicInfo: normalizeWarningRow({ ...row, ...basicSource }),
- alarmList: (detail.alarmList || []).map(normalizeAlarmRow),
- disposalHistory: (detail.disposalHistory || detail.disposalList || []).map(normalizeDisposalRow)
- };
- detailVisible.value = true;
- }
- async function handleDelete(row) {
- await ElMessageBox.confirm(`确认删除预警“${row.warningName}”吗?`, "提示", { type: "warning" });
- const res = await deleteYjxxData(row.warningId);
- if (res.code !== 200) {
- ElMessage.error(res.msg || "删除失败");
- return;
- }
- ElMessage.success("删除成功");
- loadList();
- }
- async function handlePublish(row) {
- if (!row.handler) {
- ElMessage.warning("发布前请先编辑预警并选择处置人");
- return;
- }
- await ElMessageBox.confirm(`确认发布预警“${row.warningName}”吗?`, "提示", { type: "warning" });
- const res = await releaseYjxxData(row.warningId, { reviewer: row.reviewer || undefined });
- if (res.code !== 200) {
- ElMessage.error(res.msg || "发布失败");
- return;
- }
- ElMessage.success("发布成功");
- loadList();
- }
- onMounted(async () => {
- await loadDicts();
- await loadList();
- });
- </script>
- <style scoped>
- .warning-release-page {
- background: #f5f7fb;
- }
- .page-card {
- border-radius: 16px;
- margin-bottom: 16px;
- }
- .header-card {
- background: linear-gradient(135deg, #eff6ff, #f8fbff);
- border: 1px solid #dbeafe;
- }
- .page-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 16px;
- }
- .page-title {
- font-size: 22px;
- font-weight: 700;
- color: #1e3a8a;
- }
- .page-subtitle {
- margin-top: 8px;
- font-size: 13px;
- color: #64748b;
- }
- .header-actions {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- }
- .reason-wrap {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- .reason-tag {
- cursor: pointer;
- }
- .attachment-panel {
- width: 100%;
- }
- .attachment-tip {
- margin-top: 8px;
- font-size: 12px;
- color: #64748b;
- }
- .attachment-list,
- .detail-attachment-list {
- margin-top: 12px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .attachment-item,
- .detail-attachment-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 12px;
- border: 1px solid #e5e7eb;
- border-radius: 10px;
- background: #f8fafc;
- }
- .attachment-link {
- flex: 1;
- min-width: 0;
- }
- .attachment-meta {
- font-size: 12px;
- color: #94a3b8;
- white-space: nowrap;
- }
- @media (max-width: 768px) {
- .page-header {
- flex-direction: column;
- align-items: flex-start;
- }
- }
- /* 用户选择器样式 */
- .user-picker-container {
- display: flex;
- gap: 16px;
- height: 460px;
- }
- .user-picker-left {
- width: 280px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- padding: 12px;
- overflow-y: auto;
- }
- .user-picker-right {
- flex: 1;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- padding: 12px;
- overflow: hidden;
- }
- .picker-title {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- margin-bottom: 12px;
- }
- .picker-title .current-dept {
- font-weight: 400;
- color: #6b7280;
- font-size: 13px;
- }
- .device-picker-container {
- display: flex;
- gap: 16px;
- height: 460px;
- }
- .device-picker-left {
- width: 180px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- padding: 12px;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .device-picker-right {
- flex: 1;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- padding: 12px;
- overflow: hidden;
- }
- .device-type-item {
- padding: 9px 12px;
- text-align: left;
- color: #4b5563;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- background: #fff;
- cursor: pointer;
- font-size: 13px;
- }
- .device-type-item:hover {
- background: #f3f4f6;
- }
- .device-type-item.active {
- color: #2563eb;
- border-color: #2563eb;
- background: #eff6ff;
- font-weight: 600;
- }
- </style>
|