| | |
| | | <template> |
| | | <div class="relative"> |
| | | <HeaderBox title="石盘安置C地块项目" /> |
| | | <!-- 地图 --> |
| | | <div id="container" style="width: 1920px; height: 995px"></div> |
| | | <!-- 左边 内容 --> |
| | | <div class="left-card"> |
| | |
| | | <div class="pie-canvas" ref="pieChart"></div> |
| | | <div class="legend"> |
| | | <div class="legend-item" v-for="(it, idx) in legendData" :key="idx"> |
| | | <span class="dot" :style="{ background: (it.itemStyle && it.itemStyle.color) || '#6cf' }"></span> |
| | | <div class="content"> |
| | | <div class="name">{{ it.name }}</div> |
| | | <div class="value">{{ it.value }}%</div> |
| | | <div class="dot" :style="{ backgroundColor: it.itemStyle.color }"></div> |
| | | <div class="legend-info"> |
| | | <div class="legend-name" :title="it.name">{{it.name}}</div> |
| | | <div class="legend-number">{{it.value}}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | <template #content> |
| | | <div class="risk-info"> |
| | | <div class="risk-top"> |
| | | <div class="label card1">序号</div> |
| | | <div class="label card2">风险规模</div> |
| | | <div class="label flex1">风险源内容</div> |
| | | <div class="label card4">实施情况</div> |
| | | </div> |
| | | <div class="risk-body"> |
| | | <div class="risk-item" v-for="(item, index) in riskSourceList" :key="index"> |
| | | <div class="label card1">{{ index + 1 }}</div> |
| | | <div |
| | | class="label card2" |
| | | :class="{ |
| | | 'merged-cell': item.showRiskScale && item.rowspan > 1, |
| | | 'hidden-cell': !item.showRiskScale |
| | | }" |
| | | > |
| | | <div |
| | | v-if="item.showRiskScale" |
| | | class="merged-content" |
| | | :style="{ |
| | | height: `${item.rowspan * 36}px`, |
| | | lineHeight: `${item.rowspan * 36}px` |
| | | }" |
| | | > |
| | | {{ item.riskScale }} |
| | | </div> |
| | | </div> |
| | | <div class="label flex1">{{ item.content }}</div> |
| | | <div class="label card4">{{ item.status }}</div> |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | </template> |
| | | </TitleBox> |
| | | </div> |
| | | <div class="mt--30"> |
| | | <TitleBox> |
| | | <template #title>项目风险趋势 </template> |
| | | <template #right> |
| | | <div @click="handleClick" class="risk-picker"> |
| | | <el-date-picker v-model="dateRange" ref="datePicker" type="monthrange" range-separator="" |
| | | start-placeholder="" end-placeholder=""> |
| | | </el-date-picker> |
| | | <div class="risk-ab"> |
| | | <img src="../../assets/img/home/Shape@2x (6).png" class="pick-img" /> |
| | | |
| | | <div class="pick-text"> |
| | | {{ dateTxt }} |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | <template #content> |
| | | <div class="risk-trend"> |
| | | <div id="riskTrendChart" style="width: 100%; height: 234px"></div> |
| | | </div> |
| | | </template> |
| | | </TitleBox> |
| | | </div> |
| | |
| | | { name: "正常", value: 10, itemStyle: { color: "#00FF00" } }, |
| | | { name: "异常", value: 20, itemStyle: { color: "#FF0000" } }, |
| | | { name: "待处理", value: 30, itemStyle: { color: "#FFA500" } }, |
| | | { name: "待完成", value: 30, itemStyle: { color: "#FF00FF" } }, |
| | | { name: "已完成", value: 30, itemStyle: { color: "#00FFFF" } }, |
| | | ], |
| | | dateTxt: '', |
| | | riskTrandList: [], |
| | | // 风险源数据 |
| | | riskSourceData: [ |
| | | { riskScale: '一般', content: '项目风险源内容1', status: '未开始' }, |
| | | { riskScale: '一般', content: '项目风险源内容2', status: '进行中' }, |
| | | { riskScale: '一般', content: '项目风险源内容3', status: '已完成' }, |
| | | { riskScale: '重大', content: '项目风险源内容4', status: '未开始' }, |
| | | { riskScale: '重大', content: '项目风险源内容5', status: '进行中' }, |
| | | ] |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 处理风险源数据,添加合并单元格的逻辑 |
| | | riskSourceList() { |
| | | if (!this.riskSourceData || this.riskSourceData.length === 0) { |
| | | return []; |
| | | } |
| | | |
| | | const result = []; |
| | | let i = 0; |
| | | |
| | | while (i < this.riskSourceData.length) { |
| | | const currentRiskScale = this.riskSourceData[i].riskScale; |
| | | let rowspan = 1; |
| | | |
| | | // 计算相同风险规模的行数 |
| | | for (let j = i + 1; j < this.riskSourceData.length; j++) { |
| | | if (this.riskSourceData[j].riskScale === currentRiskScale) { |
| | | rowspan++; |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 添加第一行,显示风险规模 |
| | | result.push({ |
| | | ...this.riskSourceData[i], |
| | | showRiskScale: true, |
| | | rowspan: rowspan |
| | | }); |
| | | |
| | | // 添加后续相同风险规模的行,不显示风险规模 |
| | | for (let k = 1; k < rowspan; k++) { |
| | | result.push({ |
| | | ...this.riskSourceData[i + k], |
| | | showRiskScale: false, |
| | | rowspan: 0 |
| | | }); |
| | | } |
| | | |
| | | i += rowspan; |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | }, |
| | | watch: { |
| | | dateRange: { |
| | | handler(newVal, oldVal) { |
| | | if (newVal.length > 0) { |
| | | this.dateTxt = |
| | | moment(newVal[0]).format("YYYY年MM月DD日") + |
| | | " ~ " + |
| | | moment(newVal[1]).format("YYYY年MM月DD日"); |
| | | moment(newVal[0]).format("YYYY -MM") + |
| | | " - " + |
| | | moment(newVal[1]).format("YYYY -MM"); |
| | | } |
| | | }, |
| | | immediate: true, |
| | |
| | | }, |
| | | created() { }, |
| | | mounted() { |
| | | this.setMapload(); |
| | | this.setMapload() |
| | | this.initPie3D() |
| | | |
| | | this.initRiskChart() |
| | | }, |
| | | beforeDestroy() { |
| | | }, |
| | |
| | | version: "2.0", |
| | | }) |
| | | .then((AMap) => { |
| | | |
| | | // 微信建筑图层 |
| | | let satellite = new AMap.TileLayer.Satellite(); |
| | | //创建路网图层 |
| | | let roadNet = new AMap.TileLayer.RoadNet(); |
| | | this.mapView = new AMap.Map("container", { |
| | | zoom: 13, |
| | | center: [108.357471, 22.828643], // 初始化地图中心点 |
| | | // mapStyle: "amap://styles/darkblue", |
| | | layers: [satellite], |
| | | }); |
| | | this.initCategoryMarkers(AMap); |
| | | }) |
| | | .catch((e) => { |
| | | console.error(e); //加载错误提示 |
| | |
| | | // }, |
| | | // }); |
| | | return series; |
| | | } |
| | | }, |
| | | handleClick() { |
| | | this.$refs.datePicker.focus(); |
| | | }, |
| | | // 初始化折线图 |
| | | initRiskChart() { |
| | | const riskChart = echarts.init(document.getElementById('riskTrendChart')) |
| | | |
| | | // 文本换行处理函数 |
| | | const formatLabel = (text, maxWidth = 4) => { |
| | | if (!text) return '' |
| | | const textStr = String(text) |
| | | // 如果文本长度超过最大宽度,进行换行 |
| | | if (textStr.length > maxWidth) { |
| | | let result = '' |
| | | for (let i = 0; i < textStr.length; i += maxWidth) { |
| | | result += textStr.substring(i, i + maxWidth) + '\n' |
| | | } |
| | | return result.trim() |
| | | } |
| | | return textStr |
| | | } |
| | | |
| | | let option = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | show: false |
| | | }, |
| | | legend: { |
| | | show: false |
| | | }, |
| | | grid: { |
| | | top: '5%', |
| | | bottom: '5%', |
| | | left: '5%', |
| | | right: '5%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | data: ['2025年1月检查', '2025年2月检查', '2025年3月检查', '2025年4月检查', '2025年5月检查'], |
| | | axisTick: { show: true, alignWithLabel: true }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#666' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | interval: 0, // 强制显示所有标签 |
| | | rotate: 0, // 不旋转 |
| | | formatter: function (value) { |
| | | // X轴标签格式化,最多7个字符,超出换行 |
| | | return formatLabel(value, 7) |
| | | }, |
| | | // 文本对齐方式 |
| | | align: 'center', |
| | | verticalAlign: 'top', |
| | | lineHeight: 14, |
| | | // 设置标签的最大宽度 |
| | | width: 60, |
| | | overflow: 'break' // 超出换行 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | type: 'dashed', // 虚线 |
| | | color: 'rgba(255, 255, 255, .2)' // 固定灰色 |
| | | } |
| | | }, |
| | | axisLine: { |
| | | show: false |
| | | }, |
| | | axisTick: { |
| | | show: false |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | formatter: function (value) { |
| | | // Y轴标签格式化,最多3个字符,超出换行 |
| | | const text = String(value) |
| | | return formatLabel(text, 3) |
| | | }, |
| | | // 文本对齐方式 |
| | | align: 'right', |
| | | verticalAlign: 'middle', |
| | | lineHeight: 14, |
| | | // 设置标签的最大宽度 |
| | | width: 30, |
| | | overflow: 'break' // 超出换行 |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '风险等级', |
| | | type: 'line', |
| | | data: [10, 52, 20, 33, 39], |
| | | smooth: false, // 不使用圆滑过渡 |
| | | showSymbol: false, // 不显示转折点 |
| | | lineStyle: { |
| | | width: 5, // 线条宽度 |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 1, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(36, 81, 242, 1)' }, // 起始颜色 |
| | | { offset: 0.5, color: 'rgba(11, 204, 254, 1)' }, // 中间颜色 |
| | | { offset: 1, color: 'rgba(199, 160, 254, 1)' } // 结束颜色 |
| | | ] |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | riskChart.setOption(option) |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | |
| | | left: 0; |
| | | } |
| | | |
| | | // .chart-area { |
| | | // width: 470px; |
| | | // height: 188px; |
| | | // position: relative; |
| | | // padding-right: 180px; // 右侧为图例预留更多空间 |
| | | // background: radial-gradient(circle at center, rgba(0, 191, 255, 0.15) 0%, transparent 70%); |
| | | // border-radius: 8px; |
| | | // } |
| | | |
| | | .pie-canvas { |
| | | position: absolute; |
| | | left: -37px; |
| | |
| | | |
| | | .legend { |
| | | position: absolute; |
| | | right: 20px; |
| | | top: 28px; |
| | | width: 200px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | right: 23px; |
| | | bottom: 5px; |
| | | max-height: 126px; // 3个item的高度:(32px + 10px) * 3 = 126px |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | z-index: 33; |
| | | |
| | | .legend-item { |
| | | display: flex; |
| | | align-items: center; |
| | | // 自定义滚动条样式 |
| | | &::-webkit-scrollbar { |
| | | width: 4px; |
| | | } |
| | | |
| | | color: #F0FAFF; |
| | | font-size: 11px; |
| | | font-weight: 400; |
| | | &::-webkit-scrollbar-track { |
| | | background: rgba(255, 255, 255, 0.1); |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | &:nth-child(2n+1) { |
| | | margin-right: 37px; |
| | | &::-webkit-scrollbar-thumb { |
| | | background: rgba(59, 130, 246, 0.6); |
| | | border-radius: 2px; |
| | | |
| | | &:hover { |
| | | background: rgba(59, 130, 246, 0.8); |
| | | } |
| | | } |
| | | |
| | | .legend-item { |
| | | display: flex; |
| | | margin-bottom: 10px; |
| | | |
| | | width: 180px; |
| | | height: 32px; |
| | | background-image: url('../../../src/assets/img/project/equipmentBg.png'); |
| | | background-size: 100% 100%; |
| | | |
| | | position: relative; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .dot { |
| | | width: 6px; |
| | | height: 6px; |
| | | margin-left: 5px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .legend-info { |
| | | width: 130px; |
| | | height: 32px; |
| | | padding: 7px 11px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | .legend-name { |
| | | max-width: 70px; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #CCE0F8; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; // 确保单行显示 |
| | | cursor: pointer; // 鼠标悬停时显示手型 |
| | | transition: color 0.3s ease; |
| | | |
| | | &:hover { |
| | | color: #FFFFFF; // 悬停时文字变亮 |
| | | } |
| | | } |
| | | |
| | | .legend-number { |
| | | font-weight: bold; |
| | | font-size: 18px; |
| | | color: #1BDDFF; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .legend-item .dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 2px; |
| | | margin-right: 8px; |
| | | margin-top: 2px; |
| | | display: inline-block; |
| | | box-shadow: 0 0 3px rgba(255, 255, 255, 0.2); |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .legend-item .content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .legend-item .name { |
| | | font-family: SourceHanSansCN, SourceHanSansCN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(255, 255, 255, .5); |
| | | line-height: 24px; |
| | | } |
| | | |
| | | .legend-item .value { |
| | | font-family: SourceHanSansCN, SourceHanSansCN; |
| | | font-weight: bold; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | line-height: 24px; |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | width: 107px; |
| | | } |
| | | |
| | | .label { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #FFFFFF; |
| | | text-align: center; |
| | | } |
| | | |
| | | .card4 { |
| | | width: 72px; |
| | | text-align: left; |
| | | } |
| | | |
| | | .risk-top { |
| | | padding: 6px 0; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 7px; |
| | | } |
| | | |
| | | .title { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #FFFFFF; |
| | | text-align: center; |
| | | .risk-body { |
| | | .risk-item { |
| | | display: flex; |
| | | align-items: stretch; |
| | | |
| | | &:nth-last-child(1) { |
| | | // border-right: 1px solid #337AB6; |
| | | |
| | | .label { |
| | | border-bottom: 1px solid #337AB6; |
| | | } |
| | | } |
| | | |
| | | .label { |
| | | &:nth-last-child(1) { |
| | | border-right: 1px solid #337AB6; |
| | | border-left: unset !important; |
| | | padding-left: 6px; |
| | | font-weight: bold; |
| | | font-size: 12px; |
| | | color: #66FFFF; |
| | | font-style: normal; |
| | | } |
| | | |
| | | line-height: 36px; |
| | | border-top: 1px solid #337AB6; |
| | | border-left: 1px solid #337AB6; |
| | | position: relative; |
| | | |
| | | &.merged-cell { |
| | | position: relative; |
| | | |
| | | .merged-content { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | // border-bottom: 1px solid #337AB6; |
| | | } |
| | | } |
| | | |
| | | &.hidden-cell { |
| | | border-top: none; |
| | | // border-left: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | .risk-trend { |
| | | height: 234px; |
| | | } |
| | | |
| | | .risk-picker { |
| | | width: 190px; |
| | | height: 26px; |
| | | background: rgba(14, 93, 251, 0.5); |
| | | border-radius: 4px; |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | ::v-deep .el-input__icon { |
| | | display: none; |
| | | } |
| | | |
| | | ::v-deep .el-input__inner { |
| | | background: transparent; |
| | | border: none; |
| | | color: transparent; |
| | | } |
| | | |
| | | ::v-deep .el-range-input { |
| | | background: transparent; |
| | | border: none; |
| | | color: transparent; |
| | | } |
| | | |
| | | .risk-ab { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | padding: 6px 26px 6px 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | |
| | | } |
| | | |
| | | .pick-text { |
| | | |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #FFFFFF; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | .pick-img { |
| | | width: 12px; |
| | | height: 12px; |
| | | margin-right: 12px; |
| | | } |
| | | } |
| | | |
| | | } |
| | | </style> |