Files
supervision-test-frontend-repo/src/views/dq/inspectTask/components/DqInspectTaskDetailModal.vue
T
wsm 294e683a47 !162 style(supervision): 反馈表格列名称居中
* style(supervision): 反馈表格列名称居中
* style(supervision): 详情弹窗去除标题并固定tab切换位置
* feat(xispeak): 反馈办理情况统计与脉搏一致
2026-08-19 07:34:37 +00:00

602 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<a-modal
v-model:open="visible"
:width="width"
:top="80"
:footer="null"
:maskClosable="true"
:bodyStyle="{ maxHeight: '70vh', overflowY: 'auto', padding: '4px 24px 20px' }"
wrapClassName="inspecttask-detail-modal"
destroyOnClose
>
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="item" tab="事项信息">
<div class="matter-detail">
<section class="matter-summary">
<div class="summary-heading">
<span class="summary-heading-icon">
<Icon :icon="summaryIcon" />
</span>
<div class="summary-heading-content">
<span class="summary-kicker">{{ summaryKicker }}</span>
<h3 class="summary-title">{{ detailSummary.title }}</h3>
</div>
</div>
<div class="summary-meta">
<div class="summary-meta-item">
<Icon icon="ant-design:appstore-outlined" />
<span class="summary-meta-label">问题分类</span>
<span class="summary-meta-value">{{ detailSummary.categoryName }}</span>
</div>
<div class="summary-meta-item">
<Icon icon="ant-design:lock-outlined" />
<span class="summary-meta-label">密级</span>
<span class="summary-meta-value">{{ detailSummary.secretLevel }}</span>
</div>
<div class="summary-meta-item">
<Icon icon="ant-design:clock-circle-outlined" />
<span class="summary-meta-label">完成时限</span>
<span class="summary-meta-value">{{ detailSummary.deadline }}</span>
</div>
<div class="summary-tag-group">
<a-tag class="summary-tag" :color="taskStatusTagColor">整改状态{{ detailSummary.taskStatus }}</a-tag>
<a-tag class="summary-tag">督办状态{{ detailSummary.bpmStatus }}</a-tag>
</div>
</div>
</section>
<section v-for="group in detailGroups" :key="group.title" class="detail-section">
<div class="detail-section-header">
<span class="detail-section-icon">
<Icon :icon="group.icon" />
</span>
<h3>{{ group.title }}</h3>
</div>
<div class="detail-grid">
<div v-for="field in group.fields" :key="field.label" class="detail-item" :class="{ 'detail-item-full': field.fullWidth }">
<span class="detail-label">{{ field.label }}</span>
<span class="detail-value" :class="{ 'detail-value-strong': field.bold }">{{ field.value }}</span>
</div>
</div>
</section>
</div>
</a-tab-pane>
<a-tab-pane key="feedback" tab="反馈信息">
<div class="feedback-summary-header">
<div class="feedback-summary-title">
<span class="feedback-summary-icon">
<Icon icon="ant-design:message-outlined" />
</span>
<span>办理情况</span>
<a-tag class="feedback-summary-tag"> {{ total }} </a-tag>
<a-tag class="feedback-summary-tag" color="success">已完成 {{ done }} </a-tag>
</div>
<div class="feedback-summary-desc">当前整改任务的工作进展反馈记录</div>
</div>
<a-table
class="feedback-table"
rowKey="id"
size="small"
bordered
:columns="feedbackColumns"
:data-source="feedbackList"
:pagination="false"
:scroll="{ x: 1200 }"
:loading="feedbackLoading"
:rowClassName="feedbackStatusRowClassName"
>
<template #bodyCell="{ column, record: fb }">
<template v-if="column.key === 'attachments'">
<SUploadFile
v-if="fb.id"
class="feedback-attachment-list"
:bussiness-id="fb.id"
:disabled="true"
:multiple="true"
/>
<span v-else>-</span>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, computed, defineExpose } from 'vue';
import Icon from '/@/components/Icon/index';
import SUploadFile from '/@/components/semri/fileComponent/SUploadFile.vue';
import { list } from '../../inspectProgress/DqInspectProgress.api';
import { feedbackStatusRowClassName } from '/@/views/bg/utils/feedbackStatusRender';
import type { BasicColumn } from '/@/components/Table';
const visible = ref(false);
const width = 1100;
const activeKey = ref('item');
const record = ref<Recordable>({});
const feedbackList = ref<Recordable[]>([]);
const feedbackLoading = ref(false);
function fmt(value: any): string {
return value === undefined || value === null || value === '' ? '-' : String(value);
}
function fmtDate(value: any): string {
const s = fmt(value);
return s.length > 10 ? s.slice(0, 10) : s;
}
function ynText(value: any): string {
const v = fmt(value);
if (v === '1') return '是';
if (v === '0') return '否';
return v;
}
function dictText(record: Recordable, key: string): string {
return fmt(record[`${key}_dictText`] || record[key]);
}
const summaryKicker = '整改任务';
const summaryIcon = 'ant-design:flag-outlined';
// 摘要区只承载最常用于快速判断事项状态的信息,详细字段放入下方分组,避免重复平铺。
const detailSummary = computed(() => {
const r = record.value || {};
return {
title: fmt(r.specificName),
categoryName: fmt(r.categoryName),
secretLevel: dictText(r, 'secretLevel'),
deadline: fmtDate(r.deadline),
taskStatus: fmt(r.taskStatus),
bpmStatus: dictText(r, 'bpmStatus'),
};
});
const taskStatusTagColor = computed(() => {
const text = detailSummary.value.taskStatus;
if (text.includes('已完成')) return 'success';
if (text.includes('整改中') || text.includes('进行中')) return 'processing';
if (text.includes('未开始')) return 'warning';
return undefined;
});
const detailGroups = computed(() => {
const r = record.value || {};
return [
{
title: '问题定位',
icon: 'ant-design:flag-outlined',
fields: [
{ label: '面上问题', value: fmt(r.surfaceName) },
{ label: '目标节点', value: fmt(r.targetNode) },
],
},
{
title: '整改要求',
icon: 'ant-design:edit-outlined',
fields: [
{ label: '整改措施', value: fmt(r.improveMeasure), fullWidth: true, bold: true },
{ label: '巡视建议', value: fmt(r.inspectAdvice) },
],
},
{
title: '责任分工',
icon: 'ant-design:team-outlined',
fields: [
{ label: '措施责任部门', value: dictText(r, 'measureResDept'), bold: true },
{ label: '措施责任负责人', value: dictText(r, 'measureResLeader'), bold: true },
{ label: '分管所领导', value: dictText(r, 'chargeLeaderId'), bold: true },
],
},
{
title: '成效与监督',
icon: 'ant-design:audit-outlined',
fields: [
{ label: '整改交付成果', value: fmt(r.rectificationEvidence) },
{ label: '成效评价标准', value: fmt(r.evaluationCriterion) },
{ label: '检查情况', value: fmt(r.inspectionStatus) },
{ label: '监督意见', value: fmt(r.supervisoryOpinion) },
],
},
{
title: '审批与进展',
icon: 'ant-design:safety-outlined',
fields: [
{ label: '需党群负责人审批', value: ynText(r.isNeedAppro) },
{ label: '党群负责人', value: dictText(r, 'supDeptleaderid') },
{ label: '督办次数', value: fmt(r.superviseCount) },
],
},
];
});
const feedbackColumns: BasicColumn[] = [
{ title: '反馈人', dataIndex: 'feedbackPersonName', align: 'center', width: 100 },
{ title: '反馈部门', dataIndex: 'feedbackDeptName', align: 'center', width: 140 },
{ title: '工作进展', dataIndex: 'workProgress', align: 'left', ellipsis: true },
{ title: '实际完成时间', dataIndex: 'actualTime', align: 'center', width: 120 },
{ title: '进展状态', dataIndex: 'bpmStatus_dictText', align: 'center', width: 100 },
{ title: '附件', dataIndex: 'id', key: 'attachments', align: 'left', width: 280 },
];
const total = computed(() => feedbackList.value.length);
const done = computed(() => feedbackList.value.filter((item) => item.bpmStatus_dictText === '已完成').length);
async function loadFeedback(mainId: string | number) {
feedbackLoading.value = true;
feedbackList.value = [];
try {
const res = await list({ mainId, pageNo: 1, pageSize: 9999, column: 'createTime', order: 'desc' });
feedbackList.value = Array.isArray(res) ? res : res?.records || [];
} finally {
feedbackLoading.value = false;
}
}
function open(currentRecord: Recordable) {
record.value = currentRecord || {};
activeKey.value = 'item';
visible.value = true;
if (currentRecord?.id !== undefined && currentRecord?.id !== null && currentRecord?.id !== '') {
loadFeedback(currentRecord.id);
}
}
defineExpose({ open });
</script>
<style lang="less" scoped>
// 弹窗内容会 Teleport 到 body,通过专属类限定公共组件的字号,避免影响其他页面。
:global(.inspecttask-detail-modal .ant-modal-title) {
font-size: 18px;
line-height: 26px;
}
:global(.inspecttask-detail-modal .ant-tabs-tab-btn) {
font-size: 15px;
line-height: 24px;
}
:global(.inspecttask-detail-modal .ant-table) {
font-size: 15px;
}
:global(.inspecttask-detail-modal .ant-table-thead > tr > th),
:global(.inspecttask-detail-modal .ant-table-tbody > tr > td) {
font-size: 15px;
line-height: 24px;
}
.matter-detail {
padding: 4px 2px 10px;
}
.matter-summary {
position: relative;
overflow: hidden;
margin-bottom: 16px;
padding: 20px 22px;
background: linear-gradient(135deg, #f2f8ff 0%, #f8fbff 48%, #ffffff 100%);
border: 1px solid #d6e8ff;
border-radius: 10px;
box-shadow: 0 4px 14px rgb(22 119 255 / 8%);
&::after {
position: absolute;
top: -46px;
right: -24px;
width: 132px;
height: 132px;
background: rgb(22 119 255 / 5%);
border-radius: 50%;
content: '';
pointer-events: none;
}
}
.summary-heading {
position: relative;
z-index: 1;
display: flex;
align-items: flex-start;
gap: 12px;
}
.summary-heading-icon {
display: inline-flex;
flex: 0 0 40px;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
color: #1677ff;
font-size: 20px;
background: #e6f4ff;
border-radius: 10px;
}
.summary-heading-content {
min-width: 0;
}
.summary-kicker {
display: block;
margin-bottom: 2px;
color: #64748b;
font-size: 14px;
line-height: 20px;
}
.summary-title {
margin: 0;
color: #172033;
font-size: 21px;
font-weight: 600;
line-height: 30px;
overflow-wrap: anywhere;
}
.summary-meta {
position: relative;
z-index: 1;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px 18px;
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid #deebfa;
}
.summary-meta-item {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 24px;
color: #64748b;
> :first-child {
color: #1677ff;
font-size: 15px;
}
}
.summary-meta-label {
font-size: 14px;
}
.summary-meta-value {
color: #1f2937;
font-size: 15px;
font-weight: 600;
}
.summary-tag-group {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
}
.summary-tag {
margin-inline-end: 0;
padding: 2px 10px;
font-size: 14px;
line-height: 22px;
white-space: normal;
}
.detail-section {
overflow: hidden;
margin-top: 14px;
background: #ffffff;
border: 1px solid #e7edf4;
border-radius: 8px;
}
.detail-section-header {
display: flex;
align-items: center;
gap: 8px;
min-height: 44px;
padding: 10px 16px;
background: #f8fbff;
border-bottom: 1px solid #e7edf4;
h3 {
margin: 0;
color: #27364b;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
}
.detail-section-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
color: #1677ff;
background: #e6f4ff;
border-radius: 6px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
background: #edf1f5;
}
.detail-item {
display: grid;
grid-template-columns: 142px minmax(0, 1fr);
align-items: start;
gap: 12px;
min-width: 0;
min-height: 54px;
padding: 14px 16px;
background: #ffffff;
}
.detail-item-full {
grid-column: 1 / -1;
}
.detail-label {
color: #7b8798;
font-size: 14px;
line-height: 26px;
}
.detail-value {
min-width: 0;
color: #27364b;
font-size: 15px;
line-height: 26px;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.detail-value-strong {
color: #172033;
font-weight: 600;
}
@media (max-width: 768px) {
.matter-summary {
padding: 16px;
}
.summary-meta {
align-items: flex-start;
}
.summary-tag-group {
flex-basis: 100%;
}
.detail-grid {
grid-template-columns: minmax(0, 1fr);
}
.detail-item-full {
grid-column: auto;
}
}
@media (max-width: 576px) {
.summary-title {
font-size: 19px;
line-height: 27px;
}
.summary-meta {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
}
.detail-item {
grid-template-columns: minmax(0, 1fr);
gap: 2px;
padding: 11px 14px;
}
}
.feedback-summary-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
padding: 10px 14px;
background: #f6fbff;
border: 1px solid #d6e8ff;
border-left: 4px solid #1677ff;
border-radius: 6px;
}
.feedback-summary-title {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
min-width: 0;
color: #1f2937;
font-size: 14px;
font-weight: 600;
line-height: 24px;
}
.feedback-summary-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-right: 8px;
color: #1677ff;
background: #e6f4ff;
border-radius: 50%;
}
.feedback-summary-tag {
margin-left: 10px;
}
.feedback-summary-desc {
color: #64748b;
font-size: 13px;
line-height: 22px;
white-space: nowrap;
}
.feedback-attachment-list {
min-width: 300px;
:deep(.file-list-wrapper) {
min-height: 0 !important;
padding: 4px 8px !important;
}
:deep(.ant-spin-nested-loading),
:deep(.ant-spin-container) {
min-height: 0 !important;
line-height: 20px;
}
:deep(.file-list-header) {
margin-bottom: 6px;
padding-bottom: 6px;
}
:deep(.file-list .file-item) {
padding: 8px;
margin-bottom: 6px;
}
:deep(.ant-empty) {
margin: 0 !important;
display: flex;
align-items: center;
min-height: 20px;
text-align: left;
}
:deep(.ant-empty-image) {
display: none !important;
}
:deep(.ant-empty-description) {
color: #94a3b8;
font-size: 12px;
line-height: 20px;
}
}
</style>