diff --git a/src/pages/Container/Institution/components/ProjectCompletionTable.jsx b/src/pages/Container/Institution/components/ProjectCompletionTable.jsx index 7396233..2d47aab 100644 --- a/src/pages/Container/Institution/components/ProjectCompletionTable.jsx +++ b/src/pages/Container/Institution/components/ProjectCompletionTable.jsx @@ -1,8 +1,11 @@ import React from 'react'; -export default function ProjectCompletionTable({ data, onView }) { +export default function ProjectCompletionTable({ data, loading, onView }) { const list = data && data.list ? data.list : []; + // 加载占位:固定行数的骨架,保持表格布局 + const skeletonRows = Array.from({ length: 5 }); + return (
@@ -26,26 +29,39 @@ export default function ProjectCompletionTable({ data, onView }) { - {list.map((record, index) => ( - - {index + 1} - {record.projectNo} - {record.projectName} - {record.customerName} - {record.evalTypeName} - {record.projectLeaderName} - {record.planEndDate} - - - - - ))} + {loading + ? skeletonRows.map((_, index) => ( + + + + + + + + + + + )) + : list.map((record, index) => ( + + {index + 1} + {record.projectNo} + {record.projectName} + {record.customerName} + {record.evalTypeName} + {record.projectLeaderName} + {record.planEndDate} + + + + + ))}
diff --git a/src/pages/Container/Institution/index.css b/src/pages/Container/Institution/index.css index 59d3fef..4e9101b 100644 --- a/src/pages/Container/Institution/index.css +++ b/src/pages/Container/Institution/index.css @@ -357,6 +357,40 @@ color: #fff; } +/* ============ 骨架占位(加载态) ============ */ +.sk-block { + display: inline-block; + background: linear-gradient(90deg, #eef2f7 25%, #e2e8f0 37%, #eef2f7 63%); + background-size: 400% 100%; + animation: sk-shimmer 1.3s ease-in-out infinite; + vertical-align: middle; +} + +@keyframes sk-shimmer { + 0% { background-position: 100% 0; } + 100% { background-position: 0 0; } +} + +/* 图表占位块 */ +.sk-chart { + width: 100%; + height: 225px; + border-radius: 8px; + background: linear-gradient(90deg, #eef2f7 25%, #e2e8f0 37%, #eef2f7 63%); + background-size: 400% 100%; + animation: sk-shimmer 1.3s ease-in-out infinite; +} +.sk-chart--donut { height: 185px; } + +/* 表格单元格骨架 */ +.sk-cell { + width: 70%; + height: 12px; + border-radius: 4px; +} +.sk-cell--lg { width: 85%; } +.sk-cell--sm { width: 36px; } + /* ============ 响应式 ============ */ @media (max-width: 1280px) { .dashboard-classic-top, diff --git a/src/pages/Container/Institution/index.js b/src/pages/Container/Institution/index.js index 3de426f..6e1023b 100644 --- a/src/pages/Container/Institution/index.js +++ b/src/pages/Container/Institution/index.js @@ -7,12 +7,6 @@ import './index.css'; import { STATISTIC_CARDS, - INFO_ALERTS, - PROJECT_SUMMARY, - ENTERPRISE_TYPE_STATS, - PROJECT_TYPE_DISTRIBUTION, - PROJECT_TOTAL, - PROJECT_COMPLETION_LIST, } from './mockData'; import { @@ -23,65 +17,37 @@ import { fetchProjectExecution, } from '~/api/institution'; -// ==================== UI 展示常量(与原型保持一致,非数据映射) ==================== - -function mockIndustryToApiFormat() { - return { - list: ENTERPRISE_TYPE_STATS.categories.map((name, i) => ({ - industryCode: '', - industryName: name, - projectCount: ENTERPRISE_TYPE_STATS.projectCount[i], - statutoryProjectCount: ENTERPRISE_TYPE_STATS.statutoryCount[i], - })), - }; -} - -function mockEvalTypeToApiFormat() { - return { - totalProjectCount: PROJECT_TOTAL, - items: PROJECT_TYPE_DISTRIBUTION.map(item => ({ - evalTypeCode: '', - evalTypeName: item.name, - count: item.value, - })), - }; -} - -function mockExecutionToApiFormat() { - return { - list: PROJECT_COMPLETION_LIST.map(item => ({ - projectId: item.id, - projectNo: item.projectCode, - projectName: item.projectName, - customerName: item.evalCompany, - evalTypeCode: '', - evalTypeName: item.evalType, - projectLeaderName: item.projectLeader, - planEndDate: item.endDate, - })), - }; +// ==================== 骨架占位 ==================== +function Skeleton({ width = '100%', height = 14, radius = 4, className = '' }) { + return ( +
@@ -229,20 +180,25 @@ export default function InstitutionDashboard() { 法定项目 - + {loading + ?
+ : }

评价类别占比

- + {loading + ?
+ : }
{/* ===== 第三行:项目执行情况 ===== */} {}} /> diff --git a/src/pages/Container/Institution/mockData.js b/src/pages/Container/Institution/mockData.js index a127346..72b1421 100644 --- a/src/pages/Container/Institution/mockData.js +++ b/src/pages/Container/Institution/mockData.js @@ -1,79 +1,17 @@ /** - * 机构端首页 - 模拟数据(1:1 还原原型 V1.4) + * 机构端首页 - 卡片模板(仅标题/图标/样式,value 统一为 0,由接口数据覆盖)。 + * 不再保留任何 mock 假数据:接口无数据时展示 0 / 空值。 */ +// 仅作为卡片模板(标题/图标/样式),value 统一默认 0,由接口数据覆盖。 export const STATISTIC_CARDS = [ - { key: 'riskAnalysis', title: '待风险分析', value: 3, char: '险', colorClass: 'blue' }, - { key: 'contractInput', title: '待合同录入', value: 2, char: '合', colorClass: 'violet' }, - { key: 'projectTeam', title: '待项目组成立', value: 2, char: '组', colorClass: 'green' }, - { key: 'siteSurvey', title: '待现场踏勘', value: 4, char: '场', colorClass: 'orange' }, - { key: 'reportDraft', title: '待报告编制', value: 5, char: '报', colorClass: 'blue' }, - { key: 'internalReview', title: '待内部审核', value: 3, char: '内', colorClass: 'violet' }, - { key: 'techReview', title: '待技术审核', value: 2, char: '技', colorClass: 'orange' }, - { key: 'processReview', title: '待过程控制审核', value: 1, char: '控', colorClass: 'green' }, - { key: 'archived', title: '已归档项目', value: 9, char: '档', colorClass: 'cyan' }, - { key: 'delayed', title: '项目延期', value: 2, char: '延', colorClass: 'red' }, -]; - -export const INFO_ALERTS = [ - { key: 'qualCheck', title: '资质现场审查通知', value: 1, char: '审', colorClass: 'yellow' }, - { key: 'supervisionCheck', title: '监督检查通知', value: 3, char: '检', colorClass: 'red' }, - { key: 'unreadNotice', title: '未读监管通知', value: 2, char: '未', colorClass: 'blue' }, -]; - -export const PROJECT_SUMMARY = { - total: 31, - statutory: 24, -}; - -export const ENTERPRISE_TYPE_STATS = { - categories: ['危险化学品', '非煤矿山', '金属冶炼', '工贸', '烟花爆竹', '石油天然气', '其他行业'], - projectCount: [24, 20, 15, 18, 12, 14, 10], - statutoryCount: [19, 16, 12, 14, 9, 11, 7], -}; - -export const PROJECT_TYPE_DISTRIBUTION = [ - { name: '安全预评价', value: 12, color: '#4285f4' }, - { name: '安全验收评价', value: 8, color: '#58b874' }, - { name: '安全现状评价', value: 11, color: '#ff9f2f' }, -]; - -export const PROJECT_TOTAL = 31; - -export const PROJECT_COMPLETION_LIST = [ - { - id: 1, - projectCode: 'XP-2026-001', - projectName: '华安化工园区安全预评价', - evalCompany: '重庆华安安全科技有限公司', - evalType: '安全预评价', - projectLeader: '张建国', - endDate: '2026-09-30', - }, - { - id: 2, - projectCode: 'XP-2026-002', - projectName: '鼎信建设安全验收评价', - evalCompany: '重庆鼎信安全咨询有限公司', - evalType: '安全验收评价', - projectLeader: '李明华', - endDate: '2026-08-15', - }, - { - id: 3, - projectCode: 'XP-2026-003', - projectName: '西南矿业安全现状评价', - evalCompany: '重庆西南矿业有限公司', - evalType: '安全现状评价', - projectLeader: '陈志强', - endDate: '2026-10-20', - }, - { - id: 4, - projectCode: 'XP-2026-004', - projectName: '两江装备制造安全现状评价', - evalCompany: '重庆两江装备制造有限公司', - evalType: '安全现状评价', - projectLeader: '王海峰', - endDate: '2026-11-08', - }, + { key: 'riskAnalysis', title: '待风险分析', value: 0, char: '险', colorClass: 'blue' }, + { key: 'contractInput', title: '待合同录入', value: 0, char: '合', colorClass: 'violet' }, + { key: 'projectTeam', title: '待项目组成立', value: 0, char: '组', colorClass: 'green' }, + { key: 'siteSurvey', title: '待现场踏勘', value: 0, char: '场', colorClass: 'orange' }, + { key: 'reportDraft', title: '待报告编制', value: 0, char: '报', colorClass: 'blue' }, + { key: 'internalReview', title: '待内部审核', value: 0, char: '内', colorClass: 'violet' }, + { key: 'techReview', title: '待技术审核', value: 0, char: '技', colorClass: 'orange' }, + { key: 'processReview', title: '待过程控制审核', value: 0, char: '控', colorClass: 'green' }, + { key: 'archived', title: '已归档项目', value: 0, char: '档', colorClass: 'cyan' }, + { key: 'delayed', title: '项目延期', value: 0, char: '延', colorClass: 'red' }, ]; diff --git a/src/pages/Container/Supervision/Cockpit/index.css b/src/pages/Container/Supervision/Cockpit/index.css index df558ca..c7ee99a 100644 --- a/src/pages/Container/Supervision/Cockpit/index.css +++ b/src/pages/Container/Supervision/Cockpit/index.css @@ -313,3 +313,37 @@ font-size: 12px; color: #d9f2ff; } + +/* ============ 骨架占位(深色大屏) ============ */ +.sk-block--dark { + background: linear-gradient(90deg, rgba(40, 92, 160, .35) 25%, rgba(70, 130, 210, .5) 37%, rgba(40, 92, 160, .35) 63%); + background-size: 400% 100%; + animation: sk-shimmer-dark 1.3s ease-in-out infinite; +} + +@keyframes sk-shimmer-dark { + 0% { background-position: 100% 0; } + 100% { background-position: 0 0; } +} + +/* 图表占位块 */ +.sk-chart--dark { + width: 100%; + height: 150px; + border-radius: 8px; + background: linear-gradient(90deg, rgba(40, 92, 160, .3) 25%, rgba(70, 130, 210, .45) 37%, rgba(40, 92, 160, .3) 63%); + background-size: 400% 100%; + animation: sk-shimmer-dark 1.3s ease-in-out infinite; +} +.sk-chart--donut { height: 144px; } +.sk-chart--dark.sk-chart--donut { height: 144px; } + +/* 风险/预警小骨架块微调 */ +.risk-item .sk-block--dark, +.review-stat .sk-block--dark, +.life-stat .sk-block--dark, +.progress-row .sk-block--dark, +.map-summary .sk-block--dark { + display: inline-block; + vertical-align: middle; +} diff --git a/src/pages/Container/Supervision/Cockpit/index.js b/src/pages/Container/Supervision/Cockpit/index.js index ce59288..a2234d2 100644 --- a/src/pages/Container/Supervision/Cockpit/index.js +++ b/src/pages/Container/Supervision/Cockpit/index.js @@ -14,7 +14,6 @@ import { } from '@ant-design/icons'; import DriverBack from '~/pages/Container/Driver/components/DriverBack'; import './index.css'; -import { cockpitMockData } from './mockData'; import chongqingGeoJson from './data/chongqingGeoJson.json'; import { fetchQualificationOverview, @@ -46,85 +45,60 @@ function formatNow(date = new Date()) { return `${date.getFullYear()}年${pad(date.getMonth() + 1)}月${pad(date.getDate())}日 ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } -// 将 mock 数据转为 API 结构,作为初始值 -function buildInitialFromMock(m) { +// 所有数据的真实空值初始结构(无 mock,接口无数据时展示 0 / 空) +function buildInitialState() { return { - title: m.title, + title: '重庆市安全评价在线监管一件事', currentTime: formatNow(), lifecycleStats: { - newFilingOrgCount: m.lifecycleStats[0]?.value || 0, - currentFilingOrgCount: m.lifecycleStats[1]?.value || 0, - logoutOrgCount: m.lifecycleStats[2]?.value || 0, - totalFilingOrgCount: m.lifecycleStats[3]?.value || 0, - exitedEvaluatorCount: m.lifecycleStats[4]?.value || 0, - currentEvaluatorCount: m.lifecycleStats[5]?.value || 0, + newFilingOrgCount: 0, + currentFilingOrgCount: 0, + logoutOrgCount: 0, + totalFilingOrgCount: 0, + exitedEvaluatorCount: 0, + currentEvaluatorCount: 0, }, progress: { - filingCompleteRate: m.progress[0]?.value || 0, - bizActiveRate: m.progress[1]?.value || 0, - newGrowthRate: m.progress[2]?.value || 0, + filingCompleteRate: 0, + bizActiveRate: 0, + newGrowthRate: 0, }, - industryList: m.industry.map((item, i) => ({ - industryCode: String(i + 1), - industryName: item.name, - orgCount: item.institution, - typeCount: item.rectification, - })), - riskItems: m.riskItems, - riskPie: m.riskPie, - kpiItems: m.kpis.map(k => ({ - code: '', - name: k.title, - value: parseFloat(k.value), - yoy: parseFloat(k.change), - })), - regionList: m.mapAreaStats - .filter(item => item.name !== '重庆市') - .map(item => ({ - districtCode: '', - districtName: item.name, - evalProjectCount: item.projectCount, - filingOrgCount: item.institutionCount, - })), + industryList: [], + riskItems: [], + riskPie: [], + kpiItems: [], + regionList: [], regionSummary: { - totalEvalProject: m.mapAreaStats.find(s => s.name === '重庆市')?.projectCount || 0, - totalFilingOrg: m.mapAreaStats.find(s => s.name === '重庆市')?.institutionCount || 0, + totalEvalProject: 0, + totalFilingOrg: 0, }, - mapScatterPoints: m.mapScatterPoints, - processNodes: m.todo.map((item, i) => ({ - nodeCode: `NODE_${i}`, - nodeName: item.label, - totalCount: 0, - pendingCount: item.value, - })), - trendBuckets: m.reviewLine.xAxis, - trendSeries: [ - { evalTypeCode: '', evalTypeName: '项目数', projectCounts: m.reviewLine.project, inspCounts: [] }, - { evalTypeCode: '', evalTypeName: '监督检查数', projectCounts: m.reviewLine.supervision, inspCounts: [] }, - ], + mapScatterPoints: [], + processNodes: [], + trendBuckets: [], + trendSeries: [], reviewStats: { - inspCount: m.reviewProgress[0]?.value || 0, - onsiteCheckCount: m.reviewProgress[0]?.value || 0, + inspCount: 0, + onsiteCheckCount: 0, qualKeepCount: 0, specialCount: 0, - checkedOrgCount: m.reviewProgress[4]?.value || 0, - foundProblemCount: m.reviewProgress[5]?.value || 0, + checkedOrgCount: 0, + foundProblemCount: 0, }, - reviewPieItems: m.reviewPie.map(item => ({ - code: '', - name: item.name, - value: item.value, - })), - // 项目流程 mock 所用(基于 processOverview API nodes 结构) - processMockNodes: m.process.map((item, i) => ({ - nodeCode: `NODE_${i}`, - nodeName: item.label, - totalCount: item.value, - pendingCount: 0, - })), + reviewPieItems: [], }; } +// ==================== 骨架占位(深色大屏) ==================== +function Skeleton({ width = '100%', height = 14, radius = 4, className = '' }) { + return ( +