completepage: 🍻 首页内容展示

This commit is contained in:
Bunny 2024-11-24 20:10:35 +08:00
parent 998906ba27
commit 7bdc6ed08d
19 changed files with 523 additions and 681 deletions

View File

@ -10,3 +10,8 @@ export const getRouterAsync = () => {
export const fetchUploadFile = (data: any) => {
return http.request<BaseResult<any>>('post', '/files/upload', { data }, { headers: { 'Content-Type': 'multipart/form-data' } });
};
/** 账单信息---首页内容展示 */
export const fetchHomeDatalist = (data: any) => {
return http.request<BaseResult<object>>('get', 'noManage/homeDatalist', { params: data });
};

View File

@ -33,54 +33,34 @@ watch(
await nextTick(); // DOM
setOptions({
container: '.bar-card',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
},
},
grid: {
top: '30px',
bottom: '30px',
left: '50px',
right: 0,
},
tooltip: { trigger: 'axis', axisPointer: { type: 'none' } },
grid: { top: '30px', bottom: '30px', left: '50px', right: 0 },
xAxis: [
{
type: 'category',
data: props.analyseXAxis,
axisLabel: {
fontSize: '0.875rem',
},
axisPointer: {
type: 'shadow',
},
axisLabel: { fontSize: '0.875rem' },
axisPointer: { type: 'shadow' },
},
],
yAxis: [
{
type: 'value',
axisLabel: {
padding: 0,
fontSize: '0.875rem',
},
splitLine: {
show: true, // 线
},
axisLabel: { padding: 0, fontSize: '0.875rem' },
splitLine: { show: true },
position: 'left',
name: $t('unitMoney'),
},
],
series: [
{
name: $t('amount'),
type: 'line',
itemStyle: {
color: props.color[0],
borderRadius: [10, 10, 0, 0],
},
itemStyle: { color: props.color[0], borderRadius: [10, 10, 0, 0] },
data: props.analyseData,
},
{
name: $t('amount'),
type: 'bar',
barWidth: 15,
itemStyle: {

View File

@ -1,8 +1,7 @@
<script lang="ts" setup>
import Analyse from '@/components/Analyse/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { dayjs } from '@/views/welcome/utils/utils';
import dayjs from 'dayjs';
import { currentMouth, days } from '@/enums/dateEnums';
import { fetchGetExpendOrIncome } from '@/api/v1/financial/user/billUser';
import { $t } from '@/plugins/i18n';

View File

@ -1,11 +1,10 @@
<script lang="ts" setup>
import Analyse from '@/components/Analyse/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { dayjs } from '@/views/welcome/utils/utils';
import { currentMouth, days } from '@/enums/dateEnums';
import { fetchGetExpendOrIncome } from '@/api/v1/financial/user/billUser';
import { $t } from '@/plugins/i18n';
import dayjs from 'dayjs';
const title = {
analyse: $t('revenueAnalysis'),

View File

@ -1,9 +1,14 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, onMounted, type PropType, ref, watch } from 'vue';
import { useDark, useECharts } from '@pureadmin/utils';
const { isDark } = useDark();
const props = defineProps({
data: {
type: Number as PropType<number>,
},
});
const { isDark } = useDark();
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
const chartRef = ref();
@ -12,59 +17,69 @@ const { setOptions } = useECharts(chartRef, {
renderer: 'svg',
});
setOptions({
container: '.line-card',
title: {
text: '100%',
left: '47%',
top: '30%',
textAlign: 'center',
textStyle: {
fontSize: '16',
fontWeight: 600,
},
},
polar: {
radius: ['100%', '90%'],
center: ['50%', '50%'],
},
angleAxis: {
max: 100,
show: false,
},
radiusAxis: {
type: 'category',
show: true,
axisLabel: {
show: false,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
series: [
{
type: 'bar',
roundCap: true,
barWidth: 2,
showBackground: true,
backgroundStyle: {
color: '#dfe7ef',
},
data: [100],
coordinateSystem: 'polar',
color: '#7846e5',
itemStyle: {
shadowBlur: 2,
shadowColor: '#7846e5',
shadowOffsetX: 0,
shadowOffsetY: 0,
const init = () => {
setOptions({
container: '.line-card',
title: {
text: `${props.data}%`,
left: '47%',
top: '30%',
textAlign: 'center',
textStyle: {
fontSize: '16',
fontWeight: 600,
},
},
],
polar: {
radius: ['100%', '90%'],
center: ['50%', '50%'],
},
angleAxis: {
max: 100,
show: false,
},
radiusAxis: {
type: 'category',
show: true,
axisLabel: {
show: false,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
series: [
{
type: 'bar',
roundCap: true,
barWidth: 2,
showBackground: true,
backgroundStyle: {
color: '#dfe7ef',
},
data: [props.data],
coordinateSystem: 'polar',
color: '#7846e5',
itemStyle: {
shadowBlur: 2,
shadowColor: '#7846e5',
shadowOffsetX: 0,
shadowOffsetY: 0,
},
},
],
});
};
onMounted(() => {
watch(
() => props,
() => init(),
{ deep: true },
);
});
</script>

View File

@ -0,0 +1,110 @@
<script lang="ts" setup>
import { useDark, useECharts } from '@pureadmin/utils';
import { computed, nextTick, type PropType, ref, watch } from 'vue';
const props = defineProps({
incomeData: {
type: Array as PropType<Array<number>>,
default: () => [],
},
expendData: {
type: Array as PropType<Array<number>>,
default: () => [],
},
xAxis: {
type: Array as PropType<Array<string>>,
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: props.xAxis,
axisLabel: {
fontSize: '0.875rem',
},
axisPointer: {
type: 'shadow',
},
},
],
yAxis: [
{
type: 'value',
axisLabel: {
fontSize: '0.875rem',
},
splitLine: {
show: false,
},
},
],
series: [
{
name: '收入金额',
type: 'line',
barWidth: 15,
itemStyle: {
color: '#67C23A',
borderRadius: [10, 10, 0, 0],
},
data: props.incomeData,
},
{
name: '支出金额',
type: 'bar',
barWidth: 15,
itemStyle: {
color: '#F56C6C',
borderRadius: [10, 10, 0, 0],
},
data: props.expendData,
},
],
});
},
{
deep: true,
immediate: true,
},
);
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 504px" />
</template>

View File

@ -14,6 +14,7 @@ const props = defineProps({
});
const { isDark } = useDark();
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
const chartRef = ref();

View File

@ -1,108 +0,0 @@
<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: 10,
itemStyle: {
color: "#41b6ff",
borderRadius: [10, 10, 0, 0]
},
data: props.requireData
},
{
name: "提问数量",
type: "bar",
barWidth: 10,
itemStyle: {
color: "#e86033ce",
borderRadius: [10, 10, 0, 0]
},
data: props.questionData
}
]
});
},
{
deep: true,
immediate: true
}
);
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 365px" />
</template>

View File

@ -1,62 +0,0 @@
<script setup lang="ts">
import { type PropType, ref, computed } from "vue";
import { useDark, useECharts } from "@pureadmin/utils";
const props = defineProps({
data: {
type: Array as PropType<Array<number>>,
default: () => []
},
color: {
type: String,
default: "#41b6ff"
}
});
const { isDark } = useDark();
const theme = computed(() => (isDark.value ? "dark" : "light"));
const chartRef = ref();
const { setOptions } = useECharts(chartRef, {
theme,
renderer: "svg"
});
setOptions({
container: ".line-card",
xAxis: {
type: "category",
show: false,
data: props.data
},
grid: {
top: "15px",
bottom: 0,
left: 0,
right: 0
},
yAxis: {
show: false,
type: "value"
},
series: [
{
data: props.data,
type: "line",
symbol: "none",
smooth: true,
color: props.color,
lineStyle: {
shadowOffsetY: 3,
shadowBlur: 7,
shadowColor: props.color
}
}
]
});
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 60px" />
</template>

View File

@ -1,73 +0,0 @@
<script setup lang="ts">
import { ref, computed } from "vue";
import { useDark, useECharts } from "@pureadmin/utils";
const { isDark } = useDark();
const theme = computed(() => (isDark.value ? "dark" : "light"));
const chartRef = ref();
const { setOptions } = useECharts(chartRef, {
theme,
renderer: "svg"
});
setOptions({
container: ".line-card",
title: {
text: "100%",
left: "47%",
top: "30%",
textAlign: "center",
textStyle: {
fontSize: "16",
fontWeight: 600
}
},
polar: {
radius: ["100%", "90%"],
center: ["50%", "50%"]
},
angleAxis: {
max: 100,
show: false
},
radiusAxis: {
type: "category",
show: true,
axisLabel: {
show: false
},
axisLine: {
show: false
},
axisTick: {
show: false
}
},
series: [
{
type: "bar",
roundCap: true,
barWidth: 2,
showBackground: true,
backgroundStyle: {
color: "#dfe7ef"
},
data: [100],
coordinateSystem: "polar",
color: "#7846e5",
itemStyle: {
shadowBlur: 2,
shadowColor: "#7846e5",
shadowOffsetX: 0,
shadowOffsetY: 0
}
}
]
});
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 60px" />
</template>

View File

@ -1,3 +0,0 @@
export { default as ChartBar } from "./ChartBar.vue";
export { default as ChartLine } from "./ChartLine.vue";
export { default as ChartRound } from "./ChartRound.vue";

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,176 @@
<script lang="tsx" setup>
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import PureTable from '@pureadmin/table';
import { $t } from '@/plugins/i18n';
import { ElTag, ElText } from 'element-plus';
import { onMounted, reactive, ref, watch } from 'vue';
import { fetchGetUserBillList } from '@/api/v1/financial/user/billUser';
import Empty from './empty.svg?component';
const props = defineProps({
financialType: { type: Number as PropType<any>, default: undefined },
startDate: { type: String as PropType<any>, default: undefined },
endDate: { type: String as PropType<any>, default: undefined },
});
const columns: TableColumnList = [
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
//
{
label: $t('amount'),
prop: 'amount',
sortable: true,
width: 200,
formatter({ type, amount }) {
return type === -1 ? (
<ElText size={'large'} type={'danger'} style={{ fontWeight: 800 }}>
- {amount}
</ElText>
) : (
<ElText size={'large'} type={'success'} style={{ fontWeight: 800 }}>
+ {amount}
</ElText>
);
},
filterMultiple: false,
filterClassName: 'pure-table-filter',
filters: [
{ text: '≥1000', value: 'more' },
{ text: '<1000', value: 'less' },
],
filterMethod: (value, { amount }) => {
return value === 'more' ? amount >= 1000 : amount < 1000;
},
},
//
{
label: $t('categoryName'),
prop: 'categoryName',
width: 150,
formatter({ categoryName }) {
return <ElTag effect={'plain'}>{categoryName}</ElTag>;
},
},
//
{ label: $t('description'), prop: 'description' },
//
{
label: $t('transactionDate'),
prop: 'transactionDate',
width: 190,
sortable: true,
formatter({ transactionDate }) {
return (
<ElText type={'success'} style={{ fontWeight: 800 }}>
{transactionDate}
</ElText>
);
},
},
];
const form = reactive({
// 1 - -1 -
type: props.financialType,
//
startDate: props.startDate,
//
endDate: props.endDate,
pagination: {
pageSize: 10,
currentPage: 1,
layout: 'prev, pager, next',
total: 1,
align: 'center',
hideOnSinglePage: true,
},
});
const dataList = ref([]);
const loading = ref(false);
/* 初始化数据 */
async function onSearch() {
loading.value = true;
const result = await fetchGetUserBillList({ ...form, ...form.pagination });
dataList.value = result.data.list;
form.pagination.currentPage = result.data.pageNo;
form.pagination.pageSize = result.data.pageSize;
form.pagination.total = result.data.total;
loading.value = false;
}
/* 修改分页 */
async function onCurrentChange(page: number) {
form.pagination.currentPage = page;
await onSearch();
}
onMounted(() => {
watch(
() => props,
async () => {
form.startDate = props.startDate;
form.endDate = props.endDate;
await onSearch();
},
{ immediate: true, deep: true },
);
});
</script>
<template>
<pure-table
:columns="columns"
:data="dataList"
:loading="loading"
:loading-config="{ background: 'transparent' }"
:pagination="form.pagination"
alignWhole="center"
row-key="id"
showOverflowTooltip
@page-current-change="onCurrentChange"
>
<template #empty>
<el-empty :image-size="60" description="暂无数据" style="height: 430px">
<template #image>
<Empty />
</template>
</el-empty>
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon('ri:search-line')" :title="`查看序号为${row.id}的详情`" circle plain size="small" />
</template>
</pure-table>
</template>
<style lang="scss">
.pure-table-filter {
.el-table-filter__list {
min-width: 80px;
padding: 0;
li {
line-height: 28px;
}
}
}
</style>
<style lang="scss" scoped>
:deep(.el-table) {
--el-table-border: none;
--el-table-border-color: transparent;
.el-empty__description {
margin: 0;
}
.el-scrollbar__bar {
display: none;
}
}
</style>

View File

@ -1,99 +0,0 @@
import { tableData } from '../../utils/data';
import { delay } from '@pureadmin/utils';
import { onMounted, reactive, ref } 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 }) => (
<div class='flex justify-center w-full'>
<span class='flex items-center w-[60px]'>
<span class='ml-auto mr-2'>{row.satisfaction}%</span>
<iconifyIconOffline icon={row.satisfaction > 98 ? Hearts : ThumbUp} color='#e85f33' />
</span>
</div>
),
},
{
sortable: true,
label: '统计日期',
prop: 'date',
},
{
label: '操作',
fixed: 'right',
slot: 'operation',
},
];
/** 分页配置 */
const pagination = reactive<PaginationProps>({
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,
};
}

