feat: 接口对接

master
xjs 2025-05-23 11:39:05 +08:00
parent 2254df4e95
commit eba5899cd6
30 changed files with 1100 additions and 705 deletions

View File

@ -0,0 +1,39 @@
// timer.worker.js
let timer = null;
let interval = 1000;
self.addEventListener('message', (e) => {
switch (e.data.command) {
case 'start':
startTimer(e.data.interval);
break;
case 'stop':
stopTimer();
break;
case 'update':
interval = e.data.interval;
break;
}
});
// 关闭时的清理逻辑
self.addEventListener('close', () => {
stopTimer();
});
function startTimer(newInterval) {
interval = newInterval || interval;
stopTimer(); // 先停止已有定时器
self.postMessage({ type: 'tick', timestamp:performance.now() });
timer = setInterval(() => {
const timestamp = performance.now();
self.postMessage({ type: 'tick', timestamp });
}, interval);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}

View File

@ -1,205 +0,0 @@
import axios, { AxiosRequestConfig } from 'axios'
import { handleUrl } from './encryptUrl'
// 自定义判断元素类型JS
function toType(obj: any): string {
return {}.toString
.call(obj)
.match(/\s([a-zA-Z]+)/)![1]
.toLowerCase()
}
// 参数过滤函数
function filterNull(o: any) {
for (var key in o) {
if (o[key] === null) {
delete o[key]
}
if (toType(o[key]) === 'string') {
o[key] = o[key].trim()
} else if (toType(o[key]) === 'object') {
o[key] = filterNull(o[key])
} else if (toType(o[key]) === 'array') {
o[key] = filterNull(o[key])
}
}
return o
}
/*
https://cnodejs.org/api/v1 的接口,如果是其他接口
https://cnodejs.org/topic/5378720ed6e2d16149fa16bd
alert
*/
function apiAxios(
method: string,
url: string,
params: null | string | object,
success: any,
failure: any,
unEncrypt: boolean = false // 是否不加密
) {
let contentTypeIsJson = false
if (params && typeof params != 'string') {
params = filterNull(params)
} else contentTypeIsJson = true
axios({
method: method,
url: url,
data: method === 'POST' || method === 'PUT' ? params : null,
params: method === 'GET' || method === 'DELETE' ? params : null,
withCredentials: true,
crossDomain: true,
unEncrypt,
transformRequest: [
function (data) {
if (contentTypeIsJson) return data
let ret = ''
for (let it in data) {
ret +=
encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
},
],
headers: {
'Content-Type': contentTypeIsJson
? 'application/json'
: 'application/x-www-form-urlencoded',
},
} as AxiosRequestConfig<any>)
.then(function (res) {
let response = res.data
if (response.code == 200) {
if (success) {
success(response)
}
} else {
if (failure) {
failure(response)
} else {
if (response.code == 2) {
//错误处理
setTimeout(() => {
location.reload()
}, 1000)
} else {
//错误处理
}
}
}
})
.catch(function (err) {
let res = err.response
console.error(res || err)
if (res) {
// 清楚所有的错误提示
clearTimeout(timeObj)
if (res.data.msg) {
//错误处理
} else {
//错误处理
}
return
}
})
}
let requestCount = 0
let timeObj: NodeJS.Timeout
// http request 拦截器
axios.interceptors.request.use((config) => {
requestCount++
if (requestCount == 1) {
timeObj = setTimeout(() => {
//加载中提示
}, 800)
}
if (
config.data &&
Object.prototype.toString.call(config.data) == '[object FormData]'
) {
config.headers!!['Content-Type'] = 'multipart/form-data;charset=utf-8'
config.transformRequest = [
function (data) {
return data
},
]
}
// 拦截配置有新的配置在这里新增函数处理然后合并config
let _config = handleUrl(config)
config = Object.assign(config, _config)
return config
})
// http response 拦截器
axios.interceptors.response.use((response) => {
requestCount--
if (requestCount === 0) {
setTimeout(() => {
// 关闭所有提示
}, 1500)
clearTimeout(timeObj)
}
return response
},error =>{
// let xhrErrL = { type: "XHRERR", data: error.response };
if (error.response) {
const { status, data } = error.response;
if (status === 422) {
alert(data)
}
}
})
// 返回在vue模板中的调用接口
export default {
get: function (
url: string,
params: string | object | null,
success: any,
failure: any
) {
return apiAxios('GET', url, params, success, failure)
},
post: function (
url: string,
params: string | object,
success: any,
failure: any
) {
return apiAxios('POST', url, params, success, failure)
},
put: function (
url: string,
params: string | object,
success: any,
failure: any
) {
return apiAxios('PUT', url, params, success, failure)
},
delete: function (
url: string,
params: string | object,
success: any,
failure: any
) {
return apiAxios('DELETE', url, params, success, failure)
},
// 不加密的post请求
unEncryptPost: function (
url: string,
params: string | object,
success: any,
failure: any,
) {
return apiAxios('POST', url, params, success, failure,true)
}
}

62
src/api/customFetch.ts Normal file
View File

@ -0,0 +1,62 @@
/*
* @Author: HideInMatrix
* @Date: 2024-07-15
* @LastEditors: error: git config user.name & please set dead value or install git
* @LastEditTime: 2024-09-01
* @Description:
* @FilePath: /free-music-react/src/lib/customFetch.ts
*/
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
interface FetchOptions extends RequestInit {
headers?: Record<string, string>;
}
interface ApiResponse {
result?: unknown;
error?: string;
code?: number;
success?: boolean;
message?:string;
}
const apiClient = (method: HttpMethod) => {
return async (
url: string,
data?: unknown,
options: FetchOptions = {}
): Promise<ApiResponse> => {
const config: FetchOptions = {
method,
...options,
};
if (method !== "GET" && data) {
config.body = JSON.stringify(data);
} else if (method === "GET" && data) {
const _params = [];
for (const [key, value] of Object.entries(data)) {
_params.push(`${key}=${value}`);
}
url += `?${_params.join("&")}`;
}
const response = await fetch(`${url}`, config);
const result = await response.json();
if (!response.ok) {
return {
error: result.message || "Request failed",
code: response.status,
};
}
return result;
};
};
export const getRequest = apiClient("GET");
export const postRequest = apiClient("POST");
export const putRequest = apiClient("PUT");
export const deleteRequest = apiClient("DELETE");

View File

@ -1,12 +0,0 @@
// 对指定请求链接进行加密
import { AxiosRequestConfig } from "axios";
import { useUserStore } from "@/store/user";
export const handleUrl = async (config: (AxiosRequestConfig & {unEncrypt?: boolean})) => {
const userStore = useUserStore()
if(userStore.getToken){
config.headers!!["Authorization"] = `Bearer ${userStore.getToken}`
}
return config;
}

13
src/api/fetchUrl.ts Normal file
View File

@ -0,0 +1,13 @@
const baseUrl = "http://api.screen.jinze.ycymedu.com";
export const getPaymentUrl = () => `${baseUrl}/api/bigScreenData/bigScreenData`;
export const getGainUrl = () => `${baseUrl}/api/bigScreenData/bigScreenCustomerAcq`;
export const getAcqTrend = () => `${baseUrl}/api/bigScreenData/bigScreenAcqTrend`;
export const getSegmentStatic = () => `${baseUrl}/api/bigScreenData/bigScreenStages`;
export const getBigScreenRanking = () => `${baseUrl}/api/bigScreenData/bigScreenRanks`;
export const getSixStatisticsUrl = () => `${baseUrl}/api/bigScreenData/wechatData`

View File

@ -117,11 +117,11 @@ const getSegmentClass = (digit: string, index: number) => {
let timer: number | null = null; let timer: number | null = null;
let { hours, minutes, seconds, updateTime } = useDate() let { hours, minutes, seconds, formateTime } = useDate()
onMounted(() => { onMounted(() => {
updateTime(); formateTime();
timer = window.setInterval(updateTime, 500); timer = window.setInterval(formateTime, 500);
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@ -9,7 +9,7 @@ export const useDate = () => {
let timer: number | null = null; let timer: number | null = null;
const updateTime = () => { const formateTime = () => {
const now = new Date(); const now = new Date();
hours.value = now.getHours().toString().padStart(2, "0"); hours.value = now.getHours().toString().padStart(2, "0");
@ -35,6 +35,6 @@ export const useDate = () => {
day, day,
weekday, // 返回星期几 weekday, // 返回星期几
timer, timer,
updateTime, formateTime,
}; };
}; };

View File

@ -0,0 +1,59 @@
// 定义函数类型,支持任意参数
type PollingCallback = (...args: any[]) => void;
export const requestArray = new Map<string, { callback: PollingCallback; args?: any[] }>();
// 添加函数到 Map
export const addRequest = (key: string, callback: PollingCallback, args?: any[]) => {
requestArray.set(key, { callback, args });
};
// 从 Map 中移除函数
export const removeRequest = (key: string) => {
requestArray.delete(key);
};
export const runImmediatelyByKey = (key: string) => {
const { callback, args } = requestArray.get(key) || {};
if (callback) {
callback(...(args || []));
}
};
export const runImmediatelyAll = () => {
updateTime.value = new Date().getTime();
requestArray.forEach(({ callback, args }) => {
if (args) {
callback(...args);
}else{
callback()
}
});
};
export let updateTime = ref(0);
export const usePolling = () => {
const pollingWorker = new Worker("/js/polling.worker.js");
pollingWorker.onmessage = (_e: any) => {
// 执行所有存储的函数
updateTime.value = new Date().getTime();
requestArray.forEach(({ callback, args }) => {
if (args) {
callback(...args);
} else {
callback();
}
});
};
onMounted(() => {
pollingWorker.postMessage({ command: "start", interval: 1000 * 60 * 60 });
});
onBeforeUnmount(() => {
pollingWorker.postMessage({ command: "stop" });
// 清理所有存储的函数
requestArray.clear();
});
};

View File

@ -0,0 +1,13 @@
export function convertNumber(n: number): string {
if (n >= 10000) {
const value = n / 10000;
return `${value.toString().replace(/\.0$/, '')}W`;
} else if (n >= 1000) {
const value = n / 1000;
return `${value.toString().replace(/\.0$/, '')}K`;
} else if (n >= 1) {
const value = n / 1000;
return `${value.toString().replace(/0+$/, '').replace(/\.$/, '')}K`;
}
return n.toString();
}

34
src/utils/date.ts Normal file
View File

@ -0,0 +1,34 @@
/**
* "月.日 时:分:秒"
* @param timestamp
* @returns "6.12 12:00:00"
*/
export const formatDatetime = (timestamp: number): string => {
const date = new Date(timestamp);
// 获取月份注意getMonth 返回的月份是从0开始的所以需要+1
const month = date.getMonth() + 1;
// 获取日期
const day = date.getDate();
// 获取小时
const hours = date.getHours().toString().padStart(2, '0');
// 获取分钟
const minutes = date.getMinutes().toString().padStart(2, '0');
// 获取秒数
const seconds = date.getSeconds().toString().padStart(2, '0');
// 组合成最终格式
return `${month}.${day} ${hours}:${minutes}:${seconds}`;
};
/**
*
* @returns
*/
export const getCurrentTimestamp = (): number => {
return Date.now();
};

View File

@ -11,7 +11,7 @@
<SvgComponent :content="headerRightSvg" class="w-[669px] h-[36px]" /> <SvgComponent :content="headerRightSvg" class="w-[669px] h-[36px]" />
</div> </div>
<div class="w-full h-[calc(100%-36px)] mt-[36px]"> <div class="w-full h-[calc(100%-36px)] mt-[36px]">
<AskSectionChart /> <AskSectionChart :chart-data="askSectionData.stages || []" />
</div> </div>
</div> </div>
</template> </template>
@ -38,7 +38,10 @@
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw"); const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
arrowLeftSvg.value = svg; arrowLeftSvg.value = svg;
}; };
const askSectionData = inject("askSectionData",ref({stages:[]}))
onBeforeMount(() => { onBeforeMount(() => {
getHeaderLeftSvg(); getHeaderLeftSvg();
getHeaderRightSvg(); getHeaderRightSvg();

View File

@ -39,10 +39,28 @@
const columns = [ const columns = [
{ field: "rank", header: "名次", align: "justify-center", width: "68px" }, { field: "rank", header: "名次", align: "justify-center", width: "68px" },
{ field: "name", header: "姓名", align: "justify-left", width: "100px" }, { field: "name", header: "姓名", align: "justify-left", width: "100px" },
{ field: "Status", header: "获客人数", align: "justify-center", width: "96px" }, { field: "total", header: "收费人数", align: "justify-center", width: "96px" },
]; ];
const products = [{ name: "张三" }, { name: "张三" }, { name: "张三" }, { name: "张三" }, { name: "张三" }]; let products = ref<any[]>([]);
const chargingRankingData = inject(
"chargingRankingData",
ref<{
paymentRanks: any[];
}>({
paymentRanks: [],
}),
);
watch(
() => chargingRankingData.value,
() => {
if(chargingRankingData.value.paymentRanks){
products.value = chargingRankingData.value.paymentRanks
}
},
);
const headerLeftSvg = ref(""); const headerLeftSvg = ref("");
const headerRightSvg = ref(""); const headerRightSvg = ref("");

View File

@ -5,8 +5,8 @@
<div class="w-[144px]"> <div class="w-[144px]">
<div class="relative w-[144px] h-[113px] flex items-center flex-col"> <div class="relative w-[144px] h-[113px] flex items-center flex-col">
<div class="text-[#44C1EF] italic text-[20px] font-700">今日获客</div> <div class="text-[#44C1EF] italic text-[20px] font-700">今日获客</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10 font-700">
<span class="text-[34px] italic">125</span> <span class="text-[34px] italic">{{ gainData.toDayAcq || 0 }}</span>
<span class="text-[18px]"></span> <span class="text-[18px]"></span>
</div> </div>
<SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" /> <SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" />
@ -17,11 +17,11 @@
<SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" /> <SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" />
<div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center"> <div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center">
<p class="text-[#C0EEFF] text-[12px]">昨日获客人数</p> <p class="text-[#C0EEFF] text-[12px]">昨日获客人数</p>
<p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">177</p> <p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">{{ gainData.yestertDayAcq || 0 }}</p>
<div class="text-[#FF4E4E] text-[12px] flex items-center"> <div :class="`${gainDifferentTody > 0 ?'text-[#4AFFA2]':'text-[#FF4E4E]'} text-[12px] flex items-center`">
<span class="">今日较昨日</span> <span class="">今日较昨日</span>
<SvgIcon name="arrow-up" class="text-[9px]" /> <SvgIcon :name="gainDifferentTody > 0 ? 'arrow-up' : 'arrow-down'" class="text-[9px]" />
<span>12</span> <span>{{ Math.abs(gainDifferentTody) }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -34,6 +34,7 @@
import SvgIcon from "@/components/svg-icon/SvgIcon.vue"; import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
const gainData = inject("gainData",ref({toDayAcq:0,yestertDayAcq:0}))
const groupSvg = ref(""); const groupSvg = ref("");
const getGroupBackgroundSvg = async () => { const getGroupBackgroundSvg = async () => {
@ -41,6 +42,13 @@
groupSvg.value = svg; groupSvg.value = svg;
}; };
const gainDifferentTody = computed(() => {
if((Object.prototype.toString.call(gainData.value.toDayAcq) !== "[object Undefined]") && (Object.prototype.toString.call(gainData.value.yestertDayAcq) !== "[object Undefined]")){
return gainData.value.toDayAcq - gainData.value.yestertDayAcq
}else{
return 0;
}
})
const hexagonalBoxSvg = ref(""); const hexagonalBoxSvg = ref("");

View File

@ -5,17 +5,17 @@
<div class="w-[144px]"> <div class="w-[144px]">
<div class="relative w-[144px] h-[113px] flex items-center flex-col"> <div class="relative w-[144px] h-[113px] flex items-center flex-col">
<div class="text-[#44C1EF] italic text-[20px] font-700">总获客</div> <div class="text-[#44C1EF] italic text-[20px] font-700">总获客</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10 font-700">
<span class="text-[34px] italic">125</span> <span class="text-[34px] italic">{{gainData.acqTotal}}</span>
<span class="text-[18px]"></span> <span class="text-[18px]"></span>
</div> </div>
<SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" /> <SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" />
</div> </div>
</div> </div>
<div class="relative ml-[37px] flex flex-col items-center"> <div class="relative ml-[37px] flex flex-col items-center">
<span class="text-[#C0EEFF] text-[12px]">目标值2W</span> <span class="text-[#C0EEFF] text-[12px]">目标值{{ convertNumber(gainData.goalTotal || 0)}}</span>
<SvgComponent :content="powerSvg" class="w-[55px] h-[121px] z-1 my-[4px]" /> <SvgComponent :content="powerSvg" class="w-[55px] h-[121px] z-1 my-[4px]" />
<span class="text-[#769CBF] text-[12px]">当前值0.5W</span> <span class="text-[#769CBF] text-[12px]">当前值{{convertNumber(gainData.acqTotal ||0) }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -23,6 +23,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import SvgComponent from "@/components/SvgComponent.vue"; import SvgComponent from "@/components/SvgComponent.vue";
import {convertNumber} from "@/utils/convertNumber"
const groupSvg = ref(""); const groupSvg = ref("");
const getGroupBackgroundSvg = async () => { const getGroupBackgroundSvg = async () => {
@ -63,9 +64,13 @@
} }
}; };
watch(powerFlag, () => { const gainData = inject("gainData",ref({acqTotal:0,goalTotal:0}))
watch([() => powerFlag.value,()=>gainData.value], () => {
nextTick(() => { nextTick(() => {
setChargePercentageByPath(80); if(gainData.value.goalTotal){
setChargePercentageByPath(gainData.value.acqTotal/gainData.value.goalTotal*100);
}
}); });
}); });

View File

@ -5,8 +5,8 @@
<div class="w-[144px]"> <div class="w-[144px]">
<div class="relative w-[144px] h-[113px] flex items-center flex-col"> <div class="relative w-[144px] h-[113px] flex items-center flex-col">
<div class="text-[#44C1EF] italic text-[20px] font-700">今日流失</div> <div class="text-[#44C1EF] italic text-[20px] font-700">今日流失</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10 font-700">
<span class="text-[34px] italic">125</span> <span class="text-[34px] italic">{{ gainData.toDayOutFlow || 0 }}</span>
<span class="text-[18px]"></span> <span class="text-[18px]"></span>
</div> </div>
<SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" /> <SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" />
@ -17,12 +17,8 @@
<SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" /> <SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" />
<div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center"> <div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center">
<p class="text-[#C0EEFF] text-[12px]">总流失人数</p> <p class="text-[#C0EEFF] text-[12px]">总流失人数</p>
<p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">177</p> <p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">{{ gainData.outFlowTotal || 0 }}</p>
<div class="text-[#FF4E4E] text-[12px] flex items-center">
<span class="">今日较昨日</span>
<SvgIcon name="arrow-up" class="text-[9px]" />
<span>12</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -31,9 +27,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import SvgComponent from "@/components/SvgComponent.vue"; import SvgComponent from "@/components/SvgComponent.vue";
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
const gainData = inject('gainData',ref({toDayOutFlow:0,outFlowTotal:0}))
const groupSvg = ref(""); const groupSvg = ref("");
const getGroupBackgroundSvg = async () => { const getGroupBackgroundSvg = async () => {

View File

@ -1,102 +1,109 @@
<template> <template>
<div class="w-[296px] h-[320px] relative bg-[#082059]"> <div class="w-[296px] h-[320px] relative bg-[#082059]">
<div class="flex h-full custom-border absolute top-0 left-0"> <div class="flex h-full custom-border absolute top-0 left-0">
<div class="relative h-[36px]"> <div class="relative h-[36px]">
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center"> <div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
<SvgComponent :content="arrowLeftSvg" class="w-[15px] h-[18px]" /> <SvgComponent :content="arrowLeftSvg" class="w-[15px] h-[18px]" />
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[20px] ml-[9px] font-700">线下详情</div> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[20px] ml-[9px] font-700">线下详情</div>
</div>
<SvgComponent :content="headerLeftSvg" class="w-[188px] h-[36px]" />
</div> </div>
<SvgComponent :content="headerRightSvg" class="w-[108px] h-[36px]" /> <SvgComponent :content="headerLeftSvg" class="w-[188px] h-[36px]" />
</div>
<div class="w-full h-[calc(100%-36px)] mt-[36px] flex flex-col">
<div class="ml-[22px] relative mt-[13px]">
<div class="flex items-center justify-center">
<ProportionCharts :chart-data="chartData" class="z-2 relative" />
<SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-[50%] z-1 transform-translate-x-[-50%]" />
</div>
<div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
<div
class=" bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent flex items-baseline">
<div class="text-[25px] ">
690
</div>
<div class="text-[20px]"></div>
</div>
<div class="text-[#FFFFFF] text-[16px] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[7px]">线下</div>
</div>
</div>
<ul class="px-[24px] grid grid-cols-2 gap-x-[20px] gap-y-[16px] mt-[20px] leading-[1]">
<li class="flex items-center flex-col" v-for="item in chartData" :key="item.name">
<div class="flex items-center justify-between w-full">
<div class="w-[6px] h-[6px] rounded-full" :style="{ backgroundColor: item.color }"></div>
<div class="flex-1 flex items-center text-[#C0EEFF] text-[12px] justify-between">
<span class="ml-[4px] mr-[9px]">{{ item.name }}</span>
<span>{{ item.value }}</span>
</div>
</div>
<div class="border-image w-full mt-[6px]"></div>
</li>
</ul>
</div> </div>
<SvgComponent :content="headerRightSvg" class="w-[108px] h-[36px]" />
</div> </div>
</template> <div class="w-full h-[calc(100%-36px)] mt-[36px] flex flex-col">
<div class="ml-[22px] relative mt-[13px]">
<script setup lang="ts"> <div class="flex items-center justify-center">
import SvgComponent from "@/components/SvgComponent.vue"; <ProportionCharts :chart-data="chartData" class="z-2 relative" />
import ProportionCharts from "@/views/components/chartsComponents/ProportionCharts.vue"; <SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-[50%] z-1 transform-translate-x-[-50%]" />
</div>
const headerLeftSvg = ref(""); <div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
const headerRightSvg = ref(""); <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent flex items-baseline">
<div class="text-[25px]">690</div>
const getHeaderLeftSvg = async () => { <div class="text-[20px]"></div>
const { default: svg } = await import("/src/assets/svg-img/header-bg-left-sort.svg?raw"); </div>
headerLeftSvg.value = svg; <div class="text-[#FFFFFF] text-[16px] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[7px]">线下</div>
}; </div>
</div>
const getHeaderRightSvg = async () => { <ul class="px-[24px] grid grid-cols-2 gap-x-[20px] gap-y-[16px] mt-[20px] leading-[1]">
const { default: svg } = await import("/src/assets/svg-img/header-bg-right-sort.svg?raw"); <li class="flex items-center flex-col" v-for="item in chartData" :key="item.name">
headerRightSvg.value = svg; <div class="flex items-center justify-between w-full">
}; <div class="w-[6px] h-[6px] rounded-full" :style="{ backgroundColor: item.color }"></div>
<div class="flex-1 flex items-center text-[#C0EEFF] text-[12px] justify-between">
const paymentChartSvg = ref(""); <span class="ml-[4px] mr-[9px]">{{ item.name }}</span>
const getPaymentChartSvg = async () => { <span>{{ item.value }}</span>
const { default: svg } = await import("/src/assets/svg-img/payment-chart.svg?raw"); </div>
paymentChartSvg.value = svg; </div>
}; <div class="border-image w-full mt-[6px]"></div>
</li>
const arrowLeftSvg = ref(""); </ul>
const getArrowLeftSvg = async () => { </div>
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw"); </div>
arrowLeftSvg.value = svg; </template>
};
const chartData = ref([ <script setup lang="ts">
{ name: "类型A", value: 250, color: "#0783FA" }, import SvgComponent from "@/components/SvgComponent.vue";
{ name: "类型B", value: 274, color: "#07D1FA" }, import ProportionCharts from "@/views/components/chartsComponents/ProportionCharts.vue";
{ name: "类型C", value: 310, color: "#20E6A4" },
{ name: "类型D", value: 135, color: "#FFD15C" }, const headerLeftSvg = ref("");
{ name: "其他", value: 135, color: "#07D1FA" }, const headerRightSvg = ref("");
]);
const getHeaderLeftSvg = async () => {
onBeforeMount(() => { const { default: svg } = await import("/src/assets/svg-img/header-bg-left-sort.svg?raw");
getHeaderLeftSvg(); headerLeftSvg.value = svg;
getHeaderRightSvg(); };
getArrowLeftSvg();
getPaymentChartSvg(); const getHeaderRightSvg = async () => {
}); const { default: svg } = await import("/src/assets/svg-img/header-bg-right-sort.svg?raw");
</script> headerRightSvg.value = svg;
};
<style scoped lang="scss">
@use "@/styles/custom-border.scss"; const paymentChartSvg = ref("");
const getPaymentChartSvg = async () => {
.custom-border { const { default: svg } = await import("/src/assets/svg-img/payment-chart.svg?raw");
border-image-slice: 27 27 27 27; paymentChartSvg.value = svg;
border-image-width: 1px 1px 2px 1px; };
border-image-outset: 0px 0px 0px 0px;
border-image-repeat: stretch stretch; const arrowLeftSvg = ref("");
border-image-source: url("src/assets/svg-img/border-image.png"); const getArrowLeftSvg = async () => {
border-style: solid; const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
} arrowLeftSvg.value = svg;
</style> };
const colorList = ref(["#0783FA", "#07D1FA", "#20E6A4", "#FFD15C"]);
const askSectionData = inject("askSectionData", ref<{ offline: any[] }>({ offline: [] }));
const chartData = ref<any[]>([]);
watch(
() => askSectionData.value,
() => {
if (askSectionData.value.offline.length > 0) {
chartData.value = askSectionData.value.offline.map((item, index) => ({
name: item.tag,
value: item.total,
color: colorList.value[index % colorList.value.length],
}));
}
},
);
onBeforeMount(() => {
getHeaderLeftSvg();
getHeaderRightSvg();
getArrowLeftSvg();
getPaymentChartSvg();
});
</script>
<style scoped lang="scss">
@use "@/styles/custom-border.scss";
.custom-border {
border-image-slice: 27 27 27 27;
border-image-width: 1px 1px 2px 1px;
border-image-outset: 0px 0px 0px 0px;
border-image-repeat: stretch stretch;
border-image-source: url("src/assets/svg-img/border-image.png");
border-style: solid;
}
</style>

View File

@ -17,11 +17,8 @@
<SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-[50%] z-1 transform-translate-x-[-50%]" /> <SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-[50%] z-1 transform-translate-x-[-50%]" />
</div> </div>
<div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic"> <div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
<div <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent flex items-baseline">
class=" bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent flex items-baseline"> <div class="text-[25px]">690</div>
<div class="text-[25px] ">
690
</div>
<div class="text-[20px]"></div> <div class="text-[20px]"></div>
</div> </div>
<div class="text-[#FFFFFF] text-[16px] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[7px]">线上</div> <div class="text-[#FFFFFF] text-[16px] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[7px]">线上</div>
@ -36,7 +33,7 @@
<span>{{ item.value }}</span> <span>{{ item.value }}</span>
</div> </div>
</div> </div>
<div class="border-image w-full m-[6px]"></div> <div class="border-image w-full mt-[6px]"></div>
</li> </li>
</ul> </ul>
</div> </div>
@ -71,12 +68,22 @@
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw"); const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
arrowLeftSvg.value = svg; arrowLeftSvg.value = svg;
}; };
const chartData = ref([
{ name: "类型A", value: 250, color: "#0783FA" }, const colorList = ref(["#0783FA", "#07D1FA", "#20E6A4", "#FFD15C"]);
{ name: "类型B", value: 274, color: "#07D1FA" },
{ name: "类型C", value: 310, color: "#20E6A4" }, const askSectionData = inject("askSectionData", ref<{ online: any[] }>({ online: [] }));
{ name: "类型D", value: 135, color: "#FFD15C" },
]); const chartData = ref<any[]>([]);
watch(() => askSectionData.value, () => {
if (askSectionData.value.online.length > 0) {
chartData.value = askSectionData.value.online.map((item, index) => ({
name: item.tag,
value: item.total,
color: colorList.value[index % colorList.value.length],
}));
}
});
onBeforeMount(() => { onBeforeMount(() => {
getHeaderLeftSvg(); getHeaderLeftSvg();

View File

@ -1,24 +1,46 @@
<template> <template>
<div class="w-[926px] h-[358px] relative bg-[#082059]"> <div class="w-[926px] h-[358px] relative bg-[#082059]">
<div class="flex h-full custom-border absolute top-0 left-0"> <div class="flex h-full custom-border absolute top-0 left-0">
<div class="relative h-[36px]"> <div class="relative h-[36px]">
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center"> <div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
<SvgComponent :content="arrowLeftSvg" class="w-[15px] h-[18px]" /> <SvgComponent :content="arrowLeftSvg" class="w-[15px] h-[18px]" />
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[20px] ml-[9px] font-700">经营趋势</div> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[20px] ml-[9px] font-700">经营趋势</div>
</div>
<SvgComponent :content="headerLeftSvg" class="w-[255px] h-[36px]" />
</div> </div>
<SvgComponent :content="headerLeftSvg" class="w-[255px] h-[36px]" />
</div>
<SvgComponent :content="headerRightSvg" class="w-[669px] h-[36px]" /> <SvgComponent :content="headerRightSvg" class="w-[669px] h-[36px]" />
</div> </div>
<div class="w-full h-[calc(100%-36px)] mt-[36px]"> <div class="w-full h-[calc(100%-36px)] mt-[36px]">
<OperatingTrendsChart /> <OperatingTrendsChart :chartDataArray="acqTrend" @date-change="handleDateChange" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getRequest } from "@/api/customFetch";
import { getAcqTrend } from "@/api/fetchUrl";
import SvgComponent from "@/components/SvgComponent.vue"; import SvgComponent from "@/components/SvgComponent.vue";
import OperatingTrendsChart from "@/views/components/chartsComponents/OperatingTrendsChart.vue"; import OperatingTrendsChart from "@/views/components/chartsComponents/OperatingTrendsChart.vue";
import { addRequest,removeRequest,runImmediatelyByKey } from "@/composables/usePolling";
let DateType = 0;
const acqTrend = ref<any>([]);
const getAcqTrendData = (type: number) => {
getRequest(getAcqTrend(), { Type: type }, {}).then((resp) => {
if (resp.code === 200) {
acqTrend.value = resp.result;
}
});
};
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
const handleDateChange = (type: string) => {
DateType = type === "week" ? 0 : 1;
removeRequest("getAcqTrendData"),
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
runImmediatelyByKey("getAcqTrendData");
};
const headerLeftSvg = ref(""); const headerLeftSvg = ref("");
const headerRightSvg = ref(""); const headerRightSvg = ref("");

View File

@ -5,25 +5,25 @@
<div class="w-[216px]"> <div class="w-[216px]">
<div class="relative w-[154px] h-[124px] flex items-center flex-col"> <div class="relative w-[154px] h-[124px] flex items-center flex-col">
<div class="text-[#44C1EF] italic text-[20px] font-700">总缴费</div> <div class="text-[#44C1EF] italic text-[20px] font-700">总缴费</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10 font-700">
<span class="text-[40px] italic">1265</span> <span class="text-[40px] italic">{{paymentData.chargeTotal}}</span>
<span class="text-[20px]"></span> <span class="text-[20px]"></span>
</div> </div>
<SvgComponent :content="lightningBoxSvg" class="box-light absolute" /> <SvgComponent :content="lightningBoxSvg" class="box-light absolute" />
</div> </div>
<div> <div>
<div class="flex items-center justify-between text-[12px]"> <div class="flex items-center justify-between text-[12px]">
<div class="text-[#6B89BC]">当前值1265</div> <div class="text-[#6B89BC]">当前值{{paymentData.chargeTotal}}</div>
<div class="text-[#69D4FF]"> <div class="text-[#69D4FF]">
计划: 计划:
<span class="text-[15px]">4000</span> <span class="text-[15px]">{{paymentData.estimatedTotal}}</span>
<span class="text-[13px]"></span> <span class="text-[13px]"></span>
</div> </div>
</div> </div>
</div> </div>
<YProgress :percentage="25" height="12px" class="mt-[7px]" /> <YProgress :percentage="Math.round(paymentData.chargeTotal/paymentData.estimatedTotal * 100)" height="12px" class="mt-[7px]" />
</div> </div>
<div class="ml-[22px] relative"> <div class="ml-[4px] relative">
<ProportionCharts :chart-data="chartData" class="z-2 relative" /> <ProportionCharts :chart-data="chartData" class="z-2 relative" />
<SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-0 z-1" /> <SvgComponent :content="paymentChartSvg" class="w-[143px] h-[143px] absolute top-0 left-0 z-1" />
<div <div
@ -38,7 +38,7 @@
<div class="w-[6px] h-[6px] rounded-full" :style="{ backgroundColor: item.color }"></div> <div class="w-[6px] h-[6px] rounded-full" :style="{ backgroundColor: item.color }"></div>
<div class="flex-1 flex items-center text-[#C0EEFF] text-[12px] justify-between"> <div class="flex-1 flex items-center text-[#C0EEFF] text-[12px] justify-between">
<span class="ml-[4px] mr-[9px]">{{ item.name }}</span> <span class="ml-[4px] mr-[9px]">{{ item.name }}</span>
<span>{{ item.value }}</span> <span class="w-max break-keep">{{ item.value }}</span>
</div> </div>
<div class="border-image w-full mt-[6px]"></div> <div class="border-image w-full mt-[6px]"></div>
</li> </li>
@ -58,12 +58,19 @@
groupSvg.value = svg; groupSvg.value = svg;
}; };
const chartData = ref([ const paymentData = inject("paymentData",ref({chargeTotal:0,estimatedTotal:0,items:[]}))
{ name: "类型A", value: 250, color: "#0783FA" }, const colors = ["#0783FA","#07D1FA","#20E6A4","#FFD15C","#9A68FF"]
{ name: "类型B", value: 274, color: "#07D1FA" }, const chartData = ref<any>([]);
{ name: "类型C", value: 310, color: "#20E6A4" },
{ name: "类型D", value: 135, color: "#FFD15C" }, watchEffect(() => {
]); if(paymentData.value.items){
chartData.value = paymentData.value.items.map((item:any,index) => {
return {...item,color:colors[index % colors.length]}
})
}
})
const lightningBoxSvg = ref(""); const lightningBoxSvg = ref("");
const getLightningBoxSvg = async () => { const getLightningBoxSvg = async () => {

View File

@ -17,11 +17,11 @@
</div> </div>
<div class="flex items-baseline leading-[1] mt-[29px] mx-[27px]"> <div class="flex items-baseline leading-[1] mt-[29px] mx-[27px]">
<div class="text-[#44C1EF] italic text-[20px] font-700">总获客</div> <div class="text-[#44C1EF] italic text-[20px] font-700">总获客</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[40px] font-700 italic pr-[4px]">189</div> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[40px] font-700 italic pr-[4px]">{{ sixStatisticsData.total || 0 }}</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[18px] font-700"></div> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent text-[18px] font-700"></div>
<SvgIcon name="arrow-up" class="text-[9px] text-[#4AFFA2] ml-[9px]" /> <SvgIcon name="arrow-up" class="text-[9px] text-[#4AFFA2] ml-[9px]" />
</div> </div>
<SixStatisticsChart /> <SixStatisticsChart :chartData="sixStatisticsData.items ||[]"/>
</div> </div>
</div> </div>
</template> </template>
@ -56,6 +56,8 @@
moreArrowSvg.value = svg; moreArrowSvg.value = svg;
}; };
const sixStatisticsData = inject("sixStatisticsData",ref<{total:number,items:{data:string,total:number}[]}>({total:0,items:[]}))
onBeforeMount(() => { onBeforeMount(() => {
getHeaderLeftSvg(); getHeaderLeftSvg();
getHeaderRightSvg(); getHeaderRightSvg();

View File

@ -14,8 +14,8 @@
<StudentSourceChart <StudentSourceChart
class="w-full h-full" class="w-full h-full"
:chartData="[ :chartData="[
{ name: '线下', value: 240, itemStyle: { color: 'rgba(147, 219, 255, 1)' } }, { name: '线下', value: offlineTotal, itemStyle: { color: 'rgba(147, 219, 255, 1)' } },
{ name: '线上', value: 600, itemStyle: { color: 'rgb(79, 214, 169)' } }, { name: '线上', value: onlineTotal, itemStyle: { color: 'rgb(79, 214, 169)' } },
]" ]"
:ringSize="0.8" /> :ringSize="0.8" />
<div class="flex items-center justify-between mx-[20px] my-[33px]"> <div class="flex items-center justify-between mx-[20px] my-[33px]">
@ -23,7 +23,7 @@
<SvgComponent :content="onlineSvg" class="w-[53px] h-[38px]" /> <SvgComponent :content="onlineSvg" class="w-[53px] h-[38px]" />
<div class="flex flex-col ml-[9px] items-center"> <div class="flex flex-col ml-[9px] items-center">
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
<span class="text-[18px]">240</span> <span class="text-[18px]">{{onlineTotal }}</span>
<span class="text-[14px]"></span> <span class="text-[14px]"></span>
</div> </div>
<span class="text-[#C7F0FF] text-[12px]">线上来源</span> <span class="text-[#C7F0FF] text-[12px]">线上来源</span>
@ -33,7 +33,7 @@
<SvgComponent :content="offlineSvg" class="w-[53px] h-[38px]" /> <SvgComponent :content="offlineSvg" class="w-[53px] h-[38px]" />
<div class="flex flex-col ml-[9px] items-center"> <div class="flex flex-col ml-[9px] items-center">
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
<span class="text-[18px]">240</span> <span class="text-[18px]">{{ offlineTotal }}</span>
<span class="text-[14px]"></span> <span class="text-[14px]"></span>
</div> </div>
<span class="text-[#C7F0FF] text-[12px]">线下来源</span> <span class="text-[#C7F0FF] text-[12px]">线下来源</span>
@ -78,6 +78,19 @@
onlineSvg.value = svg onlineSvg.value = svg
} }
const askSectionData = inject("askSectionData",ref<{online:any[],offline:any[]}>({online:[],offline:[]}))
const onlineTotal = ref(0)
const offlineTotal = ref(0)
watch(() =>askSectionData.value,() => {
if(askSectionData.value.online.length > 0){
onlineTotal.value = askSectionData.value.online.reduce((acc, curr) => acc + curr.total, 0)
}
if(askSectionData.value.offline.length > 0){
offlineTotal.value = askSectionData.value.offline.reduce((acc, curr) => acc + curr.total, 0)
}
})
onBeforeMount(() => { onBeforeMount(() => {
getHeaderLeftSvg(); getHeaderLeftSvg();

View File

@ -5,8 +5,8 @@
<div class="w-[144px]"> <div class="w-[144px]">
<div class="relative w-[144px] h-[113px] flex items-center flex-col"> <div class="relative w-[144px] h-[113px] flex items-center flex-col">
<div class="text-[#44C1EF] italic text-[20px] font-700">今日缴费</div> <div class="text-[#44C1EF] italic text-[20px] font-700">今日缴费</div>
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10"> <div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent mb-[15px] z-10 font-700">
<span class="text-[34px] italic">125</span> <span class="text-[34px] italic">{{paymentData.toDayTotal || 0}}</span>
<span class="text-[18px]"></span> <span class="text-[18px]"></span>
</div> </div>
<SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" /> <SvgComponent :content="hexagonalBoxSvg" class="box-light absolute" />
@ -17,11 +17,12 @@
<SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" /> <SvgComponent :content="paymentBorderSvg" class="w-[127px] h-[104px] z-1" />
<div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center"> <div class="w-[127px] h-[104px] top-0 left-0 z-1 absolute pl-[14px] flex flex-col justify-center">
<p class="text-[#C0EEFF] text-[12px]">昨日缴费人数</p> <p class="text-[#C0EEFF] text-[12px]">昨日缴费人数</p>
<p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">177</p> <p class="text-[#C0EEFF] text-[16px] font-500 mt-[18px] mb-[9px]">{{paymentData.yesterdayTotal || 0}}</p>
<div class="text-[#FF4E4E] text-[12px] flex items-center"> <div :class="`${paymentDifferent > 0 ?'text-[#4AFFA2]': 'text-[#FF4E4E]'} text-[12px] flex items-center`">
<span class="">今日较昨日</span> <span class="">今日较昨日</span>
<SvgIcon name="arrow-up" class="text-[9px]" /> <SvgIcon :name="paymentDifferent > 0 ? 'arrow-up' : 'arrow-down'" class="text-[9px]" />
<span>12</span>
<span>{{Math.abs(paymentDifferent)}}</span>
</div> </div>
</div> </div>
</div> </div>
@ -33,6 +34,7 @@
import SvgComponent from "@/components/SvgComponent.vue"; import SvgComponent from "@/components/SvgComponent.vue";
import SvgIcon from "@/components/svg-icon/SvgIcon.vue"; import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
const paymentData = inject("paymentData",ref({toDayTotal:0,yesterdayTotal:0}))
const groupSvg = ref(""); const groupSvg = ref("");
@ -54,6 +56,14 @@
const { default: svg } = await import("/src/assets/svg-img/payment-border.svg?raw"); const { default: svg } = await import("/src/assets/svg-img/payment-border.svg?raw");
paymentBorderSvg.value = svg; paymentBorderSvg.value = svg;
}; };
const paymentDifferent = computed(() => {
if(Object.prototype.toString.call(paymentData.value.toDayTotal) !== "[object Undefined]" && Object.prototype.toString.call(paymentData.value.yesterdayTotal)!== "[object Undefined]"){
return paymentData.value.toDayTotal - paymentData.value.yesterdayTotal
}else{
return 0
}
})
onBeforeMount(() => { onBeforeMount(() => {
getGroupBackgroundSvg(); getGroupBackgroundSvg();

View File

@ -41,10 +41,27 @@
const columns = [ const columns = [
{ field: "rank", header: "名次", align: "justify-center",width:'68px' }, { field: "rank", header: "名次", align: "justify-center",width:'68px' },
{ field: "name", header: "姓名", align: "justify-left",width:'100px' }, { field: "name", header: "姓名", align: "justify-left",width:'100px' },
{ field: "Status", header: "获客人数", align: "justify-center",width:'96px' }, { field: "total", header: "获客人数", align: "justify-center",width:'96px' },
]; ];
const products = [{ name: "张三" },{ name: "张三" },{ name: "张三" },{ name: "张三" },{ name: "张三" }]; let products = ref<any[]>([]);
const chargingRankingData = inject(
"chargingRankingData",
ref<{
acqRanks: any[];
}>({
acqRanks: [],
}),
);
watch(
() => chargingRankingData.value,
() => {PageTransitionEvent
if(chargingRankingData.value.acqRanks){
products.value = chargingRankingData.value.acqRanks
}
},
);
const headerLeftSvg = ref(""); const headerLeftSvg = ref("");
const headerRightSvg = ref(""); const headerRightSvg = ref("");

View File

@ -4,7 +4,16 @@
<script setup lang="ts"> <script setup lang="ts">
import * as echarts from "echarts"; import * as echarts from "echarts";
import { ref, onMounted, onUnmounted } from "vue"; import { ref, onMounted, onUnmounted, defineProps, watch } from "vue";
interface DataItem {
tag: string;
total: number;
}
const props = defineProps<{
chartData: DataItem[];
}>();
const renderItem = (params: any, api: { coord: (arg0: any[]) => any[]; value: (arg0: number) => any; style: () => any }) => { const renderItem = (params: any, api: { coord: (arg0: any[]) => any[]; value: (arg0: number) => any; style: () => any }) => {
// //
@ -85,173 +94,232 @@
let myChart: echarts.ECharts | null = null; let myChart: echarts.ECharts | null = null;
const initChart = () => { const initChart = () => {
if (chartDom.value) { if (!chartDom.value) return;
myChart = echarts.init(chartDom.value);
// myChart = echarts.init(chartDom.value);
const WIDTH = 10; //
// const WIDTH = 10;
const OBLIQUE_ANGLE_HEIGHT = 3.5; //
const leftShape = echarts.graphic.extendShape({ const OBLIQUE_ANGLE_HEIGHT = 3.5;
buildPath(ctx, shape) { const leftShape = echarts.graphic.extendShape({
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape; buildPath(ctx, shape) {
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
const p2 = [basicsXAxis - WIDTH, bottomYAxis]; const p2 = [basicsXAxis - WIDTH, bottomYAxis];
const p3 = [basicsXAxis, bottomYAxis]; const p3 = [basicsXAxis, bottomYAxis];
const p4 = [basicsXAxis, topBasicsYAxis]; const p4 = [basicsXAxis, topBasicsYAxis];
ctx.moveTo(p1[0], p1[1]); ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p2[0], p2[1]);
ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p3[0], p3[1]);
ctx.lineTo(p4[0], p4[1]); ctx.lineTo(p4[0], p4[1]);
}, },
}); });
const rightShape = echarts.graphic.extendShape({ const rightShape = echarts.graphic.extendShape({
buildPath(ctx, shape) { buildPath(ctx, shape) {
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape; const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
const p1 = [basicsXAxis, topBasicsYAxis]; const p1 = [basicsXAxis, topBasicsYAxis];
const p2 = [basicsXAxis, bottomYAxis]; const p2 = [basicsXAxis, bottomYAxis];
const p3 = [basicsXAxis + WIDTH, bottomYAxis]; const p3 = [basicsXAxis + WIDTH, bottomYAxis];
const p4 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p4 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
ctx.moveTo(p1[0], p1[1]); ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p2[0], p2[1]);
ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p3[0], p3[1]);
ctx.lineTo(p4[0], p4[1]); ctx.lineTo(p4[0], p4[1]);
}, },
}); });
const topShape = echarts.graphic.extendShape({ const topShape = echarts.graphic.extendShape({
buildPath(ctx, shape) { buildPath(ctx, shape) {
const { topBasicsYAxis, basicsXAxis } = shape; const { topBasicsYAxis, basicsXAxis } = shape;
const p1 = [basicsXAxis, topBasicsYAxis]; const p1 = [basicsXAxis, topBasicsYAxis];
const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2]; const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2];
const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
ctx.moveTo(p1[0], p1[1]); ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p2[0], p2[1]);
ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p3[0], p3[1]);
ctx.lineTo(p4[0], p4[1]); ctx.lineTo(p4[0], p4[1]);
}, },
}); });
const middleShape = echarts.graphic.extendShape({ const middleShape = echarts.graphic.extendShape({
buildPath(ctx, shape) { buildPath(ctx, shape) {
const { topBasicsYAxis, basicsXAxis, bottomYAxis } = shape; const { topBasicsYAxis, basicsXAxis, bottomYAxis } = shape;
const p1 = [basicsXAxis - 0.5, topBasicsYAxis]; const p1 = [basicsXAxis - 0.5, topBasicsYAxis];
const p2 = [basicsXAxis - 0.5, bottomYAxis]; const p2 = [basicsXAxis - 0.5, bottomYAxis];
const p3 = [basicsXAxis + 0.5, bottomYAxis]; const p3 = [basicsXAxis + 0.5, bottomYAxis];
const p4 = [basicsXAxis + 0.5, topBasicsYAxis]; const p4 = [basicsXAxis + 0.5, topBasicsYAxis];
ctx.moveTo(p1[0], p1[1]); ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p2[0], p2[1]);
ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p3[0], p3[1]);
ctx.lineTo(p4[0], p4[1]); ctx.lineTo(p4[0], p4[1]);
}, },
}); });
echarts.graphic.registerShape("leftShape", leftShape); echarts.graphic.registerShape("leftShape", leftShape);
echarts.graphic.registerShape("rightShape", rightShape); echarts.graphic.registerShape("rightShape", rightShape);
echarts.graphic.registerShape("topShape", topShape); echarts.graphic.registerShape("topShape", topShape);
echarts.graphic.registerShape("middleShape", middleShape); echarts.graphic.registerShape("middleShape", middleShape);
const options = { // updateChartData
grid: { const options = {
top: "12%", grid: {
left: "5%", top: "12%",
right: "3%", left: "5%",
bottom: "15%", right: "3%",
}, bottom: "15%",
xAxis: { },
type: "category", xAxis: {
data: [1, 2, 3, 4, 5], type: "category",
axisLabel: { data: [],
formatter: (value: any) => value, axisLabel: {
color: "#C0EEFF", formatter: (value: any) => {
fontSize: 14, if (value.length <= 4) return value;
const arr = [];
for (let i = 0; i < value.length; i += 4) {
arr.push(value.slice(i, i + 4));
}
return arr.join('\n');
}, },
nameTextStyle: { color: "#C0EEFF",
fontSize: 14,
interval: 0,
},
nameTextStyle: {
color: "#243174",
},
axisLine: {
lineStyle: {
color: "#243174", color: "#243174",
}, },
axisLine: { },
lineStyle: { },
color: "#243174", yAxis: {
}, name: "人数",
nameLocation: "end",
nameTextStyle: {
color: "#C0EEFF",
fontSize: 14,
padding: [0, 5, 0, 0],
align: "right",
},
type: "value",
min: 0,
max: 100,
interval: 20,
axisLabel: {
color: "#C0EEFF",
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: "#243174",
}, },
}, },
yAxis: { splitLine: {
name: "人数", lineStyle: {
nameLocation: "end", type: "dashed",
data: [100, 200, 300, 400], color: "#308EFF",
nameTextStyle: { width: 1,
color: "#C0EEFF", opacity: 0.2,
fontSize: 14,
padding: [0, 5, 0, 0],
align: "right",
}, },
type: "value", },
min: 0, },
max: 500, series: [
interval: 100, {
axisLabel: { type: "custom",
color: "#C0EEFF", renderItem: renderItem,
fontSize: 14, color: "blue",
}, data: [],
axisLine: { },
{
type: "bar",
label: {
show: true, show: true,
lineStyle: { position: "top",
color: "#243174", fontSize: 12,
}, color: "rgba(192, 238, 255, 1)",
}, },
splitLine: { tooltip: {
lineStyle: { show: false,
type: "dashed",
color: "#308EFF",
width: 1,
opacity: 0.2,
},
}, },
itemStyle: {
color: "transparent",
},
data: [],
}, },
series: [ ],
{ };
type: "custom",
renderItem: renderItem,
color: "blue",
data: [100, 200, 300, 400],
},
{
type: "bar",
label: {
show: true,
position: "top",
fontSize: 12,
color: "rgba(192, 238, 255, 1)",
},
tooltip: {
show: false,
},
itemStyle: {
color: "transparent",
},
data: [100, 200, 300, 400],
},
],
};
myChart.setOption(options); myChart.setOption(options);
window.addEventListener("resize", handleResize); //
if (props.chartData && props.chartData.length > 0) {
updateChartData();
} }
window.addEventListener("resize", handleResize);
};
//
const updateChartData = () => {
if (!myChart || !props.chartData) return;
const chartData = props.chartData || [];
//
const tags = chartData.map(item => item.tag);
const values = chartData.map(item => item.total);
// y
const maxValue = values.length > 0 ? Math.max(...values) : 0;
const yAxisMax = maxValue > 0 ? Math.ceil(maxValue / 100) * 100 : 100;
const yAxisInterval = Math.ceil(yAxisMax / 5 / 100) * 100 || 20;
myChart.setOption({
xAxis: {
data: tags,
},
yAxis: {
min: 0,
max: yAxisMax,
interval: yAxisInterval,
},
series: [
{
data: values,
},
{
data: values,
},
],
});
}; };
const handleResize = () => { const handleResize = () => {
myChart?.resize(); myChart?.resize();
}; };
//
watch(
() => props.chartData,
() => {
if (myChart) {
updateChartData();
}
},
{ deep: true }
);
onMounted(() => { onMounted(() => {
initChart(); initChart();
}); });

View File

@ -3,20 +3,136 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from "vue"; import { onMounted, ref, watch, computed } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
const chartDom = ref(null); const chartDom = ref(null);
let myChart: echarts.ECharts | null = null; let myChart: echarts.ECharts | null = null;
// const props = defineProps({
const chartData = ref({ chartDataArray: {
dates: ["5.24", "5.25", "5.26", "5.27", "5.28", "5.29", "5.30"], type: Array,
seriesData: [ default: () => []
{ name: "获客", data: [180, 230, 200, 280, 250, 300, 350], color: "#20E6A4" }, },
{ name: "缴费", data: [150, 210, 180, 240, 200, 260, 320], color: "#0783FA" }, });
{ name: "流失", data: [120, 380, 220, 350, 300, 280, 400], color: "#FFD15C" },
], const emits = defineEmits(["dateChange"]);
//
interface ChartDataItem {
date: string;
value: number;
}
interface ChartTypeData {
type: number;
items: ChartDataItem[];
}
interface SeriesDataItem {
name: string;
data: number[];
color: string;
}
interface ChartDataFormat {
dates: string[];
seriesData: SeriesDataItem[];
}
//
const processChartData = (dataArray: ChartTypeData[], mode: string): ChartDataFormat => {
if (!dataArray || dataArray.length === 0) return {
dates: [],
seriesData: [
{ name: "获客", data: [], color: "#20E6A4" },
{ name: "缴费", data: [], color: "#0783FA" },
{ name: "流失", data: [], color: "#FFD15C" },
],
};
//
let allDates = new Set<string>();
dataArray.forEach((item: ChartTypeData) => {
if (item.items && item.items.length > 0) {
item.items.forEach((dateItem: ChartDataItem) => {
if (dateItem.date) {
allDates.add(dateItem.date);
}
});
}
});
//
let sortedDates = Array.from(allDates).sort((a: string, b: string) => {
// MM-DD
const [monthA, dayA] = a.split('-').map(Number);
const [monthB, dayB] = b.split('-').map(Number);
if (monthA !== monthB) return monthA - monthB;
return dayA - dayB;
});
//
if (mode === "week") {
// 7
if (sortedDates.length > 7) {
sortedDates = sortedDates.slice(-7);
}
}
// 30
else if (mode === "month") {
// 3030
if (sortedDates.length > 30) {
sortedDates = sortedDates.slice(-30);
}
//
}
//
const formattedDates = sortedDates.map((date: string) => {
const [month, day] = date.split('-');
return `${month}.${day}`;
});
//
const seriesData: SeriesDataItem[] = [
{ name: "获客", data: [], color: "#20E6A4" },
{ name: "缴费", data: [], color: "#0783FA" },
{ name: "流失", data: [], color: "#FFD15C" },
];
//
sortedDates.forEach((date: string) => {
//
dataArray.forEach((typeData: ChartTypeData, typeIndex: number) => {
if (typeIndex < seriesData.length) {
const dateItem = typeData.items.find(item => item.date === date);
seriesData[typeIndex].data.push(dateItem ? dateItem.value : 0);
}
});
});
return {
dates: formattedDates,
seriesData,
};
};
// 使computedprops
const chartData = computed((): ChartDataFormat => {
if (props.chartDataArray && props.chartDataArray.length > 0) {
return processChartData(props.chartDataArray as ChartTypeData[], currentMode.value);
}
// 使
return {
dates: ["5.24", "5.25", "5.26", "5.27", "5.28", "5.29", "5.30"],
seriesData: [
{ name: "获客", data: [180, 230, 200, 280, 250, 300, 350], color: "#20E6A4" },
{ name: "缴费", data: [150, 210, 180, 240, 200, 260, 320], color: "#0783FA" },
{ name: "流失", data: [120, 380, 220, 350, 300, 280, 400], color: "#FFD15C" },
],
};
}); });
const currentMode = ref("week"); const currentMode = ref("week");
@ -70,7 +186,7 @@
// //
const generateSeries = (seriesData: any) => { const generateSeries = (seriesData: any) => {
return seriesData.map((item: any,) => { return seriesData.map((item: any) => {
const series = { const series = {
name: item.name, name: item.name,
type: "line", type: "line",
@ -94,16 +210,7 @@
// / // /
const switchDisplayMode = (mode: string) => { const switchDisplayMode = (mode: string) => {
currentMode.value = mode; currentMode.value = mode;
emits("dateChange", mode);
//
if (mode === "week") {
// ()
chartData.value.dates = ["5.24", "5.25", "5.26", "5.27", "5.28", "5.29", "5.30"];
} else {
//
chartData.value.dates = ["1月", "2月", "3月", "4月", "5月", "6月", "7月"];
}
// //
initChart(); initChart();
}; };
@ -352,27 +459,26 @@
color: "transparent", color: "transparent",
}, },
tooltip: { tooltip: {
show: false show: false,
} },
} },
], ],
}; };
myChart.setOption(option, true); myChart.setOption(option, true);
// //
const hoverBarId = 'mouseHoverBar'; const hoverBarId = "mouseHoverBar";
myChart.on("mouseover", { seriesId: "axisOverlayBar" }, (params) => {
myChart.on("mouseover", { seriesId: "axisOverlayBar" }, (params) => {
if (!myChart) return; if (!myChart) return;
// //
const dataIndex = params.dataIndex; const dataIndex = params.dataIndex;
// option // option
const _option = myChart.getOption() as any; const _option = myChart.getOption() as any;
// //
let seriesIndex = -1; let seriesIndex = -1;
if (_option.series && Array.isArray(_option.series)) { if (_option.series && Array.isArray(_option.series)) {
@ -382,22 +488,22 @@
} }
}); });
} }
// //
const hoverBarOption = { const hoverBarOption = {
id: hoverBarId, id: hoverBarId,
type: 'bar', type: "bar",
barWidth: 40, barWidth: 40,
barGap: '-100%', barGap: "-100%",
zlevel: 10, zlevel: 10,
silent: true, silent: true,
animation: true, animation: true,
data: chartData.value.dates.map((_: any, index: number) => { data: chartData.value.dates.map((_: any, index: number) => {
return index === dataIndex ? 500 : '-' return index === dataIndex ? 500 : "-";
}), }),
itemStyle: { itemStyle: {
color: { color: {
type: 'linear', type: "linear",
x: 0, x: 0,
y: 0, y: 0,
x2: 0, x2: 0,
@ -405,22 +511,22 @@
colorStops: [ colorStops: [
{ {
offset: 0, offset: 0,
color: '#fff' // color: "#fff", //
}, },
{ {
offset: 1, offset: 1,
color: '#0783FA' // color: "#0783FA", //
} },
], ],
global: false global: false,
}, },
opacity: 0.3, opacity: 0.3,
}, },
tooltip: { tooltip: {
show: false show: false,
} },
}; };
if (seriesIndex === -1) { if (seriesIndex === -1) {
// //
if (_option.series && Array.isArray(_option.series)) { if (_option.series && Array.isArray(_option.series)) {
@ -432,32 +538,31 @@
_option.series[seriesIndex].data = hoverBarOption.data; _option.series[seriesIndex].data = hoverBarOption.data;
} }
} }
// //
myChart.setOption(_option, false); myChart.setOption(_option, false);
}); });
// //
myChart.on("mouseout", { seriesId: "axisOverlayBar" }, () => { myChart.on("mouseout", { seriesId: "axisOverlayBar" }, () => {
if (!myChart) return; if (!myChart) return;
const _option = myChart.getOption() as any; const _option = myChart.getOption() as any;
const hoverBarExists = _option.series.find((series: any) => series.id && series.id === hoverBarId); const hoverBarExists = _option.series.find((series: any) => series.id && series.id === hoverBarId);
if (hoverBarExists) { if (hoverBarExists) {
try { try {
// series // series
if (_option.series && Array.isArray(_option.series)) { if (_option.series && Array.isArray(_option.series)) {
// //
const newSeries = _option.series.filter((series: any) => { const newSeries = _option.series.filter((series: any) => {
return series.id !== hoverBarId; return series.id !== hoverBarId;
}); });
// series // series
_option.series = newSeries; _option.series = newSeries;
// //
myChart.setOption(_option, true); myChart.setOption(_option, true);
} }
} catch (err) { } catch (err) {
console.error("删除高亮条出错:", err); console.error("删除高亮条出错:", err);
} }
@ -471,9 +576,13 @@
}; };
onMounted(() => { onMounted(() => {
initChart();
window.addEventListener("resize", () => myChart?.resize()); window.addEventListener("resize", () => myChart?.resize());
}); });
//
watch(() => props.chartDataArray, () => {
initChart();
}, { deep: true });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,125 +1,143 @@
<template> <template>
<div class="w-[143px] h-[143px]"> <div class="w-[143px] h-[143px]">
<div class="w-[143px] h-[143px] bg-transparent" ref="chartRef"></div> <div class="w-[143px] h-[143px] bg-transparent" ref="chartRef"></div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from "vue";
import * as echarts from 'echarts' import * as echarts from "echarts";
// //
const chartRef = ref<HTMLElement | null>(null) const chartRef = ref<HTMLElement | null>(null);
let chartInstance: echarts.ECharts | null = null let chartInstance: echarts.ECharts | null = null;
// prop // prop
const props = defineProps({ const props = defineProps({
chartData: { chartData: {
type: Array, type: Array,
required: true, required: true,
default: () => [] default: () => [],
} },
}) });
// const generatedFormattedData = () => {
const initChart = () => { const formattedData = props.chartData.map((item: any) => ({
...item,
// itemStylecolor
itemStyle: {
color: item.color || (item.itemStyle && item.itemStyle.color),
borderWidth: 0, //
},
}));
return formattedData;
};
//
const initChart = () => {
if (chartRef.value) { if (chartRef.value) {
// //
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
} }
// //
chartInstance = echarts.init(chartRef.value) chartInstance = echarts.init(chartRef.value);
// //
const formattedData = props.chartData.map((item: any) => ({
...item, //
// itemStylecolor const option = {
tooltip: {
trigger: "item",
backgroundColor: "rgba(0, 0, 0, 0.7)",
borderColor: "#44F1FF",
textStyle: {
color: "#fff",
fontSize: 10,
},
formatter: "{b}: {d}%",
},
series: [
{
name: "数据占比",
type: "pie",
radius: ["70%", "90%"],
center: ["50%", "50%"],
avoidLabelOverlap: false,
itemStyle: { itemStyle: {
color: item.color || (item.itemStyle && item.itemStyle.color), borderWidth: 0, //
borderWidth: 0 //
}
}))
//
const option = {
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: '#44F1FF',
textStyle: {
color: '#fff',
fontSize: 10
},
formatter: '{b}: {d}%',
}, },
series: [ label: {
{ show: false,
name: '数据占比', },
type: 'pie', emphasis: {
radius: ['70%', '90%'], scale: false,
center: ['50%', '50%'], itemStyle: {
avoidLabelOverlap: false, shadowBlur: 5,
itemStyle: { shadowOffsetX: 0,
borderWidth: 0 // shadowColor: "rgba(0, 0, 0, 0.5)",
}, },
label: { },
show: false labelLine: {
}, show: false,
emphasis: { },
scale: false, data: generatedFormattedData(),
itemStyle: { //
shadowBlur: 5, gap: 0,
shadowOffsetX: 0, },
shadowColor: 'rgba(0, 0, 0, 0.5)' ],
} animation: true,
}, animationDuration: 500,
labelLine: { };
show: false
},
data: formattedData,
//
gap: 0
}
],
animation: true,
animationDuration: 500
}
//
chartInstance.setOption(option)
//
window.addEventListener('resize', handleResize)
}
}
// //
const handleResize = () => { chartInstance.setOption(option);
//
window.addEventListener("resize", handleResize);
}
};
//
const handleResize = () => {
if (chartInstance) { if (chartInstance) {
chartInstance.resize() chartInstance.resize();
} }
} };
// //
onMounted(() => { onMounted(() => {
initChart() initChart();
}) });
onUnmounted(() => { watch(
() => props.chartData,
() => {
if (chartInstance) {
chartInstance.setOption({
series: [
{
data: generatedFormattedData(),
},
],
});
}
},
{ deep: true },
);
onUnmounted(() => {
// //
window.removeEventListener('resize', handleResize) window.removeEventListener("resize", handleResize);
// //
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -25,6 +25,17 @@ echarts.use([
const chartRef = useTemplateRef("chartRef") const chartRef = useTemplateRef("chartRef")
let chart: echarts.ECharts | null = null; let chart: echarts.ECharts | null = null;
const props = defineProps({
chartData:{
type:Array,
default: () => []
}
})
watch(() => props.chartData,() => {
initChart();
})
const initChart = () => { const initChart = () => {
if(!chartRef.value) return; if(!chartRef.value) return;
chart = echarts.init(chartRef.value) chart = echarts.init(chartRef.value)
@ -42,7 +53,7 @@ const initChart = () => {
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], data: props.chartData.map((item:any) => item.date),
axisLine: { axisLine: {
show: false show: false
}, },
@ -73,7 +84,7 @@ const initChart = () => {
name: '数据1', name: '数据1',
type: 'line', type: 'line',
smooth: true, smooth: true,
data: [120, 132, 101, 134, 90, 230, 210], data: props.chartData.map((item:any) => item.total),
lineStyle: { lineStyle: {
color: 'rgba(41, 241, 250, 1)', color: 'rgba(41, 241, 250, 1)',
width: 2 width: 2
@ -103,7 +114,7 @@ const initChart = () => {
} }
onMounted(() => { onMounted(() => {
initChart();
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
chart?.resize(); chart?.resize();
}); });

View File

@ -28,8 +28,8 @@
const props = withDefaults(defineProps<DefaultProps>(), { const props = withDefaults(defineProps<DefaultProps>(), {
chartData: [ chartData: [
{ name: "线下", value: 310, itemStyle: { color: "rgba(147, 219, 255, 1)" } }, { name: "线下", value: 0, itemStyle: { color: "rgba(147, 219, 255, 1)" } },
{ name: "线上", value: 335, itemStyle: { color: "rgb(79, 214, 169)" } }, { name: "线上", value: 0, itemStyle: { color: "rgb(79, 214, 169)" } },
], ],
ringSize: 1, ringSize: 1,
}); });
@ -206,6 +206,7 @@
height: 106, height: 106,
}, },
z: 0, z: 0,
cursor:"point",
origin: [127, 53], origin: [127, 53],
keyframeAnimation: [ keyframeAnimation: [
{ {

View File

@ -0,0 +1,65 @@
import {getRequest} from "@/api/customFetch";
import { getBigScreenRanking, getGainUrl, getPaymentUrl, getSegmentStatic, getSixStatisticsUrl } from "@/api/fetchUrl";
import { usePolling,addRequest } from "@/composables/usePolling";
export const useFetchAllData = () => {
const paymentData = ref<any>({})
const getPaymentData = () => {
getRequest(getPaymentUrl(),{},{}).then(resp => {
if(resp.code == 200){
paymentData.value = JSON.parse(JSON.stringify(resp.result))
}
})
};
addRequest('paymentData',getPaymentData)
provide("paymentData",paymentData)
const gainData = ref<any>({})
const getGainData = () => {
getRequest(getGainUrl(),null,{}).then(resp => {
if(resp.code === 200){
gainData.value = resp.result
}
})
}
addRequest("getGainData",getGainData)
provide("gainData",gainData)
const askSectionData = ref<any>({})
const getAskSectionData = () => {
getRequest(getSegmentStatic(), {}, {}).then((resp) => {
if (resp.code === 200) {
askSectionData.value = resp.result;
}
});
};
addRequest("getAskSectionData",getAskSectionData)
provide("askSectionData",askSectionData)
const chargingRankingData = ref<any>({})
const getChargingRankingData = () => {
getRequest(getBigScreenRanking(),{},{}).then(resp => {
if(resp.code === 200){
chargingRankingData.value = resp.result
}
})
}
addRequest("getChargingRankingData",getChargingRankingData)
provide("chargingRankingData",chargingRankingData)
const sixStatisticsData = ref<any>({})
const getSixStaticsData = () => {
getRequest(getSixStatisticsUrl(),{},{}).then(resp => {
if(resp.code === 200){
sixStatisticsData.value = resp.result
}
})
}
addRequest('getSixStaticsData',getSixStaticsData)
provide('sixStatisticsData',sixStatisticsData)
usePolling();
}

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="main-bg flex flex-col scrollbar-hide h-screen overflow-auto "> <div class="main-bg flex flex-col scrollbar-hide h-screen overflow-auto">
<header class="relative flex items-center"> <header class="relative flex items-center">
<SvgComponent :content="headerSvg" class="w-full h-[98px]" /> <SvgComponent :content="headerSvg" class="w-full h-[98px]" />
<SvgComponent :content="titleSvg" class="w-[50%] h-[69px] absolute top-0 left-50% translate-x-[-50%]" /> <SvgComponent :content="titleSvg" class="w-[50%] h-[69px] absolute top-0 left-50% translate-x-[-50%]" />
@ -8,9 +8,9 @@
<DigitalWatch class="ml-[10px]" /> <DigitalWatch class="ml-[10px]" />
</div> </div>
</header> </header>
<div class="flex items-center justify-end pr-[24px] cursor-pointer mb-[13px]"> <div class="flex items-center justify-end pr-[24px] cursor-pointer mb-[13px]" @click="updateAllData">
<SvgIcon name="circle" class="text-[14px] text-[#C0EEFF] hover:rotate-90 transition-all duration-300" /> <SvgIcon name="circle" class="text-[14px] text-[#C0EEFF] transition-all duration-300"/>
<div class="text-[#C0EEFF] text-[12px] ml-[5px]">数据更新时间:6.12 12:00:00</div> <div class="text-[#C0EEFF] text-[12px] ml-[5px]">数据更新时间:{{ formatDatetime(updateTime) }}</div>
</div> </div>
<div class="grid grid-rows-[210px_358px_320px] gap-y-5 w-full min-h-[928px] gap-[20px] flex-1 overflow-auto mb-[27px]"> <div class="grid grid-rows-[210px_358px_320px] gap-y-5 w-full min-h-[928px] gap-[20px] flex-1 overflow-auto mb-[27px]">
@ -22,14 +22,14 @@
<LossStatic class="ml-[20px]" /> <LossStatic class="ml-[20px]" />
</div> </div>
<div class="flex items-center px-[24px] justify-start"> <div class="flex items-center px-[24px] justify-start">
<OperatingTrends class=""/> <OperatingTrends class="" />
<AskSection class="ml-[20px]"/> <AskSection class="ml-[20px]" />
</div> </div>
<div class="flex items-center px-[24px] overflow-x-auto"> <div class="flex items-center px-[24px] overflow-x-auto">
<StudentSource /> <StudentSource />
<OnLineStatus class="ml-[20px]"/> <OnLineStatus class="ml-[20px]" />
<OfflineStatus class="ml-[20px]" /> <OfflineStatus class="ml-[20px]" />
<SixStatistics class="ml-[20px]"/> <SixStatistics class="ml-[20px]" />
<ChargingRanking class="ml-[20px]" /> <ChargingRanking class="ml-[20px]" />
<WinCustomer class="ml-[20px]" /> <WinCustomer class="ml-[20px]" />
</div> </div>
@ -55,11 +55,14 @@
import SixStatistics from "@/views/components/SixStatistics.vue"; import SixStatistics from "@/views/components/SixStatistics.vue";
import ChargingRanking from "./components/ChargingRanking.vue"; import ChargingRanking from "./components/ChargingRanking.vue";
import WinCustomer from "./components/WinCustomer.vue"; import WinCustomer from "./components/WinCustomer.vue";
import { useDate } from "@/composables/useDate"; import { useDate } from "@/composables/useDate";
import {useFetchAllData} from "./composables/useFetchData"
import { runImmediatelyAll, updateTime } from "@/composables/usePolling";
import { formatDatetime } from "@/utils/date";
const { year, month, day, weekday, formateTime } = useDate();
const { year, month, day, weekday, updateTime } = useDate();
const headerSvg = ref(""); const headerSvg = ref("");
const headerBackgroundSvg = async () => { const headerBackgroundSvg = async () => {
@ -74,14 +77,17 @@
}; };
useFetchAllData()
const updateAllData = () => {
runImmediatelyAll()
}
onBeforeMount(() => { onBeforeMount(() => {
updateTime(); formateTime();
headerBackgroundSvg(); headerBackgroundSvg();
headerTitleSvg(); headerTitleSvg();
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -92,5 +98,4 @@
background-repeat: no-repeat; background-repeat: no-repeat;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>