| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- <template>
- <div class="safety-platform-container">
- <header v-if="detailData" class="safety-platform-container__header">
- <div class="breadcrumb-title">{{ detailData.problem || '-' }}</div>
- <div class="detail-content">
- <span>创建人:{{ detailData.creatorName || '-' }}</span>
- <span>创建时间:{{ detailData.createdAt || '-' }}</span>
- </div>
- </header>
- <main class="safety-platform-container__main">
- <div v-if="detailData" class="audit-content">
- <el-alert
- v-if="detailData.reviewReason"
- type="error"
- :title="'不通过原因:' + detailData.reviewReason"
- show-icon
- class="detail-reject-alert"
- />
- <h4 class="section-title">
- <el-icon class="section-title__icon"><Document /></el-icon>
- <span>基本信息</span>
- </h4>
- <div class="detail-ct detail-ct--table">
- <div class="row">
- <div class="col">
- <div class="label">隐患问题:</div>
- <div class="value">{{ detailData.problem || '-' }}</div>
- </div>
- <div class="col">
- <div class="label">状态:</div>
- <div class="value">{{ detailData.statusName || '-' }}</div>
- </div>
- </div>
- <div class="row">
- <div class="col">
- <div class="label">下发数:</div>
- <div class="value">{{ detailData.issueCount ?? '-' }}</div>
- </div>
- <div class="col">
- <div class="label">反馈数:</div>
- <div class="value">{{ detailData.feedbackCount ?? '-' }}</div>
- </div>
- </div>
- <div class="row">
- <div class="col">
- <div class="label">计划开始日期:</div>
- <div class="value">{{ detailData.planStartDate || '-' }}</div>
- </div>
- <div class="col">
- <div class="label">计划完成日期:</div>
- <div class="value">{{ detailData.associationOtTimeLimit || '-' }}</div>
- </div>
- </div>
- </div>
- <h4 class="section-title">
- <el-icon class="section-title__icon"><Document /></el-icon>
- <span>举一反三内容</span>
- </h4>
- <div class="detail-ct requirement-block">
- <div class="value value-text">{{ detailData.associationOneThree || '-' }}</div>
- </div>
- <h4 class="section-title">
- <el-icon class="section-title__icon"><Document /></el-icon>
- <span>是否存在问题</span>
- </h4>
- <div class="detail-ct detail-ct--radio">
- <el-radio-group v-model="hasProblem" :disabled="isViewMode" class="has-problem-radio">
- <el-radio :label="false">否</el-radio>
- <el-radio :label="true">是</el-radio>
- </el-radio-group>
- </div>
- <h4 class="section-title" v-if="hasProblem">
- <el-icon class="section-title__icon"><Document /></el-icon>
- <span>材料上传</span>
- </h4>
- <div class="detail-ct detail-ct--table attachment-row" v-if="hasProblem">
- <div class="row" v-if="isViewMode">
- <div class="col">
- <div class="label">上传附件:</div>
- <div class="value value--attachment">
- <template v-if="attachmentFileList.length">
- <div
- class="file-container--div"
- v-for="item in attachmentFileList"
- :key="item.fileUrl || item.fileName"
- >
- <img
- class="file-container--div__icon"
- :src="FILE_TYPE_ICON[item.fileType]"
- @click="previewOnline(item.fileUrl, item.fileType)"
- />
- <span
- class="file-container--div__name"
- @click="previewOnline(item.fileUrl, item.fileType)"
- >
- {{ item.fileName }}
- </span>
- <img
- class="file-container--div__download"
- :src="DownloadIcon"
- @click="downloadFile(item.fileUrl, item.fileName)"
- />
- </div>
- </template>
- <span v-else class="empty-text">-</span>
- </div>
- </div>
- </div>
- <div class="row row--upload" v-if="!isViewMode">
- <div class="col col--full">
- <div class="label">选择附件:</div>
- <div class="value">
- <UploadFiles
- label="上传附件"
- v-if="!isViewMode"
- :file-list="materialAttachmentList"
- @uploadSuccess="handleMaterialUploadSuccess"
- class="custom-upload-files"
- />
- </div>
- </div>
- </div>
- </div>
- <h4 v-if="detailData?.issueRecords?.length" class="section-title">
- <el-icon class="section-title__icon"><Document /></el-icon>
- <span>我的任务 / 下发记录</span>
- </h4>
- <el-table v-if="detailData?.issueRecords?.length" :data="detailData.issueRecords" border size="small">
- <el-table-column prop="associationOtObligationDeptName" label="责任部门" width="120" />
- <el-table-column prop="associationOtObligationDeptUserName" label="责任人" width="100" />
- <el-table-column prop="feedbackResult" label="反馈结果" min-width="200" show-overflow-tooltip />
- <el-table-column prop="feedbackTime" label="反馈时间" width="160" />
- <el-table-column prop="statusName" label="状态" width="100" />
- <el-table-column label="操作" width="100" fixed="right">
- <template #default="{ row }">
- <el-button
- v-if="canFeedback(row)"
- type="primary"
- link
- size="small"
- @click="openFeedbackDialog(row)"
- >
- 反馈
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </main>
- <footer class="safety-platform-container__footer">
- <el-button @click="router.back()">返回</el-button>
- <el-button type="primary" @click="handleSubmit" v-if="!isViewMode">
- 提交
- </el-button>
- </footer>
- <!-- 部门反馈弹窗 -->
- <el-dialog v-model="showFeedbackDialog" title="部门反馈" width="560px" destroy-on-close @close="resetFeedbackForm">
- <el-form ref="feedbackFormRef" :model="feedbackForm" :rules="feedbackRules" label-width="100px">
- <el-form-item label="反馈结果" prop="feedbackResult">
- <el-input
- v-model="feedbackForm.feedbackResult"
- type="textarea"
- :rows="4"
- placeholder="请详细说明完成情况"
- />
- </el-form-item>
- <el-form-item label="反馈时间" prop="feedbackTime">
- <el-date-picker
- v-model="feedbackForm.feedbackTime"
- type="datetime"
- value-format="YYYY-MM-DD HH:mm:ss"
- placeholder="选填,默认当前时间"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="附件URL" prop="attachments">
- <el-input
- v-model="feedbackForm.attachments"
- type="textarea"
- :rows="2"
- placeholder="选填,多个用逗号分隔"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="showFeedbackDialog = false">取消</el-button>
- <el-button type="primary" @click="handleFeedbackSubmit">提交反馈</el-button>
- </template>
- </el-dialog>
- <PreviewOnline ref="previewOnlineRef" />
- </div>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, ref } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import { ElMessage } from 'element-plus';
- import type { FormInstance, FormRules } from 'element-plus';
- import { Document } from '@element-plus/icons-vue';
- import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
- import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
- import type { FileItem } from '@/components/UploadFiles/types';
- import { formatAttachmentList } from '@/components/UploadFiles/utils';
- import { getDrawLessonsAdminDeptDetail } from '@/api/drawLessons';
- import { submitDrawLessonsDeptFeedback, type DeptFeedbackRequest } from '@/api/drawLessons';
- import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
- import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
- import { downloadFile } from '@/views/disaster/utils';
- const router = useRouter();
- const route = useRoute();
- const currentId = computed(() => Number(route.query.id));
- const isViewMode = computed(() => route.query.operate === 'one-by-one-dept-view');
- /** 是否存在问题:否/是,为是时材料上传显示选择附件 */
- const hasProblem = ref(false);
- /** 材料上传中「选择附件」已选文件(仅当 是否存在问题=是 时使用,提交反馈时可带入) */
- const materialAttachmentList = ref<FileItem[]>([]);
- const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
- /** 详情数据(主记录 + issueRecords) */
- const detailData = ref<{
- problem?: string;
- creatorName?: string;
- createdAt?: string;
- associationOneThree?: string;
- associationOtTimeLimit?: string;
- planStartDate?: string;
- issueCount?: number;
- feedbackCount?: number;
- attachments?: string;
- attachmentList?: Array<{ fileName?: string; fileUrl?: string; fileNameOrUrl?: string; url?: string }>;
- statusName?: string;
- statusId?: number;
- /** 不通过原因(审核不通过时由后端返回) */
- reviewReason?: string;
- issueRecords?: Array<{
- id: number;
- associationOtObligationDeptName?: string;
- associationOtObligationDeptUserName?: string;
- feedbackResult?: string;
- feedbackTime?: string;
- statusName?: string;
- statusId?: number;
- }>;
- } | null>(null);
- /** 附件列表(从详情 attachments / attachmentList 解析,无数据时为空),样式与安全考核管理列表「考核文档」一致 */
- const attachmentFileList = computed(() => {
- const d = detailData.value;
- if (!d) return [];
- const normalize = (items: Array<{ fileUrl?: string; url?: string; fileName?: string; fileNameOrUrl?: string }>) => {
- return items.map((raw, index) => {
- const url = raw.fileUrl || raw.url || raw.fileNameOrUrl || '';
- const nameFromData = raw.fileName || raw.fileNameOrUrl;
- const fileName = nameFromData || (url ? url.split('/').pop() || `附件${index + 1}` : `附件${index + 1}`);
- const ext = fileName.split('.').pop()?.toLowerCase() || '';
- let fileType: 'pdf' | 'word' | 'excel' | 'ppt' = 'pdf';
- if (ext === 'doc' || ext === 'docx') fileType = 'word';
- else if (ext === 'xls' || ext === 'xlsx') fileType = 'excel';
- else if (ext === 'ppt' || ext === 'pptx') fileType = 'ppt';
- return {
- fileUrl: url,
- fileName,
- fileType,
- };
- });
- };
- if (Array.isArray(d.attachmentList) && d.attachmentList.length) {
- return normalize(d.attachmentList);
- }
- if (d.attachments) {
- try {
- const parsed = JSON.parse(d.attachments);
- if (Array.isArray(parsed) && parsed.length) {
- return normalize(parsed as Array<{ fileUrl?: string; url?: string; fileName?: string; fileNameOrUrl?: string }>);
- }
- } catch {
- const list = d.attachments
- .split(',')
- .map((s) => s.trim())
- .filter(Boolean)
- .map((s) => ({ fileNameOrUrl: s, fileUrl: s }));
- if (list.length) return normalize(list);
- }
- }
- return [];
- });
- const previewOnline = (url: string | undefined, type: string) => {
- if (url) previewOnlineRef.value?.open(url, type);
- };
- function handleMaterialUploadSuccess(fileList: FileItem[]) {
- materialAttachmentList.value = fileList;
- }
- /** 下发记录状态:3-待反馈 可反馈;4-待审核 已反馈不可再提交 */
- function canFeedback(row: { statusId?: number }) {
- return row.statusId === 3;
- }
- /** 底部提交:直接调用反馈接口(使用当前详情主记录 id) */
- async function handleSubmit() {
- const id = currentId.value;
- if (!id) return;
- try {
- let attachments = '';
- if (hasProblem.value && materialAttachmentList.value.length) {
- const formatted = await formatAttachmentList(materialAttachmentList.value);
- attachments = JSON.stringify(formatted);
- }
- const now = new Date();
- const feedbackTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
- await submitDrawLessonsDeptFeedback({
- id,
- feedbackHasIssue: hasProblem.value ? 1 : 0,
- feedbackResult: '',
- feedbackTime,
- attachments,
- });
- ElMessage.success('反馈提交成功');
- // getDetail();
- router.back();
- } catch (e) {
- console.error('反馈提交失败:', e);
- ElMessage.error(e?.message || e?.data || '反馈提交失败,请重试');
- }
- }
- const getDetail = async () => {
- if (!currentId.value) return;
- try {
- const res = await getDrawLessonsAdminDeptDetail(currentId.value);
- const data = (res as any)?.data ?? res;
- if (data && typeof data === 'object') {
- detailData.value = data;
- hasProblem.value = Number(data.feedbackHasIssue) === 1 ? true : false;
- materialAttachmentList.value = JSON.parse(data.attachments || '[]');
- }
- } catch (e) {
- console.error('获取举一反三详情失败:', e);
- ElMessage.error(e?.message || e?.data || '获取详情失败');
- }
- };
- // ---------- 部门反馈 ----------
- const showFeedbackDialog = ref(false);
- const feedbackFormRef = ref<FormInstance>();
- /** 当前操作的下发记录 ID(举一反三下发表 id) */
- const currentIssueId = ref<number>(0);
- const feedbackForm = ref<DeptFeedbackRequest>({
- id: 0,
- feedbackHasIssue: 0,
- feedbackResult: '',
- feedbackTime: '',
- attachments: '',
- });
- const feedbackRules: FormRules = {
- feedbackResult: [{ required: true, message: '请输入反馈结果', trigger: 'blur' }],
- };
- function resetFeedbackForm() {
- feedbackForm.value = {
- id: currentIssueId.value,
- feedbackHasIssue: hasProblem.value ? 1 : 0,
- feedbackResult: '',
- feedbackTime: '',
- attachments: '',
- };
- }
- async function openFeedbackDialog(row: { id: number }) {
- currentIssueId.value = row.id;
- resetFeedbackForm();
- // 若「是否存在问题」为是且已选择附件,带入反馈表单
- if (hasProblem.value && materialAttachmentList.value.length) {
- try {
- const formatted = await formatAttachmentList(materialAttachmentList.value);
- feedbackForm.value.attachments = JSON.stringify(formatted);
- } catch (e) {
- console.error('附件格式化失败:', e);
- }
- }
- showFeedbackDialog.value = true;
- }
- async function handleFeedbackSubmit() {
- await feedbackFormRef.value?.validate?.().catch(() => {});
- try {
- feedbackForm.value.id = currentIssueId.value;
- feedbackForm.value.feedbackHasIssue = hasProblem.value ? 1 : 0;
- // 若有「选择附件」且未在 openFeedbackDialog 中写入,这里再带一次
- if (hasProblem.value && materialAttachmentList.value.length && !feedbackForm.value.attachments) {
- const formatted = await formatAttachmentList(materialAttachmentList.value);
- feedbackForm.value.attachments = JSON.stringify(formatted);
- }
- await submitDrawLessonsDeptFeedback(feedbackForm.value);
- ElMessage.success('反馈提交成功');
- showFeedbackDialog.value = false;
- router.back();
- } catch (e) {
- console.error('反馈提交失败:', e);
- ElMessage.error(e?.message || e?.data || '反馈提交失败,请重试');
- }
- }
- onMounted(() => {
- getDetail();
- });
- </script>
- <style scoped lang="scss">
- @use '@/styles/page-details-layout.scss' as *;
- @use '@/styles/page-main-layout.scss' as *;
- @use '@/styles/basic-table-file.scss' as *;
- .detail-content {
- display: flex;
- gap: 30px;
- margin: 10px 0;
- font-size: 14px;
- }
- .detail-reject-alert {
- margin-bottom: 16px;
- }
- .audit-content {
- padding: 0 16px;
- .section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 20px 0 12px 0;
- font-size: 16px;
- font-weight: 600;
- color: #333;
- .section-title__icon {
- font-size: 18px;
- color: #333;
- }
- }
- .section-title:first-child {
- margin-top: 0;
- }
- .detail-ct {
- font-size: 14px;
- margin-bottom: 20px;
- &--table {
- border: 1px solid #dcdfe6;
- .row {
- display: flex;
- border-bottom: 1px solid #dcdfe6;
- &:last-child {
- border-bottom: none;
- }
- }
- .col {
- display: flex;
- flex: 1;
- min-height: 40px;
- align-items: stretch;
- &.col--wide {
- flex: 2;
- }
- &.col--full {
- flex: 1 1 100%;
- }
- .label {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- flex-shrink: 0;
- width: 140px;
- padding: 0 12px;
- background-color: #f5f5f5;
- border-right: 1px solid #dcdfe6;
- color: #333;
- }
- .value {
- flex: 1;
- display: flex;
- align-items: center;
- padding: 10px 20px;
- background-color: #fff;
- border-right: 1px solid #dcdfe6;
- color: #333;
- }
- }
- .row .col:last-child .value {
- border-right: none;
- }
- .row .col:nth-child(2) .label {
- border-left: 1px solid #dcdfe6;
- }
- }
- &.requirement-block {
- border: 1px solid #e0e0e0;
- background-color: #fff;
- padding: 16px 20px;
- min-height: 60px;
- .value-text {
- white-space: pre-wrap;
- word-break: break-word;
- color: #333;
- line-height: 1.5;
- }
- }
- &--radio {
- border: 1px solid #e0e0e0;
- background-color: #fff;
- padding: 16px 20px;
- .has-problem-radio {
- display: flex;
- gap: 24px;
- }
- }
- &.attachment-row {
- .value--attachment {
- flex-wrap: wrap;
- align-items: flex-start;
- .empty-text {
- color: #999;
- }
- }
- .row--upload .value {
- align-items: flex-start;
- }
- }
- }
- }
- .custom-upload-files :deep(.upload-button) {
- height: 34.49px;
- line-height: 34.49px;
- }
- </style>
|