completepage: 🍻 收入和支出页面完成
This commit is contained in:
parent
42662ccb26
commit
998906ba27
|
@ -20,7 +20,7 @@ VITE_BASE_API_RETRY=5
|
||||||
VITE_BASE_API_RETRY_DELAY=3000
|
VITE_BASE_API_RETRY_DELAY=3000
|
||||||
|
|
||||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
||||||
VITE_CDN=false
|
VITE_CDN=true
|
||||||
|
|
||||||
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
||||||
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const buildEnvironment = () => {
|
||||||
manualChunks: id => {
|
manualChunks: id => {
|
||||||
// 如果是包含在包中则打包成 vendor
|
// 如果是包含在包中则打包成 vendor
|
||||||
if (id.includes('node_modules')) {
|
if (id.includes('node_modules')) {
|
||||||
return `vendor`;
|
return id.toString().split('node_modules/')[1].split('/')[1].toString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
|
@ -8,6 +8,11 @@ export const fetchGetUserBillList = (data: any) => {
|
||||||
return http.request<BaseResult<ResultTable>>('get', `bill/noManage/getUserBillList/${data.currentPage}/${data.pageSize}`, { params: data });
|
return http.request<BaseResult<ResultTable>>('get', `bill/noManage/getUserBillList/${data.currentPage}/${data.pageSize}`, { params: data });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 账单信息---账单收入和支出 */
|
||||||
|
export const fetchGetExpendOrIncome = (data: any) => {
|
||||||
|
return http.request<BaseResult<object>>('get', 'bill/noManage/getExpendOrIncome', { params: data });
|
||||||
|
};
|
||||||
|
|
||||||
/** 账单信息---添加账单信息 */
|
/** 账单信息---添加账单信息 */
|
||||||
export const fetchAddUserBill = (data: any) => {
|
export const fetchAddUserBill = (data: any) => {
|
||||||
return http.request<BaseResult<object>>('post', 'bill/noManage/addUserBill', { data });
|
return http.request<BaseResult<object>>('post', 'bill/noManage/addUserBill', { data });
|
|
@ -0,0 +1,166 @@
|
||||||
|
<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,
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
// 类别
|
||||||
|
{
|
||||||
|
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: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useDark, useECharts } from '@pureadmin/utils';
|
||||||
|
import { computed, nextTick, type PropType, ref, watch } from 'vue';
|
||||||
|
import { $t } from '@/plugins/i18n';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
analyseData: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
analyseXAxis: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
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',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
padding: 0,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true, // 去网格线
|
||||||
|
},
|
||||||
|
position: 'left',
|
||||||
|
name: $t('unitMoney'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
itemStyle: {
|
||||||
|
color: props.color[0],
|
||||||
|
borderRadius: [10, 10, 0, 0],
|
||||||
|
},
|
||||||
|
data: props.analyseData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 15,
|
||||||
|
itemStyle: {
|
||||||
|
color: props.color[1],
|
||||||
|
borderRadius: [10, 10, 0, 0],
|
||||||
|
},
|
||||||
|
data: props.analyseData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="chartRef" style="width: 100%; height: 365px" />
|
||||||
|
</template>
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useDark, useECharts } from '@pureadmin/utils';
|
||||||
|
import { computed, nextTick, type PropType, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
categoryData: {
|
||||||
|
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({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
textStyle: {
|
||||||
|
color: '#606266',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
},
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: [16, 130],
|
||||||
|
roseType: 'area',
|
||||||
|
center: ['50%', '43%'],
|
||||||
|
labelLine: { length: 3, length2: 6, smooth: true },
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [5, 5, 5, 5],
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
position: 'outer',
|
||||||
|
alignTo: 'edge',
|
||||||
|
edgeDistance: 10,
|
||||||
|
},
|
||||||
|
data: props.categoryData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="chartRef" style="width: 100%; height: 373px" />
|
||||||
|
</template>
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" class="empty-icon" viewBox="0 0 1024 1024"><path d="M855.6 427.2H168.5c-12.7 0-24.4 6.9-30.6 18L4.4 684.7C1.5 689.9 0 695.8 0 701.8v287.1c0 19.4 15.7 35.1 35.1 35.1H989c19.4 0 35.1-15.7 35.1-35.1V701.8c0-6-1.5-11.8-4.4-17.1L886.2 445.2c-6.2-11.1-17.9-18-30.6-18M673.4 695.6c-16.5 0-30.8 11.5-34.3 27.7-12.7 58.5-64.8 102.3-127.2 102.3s-114.5-43.8-127.2-102.3c-3.5-16.1-17.8-27.7-34.3-27.7H119c-26.4 0-43.3-28-31.1-51.4l81.7-155.8c6.1-11.6 18-18.8 31.1-18.8h622.4c13 0 25 7.2 31.1 18.8l81.7 155.8c12.2 23.4-4.7 51.4-31.1 51.4zm146.5-486.1c-1-1.8-2.1-3.7-3.2-5.5-9.8-16.6-31.1-22.2-47.8-12.6L648.5 261c-17 9.8-22.7 31.6-12.6 48.4.9 1.4 1.7 2.9 2.5 4.4 9.5 17 31.2 22.8 48 13L807 257.3c16.7-9.7 22.4-31 12.9-47.8m-444.5 51.6L255 191.6c-16.7-9.6-38-4-47.8 12.6-1.1 1.8-2.1 3.6-3.2 5.5-9.5 16.8-3.8 38.1 12.9 47.8L337.3 327c16.9 9.7 38.6 4 48-13.1.8-1.5 1.7-2.9 2.5-4.4 10.2-16.8 4.5-38.6-12.4-48.4M512 239.3h2.5c19.5.3 35.5-15.5 35.5-35.1v-139c0-19.3-15.6-34.9-34.8-35.1h-6.4C489.6 30.3 474 46 474 65.2v139c0 19.5 15.9 35.4 35.5 35.1z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,169 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { randomGradient, useDark } from '@pureadmin/utils';
|
||||||
|
import { markRaw, type PropType, ref } from 'vue';
|
||||||
|
import { useRenderFlicker } from '@/components/Flicker';
|
||||||
|
import ReCol from '@/components/MyCol';
|
||||||
|
import CharLine from '@/components/Analyse/char-line.vue';
|
||||||
|
import CharPie from '@/components/Analyse/char-pie.vue';
|
||||||
|
import { $t } from '@/plugins/i18n';
|
||||||
|
import { currentMouth, currentWeek, currentYear } from '@/enums/dateEnums';
|
||||||
|
import AnalyseTable from '@/components/Analyse/analyse-table.vue';
|
||||||
|
|
||||||
|
interface Title {
|
||||||
|
analyse: string;
|
||||||
|
category: string;
|
||||||
|
table: string;
|
||||||
|
top: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: Object as PropType<Title> },
|
||||||
|
financialType: { type: Number as PropType<any> },
|
||||||
|
color: {
|
||||||
|
type: Array as PropType<Array<string>>,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
analyseXAxis: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
analyseData: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
categoryData: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
topData: {
|
||||||
|
type: Array as PropType<Array<number>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['emitDateRange']);
|
||||||
|
|
||||||
|
const { isDark } = useDark();
|
||||||
|
|
||||||
|
// 选择查询日期范围
|
||||||
|
const dateRange = ref(props.date);
|
||||||
|
|
||||||
|
// 日期范围选择
|
||||||
|
const shortcuts = [
|
||||||
|
{ text: $t('thisWeek'), value: currentWeek },
|
||||||
|
{ text: $t('thisMonth'), value: currentMouth },
|
||||||
|
{ text: $t('thisYear'), value: currentYear },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 选择查询日期 */
|
||||||
|
const onChangeDateRange = () => {
|
||||||
|
const value = dateRange.value;
|
||||||
|
emits('emitDateRange', value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row :gutter="24" justify="space-around">
|
||||||
|
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 400 } }" :initial="{ opacity: 0, y: 100 }" :value="18" :xs="24" class="mb-[18px]">
|
||||||
|
<el-card class="bar-card" shadow="never">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-md font-medium">{{ title.analyse }}</span>
|
||||||
|
<span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
:end-placeholder="$t('endDate')"
|
||||||
|
:shortcuts="shortcuts"
|
||||||
|
:start-placeholder="$t('startDate')"
|
||||||
|
class="!w-[230px]"
|
||||||
|
clearable
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
@change="onChangeDateRange"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-start mt-3">
|
||||||
|
<char-line :analyse-data="analyseData" :analyse-x-axis="analyseXAxis" :color="color" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</re-col>
|
||||||
|
|
||||||
|
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 400 } }" :initial="{ opacity: 0, y: 100 }" :value="6" :xs="24" class="mb-[18px]">
|
||||||
|
<el-card class="bar-card" shadow="never">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-md font-medium">{{ title.category }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-start mt-3">
|
||||||
|
<char-pie :category-data="categoryData" />
|
||||||
|
</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">{{ title.table }}</span>
|
||||||
|
</div>
|
||||||
|
<analyse-table :endDate="dateRange[1]" :financialType="financialType" :startDate="dateRange[0]" class="mt-3" />
|
||||||
|
</el-card>
|
||||||
|
</re-col>
|
||||||
|
|
||||||
|
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 560 } }" :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">{{ title.top }}</span>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar class="mt-3 h-[505px]" max-height="505">
|
||||||
|
<el-timeline>
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="(item, index) in topData"
|
||||||
|
:key="index"
|
||||||
|
:icon="markRaw(useRenderFlicker({ background: randomGradient({ randomizeHue: true }) }))"
|
||||||
|
:timestamp="item.date"
|
||||||
|
center
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<p class="text-text_color_regular text-sm">
|
||||||
|
{{ `${item.name} - ${item.amount}¥` }}
|
||||||
|
</p>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-card>
|
||||||
|
</re-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 传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: () => []
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export const currentWeek = [dayjs().startOf('week').add(1, 'day'), dayjs().endOf('week').add(1, 'day')];
|
||||||
|
export const currentMouth = [dayjs().startOf('month'), dayjs().endOf('month').add(1, 'day')];
|
||||||
|
export const currentYear = [dayjs().startOf('year'), dayjs().endOf('year')];
|
||||||
|
export const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
|
@ -8,8 +8,7 @@ const Copyright = getConfig('Copyright');
|
||||||
<template>
|
<template>
|
||||||
<footer class="layout-footer text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]">
|
<footer class="layout-footer text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]">
|
||||||
{{ Copyright }}
|
{{ Copyright }}
|
||||||
<a class="hover:text-primary" href="https://github.com/pure-admin" target="_blank"> {{ TITLE }} </a>
|
<a class="hover:text-primary" href="/" target="_blank"> {{ TITLE }} </a>
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank"> 苏ICP备2023044078号-2</a>
|
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default [
|
||||||
rank: 1,
|
rank: 1,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
// 账单查询
|
// 账单概览
|
||||||
{
|
{
|
||||||
path: '/financial-user/bill',
|
path: '/financial-user/bill',
|
||||||
name: 'BillUser',
|
name: 'BillUser',
|
||||||
|
@ -20,7 +20,28 @@ export default [
|
||||||
title: 'billManagement',
|
title: 'billManagement',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 用户分类
|
|
||||||
|
// 支出分析
|
||||||
|
{
|
||||||
|
path: '/financial-user/expend',
|
||||||
|
name: 'ExpendUser',
|
||||||
|
component: () => import('@/views/financial-user/account-bill/expend/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'icon-park-outline:expenses',
|
||||||
|
title: 'expendAnalyse',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 收入分析
|
||||||
|
{
|
||||||
|
path: '/financial-user/income',
|
||||||
|
name: 'IncomeUser',
|
||||||
|
component: () => import('@/views/financial-user/account-bill/income/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'icon-park-outline:income',
|
||||||
|
title: 'incomeAnalyse',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 账单分类管理
|
||||||
{
|
{
|
||||||
path: '/financial-user/category',
|
path: '/financial-user/category',
|
||||||
name: 'CategoryUser',
|
name: 'CategoryUser',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddBill, fetchDeleteBill, fetchGetBillList, fetchUpdateBill } from '@/api/v1/financial/bill';
|
import { fetchAddBill, fetchDeleteBill, fetchGetBillList, fetchUpdateBill } from '@/api/v1/financial/admin/bill';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddBudgetCategory, fetchDeleteBudgetCategory, fetchGetBudgetCategoryList, fetchUpdateBudgetCategory } from '@/api/v1/financial/budgetCategory';
|
import {
|
||||||
|
fetchAddBudgetCategory,
|
||||||
|
fetchDeleteBudgetCategory,
|
||||||
|
fetchGetBudgetCategoryList,
|
||||||
|
fetchUpdateBudgetCategory,
|
||||||
|
} from '@/api/v1/financial/admin/budgetCategory';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddCategory, fetchDeleteCategory, fetchGetCategoryAllList, fetchGetCategoryList, fetchUpdateCategory } from '@/api/v1/financial/category';
|
import { fetchAddCategory, fetchDeleteCategory, fetchGetCategoryAllList, fetchGetCategoryList, fetchUpdateCategory } from '@/api/v1/financial/admin/category';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
fetchDeleteDebtRepaymentPlan,
|
fetchDeleteDebtRepaymentPlan,
|
||||||
fetchGetDebtRepaymentPlanList,
|
fetchGetDebtRepaymentPlanList,
|
||||||
fetchUpdateDebtRepaymentPlan,
|
fetchUpdateDebtRepaymentPlan,
|
||||||
} from '@/api/v1/financial/debtRepaymentPlan';
|
} from '@/api/v1/financial/admin/debtRepaymentPlan';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddDebtTracking, fetchDeleteDebtTracking, fetchGetDebtTrackingList, fetchUpdateDebtTracking } from '@/api/v1/financial/debtTracking';
|
import { fetchAddDebtTracking, fetchDeleteDebtTracking, fetchGetDebtTrackingList, fetchUpdateDebtTracking } from '@/api/v1/financial/admin/debtTracking';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddSavingGoal, fetchDeleteSavingGoal, fetchGetSavingGoalList, fetchUpdateSavingGoal } from '@/api/v1/financial/savingGoal';
|
import { fetchAddSavingGoal, fetchDeleteSavingGoal, fetchGetSavingGoalList, fetchUpdateSavingGoal } from '@/api/v1/financial/admin/savingGoal';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { fetchAddUserBill, fetchDeleteUserBill, fetchGetUserBillList, fetchUpdateUserBill } from '@/api/v1/financialUser/billUser';
|
import { fetchAddUserBill, fetchDeleteUserBill, fetchGetUserBillList, fetchUpdateUserBill } from '@/api/v1/financial/user/billUser';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
@ -13,6 +13,8 @@ export const useBillUserStore = defineStore('billUserStore', {
|
||||||
return {
|
return {
|
||||||
// 账单信息列表
|
// 账单信息列表
|
||||||
datalist: [],
|
datalist: [],
|
||||||
|
// 收入和支出
|
||||||
|
expendWithIncomeList: [],
|
||||||
// 查询表单
|
// 查询表单
|
||||||
form: {
|
form: {
|
||||||
// 类型:1 - 收入,-1 - 支出
|
// 类型:1 - 收入,-1 - 支出
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
fetchDeleteUserBudgetCategory,
|
fetchDeleteUserBudgetCategory,
|
||||||
fetchGetUserBudgetCategoryList,
|
fetchGetUserBudgetCategoryList,
|
||||||
fetchUpdateUserBudgetCategory,
|
fetchUpdateUserBudgetCategory,
|
||||||
} from '@/api/v1/financialUser/budgetCategoryUser';
|
} from '@/api/v1/financial/user/budgetCategoryUser';
|
||||||
import { getDefaultDateRange } from '@/utils/date';
|
import { getDefaultDateRange } from '@/utils/date';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
fetchGetCategoryUserAllList,
|
fetchGetCategoryUserAllList,
|
||||||
fetchGetCategoryUserList,
|
fetchGetCategoryUserList,
|
||||||
fetchUpdateCategoryUser,
|
fetchUpdateCategoryUser,
|
||||||
} from '@/api/v1/financialUser/categoryUser';
|
} from '@/api/v1/financial/user/categoryUser';
|
||||||
import { pageSizes } from '@/enums/baseConstant';
|
import { pageSizes } from '@/enums/baseConstant';
|
||||||
import { storeMessage } from '@/utils/message';
|
import { storeMessage } from '@/utils/message';
|
||||||
import { storePagination } from '@/store/useStorePagination';
|
import { storePagination } from '@/store/useStorePagination';
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
fetchDeleteUserDebtRepaymentPlan,
|
fetchDeleteUserDebtRepaymentPlan,
|
||||||
fetchGetUserDebtRepaymentPlanList,
|
fetchGetUserDebtRepaymentPlanList,
|
||||||
fetchUpdateUserDebtRepaymentPlan,
|
fetchUpdateUserDebtRepaymentPlan,
|
||||||
} from '@/api/v1/financialUser/debtRepaymentPlanUser';
|
} from '@/api/v1/financial/user/debtRepaymentPlanUser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 债务还款计划表 Store
|
* 债务还款计划表 Store
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
fetchDeleteUserDebtTracking,
|
fetchDeleteUserDebtTracking,
|
||||||
fetchGetUserDebtTrackingList,
|
fetchGetUserDebtTrackingList,
|
||||||
fetchUpdateUserDebtTracking,
|
fetchUpdateUserDebtTracking,
|
||||||
} from '@/api/v1/financialUser/debtTrackingUser';
|
} from '@/api/v1/financial/user/debtTrackingUser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 债务追踪 Store
|
* 债务追踪 Store
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
fetchDeleteUserSavingGoal,
|
fetchDeleteUserSavingGoal,
|
||||||
fetchGetUserSavingGoalList,
|
fetchGetUserSavingGoalList,
|
||||||
fetchUpdateUserSavingGoal,
|
fetchUpdateUserSavingGoal,
|
||||||
} from '@/api/v1/financialUser/savingGoalUser';
|
} from '@/api/v1/financial/user/savingGoalUser';
|
||||||
import { getDefaultDateRange } from '@/utils/date';
|
import { getDefaultDateRange } from '@/utils/date';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { columns } from '@/views/configuration/emailTemplate/utils/columns';
|
||||||
import PureTableBar from '@/components/TableBar/src/bar';
|
import PureTableBar from '@/components/TableBar/src/bar';
|
||||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||||
import PureTable from '@pureadmin/table';
|
import PureTable from '@pureadmin/table';
|
||||||
import { onAdd, onDelete, onDeleteBatch, onSearch, onUpdate, selectRows } from '@/views/configuration/emailTemplate/utils/hooks';
|
import { onAdd, onDelete, onDeleteBatch, onSearch, onUpdate, selectRows, viewTemplate } from '@/views/configuration/emailTemplate/utils/hooks';
|
||||||
import Delete from '@iconify-icons/ep/delete';
|
import Delete from '@iconify-icons/ep/delete';
|
||||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||||
import Refresh from '@iconify-icons/ep/refresh';
|
import Refresh from '@iconify-icons/ep/refresh';
|
||||||
|
@ -14,6 +14,7 @@ import { useEmailTemplateStore } from '@/store/configuration/emailTemplate.ts';
|
||||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||||
import { auth } from '@/views/configuration/emailTemplate/utils/auth';
|
import { auth } from '@/views/configuration/emailTemplate/utils/auth';
|
||||||
import { hasAuth } from '@/router/utils';
|
import { hasAuth } from '@/router/utils';
|
||||||
|
import View from '@iconify-icons/ep/view';
|
||||||
|
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
|
@ -122,7 +123,12 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #operation="{ row }">
|
<template #operation="{ row }">
|
||||||
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
|
<el-button :icon="useRenderIcon(View)" :size="size" class="reset-margin" link type="primary" @click="viewTemplate(row.body)">
|
||||||
|
{{ $t('view') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)">
|
||||||
|
{{ $t('modify') }}
|
||||||
|
</el-button>
|
||||||
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.templateName}?`" @confirm="onDelete(row)">
|
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.templateName}?`" @confirm="onDelete(row)">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
|
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
|
||||||
|
|
|
@ -133,3 +133,14 @@ export const onDeleteBatch = async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 查看模板 */
|
||||||
|
export const viewTemplate = (template: string) => {
|
||||||
|
addDialog({
|
||||||
|
title: $t('view'),
|
||||||
|
draggable: true,
|
||||||
|
fullscreenIcon: true,
|
||||||
|
closeOnClickModal: false,
|
||||||
|
contentRenderer: () => <div v-html={template} />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<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';
|
||||||
|
|
||||||
|
const title = {
|
||||||
|
analyse: $t('expenditureAnalysis'),
|
||||||
|
category: $t('paymentLedger'),
|
||||||
|
table: $t('dataStatistics'),
|
||||||
|
top: $t('rankingList'),
|
||||||
|
};
|
||||||
|
// 分析概览
|
||||||
|
const analyseXAxis = ref([]);
|
||||||
|
const analyseData = ref([]);
|
||||||
|
// 分类数据
|
||||||
|
const categoryList = ref([]);
|
||||||
|
// 排行榜数据
|
||||||
|
const topList = ref([]);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
type: -1,
|
||||||
|
startDate: dayjs(currentMouth[0]).format('YYYY-MM-DD'),
|
||||||
|
endDate: dayjs(currentMouth[1]).format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 查询账单支出 */
|
||||||
|
const onSearch = async () => {
|
||||||
|
// 格式化请求数据
|
||||||
|
form.startDate = dayjs(form.startDate).format('YYYY-MM-DD');
|
||||||
|
form.endDate = dayjs(form.endDate).format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const result = await fetchGetExpendOrIncome(form);
|
||||||
|
if (result.code !== 200) return;
|
||||||
|
|
||||||
|
// 分析概览
|
||||||
|
analyseData.value = [];
|
||||||
|
analyseXAxis.value = [];
|
||||||
|
result.data.list.forEach(item => {
|
||||||
|
const transactionDate = item.transactionDate;
|
||||||
|
const date = `${dayjs(transactionDate).format('MM-DD')} ${days[dayjs(transactionDate).day()]}`;
|
||||||
|
analyseData.value.push(item.amount);
|
||||||
|
analyseXAxis.value.push(date);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分类数据
|
||||||
|
categoryList.value = result.data.categoryAmounts.map(categoryAmount => ({
|
||||||
|
value: categoryAmount.amount,
|
||||||
|
name: categoryAmount.categoryName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 排行榜
|
||||||
|
topList.value = result.data.list.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 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 选择查询日期 */
|
||||||
|
const onChangeDateRange = async dateRange => {
|
||||||
|
form.startDate = dateRange[0];
|
||||||
|
form.endDate = dateRange[1];
|
||||||
|
await onSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
onSearch();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Analyse
|
||||||
|
:analyse-data="analyseData"
|
||||||
|
:analyseXAxis="analyseXAxis"
|
||||||
|
:category-data="categoryList"
|
||||||
|
:color="['#41b6ff', '#F56C6C']"
|
||||||
|
:date="[form.startDate, form.endDate]"
|
||||||
|
:financialType="form.type"
|
||||||
|
:title="title"
|
||||||
|
:top-data="topList"
|
||||||
|
@emit-date-range="onChangeDateRange"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,89 @@
|
||||||
|
<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';
|
||||||
|
|
||||||
|
const title = {
|
||||||
|
analyse: $t('revenueAnalysis'),
|
||||||
|
category: $t('incomeBrackets'),
|
||||||
|
table: $t('dataStatistics'),
|
||||||
|
top: $t('rankingList'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分析概览
|
||||||
|
const analyseXAxis = ref([]);
|
||||||
|
const analyseData = ref([]);
|
||||||
|
// 分类数据
|
||||||
|
const categoryList = ref([]);
|
||||||
|
// 排行榜数据
|
||||||
|
const topList = ref([]);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
type: 1,
|
||||||
|
startDate: dayjs(currentMouth[0]).format('YYYY-MM-DD'),
|
||||||
|
endDate: dayjs(currentMouth[1]).format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 查询账单收入 */
|
||||||
|
const onSearch = async () => {
|
||||||
|
// 格式化请求数据
|
||||||
|
form.startDate = dayjs(form.startDate).format('YYYY-MM-DD');
|
||||||
|
form.endDate = dayjs(form.endDate).format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const result = await fetchGetExpendOrIncome(form);
|
||||||
|
if (result.code !== 200) return;
|
||||||
|
|
||||||
|
// 分析概览
|
||||||
|
analyseData.value = [];
|
||||||
|
analyseXAxis.value = [];
|
||||||
|
result.data.list.forEach(item => {
|
||||||
|
const transactionDate = item.transactionDate;
|
||||||
|
const date = `${dayjs(transactionDate).format('MM-DD')} ${days[dayjs(transactionDate).day()]}`;
|
||||||
|
analyseData.value.push(item.amount);
|
||||||
|
analyseXAxis.value.push(date);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分类数据
|
||||||
|
categoryList.value = result.data.categoryAmounts.map(categoryAmount => ({
|
||||||
|
value: categoryAmount.amount,
|
||||||
|
name: categoryAmount.categoryName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 排行榜
|
||||||
|
topList.value = result.data.list.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 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 选择查询日期 */
|
||||||
|
const onChangeDateRange = async dateRange => {
|
||||||
|
form.startDate = dateRange[0];
|
||||||
|
form.endDate = dateRange[1];
|
||||||
|
await onSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
onSearch();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Analyse
|
||||||
|
:analyse-data="analyseData"
|
||||||
|
:analyseXAxis="analyseXAxis"
|
||||||
|
:category-data="categoryList"
|
||||||
|
:color="['#41b6ff', '#67C23A']"
|
||||||
|
:date="[form.startDate, form.endDate]"
|
||||||
|
:financialType="form.type"
|
||||||
|
:title="title"
|
||||||
|
:top-data="topList"
|
||||||
|
@emit-date-range="onChangeDateRange"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -6,7 +6,7 @@ import { FormProps } from '@/views/financial-user/budget-saving/budget-category/
|
||||||
import { $t } from '@/plugins/i18n';
|
import { $t } from '@/plugins/i18n';
|
||||||
import { useAdminUserStore } from '@/store/system/adminUser';
|
import { useAdminUserStore } from '@/store/system/adminUser';
|
||||||
import { budget } from '@/enums/bill/budget';
|
import { budget } from '@/enums/bill/budget';
|
||||||
import { fetchGetAllUserParentList } from '@/api/v1/financialUser/budgetCategoryUser';
|
import { fetchGetAllUserParentList } from '@/api/v1/financial/user/budgetCategoryUser';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<FormProps>(), {
|
const props = withDefaults(defineProps<FormProps>(), {
|
||||||
formInline: () => ({
|
formInline: () => ({
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { FormProps } from '@/views/financial/budget-saving/budget-category/utils
|
||||||
import { $t } from '@/plugins/i18n';
|
import { $t } from '@/plugins/i18n';
|
||||||
import LoadingSvg from '@/assets/svg/loading.svg';
|
import LoadingSvg from '@/assets/svg/loading.svg';
|
||||||
import { useAdminUserStore } from '@/store/system/adminUser';
|
import { useAdminUserStore } from '@/store/system/adminUser';
|
||||||
import { fetchGetAllParentList } from '@/api/v1/financial/budgetCategory';
|
import { fetchGetAllParentList } from '@/api/v1/financial/admin/budgetCategory';
|
||||||
import { budget } from '@/enums/bill/budget';
|
import { budget } from '@/enums/bill/budget';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<FormProps>(), {
|
const props = withDefaults(defineProps<FormProps>(), {
|
||||||
|
|
|
@ -14,7 +14,6 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const { isDark } = useDark();
|
const { isDark } = useDark();
|
||||||
|
|
||||||
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
|
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
|
||||||
|
|
||||||
const chartRef = ref();
|
const chartRef = ref();
|
||||||
|
|
|
@ -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: 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>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,73 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as ChartBar } from "./ChartBar.vue";
|
||||||
|
export { default as ChartLine } from "./ChartLine.vue";
|
||||||
|
export { default as ChartRound } from "./ChartRound.vue";
|
|
@ -1,23 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { decode } from 'js-base64';
|
|
||||||
import { MdPreview } from 'md-editor-v3';
|
|
||||||
import 'md-editor-v3/lib/preview.css';
|
|
||||||
|
|
||||||
const content = ref();
|
|
||||||
|
|
||||||
/** 获取文档信息 */
|
|
||||||
const onSearch = async () => {
|
|
||||||
const response = await fetch('http://129.211.31.58:3000/api/v1/repos/auth/auth-server-java/contents/ReadMe.md');
|
|
||||||
const json = await response.json();
|
|
||||||
content.value = decode(json.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
onSearch();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MdPreview id="server-read-me" :modelValue="content" />
|
|
||||||
</template>
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" class="empty-icon" viewBox="0 0 1024 1024"><path d="M855.6 427.2H168.5c-12.7 0-24.4 6.9-30.6 18L4.4 684.7C1.5 689.9 0 695.8 0 701.8v287.1c0 19.4 15.7 35.1 35.1 35.1H989c19.4 0 35.1-15.7 35.1-35.1V701.8c0-6-1.5-11.8-4.4-17.1L886.2 445.2c-6.2-11.1-17.9-18-30.6-18M673.4 695.6c-16.5 0-30.8 11.5-34.3 27.7-12.7 58.5-64.8 102.3-127.2 102.3s-114.5-43.8-127.2-102.3c-3.5-16.1-17.8-27.7-34.3-27.7H119c-26.4 0-43.3-28-31.1-51.4l81.7-155.8c6.1-11.6 18-18.8 31.1-18.8h622.4c13 0 25 7.2 31.1 18.8l81.7 155.8c12.2 23.4-4.7 51.4-31.1 51.4zm146.5-486.1c-1-1.8-2.1-3.7-3.2-5.5-9.8-16.6-31.1-22.2-47.8-12.6L648.5 261c-17 9.8-22.7 31.6-12.6 48.4.9 1.4 1.7 2.9 2.5 4.4 9.5 17 31.2 22.8 48 13L807 257.3c16.7-9.7 22.4-31 12.9-47.8m-444.5 51.6L255 191.6c-16.7-9.6-38-4-47.8 12.6-1.1 1.8-2.1 3.6-3.2 5.5-9.5 16.8-3.8 38.1 12.9 47.8L337.3 327c16.9 9.7 38.6 4 48-13.1.8-1.5 1.7-2.9 2.5-4.4 10.2-16.8 4.5-38.6-12.4-48.4M512 239.3h2.5c19.5.3 35.5-15.5 35.5-35.1v-139c0-19.3-15.6-34.9-34.8-35.1h-6.4C489.6 30.3 474 46 474 65.2v139c0 19.5 15.9 35.4 35.5 35.1z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,60 @@
|
||||||
|
<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,23 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { decode } from 'js-base64';
|
|
||||||
import { MdPreview } from 'md-editor-v3';
|
|
||||||
import 'md-editor-v3/lib/preview.css';
|
|
||||||
|
|
||||||
const content = ref();
|
|
||||||
|
|
||||||
/** 获取文档信息 */
|
|
||||||
const onSearch = async () => {
|
|
||||||
const response = await fetch('http://129.211.31.58:3000/api/v1/repos/auth/auth-web/contents/ReadMe.md');
|
|
||||||
const json = await response.json();
|
|
||||||
content.value = decode(json.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
onSearch();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MdPreview id="web-read-me" :modelValue="content" />
|
|
||||||
</template>
|
|
|
@ -1,30 +1,29 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { markRaw, ref } from 'vue';
|
||||||
import ReCol from '@/components/MyCol';
|
import ReCol from '@/components/MyCol';
|
||||||
import { useDark } from './utils/utils';
|
import { useDark } from './utils/utils';
|
||||||
|
import WelcomeTable from './components/table/index.vue';
|
||||||
import { ReNormalCountTo } from '@/components/CountTo';
|
import { ReNormalCountTo } from '@/components/CountTo';
|
||||||
import ChartLine from '@/views/welcome/components/ChartLine.vue';
|
import { useRenderFlicker } from '@/components/Flicker';
|
||||||
import ChartRound from '@/views/welcome/components/ChartRound.vue';
|
import { ChartBar, ChartLine, ChartRound } from './components/charts';
|
||||||
import { chartData } from './utils/data';
|
import Segmented, { type OptionsType } from '@/components/Segmented';
|
||||||
import { getServerCommitList, getWebCommitList, serverCommitList, webCommitList } from '@/views/welcome/utils/hooks';
|
import { barChartData, chartData, latestNewsData, progressData } from './utils/data';
|
||||||
import WebReadMe from '@/views/welcome/components/web-read-me.vue';
|
|
||||||
import { TabsPaneContext } from 'element-plus';
|
defineOptions({
|
||||||
import ServerReadMe from '@/views/welcome/components/server-read-me.vue';
|
name: 'Welcome',
|
||||||
|
});
|
||||||
|
|
||||||
const { isDark } = useDark();
|
const { isDark } = useDark();
|
||||||
|
|
||||||
// 当前tab名称
|
let curWeek = ref(1); // 0上周、1本周
|
||||||
const activeName = ref('web');
|
const optionsBasis: Array<OptionsType> = [
|
||||||
|
{
|
||||||
// 修改tab名称
|
label: '上周',
|
||||||
const onTabClick = (tab: TabsPaneContext, _: Event) => {
|
},
|
||||||
activeName.value = tab.paneName;
|
{
|
||||||
};
|
label: '本周',
|
||||||
|
},
|
||||||
onMounted(() => {
|
];
|
||||||
getWebCommitList();
|
|
||||||
getServerCommitList();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -62,64 +61,64 @@ onMounted(() => {
|
||||||
</el-card>
|
</el-card>
|
||||||
</re-col>
|
</re-col>
|
||||||
|
|
||||||
<el-row :gutter="24" class="w-[100%] justify-around">
|
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 400 } }" :initial="{ opacity: 0, y: 100 }" :value="18" :xs="24" class="mb-[18px]">
|
||||||
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 560 } }" :initial="{ opacity: 0, y: 100 }" :lg="16" :sm="24" :xl="18" :xs="24" class="mb-[18px]">
|
<el-card class="bar-card" shadow="never">
|
||||||
<el-card class="h-[1178px] overflow-y-auto" shadow="never">
|
<div class="flex justify-between">
|
||||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="onTabClick">
|
<span class="text-md font-medium">分析概览</span>
|
||||||
<el-tab-pane label="前端文档" name="web">
|
<Segmented v-model="curWeek" :options="optionsBasis" />
|
||||||
<web-read-me class="mt-3 h-[100%]" />
|
</div>
|
||||||
</el-tab-pane>
|
<div class="flex justify-between items-start mt-3">
|
||||||
<el-tab-pane label="后端文档" name="server">
|
<ChartBar :questionData="barChartData[curWeek].questionData" :requireData="barChartData[curWeek].requireData" />
|
||||||
<server-read-me class="mt-3 h-[100%]" />
|
</div>
|
||||||
</el-tab-pane>
|
</el-card>
|
||||||
</el-tabs>
|
</re-col>
|
||||||
</el-card>
|
|
||||||
</re-col>
|
|
||||||
|
|
||||||
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 640 } }" :initial="{ opacity: 0, y: 100 }" :lg="8" :sm="24" :xl="6" :xs="24">
|
<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 class="mb-[18px]" shadow="never">
|
<el-card shadow="never">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-md font-medium">前端git近期20次更改</span>
|
<span class="text-md font-medium">解决概率</span>
|
||||||
</div>
|
</div>
|
||||||
<el-scrollbar class="mt-3" max-height="504">
|
<div v-for="(item, index) in progressData" :key="index" :class="['flex', 'justify-between', 'items-start', index === 0 ? 'mt-8' : 'mt-[2.15rem]']">
|
||||||
<el-timeline>
|
<el-progress :color="item.color" :duration="item.duration" :percentage="item.percentage" :stroke-width="21" :text-inside="true" striped striped-flow />
|
||||||
<el-timeline-item v-for="(item, index) in webCommitList" :key="index" :timestamp="item.date" center placement="top">
|
<span class="text-nowrap ml-2 text-text_color_regular text-sm">
|
||||||
<p class="text-text_color_regular text-sm">
|
{{ item.week }}
|
||||||
<el-link :href="item.html_url" :title="item.message" :underline="false" target="_blank">
|
</span>
|
||||||
{{ `提交信息:${item.message},提交用户:` }}
|
</div>
|
||||||
</el-link>
|
</el-card>
|
||||||
<el-link :href="item.url" :title="item.username" :underline="false" target="_blank">
|
</re-col>
|
||||||
<el-avatar :size="16" :src="item.avatar_url" class="align-middle" />
|
|
||||||
{{ ` ${item.username}` }}
|
|
||||||
</el-link>
|
|
||||||
</p>
|
|
||||||
</el-timeline-item>
|
|
||||||
</el-timeline>
|
|
||||||
</el-scrollbar>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card shadow="never">
|
<re-col v-motion :enter="{ opacity: 1, y: 0, transition: { delay: 560 } }" :initial="{ opacity: 0, y: 100 }" :value="18" :xs="24" class="mb-[18px]">
|
||||||
<div class="flex justify-between">
|
<el-card class="h-[580px]" shadow="never">
|
||||||
<span class="text-md font-medium">后端git近期20次更改</span>
|
<div class="flex justify-between">
|
||||||
</div>
|
<span class="text-md font-medium">数据统计</span>
|
||||||
<el-scrollbar class="mt-3" max-height="504">
|
</div>
|
||||||
<el-timeline>
|
<WelcomeTable class="mt-3" />
|
||||||
<el-timeline-item v-for="(item, index) in serverCommitList" :key="index" :timestamp="item.date" center placement="top">
|
</el-card>
|
||||||
<p class="text-text_color_regular text-sm">
|
</re-col>
|
||||||
<el-link :href="item.html_url" :title="item.message" :underline="false" target="_blank">
|
|
||||||
{{ `提交信息:${item.message},提交用户:` }}
|
<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-link>
|
<el-card shadow="never">
|
||||||
<el-link :href="item.url" :title="item.username" :underline="false" target="_blank">
|
<div class="flex justify-between">
|
||||||
<el-avatar :size="16" :src="item.avatar_url" class="align-middle" />
|
<span class="text-md font-medium">最新动态</span>
|
||||||
{{ ` ${item.username}` }}
|
</div>
|
||||||
</el-link>
|
<el-scrollbar class="mt-3" max-height="504">
|
||||||
</p>
|
<el-timeline>
|
||||||
</el-timeline-item>
|
<el-timeline-item
|
||||||
</el-timeline>
|
v-for="(item, index) in latestNewsData"
|
||||||
</el-scrollbar>
|
:key="index"
|
||||||
</el-card>
|
:icon="markRaw(useRenderFlicker({ background: randomGradient({ randomizeHue: true }) }))"
|
||||||
</re-col>
|
:timestamp="item.date"
|
||||||
</el-row>
|
center
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<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>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import { cloneDeep, dayjs, getRandomIntBetween } from './utils';
|
||||||
import GroupLine from '@iconify-icons/ri/group-line';
|
import GroupLine from '@iconify-icons/ri/group-line';
|
||||||
import Question from '@iconify-icons/ri/question-answer-line';
|
import Question from '@iconify-icons/ri/question-answer-line';
|
||||||
import CheckLine from '@iconify-icons/ri/chat-check-line';
|
import CheckLine from '@iconify-icons/ri/chat-check-line';
|
||||||
import Smile from '@iconify-icons/ri/star-smile-line';
|
import Smile from '@iconify-icons/ri/star-smile-line';
|
||||||
|
|
||||||
|
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
|
|
||||||
/** 需求人数、提问数量、解决数量、用户满意度 */
|
/** 需求人数、提问数量、解决数量、用户满意度 */
|
||||||
const chartData = [
|
const chartData = [
|
||||||
{
|
{
|
||||||
|
@ -47,4 +50,83 @@ const chartData = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export { chartData };
|
/** 分析概览 */
|
||||||
|
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 };
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { ref } from 'vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
// 前端代码提交记录
|
|
||||||
export const webCommitList = ref([]);
|
|
||||||
// 后端代码提交记录
|
|
||||||
export const serverCommitList = ref([]);
|
|
||||||
|
|
||||||
/** 获取web代码提交记录 */
|
|
||||||
export const getWebCommitList = async () => {
|
|
||||||
const response = await fetch('http://129.211.31.58:3000/api/v1/repos/auth/auth-web/commits?page=1&limit=20');
|
|
||||||
const json = await response.json();
|
|
||||||
webCommitList.value = json.map(item => ({
|
|
||||||
date: dayjs(item?.commit?.committer?.date).format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
url: item?.committer?.html_url,
|
|
||||||
username: item?.committer?.username,
|
|
||||||
avatar_url: item?.committer?.avatar_url,
|
|
||||||
message: item?.commit?.message,
|
|
||||||
html_url: item?.html_url,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
/** 获取后端代码提交记录 */
|
|
||||||
export const getServerCommitList = async () => {
|
|
||||||
const response = await fetch('http://129.211.31.58:3000/api/v1/repos/auth/auth-server-java/commits?page=1&limit=20');
|
|
||||||
const json = await response.json();
|
|
||||||
serverCommitList.value = json.map(item => ({
|
|
||||||
date: dayjs(item?.commit?.committer?.date).format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
url: item?.committer?.html_url,
|
|
||||||
username: item?.committer?.username,
|
|
||||||
avatar_url: item?.committer?.avatar_url,
|
|
||||||
message: item?.commit?.message,
|
|
||||||
html_url: item?.html_url,
|
|
||||||
}));
|
|
||||||
};
|
|
Loading…
Reference in New Issue