|
|
@@ -1,13 +1,521 @@
|
|
|
<template>
|
|
|
- <div>
|
|
|
- 进度
|
|
|
+ <div class="w-full h-full relative flex justify-between items-center">
|
|
|
+ <!-- 左侧面板 -->
|
|
|
+ <Panel>
|
|
|
+ <CardTitle>项目倒计时</CardTitle>
|
|
|
+
|
|
|
+ <div class="w-full flex flex-row gap-10px pt-10px mb-10px">
|
|
|
+ <div class="flex flex-col">
|
|
|
+ <div class="p-5px flex flex-row justify-center items-center bg-[#348bc74c] w-200px">
|
|
|
+ <div class="backgroundImages w-70px h-70px flex justify-center items-center">
|
|
|
+ <img src="@/assets/images/u1407.svg" />
|
|
|
+ </div>
|
|
|
+ <div class="ml-10px">
|
|
|
+ <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
|
|
|
+ <div class="text-20px mt-10px">2024-10-01</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="p-5px flex flex-row justify-center items-center bg-[#348bc74c] mt-10px w-200px"
|
|
|
+ >
|
|
|
+ <div class="backgroundImages w-70px h-70px flex justify-center items-center">
|
|
|
+ <img src="@/assets/images/u1420.svg" />
|
|
|
+ </div>
|
|
|
+ <div class="ml-10px">
|
|
|
+ <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
|
|
|
+ <div class="text-20px mt-10px">2024-10-01</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="bg-[#348bc74c] w-211px flex flex-col justify-center items-center">
|
|
|
+ <DigitalFlop :config="config5" />
|
|
|
+ <div class="text-17px color-[#ffffffb2]">剩余天数</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <CardTitle>WBS分解</CardTitle>
|
|
|
+ <div class="text-white h-350px w-full pt-10px left-bottom-board">
|
|
|
+ <ScrollBoard :config="config1" @mouseover="mouseoverHandler" @click="clickHandler" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <CardTitle>CWS分解</CardTitle>
|
|
|
+ <div class="w-full h-260px flex gap-10px p-10px box-border justify-between items-center">
|
|
|
+ <Chart :options="chartOptions" />
|
|
|
+ </div>
|
|
|
+ </Panel>
|
|
|
+
|
|
|
+ <!-- 中间区域 -->
|
|
|
+ <div
|
|
|
+ class="h-full box-border pr-10px pl-10px middle-content flex-1 flex flex-col gap-10px justify-center"
|
|
|
+ >
|
|
|
+ <div class="text-white h-500px w-full center-board">
|
|
|
+ <ProjectMilestone />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧面板 -->
|
|
|
+ <Panel>
|
|
|
+ <div class="bg-[#348bc74c] w-full h-150px flex flex-col justify-center items-center">
|
|
|
+ <DigitalFlop :config="config5" style="height: 80px; width: 100%" />
|
|
|
+ <div class="text-32px color-[#ffffffb2]">实际AC</div>
|
|
|
+ </div>
|
|
|
+ <CardTitle>单体完成比率统计</CardTitle>
|
|
|
+ <div class="text-white h-350px w-full center-board right-top-board">
|
|
|
+ <Chart :options="chartOptionsRightTop" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <CardTitle>单体-分部完成比率统计</CardTitle>
|
|
|
+ <div class="w-full box-border p-10px h-350px">
|
|
|
+ <Chart :options="chartOptionsRightBottom" />
|
|
|
+ </div>
|
|
|
+ </Panel>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
+import { reactive, onMounted, ref } from 'vue';
|
|
|
+import { ScrollBoard, DigitalFlop } from '@kjgl77/datav-vue3';
|
|
|
+import Panel from '@/components/Panel.vue';
|
|
|
+import CardTitle from '@/components/CardTitle.vue';
|
|
|
+import Chart from '@/components/Chart/index.vue';
|
|
|
+import type { EChartsOption } from 'echarts';
|
|
|
+import ProjectMilestone from './ProjectMilestone.vue';
|
|
|
+
|
|
|
+const config1 = reactive({
|
|
|
+ header: ['单体', '当前状态', '所属分类'],
|
|
|
+ data: [
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ['高处作业安全', '253', '123'],
|
|
|
+ ],
|
|
|
+ headerBGC: '#348bc763',
|
|
|
+ evenRowBGC: 'rgba(29, 66, 110, 0.6)',
|
|
|
+ oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
|
|
|
+ align: ['center'],
|
|
|
+ index: true,
|
|
|
+ columnWidth: [50],
|
|
|
+ rowNum: 6,
|
|
|
+ headerHeight: 35,
|
|
|
+ waitTime: 50005000,
|
|
|
+});
|
|
|
+
|
|
|
+const config5 = reactive({
|
|
|
+ number: [1132],
|
|
|
+ content: '{nt}%',
|
|
|
+});
|
|
|
+
|
|
|
+setInterval(() => {
|
|
|
+ config5.number = config5.number.map((num) => num + 100);
|
|
|
+}, 3000);
|
|
|
+
|
|
|
+let scrollInterval: ReturnType<typeof setInterval>;
|
|
|
+let currentZoom = { start: 0, end: 50 };
|
|
|
+
|
|
|
+const chartData = ref({
|
|
|
+ plan: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
|
|
|
+ actual: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
|
|
|
+ actual1: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
|
|
|
+});
|
|
|
+
|
|
|
+const chartOptions = ref<EChartsOption>({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ textStyle: {
|
|
|
+ color: '#000',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
|
+ axisLabel: {
|
|
|
+ color: '#fff',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: {
|
|
|
+ color: '#fff',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '单体',
|
|
|
+ data: chartData.value.plan,
|
|
|
+ type: 'bar',
|
|
|
+ itemStyle: {
|
|
|
+ color: 'rgba(42, 254, 255, 1)',
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '状态',
|
|
|
+ data: chartData.value.actual,
|
|
|
+ type: 'bar',
|
|
|
+ itemStyle: {
|
|
|
+ color: 'rgba(225, 189, 97, 1)',
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '分类',
|
|
|
+ data: chartData.value.actual1,
|
|
|
+ type: 'bar',
|
|
|
+ itemStyle: {
|
|
|
+ color: 'rgba(0, 189, 97, 1)',
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ legend: {
|
|
|
+ data: ['单体', '状态', '分类'],
|
|
|
+ top: '10px',
|
|
|
+ right: '10px',
|
|
|
+ textStyle: {
|
|
|
+ color: '#fff',
|
|
|
+ },
|
|
|
+ itemWidth: 20,
|
|
|
+ itemHeight: 10,
|
|
|
+ icon: 'rect',
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: '15%',
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ start: currentZoom.start,
|
|
|
+ end: currentZoom.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+});
|
|
|
+
|
|
|
+const startAutoScroll = () => {
|
|
|
+ scrollInterval = setInterval(() => {
|
|
|
+ if (currentZoom.end >= 100) {
|
|
|
+ currentZoom.start = 0;
|
|
|
+ currentZoom.end = 50;
|
|
|
+ } else {
|
|
|
+ currentZoom.start += 2;
|
|
|
+ currentZoom.end += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ chartOptions.value = {
|
|
|
+ ...chartOptions.value,
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ start: currentZoom.start,
|
|
|
+ end: currentZoom.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ }, 2000); // 每2秒更新一次
|
|
|
+};
|
|
|
|
|
|
+// 右侧顶部柱状图
|
|
|
+let scrollIntervalRightTop: ReturnType<typeof setInterval>;
|
|
|
+let currentZoomeRightTop = { start: 0, end: 50 };
|
|
|
+const chartDataRightTop = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
|
|
|
+const chartOptionsRightTop = ref<EChartsOption>({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ textStyle: { color: '#000' },
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: { color: '#fff' },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [
|
|
|
+ '项目A',
|
|
|
+ '项目B',
|
|
|
+ '项目C',
|
|
|
+ '项目D',
|
|
|
+ '项目E',
|
|
|
+ '项目F',
|
|
|
+ '项目G',
|
|
|
+ '项目H',
|
|
|
+ '项目I',
|
|
|
+ '项目J',
|
|
|
+ '项目K',
|
|
|
+ '项目L',
|
|
|
+ ],
|
|
|
+ axisLabel: { color: '#fff' },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '进度',
|
|
|
+ type: 'bar',
|
|
|
+ data: chartDataRightTop.value,
|
|
|
+ barWidth: 16,
|
|
|
+ itemStyle: {
|
|
|
+ color: 'rgba(42, 254, 255, 1)',
|
|
|
+ borderRadius: [4, 4, 4, 4],
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'right',
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ grid: {
|
|
|
+ top: '5%',
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ yAxisIndex: 0,
|
|
|
+ start: currentZoom.start,
|
|
|
+ end: currentZoom.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+});
|
|
|
+
|
|
|
+const startAutoScrollRightTop = () => {
|
|
|
+ clearInterval(scrollIntervalRightTop);
|
|
|
+ scrollIntervalRightTop = setInterval(() => {
|
|
|
+ if (currentZoomeRightTop.end >= 100) {
|
|
|
+ currentZoomeRightTop.start = 0;
|
|
|
+ currentZoomeRightTop.end = 50;
|
|
|
+ } else {
|
|
|
+ currentZoomeRightTop.start += 2;
|
|
|
+ currentZoomeRightTop.end += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ chartOptionsRightTop.value = {
|
|
|
+ ...chartOptionsRightTop.value,
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ yAxisIndex: 0,
|
|
|
+ start: currentZoomeRightTop.start,
|
|
|
+ end: currentZoomeRightTop.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ }, 2000);
|
|
|
+};
|
|
|
+
|
|
|
+// 右侧底部
|
|
|
+
|
|
|
+let scrollIntervalRightBottom: ReturnType<typeof setInterval>;
|
|
|
+let currentZoomeRightBottom = { start: 0, end: 50 };
|
|
|
+const chartDataRightBottom = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
|
|
|
+const chartOptionsRightBottom = ref<EChartsOption>({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ textStyle: { color: '#000' },
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: { color: '#fff' },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [
|
|
|
+ '项目A',
|
|
|
+ '项目B',
|
|
|
+ '项目C',
|
|
|
+ '项目D',
|
|
|
+ '项目E',
|
|
|
+ '项目F',
|
|
|
+ '项目G',
|
|
|
+ '项目H',
|
|
|
+ '项目I',
|
|
|
+ '项目J',
|
|
|
+ '项目K',
|
|
|
+ '项目L',
|
|
|
+ ],
|
|
|
+ axisLabel: { color: '#fff' },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '进度',
|
|
|
+ type: 'bar',
|
|
|
+ data: chartDataRightBottom.value,
|
|
|
+ barWidth: 16,
|
|
|
+ itemStyle: {
|
|
|
+ color: 'rgba(42, 254, 255, 1)',
|
|
|
+ borderRadius: [4, 4, 4, 4],
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'right',
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ grid: {
|
|
|
+ top: '5%',
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ yAxisIndex: 0,
|
|
|
+ start: currentZoom.start,
|
|
|
+ end: currentZoom.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+});
|
|
|
+
|
|
|
+const startAutoScrollRightBottom = () => {
|
|
|
+ clearInterval(scrollIntervalRightBottom);
|
|
|
+ scrollIntervalRightBottom = setInterval(() => {
|
|
|
+ if (currentZoomeRightBottom.end >= 100) {
|
|
|
+ currentZoomeRightBottom.start = 0;
|
|
|
+ currentZoomeRightBottom.end = 50;
|
|
|
+ } else {
|
|
|
+ currentZoomeRightBottom.start += 2;
|
|
|
+ currentZoomeRightBottom.end += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ chartOptionsRightBottom.value = {
|
|
|
+ ...chartOptionsRightBottom.value,
|
|
|
+ dataZoom: [
|
|
|
+ {
|
|
|
+ show: false,
|
|
|
+ type: 'slider',
|
|
|
+ yAxisIndex: 0,
|
|
|
+ start: currentZoomeRightBottom.start,
|
|
|
+ end: currentZoomeRightBottom.end,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ }, 2000);
|
|
|
+};
|
|
|
+
|
|
|
+// 模拟数据更新
|
|
|
+setInterval(() => {
|
|
|
+ chartData.value.plan = chartData.value.plan.map((item) => item + Math.floor(Math.random() * 10));
|
|
|
+ chartData.value.actual = chartData.value.actual.map(
|
|
|
+ (item) => item + Math.floor(Math.random() * 10),
|
|
|
+ );
|
|
|
+ chartData.value.actual1 = chartData.value.actual1.map(
|
|
|
+ (item) => item + Math.floor(Math.random() * 10),
|
|
|
+ );
|
|
|
+
|
|
|
+ chartDataRightTop.value = chartDataRightTop.value.map((v) => v + Math.floor(Math.random() * 10));
|
|
|
+ chartDataRightBottom.value = chartDataRightBottom.value.map(
|
|
|
+ (v) => v + Math.floor(Math.random() * 10),
|
|
|
+ );
|
|
|
+}, 5000); // 每5秒更新一次数据
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ startAutoScroll();
|
|
|
+ startAutoScrollRightTop();
|
|
|
+ startAutoScrollRightBottom();
|
|
|
+});
|
|
|
+
|
|
|
+const mouseoverHandler = (e: any) => {
|
|
|
+ console.log(e);
|
|
|
+};
|
|
|
+
|
|
|
+const clickHandler = (e: any) => {
|
|
|
+ console.log(e);
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
+<style lang="less" scoped>
|
|
|
+:deep(*) {
|
|
|
+ .el-descriptions__body {
|
|
|
+ background-color: transparent;
|
|
|
+ }
|
|
|
+ .el-descriptions__label {
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ .el-descriptions__content {
|
|
|
+ color: #ccc;
|
|
|
+ }
|
|
|
+}
|
|
|
+.backgroundImages {
|
|
|
+ background: url(@/assets/images/u1404.svg) center center no-repeat;
|
|
|
+}
|
|
|
+:deep(.left-bottom-board .dv-scroll-board .header) {
|
|
|
+ height: 35px;
|
|
|
+}
|
|
|
+:deep(.left-bottom-board .dv-scroll-board .header-item) {
|
|
|
+ font-size: 17px !important;
|
|
|
+ color: #31e4cf !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.left-board .dv-scroll-board .header) {
|
|
|
+ height: 30px !important;
|
|
|
+ color: #31e4cf;
|
|
|
+}
|
|
|
+:deep(.left-board .row-item) {
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+:deep(.left-board .header-item) {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 17px;
|
|
|
+}
|
|
|
|
|
|
-</style>
|
|
|
+:deep(.center-board .dv-scroll-board .header) {
|
|
|
+ height: 48px !important;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+:deep(.left-bottom-board .dv-scroll-board .rows .ceil) {
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+:deep(.center-board .dv-scroll-board .rows .ceil) {
|
|
|
+ font-size: 16px;
|
|
|
+ color: rgba(247, 247, 247, 0.698039215686274);
|
|
|
+}
|
|
|
+:deep(.dv-scroll-ranking-board .ranking-info .rank) {
|
|
|
+ width: auto !important;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+:deep(.dv-scroll-ranking-board .ranking-column) {
|
|
|
+ height: 14px;
|
|
|
+ border: none;
|
|
|
+ background-color: rgba(232, 232, 232, 0.27843137254902);
|
|
|
+}
|
|
|
+:deep(.dv-scroll-ranking-board .ranking-column .inside-column) {
|
|
|
+ height: 100%;
|
|
|
+ background-color: rgba(115, 235, 233, 1);
|
|
|
+}
|
|
|
+</style>
|