8 changed files with 1823 additions and 191 deletions
@ -0,0 +1,439 @@ |
|||
<template> |
|||
<div class="statistical-home"> |
|||
<div class="wrap_all"> |
|||
<!-- 左侧:行政执法 --> |
|||
<div class="wrap_left"> |
|||
<div class="cont_left"> |
|||
<h1 class="block_tit">行政执法</h1> |
|||
<div class="count_admin_wrap"> |
|||
<div class="count_admin"> |
|||
<label><img src="/statistical/images/icon_jc2.png" alt="单独执法" />单独执法</label> |
|||
<div class="count_admin_num"> |
|||
<b>{{ coordination.singleNum }}</b> |
|||
<span class="number_percent">{{ coordination.singlePer }}<i>%</i></span> |
|||
</div> |
|||
<div class="percent_box"> |
|||
<div class="percent-tag"> |
|||
<span class="data-rise"><i class="fa fa-long-arrow-up"></i></span> |
|||
<span class="data-change">{{ coordination.singleYoy }}%</span> |
|||
<label>同比</label> |
|||
</div> |
|||
<div class="percent-tag"> |
|||
<span class="data-fall"><i class="fa fa-long-arrow-down"></i></span> |
|||
<span class="data-change">{{ coordination.singleRr }}%</span> |
|||
<label>环比</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="count_admin"> |
|||
<label><img src="/statistical/images/icon_jc.png" alt="联合执法" />联合执法</label> |
|||
<div class="count_admin_num"> |
|||
<b>{{ coordination.unionNum }}</b> |
|||
<span class="number_percent">{{ coordination.unionPer }}<i>%</i></span> |
|||
</div> |
|||
<div class="percent_box"> |
|||
<div class="percent-tag"> |
|||
<span class="data-rise"><i class="fa fa-long-arrow-up"></i></span> |
|||
<span class="data-change">{{ coordination.unionYoy }}%</span> |
|||
<label>同比</label> |
|||
</div> |
|||
<div class="percent-tag"> |
|||
<span class="data-fall"><i class="fa fa-long-arrow-down"></i></span> |
|||
<span class="data-change">{{ coordination.unionRr }}%</span> |
|||
<label>环比</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="count_admin"> |
|||
<label><img src="/statistical/images/icon_qy.png" alt="被执法企业" />被执法企业</label> |
|||
<div class="count_admin_num"><b>{{ enterprise.num }}</b></div> |
|||
<div class="percent_box"> |
|||
<div class="percent-tag"> |
|||
<span class="data-rise"><i class="fa fa-long-arrow-up"></i></span> |
|||
<span class="data-change">{{ enterprise.yoy }}%</span> |
|||
<label>同比</label> |
|||
</div> |
|||
<div class="percent-tag"> |
|||
<span class="data-fall"><i class="fa fa-long-arrow-down"></i></span> |
|||
<span class="data-change">{{ enterprise.rr }}%</span> |
|||
<label>环比</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="count_admin"> |
|||
<label><img src="/statistical/images/icon_yj.png" alt="入企压减率" />入企压减率</label> |
|||
<div class="count_admin_num"><b>{{ coordination.cutNum }}</b></div> |
|||
<div class="percent_box"> |
|||
<div class="percent-tag yjlv"> |
|||
<span class="number_percent">{{ coordination.cutPer }}<i>%</i></span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div ref="adminPieRef" class="chartbox_right" style="margin-top: -10%"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 右侧:非行政执法 --> |
|||
<div class="wrap_right"> |
|||
<div class="cont_left"> |
|||
<h1 class="block_tit">非行政执法</h1> |
|||
<div class="count_wrap"> |
|||
<div v-for="(item, index) in nonTypeList" :key="item.dict_label" class="count_item"> |
|||
<label> |
|||
<img :src="`/statistical/images/icon_24_r${index + 1}.png`" :alt="item.dict_label" /> |
|||
{{ item.dict_label }} |
|||
</label> |
|||
<span class="number_count">{{ item.num }}</span> |
|||
</div> |
|||
</div> |
|||
<div ref="nonPieRef" class="chartbox_right"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 中间:地图 --> |
|||
<div class="wrap_cent"> |
|||
<div class="select_set"> |
|||
<div class="select_item"> |
|||
<span>深圳市</span> |
|||
<span class="time_wrap"> |
|||
<el-date-picker |
|||
v-model="dateRange" |
|||
type="daterange" |
|||
range-separator="至" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
value-format="YYYY-MM-DD" |
|||
:disabled-date="disabledDate" |
|||
clearable |
|||
@change="onDateChange" |
|||
/> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
<div class="link_btn" style="text-align: right"> |
|||
<router-link v-if="showLeftLink" to="/enforcement-code/statistical/city-direct">{{ leftLinkText }}</router-link> |
|||
<router-link to="/enforcement-code/statistical/district-direct"><span>{{ rightLinkText }}</span></router-link> |
|||
</div> |
|||
<div class="count_box"> |
|||
<div class="count_item admin"> |
|||
<div class="count_tit"><label>行政执法</label></div> |
|||
<div class="count_num"> |
|||
<div class="count_num_all">{{ coordination.allNum }}</div> |
|||
<div class="count_num_sub"> |
|||
<span>(市直:<b>{{ sJcNum }}</b></span> |
|||
<span>各区:<b>{{ qJcNum }}</b>)</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="count_item non_admin"> |
|||
<div class="count_tit"><label>非行政执法</label></div> |
|||
<div class="count_num"> |
|||
<div class="count_num_all">{{ njcAll }}</div> |
|||
<div class="count_num_sub"> |
|||
<span>(市直:<b>{{ sNjcNum }}</b></span> |
|||
<span>各区:<b>{{ qNjcNum }}</b>)</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="map_box"> |
|||
<img class="szmap" src="/statistical/images/szmap_3d.png" alt="深圳地图" width="1920" height="1080" border="0" /> |
|||
<div class="label_wrap"> |
|||
<DistrictPopup |
|||
v-for="config in districtConfigs" |
|||
:key="config.key" |
|||
:config="config" |
|||
:data="districtDataMap[config.name]" |
|||
:expanded="expandedDistrictIdx === config.idx" |
|||
@toggle="onDistrictToggle" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 底部:趋势图 --> |
|||
<div class="wrap_bottom"> |
|||
<div class="cont_left"> |
|||
<h3>涉企执法趋势</h3> |
|||
<div class="tab_tit"> |
|||
<a :class="{ active: trendTab === 0 }" @click="switchTrendTab(0)">行政执法</a> |
|||
<a :class="{ active: trendTab === 1 }" @click="switchTrendTab(1)">非行政执法</a> |
|||
</div> |
|||
<div class="tab_chart"> |
|||
<div v-show="trendTab === 0" ref="adminTrendRef" class="chartbox" style="position: absolute"></div> |
|||
<div v-show="trendTab === 1" ref="nonTrendRef" class="chartbox" style="position: absolute"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|||
import * as echarts from 'echarts'; |
|||
import DistrictPopup from './components/DistrictPopup.vue'; |
|||
import { |
|||
analysisEnterprises, |
|||
analysisEnterprisesCoordination, |
|||
analysisEnterprisesNonAll, |
|||
analysisNonRegistrationType, |
|||
analysisRegistrationType, |
|||
analysisTime, |
|||
areaRegistration, |
|||
nonAnalysisTime |
|||
} from '@/api/supervision/statistical/statisticalPanel'; |
|||
import type { AreaRegistrationData, DictStatItem } from '@/api/supervision/statistical/types'; |
|||
import { |
|||
normalizeAreaRegistration, |
|||
normalizeCoordination, |
|||
normalizeDictStatList, |
|||
normalizeEnterpriseStat, |
|||
normalizeTimeSeries, |
|||
toNum, |
|||
unwrapData |
|||
} from '@/api/supervision/statistical/adapters'; |
|||
import { buildAdminPieOption, buildNonAdminPieOption, buildTrendLineOption } from '../utils/chartOptions'; |
|||
import { DISTRICT_CONFIGS, DISTRICT_LIST } from '../utils/districtConfig'; |
|||
|
|||
async function safeLoad<T>(fn: () => Promise<T>): Promise<T | null> { |
|||
try { |
|||
return await fn(); |
|||
} catch (e) { |
|||
console.warn('[统计首页] 接口加载失败', e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
const districtConfigs = DISTRICT_CONFIGS; |
|||
|
|||
const dateRange = ref<[string, string] | null>(null); |
|||
const trendTab = ref(0); |
|||
const expandedDistrictIdx = ref<number | null>(null); |
|||
|
|||
const coordination = reactive({ |
|||
roleFlag: 1, |
|||
roleDept: '', |
|||
allNum: 0, |
|||
nonNum: 0, |
|||
unionNum: 0, |
|||
unionYoy: '0.0', |
|||
unionRr: '0.0', |
|||
unionPer: '0.0', |
|||
singleNum: 0, |
|||
singleYoy: '0.0', |
|||
singleRr: '0.0', |
|||
singlePer: '0.0', |
|||
cutNum: 0, |
|||
cutPer: '0.0' |
|||
}); |
|||
|
|||
const enterprise = reactive({ num: 0, yoy: '0.0', rr: '0.0' }); |
|||
const nonTypeList = ref<DictStatItem[]>([]); |
|||
const njcAll = ref(0); |
|||
const qJcNum = ref(0); |
|||
const qNjcNum = ref(0); |
|||
const districtDataMap = ref<Record<string, AreaRegistrationData>>({}); |
|||
|
|||
const adminPieRef = ref<HTMLElement>(); |
|||
const nonPieRef = ref<HTMLElement>(); |
|||
const adminTrendRef = ref<HTMLElement>(); |
|||
const nonTrendRef = ref<HTMLElement>(); |
|||
|
|||
let adminPieChart: echarts.ECharts | null = null; |
|||
let nonPieChart: echarts.ECharts | null = null; |
|||
let adminTrendChart: echarts.ECharts | null = null; |
|||
let nonTrendChart: echarts.ECharts | null = null; |
|||
|
|||
const showLeftLink = computed(() => coordination.roleFlag === 1); |
|||
const leftLinkText = computed(() => '各市直执法单位综合分析'); |
|||
const rightLinkText = computed(() => { |
|||
if ([9, 4, 3].includes(coordination.roleFlag)) { |
|||
return `${coordination.roleDept}综合分析`; |
|||
} |
|||
return '各区综合分析'; |
|||
}); |
|||
const sJcNum = computed(() => ([9, 4, 3].includes(coordination.roleFlag) ? '-' : coordination.allNum - qJcNum.value)); |
|||
const sNjcNum = computed(() => ([9, 4, 3].includes(coordination.roleFlag) ? '-' : njcAll.value - qNjcNum.value)); |
|||
|
|||
function disabledDate(time: Date) { |
|||
const min = new Date('2019-04-14').getTime(); |
|||
const max = new Date('2099-06-16').getTime(); |
|||
return time.getTime() < min || time.getTime() > max; |
|||
} |
|||
|
|||
function getTimeParams() { |
|||
if (!dateRange.value) return { beginTime: undefined, endTime: undefined }; |
|||
return { beginTime: dateRange.value[0], endTime: dateRange.value[1] }; |
|||
} |
|||
|
|||
async function loadCoordination() { |
|||
const res = await safeLoad(() => analysisEnterprisesCoordination(getTimeParams())); |
|||
if (!res) return; |
|||
const data = normalizeCoordination(unwrapData(res)); |
|||
Object.assign(coordination, data); |
|||
njcAll.value = data.nonNum; |
|||
} |
|||
|
|||
async function loadEnterprise() { |
|||
const res = await safeLoad(() => analysisEnterprises(getTimeParams())); |
|||
if (!res) return; |
|||
Object.assign(enterprise, normalizeEnterpriseStat(unwrapData(res))); |
|||
} |
|||
|
|||
async function loadNjcAll() { |
|||
const res = await safeLoad(() => analysisEnterprisesNonAll(getTimeParams())); |
|||
if (!res) return; |
|||
njcAll.value = toNum(unwrapData(res)); |
|||
} |
|||
|
|||
async function loadAdminType() { |
|||
const res = await safeLoad(() => analysisRegistrationType(getTimeParams())); |
|||
if (!res) return; |
|||
const list = normalizeDictStatList(unwrapData(res)); |
|||
const chartData = list.filter((item) => item.dict_label).map((item) => ({ value: item.num, name: item.dict_label })); |
|||
await nextTick(); |
|||
if (adminPieRef.value && chartData.length) { |
|||
adminPieChart ??= echarts.init(adminPieRef.value); |
|||
adminPieChart.setOption(buildAdminPieOption(chartData), true); |
|||
} |
|||
} |
|||
|
|||
async function loadNonType() { |
|||
const res = await safeLoad(() => analysisNonRegistrationType(getTimeParams())); |
|||
if (!res) return; |
|||
const list = normalizeDictStatList(unwrapData(res)); |
|||
nonTypeList.value = list; |
|||
const chartData = list.filter((item) => item.dict_label).map((item) => ({ value: item.num, name: item.dict_label })); |
|||
await nextTick(); |
|||
if (nonPieRef.value && chartData.length) { |
|||
nonPieChart ??= echarts.init(nonPieRef.value); |
|||
nonPieChart.setOption(buildNonAdminPieOption(chartData), true); |
|||
} |
|||
} |
|||
|
|||
async function loadAdminTrend() { |
|||
const res = await safeLoad(() => analysisTime(getTimeParams())); |
|||
if (!res) return; |
|||
const { times, ins, ent } = normalizeTimeSeries(unwrapData(res)); |
|||
await nextTick(); |
|||
if (adminTrendRef.value && times.length) { |
|||
adminTrendChart ??= echarts.init(adminTrendRef.value); |
|||
adminTrendChart.setOption( |
|||
buildTrendLineOption(times, ins.map(Number), ent.map(Number), '行政执法数量', '被检查企业数量', '行政执法数量(宗)'), |
|||
true |
|||
); |
|||
} |
|||
} |
|||
|
|||
async function loadNonTrend() { |
|||
const res = await safeLoad(() => nonAnalysisTime(getTimeParams())); |
|||
if (!res) return; |
|||
const { times, ins, ent } = normalizeTimeSeries(unwrapData(res)); |
|||
await nextTick(); |
|||
if (nonTrendRef.value && times.length) { |
|||
nonTrendChart ??= echarts.init(nonTrendRef.value); |
|||
nonTrendChart.setOption( |
|||
buildTrendLineOption(times, ins.map(Number), ent.map(Number), '非行政执法数量', '被检查企业数量', '行政执法数量(宗)'), |
|||
true |
|||
); |
|||
} |
|||
} |
|||
|
|||
async function loadAreaData() { |
|||
const params = getTimeParams(); |
|||
let jcSum = 0; |
|||
let njcSum = 0; |
|||
const dataMap: Record<string, AreaRegistrationData> = {}; |
|||
|
|||
const results = await Promise.allSettled( |
|||
DISTRICT_LIST.map((area) => areaRegistration({ ...params, deptId: area })) |
|||
); |
|||
|
|||
results.forEach((result, index) => { |
|||
if (result.status !== 'fulfilled') return; |
|||
const data = normalizeAreaRegistration(unwrapData(result.value)); |
|||
const area = data.area || DISTRICT_LIST[index]; |
|||
dataMap[area] = data; |
|||
jcSum += data.allNum; |
|||
njcSum += data.njcNum; |
|||
}); |
|||
|
|||
districtDataMap.value = dataMap; |
|||
qJcNum.value = jcSum; |
|||
qNjcNum.value = njcSum; |
|||
} |
|||
|
|||
async function refreshAll() { |
|||
await Promise.allSettled([ |
|||
loadCoordination(), |
|||
loadEnterprise(), |
|||
loadNjcAll(), |
|||
loadAdminType(), |
|||
loadNonType(), |
|||
loadAdminTrend(), |
|||
loadNonTrend(), |
|||
loadAreaData() |
|||
]); |
|||
} |
|||
|
|||
function onDateChange() { |
|||
refreshAll(); |
|||
} |
|||
|
|||
function onDistrictToggle(idx: number) { |
|||
expandedDistrictIdx.value = expandedDistrictIdx.value === idx ? null : idx; |
|||
} |
|||
|
|||
function switchTrendTab(tab: number) { |
|||
trendTab.value = tab; |
|||
nextTick(() => { |
|||
if (tab === 0) adminTrendChart?.resize(); |
|||
else nonTrendChart?.resize(); |
|||
}); |
|||
} |
|||
|
|||
function handleResize() { |
|||
adminPieChart?.resize(); |
|||
nonPieChart?.resize(); |
|||
adminTrendChart?.resize(); |
|||
nonTrendChart?.resize(); |
|||
} |
|||
|
|||
onMounted(() => { |
|||
refreshAll(); |
|||
window.addEventListener('resize', handleResize); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
window.removeEventListener('resize', handleResize); |
|||
adminPieChart?.dispose(); |
|||
nonPieChart?.dispose(); |
|||
adminTrendChart?.dispose(); |
|||
nonTrendChart?.dispose(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
@import '/statistical/styles/home.css'; |
|||
@import '/statistical/font-awesome-bak/css/font-awesome.min.css'; |
|||
|
|||
.statistical-home { |
|||
width: 100%; |
|||
min-height: calc(100vh - 50px); |
|||
overflow: auto; |
|||
background: #0b1d4b; |
|||
} |
|||
|
|||
.statistical-home :deep(.el-date-editor) { |
|||
--el-input-bg-color: rgba(0, 30, 80, 0.6); |
|||
--el-input-border-color: rgba(58, 174, 251, 0.4); |
|||
--el-input-text-color: rgba(255, 255, 255, 0.85); |
|||
--el-input-placeholder-color: rgba(255, 255, 255, 0.45); |
|||
} |
|||
|
|||
.statistical-home :deep(.el-range-separator) { |
|||
color: rgba(255, 255, 255, 0.6); |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue