feat: 更新项目首页

This commit is contained in:
皓月归尘 2025-02-13 16:06:51 +08:00
parent b573a80574
commit 70d2fa91c6
6 changed files with 544 additions and 12 deletions

View File

@ -0,0 +1,39 @@
.point {
width: var(--point-width);
height: var(--point-height);
background: var(--point-background);
position: relative;
border-radius: var(--point-border-radius);
}
.point-flicker:after {
background: var(--point-background);
}
.point-flicker:before,
.point-flicker:after {
content: "";
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
border-radius: var(--point-border-radius);
animation: flicker 1.2s ease-out infinite;
}
@keyframes flicker {
0% {
transform: scale(0.5);
opacity: 1;
}
30% {
opacity: 1;
}
100% {
transform: scale(var(--point-scale));
opacity: 0;
}
}

View File

@ -0,0 +1,44 @@
import "./index.css";
import { type Component, h, defineComponent } from "vue";
export interface attrsType {
width?: string;
height?: string;
borderRadius?: number | string;
background?: string;
scale?: number | string;
}
/**
*
* @param width string
* @param height string
* @param borderRadius number | string 050%
* @param background string
* @param scale number | string 2
* @returns Component
*/
export function useRenderFlicker(attrs?: attrsType): Component {
return defineComponent({
name: "ReFlicker",
render() {
return h(
"div",
{
class: "point point-flicker",
style: {
"--point-width": attrs?.width ?? "12px",
"--point-height": attrs?.height ?? "12px",
"--point-background":
attrs?.background ?? "var(--el-color-primary)",
"--point-border-radius": attrs?.borderRadius ?? "50%",
"--point-scale": attrs?.scale ?? "2"
}
},
{
default: () => []
}
);
}
});
}

View File

@ -4,13 +4,13 @@ import { setupStore } from "@/store";
import { useI18n } from "@/plugins/i18n"; import { useI18n } from "@/plugins/i18n";
import { getPlatformConfig } from "./config"; import { getPlatformConfig } from "./config";
import { MotionPlugin } from "@vueuse/motion"; import { MotionPlugin } from "@vueuse/motion";
// import { useEcharts } from "@/plugins/echarts"; import { useEcharts } from "@/plugins/echarts";
import { createApp, type Directive } from "vue"; import { createApp, type Directive } from "vue";
import { useElementPlus } from "@/plugins/elementPlus"; import { useElementPlus } from "@/plugins/elementPlus";
import { injectResponsiveStorage } from "@/utils/responsive"; import { injectResponsiveStorage } from "@/utils/responsive";
import Table from "@pureadmin/table"; import Table from "@pureadmin/table";
// import PureDescriptions from "@pureadmin/descriptions"; import PureDescriptions from "@pureadmin/descriptions";
// 引入重置样式 // 引入重置样式
import "./style/reset.scss"; import "./style/reset.scss";
@ -56,8 +56,12 @@ getPlatformConfig(app).then(async config => {
app.use(router); app.use(router);
await router.isReady(); await router.isReady();
injectResponsiveStorage(app, config); injectResponsiveStorage(app, config);
app.use(MotionPlugin).use(useI18n).use(useElementPlus).use(Table); app
// .use(PureDescriptions) .use(MotionPlugin)
// .use(useEcharts); .use(useI18n)
.use(useElementPlus)
.use(Table)
.use(PureDescriptions)
.use(useEcharts);
app.mount("#app"); app.mount("#app");
}); });

View File

