You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
923 lines
27 KiB
923 lines
27 KiB
<template>
|
|
<div class="nos-detail-page" v-loading="loading">
|
|
<div class="nos-detail-header">
|
|
<el-page-header @back="onBack" />
|
|
</div>
|
|
|
|
<div class="detail-content">
|
|
<h2 class="page-title">{{ pageTitle }}</h2>
|
|
<div class="title-divider" />
|
|
|
|
<div class="case-code-bar">
|
|
<span class="case-code-label">案件编号</span>
|
|
<span class="case-code-value">{{ detailData?.taskNo || '--' }}</span>
|
|
</div>
|
|
|
|
<div class="detail-info-columns">
|
|
<div class="detail-info-left">
|
|
<!-- 通知/公告 -->
|
|
<div class="section">
|
|
<div class="section-title">通知/公告</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">通知/公告</span>
|
|
<div class="detail-value">
|
|
<template v-if="(detailData?.proclamation || []).length">
|
|
<div v-for="p in detailData?.proclamation || []" :key="p.fileName" class="detail-value--notice">
|
|
<span class="notice-file">
|
|
<el-icon class="notice-icon"><Paperclip /></el-icon>
|
|
<span class="notice-name" :title="p.fileName">{{ p.fileName }}</span>
|
|
</span>
|
|
<el-button v-if="p.filePath" size="small" @click="downloadFile(p.filePath, p.fileName)">下载</el-button>
|
|
</div>
|
|
</template>
|
|
<span v-else class="detail-empty">--</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">任务来源</span>
|
|
<span class="detail-value">{{ detailData?.taskSourceName || '--' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 检查对象 -->
|
|
<div class="section">
|
|
<div class="section-title">检查对象</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">{{detailData?.taskTypeId != 5 || detailData?.taskOriginalTypeId != 5 ? '执法单位' : '巡查单位'}}</span>
|
|
<span class="detail-value">{{ detailData?.lawEnforcementAgency || '--' }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">{{detailData?.taskTypeId != 5 || detailData?.taskOriginalTypeId != 5 ? '执法人员' : '参与人员'}}</span>
|
|
<div class="detail-value">
|
|
<template v-if="(detailData?.lawEnforcer || []).length">
|
|
<p v-for="(person, index) in detailData?.lawEnforcer || []" :key="index" class="detail-text">
|
|
{{ formatLawEnforcerName(person) }}
|
|
</p>
|
|
</template>
|
|
<span v-else class="detail-empty">--</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 执法信息 -->
|
|
<div class="detail-info-right">
|
|
<div class="section">
|
|
<div class="section-title">{{detailData?.taskTypeId != 5 || detailData?.taskOriginalTypeId != 5 ? '执法信息' : '巡查信息'}}</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">企业名称</span>
|
|
<div class="detail-value">
|
|
<p v-if="detailData?.enterpriseName || detailData?.usciCode" class="detail-text">
|
|
{{ formatEnterpriseName(detailData?.enterpriseName, detailData?.usciCode) }}
|
|
</p>
|
|
<span v-else class="detail-empty">--</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">类 别</span>
|
|
<span class="detail-value">{{ detailData?.taskTypeName || '--' }}</span>
|
|
</div>
|
|
<div class="detail-row" v-if="detailData?.taskTypeId == 5 || detailData?.taskTypeId == 1 || detailData?.taskOriginalTypeId == 5 || detailData?.taskOriginalTypeId == 1">
|
|
<span class="detail-label">{{detailData?.taskTypeId != 5 || detailData?.taskOriginalTypeId != 5 ? '执法事项' : '巡查事项'}}</span>
|
|
<span class="detail-value">{{ detailData?.lawEnforcementMatters || '--' }}</span>
|
|
</div>
|
|
<div v-if="requirementLabel" class="detail-row">
|
|
<span class="detail-label">{{ requirementLabel }}</span>
|
|
<div class="detail-value">
|
|
<ul v-if="(detailData?.taskItemList || []).length" class="requirement-list">
|
|
<li v-for="item in detailData?.taskItemList || []" :key="item.id || item.itemName" class="requirement-item">
|
|
<el-icon class="requirement-icon"><CircleCheck /></el-icon>
|
|
<span>{{ item.itemName }}</span>
|
|
</li>
|
|
</ul>
|
|
<span v-else class="detail-empty">--</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">{{detailData?.taskTypeId != 5 || detailData?.taskOriginalTypeId != 5 ? '执法依据' : '依 据'}}</span>
|
|
<div class="detail-value">
|
|
<template v-if="(detailData?.legalBasis || []).length">
|
|
<p v-for="(basis, index) in detailData?.legalBasis || []" :key="index" class="detail-text">
|
|
{{ basis }}
|
|
</p>
|
|
</template>
|
|
<span v-else class="detail-empty">--</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">开始时间</span>
|
|
<span class="detail-value">{{ formatDateTime(detailData?.createTime) || '--' }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">截止日期</span>
|
|
<span class="detail-value">{{ formatDateTime(detailData?.deadline) || '--' }}</span>
|
|
</div>
|
|
<!-- <div v-if="detailData?.taskStatusName" class="detail-row">
|
|
<span class="detail-label">任务状态</span>
|
|
<span class="detail-value">{{ detailData.taskStatusName }}</span>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<template v-if="isCompletedTask">
|
|
<el-divider />
|
|
<!-- 调查信息 -->
|
|
<div class="section">
|
|
<div class="section-title title-with-btn">
|
|
<span>调查信息</span>
|
|
<el-button size="small" @click="copyInvestigationInfo">复制</el-button>
|
|
</div>
|
|
<div class="investigation-content">
|
|
<p>
|
|
<template v-if="noticeFileName"><span class="text-nowrap">依据</span>“{{ noticeFileName }}”通知/公告,</template>{{ investigationParagraph1Body }}
|
|
</p>
|
|
<p>{{ investigationParagraph2 }}</p>
|
|
<p>
|
|
{{ investigationParagraph3 }}<br />
|
|
检查时间{{ formattedCheckTimeCn }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<el-divider />
|
|
<!-- 企业材料 -->
|
|
<div class="section">
|
|
<div class="section-title">企业材料</div>
|
|
<div class="materials-list">
|
|
<div v-if="!(detailData?.taskItemList || []).length" class="detail-empty">暂无材料</div>
|
|
|
|
<div v-for="item in detailData?.taskItemList || []" :key="item.id || item.itemName" class="material-block">
|
|
<div class="material-title">{{ item.itemName || '--' }}</div>
|
|
|
|
|
|
<div class="material-attachments">
|
|
<div v-if="!(item.itemValue || []).length" class="detail-empty">暂无附件</div>
|
|
|
|
<div v-for="typeGroup in groupByType(item.itemValue || [])" :key="typeGroup.type" class="attachment-group">
|
|
<template v-if="typeGroup.type === 'image'">
|
|
<div class="attachment-grid">
|
|
<el-image
|
|
v-for="src in typeGroup.files"
|
|
:key="src"
|
|
:src="buildFileUrl(src)"
|
|
:preview-src-list="typeGroup.files.map(buildFileUrl)"
|
|
fit="cover"
|
|
class="attachment-image"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else-if="typeGroup.type === 'video'">
|
|
<div class="video-grid">
|
|
<video v-for="src in typeGroup.files" :key="src" :src="buildFileUrl(src)" class="attachment-video" controls />
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div class="doc-file-list">
|
|
<div v-for="f in typeGroup.files" :key="f" class="doc-file-row">
|
|
<div class="doc-file-name" :title="extractFileName(f)">{{ extractFileName(f) }}</div>
|
|
<div class="doc-file-actions">
|
|
<el-link type="primary" :underline="false" @click="openFile(f)">查看</el-link>
|
|
<el-link type="primary" :underline="false" @click="downloadFile(f)">下载</el-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="material-status">
|
|
<span class="status-label">审核:</span>
|
|
<span class="status-value">{{ getItemAuditText(item.pass) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<el-divider />
|
|
|
|
<div class="section">
|
|
<div class="section-title">检查结果</div>
|
|
<div class="detail-row check-result-row">
|
|
<span class="detail-label">检查结果:</span>
|
|
<div class="detail-value">{{ getCheckResultText(detailData?.taskStatus) }}</div>
|
|
</div>
|
|
<div class="detail-row check-result-row">
|
|
<span class="detail-label">检查时间:</span>
|
|
<div class="detail-value">{{detailData?.taskStatus == 5 ? detailData?.deadline : detailData?.checkTime || '--' }}</div>
|
|
</div>
|
|
|
|
</div>
|
|
<el-divider />
|
|
<!-- 云笔录 -->
|
|
<div v-if="detailData?.digitalVoiceLoggingStatus !== 0" class="section">
|
|
<div class="section-title">云笔录</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">云视频:</span>
|
|
<div class="detail-value">
|
|
<template v-if="detailData?.cloudVideo">
|
|
<video :src="buildFileUrl(detailData.cloudVideo)" class="attachment-video" controls />
|
|
</template>
|
|
<span v-else class="detail-empty">暂无视频</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">云文字:</span>
|
|
<div class="detail-value">
|
|
<div class="detail-text">{{ detailData?.cloudText || '--' }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">意 见:</span>
|
|
<div class="detail-value">
|
|
<div class="detail-text">{{ detailData?.suggestion || '--' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 评价 -->
|
|
<div v-if="detailData?.isEvaluation === 1" class="section ">
|
|
<div class="section-title">评价</div>
|
|
<div class="evaluate-content">
|
|
<div class="emoji-row">
|
|
<div class="emoji-item" :class="{ active: detailData?.satisfaction === 0 }">
|
|
<img class="emoji-img" :src="detailData?.satisfaction === 0 ? veryGoodImg : veryGoodDisImg" alt="满意" />
|
|
<span class="emoji-label">满意</span>
|
|
</div>
|
|
<div class="emoji-item" :class="{ active: detailData?.satisfaction === 1 }">
|
|
<img class="emoji-img" :src="detailData?.satisfaction === 1 ? goodImg : goodDisImg" alt="一般" />
|
|
<span class="emoji-label">一般</span>
|
|
</div>
|
|
<div class="emoji-item" :class="{ active: detailData?.satisfaction === 2 }">
|
|
<img class="emoji-img" :src="detailData?.satisfaction === 2 ? badImg : badDisImg" alt="不满意" />
|
|
<span class="emoji-label">不满意</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="detailData?.satisfaction === 1 || detailData?.satisfaction === 2" class="detail-row reason-row">
|
|
<span class="detail-label-pj">原因:</span>
|
|
<div class="detail-value">
|
|
<div class="detail-text">{{ detailData?.commentReason || '--' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, getCurrentInstance, ref, watch } from 'vue';
|
|
import { CircleCheck, Paperclip } from '@element-plus/icons-vue';
|
|
import { ElMessage } from 'element-plus';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { getTaskDetail } from '@/api/nos/index';
|
|
import type { LawEnforcer, NosLawEnforcementTaskDetailVO } from '@/api/nos/type';
|
|
import { parseTime } from '@/utils/ruoyi';
|
|
|
|
import veryGoodImg from '@/assets/images/eval-verygood.png';
|
|
import veryGoodDisImg from '@/assets/images/eval-verygood-dis.png';
|
|
import goodImg from '@/assets/images/eval-good.png';
|
|
import goodDisImg from '@/assets/images/eval-good-dis.png';
|
|
import badImg from '@/assets/images/eval-bad.png';
|
|
import badDisImg from '@/assets/images/eval-bad-dis.png';
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const detailData = ref<NosLawEnforcementTaskDetailVO | null>(null);
|
|
const loading = ref(false);
|
|
|
|
const CATEGORY_REQUIREMENT_LABEL_MAP: Record<string, string> = {
|
|
行政检查: '检查要求',
|
|
材料备案: '备案文件',
|
|
材料验证: '验证文件',
|
|
巡查: '巡查要求'
|
|
};
|
|
|
|
const TASK_TYPE_TITLE_MAP: Record<string, string> = {
|
|
行政检查: '行政检查任务',
|
|
材料备案: '材料备案任务',
|
|
材料验证: '材料验证任务',
|
|
巡查: '巡查任务'
|
|
};
|
|
|
|
const taskId = computed(() => {
|
|
const id = route.params.id;
|
|
return Array.isArray(id) ? id[0] : id;
|
|
});
|
|
|
|
const pageTitle = computed(() => {
|
|
const typeName = detailData.value?.taskTypeName || '';
|
|
return TASK_TYPE_TITLE_MAP[typeName] || (typeName ? `${typeName}任务` : '非现场执法详情');
|
|
});
|
|
|
|
const requirementLabel = computed(() => {
|
|
const typeName = detailData.value?.taskTypeName || '';
|
|
return CATEGORY_REQUIREMENT_LABEL_MAP[typeName] || '';
|
|
});
|
|
|
|
const isCompletedTask = computed(() => detailData.value?.taskStatus === 4);
|
|
|
|
const noticeFileName = computed(() => detailData.value?.proclamation?.[0]?.fileName || '');
|
|
|
|
const enforcementPersonNames = computed(() => {
|
|
return detailData.value?.lawEnforcer?.map((person) => person.lawEnforcerName).filter(Boolean).join('、') || '';
|
|
});
|
|
|
|
const requirementItems = computed(() => {
|
|
return detailData.value?.taskItemList?.map((item) => item.itemName).filter(Boolean).join('、') || '';
|
|
});
|
|
|
|
const onBack = () => {
|
|
router.back();
|
|
};
|
|
|
|
const formatDateTime = (time?: string) => {
|
|
if (!time) return '';
|
|
return parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}')?.replace(/:\d{2}$/, '') || '';
|
|
};
|
|
|
|
const formatDateTimeCn = (time?: string) => {
|
|
if (!time) return '';
|
|
const parsed = parseTime(time, '{y}年{m}月{d}日{h}时{i}分{s}秒');
|
|
return parsed || time;
|
|
};
|
|
|
|
const formattedCreateTimeCn = computed(() => formatDateTimeCn(detailData.value?.createTime));
|
|
const formattedCheckTimeCn = computed(() => formatDateTimeCn(detailData.value?.checkTime));
|
|
|
|
const investigationParagraph1Body = computed(() => {
|
|
const detail = detailData.value;
|
|
if (!detail) return '';
|
|
|
|
const personType = detail.taskTypeId === 5 ? '巡查人员' : '执法人员';
|
|
const enforcementUnit = detail.lawEnforcementAgency || '深圳市执法局';
|
|
return `在${formattedCreateTimeCn.value}${enforcementUnit},${personType}${enforcementPersonNames.value},对${detail.enterpriseName || ''}进行了${detail.taskTypeName || ''}。`;
|
|
});
|
|
|
|
const investigationParagraph2 = computed(() => {
|
|
const detail = detailData.value;
|
|
if (!detail) return '';
|
|
|
|
let text = '';
|
|
if (detail.taskTypeId === 1 || detail.taskTypeId === 5) {
|
|
const matterType = detail.taskTypeId === 5 ? '巡查事项' : '执法事项';
|
|
text += `${matterType},${detail.lawEnforcementMatters || ''};`;
|
|
}
|
|
|
|
text += `${requirementLabel.value}有:${requirementItems.value}。`;
|
|
return text;
|
|
});
|
|
|
|
const investigationParagraph3 = computed(() => {
|
|
return `企业已提交材料,经核验,企业通过本次${detailData.value?.taskTypeName || ''}。`;
|
|
});
|
|
|
|
const investigationText = computed(() => {
|
|
return `${noticeFileName.value ? `依据“${noticeFileName.value}”通知/公告,` : ''}${investigationParagraph1Body.value}\n\n${investigationParagraph2.value}\n\n${investigationParagraph3.value}\n检查时间${formattedCheckTimeCn.value}`;
|
|
});
|
|
|
|
const copyInvestigationInfo = async () => {
|
|
try {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
await navigator.clipboard.writeText(investigationText.value);
|
|
ElMessage.success('复制成功');
|
|
return;
|
|
}
|
|
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = investigationText.value;
|
|
textArea.style.position = 'absolute';
|
|
textArea.style.left = '-999999px';
|
|
document.body.prepend(textArea);
|
|
textArea.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
ElMessage.success('复制成功');
|
|
} catch {
|
|
ElMessage.error('复制失败,请手动复制');
|
|
} finally {
|
|
textArea.remove();
|
|
}
|
|
} catch {
|
|
ElMessage.error('复制失败,请手动复制');
|
|
}
|
|
};
|
|
|
|
const formatLawEnforcerName = (person: LawEnforcer) => {
|
|
return `${person.lawEnforcerName || ''}${person.lawEnforcerPhone ? ` ${person.lawEnforcerPhone}` : ''}`.trim() || '--';
|
|
};
|
|
|
|
const formatEnterpriseName = (name?: string, usciCode?: string) => {
|
|
if (name && usciCode) return `${name}(${usciCode})`;
|
|
return name || usciCode || '--';
|
|
};
|
|
const STATUS_OPTIONS = [
|
|
{ text: "企业处理中", value: 1 },
|
|
{ text: "退回企业补正", value: 2 },
|
|
{ text: "待审核", value: 3 },
|
|
{ text: "已办结", value: 4 },
|
|
{ text: "已办结(超期)", value: 5 },
|
|
{ text: "转为待核实处置案件", value: 6 },
|
|
]
|
|
// 使用上面的类型
|
|
const getCheckResultText = (taskStatus?: number) => {
|
|
const map = STATUS_OPTIONS.find(item => item.value === taskStatus)?.text || '--';
|
|
return map || '--';
|
|
};
|
|
|
|
const getItemAuditText = (pass?: number) => {
|
|
if (pass === 1) return '通过';
|
|
if (pass === 0) return '不通过';
|
|
return '待审核';
|
|
};
|
|
|
|
const BASE_API = import.meta.env.VITE_APP_BASE_API || '';
|
|
const FILE_SERVER_PREFIX = import.meta.env.VITE_APP_FILE_SERVER_PREFIX || '';
|
|
|
|
const isOssId = (val: string) => /^[0-9]+$/.test(val.trim());
|
|
|
|
const toRelativeFilePath = (val: string) => {
|
|
const raw = val.trim();
|
|
if (!raw) return '';
|
|
const prefix = FILE_SERVER_PREFIX.replace(/\/+$/, '');
|
|
let path = raw.startsWith('/') ? raw : `/${raw}`;
|
|
const normalizedPrefix = prefix.startsWith('http') ? prefix : `/${prefix}`;
|
|
while (path === normalizedPrefix || path.startsWith(`${normalizedPrefix}/`)) {
|
|
path = path.slice(normalizedPrefix.length) || '/';
|
|
if (!path.startsWith('/')) path = `/${path}`;
|
|
}
|
|
return path;
|
|
};
|
|
|
|
const buildFileUrl = (val: string) => {
|
|
if (!val) return '';
|
|
const raw = val.trim();
|
|
if (raw.startsWith('http://') || raw.startsWith('https://') || raw.startsWith('blob:') || raw.startsWith('data:')) {
|
|
return raw;
|
|
}
|
|
if (isOssId(raw)) {
|
|
return `${BASE_API}/resource/oss/download/${raw}`;
|
|
}
|
|
const relative = toRelativeFilePath(raw);
|
|
return `${FILE_SERVER_PREFIX.replace(/\/+$/, '')}${relative}`;
|
|
};
|
|
|
|
const extractFileName = (val: string) => {
|
|
if (!val) return '';
|
|
const raw = val.trim();
|
|
if (isOssId(raw)) return raw;
|
|
try {
|
|
const withoutQuery = raw.split('?')[0].split('#')[0];
|
|
const parts = withoutQuery.split('/');
|
|
return parts[parts.length - 1] || raw;
|
|
} catch {
|
|
return raw;
|
|
}
|
|
};
|
|
|
|
type FileGroupType = 'image' | 'video' | 'doc';
|
|
type FileGroup = { type: FileGroupType; files: string[] };
|
|
|
|
const getFileType = (val: string): FileGroupType => {
|
|
const raw = val.trim().toLowerCase();
|
|
if (raw.startsWith('blob:')) return 'doc';
|
|
|
|
const ext = (() => {
|
|
if (isOssId(raw)) return '';
|
|
const clean = raw.split('?')[0].split('#')[0];
|
|
const dot = clean.lastIndexOf('.');
|
|
if (dot === -1) return '';
|
|
return clean.slice(dot + 1);
|
|
})();
|
|
|
|
const imageExt = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']);
|
|
const videoExt = new Set(['mp4', 'webm', 'ogg', 'avi', 'mov', 'mkv']);
|
|
|
|
if (imageExt.has(ext)) return 'image';
|
|
if (videoExt.has(ext)) return 'video';
|
|
return 'doc';
|
|
};
|
|
|
|
const groupByType = (files: string[]): FileGroup[] => {
|
|
const img: string[] = [];
|
|
const vid: string[] = [];
|
|
const doc: string[] = [];
|
|
files.forEach((f) => {
|
|
const t = getFileType(f);
|
|
if (t === 'image') img.push(f);
|
|
else if (t === 'video') vid.push(f);
|
|
else doc.push(f);
|
|
});
|
|
const out: FileGroup[] = [];
|
|
if (img.length) out.push({ type: 'image', files: img });
|
|
if (vid.length) out.push({ type: 'video', files: vid });
|
|
if (doc.length) out.push({ type: 'doc', files: doc });
|
|
return out;
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const { proxy } = getCurrentInstance() as any;
|
|
|
|
const openFile = (file: string) => {
|
|
if (!file) return;
|
|
const url = buildFileUrl(file);
|
|
if (!url) return;
|
|
window.open(url, '_blank');
|
|
};
|
|
|
|
const downloadFile = async (file: string, fileName?: string) => {
|
|
if (!file) return;
|
|
const raw = file.trim();
|
|
|
|
if (isOssId(raw) && proxy?.$download?.oss) {
|
|
proxy.$download.oss(raw);
|
|
return;
|
|
}
|
|
|
|
const url = buildFileUrl(raw);
|
|
if (!url) {
|
|
ElMessage.error('文件下载失败');
|
|
return;
|
|
}
|
|
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.target = '_blank';
|
|
if (fileName) link.download = fileName;
|
|
link.rel = 'noopener noreferrer';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
};
|
|
|
|
const fetchDetail = async () => {
|
|
if (!taskId.value) {
|
|
ElMessage.warning('缺少 taskId');
|
|
return;
|
|
}
|
|
loading.value = true;
|
|
detailData.value = null;
|
|
try {
|
|
const res = (await getTaskDetail(taskId.value as unknown as Number)) as any;
|
|
detailData.value = (res?.data ?? res) as NosLawEnforcementTaskDetailVO;
|
|
} catch (e) {
|
|
console.error(e);
|
|
ElMessage.error('获取详情失败');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
watch(
|
|
taskId,
|
|
() => {
|
|
fetchDetail();
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.nos-detail-page {
|
|
padding: 16px;
|
|
background: #f5f7fa;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.nos-detail-header {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.detail-content {
|
|
background: #fff;
|
|
padding: 20px 24px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.page-title {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
.title-divider {
|
|
margin: 12px 0 16px;
|
|
border-bottom: 1px solid #e4e7ed;
|
|
}
|
|
|
|
.case-code-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 8px 0 16px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.case-code-label {
|
|
flex: none;
|
|
width: 88px;
|
|
text-align: right;
|
|
color: #606266;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.case-code-value {
|
|
flex: 1;
|
|
color: #303133;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.detail-info-columns {
|
|
display: flex;
|
|
gap: 32px;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.detail-info-left,
|
|
.detail-info-right {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.detail-info-right {
|
|
border-left: 1px solid #ebeef5;
|
|
padding-left: 32px;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 8px;
|
|
padding-bottom: 4px;
|
|
}
|
|
|
|
.section-title {
|
|
padding: 14px 0 8px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
.title-with-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
/* justify-content: space-between; */
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #f5f5f5;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.detail-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.detail-label {
|
|
flex: none;
|
|
width: 88px;
|
|
text-align: right;
|
|
color: #606266;
|
|
font-weight: 500;
|
|
}
|
|
.detail-label-pj {
|
|
flex: none;
|
|
/* width: 88px; */
|
|
text-align: right;
|
|
color: #606266;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.detail-value {
|
|
flex: 1;
|
|
min-width: 0;
|
|
color: #303133;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.investigation-content {
|
|
padding: 12px;
|
|
background: #f8f9fa;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
color: #303133;
|
|
line-height: 1.6;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.investigation-content p {
|
|
margin: 0 0 12px 0;
|
|
}
|
|
|
|
.investigation-content p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.text-nowrap {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.detail-value--notice {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.detail-value--notice:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.notice-file {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.notice-icon {
|
|
flex: none;
|
|
color: #909399;
|
|
}
|
|
|
|
.notice-name {
|
|
color: #303133;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.detail-text {
|
|
margin: 0;
|
|
color: #303133;
|
|
line-height: 1.6;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.detail-empty {
|
|
color: #909399;
|
|
}
|
|
|
|
.requirement-list {
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.requirement-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
padding: 4px 0;
|
|
color: #303133;
|
|
}
|
|
|
|
.requirement-icon {
|
|
flex: none;
|
|
margin-top: 2px;
|
|
font-size: 16px;
|
|
color: #409eff;
|
|
}
|
|
|
|
.materials-list {
|
|
padding-top: 4px;
|
|
padding-left: 32px;
|
|
}
|
|
|
|
.material-block {
|
|
padding-bottom: 12px;
|
|
}
|
|
|
|
.material-title {
|
|
font-weight: 600;
|
|
font-size: 15px;
|
|
color: #303133;
|
|
}
|
|
|
|
.material-status {
|
|
margin-top: 6px;
|
|
color: #909399;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.material-attachments {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.attachment-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.attachment-image {
|
|
width: 120px;
|
|
height: 90px;
|
|
border-radius: 6px;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.video-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.attachment-video {
|
|
width: 220px;
|
|
height: 160px;
|
|
background: #000;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.doc-file-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.doc-file-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 8px 0;
|
|
border-bottom: 1px dashed #f0f0f0;
|
|
}
|
|
|
|
.doc-file-name {
|
|
flex: 1;
|
|
color: #303133;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.doc-file-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.evaluate-section {
|
|
border-top: 1px solid #ebeef5;
|
|
margin-top: 24px;
|
|
padding-top: 16px;
|
|
}
|
|
|
|
.evaluate-content {
|
|
padding-left: 32px;
|
|
}
|
|
|
|
.emoji-row {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
gap: 48px;
|
|
padding: 16px 0 24px;
|
|
}
|
|
|
|
.emoji-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
opacity: 0.6;
|
|
|
|
&.active {
|
|
opacity: 1;
|
|
transform: scale(1.1);
|
|
.emoji-label {
|
|
color: #fca000;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.emoji-img {
|
|
width: 48px;
|
|
height: 48px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.emoji-label {
|
|
font-size: 13px;
|
|
color: #909399;
|
|
}
|
|
}
|
|
|
|
.reason-row {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.check-result-row {
|
|
border-bottom: none;
|
|
padding: 4px 0;
|
|
}
|
|
</style>
|
|
|