| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- <!-- 应急预案审批 -->
- <template>
- <div class="app-container">
- <!-- 搜索区 -->
- <el-form :model="queryParams" :inline="true" class="search-form">
- <el-form-item label="预案名称">
- <el-input v-model="queryParams.planName" placeholder="请输入预案名称" clearable style="width: 220px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleSearch">搜索</el-button>
- <el-button @click="handleReset">重置</el-button>
- </el-form-item>
- </el-form>
- <!-- 数据表格 -->
- <el-table :data="pageList" border stripe style="width: 100%">
- <el-table-column label="序号" type="index" width="60" align="center" />
- <el-table-column prop="planName" label="预案名称" min-width="180" show-overflow-tooltip />
- <el-table-column prop="planType" label="预案类型" width="130" />
- <el-table-column prop="planLevel" label="预案级别" width="100" align="center" />
- <el-table-column prop="reviewers" label="审批人员" min-width="180" show-overflow-tooltip />
- <el-table-column prop="auditRecord" label="审批记录/意见" min-width="240" show-overflow-tooltip />
- <el-table-column prop="status" label="状态" width="100" align="center">
- <template #default="{ row }">
- <el-tag :type="statusTagType(row.status)">{{ row.status }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="130" align="center">
- <template #default="{ row }">
- <el-button link type="primary" @click="handleView(row)">查看</el-button>
- <el-button link type="primary" @click="handleApprove(row)">审批</el-button>
- </template>
- </el-table-column>
- </el-table>
- <!-- 分页 -->
- <el-pagination
- style="margin-top: 16px; justify-content: flex-end"
- v-model:current-page="pagination.page"
- v-model:page-size="pagination.size"
- :page-sizes="[10, 20, 50]"
- :total="tableData.length"
- layout="total, sizes, prev, pager, next, jumper"
- @size-change="onSizeChange"
- @current-change="onPageChange"
- />
- <!-- 查看详情弹窗 -->
- <el-dialog v-model="detailVisible" title="预案详情" width="720px" destroy-on-close>
- <div class="detail-section">
- <div class="section-title">预案基本信息</div>
- <el-descriptions :column="2" border>
- <el-descriptions-item label="预案名称">{{ currentRow.planName }}</el-descriptions-item>
- <el-descriptions-item label="预案类型">{{ currentRow.planType }}</el-descriptions-item>
- <el-descriptions-item label="预案等级">{{ currentRow.planLevel }}</el-descriptions-item>
- <el-descriptions-item label="审批人员">{{ currentRow.reviewers }}</el-descriptions-item>
- </el-descriptions>
- </div>
- <!-- 审批流程 2x2 卡片 -->
- <div class="detail-section">
- <div class="section-title">审批流程</div>
- <div class="flow-grid">
- <div class="flow-card blue">
- <div class="flow-step">环节一:预案起草人提交</div>
- <div class="flow-info">审批人:安全总监 · 权限:初审/退回</div>
- </div>
- <div class="flow-card green">
- <div class="flow-step">环节二:专家技术评审</div>
- <div class="flow-info">审批人:专家组(3人) · 权限:修正建议/通过</div>
- </div>
- <div class="flow-card blue">
- <div class="flow-step">环节三:领导层终审</div>
- <div class="flow-info">审批人:应急总指挥 · 权限:批准/驳回</div>
- </div>
- <div class="flow-card yellow">
- <div class="flow-step">环节四:签发与归档</div>
- <div class="flow-info">状态:完成</div>
- </div>
- </div>
- </div>
- <!-- 预案调整 -->
- <div class="detail-section">
- <div class="section-title">预案调整</div>
- <div class="adjust-box">
- <div class="adjust-title">{{ currentRow.planName }} V{{ currentRow.version || '2.1' }}</div>
- <div class="adjust-content">
- 根据刘宏专家评审意见,优化预警阈值及撤离路线;增加物资调度流程。
- 评审结果:"科学性提升,建议补充应急通讯保障" — 已修正。
- </div>
- </div>
- </div>
- <!-- 预案发布 -->
- <div class="detail-section">
- <div class="section-title">预案发布</div>
- <div class="publish-box">
- <template v-if="currentRow.status === '已发布'">
- <el-tag type="success" size="large">已发布</el-tag>
- </template>
- <template v-else>
- <div class="unpublished-tip">未发布预案(应急处置不可用)</div>
- <el-button type="success" @click="handlePublish">发布</el-button>
- </template>
- </div>
- </div>
- </el-dialog>
- <!-- 审批弹窗 -->
- <el-dialog v-model="approveVisible" title="审批流程" width="600px" destroy-on-close>
- <!-- 案件基本信息 -->
- <div class="approve-header">
- <el-descriptions :column="1" border>
- <el-descriptions-item label="案件名称">{{ currentRow.planName }}</el-descriptions-item>
- <el-descriptions-item label="案件类型">{{ currentRow.planType }}</el-descriptions-item>
- <el-descriptions-item label="案件等级">{{ currentRow.planLevel }}</el-descriptions-item>
- </el-descriptions>
- </div>
- <!-- 审批环节列表 -->
- <div class="approve-steps">
- <div v-for="(step, idx) in approveSteps" :key="idx" :class="['approve-step-card', step.colorClass]">
- <div class="step-header">
- <span class="step-dot"></span>
- <span class="step-title">{{ step.title }}</span>
- </div>
- <div class="step-info">审批人:{{ step.approver }} · 权限:{{ step.permission }}</div>
- <div class="step-opinion">
- <span class="opinion-label">审批意见:</span>
- <el-input v-model="step.opinion" type="textarea" :rows="2" placeholder="请输入审批意见" />
- </div>
- </div>
- </div>
- <template #footer>
- <el-button type="danger" @click="handleReject">驳回</el-button>
- <el-button type="primary" @click="handleComplete">完成</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { getPlanApprovalPage, getPlanApprovalById, approvePlan, rejectPlan, publishPlan, getApprovalSteps } from '@/api/dispatch'
- // ---------- 查询 ----------
- const queryParams = ref({ planName: '' })
- // ---------- 数据 ----------
- const tableData = ref([])
- async function loadData() {
- try {
- const res = await getPlanApprovalPage(1, 999, queryParams.value)
- tableData.value = res?.data?.records ?? res?.data ?? res ?? []
- } catch {
- ElMessage.error('加载数据失败')
- }
- }
- onMounted(() => { loadData() })
- // ---------- 分页 ----------
- const pagination = ref({ page: 1, size: 10 })
- const pageList = computed(() => {
- const start = (pagination.value.page - 1) * pagination.value.size
- return tableData.value.slice(start, start + pagination.value.size)
- })
- const onSizeChange = () => { pagination.value.page = 1 }
- const onPageChange = () => {}
- // ---------- 搜索 / 重置 ----------
- const handleSearch = () => { pagination.value.page = 1; loadData() }
- const handleReset = () => {
- queryParams.value.planName = ''
- pagination.value.page = 1
- loadData()
- }
- // ---------- 状态 Tag 类型 ----------
- const statusTagType = (status) => {
- if (status === '终审中') return 'warning'
- if (status === '待修正') return 'danger'
- if (status === '已发布') return 'success'
- return 'info'
- }
- // ---------- 当前操作行 ----------
- const currentRow = ref({})
- // ---------- 查看详情弹窗 ----------
- const detailVisible = ref(false)
- const handleView = async (row) => {
- currentRow.value = { ...row }
- try {
- const res = await getApprovalSteps(row.id)
- currentRow.value.approvalSteps = res?.data?.records ?? res?.data ?? res ?? []
- } catch { /* ignore */ }
- detailVisible.value = true
- }
- // ---------- 发布 ----------
- const handlePublish = () => {
- ElMessageBox.confirm('确认发布该预案?发布后将在应急处置中启用。', '提示', { type: 'warning' }).then(async () => {
- try {
- await publishPlan(currentRow.value.id)
- currentRow.value.status = '已发布'
- ElMessage.success('发布成功')
- loadData()
- } catch {
- ElMessage.error('发布失败')
- }
- }).catch(() => {})
- }
- // ---------- 审批弹窗 ----------
- const approveVisible = ref(false)
- const approveSteps = ref([])
- const handleApprove = async (row) => {
- currentRow.value = { ...row }
- try {
- const res = await getApprovalSteps(row.id)
- const steps = res?.data?.records ?? res?.data ?? res ?? []
- if (steps.length > 0) {
- approveSteps.value = steps
- } else {
- approveSteps.value = [
- { title: '环节一:预案起草人提交', approver: '安全总监', permission: '初审/退回', colorClass: 'blue', opinion: '' },
- { title: '环节二:专家技术评审', approver: '专家组(3人)', permission: '修正建议/通过', colorClass: 'green', opinion: '' },
- { title: '环节三:领导层终审', approver: '应急总指挥', permission: '批准/驳回', colorClass: 'blue', opinion: '' },
- ]
- }
- } catch {
- approveSteps.value = [
- { title: '环节一:预案起草人提交', approver: '安全总监', permission: '初审/退回', colorClass: 'blue', opinion: '' },
- { title: '环节二:专家技术评审', approver: '专家组(3人)', permission: '修正建议/通过', colorClass: 'green', opinion: '' },
- { title: '环节三:领导层终审', approver: '应急总指挥', permission: '批准/驳回', colorClass: 'blue', opinion: '' },
- ]
- }
- approveVisible.value = true
- }
- const handleReject = async () => {
- try {
- await rejectPlan(currentRow.value.id)
- approveVisible.value = false
- ElMessage.success('已驳回,状态变更为待修正')
- loadData()
- } catch {
- ElMessage.error('驳回失败')
- }
- }
- const handleComplete = async () => {
- try {
- await approvePlan(currentRow.value.id)
- approveVisible.value = false
- ElMessage.success('审批完成')
- loadData()
- } catch {
- ElMessage.error('审批失败')
- }
- }
- </script>
- <style scoped lang="scss">
- .search-form {
- margin-bottom: 12px;
- :deep(.el-form-item) {
- margin-bottom: 8px;
- }
- }
- /* ---- 查看详情样式 ---- */
- .detail-section {
- margin-bottom: 20px;
- .section-title {
- font-weight: bold;
- font-size: 15px;
- margin-bottom: 10px;
- padding-left: 8px;
- border-left: 3px solid #409eff;
- }
- }
- .flow-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 12px;
- }
- .flow-card {
- border-radius: 6px;
- padding: 14px 16px;
- color: #fff;
- .flow-step {
- font-weight: bold;
- font-size: 14px;
- margin-bottom: 6px;
- }
- .flow-info {
- font-size: 13px;
- opacity: 0.9;
- }
- &.blue { background: #409eff; }
- &.green { background: #67c23a; }
- &.yellow { background: #e6a23c; }
- }
- .adjust-box {
- background: #fafafa;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 12px 16px;
- .adjust-title {
- font-weight: bold;
- margin-bottom: 6px;
- }
- .adjust-content {
- font-size: 13px;
- color: #606266;
- line-height: 1.6;
- }
- }
- .publish-box {
- background: #f0f9eb;
- border: 1px solid #e1f3d8;
- border-radius: 4px;
- padding: 16px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .unpublished-tip {
- font-size: 14px;
- color: #e6a23c;
- font-weight: bold;
- }
- }
- /* ---- 审批弹窗样式 ---- */
- .approve-header {
- margin-bottom: 16px;
- }
- .approve-steps {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .approve-step-card {
- border-radius: 6px;
- padding: 14px 16px;
- .step-header {
- display: flex;
- align-items: center;
- margin-bottom: 6px;
- .step-dot {
- display: inline-block;
- width: 10px;
- height: 10px;
- border-radius: 50%;
- margin-right: 8px;
- background: #fff;
- border: 2px solid #fff;
- }
- .step-title {
- font-weight: bold;
- font-size: 14px;
- color: #fff;
- }
- }
- .step-info {
- font-size: 13px;
- color: rgba(255, 255, 255, 0.9);
- margin-bottom: 8px;
- }
- .step-opinion {
- .opinion-label {
- font-size: 13px;
- color: #fff;
- display: block;
- margin-bottom: 4px;
- }
- }
- &.blue {
- background: #409eff;
- .step-dot { border-color: #fff; }
- }
- &.green {
- background: #67c23a;
- .step-dot { border-color: #fff; }
- }
- }
- </style>
|