,
+ required: false,
+ default: 0,
+ validator(value: number) {
+ return value < 10 && value >= 0 && Number.isInteger(value);
+ }
+ }
+};
diff --git a/src/components/ReCountTo/src/rebound/rebound.css b/src/components/ReCountTo/src/rebound/rebound.css
new file mode 100644
index 0000000..9fc5932
--- /dev/null
+++ b/src/components/ReCountTo/src/rebound/rebound.css
@@ -0,0 +1,77 @@
+.scroll-num {
+ width: var(--width, 20px);
+ height: var(--height, calc(var(--width, 20px) * 1.8));
+ color: var(--color, #333);
+ font-size: var(--height, calc(var(--width, 20px) * 1.1));
+ line-height: var(--height, calc(var(--width, 20px) * 1.8));
+ text-align: center;
+ overflow: hidden;
+ animation: enhance-bounce-in-down 1s calc(var(--delay) * 1s) forwards;
+}
+
+ul {
+ animation:
+ move 0.3s linear infinite,
+ bounce-in-down 1s calc(var(--delay) * 1s) forwards;
+}
+
+@keyframes move {
+ from {
+ transform: translateY(-90%);
+ filter: url(#blur);
+ }
+
+ to {
+ transform: translateY(1%);
+ filter: url(#blur);
+ }
+}
+
+@keyframes bounce-in-down {
+ from {
+ transform: translateY(calc(var(--i) * -9.09% - 7%));
+ filter: none;
+ }
+
+ 25% {
+ transform: translateY(calc(var(--i) * -9.09% + 3%));
+ }
+
+ 50% {
+ transform: translateY(calc(var(--i) * -9.09% - 1%));
+ }
+
+ 70% {
+ transform: translateY(calc(var(--i) * -9.09% + 0.6%));
+ }
+
+ 85% {
+ transform: translateY(calc(var(--i) * -9.09% - 0.3%));
+ }
+
+ to {
+ transform: translateY(calc(var(--i) * -9.09%));
+ }
+}
+
+@keyframes enhance-bounce-in-down {
+ 25% {
+ transform: translateY(8%);
+ }
+
+ 50% {
+ transform: translateY(-4%);
+ }
+
+ 70% {
+ transform: translateY(2%);
+ }
+
+ 85% {
+ transform: translateY(-1%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+}
diff --git a/src/components/ReFlicker/index.css b/src/components/ReFlicker/index.css
new file mode 100644
index 0000000..4c40af4
--- /dev/null
+++ b/src/components/ReFlicker/index.css
@@ -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;
+ }
+}
diff --git a/src/components/ReFlicker/index.ts b/src/components/ReFlicker/index.ts
new file mode 100644
index 0000000..b8d29b7
--- /dev/null
+++ b/src/components/ReFlicker/index.ts
@@ -0,0 +1,44 @@
+import "./index.css";
+import { h, defineComponent, type Component } 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 传0为方形、传50%或者不传为圆形
+ * @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: () => []
+ }
+ );
+ }
+ });
+}
diff --git a/src/main.ts b/src/main.ts
index d7ea35b..5237cdd 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,7 +4,7 @@ import { setupStore } from "@/store";
import { useI18n } from "@/plugins/i18n";
import { getPlatformConfig } from "./config";
import { MotionPlugin } from "@vueuse/motion";
-// import { useEcharts } from "@/plugins/echarts";
+import { useEcharts } from "@/plugins/echarts";
import { createApp, type Directive } from "vue";
import { useElementPlus } from "@/plugins/elementPlus";
import { injectResponsiveStorage } from "@/utils/responsive";
@@ -61,7 +61,7 @@ getPlatformConfig(app).then(async config => {
.use(useI18n)
.use(useElementPlus)
.use(Table)
- .use(PureDescriptions);
- // .use(useEcharts);
+ .use(PureDescriptions)
+ .use(useEcharts);
app.mount("#app");
});
diff --git a/src/views/welcome/components/chart/bar.vue b/src/views/welcome/components/chart/bar.vue
new file mode 100644
index 0000000..6d9af15
--- /dev/null
+++ b/src/views/welcome/components/chart/bar.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
diff --git a/src/views/welcome/components/chart/index.ts b/src/views/welcome/components/chart/index.ts
new file mode 100644
index 0000000..42cd5e6
--- /dev/null
+++ b/src/views/welcome/components/chart/index.ts
@@ -0,0 +1,3 @@
+export { default as barChart } from "./bar.vue";
+export { default as lineChart } from "./line.vue";
+export { default as roundChart } from "./round.vue";
diff --git a/src/views/welcome/components/chart/line.vue b/src/views/welcome/components/chart/line.vue
new file mode 100644
index 0000000..fa72ec1
--- /dev/null
+++ b/src/views/welcome/components/chart/line.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
diff --git a/src/views/welcome/components/chart/round.vue b/src/views/welcome/components/chart/round.vue
new file mode 100644
index 0000000..769f2b2
--- /dev/null
+++ b/src/views/welcome/components/chart/round.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
diff --git a/src/views/welcome/components/table/columns.tsx b/src/views/welcome/components/table/columns.tsx
new file mode 100644
index 0000000..c6d0b8e
--- /dev/null
+++ b/src/views/welcome/components/table/columns.tsx
@@ -0,0 +1,104 @@
+import { tableData } from "../../data";
+import { delay } from "@pureadmin/utils";
+import { ref, onMounted, reactive } from "vue";
+import type { PaginationProps } from "@pureadmin/table";
+import ThumbUp from "@iconify-icons/ri/thumb-up-line";
+import Hearts from "@iconify-icons/ri/hearts-line";
+import Empty from "./empty.svg?component";
+
+export function useColumns() {
+ const dataList = ref([]);
+ const loading = ref(true);
+ const columns: TableColumnList = [
+ {
+ sortable: true,
+ label: "序号",
+ prop: "id"
+ },
+ {
+ sortable: true,
+ label: "需求人数",
+ prop: "requiredNumber",
+ filterMultiple: false,
+ filterClassName: "pure-table-filter",
+ filters: [
+ { text: "≥16000", value: "more" },
+ { text: "<16000", value: "less" }
+ ],
+ filterMethod: (value, { requiredNumber }) => {
+ return value === "more"
+ ? requiredNumber >= 16000
+ : requiredNumber < 16000;
+ }
+ },
+ {
+ sortable: true,
+ label: "提问数量",
+ prop: "questionNumber"
+ },
+ {
+ sortable: true,
+ label: "解决数量",
+ prop: "resolveNumber"
+ },
+ {
+ sortable: true,
+ label: "用户满意度",
+ minWidth: 100,
+ prop: "satisfaction",
+ cellRenderer: ({ row }) => (
+
+
+ {row.satisfaction}%
+ 98 ? Hearts : ThumbUp}
+ color="#e85f33"
+ />
+
+
+ )
+ },
+ {
+ sortable: true,
+ label: "统计日期",
+ prop: "date"
+ },
+ {
+ label: "操作",
+ fixed: "right",
+ slot: "operation"
+ }
+ ];
+
+ /** 分页配置 */
+ const pagination = reactive({
+ pageSize: 10,
+ currentPage: 1,
+ layout: "prev, pager, next",
+ total: 0,
+ align: "center"
+ });
+
+ function onCurrentChange(page: number) {
+ console.log("onCurrentChange", page);
+ loading.value = true;
+ delay(300).then(() => {
+ loading.value = false;
+ });
+ }
+
+ onMounted(() => {
+ dataList.value = tableData;
+ pagination.total = dataList.value.length;
+ loading.value = false;
+ });
+
+ return {
+ Empty,
+ loading,
+ columns,
+ dataList,
+ pagination,
+ onCurrentChange
+ };
+}
diff --git a/src/views/welcome/components/table/empty.svg b/src/views/welcome/components/table/empty.svg
new file mode 100644
index 0000000..5c8b211
--- /dev/null
+++ b/src/views/welcome/components/table/empty.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/views/welcome/components/table/index.vue b/src/views/welcome/components/table/index.vue
new file mode 100644
index 0000000..ab8c179
--- /dev/null
+++ b/src/views/welcome/components/table/index.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/welcome/data.ts b/src/views/welcome/data.ts
new file mode 100644
index 0000000..3bb5021
--- /dev/null
+++ b/src/views/welcome/data.ts
@@ -0,0 +1,134 @@
+import { dayjs, cloneDeep, getRandomIntBetween } from "./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 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 progressData = [
+ {
+ week: "周一",
+ percentage: 85,
+ duration: 110,
+ color: "#41b6ff"
+ },
+ {
+ week: "周二",
+ percentage: 86,
+ duration: 105,
+ color: "#41b6ff"
+ },
+ {
+ week: "周三",
+ percentage: 88,
+ duration: 100,
+ color: "#41b6ff"
+ },
+ {
+ week: "周四",
+ percentage: 89,
+ duration: 95,
+ color: "#41b6ff"
+ },
+ {
+ week: "周五",
+ percentage: 94,
+ duration: 90,
+ color: "#26ce83"
+ },
+ {
+ week: "周六",
+ percentage: 96,
+ duration: 85,
+ color: "#26ce83"
+ },
+ {
+ week: "周日",
+ percentage: 100,
+ duration: 80,
+ color: "#26ce83"
+ }
+].reverse();
+
+/** 数据统计 */
+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()]
+ }`
+ });
+ });
+
+export { chartData, barChartData, progressData, tableData, latestNewsData };
diff --git a/src/views/welcome/index.vue b/src/views/welcome/index.vue
index b82b011..8e811aa 100644
--- a/src/views/welcome/index.vue
+++ b/src/views/welcome/index.vue
@@ -1,9 +1,276 @@
- Pure-Admin-Thin(国际化版本)
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 分析概览
+
+
+
+
+
+
+
+
+
+
+
+ 解决概率
+
+
+
+
+ {{ item.week }}
+
+
+
+
+
+
+
+
+ 数据统计
+
+
+
+
+
+
+
+
+ 最新动态
+
+
+
+
+
+ {{
+ `新增 ${item.requiredNumber} 条问题,${item.resolveNumber} 条已解决`
+ }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/welcome/utils.ts b/src/views/welcome/utils.ts
new file mode 100644
index 0000000..7708a7e
--- /dev/null
+++ b/src/views/welcome/utils.ts
@@ -0,0 +1,6 @@
+export { default as dayjs } from "dayjs";
+export { useDark, cloneDeep, randomGradient } from "@pureadmin/utils";
+
+export function getRandomIntBetween(min: number, max: number) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}