feat: ✨ 控制台添加访问趋势统计
This commit is contained in:
35
src/api/stats.ts
Normal file
35
src/api/stats.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const STATS_BASE_URL = "/api/v1/stats";
|
||||
|
||||
class StatsAPI {
|
||||
static getVisitTrend(queryParams: VisitTrendQuery) {
|
||||
return request<any, VisitTrendVO>({
|
||||
url: `${STATS_BASE_URL}/visit-trend`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default StatsAPI;
|
||||
|
||||
/** 访问趋势视图对象 */
|
||||
export interface VisitTrendVO {
|
||||
/** 日期列表 */
|
||||
dates: string[];
|
||||
/** 浏览量(PV) */
|
||||
pvList: number[];
|
||||
/** 访客数(UV) */
|
||||
uvList: number[];
|
||||
/** IP数 */
|
||||
ipList: number[];
|
||||
}
|
||||
|
||||
/** 访问趋势查询参数 */
|
||||
export interface VisitTrendQuery {
|
||||
/** 开始日期 */
|
||||
startDate: string;
|
||||
/** 结束日期 */
|
||||
endDate: string;
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#FD8E66"/><path d="M377.745 354.306h63.05l-78.638 315.388h-63.05l78.638-315.388zm140.642 0h103.519c69.926 0 117.527 24.3 98.942 98.896-17.958 72.017-80.148 104.401-147.89 104.401H530.77L502.8 669.694h-63.05l78.637-315.388zm62.702 153.443c43.489 0 68.927-18.329 77.964-54.547 9.153-36.659-10.779-49.018-54.222-49.018h-35.823l-25.833 103.565h37.914z" fill="#FFF"/></svg>
|
||||
<svg t="1719764355721" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="34051" width="200" height="200"><path d="M512 0a512 512 0 1 1 0 1024A512 512 0 0 1 512 0z m199.094857 296.155429H327.68a49.737143 49.737143 0 0 0-49.737143 49.737142v280.356572a49.737143 49.737143 0 0 0 49.737143 49.810286h120.466286v68.754285H363.300571a17.115429 17.115429 0 0 0 0 34.230857h311.222858a17.115429 17.115429 0 0 0 0-34.230857H588.507429v-68.754285h122.587428a49.737143 49.737143 0 0 0 49.737143-49.810286V345.965714a49.737143 49.737143 0 0 0-49.737143-49.810285z m16.091429 35.328v310.637714H312.393143V331.483429h414.793143z m-35.181715 34.157714H346.697143v241.664h345.234286V365.641143z" fill="#00BC70" p-id="34052"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 764 B |
@@ -1 +0,0 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#FF822B"/><path d="M324.409 655.019c180.881 0 327.51-146.631 327.51-327.51 0-152.138-103.734-280.047-244.33-316.854C205.813 52.464 47.496 213.018 8.986 415.982c38.6 137.898 165.196 239.037 315.422 239.037z" fill="#FFF" fill-opacity=".2"/><path d="M512 1024c282.767 0 512-229.233 512-512 0-31.766-2.891-62.854-8.434-93.019-87.509-82.881-205.691-133.718-335.742-133.718-269.71 0-488.357 218.645-488.357 488.357 0 54.96 9.084 107.803 25.823 157.104C300.627 989.489 402.283 1024 512 1024z" fill="#FFF" fill-opacity=".15"/><path d="M732.536 756.566c36.39 0 65.89-29.5 65.89-65.89 0 36.39 29.502 65.89 65.889 65.89-17.054 0-65.89 29.503-65.89 65.89 0-36.387-29.5-65.89-65.889-65.89zM159.686 247.28c25.686 0 46.51-20.823 46.51-46.51 0 25.687 20.823 46.51 46.51 46.51-12.037 0-46.51 20.824-46.51 46.51 0-25.686-20.824-46.51-46.51-46.51z" fill="#FFF" fill-opacity=".5"/><path d="M206.195 333.323c8.563 0 15.504-6.94 15.504-15.503 0 8.562 6.94 15.503 15.503 15.503-4.012 0-15.503 6.941-15.503 15.504 0-8.563-6.941-15.504-15.504-15.504z" fill="#FFF" fill-opacity=".3"/><path d="M802.301 726.987c0 8.11-1.387 15.686-4.155 22.728-2.775 7.043-6.713 13.232-11.829 18.566-5.116 5.336-11.085 9.494-17.905 12.486-6.821 2.984-14.281 4.48-22.38 4.48H281.805c-8.1 0-15.773-1.496-23.019-4.48-7.247-2.992-13.641-7.15-19.183-12.486-5.542-5.334-9.912-11.523-13.108-18.566-3.198-7.042-4.796-14.618-4.796-22.728v-319.47c0-16.218 5.648-29.983 16.945-41.294 11.296-11.31 25.044-16.965 41.243-16.965h464.226c16.199 0 29.947 5.655 41.243 16.965 11.294 11.311 16.945 25.076 16.945 41.295v87.07h-145.15c-16.2 0-29.947 5.548-41.243 16.645-11.297 11.098-16.946 24.755-16.946 40.974.427 11.098 2.772 20.914 7.034 29.45 3.41 7.256 9.059 13.872 16.945 19.847 7.886 5.976 19.29 8.964 34.21 8.964H802.3v116.52zm-86.962-407.18H425.038c23.019-11.95 44.76-23.474 65.222-34.571a6020.558 6020.558 0 0 0 53.072-28.17c17.478-9.39 31.119-16.646 40.924-21.768 14.92-8.109 28.241-11.844 39.964-11.203 11.723.64 21.634 2.667 29.734 6.082 9.378 4.694 17.478 10.883 24.298 18.566l37.087 71.064zm-86.963 232.4c0-8.109 2.77-14.938 8.313-20.487 5.542-5.548 12.362-8.323 20.462-8.323s14.92 2.775 20.461 8.323c5.543 5.549 8.313 12.378 8.313 20.487 0 8.11-2.77 15.046-8.313 20.807-5.542 5.762-12.362 8.644-20.461 8.644-8.1 0-14.92-2.882-20.462-8.644-5.542-5.761-8.313-12.697-8.313-20.807z" fill="#FFF"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#F15BB5"/><path d="m772 432.8-7.9-63.3v-.2c-.1-1-.4-2.1-.7-3.1v-.3l-.2-.4-25.7-72.1c-.3-.7-.6-1.5-1-2.2-7-14.4-21.5-23.7-37.5-24h-24.1c-10 0-18.6 7.8-18.9 17.8-.4 10.5 8 19.1 18.4 19.1H699c1.9.3 3.4 1.4 4.2 3.1l13.5 37.9c.7 1.9.4 3.9-.7 5.6-1.1 1.6-3 2.6-5 2.6h-67.3c-3.5 0-6.2-2.8-6.2-6.2v-24.5c0-67.9-55.1-123-123-123s-123 55.1-123 123v24.5c0 3.5-2.8 6.2-6.2 6.2h-69.8c-3.4 0-6.2-2.8-6.2-6.2 0-.8.2-1.6.5-2.3l15.5-36.8c.7-2.1 2.5-3.5 4.7-3.9h24.1c10 0 18.6-7.8 18.9-17.8.4-10.5-8-19.1-18.4-19.1H330c-16.5.5-31.3 10.2-38.1 25.3l-30.6 72.1v.2c-.2.4-.3.8-.5 1.2v.4c-.3 1-.6 2.1-.7 3.1l-39 310.8c-6.4 50.5 29.5 96.7 80.1 103 3.8.5 7.7.7 11.5.7h94.5C514.8 718.5 680.7 597.9 772 432.8zM440.7 322.5c0-41 33.3-74.1 74.3-73.8 40.7.3 73.3 34.1 73.3 74.8V347c0 3.5-2.8 6.2-6.2 6.2H446.9c-3.5 0-6.2-2.8-6.2-6.2v-24.5zm152.7 257L514 662.4c-2.3 2.4-6.3 2.5-8.7.2l-.2-.2-79.4-82.9c-15.1-15.1-18.8-38.2-9.3-57.3 13.4-26.8 47.7-36.1 73.3-18.2 2.3 1.6 4.4 3.5 6.4 5.5l9.2 9.2c2.4 2.4 6.3 2.4 8.7 0l9.4-9.4c19.4-19.4 50.8-19.4 70.2 0 19.3 19.4 19.3 50.8-.2 70.2z" fill="#FFFDF3"/><path d="M803.7 691.6c0-3.8-.3-7.7-.7-11.4l-31-247.4c-91.3 165.1-257.2 285.7-364.8 351h304.1c51 0 92.4-41.2 92.4-92.2z" fill="#FFF" opacity=".9"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
1
src/assets/icons/pv.svg
Normal file
1
src/assets/icons/pv.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1719763848667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1470" width="200" height="200"><path d="M493.037037 18.128593C230.779259 18.128593 18.128593 230.779259 18.128593 493.037037S230.779259 967.945481 493.037037 967.945481 967.945481 755.294815 967.945481 493.037037 755.294815 18.128593 493.037037 18.128593z" fill="#8F7AF6" p-id="1471"></path><path d="M488.903111 285.278815c-145.256296 0-262.97837 171.728593-262.97837 200.362666 0 34.360889 117.76 200.400593 262.97837 200.400593 145.256296 0 263.016296-157.430519 263.016296-200.400593 0-28.634074-117.76-200.362667-263.016296-200.362666z m159.895704 294.001778c-29.316741 26.775704-89.050074 71.717926-159.895704 71.717926-65.232593 0-121.742222-39.594667-157.696-72.817778-43.880296-40.580741-65.611852-78.734222-70.807704-91.780741 6.33363-14.639407 31.364741-54.234074 71.907556-92.084148 36.02963-33.754074 92.463407-73.955556 156.634074-73.955556 64.094815 0 120.528593 40.201481 156.634074 73.879704 41.377185 38.760296 66.597926 79.113481 72.248889 93.032296-3.944296 13.50163-25.903407 52.565333-69.025185 92.008297z" fill="#F4F8FF" p-id="1472"></path><path d="M488.903111 360.410074a112.715852 112.715852 0 1 0 0 225.431704 112.715852 112.715852 0 0 0 0-225.431704zM458.903704 470.660741a32.616296 32.616296 0 1 1 0-65.232593 32.616296 32.616296 0 0 1 0 65.232593z" fill="#F4F8FF" p-id="1473"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/icons/uv.svg
Normal file
1
src/assets/icons/uv.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1719764607585" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="61790" width="200" height="200"><path d="M597.8 316.59c-8.82 0-17.64 1.26-25.2 3.78 18.9 27.72 28.98 60.48 28.98 97.03 0 39.06-13.86 76.87-37.8 108.37l-2.52 2.52v1.26c-2.52 2.52-6.3 6.3-7.56 10.08 0 1.26 0 1.26 1.26 1.26 2.52 1.26 6.3 2.52 10.08 2.52h1.26l5.04 2.52c51.66 16.38 95.77 45.36 124.75 81.91 12.6 16.38 20.16 35.28 20.16 55.44 0 11.34-2.52 20.16-6.3 30.24h36.54c28.98 0 51.66-21.42 51.66-49.14-1.26-13.86-5.04-23.94-12.6-32.76-21.42-26.46-54.18-49.14-95.77-61.74 0 0-16.38-3.78-21.42-6.3-8.82-6.3-13.86-16.38-13.86-27.72 0-12.6 10.08-22.68 16.38-28.98 16.38-20.16 26.46-46.62 26.46-76.87 0-61.76-44.1-113.42-99.54-113.42z m-142.4-27.72c-61.74 0-112.15 57.96-112.15 128.53 0 34.02 11.34 64.26 28.98 86.95 11.34 6.3 18.9 20.16 18.9 34.02 0 12.6-6.3 23.94-16.38 31.5v1.26c-1.26 0-2.52 1.26-2.52 1.26-6.3 3.78-12.6 6.3-20.16 6.3-47.88 16.38-88.21 41.58-112.15 73.09h1.26c-6.3 8.82-8.82 18.9-8.82 28.98 0 30.24 25.2 55.44 56.7 55.44h332.67c32.76 0 57.96-23.94 57.96-55.44 0-12.6-5.04-23.94-11.34-34.02-23.94-31.5-61.74-55.44-108.37-70.57 0 0-3.47-0.93-7.84-2.18l-9.15-2.78c-2.94-0.96-5.51-1.88-6.95-2.6-10.08-6.3-16.38-18.9-16.38-31.5 0-13.86 11.34-25.2 18.9-34.02 18.9-22.68 28.98-52.92 28.98-86.95 0.01-70.57-50.39-127.27-112.14-127.27zM511.95 65.2c247.06 0 447.33 200.28 447.33 447.33S759.01 959.87 511.95 959.87 64.62 759.59 64.62 512.53 264.9 65.2 511.95 65.2z" fill="#FF6800" p-id="61791"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
2
src/types/components.d.ts
vendored
2
src/types/components.d.ts
vendored
@@ -9,7 +9,7 @@ declare module "vue" {
|
||||
export interface GlobalComponents {
|
||||
AppLink: (typeof import("./../components/AppLink/index.vue"))["default"];
|
||||
AppMain: (typeof import("./../layout/components/AppMain/index.vue"))["default"];
|
||||
BarChart: (typeof import("./../views/dashboard/components/BarChart.vue"))["default"];
|
||||
VisitTrend: (typeof import("./../views/dashboard/components/VisitTrend.vue"))["default"];
|
||||
Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"];
|
||||
CURD: (typeof import("./../components/CURD/index.vue"))["default"];
|
||||
DeptTree: (typeof import("./../views/system/user/components/dept-tree.vue"))["default"];
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
<!-- 线 + 柱混合图 -->
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
业绩柱状图
|
||||
<el-tooltip effect="dark" content="点击试试下载" placement="bottom">
|
||||
<i-ep-download class="download" @click="downloadEchart" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :id="id" :class="className" :style="{ height, width }"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "barChart",
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const options = {
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
crossStyle: {
|
||||
color: "#999",
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
x: "center",
|
||||
y: "bottom",
|
||||
data: ["收入", "毛利润", "收入增长率", "利润增长率"],
|
||||
textStyle: {
|
||||
color: "#999",
|
||||
},
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["浙江", "北京", "上海", "广东", "深圳"],
|
||||
axisPointer: {
|
||||
type: "shadow",
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
min: 0,
|
||||
max: 10000,
|
||||
interval: 2000,
|
||||
axisLabel: {
|
||||
formatter: "{value} ",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
axisLabel: {
|
||||
formatter: "{value}%",
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "收入",
|
||||
type: "bar",
|
||||
data: [7000, 7100, 7200, 7300, 7400],
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: "#83bff6" },
|
||||
{ offset: 0.5, color: "#188df0" },
|
||||
{ offset: 1, color: "#188df0" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "毛利润",
|
||||
type: "bar",
|
||||
data: [8000, 8200, 8400, 8600, 8800],
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: "#25d73c" },
|
||||
{ offset: 0.5, color: "#1bc23d" },
|
||||
{ offset: 1, color: "#179e61" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "收入增长率",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
data: [60, 65, 70, 75, 80],
|
||||
itemStyle: {
|
||||
color: "#67C23A",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "利润增长率",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
data: [70, 75, 80, 85, 90],
|
||||
itemStyle: {
|
||||
color: "#409EFF",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const downloadEchart = () => {
|
||||
// 获取画布图表地址信息
|
||||
const img = new Image();
|
||||
img.src = chart.value.getDataURL({
|
||||
type: "png",
|
||||
pixelRatio: 1,
|
||||
backgroundColor: "#fff",
|
||||
});
|
||||
// 当图片加载完成后,生成 URL 并下载
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
||||
const link = document.createElement("a");
|
||||
link.download = `业绩柱状图.png`;
|
||||
link.href = canvas.toDataURL("image/png", 0.9);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const chart = ref<any>("");
|
||||
onMounted(() => {
|
||||
// 图表初始化
|
||||
chart.value = markRaw(
|
||||
echarts.init(document.getElementById(props.id) as HTMLDivElement)
|
||||
);
|
||||
|
||||
chart.value.setOption(options);
|
||||
|
||||
// 大小自适应
|
||||
window.addEventListener("resize", () => {
|
||||
chart.value.resize();
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (chart.value) {
|
||||
chart.value.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.download {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,115 +0,0 @@
|
||||
<!-- 漏斗图 -->
|
||||
<template>
|
||||
<div :id="id" :class="className" :style="{ height, width }"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "funnelChart",
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const options = {
|
||||
title: {
|
||||
show: true,
|
||||
text: "订单线索转化漏斗图",
|
||||
x: "center",
|
||||
padding: 15,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontStyle: "normal",
|
||||
fontWeight: "bold",
|
||||
color: "#337ecc",
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
x: "center",
|
||||
y: "bottom",
|
||||
data: ["Show", "Click", "Visit", "Inquiry", "Order"],
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
name: "Funnel",
|
||||
type: "funnel",
|
||||
left: "20%",
|
||||
top: 60,
|
||||
bottom: 60,
|
||||
width: "60%",
|
||||
sort: "descending",
|
||||
gap: 2,
|
||||
label: {
|
||||
show: true,
|
||||
position: "inside",
|
||||
},
|
||||
labelLine: {
|
||||
length: 10,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
type: "solid",
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: "#fff",
|
||||
borderWidth: 1,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{ value: 60, name: "Visit" },
|
||||
{ value: 40, name: "Inquiry" },
|
||||
{ value: 20, name: "Order" },
|
||||
{ value: 80, name: "Click" },
|
||||
{ value: 100, name: "Show" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chart = ref<any>("");
|
||||
|
||||
onMounted(() => {
|
||||
chart.value = markRaw(
|
||||
echarts.init(document.getElementById(props.id) as HTMLDivElement)
|
||||
);
|
||||
|
||||
chart.value.setOption(options);
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
chart.value.resize();
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (chart.value) {
|
||||
chart.value.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,89 +0,0 @@
|
||||
<!-- 饼图 -->
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header> 产品分类饼图 </template>
|
||||
<div :id="id" :class="className" :style="{ height, width }"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "pieChart",
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const options = {
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
top: "bottom",
|
||||
textStyle: {
|
||||
color: "#999",
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "Nightingale Chart",
|
||||
type: "pie",
|
||||
radius: [50, 130],
|
||||
center: ["50%", "50%"],
|
||||
roseType: "area",
|
||||
itemStyle: {
|
||||
borderRadius: 1,
|
||||
color: function (params: any) {
|
||||
//自定义颜色
|
||||
const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
|
||||
return colorList[params.dataIndex];
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{ value: 26, name: "家用电器" },
|
||||
{ value: 27, name: "户外运动" },
|
||||
{ value: 24, name: "汽车用品" },
|
||||
{ value: 23, name: "手机数码" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chart = ref<any>("");
|
||||
|
||||
onMounted(() => {
|
||||
chart.value = markRaw(
|
||||
echarts.init(document.getElementById(props.id) as HTMLDivElement)
|
||||
);
|
||||
|
||||
chart.value.setOption(options);
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
chart.value.resize();
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (chart.value) {
|
||||
chart.value.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,109 +0,0 @@
|
||||
<!-- 雷达图 -->
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header> 订单状态雷达图 </template>
|
||||
<div :id="id" :class="className" :style="{ height, width }"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "radarChart",
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const options = {
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
x: "center",
|
||||
y: "bottom",
|
||||
data: ["预定数量", "下单数量", "发货数量"],
|
||||
textStyle: {
|
||||
color: "#999",
|
||||
},
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
radius: "60%",
|
||||
indicator: [
|
||||
{ name: "家用电器" },
|
||||
{ name: "服装箱包" },
|
||||
{ name: "运动户外" },
|
||||
{ name: "手机数码" },
|
||||
{ name: "汽车用品" },
|
||||
{ name: "家具厨具" },
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "Budget vs spending",
|
||||
type: "radar",
|
||||
itemStyle: {
|
||||
borderRadius: 6,
|
||||
color: function (params: any) {
|
||||
//自定义颜色
|
||||
const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
|
||||
return colorList[params.dataIndex];
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [400, 400, 400, 400, 400, 400],
|
||||
name: "预定数量",
|
||||
},
|
||||
{
|
||||
value: [300, 300, 300, 300, 300, 300],
|
||||
name: "下单数量",
|
||||
},
|
||||
{
|
||||
value: [200, 200, 200, 200, 200, 200],
|
||||
name: "发货数量",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chart = ref<any>("");
|
||||
|
||||
onMounted(() => {
|
||||
chart.value = markRaw(
|
||||
echarts.init(document.getElementById(props.id) as HTMLDivElement)
|
||||
);
|
||||
|
||||
chart.value.setOption(options);
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
chart.value.resize();
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (chart.value) {
|
||||
chart.value.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
210
src/views/dashboard/components/VisitTrend.vue
Normal file
210
src/views/dashboard/components/VisitTrend.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<!-- 线 + 柱混合图 -->
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex-x-between">
|
||||
<div class="flex-y-center">
|
||||
访问趋势
|
||||
<el-tooltip effect="dark" content="点击试试下载" placement="bottom">
|
||||
<i-ep-download
|
||||
class="cursor-pointer hover:color-#409eff ml-2"
|
||||
@click="handleDownloadChart"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-radio-group
|
||||
v-model="dataRange"
|
||||
size="small"
|
||||
@change="handleDateRangeChange"
|
||||
>
|
||||
<el-radio-button label="近7天" :value="1" />
|
||||
<el-radio-button label="近30天" :value="2" />
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :id="id" :class="className" :style="{ height, width }"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from "echarts";
|
||||
import StatsAPI, { VisitTrendVO, VisitTrendQuery } from "@/api/stats";
|
||||
|
||||
const dataRange = ref(1);
|
||||
const chart: Ref<echarts.ECharts | null> = ref(null);
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "VisitTrend",
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "200px",
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
/** 设置图表 */
|
||||
const setChartOptions = (data: VisitTrendVO) => {
|
||||
if (!chart.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
legend: {
|
||||
data: ["浏览量(PV)", "IP"],
|
||||
bottom: 0,
|
||||
},
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: "7%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: data.dates,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "浏览量(PV)",
|
||||
type: "line",
|
||||
data: data.pvList,
|
||||
areaStyle: {
|
||||
color: "rgba(64, 158, 255, 0.3)",
|
||||
},
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: "#409EFF",
|
||||
},
|
||||
lineStyle: {
|
||||
color: "#409EFF",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IP",
|
||||
type: "line",
|
||||
data: data.ipList,
|
||||
areaStyle: {
|
||||
color: "rgba(103, 194, 58, 0.3)",
|
||||
},
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: "#67C23A",
|
||||
},
|
||||
lineStyle: {
|
||||
color: "#67C23A",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
chart.value.setOption(options);
|
||||
};
|
||||
|
||||
// 计算日期范围
|
||||
const calculateDateRange = () => {
|
||||
const now = new Date();
|
||||
const endDate = now.toISOString().split("T")[0];
|
||||
const days = dataRange.value === 1 ? 7 : 30;
|
||||
const startDate = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() - days
|
||||
)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
return { startDate, endDate };
|
||||
};
|
||||
|
||||
// 加载数据
|
||||
const loadData = () => {
|
||||
const { startDate, endDate } = calculateDateRange();
|
||||
StatsAPI.getVisitTrend({
|
||||
startDate,
|
||||
endDate,
|
||||
} as VisitTrendQuery).then((data) => {
|
||||
setChartOptions(data);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
// 下载图表
|
||||
const handleDownloadChart = () => {
|
||||
if (!chart.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取画布图表地址信息
|
||||
const img = new Image();
|
||||
img.src = chart.value.getDataURL({
|
||||
type: "png",
|
||||
pixelRatio: 1,
|
||||
backgroundColor: "#fff",
|
||||
});
|
||||
// 当图片加载完成后,生成 URL 并下载
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
||||
const link = document.createElement("a");
|
||||
link.download = `业绩柱状图.png`;
|
||||
link.href = canvas.toDataURL("image/png", 0.9);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
if (chart.value) {
|
||||
chart.value.resize();
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化图表
|
||||
onMounted(() => {
|
||||
chart.value = markRaw(
|
||||
echarts.init(document.getElementById(props.id) as HTMLDivElement)
|
||||
);
|
||||
loadData();
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
handleResize();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -41,7 +41,7 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 数据卡片 -->
|
||||
<el-row :gutter="10" class="mt-3">
|
||||
<el-row :gutter="10" class="mt-5">
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
@@ -55,7 +55,7 @@
|
||||
<span class="text-[var(--el-text-color-secondary)]">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<el-tag :type="item.tagType">
|
||||
<el-tag v-if="item.tagText" :type="item.tagType" size="small">
|
||||
{{ item.tagText }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -72,29 +72,16 @@
|
||||
class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
|
||||
>
|
||||
<span> {{ item.dataDesc }} </span>
|
||||
<span> {{ Math.round(item.count * 15) }} </span>
|
||||
<span> {{ item.totalCount }} </span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Echarts 图表 -->
|
||||
<el-row :gutter="10" class="mt-3">
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:lg="8"
|
||||
class="mb-2"
|
||||
v-for="item in chartData"
|
||||
:key="item"
|
||||
>
|
||||
<component
|
||||
:is="chartComponent(item)"
|
||||
:id="item"
|
||||
height="400px"
|
||||
width="100%"
|
||||
class="bg-[var(--el-bg-color-overlay)]"
|
||||
/>
|
||||
<el-row :gutter="10" class="mt-5">
|
||||
<el-col :xs="24" :span="24" class="mb-2">
|
||||
<VisitTrend id="VisitTrend" width="100%" height="450px" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -130,37 +117,37 @@ const greetings = computed(() => {
|
||||
|
||||
const duration = 5000;
|
||||
|
||||
// 销售额
|
||||
const amount = ref(0);
|
||||
const amountOutput = useTransition(amount, {
|
||||
// 在线用户数
|
||||
const onlineUserCount = ref(0);
|
||||
const onlineUserCountOutput = useTransition(onlineUserCount, {
|
||||
duration: duration,
|
||||
transition: TransitionPresets.easeOutExpo,
|
||||
});
|
||||
amount.value = 2000;
|
||||
onlineUserCount.value = 1;
|
||||
|
||||
// 浏览量
|
||||
const pvCount = ref(0);
|
||||
const pvCountOutput = useTransition(pvCount, {
|
||||
duration: duration,
|
||||
transition: TransitionPresets.easeOutExpo,
|
||||
});
|
||||
pvCount.value = 2000;
|
||||
|
||||
// 访客数
|
||||
const visitCount = ref(0);
|
||||
const visitCountOutput = useTransition(visitCount, {
|
||||
const uvCount = ref(0);
|
||||
const uvCountOutput = useTransition(uvCount, {
|
||||
duration: duration,
|
||||
transition: TransitionPresets.easeOutExpo,
|
||||
});
|
||||
visitCount.value = 2000;
|
||||
uvCount.value = 2000;
|
||||
|
||||
// IP数
|
||||
const dauCount = ref(0);
|
||||
const dauCountOutput = useTransition(dauCount, {
|
||||
const ipCount = ref(0);
|
||||
const ipCountOutput = useTransition(ipCount, {
|
||||
duration: duration,
|
||||
transition: TransitionPresets.easeOutExpo,
|
||||
});
|
||||
dauCount.value = 2000;
|
||||
|
||||
// 订单量
|
||||
const orderCount = ref(0);
|
||||
const orderCountOutput = useTransition(orderCount, {
|
||||
duration: duration,
|
||||
transition: TransitionPresets.easeOutExpo,
|
||||
});
|
||||
orderCount.value = 2000;
|
||||
ipCount.value = 2000;
|
||||
|
||||
// 右上角数量
|
||||
const statisticData = ref([
|
||||
@@ -194,49 +181,65 @@ interface CardProp {
|
||||
>;
|
||||
tagText: string;
|
||||
count: any;
|
||||
totalCount: any;
|
||||
dataDesc: string;
|
||||
iconClass: string;
|
||||
}
|
||||
// 卡片数量
|
||||
const cardData = ref<CardProp[]>([
|
||||
{
|
||||
title: "访客数",
|
||||
title: "在线用户",
|
||||
tagType: "success",
|
||||
tagText: "日",
|
||||
count: visitCountOutput,
|
||||
dataDesc: "总访客数",
|
||||
tagText: "-",
|
||||
count: onlineUserCountOutput,
|
||||
totalCount: "3",
|
||||
dataDesc: "总用户数",
|
||||
iconClass: "visit",
|
||||
},
|
||||
{
|
||||
title: "浏览量(PV)",
|
||||
tagType: "primary",
|
||||
tagText: "日",
|
||||
count: pvCountOutput,
|
||||
totalCount: 3000,
|
||||
dataDesc: "总浏览量",
|
||||
iconClass: "pv",
|
||||
},
|
||||
{
|
||||
title: "访客数(UV)",
|
||||
tagType: "danger",
|
||||
tagText: "日",
|
||||
count: uvCountOutput,
|
||||
totalCount: 3000,
|
||||
dataDesc: "总访客数",
|
||||
iconClass: "uv",
|
||||
},
|
||||
{
|
||||
title: "IP数",
|
||||
tagType: "success",
|
||||
tagText: "日",
|
||||
count: dauCountOutput,
|
||||
count: ipCountOutput,
|
||||
totalCount: 3000,
|
||||
dataDesc: "总IP数",
|
||||
iconClass: "ip",
|
||||
},
|
||||
]);
|
||||
|
||||
// 通知公告数据
|
||||
const notices = ref([
|
||||
{
|
||||
title: "销售额",
|
||||
tagType: "primary",
|
||||
tagText: "月",
|
||||
count: amountOutput,
|
||||
dataDesc: "总IP数",
|
||||
iconClass: "money",
|
||||
title: "系统更新",
|
||||
content: "系统将于今晚22:00进行更新,请提前保存好工作。",
|
||||
},
|
||||
{
|
||||
title: "订单量",
|
||||
tagType: "danger",
|
||||
tagText: "季",
|
||||
count: orderCountOutput,
|
||||
dataDesc: "总订单量",
|
||||
iconClass: "order",
|
||||
title: "假期通知",
|
||||
content: "国庆假期将于10月1日开始,请提前做好工作安排。",
|
||||
},
|
||||
{
|
||||
title: "紧急通知",
|
||||
content: "请所有员工注意,明天将进行紧急疏散演练。",
|
||||
},
|
||||
]);
|
||||
// 图表数据
|
||||
const chartData = ref(["BarChart", "PieChart", "RadarChart"]);
|
||||
const chartComponent = (item: string) => {
|
||||
return defineAsyncComponent(() => import(`./components/${item}.vue`));
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -29,14 +29,19 @@
|
||||
>
|
||||
<el-table-column label="操作时间" prop="createTime" width="180" />
|
||||
<el-table-column label="操作人" prop="operator" width="120" />
|
||||
<el-table-column label="日志模块" prop="module" width="120" />
|
||||
<el-table-column label="日志内容" prop="content" min-width="100" />
|
||||
<el-table-column label="日志模块" prop="module" width="100" />
|
||||
<el-table-column label="日志内容" prop="content" min-width="200" />
|
||||
<el-table-column label="IP 地址" prop="ip" width="150" />
|
||||
<el-table-column label="地区" prop="region" width="200" />
|
||||
<el-table-column label="地区" prop="region" width="150" />
|
||||
<el-table-column label="浏览器" prop="browser" width="150" />
|
||||
<el-table-column label="终端系统" prop="os" width="300" />
|
||||
<el-table-column
|
||||
label="执行时间(毫秒)"
|
||||
label="终端系统"
|
||||
prop="os"
|
||||
width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="执行时间(ms)"
|
||||
prop="executionTime"
|
||||
width="150"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user