|
@@ -38,7 +38,7 @@
|
|
|
<el-option
|
|
<el-option
|
|
|
v-for="d in warningLevelOptions"
|
|
v-for="d in warningLevelOptions"
|
|
|
:key="d.value"
|
|
:key="d.value"
|
|
|
- :label="getWarningLevelDisplay(d.value).label"
|
|
|
|
|
|
|
+ :label="d.label"
|
|
|
:value="d.value"
|
|
:value="d.value"
|
|
|
/>
|
|
/>
|
|
|
</el-select>
|
|
</el-select>
|
|
@@ -74,11 +74,12 @@
|
|
|
<!-- 列表 -->
|
|
<!-- 列表 -->
|
|
|
<el-card shadow="hover" class="list-card">
|
|
<el-card shadow="hover" class="list-card">
|
|
|
<div class="list-toolbar">
|
|
<div class="list-toolbar">
|
|
|
- <span class="list-total">共 {{ total }} 条警示信息</span>
|
|
|
|
|
|
|
+ <span class="list-total">共 {{ total }} 条风险警示信息</span>
|
|
|
<el-button type="primary" :icon="Plus" @click="handleAdd">发布警示</el-button>
|
|
<el-button type="primary" :icon="Plus" @click="handleAdd">发布警示</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<el-table v-loading="loading" :data="warningList" border stripe>
|
|
<el-table v-loading="loading" :data="warningList" border stripe>
|
|
|
|
|
+ <el-table-column type="index" label="序号" align="center" width="60" />
|
|
|
<el-table-column prop="warningTitle" label="警示标题" align="center" :show-overflow-tooltip="true" min-width="220" />
|
|
<el-table-column prop="warningTitle" label="警示标题" align="center" :show-overflow-tooltip="true" min-width="220" />
|
|
|
<el-table-column label="警示类型" align="center" prop="warningType" width="130">
|
|
<el-table-column label="警示类型" align="center" prop="warningType" width="130">
|
|
|
<template #default="{ row }">{{ getDictLabel(warningTypeOptions, row.warningType) }}</template>
|
|
<template #default="{ row }">{{ getDictLabel(warningTypeOptions, row.warningType) }}</template>
|
|
@@ -86,7 +87,7 @@
|
|
|
<el-table-column label="警示等级" align="center" width="120">
|
|
<el-table-column label="警示等级" align="center" width="120">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="getWarningLevelTag(row.warningLevel)" effect="light" round>
|
|
<el-tag :type="getWarningLevelTag(row.warningLevel)" effect="light" round>
|
|
|
- {{ getWarningLevelDisplay(row.warningLevel).label }}
|
|
|
|
|
|
|
+ {{ getDictLabel(warningLevelOptions, row.warningLevel) }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
@@ -146,7 +147,7 @@
|
|
|
<el-option
|
|
<el-option
|
|
|
v-for="d in warningLevelOptions"
|
|
v-for="d in warningLevelOptions"
|
|
|
:key="d.value"
|
|
:key="d.value"
|
|
|
- :label="getWarningLevelDisplay(d.value).label"
|
|
|
|
|
|
|
+ :label="d.label"
|
|
|
:value="d.value"
|
|
:value="d.value"
|
|
|
/>
|
|
/>
|
|
|
</el-select>
|
|
</el-select>
|
|
@@ -173,36 +174,6 @@
|
|
|
style="width: 100%"
|
|
style="width: 100%"
|
|
|
/>
|
|
/>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
-
|
|
|
|
|
- <el-form-item label="附件上传">
|
|
|
|
|
- <el-upload
|
|
|
|
|
- v-model:file-list="attachmentFileList"
|
|
|
|
|
- :http-request="customUpload"
|
|
|
|
|
- :limit="1"
|
|
|
|
|
- :accept="'.pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png'"
|
|
|
|
|
- :before-upload="beforeAttachmentUpload"
|
|
|
|
|
- :on-success="handleAttachmentSuccess"
|
|
|
|
|
- :on-error="handleUploadError"
|
|
|
|
|
- :on-exceed="handleAttachmentExceed"
|
|
|
|
|
- :on-remove="handleAttachmentRemove"
|
|
|
|
|
- >
|
|
|
|
|
- <template #trigger>
|
|
|
|
|
- <div class="upload-trigger-area">
|
|
|
|
|
- <div class="upload-action-row">
|
|
|
|
|
- <el-button type="primary" plain>选择文件</el-button>
|
|
|
|
|
- <span class="upload-placeholder">{{ attachmentFileList.length > 0 ? '已选择 1 个文件' : '未选择任何文件' }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="upload-tip-box">
|
|
|
|
|
- <div class="upload-tip-icon">
|
|
|
|
|
- <el-icon><UploadFilled /></el-icon>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="upload-tip-text">点击上传或拖拽文件到此区域</div>
|
|
|
|
|
- <div class="upload-tip-desc">支持 PDF、Word、Excel、图片格式,单个文件不超过10MB</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
<div class="dialog-footer">
|
|
@@ -219,7 +190,7 @@
|
|
|
<el-descriptions-item label="警示类型">{{ getDictLabel(warningTypeOptions, viewForm.warningType) }}</el-descriptions-item>
|
|
<el-descriptions-item label="警示类型">{{ getDictLabel(warningTypeOptions, viewForm.warningType) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="警示等级">
|
|
<el-descriptions-item label="警示等级">
|
|
|
<el-tag :type="getWarningLevelTag(viewForm.warningLevel)">
|
|
<el-tag :type="getWarningLevelTag(viewForm.warningLevel)">
|
|
|
- {{ getWarningLevelDisplay(viewForm.warningLevel).label }}
|
|
|
|
|
|
|
+ {{ getDictLabel(warningLevelOptions, viewForm.warningLevel) }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="发布人">{{ viewForm.releaserName }}</el-descriptions-item>
|
|
<el-descriptions-item label="发布人">{{ viewForm.releaserName }}</el-descriptions-item>
|
|
@@ -228,18 +199,6 @@
|
|
|
<div class="multi-line">{{ viewForm.warningContent || '—' }}</div>
|
|
<div class="multi-line">{{ viewForm.warningContent || '—' }}</div>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="备注">{{ viewForm.remark || '—' }}</el-descriptions-item>
|
|
<el-descriptions-item label="备注">{{ viewForm.remark || '—' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item v-if="viewForm.attachmentUrl" label="附件">
|
|
|
|
|
- <a
|
|
|
|
|
- class="attachment-link"
|
|
|
|
|
- :href="viewForm.attachmentUrl"
|
|
|
|
|
- target="_blank"
|
|
|
|
|
- rel="noopener noreferrer"
|
|
|
|
|
- @click.prevent="handleDownloadAttachment(viewForm.attachmentUrl)"
|
|
|
|
|
- >
|
|
|
|
|
- <el-icon><Document /></el-icon>
|
|
|
|
|
- <span>{{ viewForm.attachmentName || '下载附件' }}</span>
|
|
|
|
|
- </a>
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="viewDialog.visible = false">关闭</el-button>
|
|
<el-button @click="viewDialog.visible = false">关闭</el-button>
|
|
@@ -253,11 +212,9 @@ import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
|
|
|
import { useUserStore } from '@/store/modules/user';
|
|
import { useUserStore } from '@/store/modules/user';
|
|
|
import { listWarning, getWarning, addWarning, delWarning } from '@/api/risk/warning';
|
|
import { listWarning, getWarning, addWarning, delWarning } from '@/api/risk/warning';
|
|
|
import { getDicts } from '@/api/system/dict/data';
|
|
import { getDicts } from '@/api/system/dict/data';
|
|
|
-import type { DictDataVO } from '@/api/system/dict/data/types';
|
|
|
|
|
import type { RiskWarningBo, RiskWarningVo } from '@/api/risk/types';
|
|
import type { RiskWarningBo, RiskWarningVo } from '@/api/risk/types';
|
|
|
-import { uploadFile } from '@/api/risk/file';
|
|
|
|
|
-import { ElMessage, FormInstance, UploadUserFile } from 'element-plus';
|
|
|
|
|
-import { Document, Plus, UploadFilled } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
+import { FormInstance } from 'element-plus';
|
|
|
|
|
+import { Plus } from '@element-plus/icons-vue';
|
|
|
import type { ComponentInternalInstance } from 'vue';
|
|
import type { ComponentInternalInstance } from 'vue';
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
@@ -269,29 +226,21 @@ const total = ref(0);
|
|
|
const dateRange = ref<string[]>([]);
|
|
const dateRange = ref<string[]>([]);
|
|
|
const warningTypeOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
const warningTypeOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
|
const warningLevelOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
const warningLevelOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
|
-const attachmentFileList = ref<UploadUserFile[]>([]);
|
|
|
|
|
|
|
|
|
|
const queryFormRef = ref<FormInstance>();
|
|
const queryFormRef = ref<FormInstance>();
|
|
|
const formRef = ref<FormInstance>();
|
|
const formRef = ref<FormInstance>();
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 等级字典值 → 需求显示文字 + el-tag 颜色
|
|
|
|
|
- * 字典值 urgent / important / normal
|
|
|
|
|
- * 需求显示:重大风险(red) / 较大风险(yellow) / 一般风险(blue)
|
|
|
|
|
|
|
+ * 警示等级中文 value → el-tag 颜色
|
|
|
|
|
+ * 字典 value 与设计稿对应:重大风险(红) / 较大风险(黄) / 一般风险(蓝)
|
|
|
*/
|
|
*/
|
|
|
-const LEVEL_DISPLAY: Record<string, { label: string; tag: 'danger' | 'warning' | 'primary' }> = {
|
|
|
|
|
- urgent: { label: '重大风险', tag: 'danger' },
|
|
|
|
|
- important: { label: '较大风险', tag: 'warning' },
|
|
|
|
|
- normal: { label: '一般风险', tag: 'primary' }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const getWarningLevelDisplay = (val: string) => {
|
|
|
|
|
- return LEVEL_DISPLAY[val] || { label: val || '-', tag: 'info' };
|
|
|
|
|
|
|
+const LEVEL_TAG: Record<string, 'danger' | 'warning' | 'primary'> = {
|
|
|
|
|
+ '重大风险': 'danger',
|
|
|
|
|
+ '较大风险': 'warning',
|
|
|
|
|
+ '一般风险': 'primary'
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const getWarningLevelTag = (val: string): any => {
|
|
|
|
|
- return LEVEL_DISPLAY[val]?.tag || 'info';
|
|
|
|
|
-};
|
|
|
|
|
|
|
+const getWarningLevelTag = (val: string) => LEVEL_TAG[val] || 'info';
|
|
|
|
|
|
|
|
const getDictLabel = (opts: Array<{ label: string; value: string }>, val: string) => {
|
|
const getDictLabel = (opts: Array<{ label: string; value: string }>, val: string) => {
|
|
|
if (!val) return '';
|
|
if (!val) return '';
|
|
@@ -338,9 +287,7 @@ const data = reactive<any>({
|
|
|
expireTime: '',
|
|
expireTime: '',
|
|
|
releaserName: '',
|
|
releaserName: '',
|
|
|
publishScope: '全员',
|
|
publishScope: '全员',
|
|
|
- status: '已发布',
|
|
|
|
|
- attachmentUrl: '',
|
|
|
|
|
- attachmentName: ''
|
|
|
|
|
|
|
+ status: '已发布'
|
|
|
} as any,
|
|
} as any,
|
|
|
queryParams: {
|
|
queryParams: {
|
|
|
pageNum: 1,
|
|
pageNum: 1,
|
|
@@ -442,19 +389,9 @@ const handleDelete = async (row: RiskWarningVo) => {
|
|
|
const submitForm = () => {
|
|
const submitForm = () => {
|
|
|
formRef.value?.validate(async (valid: boolean) => {
|
|
formRef.value?.validate(async (valid: boolean) => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
- // 自动填充发布人
|
|
|
|
|
- form.value.releaserName = form.value.releaserName || userStore.nickname || 'admin';
|
|
|
|
|
|
|
+ // 自动填充发布人(从当前登录用户取)
|
|
|
|
|
+ form.value.releaserName = form.value.releaserName || userStore.nickname || userStore.name || 'admin';
|
|
|
form.value.publishScope = '全员';
|
|
form.value.publishScope = '全员';
|
|
|
- // 提取附件 URL 与原始文件名
|
|
|
|
|
- const attach: any = attachmentFileList.value.find((f: any) => f.status === 'success');
|
|
|
|
|
- if (attach) {
|
|
|
|
|
- form.value.attachmentUrl =
|
|
|
|
|
- attach.url || attach.response?.data?.url || attach.response?.data?.ossId || '';
|
|
|
|
|
- form.value.attachmentName = attach.name || attach.response?.data?.originalName || '';
|
|
|
|
|
- } else {
|
|
|
|
|
- form.value.attachmentUrl = '';
|
|
|
|
|
- form.value.attachmentName = '';
|
|
|
|
|
- }
|
|
|
|
|
await addWarning(form.value as RiskWarningBo);
|
|
await addWarning(form.value as RiskWarningBo);
|
|
|
proxy?.$modal.msgSuccess('发布成功');
|
|
proxy?.$modal.msgSuccess('发布成功');
|
|
|
dialog.visible = false;
|
|
dialog.visible = false;
|
|
@@ -481,11 +418,8 @@ const reset = () => {
|
|
|
expireTime: '',
|
|
expireTime: '',
|
|
|
releaserName: '',
|
|
releaserName: '',
|
|
|
publishScope: '全员',
|
|
publishScope: '全员',
|
|
|
- status: '已发布',
|
|
|
|
|
- attachmentUrl: '',
|
|
|
|
|
- attachmentName: ''
|
|
|
|
|
|
|
+ status: '已发布'
|
|
|
} as any;
|
|
} as any;
|
|
|
- attachmentFileList.value = [];
|
|
|
|
|
formRef.value?.resetFields();
|
|
formRef.value?.resetFields();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -494,91 +428,6 @@ const closeDialog = () => {
|
|
|
reset();
|
|
reset();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// ============ 附件上传(MinIO) ============
|
|
|
|
|
-const ALLOWED_EXTS = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png'];
|
|
|
|
|
-const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 自定义上传(走 axios baseURL,避免跨域/405)
|
|
|
|
|
- */
|
|
|
|
|
-const customUpload = async (options: any) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res: any = await uploadFile(options.file);
|
|
|
|
|
- if (res && res.code === 200 && res.data) {
|
|
|
|
|
- // 把后端返回的 url 写入 file 对象,方便 onSuccess 后回填
|
|
|
|
|
- options.file.url = res.data.url;
|
|
|
|
|
- options.onSuccess(res, options.file);
|
|
|
|
|
- } else {
|
|
|
|
|
- options.onError(new Error(res?.msg || '上传失败'));
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err: any) {
|
|
|
|
|
- options.onError(err);
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 上传前校验扩展名 + 大小
|
|
|
|
|
- */
|
|
|
|
|
-const beforeAttachmentUpload = (file: any) => {
|
|
|
|
|
- const ext = (file.name.split('.').pop() || '').toLowerCase();
|
|
|
|
|
- if (!ALLOWED_EXTS.includes(ext)) {
|
|
|
|
|
- ElMessage.warning(`不支持的文件类型 .${ext},仅允许: ${ALLOWED_EXTS.join('/')}`);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- if (file.size > MAX_FILE_SIZE) {
|
|
|
|
|
- ElMessage.warning(`文件 ${file.name} 超过 10MB 上限`);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 单个文件上传成功
|
|
|
|
|
- */
|
|
|
|
|
-const handleAttachmentSuccess = (response: any, file: any) => {
|
|
|
|
|
- if (response && response.code === 200) {
|
|
|
|
|
- ElMessage.success(`附件 ${file.name} 上传成功`);
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(`附件 ${file.name} 上传失败: ${response?.msg || '未知错误'}`);
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 上传失败
|
|
|
|
|
- */
|
|
|
|
|
-const handleUploadError = (error: any) => {
|
|
|
|
|
- console.error('附件上传失败', error);
|
|
|
|
|
- ElMessage.error(`附件上传失败: ${error?.message || '网络异常'}`);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 超过上传数量限制
|
|
|
|
|
- */
|
|
|
|
|
-const handleAttachmentExceed = () => {
|
|
|
|
|
- ElMessage.warning('最多只能上传 1 个附件,如需更换请先删除已有附件');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 移除附件
|
|
|
|
|
- */
|
|
|
|
|
-const handleAttachmentRemove = () => {
|
|
|
|
|
- form.value.attachmentUrl = '';
|
|
|
|
|
- form.value.attachmentName = '';
|
|
|
|
|
- ElMessage.success('已移除附件');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 下载附件(详情弹窗点击触发)
|
|
|
|
|
- */
|
|
|
|
|
-const handleDownloadAttachment = (url?: string) => {
|
|
|
|
|
- if (!url) {
|
|
|
|
|
- ElMessage.warning('该警示未上传附件');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- // MinIO public 桶,直接打开即可下载
|
|
|
|
|
- window.open(url, '_blank');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
const formatDateTime = (val: any): string => {
|
|
const formatDateTime = (val: any): string => {
|
|
|
if (!val) return '';
|
|
if (!val) return '';
|
|
|
const d = new Date(val);
|
|
const d = new Date(val);
|
|
@@ -594,7 +443,7 @@ onMounted(async () => {
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
- warning-page {
|
|
|
|
|
|
|
+.warning-page {
|
|
|
padding: 16px;
|
|
padding: 16px;
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
@@ -635,58 +484,4 @@ onMounted(async () => {
|
|
|
word-break: break-all;
|
|
word-break: break-all;
|
|
|
line-height: 1.6;
|
|
line-height: 1.6;
|
|
|
}
|
|
}
|
|
|
-.attachment-link {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- color: #409eff;
|
|
|
|
|
- text-decoration: none;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: color 0.2s;
|
|
|
|
|
-}
|
|
|
|
|
-.attachment-link:hover {
|
|
|
|
|
- color: #66b1ff;
|
|
|
|
|
- text-decoration: underline;
|
|
|
|
|
-}
|
|
|
|
|
-.attachment-link .el-icon {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-trigger-area {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-action-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- margin-bottom: 8px;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-placeholder {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #909399;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-tip-box {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- padding: 16px 0;
|
|
|
|
|
- border: 1px dashed #d9d9d9;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- background: #fafafa;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-tip-icon {
|
|
|
|
|
- color: #c0c4cc;
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- margin-bottom: 6px;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-tip-text {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-tip-desc {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: #909399;
|
|
|
|
|
- margin-top: 2px;
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|
|
|
|
|
+</style>
|