数据报表

This commit is contained in:
梁泽军
2025-03-20 09:22:17 +08:00
parent 44504dc6eb
commit 203bb50212
11 changed files with 513 additions and 61 deletions

View File

@@ -64,3 +64,8 @@ export function feedbackPost(data: any) {
export function cancelled(data?: any) {
return request.post({ url: "/login/cancelled", data });
}
//获取插件数据
export function getCharts(data?: any) {
return request.post({ url: "/charts/getCharts", data });
}

View File

@@ -153,8 +153,12 @@
"setting": {
"urlCheck": false,
"es6": true,
"minified": true
"minified": true,
"use2dCanvas": true
},
"requiredBackgroundModes": [
"canvas"
],
"__usePrivacyCheck__": true,
"usingComponents": true
},

View File

@@ -10,6 +10,16 @@
"share": true
}
},
{
"path": "pages/overview/overview",
"style": {
"navigationBarTitleText": "数据",
"enablePullDownRefresh": false
},
"meta": {
"share": true
}
},
{
"path": "pages/skills/skills",
"style": {

View File

@@ -0,0 +1,172 @@
<template>
<div class="chart-wrapper">
<div ref="chartRef" :style="{
width: '100%',
height: containerHeight + 'px',
minHeight: '300px'
}" class="chart-container"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
import * as echarts from 'echarts'
const props = defineProps({
title: {
type: String,
default: '使用人数'
},
data: {
type: Array,
required: true
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '600px'
}
})
const chartRef = ref(null)
const chartInstance = ref(null)
// 动态计算容器高度(核心逻辑)
const containerHeight = computed(() => {
const minItemHeight = 40 // 每个条目的最小高度
const baseHeight = 150 // 标题+边距等固定高度
return Math.min(
props.data.length * minItemHeight + baseHeight,
props.data.length * minItemHeight + baseHeight,
)
})
const options = {
// 调整布局留白
title: {
text: props.title,
left: 'center',
top: '0%',
},
grid: {
left: '5%', // 给y轴标签留足空间
right: '10%',
containLabel: true
},
dataset: {
source: props.data
},
xAxis: {
type: 'value',
axisLabel: {
formatter: '{value} 次'
}
},
yAxis: {
type: 'category',
axisLabel: {
fontSize: 12,
margin: 15,
// 自动换行策略
formatter: value => {
const maxLineLength = 10 // 每行最多字符数
const regex = new RegExp(`(.{1,${maxLineLength}})`, 'g')
return value.match(regex)?.join('\n') || value
}
},
// 动态间距配置
boundaryGap: [0.1, 0.1] // 上下留白比例
},
series: [{
silent: true, // 禁用系列交互
type: 'bar',
barWidth: '60%',
itemStyle: {
color: params => params.data[1] > 0 ? '#37A2FF' : '#ccc',
borderRadius: [0, 5, 5, 0]
},
label: {
show: true,
position: 'right',
color: '#666'
}
}],
tooltip: {
trigger: 'axis',
formatter: params => {
return `${params[0].data[0]}<br/>使用次数: ${params[0].data[1]}`
}
},
}
// 初始化图表
const initChart = () => {
if (!chartRef.value) return
chartInstance.value = echarts.init(chartRef.value)
chartInstance.value.setOption(options)
// 绑定点击事件
chartInstance.value.on('click', handleClick)
}
// 点击事件处理
const handleClick = (params) => {
console.log('图表点击:', params)
}
// 窗口变化时自动调整
const handleResize = () => {
chartInstance.value?.resize()
}
onMounted(() => {
initChart()
chartRef.value.style.touchAction = 'none'
window.addEventListener('resize', updateSize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chartInstance.value?.dispose()
})
const updateSize = () => {
const width = chartRef.value.offsetWidth
chartOptions.value.grid = {
left: width < 600 ? '15%' : '20%',
right: '5%'
}
chartInstance.setOption(chartOptions.value)
}
// 监听配置变化
watch(() => props.data, (newVal) => {
chartInstance.value?.setOption(newVal)
}, { deep: true })
</script>
<style scoped>
.chart-wrapper {
touch-action: pan-y;
/* 允许垂直滚动 */
pointer-events: none;
/* 穿透点击事件 */
}
.chart-container {
pointer-events: auto;
/* 恢复图表区域事件 */
/* min-height: 300px; */
}
</style>

View File

@@ -0,0 +1,120 @@
<template>
<div ref="chartEl" :style="{ width, height }"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as echarts from 'echarts'
const props = defineProps({
title: {
type: String,
default: 'Pie Chart'
},
data: {
type: Array,
required: true,
validator: (val) => val.every(i => i.name && typeof i.value === 'number')
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '400px'
}
})
const chartEl = ref(null)
let chartInstance = null
// 确保ECharts正确初始化
const initChart = () => {
if (!chartEl.value) return
// 先销毁已有实例
if (chartInstance) {
chartInstance.dispose()
}
chartInstance = echarts.init(chartEl.value)
chartInstance.setOption({
title: {
text: props.title,
left: 'center',
top: '0%',
},
tooltip: {
trigger: 'item'
},
legend: {
top: '7%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
top: '20%',
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
// label: {
// show: false,
// position: 'center'
// },
emphasis: {
scale: true, // 启用放大效果
scaleSize: 8, // 放大尺寸像素值
itemStyle: {
shadowBlur: 15, // 增加阴影效果
shadowColor: 'rgba(0, 0, 0, 0.3)'
},
label: {
show: true,
fontSize: 20, // 减小强调文字尺寸
fontWeight: 'bold',
color: '#333',
formatter: '{b}\n{d}%' // 两行显示
}
},
selectedOffset: 10, // 选中项偏移量
labelLine: {
show: false
},
data: props.data
}
]
})
}
onMounted(() => {
// 延迟初始化确保DOM就绪
setTimeout(initChart, 0)
window.addEventListener('resize', initChart)
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
window.removeEventListener('resize', initChart)
})
// 数据变化时更新
watch(() => props.data, () => {
if (chartInstance) {
chartInstance.setOption({
series: [{
data: props.data
}]
})
}
}, { deep: true })
</script>

View File

@@ -0,0 +1,107 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<page-status :status="status">
<view class="ai-creation">
<view class="flex-1 min-h-0">
<scroll-view scroll-y class="h-full">
<VerticalPieChart :data="chartData4" title="部门使用次数" />
<VerticalBarChart :data="chartData" title="模板使用人数" />
<VerticalBarChart :data="chartData2" title="模板使用次数" />
<VerticalBarChart :data="chartData3" title="个人使用次数" />
</scroll-view>
</view>
</view>
</page-status>
<tabbar />
</template>
<script setup lang="ts">
import { getCharts } from '@/api/user'
import { onLoad, onPullDownRefresh, onShow } from '@dcloudio/uni-app'
import { ref } from 'vue'
import VerticalBarChart from './_components/chart.vue'
import VerticalPieChart from './_components/pie-chart.vue'
import { PageStatusEnum } from '@/enums/appEnums'
import { cloneDeep } from 'lodash-es'
const status = ref(PageStatusEnum.LOADING)
const queryParams = ref({
startTime: "2025-03-18 00:00:00",
endTime: "2025-03-19 00:00:00",
})
const data = ref<any[]>([])
const chartData = ref([])
const chartData2 = ref([])
const chartData3 = ref([])
const chartData4 = ref([
{ name: '分类A', value: 335 },
{ name: '分类B', value: 310 },
{ name: '分类C', value: 234 }
])
// 获取数据
const getData = async () => {
try {
const responseData = await getCharts(queryParams.value)
console.log('responseData', responseData);
chartData.value = responseData.modelUseUserTotal.map((item: any) => ([item.name, item.total]))
chartData2.value = responseData.modelVisitTotal.map((item: any) => ([item.name, item.total]))
chartData3.value = responseData.userUseTotalList.map((item: any) => ([item.name, item.total]))
chartData4.value = responseData.companyTotalList.map((item: any) => ({ name: item.name, value: item.total }))
status.value = PageStatusEnum.NORMAL
} catch (error) {
status.value = PageStatusEnum.ERROR
}
}
onLoad(() => {
getData()
})
//下拉状态
// const refresherStatus = ref(false)
// //下拉刷新
// const refresh = async () => {
// refresherStatus.value = true
// await getData()
// refresherStatus.value = false
// }
// const refreshDebounce = () => {
// uni.$u.debounce(refresh, 500)
// }
</script>
<style lang="scss">
page {
height: 100%;
}
.ai-creation {
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(44.7deg, #eaffff 0%, #faf6ff 50%, #f2f3ff 63%, #eaffff 100%);
background-size: cover;
}
</style>