LRelease.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <!-- 预警发布 -->
  2. <template>
  3. <div class="app-container warning-release-page">
  4. <el-card shadow="never" class="page-card header-card">
  5. <div class="page-header">
  6. <div>
  7. <div class="page-title">预警发布</div>
  8. <div class="page-subtitle">支持按预警类型、预警级别、发布时间查询,并对预警信息进行新增、编辑、查看、发布等操作</div>
  9. </div>
  10. <div class="header-actions">
  11. <el-button type="primary" icon="Plus" @click="handleAdd">新增预警</el-button>
  12. <el-button icon="Refresh" @click="loadList">刷新</el-button>
  13. </div>
  14. </div>
  15. </el-card>
  16. <el-card shadow="never" class="page-card">
  17. <el-form :model="queryParams" :inline="true">
  18. <el-form-item label="预警名称">
  19. <el-input
  20. v-model="queryParams.warningName"
  21. placeholder="请输入预警名称"
  22. clearable
  23. style="width: 220px"
  24. @keyup.enter="handleQuery"
  25. />
  26. </el-form-item>
  27. <el-form-item label="预警类型">
  28. <el-select v-model="queryParams.warningType" placeholder="请选择预警类型" clearable style="width: 180px">
  29. <el-option
  30. v-for="item in warningTypeOptions"
  31. :key="item.dictValue"
  32. :label="item.dictLabel"
  33. :value="item.dictValue"
  34. />
  35. </el-select>
  36. </el-form-item>
  37. <el-form-item label="预警级别">
  38. <el-select v-model="queryParams.warningLevel" placeholder="请选择预警级别" clearable style="width: 180px">
  39. <el-option
  40. v-for="item in warningLevelOptions"
  41. :key="item.dictValue"
  42. :label="item.dictLabel"
  43. :value="item.dictValue"
  44. />
  45. </el-select>
  46. </el-form-item>
  47. <el-form-item label="发布时间">
  48. <el-date-picker
  49. v-model="queryParams.dateRange"
  50. type="datetimerange"
  51. value-format="YYYY-MM-DD HH:mm:ss"
  52. range-separator="至"
  53. start-placeholder="开始时间"
  54. end-placeholder="结束时间"
  55. style="width: 360px"
  56. />
  57. </el-form-item>
  58. <el-form-item>
  59. <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
  60. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  61. </el-form-item>
  62. </el-form>
  63. </el-card>
  64. <el-card shadow="never" class="page-card">
  65. <el-table v-loading="loading" :data="warningList">
  66. <el-table-column label="序号" width="70" align="center">
  67. <template #default="scope">
  68. {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
  69. </template>
  70. </el-table-column>
  71. <el-table-column label="预警编号" prop="warningNo" width="180" />
  72. <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
  73. <el-table-column label="预警类型" prop="warningTypeText" width="120" />
  74. <el-table-column label="预警级别" width="110" align="center">
  75. <template #default="scope">
  76. <el-tag :type="getLevelTagType(scope.row.warningLevel)">
  77. {{ scope.row.warningLevelText || scope.row.warningLevel || "-" }}
  78. </el-tag>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="预警位置" prop="location" min-width="180" show-overflow-tooltip />
  82. <el-table-column label="发布时间" prop="publishTime" width="180" />
  83. <el-table-column label="发布人" prop="publisher" width="120" />
  84. <el-table-column label="状态" width="120" align="center">
  85. <template #default="scope">
  86. <el-tag :type="getStatusTagType(scope.row.status)">
  87. {{ getStatusText(scope.row.status) }}
  88. </el-tag>
  89. </template>
  90. </el-table-column>
  91. <el-table-column label="操作" fixed="right" width="280" align="center">
  92. <template #default="scope">
  93. <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
  94. <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === 'DRAFT'">编辑</el-button>
  95. <el-button link type="success" @click="handlePublish(scope.row)" v-if="scope.row.status === 'DRAFT'">发布</el-button>
  96. <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
  97. </template>
  98. </el-table-column>
  99. </el-table>
  100. <pagination
  101. v-show="total > 0"
  102. :total="total"
  103. v-model:page="queryParams.pageNum"
  104. v-model:limit="queryParams.pageSize"
  105. @pagination="loadList"
  106. />
  107. </el-card>
  108. <el-dialog v-model="editVisible" :title="dialogTitle" width="860px" append-to-body @close="resetForm">
  109. <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
  110. <el-row :gutter="16">
  111. <el-col :span="12">
  112. <el-form-item label="预警类型" prop="warningType">
  113. <el-select v-model="formData.warningType" placeholder="请选择预警类型" style="width: 100%" @change="handleTypeLevelChange">
  114. <el-option
  115. v-for="item in warningTypeOptions"
  116. :key="item.dictValue"
  117. :label="item.dictLabel"
  118. :value="item.dictValue"
  119. />
  120. </el-select>
  121. </el-form-item>
  122. </el-col>
  123. <el-col :span="12">
  124. <el-form-item label="预警级别" prop="warningLevel">
  125. <el-select v-model="formData.warningLevel" placeholder="请选择预警级别" style="width: 100%" @change="handleTypeLevelChange">
  126. <el-option
  127. v-for="item in warningLevelOptions"
  128. :key="item.dictValue"
  129. :label="item.dictLabel"
  130. :value="item.dictValue"
  131. />
  132. </el-select>
  133. </el-form-item>
  134. </el-col>
  135. <el-col :span="12">
  136. <el-form-item label="预警专项">
  137. <el-input v-model="formData.warningSpecial" placeholder="请输入预警专项" />
  138. </el-form-item>
  139. </el-col>
  140. <el-col :span="12">
  141. <el-form-item label="权属单位">
  142. <el-input v-model="formData.ownershipUnit" placeholder="请输入权属单位" />
  143. </el-form-item>
  144. </el-col>
  145. <el-col :span="12">
  146. <el-form-item label="发布人">
  147. <el-input v-model="formData.publisher" placeholder="请输入发布人" />
  148. </el-form-item>
  149. </el-col>
  150. <el-col :span="12">
  151. <el-form-item label="处置人">
  152. <el-input v-model="formData.handler" placeholder="请输入处置人" />
  153. </el-form-item>
  154. </el-col>
  155. <el-col :span="24">
  156. <el-form-item label="预警名称" prop="warningName">
  157. <el-input v-model="formData.warningName" placeholder="请输入预警名称" maxlength="100" show-word-limit />
  158. </el-form-item>
  159. </el-col>
  160. <el-col :span="24">
  161. <el-form-item label="预警位置" prop="location">
  162. <el-input v-model="formData.location" placeholder="请输入预警位置" />
  163. </el-form-item>
  164. </el-col>
  165. <el-col :span="12">
  166. <el-form-item label="经度">
  167. <el-input v-model="formData.longitude" placeholder="请输入经度" />
  168. </el-form-item>
  169. </el-col>
  170. <el-col :span="12">
  171. <el-form-item label="纬度">
  172. <el-input v-model="formData.latitude" placeholder="请输入纬度" />
  173. </el-form-item>
  174. </el-col>
  175. <el-col :span="12">
  176. <el-form-item label="发布时间">
  177. <el-date-picker
  178. v-model="formData.publishTime"
  179. type="datetime"
  180. value-format="YYYY-MM-DD HH:mm:ss"
  181. placeholder="请选择发布时间"
  182. style="width: 100%"
  183. />
  184. </el-form-item>
  185. </el-col>
  186. <el-col :span="12">
  187. <el-form-item label="状态">
  188. <el-select v-model="formData.status" style="width: 100%">
  189. <el-option label="启用" value="ENABLED" />
  190. <el-option label="禁用" value="DISABLED" />
  191. <el-option label="作废" value="INVALID" />
  192. </el-select>
  193. </el-form-item>
  194. </el-col>
  195. <el-col :span="24">
  196. <el-form-item label="内容描述" prop="warningContent">
  197. <el-input
  198. v-model="formData.warningContent"
  199. type="textarea"
  200. :rows="4"
  201. maxlength="500"
  202. show-word-limit
  203. placeholder="请输入预警内容描述"
  204. />
  205. </el-form-item>
  206. </el-col>
  207. <el-col :span="24" v-if="reasonOptions.length">
  208. <el-form-item label="推荐原因">
  209. <div class="reason-wrap">
  210. <el-tag
  211. v-for="item in reasonOptions"
  212. :key="item.reasonId"
  213. class="reason-tag"
  214. effect="plain"
  215. @click="appendReason(item)"
  216. >
  217. {{ item.reasonName }}
  218. </el-tag>
  219. </div>
  220. </el-form-item>
  221. </el-col>
  222. <el-col :span="24">
  223. <el-form-item label="备注">
  224. <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
  225. </el-form-item>
  226. </el-col>
  227. <el-col :span="24">
  228. <el-form-item label="附件上传">
  229. <div class="attachment-panel">
  230. <el-upload
  231. ref="uploadRef"
  232. action="#"
  233. :auto-upload="false"
  234. :show-file-list="false"
  235. multiple
  236. :on-change="handleUploadChange"
  237. :on-exceed="handleUploadExceed"
  238. :limit="9"
  239. accept=".pdf,.doc,.docx,.xls,.xlsx,.zip,.jpg,.jpeg,.png"
  240. >
  241. <el-button type="primary">选择附件</el-button>
  242. </el-upload>
  243. <div class="attachment-tip">支持常见文档、压缩包和图片,新增或编辑时会随保存接口一起上传。</div>
  244. <div v-if="formAttachments.length" class="attachment-list">
  245. <div v-for="item in formAttachments" :key="item.uid || item.attachmentId || item.name" class="attachment-item">
  246. <el-link
  247. v-if="item.url"
  248. :href="item.url"
  249. target="_blank"
  250. type="primary"
  251. class="attachment-link"
  252. >
  253. {{ item.name }}
  254. </el-link>
  255. <span v-else class="attachment-link">{{ item.name }}</span>
  256. <span class="attachment-meta">{{ item.sizeText || item.createTime || "" }}</span>
  257. <el-button link type="danger" @click="removeAttachment(item)">移除</el-button>
  258. </div>
  259. </div>
  260. </div>
  261. </el-form-item>
  262. </el-col>
  263. </el-row>
  264. </el-form>
  265. <template #footer>
  266. <div class="dialog-footer">
  267. <el-button @click="editVisible = false">取 消</el-button>
  268. <el-button type="primary" :loading="submitLoading" @click="submitForm">保 存</el-button>
  269. </div>
  270. </template>
  271. </el-dialog>
  272. <el-dialog v-model="detailVisible" title="预警详情" width="900px" append-to-body>
  273. <template v-if="detailData.basicInfo">
  274. <el-descriptions :column="2" border>
  275. <el-descriptions-item label="预警编号">{{ detailData.basicInfo.warningNo || "-" }}</el-descriptions-item>
  276. <el-descriptions-item label="预警名称">{{ detailData.basicInfo.warningName || "-" }}</el-descriptions-item>
  277. <el-descriptions-item label="预警类型">{{ formatTypeText(detailData.basicInfo.warningType) }}</el-descriptions-item>
  278. <el-descriptions-item label="预警级别">
  279. <el-tag :type="getLevelTagType(detailData.basicInfo.warningLevel)">
  280. {{ formatLevelText(detailData.basicInfo.warningLevel) }}
  281. </el-tag>
  282. </el-descriptions-item>
  283. <el-descriptions-item label="预警专项">{{ detailData.basicInfo.warningSpecial || "-" }}</el-descriptions-item>
  284. <el-descriptions-item label="权属单位">{{ detailData.basicInfo.ownershipUnit || "-" }}</el-descriptions-item>
  285. <el-descriptions-item label="发布人">{{ detailData.basicInfo.publisher || "-" }}</el-descriptions-item>
  286. <el-descriptions-item label="处置人">{{ detailData.basicInfo.handler || "-" }}</el-descriptions-item>
  287. <el-descriptions-item label="预警位置" :span="2">{{ detailData.basicInfo.location || "-" }}</el-descriptions-item>
  288. <el-descriptions-item label="发布时间">{{ detailData.basicInfo.publishTime || "-" }}</el-descriptions-item>
  289. <el-descriptions-item label="状态">{{ getStatusText(detailData.basicInfo.status) }}</el-descriptions-item>
  290. <el-descriptions-item label="内容描述" :span="2">{{ detailData.basicInfo.warningContent || "-" }}</el-descriptions-item>
  291. <el-descriptions-item label="备注" :span="2">{{ detailData.basicInfo.remark || "-" }}</el-descriptions-item>
  292. </el-descriptions>
  293. <el-divider content-position="left">关联报警</el-divider>
  294. <el-table :data="detailData.alarmList || []" border size="small">
  295. <el-table-column label="报警ID" prop="alarmId" min-width="160" />
  296. <el-table-column label="报警名称" prop="alarmName" min-width="180" show-overflow-tooltip />
  297. <el-table-column label="报警类型" min-width="120">
  298. <template #default="scope">
  299. {{ formatTypeText(scope.row.alarmType || scope.row.warningType) }}
  300. </template>
  301. </el-table-column>
  302. </el-table>
  303. <el-divider content-position="left">处置记录</el-divider>
  304. <el-table :data="detailData.disposalHistory || detailData.disposalList || []" border size="small">
  305. <el-table-column label="处置类型" prop="disposalTypeName" min-width="120" />
  306. <el-table-column label="处置人" prop="disposalUser" min-width="120" />
  307. <el-table-column label="处置内容" prop="disposalContent" min-width="220" show-overflow-tooltip />
  308. <el-table-column label="处置时间" prop="createTime" min-width="180" />
  309. </el-table>
  310. <el-divider content-position="left">附件信息</el-divider>
  311. <div v-if="(detailData.attachmentList || []).length" class="detail-attachment-list">
  312. <div v-for="item in detailData.attachmentList" :key="item.attachmentId || item.attachmentName" class="detail-attachment-item">
  313. <el-link :href="buildAttachmentUrl(item.attachmentUrl)" target="_blank" type="primary">
  314. {{ item.attachmentName || "附件" }}
  315. </el-link>
  316. <span>{{ item.createTime || "-" }}</span>
  317. </div>
  318. </div>
  319. <el-empty v-else description="暂无附件信息" />
  320. </template>
  321. </el-dialog>
  322. </div>
  323. </template>
  324. <script setup>
  325. import {computed, onMounted, reactive, ref} from "vue";
  326. import {ElMessage, ElMessageBox} from "element-plus";
  327. import {
  328. deleteYjxxData,
  329. getAlertTypeDictionaryData,
  330. getdbyjlistData,
  331. getWarningDetailData,
  332. getWarningReasonListData,
  333. releaseYjxxData,
  334. submitYjxxData,
  335. updateYjxxData
  336. } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
  337. const loading = ref(false);
  338. const submitLoading = ref(false);
  339. const total = ref(0);
  340. const warningList = ref([]);
  341. const warningTypeOptions = ref([]);
  342. const warningLevelOptions = ref([]);
  343. const reasonOptions = ref([]);
  344. const uploadRef = ref();
  345. const formAttachments = ref([]);
  346. const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
  347. const editVisible = ref(false);
  348. const detailVisible = ref(false);
  349. const dialogTitle = ref("新增预警");
  350. const isEdit = ref(false);
  351. const formRef = ref();
  352. const detailData = ref({});
  353. const queryParams = reactive({
  354. pageNum: 1,
  355. pageSize: 10,
  356. warningName: "",
  357. warningType: "",
  358. warningLevel: "",
  359. dateRange: []
  360. });
  361. const formData = reactive(createEmptyForm());
  362. const formRules = {
  363. warningType: [{ required: true, message: "请选择预警类型", trigger: "change" }],
  364. warningLevel: [{ required: true, message: "请选择预警级别", trigger: "change" }],
  365. warningName: [{ required: true, message: "请输入预警名称", trigger: "blur" }],
  366. location: [{ required: true, message: "请输入预警位置", trigger: "blur" }],
  367. warningContent: [{ required: true, message: "请输入预警内容描述", trigger: "blur" }]
  368. };
  369. const warningTypeLabelMap = computed(() => {
  370. return warningTypeOptions.value.reduce((acc, item) => {
  371. acc[item.dictValue] = item.dictLabel;
  372. return acc;
  373. }, {});
  374. });
  375. const warningLevelLabelMap = computed(() => {
  376. return warningLevelOptions.value.reduce((acc, item) => {
  377. acc[item.dictValue] = item.dictLabel;
  378. return acc;
  379. }, {});
  380. });
  381. function createEmptyForm() {
  382. return {
  383. warningId: "",
  384. warningNo: "",
  385. warningName: "",
  386. warningType: "",
  387. warningLevel: "",
  388. warningSpecial: "",
  389. location: "",
  390. longitude: "",
  391. latitude: "",
  392. ownershipUnit: "",
  393. publisher: "",
  394. publishTime: "",
  395. handler: "",
  396. status: "ENABLED",
  397. warningContent: "",
  398. remark: ""
  399. };
  400. }
  401. function resetForm() {
  402. Object.assign(formData, createEmptyForm());
  403. reasonOptions.value = [];
  404. formAttachments.value = [];
  405. uploadRef.value?.clearFiles?.();
  406. formRef.value?.clearValidate();
  407. }
  408. function formatFileSize(bytes) {
  409. if (!bytes) return "";
  410. const units = ["B", "KB", "MB", "GB"];
  411. const index = Math.floor(Math.log(bytes) / Math.log(1024));
  412. return `${(bytes / 1024 ** index).toFixed(2)} ${units[index]}`;
  413. }
  414. function buildAttachmentUrl(path) {
  415. if (!path) return "";
  416. if (/^https?:\/\//i.test(path)) {
  417. return path;
  418. }
  419. const base = minioBaseUrl.endsWith("/") ? minioBaseUrl : `${minioBaseUrl}/`;
  420. return `${base}${String(path).replace(/^\/+/, "")}`;
  421. }
  422. function normalizeExistingAttachment(item) {
  423. return {
  424. uid: item.attachmentId || item.attachmentName,
  425. name: item.attachmentName || "附件",
  426. url: buildAttachmentUrl(item.attachmentUrl),
  427. attachmentId: item.attachmentId,
  428. attachmentUrl: item.attachmentUrl,
  429. createTime: item.createTime || "",
  430. raw: null,
  431. sizeText: item.attachmentSize ? formatFileSize(item.attachmentSize) : ""
  432. };
  433. }
  434. function handleUploadChange(file) {
  435. if (!file?.raw) return;
  436. formAttachments.value.push({
  437. uid: file.uid,
  438. name: file.name,
  439. raw: file.raw,
  440. url: "",
  441. sizeText: formatFileSize(file.size)
  442. });
  443. }
  444. function handleUploadExceed() {
  445. ElMessage.warning("最多上传 9 个附件");
  446. }
  447. function removeAttachment(item) {
  448. formAttachments.value = formAttachments.value.filter((current) => current.uid !== item.uid);
  449. }
  450. function formatTypeText(value) {
  451. return warningTypeLabelMap.value[value] || value || "-";
  452. }
  453. function formatLevelText(value) {
  454. return warningLevelLabelMap.value[value] || value || "-";
  455. }
  456. function getLevelTagType(level) {
  457. return {
  458. "0": "danger",
  459. "1": "danger",
  460. "2": "warning",
  461. "3": "success",
  462. "4": "info"
  463. }[level] || "info";
  464. }
  465. function getStatusText(status) {
  466. return {
  467. ENABLED: "启用",
  468. DISABLED: "禁用",
  469. INVALID: "作废",
  470. DRAFT: "草稿",
  471. RELEASED: "已发布",
  472. PENDING: "待处理",
  473. PROCESSING: "处理中",
  474. HANDLED: "已处理",
  475. CLOSED: "已解除",
  476. RETURNED: "退回"
  477. }[status] || status || "-";
  478. }
  479. function getStatusTagType(status) {
  480. return {
  481. ENABLED: "success",
  482. DISABLED: "warning",
  483. INVALID: "danger",
  484. DRAFT: "info",
  485. RELEASED: "success",
  486. PENDING: "warning",
  487. PROCESSING: "warning",
  488. HANDLED: "success",
  489. CLOSED: "info",
  490. RETURNED: "danger"
  491. }[status] || "";
  492. }
  493. function normalizeWarningRow(item) {
  494. const row = { ...item };
  495. row.warningId = item.warningId || item.warning_id || "";
  496. row.warningNo = item.warningNo || item.warning_no || "";
  497. row.warningName = item.warningName || item.warning_name || "";
  498. row.warningType = item.warningType || item.warning_type || "";
  499. row.warningLevel = item.warningLevel || item.warning_level || "";
  500. row.warningSpecial = item.warningSpecial || item.warning_special || "";
  501. row.location = item.location || "";
  502. row.longitude = item.longitude || "";
  503. row.latitude = item.latitude || "";
  504. row.ownershipUnit = item.ownershipUnit || item.ownership_unit || "";
  505. row.publisher = item.publisher || "";
  506. row.publishTime = item.publishTime || item.publish_time || "";
  507. row.handler = item.handler || "";
  508. row.status = item.status || "";
  509. row.warningContent = item.warningContent || item.warning_content || "";
  510. row.remark = item.remark || "";
  511. row.warningTypeText = formatTypeText(row.warningType);
  512. row.warningLevelText = formatLevelText(row.warningLevel);
  513. return row;
  514. }
  515. function normalizeAlarmRow(item) {
  516. return {
  517. ...item,
  518. alarmId: item.alarmId || item.alarm_id || "",
  519. alarmName: item.alarmName || item.alarm_name || item.deviceName || item.device_name || "",
  520. alarmType: item.alarmType || item.alarm_type || item.warningType || item.warning_type || ""
  521. };
  522. }
  523. function normalizeDisposalRow(item) {
  524. return {
  525. ...item,
  526. disposalTypeName: item.disposalTypeName || item.disposal_type_name || item.disposalType || item.disposal_type || "",
  527. disposalUser: item.disposalUser || item.disposal_user || "",
  528. disposalContent: item.disposalContent || item.disposal_content || "",
  529. createTime: item.createTime || item.create_time || ""
  530. };
  531. }
  532. async function loadDicts() {
  533. const [typeRes, levelRes] = await Promise.all([
  534. getAlertTypeDictionaryData({ dictType: "alert_type" }),
  535. getAlertTypeDictionaryData({ dictType: "alert_level" })
  536. ]);
  537. warningTypeOptions.value = typeRes.rows || [];
  538. warningLevelOptions.value = levelRes.rows || [];
  539. }
  540. async function loadReasonOptions() {
  541. if (!formData.warningType || !formData.warningLevel) {
  542. reasonOptions.value = [];
  543. return;
  544. }
  545. const res = await getWarningReasonListData({
  546. warningType: formData.warningType,
  547. warningLevel: formData.warningLevel
  548. });
  549. reasonOptions.value = res.data || [];
  550. }
  551. function handleTypeLevelChange() {
  552. loadReasonOptions();
  553. }
  554. function appendReason(item) {
  555. const content = item.description ? `${item.reasonName}:${item.description}` : item.reasonName;
  556. formData.warningContent = formData.warningContent
  557. ? `${formData.warningContent}\n${content}`
  558. : content;
  559. }
  560. async function loadList() {
  561. loading.value = true;
  562. try {
  563. const params = {
  564. pageNum: queryParams.pageNum,
  565. pageSize: queryParams.pageSize,
  566. warningName: queryParams.warningName,
  567. warningType: queryParams.warningType,
  568. warningLevel: queryParams.warningLevel,
  569. startTime: queryParams.dateRange?.[0] || "",
  570. endTime: queryParams.dateRange?.[1] || ""
  571. };
  572. const res = await getdbyjlistData(params);
  573. warningList.value = (res.data?.records || []).map(normalizeWarningRow);
  574. total.value = res.data?.total || 0;
  575. } finally {
  576. loading.value = false;
  577. }
  578. }
  579. function handleQuery() {
  580. queryParams.pageNum = 1;
  581. loadList();
  582. }
  583. function resetQuery() {
  584. queryParams.pageNum = 1;
  585. queryParams.pageSize = 10;
  586. queryParams.warningName = "";
  587. queryParams.warningType = "";
  588. queryParams.warningLevel = "";
  589. queryParams.dateRange = [];
  590. loadList();
  591. }
  592. function handleAdd() {
  593. isEdit.value = false;
  594. dialogTitle.value = "新增预警";
  595. resetForm();
  596. editVisible.value = true;
  597. }
  598. async function handleEdit(row) {
  599. isEdit.value = true;
  600. dialogTitle.value = "编辑预警";
  601. resetForm();
  602. Object.assign(formData, { ...row });
  603. const detailRes = await getWarningDetailData(row.warningId);
  604. const detail = detailRes.data || {};
  605. formAttachments.value = (detail.attachmentList || []).map(normalizeExistingAttachment);
  606. await loadReasonOptions();
  607. editVisible.value = true;
  608. }
  609. async function submitForm() {
  610. await formRef.value?.validate();
  611. submitLoading.value = true;
  612. try {
  613. const payload = {
  614. warning: {
  615. warningId: formData.warningId,
  616. warningNo: formData.warningNo,
  617. warningName: formData.warningName,
  618. warningType: formData.warningType,
  619. warningLevel: formData.warningLevel,
  620. warningSpecial: formData.warningSpecial,
  621. location: formData.location,
  622. longitude: formData.longitude,
  623. latitude: formData.latitude,
  624. ownershipUnit: formData.ownershipUnit,
  625. publisher: formData.publisher,
  626. publishTime: formData.publishTime,
  627. handler: formData.handler,
  628. status: formData.status || "ENABLED",
  629. warningContent: formData.warningContent,
  630. remark: formData.remark
  631. },
  632. alarmIds: [],
  633. files: formAttachments.value.map((item) => item.raw).filter(Boolean)
  634. };
  635. const res = isEdit.value ? await updateYjxxData(payload) : await submitYjxxData(payload);
  636. if (res.code !== 200) {
  637. ElMessage.error(res.msg || "保存失败");
  638. return;
  639. }
  640. ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
  641. editVisible.value = false;
  642. loadList();
  643. } finally {
  644. submitLoading.value = false;
  645. }
  646. }
  647. async function handleView(row) {
  648. const res = await getWarningDetailData(row.warningId);
  649. const detail = res.data || {};
  650. const basicSource = detail.basicInfo || detail;
  651. detailData.value = {
  652. ...detail,
  653. basicInfo: normalizeWarningRow({ ...row, ...basicSource }),
  654. alarmList: (detail.alarmList || []).map(normalizeAlarmRow),
  655. disposalHistory: (detail.disposalHistory || detail.disposalList || []).map(normalizeDisposalRow)
  656. };
  657. detailVisible.value = true;
  658. }
  659. async function handleDelete(row) {
  660. await ElMessageBox.confirm(`确认删除预警“${row.warningName}”吗?`, "提示", { type: "warning" });
  661. const res = await deleteYjxxData(row.warningId);
  662. if (res.code !== 200) {
  663. ElMessage.error(res.msg || "删除失败");
  664. return;
  665. }
  666. ElMessage.success("删除成功");
  667. loadList();
  668. }
  669. async function handlePublish(row) {
  670. await ElMessageBox.confirm(`确认发布预警“${row.warningName}”吗?`, "提示", { type: "warning" });
  671. const res = await releaseYjxxData(row.warningId);
  672. if (res.code !== 200) {
  673. ElMessage.error(res.msg || "发布失败");
  674. return;
  675. }
  676. ElMessage.success("发布成功");
  677. loadList();
  678. }
  679. onMounted(async () => {
  680. await loadDicts();
  681. await loadList();
  682. });
  683. </script>
  684. <style scoped>
  685. .warning-release-page {
  686. background: #f5f7fb;
  687. }
  688. .page-card {
  689. border-radius: 16px;
  690. margin-bottom: 16px;
  691. }
  692. .header-card {
  693. background: linear-gradient(135deg, #eff6ff, #f8fbff);
  694. border: 1px solid #dbeafe;
  695. }
  696. .page-header {
  697. display: flex;
  698. justify-content: space-between;
  699. align-items: center;
  700. gap: 16px;
  701. }
  702. .page-title {
  703. font-size: 22px;
  704. font-weight: 700;
  705. color: #1e3a8a;
  706. }
  707. .page-subtitle {
  708. margin-top: 8px;
  709. font-size: 13px;
  710. color: #64748b;
  711. }
  712. .header-actions {
  713. display: flex;
  714. gap: 10px;
  715. flex-wrap: wrap;
  716. }
  717. .reason-wrap {
  718. display: flex;
  719. flex-wrap: wrap;
  720. gap: 8px;
  721. }
  722. .reason-tag {
  723. cursor: pointer;
  724. }
  725. .attachment-panel {
  726. width: 100%;
  727. }
  728. .attachment-tip {
  729. margin-top: 8px;
  730. font-size: 12px;
  731. color: #64748b;
  732. }
  733. .attachment-list,
  734. .detail-attachment-list {
  735. margin-top: 12px;
  736. display: flex;
  737. flex-direction: column;
  738. gap: 8px;
  739. }
  740. .attachment-item,
  741. .detail-attachment-item {
  742. display: flex;
  743. align-items: center;
  744. justify-content: space-between;
  745. gap: 12px;
  746. padding: 10px 12px;
  747. border: 1px solid #e5e7eb;
  748. border-radius: 10px;
  749. background: #f8fafc;
  750. }
  751. .attachment-link {
  752. flex: 1;
  753. min-width: 0;
  754. }
  755. .attachment-meta {
  756. font-size: 12px;
  757. color: #94a3b8;
  758. white-space: nowrap;
  759. }
  760. @media (max-width: 768px) {
  761. .page-header {
  762. flex-direction: column;
  763. align-items: flex-start;
  764. }
  765. }
  766. </style>