cxc-szcx-uniapp/pages/views/renliziyuan/renyuanxinxi/qttongji - 副本.vue
廖德云 1ef1384fa8 Merge branch 'master' of http://ngtools.cn:53000/ldeyun/cxc-szcx-uniapp
# Conflicts:
#	.env.development
#	api/renyuan.js
#	manifest.json
#	pages.json
#	pages/views/renliziyuan/renyuanxinxi/index.vue
#	pages/views/renliziyuan/renyuanxinxi/tongji.vue
2025-02-12 02:21:11 +08:00

731 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<view class="container" id="top1">
<uni-row style="margin-bottom: 10rpx; margin-left: 30rpx; margin-right: 30rpx">
<uni-col :span="24"><uni-title :title="'所选单位ID'" align="left" type="h4"></uni-title></uni-col>
</uni-row>
<uni-row style="margin-bottom: 20rpx; margin-left: 30rpx; margin-right: 30rpx">
<uni-col :span="24">
<trq-depart-select v-model="selectedOrgCode" returnCodeOrID="orgCode" @change="onOrgCodeChange"></trq-depart-select>
</uni-col>
</uni-row>
<uni-row style="margin-bottom: 20rpx; margin-left: 30rpx; margin-right: 30rpx">
<uni-col :span="24">
<picker mode="selector" :range="fieldList" range-key="label" @change="onFieldChange">
<view class="picker">选择字段: {{ selectedFieldLabel }}</view>
</picker>
</uni-col>
</uni-row>
</view>
<!-- ECharts图表 -->
<view class="chart-container">
<l-echart ref="chart" @finished="initChart" />
</view>
<!-- 数据表格 -->
<uni-row style="margin-top: 10px; margin-left: 30rpx; margin-right: 30rpx" v-if="personnelList.length > 0">
<uni-col :span="3">
<view class="titleStyle">序号</view>
</uni-col>
<uni-col :span="5">
<view class="titleStyle">姓名</view>
</uni-col>
<uni-col :span="5">
<view class="titleStyle">性别</view>
</uni-col>
<uni-col :span="5">
<view class="titleStyle">年龄</view>
</uni-col>
<uni-col :span="6">
<view class="titleStyle">操作</view>
</uni-col>
</uni-row>
<scroll-view scroll-y :style="{ height: bottomHeight + 'px' }">
<uni-row style="margin-bottom: 10rpx; margin-left: 30rpx; margin-right: 30rpx">
<view v-for="(item, index) in personnelList">
<uni-col :span="3">
<view class="dataStyle">
{{ index + 1 }}
</view>
</uni-col>
<uni-col :span="5">
<view class="dataStyle">
{{ item.xm }}
</view>
</uni-col>
<uni-col :span="5">
<view class="dataStyle">
{{ item.xb_dictText }}
</view>
</uni-col>
<uni-col :span="5">
<view class="dataStyle">
{{ item.nl }}
</view>
</uni-col>
<uni-col :span="6">
<view class="dataStyle">
<button size="mini" type="primary" @click="detail(item)">详情</button>
</view>
</uni-col>
</view>
</uni-row>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import * as echarts from 'echarts';
import { cxcRyDatAstatistics, cxcRyDatAstatisticsDetails } from '@/api/renyuan.js';
// 存储下方组件的高度 tableData
const bottomHeight = ref(0);
// 新增加载状态
const chart = ref(null);
const fieldList = ref([
{
label: '性别',
value: 'xb'
},
{
label: '年龄',
value: 'nl'
},
{
label: '学历',
value: 'rylb1'
}
]); // 字段列表
const selectedOrgCode = ref(''); // 当前选择的单位 orgCode
const selectedOrgCodeLabel = ref('请选择单位'); // 当前选择的单位名称
const selectedField = ref(''); // 当前选择的字段
const selectedFieldLabel = ref('请选择字段'); // 当前选择的字段名称
const orgCodeGroupData = ref([]); //按照orgcode进行分组的数据
const chartData = ref({}); // 图表数据
const personnelList = ref([]); // 人员列表 initChart
const chartOption = ref({});
function detail(record) {
// console.log(record)
uni.navigateTo({
url: '/pages/views/renliziyuan/renyuanxinxi/detail?data=' + encodeURIComponent(JSON.stringify(record))
});
}
onMounted(() => {
// #ifdef APP
getHeight();
// #endif
});
// #ifdef APP
const getHeight = () => {
// 获取屏幕高度
const systemInfo = uni.getSystemInfoSync();
const screenHeight = systemInfo.screenHeight;
// 创建选择器查询对象
const query = uni.createSelectorQuery();
// 获取上方组件的高度
query
.select('#top1')
.boundingClientRect((rect1) => {
// 计算上方组件高度总和
const topComponentsHeight = rect1.height;
// 计算下方组件的高度
bottomHeight.value = screenHeight - topComponentsHeight - 415;
})
.exec();
};
// #endif
// 初始化 ECharts length departChange
// 初始化图表
const initChart = () => {
setTimeout(async () => {
if (!chart.value) return;
const myChart = await chart.value.init(echarts);
myChart.setOption(chartOption.value);
}, 300);
};
// 更新图表
const updateChart = () => {
if (!chart) return;
chartOption.value = transformToEChartsFormat(chartData.value);
// 初始化图表
setTimeout(async () => {
if (!chart.value) return;
const myChart = await chart.value.init(echarts);
myChart.setOption(chartOption.value);
myChart.on('click', (params) => {
try {
console.log(JSON.stringify(params.seriesIndex));
} catch (error) {
console.error('Error stringifying params:', error);
}
personnelList.value = fetchPersonnelList(params.seriesName);
});
}, 300);
};
//根据 orgCode 的分级格式,递归地汇总本级及其所有下级的数据,并将下级数据放在本级的 children 属性中。
const groupByOrgCode = (orgCode, data) => {
// 过滤出本级和所有下级的数据
const filteredData = data.filter((item) => item.orgCode.startsWith(orgCode));
// 如果过滤后的数据为空,返回 null
if (filteredData.length === 0) {
return null;
}
// 按照 fieldValue 分组
const groupedByFieldValue = {};
filteredData.forEach((item) => {
if (!groupedByFieldValue[item.fieldValue]) {
groupedByFieldValue[item.fieldValue] = {
number: 0,
ldhth: []
};
}
groupedByFieldValue[item.fieldValue].number += item.number;
groupedByFieldValue[item.fieldValue].ldhth.push(...item.ldhth.split(','));
});
// 构建本级结果
const result = {
orgCode: orgCode,
fieldValues: Object.keys(groupedByFieldValue).map((fieldValue) => ({
fieldValue: fieldValue,
number: groupedByFieldValue[fieldValue].number,
ldhth: [...new Set(groupedByFieldValue[fieldValue].ldhth)] // 去重
})),
children: []
};
// 获取所有下一级的 orgCode
const nextLevelOrgCodes = new Set();
filteredData.forEach((item) => {
if (item.orgCode !== orgCode && item.orgCode.startsWith(orgCode)) {
const nextLevelOrgCode = item.orgCode.substring(0, orgCode.length + 3);
nextLevelOrgCodes.add(nextLevelOrgCode);
}
});
// 递归处理下一级数据
nextLevelOrgCodes.forEach((nextLevelOrgCode) => {
const child = groupByOrgCode(nextLevelOrgCode, data);
if (child) {
result.children.push(child);
}
});
return result;
};
//转换分组数据 本级汇总及下一级的合集
const transformData = (selectOrgCode, inputData) => {
try {
// 存储本级数据
const currentLevelData = {};
// 存储下一级数据,按 orgCode 分组
const childrenData = {};
// 遍历输入数据
inputData.forEach((item) => {
const { orgCode, fieldValue, number, ldhth } = item;
// 提取本级 orgCode
const currentOrgCode = selectOrgCode;
// 检查是否为下一级 orgCode
if (orgCode.startsWith(currentOrgCode) && orgCode.length > currentOrgCode.length) {
// 提取下一级 orgCode
const childOrgCode = orgCode.slice(0, currentOrgCode.length + 3);
if (!childrenData[childOrgCode]) {
childrenData[childOrgCode] = {};
}
if (!childrenData[childOrgCode][fieldValue]) {
childrenData[childOrgCode][fieldValue] = { number: 0, ldhth: [] };
}
childrenData[childOrgCode][fieldValue].number += number;
childrenData[childOrgCode][fieldValue].ldhth.push(ldhth);
}
// 汇总本级数据
if (!currentLevelData[fieldValue]) {
currentLevelData[fieldValue] = { number: 0, ldhth: [] };
}
currentLevelData[fieldValue].number += number;
currentLevelData[fieldValue].ldhth.push(ldhth);
});
// 转换本级数据格式
const formattedCurrentLevelData = Object.keys(currentLevelData).map((fieldValue) => ({
fieldValue,
number: currentLevelData[fieldValue].number,
ldhth: currentLevelData[fieldValue].ldhth.join(',')
}));
// 转换下一级数据格式
const formattedChildrenData = Object.keys(childrenData).map((childOrgCode) => ({
orgCode: childOrgCode,
data: Object.keys(childrenData[childOrgCode]).map((fieldValue) => ({
fieldValue,
number: childrenData[childOrgCode][fieldValue].number,
ldhth: childrenData[childOrgCode][fieldValue].ldhth.join(',')
}))
}));
console.log({
orgCode: selectOrgCode,
data: formattedCurrentLevelData,
children: formattedChildrenData
});
// 组合最终结果
return {
orgCode: selectOrgCode,
data: formattedCurrentLevelData,
children: formattedChildrenData
};
} catch (error) {
console.log(error);
//TODO handle the exception
}
};
const transformToEChartsFormat = (data) => {
const result = {
xAxis: { data: [] },
legend: { data: [] },
series: []
};
// 用于存储不同orgCode的series数据
const seriesMap = {};
try {
data.forEach((item) => {
const orgCode = item.orgCode;
// 初始化当前orgCode对应的series数据
if (!seriesMap[orgCode]) {
seriesMap[orgCode] = { name: orgCode, type: 'bar', data: [] };
result.xAxis.data.push(orgCode);
}
item.data.forEach((field) => {
const fieldValue = field.fieldValue;
// 确保图例中包含当前fieldValue
if (!result.legend.data.includes(fieldValue)) {
result.legend.data.push(fieldValue);
}
// 确保seriesMap中的每个series都有对应fieldValue的位置
seriesMap[orgCode].data[fieldValue] = seriesMap[orgCode].data[fieldValue] || 0;
// 添加number到对应位置
seriesMap[orgCode].data[fieldValue] += field.number;
});
});
// 将seriesMap中的数据转换为正确的格式并添加到result.series中
for (let orgCode in seriesMap) {
// 转换seriesMap中的data为数组格式
seriesMap[orgCode].data = result.legend.data.map((value) => seriesMap[orgCode].data[value] || 0);
result.series.push(seriesMap[orgCode]);
}
} catch (error) {
//TODO handle the exception
}
const Option = {
title: {
text: '人员年龄分组统计',
padding: [0, 0, 0, 30]
},
toolbox: {
padding: [0, 30, 0, 0],
show: true,
feature: {
//工具配置项
restore: {
show: true //是否显示该工具
},
saveAsImage: {
show: true //是否显示该工具
}
}
},
legend: {
data: result.legend,
itemGap: 5,
padding: [0, 15, 0, 15],
y: 'bottom',
itemHeight: 8, //高
itemWidth: 8, //宽
type: 'scroll'
},
xAxis: {
type: 'category',
data: result.xAxis,
axisLabel: {
color: '#7F84B5',
fontWeight: 300,
interval: 0,
rotate: 0
},
padding: [0, 10, 0, 10],
axisTick: {
show: false //刻度线
},
axisLine: {
show: false //不显示坐标轴线
}
},
yAxis: [
{
show: true,
boundaryGap: false, //解决数据与线不对应问题
type: 'value',
// name: 'Budget (million USD)',
// data: this.yList,
minInterval: 1,
axisLabel: {
interval: 0
},
splitLine: {
show: true,
lineStyle: {
//背景网格线
type: 'dashed'
}
},
axisTick: {
show: true //刻度线
},
axisLine: {
show: false //不显示坐标轴线
}
}
],
series: result.series
};
return Option;
};
// 统计后台获取数据中单位的个数
const countUniqueOrgCodes = (data) => {
// 创建一个 Set 来存储唯一的 orgCode
const orgCodeSet = new Set();
// 遍历数据数组
for (let i = 0; i < data.length; i++) {
// 将每个数据项的 orgCode 添加到 Set 中
orgCodeSet.add(data[i].orgCode);
}
// 返回 Set 的大小,即不同 orgCode 的个数
return orgCodeSet.size;
};
// 生成随机颜色的辅助函数
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
//根据数据生成echart需要的option 一个单位的情况
const convertToEChartsOption = (data) => {
// 用于存储按 orgCode 和 fieldValue 分组的数据
const groupedData = {};
// 遍历原始数据
data.forEach((item) => {
const { orgCode, fieldValue, number } = item;
if (!groupedData[orgCode]) {
groupedData[orgCode] = {};
}
groupedData[orgCode][fieldValue] = number;
});
// 获取所有不同的 orgCode
const orgCodes = Object.keys(groupedData);
console.log(orgCodes, groupedData);
let labels = [];
let datasets = [];
let xDatas = [];
// 如果只有一个 orgCode生成单柱状图数据
if (orgCodes.length === 1) {
const singleOrgCode = orgCodes[0];
const singleOrgData = groupedData[singleOrgCode];
console.log('单个', singleOrgCode, singleOrgData);
labels = Object.keys(singleOrgData);
console.log('单个labels', labels);
xDatas = labels;
datasets = [
{
type: 'bar',
data: labels.map((label, index) => {
return {
value: singleOrgData[label],
itemStyle: {
color: ['#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE'][index]
}
};
})
}
];
console.log('单个的数据', xDatas, labels, datasets);
} else {
// 如果有多个 orgCode生成多柱状图数据
const allFieldValues = new Set();
orgCodes.forEach((orgCode) => {
const fieldValues = Object.keys(groupedData[orgCode]);
fieldValues.forEach((fieldValue) => allFieldValues.add(fieldValue));
});
const sortedFieldValues = Array.from(allFieldValues).sort();
console.log('多个数据', sortedFieldValues);
labels = sortedFieldValues;
xDatas = orgCodes;
datasets = orgCodes.map((orgCode) => {
const dataPoints = sortedFieldValues.map((fieldValue, index) => {
return {
value: groupedData[orgCode][fieldValue] || 0,
itemStyle: {
color: ['#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE'][index]
}
};
});
return {
name: orgCode,
type: 'bar',
data: dataPoints
};
});
}
console.log(labels, orgCodes, datasets);
// 构建 ECharts option 对象
const option = {
title: {
text: '人员年龄分组统计',
padding: [0, 0, 0, 30]
},
toolbox: {
padding: [0, 30, 0, 0],
show: true,
feature: {
//工具配置项
restore: {
show: true //是否显示该工具
},
saveAsImage: {
show: true //是否显示该工具
}
}
},
legend: {
data: labels,
itemGap: 5,
padding: [0, 15, 0, 15],
y: 'bottom',
itemHeight: 8, //高
itemWidth: 8, //宽
type: 'scroll'
},
xAxis: {
type: 'category',
data: xDatas,
axisLabel: {
color: '#7F84B5',
fontWeight: 300,
interval: 0,
rotate: 0
},
padding: [0, 10, 0, 10],
axisTick: {
show: false //刻度线
},
axisLine: {
show: false //不显示坐标轴线
}
},
yAxis: [
{
show: true,
boundaryGap: false, //解决数据与线不对应问题
type: 'value',
// name: 'Budget (million USD)',
// data: this.yList,
minInterval: 1,
axisLabel: {
interval: 0
},
splitLine: {
show: true,
lineStyle: {
//背景网格线
type: 'dashed'
}
},
axisTick: {
show: true //刻度线
},
axisLine: {
show: false //不显示坐标轴线
}
}
],
series: datasets
};
return option;
};
// 获取统计数据
const fetchStatisticsData = async () => {
if (!selectedOrgCode.value || !selectedField.value) return;
try {
const res = await cxcRyDatAstatistics({
orgCode: selectedOrgCode.value,
field: selectedField.value
});
console.log(res);
orgCodeGroupData.value = groupByOrgCode(selectedOrgCode.value, res);
console.log(orgCodeGroupData.value);
chartData.value = orgCodeGroupData.value.children;
// updateChart();
} catch (error) {
console.error('获取统计数据失败:', error);
}
};
// 获取人员列表 delimiter
const fetchPersonnelList = async (ldhthList) => {
try {
const res = await cxcRyDatAstatisticsDetails({
ldhth: ldhthList
});
console.log(res);
personnelList.value = res.data;
} catch (error) {
console.error('获取人员列表失败:', error);
}
};
// 事件处理
const onOrgCodeChange = (e, data) => {
selectedOrgCode.value = e;
console.log(data.value.title);
selectedOrgCodeLabel.value = data.value.title;
fetchStatisticsData();
};
const onFieldChange = (e) => {
const index = e.detail.value;
selectedField.value = fieldList.value[index].value;
selectedFieldLabel.value = fieldList.value[index].label;
fetchStatisticsData();
};
const onChartClick = (e) => {
const { ldhth } = chartData.value;
if (ldhth && ldhth.length > 0) {
fetchPersonnelList(ldhth);
}
};
</script>
<style scoped>
.container {
margin: 20, 20, 20, 20rpx;
}
.input-group {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
}
.input {
flex: 1;
border: 1rpx solid #ddd;
padding: 15rpx;
border-radius: 8rpx;
}
.query-btn {
background: #007aff;
color: white;
padding: 0 40rpx;
border-radius: 8rpx;
}
.stats-box {
display: flex;
justify-content: space-around;
margin: 30rpx 0;
padding: 20rpx;
background: #f8f8f8;
border-radius: 12rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.label {
font-size: 24rpx;
color: #666;
}
.value {
font-size: 36rpx;
font-weight: bold;
color: #0000ff;
}
.chart-container {
height: 800rpx;
margin-top: 20rpx;
}
.titleStyle {
font-size: 12px;
color: #747474;
line-height: 30px;
height: 30px;
background: #f2f9fc;
text-align: center;
vertical-align: middle;
border-left: 1px solid #919191;
border-bottom: 1px solid #919191;
}
/* 内容样式 */
.dataStyle {
max-font-size: 14px;
/* 最大字体限制 */
min-font-size: 10px;
/* 最小字体限制 */
font-size: 12px;
color: #00007f;
line-height: 30px;
height: 30px;
font-weight: 500;
text-align: center;
vertical-align: middle;
border-bottom: 1px solid #919191;
border-left: 1px solid #919191;
text-overflow: ellipsis;
}
</style>