@ -10,6 +10,8 @@ import { useNav } from "@/layout/hooks/useNav";
import { useEventListener } from "@vueuse/core"; import { useEventListener } from "@vueuse/core";
import type { FormInstance } from "element-plus"; import type { FormInstance } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n"; import { $t, transformI18n } from "@/plugins/i18n";
import { getConfig } from "@/config";
// import { operates } from "./utils/enums"; // import { operates } from "./utils/enums";
import { useLayout } from "@/layout/hooks/useLayout"; import { useLayout } from "@/layout/hooks/useLayout";
import LoginPhone from "./components/LoginPhone.vue"; import LoginPhone from "./components/LoginPhone.vue";
@ -34,6 +36,7 @@ import Info from "@iconify-icons/ri/information-line";
import { GetCaptchaAPI } from "@/api/login"; import { GetCaptchaAPI } from "@/api/login";
import { getUserInfoAPI } from "@/api/login"; import { getUserInfoAPI } from "@/api/login";
import { setUserInfo } from "@/utils/auth"; import { setUserInfo } from "@/utils/auth";
const Period = getConfig("Period");
defineOptions({ defineOptions({
name: "Login" name: "Login"
@ -347,12 +350,8 @@ onMounted(async () => {
<div <div
class="w-full flex-c absolute bottom-3 text-sm text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]" class="w-full flex-c absolute bottom-3 text-sm text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]"
> >
Copyright © 2020-present Copyright © {{ Period }}
<a <a class="hover:text-primary" href="#" target="_blank">
class="hover:text-primary"
href="https://github.com/pure-admin"
target="_blank"
>
&nbsp;{{ title }} &nbsp;{{ title }}
</a> </a>
</div> </div>

View File

@ -0,0 +1,108 @@
<script setup lang="ts">
import { useDark, useECharts } from "@pureadmin/utils";
import { type PropType, ref, computed, watch, nextTick } from "vue";
const props = defineProps({
requireData: {
type: Array as PropType<Array<number>>,
default: () => []
},
questionData: {
type: Array as PropType<Array<number>>,
default: () => []
}
});
const { isDark } = useDark();
const theme = computed(() => (isDark.value ? "dark" : "light"));
const chartRef = ref();
const { setOptions } = useECharts(chartRef, {
theme
});
watch(
() => props,
async () => {
await nextTick(); // DOM
setOptions({
container: ".bar-card",
color: ["#41b6ff", "#e85f33"],
tooltip: {
trigger: "axis",
axisPointer: {
type: "none"
}
},
grid: {
top: "20px",
left: "50px",
right: 0
},
legend: {
data: ["查询数量", "匹配数量"],
textStyle: {
color: "#606266",
fontSize: "0.875rem"
},
bottom: 0
},
xAxis: [
{
type: "category",
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
axisLabel: {
fontSize: "0.875rem"
},
axisPointer: {
type: "shadow"
}
}
],
yAxis: [
{
type: "value",
axisLabel: {
fontSize: "0.875rem"
},
splitLine: {
show: false // 线
}
// name: ": "
}
],
series: [
{
name: "查询数量",
type: "bar",
barWidth: 45,
itemStyle: {
color: "#41b6ff",
borderRadius: [10, 10, 0, 0]
},
data: props.requireData
},
{
name: "匹配数量",
type: "bar",
barWidth: 45,
itemStyle: {
color: "#e86033ce",
borderRadius: [10, 10, 0, 0]
},
data: props.questionData
}
]
});
},
{
deep: true,
immediate: true
}
);
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 550px" />
</template>

View File

@ -2,8 +2,346 @@
defineOptions({ defineOptions({
name: "Welcome" name: "Welcome"
}); });
import { ref, markRaw } from "vue";
import dayjs from "dayjs";
import ReCol from "@/components/ReCol";
import ChartBar from "./components/ChartBar.vue";
import { useRenderFlicker } from "@/components/ReFlicker";
import Segmented, { type OptionsType } from "@/components/ReSegmented";
import { useDark, cloneDeep, randomGradient } from "@pureadmin/utils";
import GroupLine from "@iconify-icons/ri/group-line";
import Question from "@iconify-icons/ri/question-answer-line";
import CheckLine from "@iconify-icons/ri/chat-check-line";
import Smile from "@iconify-icons/ri/star-smile-line";
const { isDark } = useDark();
const getRandomIntBetween = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
let curWeek = ref(1); // 01
const optionsBasis: Array<OptionsType> = [
{
label: "上周"
},
{
label: "本周"
}
];
const days = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
/** 需求人数、提问数量、解决数量、用户满意度 */
const chartData = [
{
icon: GroupLine,
bgColor: "#effaff",
color: "#41b6ff",
duration: 2200,
name: "需求人数",
value: 36000,
percent: "+88%",
data: [2101, 5288, 4239, 4962, 6752, 5208, 7450] // 线
},
{
icon: Question,
bgColor: "#fff5f4",
color: "#e85f33",
duration: 1600,
name: "提问数量",
value: 16580,
percent: "+70%",
data: [2216, 1148, 1255, 788, 4821, 1973, 4379]
},
{
icon: CheckLine,
bgColor: "#eff8f4",
color: "#26ce83",
duration: 1500,
name: "解决数量",
value: 16499,
percent: "+99%",
data: [861, 1002, 3195, 1715, 3666, 2415, 3645]
},
{
icon: Smile,
bgColor: "#f6f4fe",
color: "#7846e5",
duration: 100,
name: "用户满意度",
value: 100,
percent: "+100%",
data: [100]
}
];
/** 分析概览 */
const barChartData = [
{
requireData: [2101, 5288, 4239, 4962, 6752, 5208, 7450],
questionData: [2216, 1148, 1255, 1788, 4821, 1973, 4379]
},
{
requireData: [2101, 3280, 4400, 4962, 5752, 6889, 7600],
questionData: [2116, 3148, 3255, 3788, 4821, 4970, 5390]
}
];
/** 数据统计 */
const tableData = Array.from({ length: 30 }).map((_, index) => {
return {
id: index + 1,
requiredNumber: getRandomIntBetween(13500, 19999),
questionNumber: getRandomIntBetween(12600, 16999),
resolveNumber: getRandomIntBetween(13500, 17999),
satisfaction: getRandomIntBetween(95, 100),
date: dayjs().subtract(index, "day").format("YYYY-MM-DD")
};
});
/** 最新动态 */
const latestNewsData = cloneDeep(tableData)
.slice(0, 14)
.map((item, index) => {
return Object.assign(item, {
date: `${dayjs().subtract(index, "day").format("YYYY-MM-DD")} ${
days[dayjs().subtract(index, "day").day()]
}`
});
});
</script> </script>
<template> <template>
<h1>首页</h1> <div>
<el-row :gutter="24" justify="space-around">
<re-col
v-motion
class="mb-[18px]"
:value="8"
:md="12"
:sm="12"
:xs="24"
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 80
}
}"
>
<el-card class="line-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium"> 今日查询 </span>
<div
class="w-8 h-8 flex justify-center items-center rounded-md"
:style="{
backgroundColor: isDark ? 'transparent' : '#effaff'
}"
>
<IconifyIconOffline
:icon="GroupLine"
color="#41b6ff"
width="18"
/>
</div>
</div>
<div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">688780</h2>
</div>
</div>
</el-card>
</re-col>
<re-col
v-motion
class="mb-[18px]"
:value="8"
:md="12"
:sm="12"
:xs="24"
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 80
}
}"
>
<el-card class="line-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium"> 当月查询 </span>
<div
class="w-8 h-8 flex justify-center items-center rounded-md"
:style="{
backgroundColor: isDark ? 'transparent' : '#fff5f4'
}"
>
<IconifyIconOffline :icon="Question" color="#e85f33" width="18" />
</div>
</div>
<div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">79999897098</h2>
</div>
</div>
</el-card>
</re-col>
<re-col
v-motion
class="mb-[18px]"
:value="8"
:md="12"
:sm="12"
:xs="24"
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 80
}
}"
>
<el-card class="line-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium"> 上月查询 </span>
<div
class="w-8 h-8 flex justify-center items-center rounded-md"
:style="{
backgroundColor: isDark ? 'transparent' : '#eff8f4'
}"
>
<IconifyIconOffline
:icon="CheckLine"
color="#26ce83"
width="18"
/>
</div>
</div>
<div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">7898067690</h2>
</div>
</div>
</el-card>
</re-col>
<re-col
v-motion
class="mb-[18px]"
:value="16"
:xs="24"
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 400
}
}"
>
<el-card class="bar-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">数据概览</span>
<Segmented v-model="curWeek" :options="optionsBasis" />
</div>
<div class="flex justify-between items-start mt-3">
<ChartBar
:requireData="barChartData[curWeek].requireData"
:questionData="barChartData[curWeek].questionData"
/>
</div>
</el-card>
</re-col>
<re-col
v-motion
class="mb-[18px]"
:value="8"
:xs="24"
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 640
}
}"
>
<el-card shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">查询动态</span>
</div>
<el-scrollbar max-height="558" class="mt-3">
<el-timeline>
<el-timeline-item
v-for="(item, index) in latestNewsData"
:key="index"
center
placement="top"
:icon="
markRaw(
useRenderFlicker({
background: randomGradient({
randomizeHue: true
})
})
)
"
:timestamp="item.date"
>
<p class="text-text_color_regular text-sm">
{{
`查询 ${item.requiredNumber} 条数据,${item.resolveNumber} 条已匹配`
}}
</p>
</el-timeline-item>
</el-timeline>
</el-scrollbar>
</el-card>
</re-col>
</el-row>
</div>
</template> </template>
<style lang="scss" scoped>
:deep(.el-card) {
--el-card-border-color: none;
/* 解决概率进度条宽度 */
.el-progress--line {
width: 85%;
}
/* 解决概率进度条字体大小 */
.el-progress-bar__innerText {
font-size: 15px;
}
/* 隐藏 el-scrollbar 滚动条 */
.el-scrollbar__bar {
display: none;
}
/* el-timeline 每一项上下、左右边距 */
.el-timeline-item {
margin: 0 6px;
}
}
.main-content {
margin: 20px 20px 0 !important;
}
</style>