View File

@ -1,60 +0,0 @@
<script lang="ts" setup>
import { useColumns } from './columns';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import PureTable from '@pureadmin/table';
const { loading, columns, dataList, pagination, Empty, onCurrentChange } = useColumns();
</script>
<template>
<pure-table
:columns="columns"
:data="dataList.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize)"
:loading="loading"
:loading-config="{ background: 'transparent' }"
:pagination="pagination"
alignWhole="center"
row-key="id"
showOverflowTooltip
@page-current-change="onCurrentChange"
>
<template #empty>
<el-empty :image-size="60" description="暂无数据">
<template #image>
<Empty />
</template>
</el-empty>
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon('ri:search-line')" :title="`查看序号为${row.id}的详情`" circle plain size="small" />
</template>
</pure-table>
</template>
<style lang="scss">
.pure-table-filter {
.el-table-filter__list {
min-width: 80px;
padding: 0;
li {
line-height: 28px;
}
}
}
</style>
<style lang="scss" scoped>
:deep(.el-table) {
--el-table-border: none;
--el-table-border-color: transparent;
.el-empty__description {
margin: 0;
}
.el-scrollbar__bar {
display: none;
}
}
</style>

View File

@ -1,29 +1,20 @@
<script lang="ts" setup>
import { markRaw, ref } from 'vue';
import { markRaw, onMounted } from 'vue';
import ReCol from '@/components/MyCol';
import { useDark } from './utils/utils';
import WelcomeTable from './components/table/index.vue';
import { randomGradient, useDark } from '@pureadmin/utils';
import WelcomeTable from './components/home-table.vue';
import { ReNormalCountTo } from '@/components/CountTo';
import { useRenderFlicker } from '@/components/Flicker';
import { ChartBar, ChartLine, ChartRound } from './components/charts';
import Segmented, { type OptionsType } from '@/components/Segmented';
import { barChartData, chartData, latestNewsData, progressData } from './utils/data';
defineOptions({
name: 'Welcome',
});
import ChartBar from '@/views/welcome/components/chart-bar.vue';
import ChartLine from '@/views/welcome/components/chart-line.vue';
import { chartData, expendData, expendPercent, homeRanks, incomeData, onSearch, xAxis } from '@/views/welcome/utils/hooks';
import ChartRound from '@/views/welcome/components/ChartRound.vue';
const { isDark } = useDark();
let curWeek = ref(1); // 01
const optionsBasis: Array<OptionsType> = [
{
label: '上周',
},
{
label: '本周',
},
];
onMounted(() => {
onSearch();
});
</script>
<template>
@ -47,16 +38,42 @@ const optionsBasis: Array<OptionsType> = [
{{ item.name }}
</span>
<div :style="{ backgroundColor: isDark ? 'transparent' : item.bgColor }" class="w-8 h-8 flex justify-center items-center rounded-md">
<IconifyIconOffline :color="item.color" :icon="item.icon" width="18" />
<IconifyIconOnline :color="item.color" :icon="item.icon" width="18" />
</div>
</div>
<div class="flex justify-between items-start mt-3">
<div class="w-1/2">
<ReNormalCountTo :duration="item.duration" :endVal="item.value" :fontSize="'1.6em'" :startVal="100" />
<p class="font-medium text-green-500">{{ item.percent }}</p>
<span> <ReNormalCountTo :duration="item.duration" :endVal="item.value" :fontSize="'1.6em'" :startVal="100" /> </span>
<component :is="item.percent" />
</div>
<ChartLine v-if="item.data.length > 1" :color="item.color" :data="item.data" class="!w-1/2" />
<ChartRound v-else class="!w-1/2" />
<ChartLine :color="item.color" :data="item.data" class="!w-1/2" />
</div>
</el-card>
</re-col>
<!-- 支出百分比 -->
<re-col
v-motion
:enter="{ opacity: 1, y: 0, transition: { delay: 80 * 4 } }"
:initial="{ opacity: 0, y: 100 }"
:md="12"
:sm="12"
:value="6"
:xs="24"
class="mb-[18px]"
>
<el-card class="line-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium"> 支出百分比 </span>
<div :style="{ backgroundColor: isDark ? 'transparent' : '#fff5f4' }" class="w-8 h-8 flex justify-center items-center rounded-md">
<IconifyIconOnline color="#F56C6C" icon="item.icon" width="18" />
</div>
</div>
<div class="flex justify-between items-start mt-3">
<div class="w-1/2">
<span class="font-medium text-purple-500" style="font-size: 1.6em">{{ `${expendPercent} %` }}</span>
</div>
<ChartRound :data="expendPercent" class="!w-1/2" />
</div>
</el-card>
</re-col>
@ -65,46 +82,22 @@ const optionsBasis: Array<OptionsType> = [
<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 :questionData="barChartData[curWeek].questionData" :requireData="barChartData[curWeek].requireData" />
<ChartBar :expend-data="expendData" :income-data="incomeData" :x-axis="xAxis" />
</div>
</el-card>
</re-col>
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 480 } }" :initial="{ opacity: 0, y: 100 }" :value="6" :xs="24" class="mb-[18px]">
<el-card shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">解决概率</span>
</div>
<div v-for="(item, index) in progressData" :key="index" :class="['flex', 'justify-between', 'items-start', index === 0 ? 'mt-8' : 'mt-[2.15rem]']">
<el-progress :color="item.color" :duration="item.duration" :percentage="item.percentage" :stroke-width="21" :text-inside="true" striped striped-flow />
<span class="text-nowrap ml-2 text-text_color_regular text-sm">
{{ item.week }}
</span>
</div>
</el-card>
</re-col>
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 560 } }" :initial="{ opacity: 0, y: 100 }" :value="18" :xs="24" class="mb-[18px]">
<el-card class="h-[580px]" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">数据统计</span>
</div>
<WelcomeTable class="mt-3" />
</el-card>
</re-col>
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 640 } }" :initial="{ opacity: 0, y: 100 }" :value="6" :xs="24" class="mb-[18px]">
<el-card shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">最新动态</span>
<span class="text-md font-medium">收入支出排行榜</span>
</div>
<el-scrollbar class="mt-3" max-height="504">
<el-timeline>
<el-timeline-item
v-for="(item, index) in latestNewsData"
v-for="(item, index) in homeRanks"
:key="index"
:icon="markRaw(useRenderFlicker({ background: randomGradient({ randomizeHue: true }) }))"
:timestamp="item.date"
@ -112,13 +105,24 @@ const optionsBasis: Array<OptionsType> = [
placement="top"
>
<p class="text-text_color_regular text-sm">
{{ `新增 ${item.requiredNumber} 条问题,${item.resolveNumber} 条已解决` }}
<el-text v-show="item.type === -1" type="danger">{{ '支出' }}</el-text>
<el-text v-show="item.type === 1" type="success">{{ '收入' }}</el-text>
{{ `${item.name} ${item.amount}` }}
</p>
</el-timeline-item>
</el-timeline>
</el-scrollbar>
</el-card>
</re-col>
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 560 } }" :initial="{ opacity: 0, y: 100 }" :value="24" :xs="24" class="mb-[18px]">
<el-card class="h-[580px]" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium">数据统计</span>
</div>
<WelcomeTable class="mt-3" />
</el-card>
</re-col>
</el-row>
</div>
</template>

