|
@@ -58,23 +58,15 @@
|
|
|
<!-- 列表 -->
|
|
<!-- 列表 -->
|
|
|
<ContentWrap class="flex-1 overflow-hidden mt-15px" style="border: none">
|
|
<ContentWrap class="flex-1 overflow-hidden mt-15px" style="border: none">
|
|
|
<div class="stats-cards">
|
|
<div class="stats-cards">
|
|
|
- <div class="stats-card">
|
|
|
|
|
- <div
|
|
|
|
|
- class="stats-card__decor stats-card__decor--left"
|
|
|
|
|
- :style="{
|
|
|
|
|
- background:
|
|
|
|
|
- 'radial-gradient(circle, rgba(61, 124, 255, 0.2) 0%, rgba(61, 124, 255, 0) 72%)'
|
|
|
|
|
- }"></div>
|
|
|
|
|
- <div class="stats-card__decor stats-card__decor--right"></div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="stats-card"
|
|
|
|
|
+ style="background: linear-gradient(180deg, #fff 0%, #e8f2ff 100%)">
|
|
|
<div class="stats-card__header">
|
|
<div class="stats-card__header">
|
|
|
- <div class="stats-card__icon-wrap">
|
|
|
|
|
- <div class="stats-card__icon">
|
|
|
|
|
- <Icon icon="eos-icons:counting" color="#2563eb" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Icon icon="eos-icons:counting" color="#2563eb" />
|
|
|
|
|
+
|
|
|
<div class="stats-card__title">隐患总数</div>
|
|
<div class="stats-card__title">隐患总数</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="stats-card__body">
|
|
|
|
|
|
|
+ <div class="stats-card__body py-8">
|
|
|
<CountTo
|
|
<CountTo
|
|
|
:duration="2600"
|
|
:duration="2600"
|
|
|
:end-val="hiddenCount"
|
|
:end-val="hiddenCount"
|
|
@@ -83,23 +75,14 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="stats-card">
|
|
|
|
|
- <div
|
|
|
|
|
- class="stats-card__decor stats-card__decor--left"
|
|
|
|
|
- :style="{
|
|
|
|
|
- background:
|
|
|
|
|
- 'radial-gradient(circle, rgba(255, 91, 97, 0.22) 0%, rgba(255, 91, 97, 0) 72%)'
|
|
|
|
|
- }"></div>
|
|
|
|
|
- <div class="stats-card__decor stats-card__decor--right"></div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="stats-card"
|
|
|
|
|
+ style="background: linear-gradient(180deg, #fff4f4 0%, #ffe8e8 100%)">
|
|
|
<div class="stats-card__header">
|
|
<div class="stats-card__header">
|
|
|
- <div class="stats-card__icon-wrap">
|
|
|
|
|
- <div class="stats-card__icon" :style="{ color: '#ff5b61' }">
|
|
|
|
|
- <Icon icon="ep:info-filled" color="#de3b3b" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Icon icon="ep:info-filled" color="#de3b3b" />
|
|
|
<div class="stats-card__title">未整改数</div>
|
|
<div class="stats-card__title">未整改数</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="stats-card__body">
|
|
|
|
|
|
|
+ <div class="stats-card__body py-8">
|
|
|
<CountTo
|
|
<CountTo
|
|
|
:duration="2600"
|
|
:duration="2600"
|
|
|
:end-val="todo"
|
|
:end-val="todo"
|
|
@@ -108,26 +91,31 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="stats-card">
|
|
|
|
|
- <div class="stats-card__decor stats-card__decor--left"></div>
|
|
|
|
|
- <div class="stats-card__decor stats-card__decor--right"></div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="stats-card"
|
|
|
|
|
+ style="background: linear-gradient(180deg, #fff 0%, #e8f2ff 100%)">
|
|
|
<div class="stats-card__header">
|
|
<div class="stats-card__header">
|
|
|
- <div class="stats-card__icon-wrap">
|
|
|
|
|
- <div class="stats-card__icon">
|
|
|
|
|
- <Icon icon="ep:info-filled" color="#de3b3b" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Icon icon="material-symbols:android-cell-4-bar" color="#2563eb" />
|
|
|
|
|
+
|
|
|
<div class="stats-card__title">整改率</div>
|
|
<div class="stats-card__title">整改率</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="stats-card__body">
|
|
|
|
|
|
|
+ <div class="stats-card__body py-8">
|
|
|
<CountTo
|
|
<CountTo
|
|
|
:duration="2600"
|
|
:duration="2600"
|
|
|
- :end-val="((hiddenCount - todo) / hiddenCount) * 100"
|
|
|
|
|
|
|
+ :end-val="((hiddenCount - todo) / hiddenCount) * 100 || 0"
|
|
|
:start-val="0"
|
|
:start-val="0"
|
|
|
class="stats-card__count text-2xl text-center! text-[#2563eb]!" />
|
|
class="stats-card__count text-2xl text-center! text-[#2563eb]!" />
|
|
|
<span class="text-[#2563eb]!">%</span>
|
|
<span class="text-[#2563eb]!">%</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 饼图 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="stats-card pie-chart-card"
|
|
|
|
|
+ style="background: linear-gradient(180deg, #fff 0%, #e8f2ff 100%)">
|
|
|
|
|
+ <div class="pie-chart-title">隐患来源分布</div>
|
|
|
|
|
+ <div ref="pieChartRef" class="pie-chart-container"></div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
<zm-table
|
|
<zm-table
|
|
|
:loading="loading"
|
|
:loading="loading"
|
|
@@ -371,6 +359,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
|
|
+import { ref, reactive, watch, nextTick } from 'vue'
|
|
|
import { IotHiddenApi, IotHiddenTypeApi } from '@/api/pms/qhse/index'
|
|
import { IotHiddenApi, IotHiddenTypeApi } from '@/api/pms/qhse/index'
|
|
|
import DeptTree from '@/views/system/user/DeptTree2.vue'
|
|
import DeptTree from '@/views/system/user/DeptTree2.vue'
|
|
|
|
|
|
|
@@ -390,6 +379,13 @@ const userStore = useUserStore()
|
|
|
import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
const { ZmTable, ZmTableColumn } = useTableComponents()
|
|
const { ZmTable, ZmTableColumn } = useTableComponents()
|
|
|
|
|
|
|
|
|
|
+import * as echarts from 'echarts/core'
|
|
|
|
|
+import { PieChart } from 'echarts/charts'
|
|
|
|
|
+import { TooltipComponent, LegendComponent } from 'echarts/components'
|
|
|
|
|
+import { CanvasRenderer } from 'echarts/renderers'
|
|
|
|
|
+
|
|
|
|
|
+echarts.use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
|
|
|
|
+
|
|
|
defineOptions({ name: 'IotQHSECertificate' })
|
|
defineOptions({ name: 'IotQHSECertificate' })
|
|
|
|
|
|
|
|
const loading = ref(true) // 列表的加载中
|
|
const loading = ref(true) // 列表的加载中
|
|
@@ -704,6 +700,8 @@ const getTree = async () => {
|
|
|
let hiddenCount = ref(0)
|
|
let hiddenCount = ref(0)
|
|
|
let todo = ref(0)
|
|
let todo = ref(0)
|
|
|
let source = ref('')
|
|
let source = ref('')
|
|
|
|
|
+const pieChartRef = ref<HTMLDivElement>()
|
|
|
|
|
+let pieChart: echarts.ECharts | null = null
|
|
|
async function getStatic() {
|
|
async function getStatic() {
|
|
|
if (queryParams.deptId) {
|
|
if (queryParams.deptId) {
|
|
|
const res = await IotHiddenApi.getHiddenStatistics(queryParams.deptId)
|
|
const res = await IotHiddenApi.getHiddenStatistics(queryParams.deptId)
|
|
@@ -716,6 +714,71 @@ async function getStatic() {
|
|
|
todo.value = res.todo
|
|
todo.value = res.todo
|
|
|
source.value = res.source
|
|
source.value = res.source
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initPieChart()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const initPieChart = () => {
|
|
|
|
|
+ if (!pieChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ if (pieChart) {
|
|
|
|
|
+ pieChart.dispose()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pieChart = echarts.init(pieChartRef.value)
|
|
|
|
|
+
|
|
|
|
|
+ const chartData = Array.isArray(source.value)
|
|
|
|
|
+ ? source.value.map((item: any) => ({
|
|
|
|
|
+ name: item.classify || '未知',
|
|
|
|
|
+ value: item.count || 0
|
|
|
|
|
+ }))
|
|
|
|
|
+ : []
|
|
|
|
|
+
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ formatter: '{b}: {c} ({d}%)'
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ orient: 'vertical',
|
|
|
|
|
+ left: 'left',
|
|
|
|
|
+ top: 'middle',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ fontSize: 11
|
|
|
|
|
+ },
|
|
|
|
|
+ itemWidth: 10,
|
|
|
|
|
+ itemHeight: 10
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '隐患来源',
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['35%', '60%'],
|
|
|
|
|
+ center: ['58%', '50%'],
|
|
|
|
|
+ avoidLabelOverlap: false,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ borderRadius: 6,
|
|
|
|
|
+ borderColor: '#fff',
|
|
|
|
|
+ borderWidth: 2
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ fontWeight: 'bold'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ data: chartData
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pieChart.setOption(option)
|
|
|
}
|
|
}
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
getList()
|
|
getList()
|
|
@@ -723,6 +786,23 @@ onMounted(async () => {
|
|
|
|
|
|
|
|
getStatic()
|
|
getStatic()
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ if (pieChart) {
|
|
|
|
|
+ pieChart.dispose()
|
|
|
|
|
+ pieChart = null
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => source.value,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initPieChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true }
|
|
|
|
|
+)
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
@@ -748,20 +828,12 @@ onMounted(async () => {
|
|
|
.stats-card {
|
|
.stats-card {
|
|
|
position: relative;
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
- min-height: 132px;
|
|
|
|
|
- padding: 18px 18px 16px;
|
|
|
|
|
- border-radius: 22px;
|
|
|
|
|
- background: radial-gradient(
|
|
|
|
|
- circle at 18% 22%,
|
|
|
|
|
- rgb(255 255 255 / 92%) 0%,
|
|
|
|
|
- rgb(255 255 255 / 0%) 20%
|
|
|
|
|
- ),
|
|
|
|
|
- radial-gradient(circle at 88% 80%, rgb(255 215 158 / 22%) 0%, rgb(255 215 158 / 0%) 16%),
|
|
|
|
|
- linear-gradient(135deg, rgb(239 245 255 / 96%) 0%, rgb(217 230 248 / 88%) 100%);
|
|
|
|
|
- border: 1px solid rgb(255 255 255 / 62%);
|
|
|
|
|
- box-shadow:
|
|
|
|
|
- inset 0 1px 0 rgb(255 255 255 / 86%),
|
|
|
|
|
- 0 14px 30px rgb(116 146 191 / 12%);
|
|
|
|
|
|
|
+ min-height: 100px;
|
|
|
|
|
+ padding: 18px 18px 0px;
|
|
|
|
|
+
|
|
|
|
|
+ border: 1px solid #e4ecf7;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-shadow: 0 4px 12px rgb(31 91 184 / 8%);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.stats-card__decor {
|
|
.stats-card__decor {
|
|
@@ -834,7 +906,7 @@ onMounted(async () => {
|
|
|
font-weight: 800;
|
|
font-weight: 800;
|
|
|
line-height: 0.92;
|
|
line-height: 0.92;
|
|
|
letter-spacing: 1px;
|
|
letter-spacing: 1px;
|
|
|
- font-style: italic;
|
|
|
|
|
|
|
+ /* font-style: italic; */
|
|
|
text-shadow: 0 8px 18px rgb(68 110 183 / 10%);
|
|
text-shadow: 0 8px 18px rgb(68 110 183 / 10%);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -845,6 +917,26 @@ onMounted(async () => {
|
|
|
line-height: 1;
|
|
line-height: 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.pie-chart-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ min-height: 180px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.pie-chart-title {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #324b72;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.pie-chart-container {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 50px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
.stats-cards {
|
|
.stats-cards {
|
|
|
grid-template-columns: 1fr;
|
|
grid-template-columns: 1fr;
|
|
@@ -888,5 +980,9 @@ onMounted(async () => {
|
|
|
font-size: 18px;
|
|
font-size: 18px;
|
|
|
padding-bottom: 6px;
|
|
padding-bottom: 6px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .pie-chart-container {
|
|
|
|
|
+ min-height: 220px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|