4 changed files with 469 additions and 52 deletions
@ -1,55 +1,405 @@ |
|||||||
<template> |
<template> |
||||||
<div class="app-container hazard-ledger"> |
<div class="app-container hazard-ledger"> |
||||||
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="filter-bar"> |
<data-list-panel modern-table> |
||||||
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" clearable placeholder="全部状态" class="filter-control"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> |
<template #filters> |
||||||
<el-form-item label="单位" prop="unitId"><el-select v-model="queryParams.unitId" clearable filterable placeholder="全部单位" class="filter-control"><el-option v-for="item in unitOptions" :key="item.id" :label="item.label" :value="item.id" /></el-select></el-form-item> |
<el-form |
||||||
<!-- 隐患名称筛选暂时隐藏,保留 queryParams 以便后续恢复。 --> |
ref="queryForm" |
||||||
<!-- <el-form-item label="隐患名称" prop="hazardName"><el-input v-model.trim="queryParams.hazardName" clearable placeholder="请输入隐患名称" class="name-control" @keyup.enter.native="handleQuery" /></el-form-item> --> |
:model="queryParams" |
||||||
<el-form-item><el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button><el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button></el-form-item> |
size="small" |
||||||
<div class="summary-inline"><span>当前筛选 <b>{{ total }}</b> 条</span><span>本页待审核 <b class="warning-text">{{ summary.pending }}</b></span><span>本页整改中 <b class="warning-text">{{ summary.rectifying }}</b></span><span>本页已闭环 <b class="success-text">{{ summary.closed }}</b></span><span>本页已驳回 <b class="danger-text">{{ summary.rejected }}</b></span></div> |
:inline="true" |
||||||
</el-form> |
class="data-list-filter-form" |
||||||
<el-card shadow="never" class="table-card"> |
> |
||||||
<el-table v-loading="loading" :data="ledgerList" stripe empty-text="暂无隐患台账数据" class="ledger-table"> |
<el-form-item label="状态" prop="status"> |
||||||
<el-table-column prop="reportNo" label="隐患编号" min-width="150" show-overflow-tooltip /><el-table-column prop="hazardName" label="隐患名称" min-width="190" show-overflow-tooltip /> |
<el-select v-model="queryParams.status" clearable placeholder="全部状态"> |
||||||
<el-table-column label="分级" width="100" align="center"><template slot-scope="scope"><el-tag :type="levelTagType(scope.row)" size="small">{{ levelLabel(scope.row) }}</el-tag></template></el-table-column> |
<el-option |
||||||
<el-table-column label="状态" width="110" align="center"><template slot-scope="scope"><el-tag :type="statusTagType(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag></template></el-table-column> |
v-for="item in statusOptions" |
||||||
<el-table-column label="积分发放" min-width="130" align="center"><template slot-scope="scope"><el-tag :type="pointsTagType(scope.row)" size="small">{{ pointsLabel(scope.row) }}</el-tag></template></el-table-column> |
:key="item.value" |
||||||
<el-table-column label="平台同步" min-width="120" align="center"><template slot-scope="scope"><el-tag :type="pushTagType(scope.row)" size="small">{{ pushLabel(scope.row) }}</el-tag></template></el-table-column> |
:label="item.label" |
||||||
<el-table-column prop="reporterName" label="上报人" width="100" show-overflow-tooltip /><el-table-column label="时间" width="145"><template slot-scope="scope">{{ formatTime(scope.row.createTime || scope.row.discoverTime) }}</template></el-table-column> |
:value="item.value" |
||||||
<el-table-column label="操作" width="210" fixed="right" align="center"><template slot-scope="scope"><el-button type="text" size="small" @click="openDetail(scope.row)">详情</el-button><el-button v-if="isPending(scope.row)" type="primary" size="mini" @click="goAudit(scope.row)">去审核</el-button><el-button v-if="isSyncFailed(scope.row)" type="text" size="small" class="retry-button" @click="handleRetrySync(scope.row)">重试同步</el-button></template></el-table-column> |
/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="单位" prop="unitId"> |
||||||
|
<el-select v-model="queryParams.unitId" clearable filterable placeholder="全部单位"> |
||||||
|
<el-option |
||||||
|
v-for="item in unitOptions" |
||||||
|
:key="item.id" |
||||||
|
:label="item.label" |
||||||
|
:value="item.id" |
||||||
|
/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<!-- 隐患名称筛选暂时隐藏,保留 queryParams 以便后续恢复。 --> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">查询</el-button> |
||||||
|
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template #actions> |
||||||
|
<div class="summary-inline"> |
||||||
|
<span>当前筛选 <b>{{ total }}</b> 条</span> |
||||||
|
<span>本页待提交 <b>{{ summary.draft }}</b></span> |
||||||
|
<span>本页审核中 <b class="primary-text">{{ summary.reviewing }}</b></span> |
||||||
|
<span>本页整改中 <b class="warning-text">{{ summary.rectifying }}</b></span> |
||||||
|
<span>本页已通过 <b class="success-text">{{ summary.approved }}</b></span> |
||||||
|
<span>本页已驳回 <b class="danger-text">{{ summary.rejected }}</b></span> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<el-table |
||||||
|
v-loading="loading" |
||||||
|
:data="ledgerList" |
||||||
|
border |
||||||
|
empty-text="暂无隐患台账数据" |
||||||
|
class="data-list-table ledger-table" |
||||||
|
> |
||||||
|
<el-table-column prop="reportNo" label="隐患编号" min-width="150" show-overflow-tooltip /> |
||||||
|
<el-table-column prop="hazardName" label="隐患名称" min-width="190" show-overflow-tooltip /> |
||||||
|
<el-table-column label="分级" width="100" align="center"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-tag :type="levelTagType(scope.row)" size="small">{{ levelLabel(scope.row) }}</el-tag> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="状态" width="110" align="center"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-tag :type="statusTagType(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="积分发放" min-width="130" align="center"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-tag :type="pointsTagType(scope.row)" size="small">{{ pointsLabel(scope.row) }}</el-tag> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<!-- 平台同步列暂时隐藏,后续需要时可直接恢复。 --> |
||||||
|
<!-- <el-table-column label="平台同步" min-width="120" align="center"> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-tag :type="pushTagType(scope.row)" size="small">{{ pushLabel(scope.row) }}</el-tag> |
||||||
|
</template> |
||||||
|
</el-table-column> --> |
||||||
|
<el-table-column prop="reporterName" label="上报人" width="100" show-overflow-tooltip /> |
||||||
|
<el-table-column label="时间" width="145"> |
||||||
|
<template slot-scope="scope">{{ formatTime(scope.row.createTime || scope.row.discoverTime) }}</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
label="操作" |
||||||
|
width="210" |
||||||
|
fixed="right" |
||||||
|
align="center" |
||||||
|
class-name="data-list-operation-column" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-button size="mini" @click="openDetail(scope.row)">详情</el-button> |
||||||
|
<el-button v-if="isReviewing(scope.row)" type="primary" size="mini" @click="goAudit(scope.row)">去审核</el-button> |
||||||
|
<el-button v-if="isSyncFailed(scope.row)" type="warning" size="mini" @click="handleRetrySync(scope.row)">重试同步</el-button> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
</el-table> |
</el-table> |
||||||
<div class="table-note">隐患状态与员工端一致:待审核 → 整改中 → 已闭环(审批通过即发积分)/ 已驳回。整改闭环由生产运营平台负责,不在本系统范围。</div> |
|
||||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> |
<template #footer> |
||||||
</el-card> |
<pagination |
||||||
|
v-show="total > 0" |
||||||
|
:total="total" |
||||||
|
:page.sync="queryParams.pageNum" |
||||||
|
:limit.sync="queryParams.pageSize" |
||||||
|
@pagination="getList" |
||||||
|
/> |
||||||
|
<div class="table-note"> |
||||||
|
隐患状态:待提交 → 审核中 → 整改中 → 已通过 / 已驳回。整改闭环由生产运营平台负责,不在本系统范围。 |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</data-list-panel> |
||||||
<el-dialog title="隐患详情" :visible.sync="detailVisible" width="980px" top="5vh" append-to-body> |
<el-dialog title="隐患详情" :visible.sync="detailVisible" width="980px" top="5vh" append-to-body> |
||||||
<div v-loading="detailLoading" class="detail-dialog"><template v-if="detail"><div class="detail-head"><div><span class="detail-no">{{ detail.reportNo || '暂无编号' }}</span><span class="detail-name">{{ detail.hazardName || '未命名隐患' }}</span></div><div class="detail-tags"><el-tag :type="levelTagType(detail)" size="small">{{ levelLabel(detail) }}</el-tag><el-tag :type="statusTagType(detail)" size="small">{{ statusLabel(detail) }}</el-tag></div></div><el-descriptions title="基础信息" :column="3" border size="small"><el-descriptions-item v-for="item in basicFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item></el-descriptions><el-descriptions title="整改与流程" :column="3" border size="small" class="detail-section"><el-descriptions-item v-for="item in flowFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item></el-descriptions><div class="text-section"><div class="section-title">描述与处理</div><div v-for="item in textFields" :key="item.label" class="text-row"><span>{{ item.label }}</span><p>{{ detail[item.key] || '—' }}</p></div></div><div class="attachment-section"><div class="section-title">附件({{ attachments.length }})</div><el-tag v-for="item in attachments" :key="item.id || item.filePath" size="small" effect="plain" class="attachment-tag">{{ item.fileName || '附件' }}</el-tag><span v-if="!attachments.length" class="muted">暂无附件</span></div></template><el-empty v-else-if="!detailLoading" description="暂无详情数据" /></div> |
<div v-loading="detailLoading" class="detail-dialog"> |
||||||
<span slot="footer"><el-button @click="detailVisible = false">关闭</el-button><el-button v-if="detail && isPending(detail)" type="primary" @click="goAudit(detail)">去审核</el-button></span> |
<template v-if="detail"> |
||||||
|
<div class="detail-head"> |
||||||
|
<div> |
||||||
|
<span class="detail-no">{{ detail.reportNo || '暂无编号' }}</span> |
||||||
|
<span class="detail-name">{{ detail.hazardName || '未命名隐患' }}</span> |
||||||
|
</div> |
||||||
|
<div class="detail-tags"> |
||||||
|
<el-tag :type="levelTagType(detail)" size="small">{{ levelLabel(detail) }}</el-tag> |
||||||
|
<el-tag :type="statusTagType(detail)" size="small">{{ statusLabel(detail) }}</el-tag> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<el-descriptions title="基础信息" :column="3" border size="small"> |
||||||
|
<el-descriptions-item v-for="item in basicFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item> |
||||||
|
</el-descriptions> |
||||||
|
<el-descriptions title="整改与流程" :column="3" border size="small" class="detail-section"> |
||||||
|
<el-descriptions-item v-for="item in flowFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item> |
||||||
|
</el-descriptions> |
||||||
|
<div class="text-section"> |
||||||
|
<div class="section-title">描述与处理</div> |
||||||
|
<div v-for="item in textFields" :key="item.label" class="text-row"> |
||||||
|
<span>{{ item.label }}</span> |
||||||
|
<p>{{ detail[item.key] || '—' }}</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="attachment-section"> |
||||||
|
<div class="section-title">附件({{ attachments.length }})</div> |
||||||
|
<div v-if="attachments.length" class="attachment-grid"> |
||||||
|
<el-image |
||||||
|
v-for="(item, index) in imageAttachments" |
||||||
|
:key="attachmentKey(item, index)" |
||||||
|
:src="attachmentUrl(item)" |
||||||
|
:preview-src-list="previewUrls" |
||||||
|
:initial-index="previewIndex(item)" |
||||||
|
fit="cover" |
||||||
|
class="attachment-image" |
||||||
|
> |
||||||
|
<div slot="error" class="attachment-placeholder"> |
||||||
|
<i class="el-icon-picture-outline" /> |
||||||
|
<span>{{ item.fileName || '图片加载失败' }}</span> |
||||||
|
</div> |
||||||
|
</el-image> |
||||||
|
<a |
||||||
|
v-for="(item, index) in otherAttachments" |
||||||
|
:key="attachmentKey(item, index)" |
||||||
|
:href="attachmentUrl(item) || undefined" |
||||||
|
:target="attachmentUrl(item) ? '_blank' : undefined" |
||||||
|
rel="noopener noreferrer" |
||||||
|
class="attachment-file" |
||||||
|
> |
||||||
|
<i class="el-icon-document" /> |
||||||
|
<span>{{ item.fileName || '附件' }}</span> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
<span v-else class="muted">暂无附件</span> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<el-empty v-else-if="!detailLoading" description="暂无详情数据" /> |
||||||
|
</div> |
||||||
|
<span slot="footer"> |
||||||
|
<el-button @click="detailVisible = false">关闭</el-button> |
||||||
|
<el-button v-if="detail && isReviewing(detail)" type="primary" @click="goAudit(detail)">去审核</el-button> |
||||||
|
</span> |
||||||
</el-dialog> |
</el-dialog> |
||||||
</div> |
</div> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script> |
<script> |
||||||
|
/* global MyConfig */ |
||||||
import { deptTreeSelect } from '@/api/system/user' |
import { deptTreeSelect } from '@/api/system/user' |
||||||
import { listHazardLedger, getHazardLedgerDetail, listHazardLedgerAttachments, retryHazardSync } from '@/api/hazard/ledger' |
import { listHazardLedger, getHazardLedgerDetail, listHazardLedgerAttachments, retryHazardSync } from '@/api/hazard/ledger' |
||||||
|
|
||||||
export default { |
export default { |
||||||
name: 'HazardLedger', |
name: 'HazardLedger', |
||||||
data() { return { loading: false, detailLoading: false, detailVisible: false, ledgerList: [], total: 0, unitOptions: [], attachments: [], detail: null, queryParams: { pageNum: 1, pageSize: 10, status: undefined, unitId: undefined, hazardName: undefined }, statusOptions: [{ label: '待审核', value: '1' }, { label: '整改中', value: '2' }, { label: '已闭环', value: '3' }, { label: '已驳回', value: '4' }], basicFields: [{ label: '单位', key: 'unitName' }, { label: '所属部门', key: 'departmentName', fallback: 'factoryDeptName' }, { label: '上报人', key: 'reporterName' }, { label: '隐患分类', key: 'hazardCategoryName', fallback: 'hazardCategory' }, { label: '隐患类型', key: 'hazardTypeName', fallback: 'hazardType' }, { label: '隐患来源', key: 'sourceTypeName', fallback: 'sourceType' }, { label: '专业', key: 'majorName', fallback: 'major' }, { label: '风险分析单元', key: 'riskUnit' }, { label: '风险分析对象', key: 'riskTarget' }, { label: '发现时间', key: 'discoverTime', time: true }, { label: '创建时间', key: 'createTime', time: true }, { label: '更新人', key: 'updateBy' }], flowFields: [{ label: '治理类型', key: 'governanceTypeName', fallback: 'governanceType' }, { label: '整改负责人', key: 'chargePersonName' }, { label: '限定完成时间', key: 'limitedCompleteTime', time: true }, { label: '实际完成时间', key: 'actualCompleteTime', time: true }, { label: '奖励积分', key: 'pointsAwarded', suffix: ' 分' }, { label: '积分状态', key: 'pointsStatusName', fallback: 'pointsStatus' }, { label: '平台同步', key: 'pushStatusName', fallback: 'pushStatus' }, { label: '同步时间', key: 'pushTime', time: true }, { label: '第三方记录', key: 'thirdPartyId' }, { label: '流程状态', key: 'flowStatus' }, { label: '审核人', key: 'auditUserNameName', fallback: 'auditUserName' }, { label: '审核时间', key: 'auditTime', time: true }], textFields: [{ label: '隐患描述', key: 'description' }, { label: '原因分析', key: 'causeAnalysis' }, { label: '整改方案', key: 'rectificationPlan' }, { label: '防范措施', key: 'preventiveMeasures' }, { label: '审核意见', key: 'auditRemark' }, { label: '备注', key: 'remark' }] } }, |
data() { return { loading: false, detailLoading: false, detailVisible: false, ledgerList: [], total: 0, unitOptions: [], attachments: [], detail: null, queryParams: { pageNum: 1, pageSize: 10, status: undefined, unitId: undefined, hazardName: undefined }, statusOptions: [{ label: '待提交', value: 'draft' }, { label: '审核中', value: 'reviewing' }, { label: '整改中', value: 'rectifying' }, { label: '已通过', value: 'approved' }, { label: '已驳回', value: 'rejected' }], basicFields: [{ label: '单位', key: 'unitName' }, { label: '所属部门', key: 'departmentName', fallback: 'factoryDeptName' }, { label: '上报人', key: 'reporterName' }, { label: '隐患分类', key: 'hazardCategoryName', fallback: 'hazardCategory' }, { label: '隐患类型', key: 'hazardTypeName', fallback: 'hazardType' }, { label: '隐患来源', key: 'sourceTypeName', fallback: 'sourceType' }, { label: '专业', key: 'majorName', fallback: 'major' }, { label: '风险分析单元', key: 'riskUnit' }, { label: '风险分析对象', key: 'riskTarget' }, { label: '发现时间', key: 'discoverTime', time: true }, { label: '创建时间', key: 'createTime', time: true }, { label: '更新人', key: 'updateBy' }], flowFields: [{ label: '治理类型', key: 'governanceTypeName', fallback: 'governanceType' }, { label: '整改负责人', key: 'chargePersonName' }, { label: '限定完成时间', key: 'limitedCompleteTime', time: true }, { label: '实际完成时间', key: 'actualCompleteTime', time: true }, { label: '奖励积分', key: 'pointsAwarded', suffix: ' 分' }, { label: '积分状态', key: 'pointsStatusName', fallback: 'pointsStatus' }, { label: '平台同步', key: 'pushStatusName', fallback: 'pushStatus' }, { label: '同步时间', key: 'pushTime', time: true }, { label: '第三方记录', key: 'thirdPartyId' }, { label: '流程状态', key: 'flowStatus' }, { label: '审核人', key: 'auditUserNameName', fallback: 'auditUserName' }, { label: '审核时间', key: 'auditTime', time: true }], textFields: [{ label: '隐患描述', key: 'description' }, { label: '原因分析', key: 'causeAnalysis' }, { label: '整改方案', key: 'rectificationPlan' }, { label: '防范措施', key: 'preventiveMeasures' }, { label: '审核意见', key: 'auditRemark' }, { label: '备注', key: 'remark' }] } }, |
||||||
computed: { summary() { return this.ledgerList.reduce((result, row) => { const status = this.statusKey(row); if (status === '1') result.pending++; else if (status === '2') result.rectifying++; else if (status === '3') result.closed++; else if (status === '4') result.rejected++; return result }, { pending: 0, rectifying: 0, closed: 0, rejected: 0 }) } }, |
computed: { |
||||||
|
summary() { return this.ledgerList.reduce((result, row) => { const status = this.statusKey(row); if (status === 'draft') result.draft++; else if (status === 'reviewing') result.reviewing++; else if (status === 'rectifying') result.rectifying++; else if (status === 'approved') result.approved++; else if (status === 'rejected') result.rejected++; return result }, { draft: 0, reviewing: 0, rectifying: 0, approved: 0, rejected: 0 }) }, |
||||||
|
imageAttachments() { return this.attachments.filter(item => this.isImageAttachment(item)) }, |
||||||
|
otherAttachments() { return this.attachments.filter(item => !this.isImageAttachment(item)) }, |
||||||
|
previewUrls() { return this.imageAttachments.map(item => this.attachmentUrl(item)).filter(Boolean) } |
||||||
|
}, |
||||||
created() { this.getList(); this.getUnits() }, |
created() { this.getList(); this.getUnits() }, |
||||||
methods: { |
methods: { |
||||||
getList() { this.loading = true; listHazardLedger(this.queryParams).then(response => { const page = response.data || response; this.ledgerList = page.list || page.rows || []; this.total = Number(page.total || page.totalCount || this.ledgerList.length) }).catch(() => { this.ledgerList = []; this.total = 0; this.$modal.msgError('隐患台账加载失败,请检查后端服务和登录状态') }).finally(() => { this.loading = false }) }, |
getList() { this.loading = true; listHazardLedger(this.queryParams).then(response => { const page = response.data || response; this.ledgerList = page.list || page.rows || []; this.total = Number(page.total || page.totalCount || this.ledgerList.length) }).catch(() => { this.ledgerList = []; this.total = 0; this.$modal.msgError('隐患台账加载失败,请检查后端服务和登录状态') }).finally(() => { this.loading = false }) }, |
||||||
getUnits() { deptTreeSelect().then(response => { this.unitOptions = this.flattenTree(response.data || []) }).catch(() => { this.unitOptions = [] }) }, flattenTree(nodes, result = []) { nodes.forEach(node => { result.push({ id: node.id, label: node.label || node.deptName }); if (node.children) this.flattenTree(node.children, result) }); return result }, |
getUnits() { deptTreeSelect().then(response => { this.unitOptions = this.flattenTree(response.data || []) }).catch(() => { this.unitOptions = [] }) }, flattenTree(nodes, result = []) { nodes.forEach(node => { result.push({ id: node.id, label: node.label || node.deptName }); if (node.children) this.flattenTree(node.children, result) }); return result }, |
||||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() }, resetQuery() { this.$refs.queryForm.resetFields(); this.queryParams.pageNum = 1; this.getList() }, |
handleQuery() { this.queryParams.pageNum = 1; this.getList() }, resetQuery() { this.$refs.queryForm.resetFields(); this.queryParams.pageNum = 1; this.getList() }, |
||||||
openDetail(row) { this.detailVisible = true; this.detail = null; this.attachments = []; this.detailLoading = true; Promise.all([getHazardLedgerDetail(row.id), listHazardLedgerAttachments(row.id)]).then(results => { this.detail = results[0].data || results[0]; const data = results[1].data || results[1]; this.attachments = Array.isArray(data) ? data : [] }).catch(() => { this.$modal.msgError('隐患详情加载失败') }).finally(() => { this.detailLoading = false }) }, |
openDetail(row) { this.detailVisible = true; this.detail = null; this.attachments = []; this.detailLoading = true; Promise.all([getHazardLedgerDetail(row.id), listHazardLedgerAttachments(row.id)]).then(results => { this.detail = results[0].data || results[0]; const data = results[1].data || results[1]; this.attachments = Array.isArray(data) ? data : [] }).catch(() => { this.$modal.msgError('隐患详情加载失败') }).finally(() => { this.detailLoading = false }) }, |
||||||
goAudit(row) { const taskId = row.taskId || row.taskID || row.todoTaskId; if (!taskId) { this.$modal.msgWarning('当前记录缺少任务 ID,无法进入审核'); return } this.detailVisible = false; this.$router.push({ path: '/hazard/audit/detail', query: { id: String(row.id), taskId: String(taskId) } }) }, |
goAudit(row) { const taskId = row.taskId || row.taskID || row.todoTaskId; if (!taskId) { this.$modal.msgWarning('当前记录缺少任务 ID,无法进入审核'); return } this.detailVisible = false; this.$router.push({ path: '/hazard/audit/detail', query: { id: String(row.id), taskId: String(taskId) }}) }, |
||||||
handleRetrySync(row) { this.$confirm('确认重试将该隐患同步到生产运营平台吗?', '重试同步', { type: 'warning' }).then(() => retryHazardSync(row.id)).then(() => { this.$modal.msgSuccess('同步重试成功'); this.getList() }).catch(error => { if (error !== 'cancel') this.$modal.msgError(error.message || '同步重试失败') }) }, |
handleRetrySync(row) { this.$confirm('确认重试将该隐患同步到生产运营平台吗?', '重试同步', { type: 'warning' }).then(() => retryHazardSync(row.id)).then(() => { this.$modal.msgSuccess('同步重试成功'); this.getList() }).catch(error => { if (error !== 'cancel') this.$modal.msgError(error.message || '同步重试失败') }) }, |
||||||
statusKey(row) { const value = String(row.status || ''); if (/待审核/.test(row.statusName || '') || value === '1') return '1'; if (/整改中/.test(row.statusName || '') || value === '2') return '2'; if (/闭环|已通过/.test(row.statusName || '') || value === '3') return '3'; if (/驳回/.test(row.statusName || '') || value === '4') return '4'; return value }, isPending(row) { return this.statusKey(row) === '1' }, isSyncFailed(row) { return /失败|异常/.test(row.pushStatusName || row.pushStatus || '') }, levelLabel(row) { return row.hazardLevelName || ({ general: '一般', major: '较大', critical: '重大' }[row.hazardLevel] || row.hazardLevel || '—') }, statusLabel(row) { return row.statusName || ({ 0: '待提交', 1: '待审核', 2: '整改中', 3: '已闭环', 4: '已驳回' }[String(row.status)] || row.status || '—') }, pointsLabel(row) { if (row.pointsStatusName) return row.pointsAwarded === undefined || row.pointsAwarded === null ? row.pointsStatusName : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} ${row.pointsStatusName}`; return row.pointsAwarded === undefined || row.pointsAwarded === null ? '—' : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} 分` }, pushLabel(row) { return row.pushStatusName || ({ SUCCESS: '已同步', FAIL: '同步失败', FAILED: '同步失败', WAIT: '待同步' }[String(row.pushStatus).toUpperCase()] || row.pushStatus || '—') }, statusTagType(row) { const value = this.statusKey(row); return value === '3' ? 'success' : value === '4' ? 'danger' : value === '1' || value === '2' ? 'warning' : 'info' }, levelTagType(row) { return String(row.hazardLevel || '').toLowerCase() === 'critical' ? 'danger' : String(row.hazardLevel || '').toLowerCase() === 'major' ? 'warning' : 'info' }, pointsTagType(row) { return row.pointsStatusName && /待|未/.test(row.pointsStatusName) ? 'info' : row.pointsAwarded === undefined || row.pointsAwarded === null ? 'info' : 'success' }, pushTagType(row) { return this.isSyncFailed(row) ? 'danger' : /待/.test(this.pushLabel(row)) ? 'info' : 'success' }, |
statusKey(row) { const value = String(row.status || '').toLowerCase(); const name = row.statusName || ''; if (/待提交|未提交/.test(name) || value === 'draft' || value === '0') return 'draft'; if (/审核中|待审核/.test(name) || value === 'reviewing' || value === '1') return 'reviewing'; if (/整改中/.test(name) || value === 'rectifying' || value === '2') return 'rectifying'; if (/已通过|已闭环/.test(name) || value === 'approved' || value === '3') return 'approved'; if (/已驳回|驳回/.test(name) || value === 'rejected' || value === '4') return 'rejected'; return value }, isReviewing(row) { return this.statusKey(row) === 'reviewing' }, isSyncFailed(row) { return /失败|异常/.test(row.pushStatusName || row.pushStatus || '') }, levelLabel(row) { return row.hazardLevelName || ({ general: '一般', major: '较大', critical: '重大' }[row.hazardLevel] || row.hazardLevel || '—') }, statusLabel(row) { const key = this.statusKey(row); return ({ draft: '待提交', reviewing: '审核中', rectifying: '整改中', approved: '已通过', rejected: '已驳回' }[key] || row.statusName || row.status || '—') }, pointsLabel(row) { if (row.pointsStatusName) return row.pointsAwarded === undefined || row.pointsAwarded === null ? row.pointsStatusName : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} ${row.pointsStatusName}`; return row.pointsAwarded === undefined || row.pointsAwarded === null ? '—' : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} 分` }, pushLabel(row) { return row.pushStatusName || ({ SUCCESS: '已同步', FAIL: '同步失败', FAILED: '同步失败', WAIT: '待同步' }[String(row.pushStatus).toUpperCase()] || row.pushStatus || '—') }, statusTagType(row) { const value = this.statusKey(row); return value === 'approved' ? 'success' : value === 'rejected' ? 'danger' : value === 'rectifying' ? 'warning' : value === 'reviewing' ? 'primary' : 'info' }, levelTagType(row) { return String(row.hazardLevel || '').toLowerCase() === 'critical' ? 'danger' : String(row.hazardLevel || '').toLowerCase() === 'major' ? 'warning' : 'info' }, pointsTagType(row) { return row.pointsStatusName && /待|未/.test(row.pointsStatusName) ? 'info' : row.pointsAwarded === undefined || row.pointsAwarded === null ? 'info' : 'success' }, pushTagType(row) { return this.isSyncFailed(row) ? 'danger' : /待/.test(this.pushLabel(row)) ? 'info' : 'success' }, |
||||||
|
isImageAttachment(item) { return String(item.attachmentType || '').toUpperCase() === 'IMAGE' || /^image\//i.test(item.fileType || '') || /\.(png|jpe?g|gif|webp|bmp|svg)(?:\?.*)?$/i.test(item.fileName || item.filePath || item.fileUrl || '') }, |
||||||
|
attachmentUrl(item) { const path = item.fileUrl || item.filePath || ''; if (!path || /^https?:\/\//i.test(path) || /^data:image\//i.test(path)) return path; return MyConfig.BASE_API.replace(/\/$/, '') + '/' + path.replace(/^\//, '') }, |
||||||
|
previewIndex(item) { return this.previewUrls.indexOf(this.attachmentUrl(item)) }, |
||||||
|
attachmentKey(item, index) { return item.id || item.fileUrl || item.filePath || `attachment-${index}` }, |
||||||
displayField(row, item) { const value = row[item.key] || (item.fallback && row[item.fallback]); if (value === undefined || value === null || value === '') return '—'; return (item.time ? this.formatTime(value) : String(value)) + (item.suffix || '') }, formatTime(value) { return value ? String(value).replace('T', ' ').slice(0, 19) : '—' } |
displayField(row, item) { const value = row[item.key] || (item.fallback && row[item.fallback]); if (value === undefined || value === null || value === '') return '—'; return (item.time ? this.formatTime(value) : String(value)) + (item.suffix || '') }, formatTime(value) { return value ? String(value).replace('T', ' ').slice(0, 19) : '—' } |
||||||
} |
} |
||||||
} |
} |
||||||
</script> |
</script> |
||||||
|
|
||||||
<style lang="scss" scoped> |
<style lang="scss" scoped> |
||||||
.hazard-ledger { min-height: calc(100vh - 52px); background: #f0f2f5; }.filter-bar { display: flex; align-items: center; flex-wrap: wrap; margin-bottom: 14px; padding: 14px 16px 0; background: #fff; border: 1px solid #f0f0f2; border-radius: 8px; }.filter-control { width: 170px; }.name-control { width: 210px; }.summary-inline { display: flex; align-items: center; gap: 20px; margin: 0 0 14px auto; padding: 0 6px; color: #9898ae; font-size: 12px; white-space: nowrap; }.summary-inline b { margin-left: 3px; color: #1a1a2e; }.warning-text { color: #c97e24 !important; }.success-text { color: #2d8a4e !important; }.danger-text { color: #d64550 !important; }.table-card { border: 1px solid #f0f0f2; border-radius: 8px; }.table-note { margin-top: 12px; color: #9898ae; font-size: 11px; line-height: 1.5; }.retry-button { color: #c97e24; }.detail-dialog { min-height: 250px; max-height: 68vh; overflow-y: auto; padding: 2px 2px 8px; }.detail-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }.detail-no { margin-right: 14px; color: #9898ae; font-size: 12px; }.detail-name { color: #1a1a2e; font-size: 16px; font-weight: 600; }.detail-tags { display: flex; gap: 8px; }.detail-section, .text-section, .attachment-section { margin-top: 16px; }.section-title { margin-bottom: 8px; color: #5a5a72; font-size: 13px; font-weight: 600; }.text-row { display: grid; grid-template-columns: 90px 1fr; gap: 10px; padding: 7px 0; border-bottom: 1px solid #f0f0f2; font-size: 12px; }.text-row > span { color: #9898ae; }.text-row p { margin: 0; color: #1a1a2e; white-space: pre-wrap; word-break: break-word; }.attachment-tag { margin: 0 8px 8px 0; }.muted { color: #9898ae; font-size: 12px; }@media (max-width: 800px) { .summary-inline { width: 100%; justify-content: flex-end; flex-wrap: wrap; gap: 8px 16px; margin: 0 0 10px; white-space: normal; }.filter-control, .name-control { width: 100%; }.filter-bar .el-form-item { width: 100%; margin-right: 0; } } |
.hazard-ledger { |
||||||
|
min-height: calc(100vh - 52px); |
||||||
|
background: #f0f2f5; |
||||||
|
} |
||||||
|
|
||||||
|
.summary-inline { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 20px; |
||||||
|
color: var(--prototype-text-secondary, #6b7280); |
||||||
|
font-size: 12px; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.summary-inline b { |
||||||
|
margin-left: 3px; |
||||||
|
color: var(--prototype-text, #1a1a2e); |
||||||
|
} |
||||||
|
|
||||||
|
.warning-text { |
||||||
|
color: #c97e24 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.primary-text { |
||||||
|
color: #409eff !important; |
||||||
|
} |
||||||
|
|
||||||
|
.success-text { |
||||||
|
color: #2d8a4e !important; |
||||||
|
} |
||||||
|
|
||||||
|
.danger-text { |
||||||
|
color: #d64550 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.table-note { |
||||||
|
margin-top: 10px; |
||||||
|
padding: 9px 12px; |
||||||
|
color: var(--prototype-text-secondary, #6b7280); |
||||||
|
font-size: 12px; |
||||||
|
line-height: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-dialog { |
||||||
|
min-height: 250px; |
||||||
|
max-height: 68vh; |
||||||
|
overflow-y: auto; |
||||||
|
padding: 2px 2px 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-head { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
margin-bottom: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-no { |
||||||
|
margin-right: 14px; |
||||||
|
color: #9898ae; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-name { |
||||||
|
color: #1a1a2e; |
||||||
|
font-size: 16px; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-tags { |
||||||
|
display: flex; |
||||||
|
gap: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.detail-section, |
||||||
|
.text-section, |
||||||
|
.attachment-section { |
||||||
|
margin-top: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.section-title { |
||||||
|
margin-bottom: 8px; |
||||||
|
color: #5a5a72; |
||||||
|
font-size: 13px; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
|
||||||
|
.text-row { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 90px 1fr; |
||||||
|
gap: 10px; |
||||||
|
padding: 7px 0; |
||||||
|
border-bottom: 1px solid #f0f0f2; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.text-row > span { |
||||||
|
color: #9898ae; |
||||||
|
} |
||||||
|
|
||||||
|
.text-row p { |
||||||
|
margin: 0; |
||||||
|
color: #1a1a2e; |
||||||
|
white-space: pre-wrap; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr)); |
||||||
|
gap: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-image, |
||||||
|
.attachment-file { |
||||||
|
width: 100%; |
||||||
|
height: 150px; |
||||||
|
overflow: hidden; |
||||||
|
border: 1px solid #e4e7ed; |
||||||
|
border-radius: 6px; |
||||||
|
background: #f7f9fc; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-image { |
||||||
|
cursor: zoom-in; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-file, |
||||||
|
.attachment-placeholder { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 8px; |
||||||
|
padding: 12px; |
||||||
|
color: #909399; |
||||||
|
text-align: center; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-file { |
||||||
|
font-size: 13px; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-file:hover { |
||||||
|
border-color: #409eff; |
||||||
|
color: #409eff; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-file i, |
||||||
|
.attachment-placeholder i { |
||||||
|
font-size: 28px; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-file span, |
||||||
|
.attachment-placeholder span { |
||||||
|
max-width: 100%; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-placeholder { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.muted { |
||||||
|
color: #9898ae; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 800px) { |
||||||
|
.summary-inline { |
||||||
|
width: 100%; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 8px 16px; |
||||||
|
white-space: normal; |
||||||
|
} |
||||||
|
|
||||||
|
.attachment-grid { |
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 520px) { |
||||||
|
.attachment-grid { |
||||||
|
grid-template-columns: 1fr; |
||||||
|
} |
||||||
|
} |
||||||
</style> |
</style> |
||||||
|
|||||||
Loading…
Reference in new issue