数据报表

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

@ -2,6 +2,6 @@
VITE_MODE = dev
# 请求域名
# VITE_APP_BASE_URL='http://192.168.1.249:8084/ai-agent-server/'
VITE_APP_BASE_URL='https://info.xglpa.com/ai-agent-server'
VITE_APP_BASE_URL='http://192.168.1.249:8084/ai-agent-server'
# VITE_APP_BASE_URL='https://info.xglpa.com/ai-agent-server'

View File

@ -19,9 +19,11 @@
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build": "node scripts/publish.js",
"build:h5-test": "vite build --mode test",
"build:h5-prod": "vite build --mode production",
"build:app": "uni build -p app",
"build:custom": "uni build -p",
"build:h5": "uni build && node scripts/release.mjs -t h5 -o mobile",
"build:h5": "uni build && node scripts/release.mjs -t h5 -o h5",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
@ -33,7 +35,6 @@
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"build:prod": "uni build h5 mobile --mode production",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
@ -54,6 +55,7 @@
"@iktakahiro/markdown-it-katex": "4.0.1",
"@vueuse/core": "9.8.2",
"css-color-function": "1.3.3",
"echarts": "^5.6.0",
"github-markdown-css": "5.2.0",
"highlight.js": "11.0.0",
"howler": "2.2.4",
@ -107,4 +109,4 @@
"vite": "4.1.4",
"weapp-tailwindcss-webpack-plugin": "1.12.8"
}
}
}

View File

@ -1,46 +1,47 @@
import path from 'path'
import fsExtra from 'fs-extra'
import minimist from 'minimist'
const { existsSync, remove, copy } = fsExtra
const cwd = process.cwd()
import path from "path";
import fsExtra from "fs-extra";
import minimist from "minimist";
const { existsSync, remove, copy } = fsExtra;
const cwd = process.cwd();
const argv = minimist(process.argv.slice(2), {
alias: {
target: 't',
output: 'o'
}
})
alias: {
target: "t",
output: "o",
},
});
//打包发布路径,谨慎改动
const releaseRelativePath = `../public/${argv.output}`
const distPath = path.resolve(cwd, `dist/build/${argv.target}`)
const releasePath = path.resolve(cwd, releaseRelativePath)
const releaseRelativePath = `./output/${argv.output}`;
const distPath = path.resolve(cwd, `dist/build/${argv.target}`);
const releasePath = path.resolve(cwd, releaseRelativePath);
async function build() {
if (existsSync(releasePath)) {
await remove(releasePath)
}
console.log(
`文件正在复制dist/build/${argv.target} ==> ${releaseRelativePath}`
)
try {
await copyFile(distPath, releasePath)
} catch (error) {
console.log(`\n ${error}`)
}
console.log(
`文件已复制dist/build/${argv.target} ==> ${releaseRelativePath}`
)
existsSync(releasePath);
// if (existsSync(releasePath)) {
// await remove(releasePath);
// }
// console.log(
// `文件正在复制dist/build/${argv.target} ==> ${releaseRelativePath}`
// );
// try {
// await copyFile(distPath, releasePath);
// } catch (error) {
// console.log(`\n ${error}`);
// }
// console.log(
// `文件已复制dist/build/${argv.target} ==> ${releaseRelativePath}`
// );
}
function copyFile(sourceDir, targetDir) {
return new Promise((resolve, reject) => {
copy(sourceDir, targetDir, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
return new Promise((resolve, reject) => {
copy(sourceDir, targetDir, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
build()
build();

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>

View File

@ -1,4 +1,4 @@
import { defineConfig } from "vite";
import { defineConfig, loadEnv } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import tailwindcss from "tailwindcss";
import autoprefixer from "autoprefixer";
@ -24,22 +24,33 @@ if (!weappTailwindcssDisabled) {
}
// https://vitejs.dev/config/
export default defineConfig({
base: "https://cdn.xglpa.com/ai-agent-m/",
plugins: [
uni(),
uniRouter({
includes: ["style"],
}),
weappTailwindcssDisabled ? undefined : vwt(),
],
css: {
postcss: {
plugins: postcssPlugin,
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd());
console.log("env", env.VITE_MODE);
return {
// base: "https://cdn.xglpa.com/ai-agent-m/",
plugins: [
uni(),
uniRouter({
includes: ["style"],
}),
weappTailwindcssDisabled ? undefined : vwt(),
],
css: {
postcss: {
plugins: postcssPlugin,
},
},
},
server: {
port: 8991,
},
// base: "./",
server: {
port: 8991,
},
// 根据环境变量动态配置
build: {
outDir: `dist/${mode}`, // 输出到不同目录(如 dist/development、dist/test
assetsDir: "static",
minify: mode === "production" ? "terser" : false, // 生产环境压缩代码
},
};
});

View File

@ -3506,6 +3506,14 @@ dom-walk@^0.1.0:
resolved "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
echarts@^5.6.0:
version "5.6.0"
resolved "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz#2377874dca9fb50f104051c3553544752da3c9d6"
integrity sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==
dependencies:
tslib "2.3.0"
zrender "5.6.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz"
@ -5664,6 +5672,11 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz"
@ -6006,3 +6019,10 @@ z-paging@2.7.6:
version "2.7.6"
resolved "https://registry.npmjs.org/z-paging/-/z-paging-2.7.6.tgz"
integrity sha512-zwY7Y2DczBTq61XT69sreCqtwagZaAuGmYb0GjGEx7AgiV7yhxS8VtkaZMf7cI60Cf+8j2zENjV9yrbBwTPvLQ==
zrender@5.6.1:
version "5.6.1"
resolved "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz#e08d57ecf4acac708c4fcb7481eb201df7f10a6b"
integrity sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==
dependencies:
tslib "2.3.0"