View File

@ -1,132 +0,0 @@
import { cloneDeep, dayjs, 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, 26)
.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 };

View File

@ -0,0 +1,96 @@
import { ref } from 'vue';
import { days } from '@/enums/dateEnums';
import { fetchHomeDatalist } from '@/api/v1/system/system';
import { default as dayjs } from 'dayjs';
// 支出百分比
export const expendPercent = ref(0);
// 顶部图表数据
export const chartData = ref([]);
// 收入支出排行榜
export const homeRanks = ref([]);
// 分析概览x轴
export const xAxis = ref([]);
// 分析概览收入
export const incomeData = ref([]);
// 分析概览支出
export const expendData = ref([]);
export const onSearch = async () => {
const result = await fetchHomeDatalist();
if (result.code !== 200) {
return;
}
chartData.value = [];
// 顶部数据
const homeCard = result.data?.homeCard;
const income = homeCard?.income?.amount;
const expend = homeCard?.expend?.amount;
const incomeCharts = homeCard?.income?.charts;
const expendCharts = homeCard?.expend?.charts;
console.log(expend);
/* 收入金额 */
let data = {
icon: 'icon-park-outline:income',
bgColor: '#effaff',
color: '#67C23A',
duration: 2200,
name: '本月总收入',
value: income,
percent: <p class='font-medium text-green-500'>{`+ ${income}`}</p>,
data: incomeCharts,
};
chartData.value.push(data);
/* 支出金额 */
data = {
icon: 'icon-park-outline:expenses',
bgColor: '#fff5f4',
color: '#F56C6C',
duration: 2200,
name: '本月总支出',
value: expend,
percent: <p class='font-medium text-red-500'>{`- ${expend}`}</p>,
data: expendCharts,
};
chartData.value.push(data);
/* 盈利金额 */
const profit = incomeCharts.map((value, index) => {
const number = value - expendCharts[index];
return isNaN(number) ? 0 : number;
});
const profitValue = (income - expend).toFixed(2);
data = {
icon: 'hugeicons:profit',
bgColor: '#fff5f4',
color: '#409EFF',
duration: 2200,
name: '盈利金额',
value: Number(profitValue),
percent: <p class='font-medium text-blue-500'>{`${profitValue}`}</p>,
data: profit,
};
chartData.value.push(data);
/* 支出百分比 */
expendPercent.value = Number((expend / (income + expend)).toFixed(2)) * 100;
// 收入和支出排行榜
homeRanks.value = result.data.homeRanks.map(item => {
const transactionDate = item.transactionDate;
const date = `${dayjs(transactionDate).format('YYYY-MM-DD')} ${days[dayjs(transactionDate).day()]}`;
return { date, amount: item.amount, name: item.categoryName, type: item.type };
});
// 收入和支出分析概览
xAxis.value = result.data.homeOverview
.filter(item => item.type === 1)
.map(item => {
const transactionDate = item.transactionDate;
return `${dayjs(transactionDate).format('DD')} ${days[dayjs(transactionDate).day()]}`;
});
incomeData.value = result.data.homeOverview.filter(item => item.type === 1).map(item => item.amount);
expendData.value = result.data.homeOverview.filter(item => item.type === -1).map(item => item.amount);
};

View File

@ -1,6 +0,0 @@
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;
}