completepage: 🍻 首页内容展示
This commit is contained in:
parent
998906ba27
commit
7bdc6ed08d
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -14,6 +14,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const { isDark } = useDark();
|
||||
|
||||
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
|
||||
|
||||
const chartRef = ref();
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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";
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -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>
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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>
|
|
@ -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); // 0上周、1本周
|
||||
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>
|
||||
|
|
|
@ -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 };
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue