财务门户设计
This commit is contained in:
@@ -80,6 +80,7 @@
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-packagejson": "2.5.19",
|
||||
"rimraf": "6.0.1",
|
||||
"sass-embedded": "^1.97.3",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint-config-recommended": "17.0.0",
|
||||
"stylelint-config-recommended-less": "3.0.1",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<FormImport @register="registerImportModal" @success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="ViewsBizDeviceInfoList">
|
||||
@@ -156,31 +155,12 @@
|
||||
},
|
||||
];
|
||||
|
||||
const actionColumn: BasicColumn<BizDeviceInfo> = {
|
||||
width: 160,
|
||||
align: 'center',
|
||||
actions: (record: BizDeviceInfo) => [
|
||||
{
|
||||
icon: 'i-ant-design:delete-outlined',
|
||||
color: 'error',
|
||||
title: t('删除'),
|
||||
popConfirm: {
|
||||
title: t('是否确认删除磁盘信息?'),
|
||||
confirm: handleDelete.bind(this, record),
|
||||
},
|
||||
auth: 'biz:deviceInfo:edit',
|
||||
ifShow: record.ustatus == '0'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const [registerTable, { reload, getForm }] = useTable<BizDeviceInfo>({
|
||||
api: bizDeviceInfoListData,
|
||||
beforeFetch: (params) => {
|
||||
return params;
|
||||
},
|
||||
columns: tableColumns,
|
||||
actionColumn: actionColumn,
|
||||
formConfig: searchForm,
|
||||
showTableSetting: true,
|
||||
useSearchForm: true,
|
||||
@@ -209,19 +189,6 @@
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const [registerImportModal, { openModal: importModal }] = useModal();
|
||||
|
||||
function handleImport() {
|
||||
importModal(true, {});
|
||||
}
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
const params = { id: record.id };
|
||||
const res = await bizDeviceInfoDelete(params);
|
||||
showMessage(res.message);
|
||||
await handleSuccess(record);
|
||||
}
|
||||
|
||||
async function handleSuccess(record: Recordable) {
|
||||
await reload({ record });
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<FormImport @register="registerImportModal" @success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="ViewsBizServerInfoList">
|
||||
@@ -200,31 +199,12 @@
|
||||
},
|
||||
];
|
||||
|
||||
const actionColumn: BasicColumn<BizServerInfo> = {
|
||||
width: 160,
|
||||
align: 'center',
|
||||
actions: (record: BizServerInfo) => [
|
||||
{
|
||||
icon: 'i-ant-design:delete-outlined',
|
||||
color: 'error',
|
||||
title: t('删除'),
|
||||
popConfirm: {
|
||||
title: t('是否确认删除运行信息?'),
|
||||
confirm: handleDelete.bind(this, record),
|
||||
},
|
||||
auth: 'biz:serverInfo:edit',
|
||||
ifShow: record.ustatus == '0'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const [registerTable, { reload, getForm }] = useTable<BizServerInfo>({
|
||||
api: bizServerInfoListData,
|
||||
beforeFetch: (params) => {
|
||||
return params;
|
||||
},
|
||||
columns: tableColumns,
|
||||
actionColumn: actionColumn,
|
||||
formConfig: searchForm,
|
||||
showTableSetting: true,
|
||||
useSearchForm: true,
|
||||
@@ -253,19 +233,6 @@
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const [registerImportModal, { openModal: importModal }] = useModal();
|
||||
|
||||
function handleImport() {
|
||||
importModal(true, {});
|
||||
}
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
const params = { id: record.id };
|
||||
const res = await bizServerInfoDelete(params);
|
||||
showMessage(res.message);
|
||||
await handleSuccess(record);
|
||||
}
|
||||
|
||||
async function handleSuccess(record: Recordable) {
|
||||
await reload({ record });
|
||||
}
|
||||
|
||||
@@ -37,57 +37,54 @@
|
||||
</span>
|
||||
<span class="trigger-time">{{ item.triggerTime }}</span>
|
||||
<span class="alert-type">{{ item.alertType }}</span>
|
||||
<span
|
||||
class="alert-title"
|
||||
@mouseenter="showFullContent(item.alertTitle || '', $event)"
|
||||
@mouseleave="hideFullContent"
|
||||
|
||||
<Tooltip
|
||||
:title="item.alertTitle || '--'"
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
:mouseEnterDelay="0.1"
|
||||
class="alert-title-tooltip"
|
||||
>
|
||||
<a-button type="link" size="small" @click="openModal(true, item)">
|
||||
<span class="title-text">{{ item.alertTitle || '--' }}</span>
|
||||
</a-button>
|
||||
</span>
|
||||
<span class="alert-title"
|
||||
@mouseenter="showFullContent(item.alertContent || '', $event)"
|
||||
@mouseleave="hideFullContent"
|
||||
>
|
||||
{{ item.alertContent || '--' }}
|
||||
</span>
|
||||
<span class="alert-title" ref="titleRefs">
|
||||
<a-button type="link" size="small" @click="openModal(true, item)">
|
||||
<span class="title-text">{{ item.alertTitle || '--' }}</span>
|
||||
</a-button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
:title="item.alertContent || '--'"
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
:mouseEnterDelay="0.1"
|
||||
class="alert-content-tooltip"
|
||||
>
|
||||
<span class="alert-title alert-content">
|
||||
{{ item.alertContent || '--' }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 新增:预警标题悬浮提示框 -->
|
||||
<div
|
||||
v-if="showContentTooltip"
|
||||
class="custom-tooltip"
|
||||
:style="{
|
||||
top: `${tooltipTop}px`,
|
||||
left: `${tooltipLeft}px`,
|
||||
zIndex: 9999
|
||||
}"
|
||||
>
|
||||
<pre class="tooltip-content">{{ fullContent }}</pre>
|
||||
<div class="tooltip-arrow"></div>
|
||||
</div>
|
||||
|
||||
<Modal @register="register" @modalClose="fetchAppList(currentStatus)" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Card, Button } from 'ant-design-vue';
|
||||
import { ref, onMounted, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useModal } from '@jeesite/core/components/Modal';
|
||||
import { Card, Tooltip, Button } from 'ant-design-vue';
|
||||
import { bizWarningAlertListAll, BizWarningAlert } from '@jeesite/biz/api/biz/warningAlert';
|
||||
import Modal from './info/WarningInfo.vue';
|
||||
|
||||
const [register, { openModal }] = useModal();
|
||||
const alertList = ref<BizWarningAlert[]>([]);
|
||||
const loading = ref<boolean>(false); // 新增加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
const router = useRouter();
|
||||
const titleRefs = ref<HTMLElement[]>([]);
|
||||
|
||||
// 状态选项配置
|
||||
const statusOptions = ref([
|
||||
{ value: '0', label: '未处理' },
|
||||
{ value: '1', label: '处理中' },
|
||||
@@ -95,65 +92,33 @@ const statusOptions = ref([
|
||||
{ value: '3', label: '已忽略' },
|
||||
{ value: '4', label: '已关闭' },
|
||||
]);
|
||||
const currentStatus = ref<string>('0'); // 默认选中未处理
|
||||
const currentStatus = ref<string>('0');
|
||||
|
||||
// 新增:预警标题悬浮提示相关
|
||||
const showContentTooltip = ref(false);
|
||||
const fullContent = ref('');
|
||||
const tooltipTop = ref(0);
|
||||
const tooltipLeft = ref(0);
|
||||
|
||||
// 新增:显示完整标题
|
||||
const showFullContent = (content: string, event: MouseEvent) => {
|
||||
// 内容为空或无截断时不显示提示
|
||||
if (!content) return;
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
// 判断内容是否被截断(容器宽度 < 内容宽度)
|
||||
if (target.scrollWidth <= target.clientWidth) return;
|
||||
|
||||
// 直接赋值原始内容,不做任何格式化处理
|
||||
fullContent.value = content;
|
||||
// 定位提示框(鼠标位置偏移,避免遮挡鼠标)
|
||||
tooltipTop.value = event.clientY + 10;
|
||||
tooltipLeft.value = event.clientX + 10;
|
||||
showContentTooltip.value = true;
|
||||
};
|
||||
|
||||
// 新增:隐藏提示框
|
||||
const hideFullContent = () => {
|
||||
showContentTooltip.value = false;
|
||||
fullContent.value = '';
|
||||
};
|
||||
|
||||
// 处理状态切换(新增方法)
|
||||
const handleStatusChange = (status: string) => {
|
||||
if (currentStatus.value === status) return; // 避免重复请求
|
||||
if (currentStatus.value === status) return;
|
||||
currentStatus.value = status;
|
||||
fetchAppList(status); // 切换状态时传递参数
|
||||
fetchAppList(status);
|
||||
};
|
||||
|
||||
// 获取预警列表数据
|
||||
const fetchAppList = async (status: string) => {
|
||||
try {
|
||||
loading.value = true; // 开始加载
|
||||
const params = { alertStatus: status }; // 传递状态参数
|
||||
loading.value = true;
|
||||
const params = { alertStatus: status };
|
||||
const result = await bizWarningAlertListAll(params);
|
||||
alertList.value = result || [];
|
||||
await nextTick();
|
||||
} catch (error) {
|
||||
console.error('获取预警列表失败:', error);
|
||||
alertList.value = [];
|
||||
} finally {
|
||||
loading.value = false; // 结束加载
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到更多页面(优化:携带当前状态参数)
|
||||
const goToMorePage = () => {
|
||||
router.push('/biz/warningAlert/list');
|
||||
};
|
||||
|
||||
// 转换预警级别文本
|
||||
const getLevelText = (level: number | undefined) => {
|
||||
if (!level) return '未知';
|
||||
const levelMap = {
|
||||
@@ -165,30 +130,26 @@ const getLevelText = (level: number | undefined) => {
|
||||
return levelMap[level as keyof typeof levelMap] || '未知';
|
||||
};
|
||||
|
||||
// 初始化加载默认状态数据
|
||||
onMounted(() => {
|
||||
fetchAppList(currentStatus.value); // 初始化时传递默认状态(0)
|
||||
fetchAppList(currentStatus.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* extra插槽容器:状态标签 + 更多按钮 横向排列 */
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 状态筛选栏(非按钮样式) */
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px; /* 标签间距 */
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 状态项样式(纯文本标签) */
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
@@ -198,13 +159,11 @@ onMounted(() => {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 选中状态样式(下划线 + 高亮色) */
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 选中状态下划线 */
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -216,56 +175,50 @@ onMounted(() => {
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 悬浮效果 */
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
/* 列表外层容器:移除边框 + 滚动核心样式 + 极致紧凑 */
|
||||
.alert-list-container {
|
||||
width: 100%;
|
||||
height: 32vh; /* 限制最大高度,超出滚动 */
|
||||
overflow-y: auto; /* 垂直滚动 */
|
||||
overflow-x: hidden; /* 隐藏横向滚动 */
|
||||
padding: 0; /* 移除内边距 */
|
||||
margin: 0; /* 移除外边距 */
|
||||
height: 32vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 空数据/加载提示 */
|
||||
.empty-tip {
|
||||
padding: 20px 0; /* 缩减空状态内边距,更紧凑 */
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 预警列表 */
|
||||
.alert-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 单个预警项:flex布局 + 紧凑排列(级别→时间→类型→标题) */
|
||||
.alert-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
gap: 10px; /* 统一控制元素间基础间距 */
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 最后一项去掉底部边框 */
|
||||
.alert-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 鼠标悬浮高亮 */
|
||||
.alert-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 预警级别标签(固定宽度,不压缩) */
|
||||
.level-tag {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
@@ -274,51 +227,55 @@ onMounted(() => {
|
||||
color: #fff;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
flex-shrink: 0; /* 固定宽度,不被压缩 */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 级别颜色 */
|
||||
.level-1 { background-color: #f5222d; } /* 紧急 */
|
||||
.level-2 { background-color: #fa8c16; } /* 高 */
|
||||
.level-3 { background-color: #faad14; } /* 中 */
|
||||
.level-4 { background-color: #1890ff; } /* 低 */
|
||||
.level-undefined { background-color: #8c8c8c; } /* 未知 */
|
||||
.level-1 { background-color: #f5222d; }
|
||||
.level-2 { background-color: #fa8c16; }
|
||||
.level-3 { background-color: #faad14; }
|
||||
.level-4 { background-color: #1890ff; }
|
||||
.level-undefined { background-color: #8c8c8c; }
|
||||
|
||||
/* 预警触发时间(固定宽度,不压缩) */
|
||||
.trigger-time {
|
||||
width: 120px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0; /* 固定宽度,不被压缩 */
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 预警类型(固定宽度,不压缩) */
|
||||
.alert-type {
|
||||
width: 60px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0; /* 固定宽度,不被压缩 */
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 预警标题(仅修改这部分样式) */
|
||||
.alert-title {
|
||||
width: 100px; /* 固定宽度,可根据需求调整(比如200px/400px) */
|
||||
flex-shrink: 0; /* 关键:禁止宽度被压缩,保证固定宽度生效 */
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
color: #666;
|
||||
white-space: nowrap; /* 强制不换行 */
|
||||
overflow: hidden; /* 隐藏超出宽度的内容 */
|
||||
text-overflow: ellipsis; /* 超出显示省略号 */
|
||||
margin-left: 4px; /* 保留与类型的微小间距 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
width: 200px;
|
||||
flex-grow: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* 新增:标题文本样式 - 确保省略号生效 */
|
||||
.title-text {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
@@ -326,71 +283,38 @@ onMounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 新增:自定义提示框样式 */
|
||||
.custom-tooltip {
|
||||
position: fixed;
|
||||
background: #1f2937;
|
||||
color: #ffffff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
z-index: 9999;
|
||||
animation: fadeIn 0.2s ease-in-out;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
color: #e5e7eb;
|
||||
margin: 0;
|
||||
:deep(.alert-title .ant-btn-link) {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.custom-tooltip::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
:deep(.ant-tooltip) {
|
||||
z-index: 1070 !important;
|
||||
}
|
||||
|
||||
.custom-tooltip::-webkit-scrollbar-track {
|
||||
background: #2d3748;
|
||||
border-radius: 2px;
|
||||
:deep(.ant-tooltip-inner) {
|
||||
max-width: 500px !important;
|
||||
padding: 12px 16px !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.6 !important;
|
||||
white-space: pre-wrap !important;
|
||||
word-wrap: break-word !important;
|
||||
background: #1f2937 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.custom-tooltip::-webkit-scrollbar-thumb {
|
||||
background: #4a5568;
|
||||
border-radius: 2px;
|
||||
:deep(.ant-tooltip-arrow-content) {
|
||||
background: #1f2937 !important;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 10px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 8px solid #1f2937;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式优化 */
|
||||
.alert-list-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
@@ -405,7 +329,6 @@ onMounted(() => {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 小屏幕适配 */
|
||||
@media (max-width: 768px) {
|
||||
.status-filter-container {
|
||||
gap: 8px;
|
||||
@@ -435,5 +358,11 @@ onMounted(() => {
|
||||
.alert-list-container {
|
||||
max-height: 200px;
|
||||
}
|
||||
.alert-title {
|
||||
width: 80px;
|
||||
}
|
||||
.alert-content {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -11,6 +11,9 @@
|
||||
<TabPane key="3" tab="文件管理">
|
||||
<MyfileInfo />
|
||||
</TabPane>
|
||||
<TabPane key="4" tab="便签管理">
|
||||
<NoteTodo />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,6 +21,7 @@
|
||||
<script lang="ts" setup name="AboutPage">
|
||||
import { h, ref, onMounted } from 'vue';
|
||||
import { Tag, Tabs, TabPane } from 'ant-design-vue';
|
||||
import NoteTodo from './NoteTodo.vue';
|
||||
import MySchedule from './MySchedule.vue';
|
||||
import Calendar from './calendar/list.vue';
|
||||
import ItemsInfo from './listItem/list.vue';
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<Card title="年度账户收支趋势" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度账户趋势" class="account-trend-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, watchEffect } from 'vue';
|
||||
import { Card } from 'ant-design-vue';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { BizItemInfo, bizItemInfoListAll } from '@jeesite/biz/api/biz/itemInfo';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
@@ -19,7 +18,6 @@ const rawData = ref<BizItemInfo[]>([]);
|
||||
const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
if (!num) return 0;
|
||||
@@ -111,16 +109,27 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
chartInstance.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } },
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
textStyle: { color: '#fff' }
|
||||
},
|
||||
legend: {
|
||||
data: ['收入', '支出', '支出占比'],
|
||||
top: 10,
|
||||
textStyle: { fontSize: 12, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { data: ['收入', '支出', '支出占比'], top: 10, textStyle: { fontSize: 12 } },
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -129,42 +138,46 @@ const initChart = () => {
|
||||
data: [],
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
lineHeight: 1.2
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '占比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(2)}%`
|
||||
formatter: (value: number) => `${value.toFixed(2)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true,
|
||||
position: 'right'
|
||||
@@ -172,8 +185,8 @@ const initChart = () => {
|
||||
],
|
||||
series: [],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无收入支出数据',
|
||||
textStyle: { fontSize: 16, color: '#666', fontWeight: '500' },
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 16, color: '#a0cfff', fontWeight: '500' },
|
||||
position: 'center',
|
||||
effect: 'none'
|
||||
}
|
||||
@@ -196,50 +209,53 @@ const initChart = () => {
|
||||
distance: 3,
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
fontWeight: '500'
|
||||
color: '#40c4ff',
|
||||
fontWeight: '500',
|
||||
textShadowColor: 'rgba(0,0,0,0.3)',
|
||||
textShadowBlur: 1
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(2)}`
|
||||
};
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#20a0ff' }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontSize: 12 },
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any[]) => {
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px;">暂无数据</div>';
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px; color: #a0cfff;">暂无数据</div>';
|
||||
const currentXAxisValue = params[0]?.axisValue || '';
|
||||
const item = rawData.value.find(i => i.xaxis === currentXAxisValue);
|
||||
if (!item) return `<div style="padding: 8px;">${currentXAxisValue}:暂无明细</div>`;
|
||||
if (!item) return `<div style="padding: 8px; color: #a0cfff;">${currentXAxisValue}:暂无明细</div>`;
|
||||
const netProfit = calculateNetProfit(item.index01, item.index02);
|
||||
const ratio = calculateExpenseRatio(item.index01, item.index02);
|
||||
const netProfitColor = netProfit > 0 ? '#52c41a' : netProfit < 0 ? '#f5222d' : '#666';
|
||||
const netProfitColor = netProfit > 0 ? '#40c4ff' : netProfit < 0 ? '#ff4d4f' : '#a0cfff';
|
||||
return `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 8px; text-align: center;">${currentXAxisValue}</div>
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 8px; text-align: center;">${currentXAxisValue}</div>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 11px; min-width: 400px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8f8f8;">
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">支出占比</th>
|
||||
<tr style="background-color: rgba(18, 60, 110, 0.5);">
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">支出占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #1890ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #f5222d;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #fa8c16;">${ratio.toFixed(2)}%</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #40c4ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ff4d4f;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ffa066;">${ratio.toFixed(2)}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -250,12 +266,12 @@ const initChart = () => {
|
||||
data: ['收入', '支出', '支出占比'],
|
||||
top: 10,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 11 }
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' }
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 50,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -264,16 +280,18 @@ const initChart = () => {
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
margin: 8
|
||||
margin: 6,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
},
|
||||
boundaryGap: true
|
||||
},
|
||||
@@ -281,16 +299,17 @@ const initChart = () => {
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: amountMin,
|
||||
max: amountMax,
|
||||
axisLine: { onZero: true },
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
minInterval: 0.1,
|
||||
@@ -298,17 +317,18 @@ const initChart = () => {
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '占比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(2)}%`
|
||||
formatter: (value: number) => `${value.toFixed(2)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: ratioMin,
|
||||
max: ratioMax,
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true,
|
||||
position: 'right'
|
||||
@@ -316,7 +336,7 @@ const initChart = () => {
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 14, color: '#999' },
|
||||
textStyle: { fontSize: 14, color: '#a0cfff' },
|
||||
effect: 'bubble',
|
||||
effectOption: { effect: { n: 0 } }
|
||||
},
|
||||
@@ -327,10 +347,12 @@ const initChart = () => {
|
||||
data: incomeData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 0, color: '#40c4ff' },
|
||||
{ offset: 1, color: '#096dd9' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
barGap: '30%',
|
||||
@@ -340,9 +362,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#1890ff' }
|
||||
])
|
||||
{ offset: 0, color: '#69b1ff' },
|
||||
{ offset: 1, color: '#40c4ff' }
|
||||
]),
|
||||
shadowColor: 'rgba(64, 196, 255, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -352,10 +376,12 @@ const initChart = () => {
|
||||
data: expenseData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#cf1322' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
label: barLabelConfig,
|
||||
@@ -363,9 +389,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 0, color: '#f5222d' }
|
||||
])
|
||||
{ offset: 0, color: '#ff7875' },
|
||||
{ offset: 1, color: '#ff4d4f' }
|
||||
]),
|
||||
shadowColor: 'rgba(255, 77, 79, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -379,12 +407,12 @@ const initChart = () => {
|
||||
symbolSize: 5,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#fa8c16'
|
||||
color: '#ffa066'
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(250, 140, 22, 0.25)' },
|
||||
{ offset: 1, color: 'rgba(250, 140, 22, 0.04)' }
|
||||
{ offset: 0, color: 'rgba(255, 160, 102, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(255, 160, 102, 0.05)' }
|
||||
])
|
||||
},
|
||||
label: {
|
||||
@@ -393,8 +421,10 @@ const initChart = () => {
|
||||
distance: 4,
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
color: '#fa8c16',
|
||||
fontWeight: 500
|
||||
color: '#ffa066',
|
||||
fontWeight: 500,
|
||||
textShadowColor: 'rgba(0,0,0,0.3)',
|
||||
textShadowBlur: 1
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(2)}%`
|
||||
},
|
||||
@@ -462,90 +492,88 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
.account-trend-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card:hover) {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
max-width: 450px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-xaxis-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.2;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-yaxis-name) {
|
||||
margin-right: 3px;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend-item) {
|
||||
margin-right: 12px !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
white-space: nowrap;
|
||||
color: #40c4ff !important;
|
||||
}
|
||||
|
||||
.status-filter-container,
|
||||
.status-filter,
|
||||
.status-item {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Card title="年度分类支出结构" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度支出结构" class="category-expense-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -19,22 +19,27 @@ const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
const formatNumber = (num: string | undefined, decimal = 0): number => {
|
||||
if (!num) return 0;
|
||||
const parsed = Number(num);
|
||||
return isNaN(parsed) ? 0 : Number(parsed.toFixed(decimal));
|
||||
};
|
||||
|
||||
const toTenThousandYuan = (num: string | undefined): number => {
|
||||
const rawNum = formatNumber(num);
|
||||
return formatNumber((rawNum / 10000).toString());
|
||||
const formatWithCommas = (num: number): string => {
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
};
|
||||
|
||||
const sortByMonth = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
return data.sort((a, b) => {
|
||||
const monthA = a.xaxis ? parseInt(a.xaxis, 10) : 0;
|
||||
const monthB = b.xaxis ? parseInt(b.xaxis, 10) : 0;
|
||||
return monthA - monthB;
|
||||
const convertToTenThousand = (num: number, decimal = 2): number => {
|
||||
return Number((num / 10000).toFixed(decimal));
|
||||
};
|
||||
|
||||
const formatTenThousand = (num: number): string => {
|
||||
return convertToTenThousand(num).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
};
|
||||
|
||||
@@ -46,7 +51,7 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
};
|
||||
const result = await bizItemInfoListAll(reqParams);
|
||||
const validData = (result || []).filter(item => item.xaxis && item.index01);
|
||||
rawData.value = sortByMonth(validData);
|
||||
rawData.value = validData || [];
|
||||
} catch (error) {
|
||||
console.error('获取数据列表失败:', error);
|
||||
rawData.value = [];
|
||||
@@ -62,11 +67,11 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
chartInstance.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
top: '50%',
|
||||
text: '暂无支出数据',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'item' },
|
||||
padding: [0, 0, 0, 0]
|
||||
@@ -76,22 +81,29 @@ const initChart = () => {
|
||||
|
||||
const pieData = rawData.value.map(item => ({
|
||||
name: item.xaxis || '',
|
||||
value: toTenThousandYuan(item.index01)
|
||||
value: formatNumber(item.index01)
|
||||
})).filter(item => item.value > 0);
|
||||
|
||||
const totalValue = pieData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
padding: [0, 0, 0, 0],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
textStyle: { fontSize: 12 },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any) => `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 4px;">${params.name}</div>
|
||||
<div style="color: #666;">支出:${params.value.toFixed(2)} 万元</div>
|
||||
`
|
||||
formatter: (params: any) => {
|
||||
const percent = totalValue > 0 ? ((params.value / totalValue) * 100).toFixed(2) : 0;
|
||||
return `
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 4px;">${params.name}</div>
|
||||
<div style="color: #a0cfff;">支出:${formatWithCommas(params.value)} 元</div>
|
||||
<div style="color: #a0cfff;">占比:${percent}%</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
@@ -100,7 +112,7 @@ const initChart = () => {
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
color: '#a0cfff',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
itemGap: 15,
|
||||
@@ -109,9 +121,9 @@ const initChart = () => {
|
||||
selectedMode: true,
|
||||
scrollDataIndex: 0,
|
||||
pageButtonPosition: 'end',
|
||||
pageIconColor: '#1890ff',
|
||||
pageIconInactiveColor: '#ccc',
|
||||
pageTextStyle: { fontSize: 9 },
|
||||
pageIconColor: '#40c4ff',
|
||||
pageIconInactiveColor: 'rgba(32, 160, 255, 0.3)',
|
||||
pageTextStyle: { fontSize: 9, color: '#a0cfff' },
|
||||
height: 30,
|
||||
formatter: (name: string) => {
|
||||
return name.length > 6 ? `${name.substring(0, 6)}...` : name;
|
||||
@@ -122,26 +134,34 @@ const initChart = () => {
|
||||
name: '支出',
|
||||
type: 'pie',
|
||||
radius: ['30%', '60%'],
|
||||
center: ['50%', '45%'],
|
||||
center: ['50%', '50%'],
|
||||
data: pieData,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (params: any) => {
|
||||
const shortName = params.name.length > 4 ? `${params.name.substring(0, 4)}...` : params.name;
|
||||
return `${shortName}: ${params.value}万元`;
|
||||
const percent = totalValue > 0 ? ((params.value / totalValue) * 100).toFixed(1) : 0;
|
||||
return `${shortName}: ${formatTenThousand(params.value)}万元 (${percent}%)`;
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderColor: 'rgba(9, 30, 58, 0.8)',
|
||||
borderWidth: 2
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
shadowColor: 'rgba(64, 196, 255, 0.5)'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#a0cfff'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,60 +219,51 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
padding: 0 !important;
|
||||
.category-expense-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend) {
|
||||
@@ -275,12 +286,14 @@ onUnmounted(() => {
|
||||
margin-right: 15px !important;
|
||||
white-space: nowrap !important;
|
||||
cursor: pointer !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Card title="年度分类收入分布" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度收入分布" class="category-income-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -19,22 +19,27 @@ const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
const formatNumber = (num: string | undefined, decimal = 0): number => {
|
||||
if (!num) return 0;
|
||||
const parsed = Number(num);
|
||||
return isNaN(parsed) ? 0 : Number(parsed.toFixed(decimal));
|
||||
};
|
||||
|
||||
const toTenThousandYuan = (num: string | undefined): number => {
|
||||
const rawNum = formatNumber(num);
|
||||
return formatNumber((rawNum / 10000).toString());
|
||||
const formatWithCommas = (num: number): string => {
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
};
|
||||
|
||||
const sortByMonth = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
return data.sort((a, b) => {
|
||||
const monthA = a.xaxis ? parseInt(a.xaxis, 10) : 0;
|
||||
const monthB = b.xaxis ? parseInt(b.xaxis, 10) : 0;
|
||||
return monthA - monthB;
|
||||
const convertToTenThousand = (num: number, decimal = 2): number => {
|
||||
return Number((num / 10000).toFixed(decimal));
|
||||
};
|
||||
|
||||
const formatTenThousand = (num: number): string => {
|
||||
return convertToTenThousand(num).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
};
|
||||
|
||||
@@ -46,7 +51,7 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
};
|
||||
const result = await bizItemInfoListAll(reqParams);
|
||||
const validData = (result || []).filter(item => item.xaxis && item.index01);
|
||||
rawData.value = sortByMonth(validData);
|
||||
rawData.value = validData || [];
|
||||
} catch (error) {
|
||||
console.error('获取数据列表失败:', error);
|
||||
rawData.value = [];
|
||||
@@ -62,11 +67,11 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
chartInstance.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
top: '50%',
|
||||
text: '暂无收入数据',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'item' },
|
||||
padding: [0, 0, 0, 0]
|
||||
@@ -76,22 +81,29 @@ const initChart = () => {
|
||||
|
||||
const pieData = rawData.value.map(item => ({
|
||||
name: item.xaxis || '',
|
||||
value: toTenThousandYuan(item.index01)
|
||||
value: formatNumber(item.index01)
|
||||
})).filter(item => item.value > 0);
|
||||
|
||||
const totalValue = pieData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
padding: [0, 0, 0, 0],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
textStyle: { fontSize: 12 },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any) => `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 4px;">${params.name}</div>
|
||||
<div style="color: #666;">收入:${params.value.toFixed(2)} 万元</div>
|
||||
`
|
||||
formatter: (params: any) => {
|
||||
const percent = totalValue > 0 ? ((params.value / totalValue) * 100).toFixed(2) : 0;
|
||||
return `
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 4px;">${params.name}</div>
|
||||
<div style="color: #a0cfff;">收入:${formatWithCommas(params.value)} 元</div>
|
||||
<div style="color: #a0cfff;">占比:${percent}%</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
@@ -100,7 +112,7 @@ const initChart = () => {
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
color: '#a0cfff',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
itemGap: 15,
|
||||
@@ -109,9 +121,9 @@ const initChart = () => {
|
||||
selectedMode: true,
|
||||
scrollDataIndex: 0,
|
||||
pageButtonPosition: 'end',
|
||||
pageIconColor: '#1890ff',
|
||||
pageIconInactiveColor: '#ccc',
|
||||
pageTextStyle: { fontSize: 9 },
|
||||
pageIconColor: '#40c4ff',
|
||||
pageIconInactiveColor: 'rgba(32, 160, 255, 0.3)',
|
||||
pageTextStyle: { fontSize: 9, color: '#a0cfff' },
|
||||
height: 30,
|
||||
formatter: (name: string) => {
|
||||
return name.length > 6 ? `${name.substring(0, 6)}...` : name;
|
||||
@@ -122,26 +134,34 @@ const initChart = () => {
|
||||
name: '收入',
|
||||
type: 'pie',
|
||||
radius: ['30%', '60%'],
|
||||
center: ['50%', '45%'],
|
||||
center: ['50%', '50%'],
|
||||
data: pieData,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (params: any) => {
|
||||
const shortName = params.name.length > 4 ? `${params.name.substring(0, 4)}...` : params.name;
|
||||
return `${shortName}: ${params.value}万元`;
|
||||
const percent = totalValue > 0 ? ((params.value / totalValue) * 100).toFixed(1) : 0;
|
||||
return `${shortName}: ${formatTenThousand(params.value)}万元 (${percent}%)`;
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderColor: 'rgba(9, 30, 58, 0.8)',
|
||||
borderWidth: 2
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
shadowColor: 'rgba(64, 196, 255, 0.5)'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#a0cfff'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,60 +219,51 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
padding: 0 !important;
|
||||
.category-income-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend) {
|
||||
@@ -275,12 +286,14 @@ onUnmounted(() => {
|
||||
margin-right: 15px !important;
|
||||
white-space: nowrap !important;
|
||||
cursor: pointer !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<Card title="年度各月收支趋势" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度各月趋势" class="month-trend-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, watchEffect } from 'vue';
|
||||
import { Card } from 'ant-design-vue';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { BizItemInfo, bizItemInfoListAll } from '@jeesite/biz/api/biz/itemInfo';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
@@ -19,7 +18,6 @@ const rawData = ref<BizItemInfo[]>([]);
|
||||
const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let myChart: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
if (!num) return 0;
|
||||
@@ -102,7 +100,6 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
rawData.value = sortByMonth(validData);
|
||||
} catch (error) {
|
||||
console.error('获取数据列表失败:', error);
|
||||
createMessage.error('获取月度收支数据失败,请稍后重试');
|
||||
rawData.value = [];
|
||||
}
|
||||
};
|
||||
@@ -116,16 +113,17 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
myChart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { data: ['收入', '支出', '支出占比'], top: 10, textStyle: { fontSize: 12 } },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } } },
|
||||
legend: { data: ['收入', '支出', '支出占比'], top: 10, textStyle: { fontSize: 12, color: '#a0cfff' } },
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -134,41 +132,46 @@ const initChart = () => {
|
||||
data: [],
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
lineHeight: 1.2
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
color: '#a0cfff',
|
||||
margin: 6
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
position: 'right',
|
||||
@@ -176,8 +179,8 @@ const initChart = () => {
|
||||
],
|
||||
series: [],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无收入支出数据',
|
||||
textStyle: { fontSize: 16, color: '#666', fontWeight: '500' },
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 16, color: '#a0cfff', fontWeight: '500' },
|
||||
position: 'center',
|
||||
effect: 'none'
|
||||
}
|
||||
@@ -200,8 +203,10 @@ const initChart = () => {
|
||||
distance: 3,
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
fontWeight: '500'
|
||||
color: '#40c4ff',
|
||||
fontWeight: '500',
|
||||
textShadowColor: 'rgba(0,0,0,0.3)',
|
||||
textShadowBlur: 1
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(2)}`
|
||||
};
|
||||
@@ -213,7 +218,8 @@ const initChart = () => {
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
fontWeight: '500',
|
||||
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.8)',
|
||||
color: '#a0cfff',
|
||||
padding: [2, 4],
|
||||
borderRadius: 2
|
||||
},
|
||||
@@ -221,42 +227,43 @@ const initChart = () => {
|
||||
};
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#20a0ff' }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontSize: 12 },
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any[]) => {
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px;">暂无数据</div>';
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px; color: #a0cfff;">暂无数据</div>';
|
||||
const currentMonth = params[0]?.axisValue || '';
|
||||
const item = rawData.value.find(i => formatMonth(i.xaxis) === currentMonth);
|
||||
if (!item) return `<div style="padding: 8px;">${currentMonth}:暂无明细</div>`;
|
||||
if (!item) return `<div style="padding: 8px; color: #a0cfff;">${currentMonth}:暂无明细</div>`;
|
||||
const netProfit = calculateNetProfit(item.index01, item.index02);
|
||||
const netProfitColor = netProfit > 0 ? '#52c41a' : netProfit < 0 ? '#f5222d' : '#666';
|
||||
const netProfitColor = netProfit > 0 ? '#40c4ff' : netProfit < 0 ? '#ff4d4f' : '#a0cfff';
|
||||
return `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 8px; text-align: center;">${currentMonth}</div>
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 8px; text-align: center;">${currentMonth}</div>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 11px; min-width: 400px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8f8f8;">
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">支出占比</th>
|
||||
<tr style="background-color: rgba(18, 60, 110, 0.5);">
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">支出占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #1890ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #f5222d;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #722ed1;">${formatPercentage(item.index03)}</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #40c4ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ff4d4f;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ffa066;">${formatPercentage(item.index03)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -267,12 +274,12 @@ const initChart = () => {
|
||||
data: ['收入', '支出', '支出占比'],
|
||||
top: 10,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 11 }
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' }
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -281,16 +288,18 @@ const initChart = () => {
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
margin: 8
|
||||
margin: 6,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
},
|
||||
boundaryGap: true
|
||||
},
|
||||
@@ -298,32 +307,34 @@ const initChart = () => {
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: amountMin,
|
||||
max: amountMax,
|
||||
axisLine: { onZero: true },
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: percentMin,
|
||||
max: percentMax,
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
position: 'right',
|
||||
@@ -332,7 +343,7 @@ const initChart = () => {
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 14, color: '#999' },
|
||||
textStyle: { fontSize: 14, color: '#a0cfff' },
|
||||
effect: 'bubble',
|
||||
effectOption: { effect: { n: 0 } }
|
||||
},
|
||||
@@ -343,10 +354,12 @@ const initChart = () => {
|
||||
data: incomeData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 0, color: '#40c4ff' },
|
||||
{ offset: 1, color: '#096dd9' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
barGap: '30%',
|
||||
@@ -356,9 +369,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#1890ff' }
|
||||
])
|
||||
{ offset: 0, color: '#69b1ff' },
|
||||
{ offset: 1, color: '#40c4ff' }
|
||||
]),
|
||||
shadowColor: 'rgba(64, 196, 255, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,10 +383,12 @@ const initChart = () => {
|
||||
data: expenseData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#cf1322' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
label: barLabelConfig,
|
||||
@@ -379,9 +396,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#f5222d' }
|
||||
])
|
||||
{ offset: 0, color: '#ff7875' },
|
||||
{ offset: 1, color: '#ff4d4f' }
|
||||
]),
|
||||
shadowColor: 'rgba(255, 77, 79, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -395,12 +414,12 @@ const initChart = () => {
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#722ed1',
|
||||
color: '#ffa066',
|
||||
type: 'solid'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#722ed1',
|
||||
borderColor: '#fff',
|
||||
color: '#ffa066',
|
||||
borderColor: 'rgba(9, 30, 58, 0.8)',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: lineLabelConfig,
|
||||
@@ -467,73 +486,61 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
.month-trend-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card:hover) {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
max-width: 450px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip table) {
|
||||
@@ -544,19 +551,23 @@ onUnmounted(() => {
|
||||
:deep(.echarts-xaxis-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.2;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-yaxis-name) {
|
||||
margin-right: 3px;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend-item) {
|
||||
margin-right: 12px !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
white-space: nowrap;
|
||||
color: #40c4ff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar) {
|
||||
@@ -565,12 +576,12 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-thumb) {
|
||||
background: #d9d9d9;
|
||||
background: rgba(32, 160, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-track) {
|
||||
background: #f5f5f5;
|
||||
background: rgba(9, 30, 58, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<Card title="年度各月环比情况" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度环比趋势" class="month-mom-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, watchEffect } from 'vue';
|
||||
import { Card } from 'ant-design-vue';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { BizItemInfo, bizItemInfoListAll } from '@jeesite/biz/api/biz/itemInfo';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
@@ -19,7 +18,6 @@ const rawData = ref<BizItemInfo[]>([]);
|
||||
const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let myChart: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
if (!num) return 0;
|
||||
@@ -97,7 +95,6 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
rawData.value = sortByMonth(validData);
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
createMessage.error('获取月度环比数据失败,请稍后重试');
|
||||
rawData.value = [];
|
||||
}
|
||||
};
|
||||
@@ -111,18 +108,43 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
myChart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['本月收入','上月收入','本月支出','上月支出','收入环比','支出环比','净收益'], top: 10 },
|
||||
grid: { left:10, right:40, bottom:60, top:40, containLabel:true },
|
||||
xAxis: { type: 'category', data: [] },
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: { trigger: 'axis', axisPointer: { lineStyle: { color: '#40c4ff' } } },
|
||||
legend: {
|
||||
data: ['本月收入','上月收入','本月支出','上月支出','收入环比','支出环比','净收益'],
|
||||
top: 10,
|
||||
textStyle: { color: '#a0cfff', fontSize: 11 }
|
||||
},
|
||||
grid: { left:10, right:20, bottom:12, top:40, containLabel:true },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
axisLabel: { color: '#a0cfff' },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } }
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: '万元' },
|
||||
{ type: 'value', name: '%', position: 'right' }
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: { color: '#a0cfff' },
|
||||
axisLabel: { color: '#a0cfff' },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '%',
|
||||
position: 'right',
|
||||
nameTextStyle: { color: '#a0cfff' },
|
||||
axisLabel: { color: '#a0cfff' },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } }
|
||||
}
|
||||
],
|
||||
series: [],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无月度环比数据',
|
||||
textStyle: { fontSize: 16, color: '#666', fontWeight: '500' },
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 16, color: '#a0cfff', fontWeight: '500' },
|
||||
position: 'center'
|
||||
}
|
||||
}, true);
|
||||
@@ -145,7 +167,7 @@ const initChart = () => {
|
||||
const profitBarLabelConfig = {
|
||||
show: true,
|
||||
position: 'top',
|
||||
textStyle: { fontSize: 9, fontWeight: 'bold' },
|
||||
textStyle: { fontSize: 9, fontWeight: 'bold', color: '#a0cfff' },
|
||||
formatter: (v: any) => formatWithThousandsSeparator(v.value)
|
||||
};
|
||||
|
||||
@@ -161,17 +183,19 @@ const initChart = () => {
|
||||
};
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#eee',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
textStyle: { fontSize: 12 },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
axisPointer: { lineStyle: { color: '#40c4ff' } },
|
||||
formatter: (params: any[]) => {
|
||||
const month = params[0]?.axisValue;
|
||||
const item = rawData.value.find(i => formatMonth(i.xaxis) === month);
|
||||
if (!item) return '无数据';
|
||||
if (!item) return '<div style="color:#a0cfff;">无数据</div>';
|
||||
|
||||
const incomeMom = calculateMonthOnMonth(item.index01, item.index04);
|
||||
const expenseMom = calculateMonthOnMonth(item.index02, item.index05);
|
||||
@@ -180,47 +204,47 @@ const initChart = () => {
|
||||
let netProfitText = '';
|
||||
let netProfitColor = '';
|
||||
if (netProfitInYuan > 0) {
|
||||
netProfitText = `<span style="font-weight:bold;">↑ ${formatWithThousandsSeparator(netProfitInYuan)}</span>`;
|
||||
netProfitColor = '#52c41a';
|
||||
netProfitText = `<span style="font-weight:bold; color:#40c4ff;">↑ ${formatWithThousandsSeparator(netProfitInYuan)}</span>`;
|
||||
netProfitColor = '#40c4ff';
|
||||
} else if (netProfitInYuan < 0) {
|
||||
netProfitText = `<span style="font-weight:bold;">↓ ${formatWithThousandsSeparator(Math.abs(netProfitInYuan))}</span>`;
|
||||
netProfitColor = '#f5222d';
|
||||
netProfitText = `<span style="font-weight:bold; color:#ff4d4f;">↓ ${formatWithThousandsSeparator(Math.abs(netProfitInYuan))}</span>`;
|
||||
netProfitColor = '#ff4d4f';
|
||||
} else {
|
||||
netProfitText = '<span style="font-weight:bold;">— 收支平衡</span>';
|
||||
netProfitColor = '#666';
|
||||
netProfitText = '<span style="font-weight:bold; color:#a0cfff;">— 收支平衡</span>';
|
||||
netProfitColor = '#a0cfff';
|
||||
}
|
||||
|
||||
const incomeMomText = incomeMom >= 0
|
||||
? `<span style="color:#f5222d; font-weight:bold;">↑ ${incomeMom.toFixed(2)}</span>`
|
||||
: `<span style="color:#1890ff; font-weight:bold;">↓ ${Math.abs(incomeMom).toFixed(2)}</span>`;
|
||||
? `<span style="color:#ff4d4f; font-weight:bold;">↑ ${incomeMom.toFixed(2)}</span>`
|
||||
: `<span style="color:#40c4ff; font-weight:bold;">↓ ${Math.abs(incomeMom).toFixed(2)}</span>`;
|
||||
|
||||
const expenseMomText = expenseMom >= 0
|
||||
? `<span style="color:#f5222d; font-weight:bold;">↑ ${expenseMom.toFixed(2)}</span>`
|
||||
: `<span style="color:#1890ff; font-weight:bold;">↓ ${Math.abs(expenseMom).toFixed(2)}</span>`;
|
||||
? `<span style="color:#ff4d4f; font-weight:bold;">↑ ${expenseMom.toFixed(2)}</span>`
|
||||
: `<span style="color:#40c4ff; font-weight:bold;">↓ ${Math.abs(expenseMom).toFixed(2)}</span>`;
|
||||
|
||||
return `
|
||||
<div style="text-align:center; font-weight:600; margin-bottom:8px;">${month}</div>
|
||||
<div style="text-align:center; font-weight:600; margin-bottom:8px; color:#40c4ff;">${month}</div>
|
||||
<table style="width:100%; border-collapse:collapse; font-size:12px;">
|
||||
<thead>
|
||||
<tr style="background:#f7f8fa;">
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">本月收入(元)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">上月收入(元)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">收入环比(%)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">本月支出(元)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">上月支出(元)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">支出环比(%)</th>
|
||||
<th style="border:1px solid #eee; padding:6px; text-align:center;">净收益(元)</th>
|
||||
<tr style="background:rgba(18, 60, 110, 0.5);">
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">本月收入(元)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">上月收入(元)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">收入环比(%)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">本月支出(元)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">上月支出(元)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">支出环比(%)</th>
|
||||
<th style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center; color:#a0cfff;">净收益(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border:1px solid #eee; padding:6px; color:#1890ff; text-align:right;">${formatWithThousandsSeparator(item.index01)}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; color:#ff7875; text-align:right;">${formatWithThousandsSeparator(item.index04)}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; text-align:center;">${incomeMomText}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; color:#f5222d; text-align:right;">${formatWithThousandsSeparator(item.index02)}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; color:#ffc53d; text-align:right;">${formatWithThousandsSeparator(item.index05)}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; text-align:center;">${expenseMomText}</td>
|
||||
<td style="border:1px solid #eee; padding:6px; color:${netProfitColor}; text-align:center;">${netProfitText}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; color:#40c4ff; text-align:right;">${formatWithThousandsSeparator(item.index01)}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; color:#ff7875; text-align:right;">${formatWithThousandsSeparator(item.index04)}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center;">${incomeMomText}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; color:#ff4d4f; text-align:right;">${formatWithThousandsSeparator(item.index02)}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; color:#ffc53d; text-align:right;">${formatWithThousandsSeparator(item.index05)}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; text-align:center;">${expenseMomText}</td>
|
||||
<td style="border:1px solid rgba(32, 160, 255, 0.3); padding:6px; color:${netProfitColor}; text-align:center;">${netProfitText}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -229,29 +253,45 @@ const initChart = () => {
|
||||
},
|
||||
legend: {
|
||||
data: ['本月收入', '上月收入', '本月支出', '上月支出', '收入环比', '支出环比', '净收益'],
|
||||
top: 10, left: 'center', textStyle: { fontSize: 11 }
|
||||
top: 10,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' }
|
||||
},
|
||||
grid: { left: 10, right: 40, bottom: 60, top: 40, containLabel: true },
|
||||
grid: { left: 10, right: 20, bottom: 12, top: 40, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxisData,
|
||||
axisLabel: { fontSize: 10, rotate: 60 }
|
||||
axisLabel: { fontSize: 10, rotate: 45, color: '#a0cfff' },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '万元',
|
||||
nameTextStyle: { color: '#a0cfff', fontSize: 11 },
|
||||
min: amountMin, max: amountMax,
|
||||
axisLabel: {
|
||||
formatter: (v: any) => formatWithThousandsSeparator(v),
|
||||
fontSize: 10
|
||||
fontSize: 10,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { lineStyle: { type: 'dashed', opacity: 0.3 } }
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { type: 'dashed', opacity: 0.3, color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } }
|
||||
},
|
||||
{
|
||||
type: 'value', name: '%', position: 'right',
|
||||
type: 'value',
|
||||
name: '%',
|
||||
position: 'right',
|
||||
nameTextStyle: { color: '#a0cfff', fontSize: 11 },
|
||||
min: pMin, max: pMax,
|
||||
axisLabel: { formatter: (v: any) => v.toFixed(1) + '%' }
|
||||
axisLabel: {
|
||||
formatter: (v: any) => v.toFixed(1) + '%',
|
||||
color: '#a0cfff'
|
||||
},
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
@@ -284,15 +324,15 @@ const initChart = () => {
|
||||
lineStyle: {
|
||||
width: 1.5,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 0, color: '#40c4ff' },
|
||||
{ offset: 1, color: '#096dd9' }
|
||||
])
|
||||
},
|
||||
itemStyle: { color: '#1890ff' },
|
||||
itemStyle: { color: '#40c4ff' },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(24,144,255,0.25)' },
|
||||
{ offset: 1, color: 'rgba(24,144,255,0.02)' }
|
||||
{ offset: 0, color: 'rgba(64,196,255,0.25)' },
|
||||
{ offset: 1, color: 'rgba(64,196,255,0.02)' }
|
||||
])
|
||||
},
|
||||
label: { show: false },
|
||||
@@ -327,15 +367,15 @@ const initChart = () => {
|
||||
lineStyle: {
|
||||
width: 1.5,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#cf1322' }
|
||||
])
|
||||
},
|
||||
itemStyle: { color: '#f5222d' },
|
||||
itemStyle: { color: '#ff4d4f' },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(245,34,45,0.25)' },
|
||||
{ offset: 1, color: 'rgba(245,34,45,0.02)' }
|
||||
{ offset: 0, color: 'rgba(255,77,79,0.25)' },
|
||||
{ offset: 1, color: 'rgba(255,77,79,0.02)' }
|
||||
])
|
||||
},
|
||||
label: { show: false },
|
||||
@@ -357,8 +397,8 @@ const initChart = () => {
|
||||
data: expenseMomData,
|
||||
yAxisIndex: 1,
|
||||
...momLineStyle,
|
||||
lineStyle: { ...momLineStyle.lineStyle, color: '#722ed1' },
|
||||
itemStyle: { color: '#722ed1' },
|
||||
lineStyle: { ...momLineStyle.lineStyle, color: '#ffa066' },
|
||||
itemStyle: { color: '#ffa066' },
|
||||
label: { show: false }
|
||||
},
|
||||
{
|
||||
@@ -370,9 +410,11 @@ const initChart = () => {
|
||||
barGap: '10%',
|
||||
barCategoryGap: '20%',
|
||||
itemStyle: {
|
||||
color: (params: any) => params.data >= 0 ? '#52c41a' : '#f5222d',
|
||||
color: (params: any) => params.data >= 0 ? '#40c4ff' : '#ff4d4f',
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
opacity: 0.85
|
||||
opacity: 0.85,
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
label: profitBarLabelConfig,
|
||||
emphasis: { itemStyle: { opacity: 1 } }
|
||||
@@ -430,30 +472,90 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
transition: all 0.3s ease;
|
||||
.month-mom-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
:deep(.ant-card:hover) {
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
}
|
||||
:deep(.echarts-tooltip table) {
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
min-width: 600px;
|
||||
}
|
||||
:deep(.echarts-tooltip small) {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
color: #999;
|
||||
|
||||
:deep(.echarts-tooltip table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar) {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-thumb) {
|
||||
background: rgba(32, 160, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-track) {
|
||||
background: rgba(9, 30, 58, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.echarts-xaxis-label) {
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-yaxis-label) {
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-axis-line) {
|
||||
stroke: rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -8,24 +8,24 @@
|
||||
<div class="layout-container">
|
||||
<div class="left-panel">
|
||||
<div class="stat-card">
|
||||
<span class="stat-title">收入</span>
|
||||
<span class="stat-title">收入(元)</span>
|
||||
<div class="stat-content">
|
||||
<Icon icon="icons/erp-income.png" size="28"/>
|
||||
<span class="stat-value">{{ thisIncome }} (元)</span>
|
||||
<Icon icon="icons/erp-income.png" size="32"/>
|
||||
<span class="stat-value">{{ thisIncome || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-title">支出</span>
|
||||
<span class="stat-title">支出(元)</span>
|
||||
<div class="stat-content">
|
||||
<Icon icon="icons/erp-expense.png" size="28"/>
|
||||
<span class="stat-value">{{ thisExpense }} (元)</span>
|
||||
<Icon icon="icons/erp-expense.png" size="32"/>
|
||||
<span class="stat-value">{{ thisExpense || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-title">占比</span>
|
||||
<div class="stat-content">
|
||||
<Icon icon="icons/erp-share.png" size="28"/>
|
||||
<span class="stat-value">{{ thisShare }} (%)</span>
|
||||
<Icon icon="icons/erp-share.png" size="32"/>
|
||||
<span class="stat-value">{{ thisShare || 0 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +60,6 @@ const selectedMap = ref<Record<string, boolean>>({});
|
||||
|
||||
const totalAmountText = computed(() => {
|
||||
if (!listAccount.value.length) return '0.00';
|
||||
|
||||
let total = 0;
|
||||
listAccount.value.forEach(item => {
|
||||
const name = item.accountName || '未知账户';
|
||||
@@ -79,16 +78,14 @@ const formatPercent = (value: number) => {
|
||||
|
||||
const generateRandomColors = (count: number): string[] => {
|
||||
const colorLibrary = [
|
||||
'#1890ff', '#52c41a', '#f5a623', '#fa8c16', '#722ed1', '#eb2f96',
|
||||
'#13c2c2', '#2f54eb', '#f7ba1e', '#f5222d', '#8543e0', '#0fc6c2',
|
||||
'#7cb305', '#ff7a45', '#ff4d4f', '#6b778c', '#5d7092', '#91d5ff'
|
||||
'#40c4ff', '#20a0ff', '#096dd9', '#1890ff', '#69b1ff', '#8cc5ff',
|
||||
'#91d5ff', '#b3d9f2', '#e6f7ff', '#0050b3', '#13c2c2', '#0fc6c2',
|
||||
'#722ed1', '#52c41a', '#f5a623', '#fa8c16', '#eb2f96', '#2f54eb'
|
||||
];
|
||||
|
||||
if (count <= colorLibrary.length) return colorLibrary.slice(0, count);
|
||||
|
||||
const colors = [...colorLibrary];
|
||||
while (colors.length < count) {
|
||||
const hue = Math.floor(Math.random() * 360);
|
||||
const hue = Math.floor(Math.random() * 60) + 180;
|
||||
colors.push(`hsl(${hue}, 75%, 55%)`);
|
||||
}
|
||||
return colors;
|
||||
@@ -105,10 +102,7 @@ const getThisData = async (params?: Record<string, any>) => {
|
||||
thisExpense.value = Number(res?.[0]?.index02) || 0;
|
||||
thisShare.value = Number(res?.[0]?.index04) || 0;
|
||||
} catch (error) {
|
||||
console.error('获取收支数据失败:', error);
|
||||
thisIncome.value = 0;
|
||||
thisExpense.value = 0;
|
||||
thisShare.value = 0;
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,66 +110,59 @@ const fetchList = async () => {
|
||||
try {
|
||||
const res = await erpAccountListAll();
|
||||
listAccount.value = res || [];
|
||||
listAccount.value.forEach(item => {
|
||||
const name = item.accountName || '未知账户';
|
||||
if (selectedMap.value[name] === undefined) {
|
||||
selectedMap.value[name] = true;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取账户列表失败:', error);
|
||||
console.error(error);
|
||||
listAccount.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const buildChartOption = (): EChartsOption => {
|
||||
if (!listAccount.value.length) {
|
||||
return {
|
||||
title: { text: '暂无账户数据', left: 'center', top: 'middle', textStyle: { fontSize: 14, color: '#999' } },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { show: false },
|
||||
series: []
|
||||
};
|
||||
}
|
||||
|
||||
const pieData = listAccount.value
|
||||
const allPieData = listAccount.value
|
||||
.map(item => ({
|
||||
name: item.accountName || '未知账户',
|
||||
value: Number(item.currentBalance) || 0
|
||||
}))
|
||||
.filter(item => item.value > 0 && item.name);
|
||||
|
||||
if (!pieData.length) {
|
||||
return {
|
||||
title: { text: '暂无有效账户金额数据', left: 'center', top: 'middle', textStyle: { fontSize: 14, color: '#999' } },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { show: false },
|
||||
series: []
|
||||
};
|
||||
}
|
||||
const filteredPieData = allPieData.filter(item => selectedMap.value[item.name] !== false);
|
||||
const colors = generateRandomColors(allPieData.length);
|
||||
const filteredTotal = filteredPieData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const colors = generateRandomColors(pieData.length);
|
||||
const total = pieData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const selected: Record<string, boolean> = {};
|
||||
pieData.forEach(item => {
|
||||
allPieData.forEach(item => {
|
||||
selected[item.name] = selectedMap.value[item.name] !== false;
|
||||
});
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: ({ name, value, percent }: any) => `${name}: ${value} 元 (${(percent * 100).toFixed(1)}%)`,
|
||||
textStyle: { fontSize: 11 },
|
||||
formatter: ({ name, value }: any) => {
|
||||
const percent = filteredTotal > 0 ? (value / filteredTotal) * 100 : 0;
|
||||
return `${name}: ${value} 元 (${percent.toFixed(1)}%)`;
|
||||
},
|
||||
textStyle: { fontSize: 11, color: '#fff' },
|
||||
padding: 8,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.9)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.5)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
borderRadius: 4,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
textStyle: { fontSize: 11, color: '#666' },
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 8,
|
||||
selected: selected,
|
||||
selected,
|
||||
formatter: (name: string) => name.length > 8 ? `${name.slice(0, 8)}...` : name
|
||||
},
|
||||
series: [{
|
||||
@@ -184,28 +171,33 @@ const buildChartOption = (): EChartsOption => {
|
||||
radius: ['30%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 1 },
|
||||
itemStyle: {
|
||||
borderRadius: 6,
|
||||
borderColor: 'rgba(9, 30, 58, 0.8)',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 11,
|
||||
color: '#40c4ff',
|
||||
distance: 10,
|
||||
formatter: (params: any) => {
|
||||
if (total <= 0) return params.name;
|
||||
return `${params.name} ${formatPercent((params.value / total) * 100)}`;
|
||||
},
|
||||
color: '#333',
|
||||
distance: 10
|
||||
if (filteredTotal <= 0) return params.name;
|
||||
const percent = (params.value / filteredTotal) * 100;
|
||||
return `${params.name} ${formatPercent(percent)}`;
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 10,
|
||||
length2: 8,
|
||||
smooth: 0.2,
|
||||
lineStyle: { width: 1, color: '#999' }
|
||||
lineStyle: { width: 1, color: '#91d5ff' }
|
||||
},
|
||||
data: pieData.map((item, index) => ({
|
||||
data: allPieData.map((item, index) => ({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
itemStyle: { color: colors[index] }
|
||||
itemStyle: { color: colors[index] },
|
||||
}))
|
||||
}]
|
||||
};
|
||||
@@ -213,27 +205,16 @@ const buildChartOption = (): EChartsOption => {
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartDom.value) return;
|
||||
|
||||
if (myChart) {
|
||||
myChart.off('legendselectchanged');
|
||||
myChart.dispose();
|
||||
myChart = null;
|
||||
}
|
||||
|
||||
try {
|
||||
myChart = echarts.init(chartDom.value);
|
||||
|
||||
myChart.on('legendselectchanged', (params: any) => {
|
||||
if (params && params.name !== undefined) {
|
||||
selectedMap.value[params.name] = params.selected[params.name];
|
||||
}
|
||||
});
|
||||
|
||||
const option = buildChartOption();
|
||||
myChart.setOption(option);
|
||||
} catch (e) {
|
||||
console.error('初始化图表失败:', e);
|
||||
}
|
||||
myChart = echarts.init(chartDom.value);
|
||||
myChart.on('legendselectchanged', (params: any) => {
|
||||
selectedMap.value[params.name] = params.selected[params.name];
|
||||
myChart?.setOption(buildChartOption());
|
||||
});
|
||||
myChart.setOption(buildChartOption());
|
||||
};
|
||||
|
||||
const resizeChart = () => {
|
||||
@@ -244,7 +225,11 @@ const resizeChart = () => {
|
||||
};
|
||||
|
||||
watch(() => props.formParams, async () => {
|
||||
await Promise.all([fetchList(), getThisData()]);
|
||||
await getThisData();
|
||||
}, { deep: true });
|
||||
|
||||
watch([listAccount, selectedMap], () => {
|
||||
nextTick(() => myChart?.setOption(buildChartOption()));
|
||||
}, { deep: true });
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -258,12 +243,8 @@ onMounted(async () => {
|
||||
onUnmounted(() => {
|
||||
if (resizeTimer) clearTimeout(resizeTimer);
|
||||
window.removeEventListener('resize', resizeChart);
|
||||
if (myChart) {
|
||||
myChart.off('legendselectchanged');
|
||||
myChart.dispose();
|
||||
myChart = null;
|
||||
}
|
||||
selectedMap.value = {};
|
||||
myChart?.dispose();
|
||||
myChart = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -272,17 +253,27 @@ onUnmounted(() => {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
@@ -293,17 +284,19 @@ onUnmounted(() => {
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
display: flex;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
color: #a0cfff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
color: #1890ff;
|
||||
color: #40c4ff;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 0 5px rgba(64, 196, 255, 0.5);
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
@@ -316,53 +309,68 @@ onUnmounted(() => {
|
||||
overflow: hidden !important;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 150px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
box-sizing: border-box;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #f0f7ff;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
border: 1px solid #e6f4ff;
|
||||
flex-shrink: 0;
|
||||
min-height: 0;
|
||||
background-color: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 12px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 0 10px rgba(32, 160, 255, 0.1) !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: rgba(64, 196, 255, 0.5) !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #1f2937;
|
||||
margin-bottom: 8px;
|
||||
color: #a0cfff;
|
||||
margin-bottom: 2px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.stat-content .icon) {
|
||||
flex-shrink: 0;
|
||||
filter: hue-rotate(180deg) saturate(1.5);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
color: #40c4ff;
|
||||
margin-left: auto;
|
||||
text-shadow: 0 0 5px rgba(64, 196, 255, 0.3);
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
@@ -372,6 +380,9 @@ onUnmounted(() => {
|
||||
min-width: 0;
|
||||
overflow: hidden !important;
|
||||
max-height: 100%;
|
||||
background: rgba(9, 30, 58, 0.5) !important;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
@@ -379,15 +390,16 @@ onUnmounted(() => {
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
:deep(.left-panel::-webkit-scrollbar) {
|
||||
width: 4px;
|
||||
background: transparent;
|
||||
background: rgba(9, 30, 58, 0.3);
|
||||
}
|
||||
|
||||
:deep(.left-panel::-webkit-scrollbar-thumb) {
|
||||
background: #d9d9d9;
|
||||
background: rgba(32, 160, 255, 0.5);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@@ -400,7 +412,7 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.left-panel {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
@@ -411,22 +423,16 @@ onUnmounted(() => {
|
||||
padding-right: 0;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
|
||||
.stat-card {
|
||||
min-width: 120px;
|
||||
flex: 1;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
|
||||
.right-panel {
|
||||
height: calc(100% - 132px) !important;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend) {
|
||||
top: auto !important;
|
||||
bottom: 10px !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
orient: 'horizontal' !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<Card title="年度各季收支趋势" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度各季趋势" class="quarter-trend-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, watchEffect } from 'vue';
|
||||
import { Card } from 'ant-design-vue';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { BizItemInfo, bizItemInfoListAll } from '@jeesite/biz/api/biz/itemInfo';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
@@ -18,10 +17,8 @@ const props = defineProps<{
|
||||
const rawData = ref<BizItemInfo[]>([]);
|
||||
const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let myChart: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null; // 修复定时器类型
|
||||
const { createMessage } = useMessage();
|
||||
let resizeTimer: number | null = null;
|
||||
|
||||
// 格式化数字(保留指定位数小数)
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
if (!num) return 0;
|
||||
const parsed = Number(num);
|
||||
@@ -29,13 +26,11 @@ const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
return Number(parsed.toFixed(decimal));
|
||||
};
|
||||
|
||||
// 转换为万元单位
|
||||
const toTenThousandYuan = (num: string | undefined): number => {
|
||||
const rawNum = formatNumber(num);
|
||||
return formatNumber((rawNum / 10000).toString());
|
||||
};
|
||||
|
||||
// 千分位格式化数字
|
||||
const formatWithThousandsSeparator = (num: string | number | undefined): string => {
|
||||
const parsed = Number(num);
|
||||
if (isNaN(parsed)) return '0.00';
|
||||
@@ -45,20 +40,17 @@ const formatWithThousandsSeparator = (num: string | number | undefined): string
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化百分比
|
||||
const formatPercentage = (num: string | undefined, decimal = 2): string => {
|
||||
const parsed = formatNumber(num, decimal);
|
||||
return `${parsed.toFixed(decimal)}%`;
|
||||
};
|
||||
|
||||
// 计算净收益
|
||||
const calculateNetProfit = (income: string | undefined, expense: string | undefined): number => {
|
||||
const incomeNum = formatNumber(income);
|
||||
const expenseNum = formatNumber(expense);
|
||||
return formatNumber((incomeNum - expenseNum).toString());
|
||||
};
|
||||
|
||||
// 按月份排序
|
||||
const sortByMonth = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
return data.sort((a, b) => {
|
||||
const monthA = a.xaxis ? parseInt(a.xaxis, 10) : 0;
|
||||
@@ -67,7 +59,6 @@ const sortByMonth = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
});
|
||||
};
|
||||
|
||||
// 计算金额轴的范围
|
||||
const calculateYAxisExtent = (data: number[]) => {
|
||||
if (data.length === 0) return [0, 10];
|
||||
|
||||
@@ -85,7 +76,6 @@ const calculateYAxisExtent = (data: number[]) => {
|
||||
return [minExtent, maxExtent];
|
||||
};
|
||||
|
||||
// 计算百分比轴的范围
|
||||
const calculatePercentYAxisExtent = (data: number[]) => {
|
||||
if (data.length === 0) return [0, 100];
|
||||
|
||||
@@ -101,7 +91,6 @@ const calculatePercentYAxisExtent = (data: number[]) => {
|
||||
return [0, maxExtent > 0 ? maxExtent : 10];
|
||||
};
|
||||
|
||||
// 获取数据列表
|
||||
const fetchDataList = async (params?: Record<string, any>) => {
|
||||
try {
|
||||
const reqParams = {
|
||||
@@ -116,34 +105,30 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
rawData.value = sortByMonth(validData);
|
||||
} catch (error) {
|
||||
console.error('获取数据列表失败:', error);
|
||||
createMessage.error('获取收支趋势数据失败,请稍后重试'); // 增加友好提示
|
||||
rawData.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化/更新图表(核心优化)
|
||||
const initChart = () => {
|
||||
// 确保DOM元素存在
|
||||
if (!chartDom.value) return;
|
||||
|
||||
// 懒初始化图表实例,避免重复销毁创建
|
||||
if (!myChart) {
|
||||
myChart = echarts.init(chartDom.value);
|
||||
}
|
||||
|
||||
// 无数据时的配置
|
||||
if (rawData.value.length === 0) {
|
||||
myChart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { data: ['收入', '支出', '支出占比'], top: 10, textStyle: { fontSize: 12 } },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } } },
|
||||
legend: { data: ['收入', '支出', '支出占比'], top: 10, textStyle: { fontSize: 12, color: '#a0cfff' } },
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -152,41 +137,46 @@ const initChart = () => {
|
||||
data: [],
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
lineHeight: 1.2
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
color: '#a0cfff',
|
||||
margin: 6
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
position: 'right',
|
||||
@@ -194,16 +184,15 @@ const initChart = () => {
|
||||
],
|
||||
series: [],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无收入支出数据',
|
||||
textStyle: { fontSize: 16, color: '#666', fontWeight: '500' },
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 16, color: '#a0cfff', fontWeight: '500' },
|
||||
position: 'center',
|
||||
effect: 'none'
|
||||
}
|
||||
}, true); // 强制更新配置
|
||||
}, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理图表数据
|
||||
const xAxisData = rawData.value.map(item => item.xaxis);
|
||||
const incomeData = rawData.value.map(item => toTenThousandYuan(item.index01));
|
||||
const expenseData = rawData.value.map(item => toTenThousandYuan(item.index02));
|
||||
@@ -213,15 +202,16 @@ const initChart = () => {
|
||||
const [amountMin, amountMax] = calculateYAxisExtent(amountData);
|
||||
const [percentMin, percentMax] = calculatePercentYAxisExtent(proportionData);
|
||||
|
||||
// 标签配置
|
||||
const barLabelConfig = {
|
||||
show: true,
|
||||
position: 'top',
|
||||
distance: 3,
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
fontWeight: '500'
|
||||
color: '#40c4ff',
|
||||
fontWeight: '500',
|
||||
textShadowColor: 'rgba(0,0,0,0.3)',
|
||||
textShadowBlur: 1
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(2)}`
|
||||
};
|
||||
@@ -233,55 +223,56 @@ const initChart = () => {
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
fontWeight: '500',
|
||||
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.8)',
|
||||
color: '#a0cfff',
|
||||
padding: [2, 4],
|
||||
borderRadius: 2
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(1)}%`
|
||||
};
|
||||
|
||||
// 图表主配置
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#20a0ff' }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontSize: 12 },
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any[]) => {
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px;">暂无数据</div>';
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px; color: #a0cfff;">暂无数据</div>';
|
||||
|
||||
const currentQuarter = params[0]?.axisValue || '';
|
||||
const item = rawData.value.find(i => i.xaxis === currentQuarter);
|
||||
|
||||
if (!item) return `<div style="padding: 8px;">${currentQuarter}:暂无明细</div>`;
|
||||
if (!item) return `<div style="padding: 8px; color: #a0cfff;">${currentQuarter}:暂无明细</div>`;
|
||||
|
||||
const netProfit = calculateNetProfit(item.index01, item.index02);
|
||||
const netProfitColor = netProfit > 0 ? '#52c41a' : netProfit < 0 ? '#f5222d' : '#666';
|
||||
const netProfitColor = netProfit > 0 ? '#40c4ff' : netProfit < 0 ? '#ff4d4f' : '#a0cfff';
|
||||
|
||||
return `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 8px; text-align: center;">${currentQuarter}</div>
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 8px; text-align: center;">${currentQuarter}</div>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 11px; min-width: 400px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8f8f8;">
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">支出占比</th>
|
||||
<tr style="background-color: rgba(18, 60, 110, 0.5);">
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">支出占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #1890ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #f5222d;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #722ed1;">${formatPercentage(item.index03)}</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #40c4ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ff4d4f;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ffa066;">${formatPercentage(item.index03)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -292,12 +283,12 @@ const initChart = () => {
|
||||
data: ['收入', '支出', '支出占比'],
|
||||
top: 10,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 11 }
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' }
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40,
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -306,16 +297,18 @@ const initChart = () => {
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
margin: 8
|
||||
margin: 6,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
},
|
||||
boundaryGap: true
|
||||
},
|
||||
@@ -323,32 +316,34 @@ const initChart = () => {
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
formatter: (value: number) => value.toFixed(2),
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: amountMin,
|
||||
max: amountMax,
|
||||
axisLine: { onZero: true },
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`,
|
||||
color: '#a0cfff'
|
||||
},
|
||||
min: percentMin,
|
||||
max: percentMax,
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: false,
|
||||
position: 'right',
|
||||
@@ -357,7 +352,7 @@ const initChart = () => {
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 14, color: '#999' },
|
||||
textStyle: { fontSize: 14, color: '#a0cfff' },
|
||||
effect: 'bubble',
|
||||
effectOption: { effect: { n: 0 } }
|
||||
},
|
||||
@@ -368,10 +363,12 @@ const initChart = () => {
|
||||
data: incomeData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 0, color: '#40c4ff' },
|
||||
{ offset: 1, color: '#096dd9' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
barGap: '30%',
|
||||
@@ -381,9 +378,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#1890ff' }
|
||||
])
|
||||
{ offset: 0, color: '#69b1ff' },
|
||||
{ offset: 1, color: '#40c4ff' }
|
||||
]),
|
||||
shadowColor: 'rgba(64, 196, 255, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -393,10 +392,12 @@ const initChart = () => {
|
||||
data: expenseData,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#cf1322' }
|
||||
]),
|
||||
borderRadius: [6, 6, 0, 0]
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
barWidth: 12,
|
||||
label: barLabelConfig,
|
||||
@@ -404,9 +405,11 @@ const initChart = () => {
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#f5222d' }
|
||||
])
|
||||
{ offset: 0, color: '#ff7875' },
|
||||
{ offset: 1, color: '#ff4d4f' }
|
||||
]),
|
||||
shadowColor: 'rgba(255, 77, 79, 0.5)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -420,12 +423,12 @@ const initChart = () => {
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#722ed1',
|
||||
color: '#ffa066',
|
||||
type: 'solid'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#722ed1',
|
||||
borderColor: '#fff',
|
||||
color: '#ffa066',
|
||||
borderColor: 'rgba(9, 30, 58, 0.8)',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: lineLabelConfig,
|
||||
@@ -441,11 +444,9 @@ const initChart = () => {
|
||||
animationEasingUpdate: 'quinticInOut'
|
||||
};
|
||||
|
||||
// 关键:第二个参数设为true,强制更新配置
|
||||
myChart.setOption(option, true);
|
||||
};
|
||||
|
||||
// 调整图表大小(防抖)
|
||||
const resizeChart = () => {
|
||||
if (myChart) {
|
||||
myChart.resize({
|
||||
@@ -462,7 +463,6 @@ const debounceResize = () => {
|
||||
resizeTimer = window.setTimeout(resizeChart, 100);
|
||||
};
|
||||
|
||||
// 监听formParams变化,自动重新获取数据
|
||||
watch(
|
||||
() => props.formParams,
|
||||
(newParams) => {
|
||||
@@ -473,27 +473,20 @@ watch(
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 监听数据/DOM变化,自动更新图表(更高效)
|
||||
watchEffect(() => {
|
||||
if (rawData.value || chartDom.value) {
|
||||
initChart();
|
||||
}
|
||||
});
|
||||
|
||||
// 生命周期管理
|
||||
onMounted(() => {
|
||||
// 初始加载数据
|
||||
fetchDataList(props.formParams);
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', debounceResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理定时器,防止内存泄漏
|
||||
if (resizeTimer) clearTimeout(resizeTimer);
|
||||
// 移除事件监听
|
||||
window.removeEventListener('resize', debounceResize);
|
||||
// 销毁图表实例
|
||||
if (myChart) {
|
||||
myChart.dispose();
|
||||
myChart = null;
|
||||
@@ -502,71 +495,59 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
.quarter-trend-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card:hover) {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
max-width: 450px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
@@ -579,19 +560,23 @@ onUnmounted(() => {
|
||||
:deep(.echarts-xaxis-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.2;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-yaxis-name) {
|
||||
margin-right: 3px;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend-item) {
|
||||
margin-right: 12px !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
white-space: nowrap;
|
||||
color: #40c4ff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar) {
|
||||
@@ -600,12 +585,18 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-thumb) {
|
||||
background: #d9d9d9;
|
||||
background: rgba(32, 160, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-track) {
|
||||
background: #f5f5f5;
|
||||
background: rgba(9, 30, 58, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.status-filter-container,
|
||||
.status-filter,
|
||||
.status-item {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Card title="年度支出排名及占比" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: calc(100% - 40px);"></div>
|
||||
<Card title="年度支出排名及占比" class="expense-ranking-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -19,24 +19,20 @@ const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
|
||||
// 格式化数字
|
||||
const formatNumber = (num: string | undefined): number => {
|
||||
if (!num) return 0;
|
||||
const parsed = Number(num);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
};
|
||||
|
||||
// 格式化金额显示
|
||||
const formatMoney = (num: number): string => {
|
||||
return num.toLocaleString('zh-CN');
|
||||
};
|
||||
|
||||
// 转换为万元单位
|
||||
const toTenThousandYuan = (num: number): number => {
|
||||
return Number((num / 10000).toFixed(2));
|
||||
};
|
||||
|
||||
// 按序号排序
|
||||
const sortBySeq = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
return data.sort((a, b) => {
|
||||
const seqA = a.seq ? parseInt(a.seq, 10) : 999;
|
||||
@@ -45,7 +41,6 @@ const sortBySeq = (data: BizItemInfo[]): BizItemInfo[] => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取数据列表
|
||||
const fetchDataList = async (params?: Record<string, any>) => {
|
||||
try {
|
||||
const reqParams = {
|
||||
@@ -62,86 +57,77 @@ const fetchDataList = async (params?: Record<string, any>) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取排名对应的渐变颜色
|
||||
const getRankColorGradient = (rank: number) => {
|
||||
if (rank === 1) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#ff4d4f' },
|
||||
{ offset: 0.5, color: '#ff7875' },
|
||||
{ offset: 1, color: '#ff8c8c' }
|
||||
]);
|
||||
} else if (rank === 2) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#ffa940' },
|
||||
{ offset: 0.5, color: '#ffc069' },
|
||||
{ offset: 1, color: '#ffd08f' }
|
||||
]);
|
||||
} else if (rank === 3) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#ffec3d' },
|
||||
{ offset: 0.5, color: '#fff566' },
|
||||
{ offset: 1, color: '#fff98f' }
|
||||
]);
|
||||
} else {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 0.5, color: '#69c0ff' },
|
||||
{ offset: 1, color: '#91d5ff' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const getRankEmphasisColor = (rank: number) => {
|
||||
if (rank === 1) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 0.5, color: '#ff4d4f' },
|
||||
{ offset: 1, color: '#ff7875' }
|
||||
{ offset: 1, color: '#ff4d4f' }
|
||||
]);
|
||||
} else if (rank === 2) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fa8c16' },
|
||||
{ offset: 0.5, color: '#ffa940' },
|
||||
{ offset: 1, color: '#ffc069' }
|
||||
{ offset: 1, color: '#ffa940' }
|
||||
]);
|
||||
} else if (rank === 3) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fadb14' },
|
||||
{ offset: 0.5, color: '#ffec3d' },
|
||||
{ offset: 1, color: '#fff566' }
|
||||
{ offset: 1, color: '#ffec3d' }
|
||||
]);
|
||||
} else {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 0.5, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#69c0ff' }
|
||||
{ offset: 1, color: '#40a9ff' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取hover时的强调色
|
||||
const getRankEmphasisColor = (rank: number) => {
|
||||
if (rank === 1) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#cf1322' },
|
||||
{ offset: 1, color: '#f5222d' }
|
||||
]);
|
||||
} else if (rank === 2) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#d46b08' },
|
||||
{ offset: 1, color: '#fa8c16' }
|
||||
]);
|
||||
} else if (rank === 3) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#d4b106' },
|
||||
{ offset: 1, color: '#fadb14' }
|
||||
]);
|
||||
} else {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#096dd9' },
|
||||
{ offset: 1, color: '#1890ff' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化/更新图表
|
||||
const initChart = () => {
|
||||
// 确保DOM元素存在
|
||||
if (!chartDom.value) return;
|
||||
|
||||
// 初始化图表实例(避免重复创建)
|
||||
if (!chartInstance) {
|
||||
chartInstance = echarts.init(chartDom.value);
|
||||
}
|
||||
|
||||
// 无数据时显示提示
|
||||
if (rawData.value.length === 0) {
|
||||
chartInstance.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
top: '50%',
|
||||
text: '暂无排名数据',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'axis' },
|
||||
padding: [2, 2, 2, 2]
|
||||
}, true); // 第二个参数设为true,强制更新
|
||||
}, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理图表数据
|
||||
const categories = rawData.value.map(item => item.xaxis || '').reverse();
|
||||
const rawAmountData = rawData.value.map(item => formatNumber(item.index01)).reverse();
|
||||
const amountData = rawAmountData.map(num => toTenThousandYuan(num));
|
||||
@@ -151,46 +137,45 @@ const initChart = () => {
|
||||
}).reverse();
|
||||
const rankData = rawData.value.map(item => parseInt(item.seq || '0', 10)).reverse();
|
||||
|
||||
// 图表配置项
|
||||
const option = {
|
||||
padding: [2, 2, 2, 2],
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontSize: 12 },
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: '#40c4ff' } },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any) => {
|
||||
const index = params[0].dataIndex;
|
||||
return `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 8px;">${categories[index]}</div>
|
||||
<div style="color: #666; margin-bottom: 4px;">排名:${rankData[index]}名</div>
|
||||
<div style="color: #666; margin-bottom: 4px;">支出:${formatMoney(rawAmountData[index])} 元</div>
|
||||
<div style="color: #666;">占比:${ratioData[index]}%</div>
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 8px;">${categories[index]}</div>
|
||||
<div style="color: #a0cfff; margin-bottom: 4px;">排名:${rankData[index]}名</div>
|
||||
<div style="color: #a0cfff; margin-bottom: 4px;">支出:${formatMoney(rawAmountData[index])} 元</div>
|
||||
<div style="color: #a0cfff;">占比:${ratioData[index]}%</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '18%',
|
||||
top: '2%',
|
||||
bottom: '2%',
|
||||
left: 10,
|
||||
right: 120,
|
||||
top: 10,
|
||||
bottom: 12,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '支出金额 (万元)',
|
||||
nameTextStyle: { fontSize: 11, color: '#666' },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
color: '#666',
|
||||
color: '#a0cfff',
|
||||
formatter: (value: number) => value.toFixed(1)
|
||||
},
|
||||
axisLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
splitLine: { lineStyle: { color: '#f5f5f5' } }
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } }
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
@@ -199,7 +184,7 @@ const initChart = () => {
|
||||
data: categories,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
color: '#333',
|
||||
color: '#a0cfff',
|
||||
formatter: (name: string, index: number) => {
|
||||
const shortName = name.length > 8 ? `${name.substring(0, 8)}...` : name;
|
||||
return `${rankData[index]} ${shortName}`;
|
||||
@@ -209,7 +194,7 @@ const initChart = () => {
|
||||
axisTick: { show: false },
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ['#fafafa', '#fff'],
|
||||
color: 'rgba(32, 160, 255, 0.1)',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
@@ -223,29 +208,33 @@ const initChart = () => {
|
||||
barWidth: 12,
|
||||
itemStyle: {
|
||||
borderRadius: [0, 4, 4, 0],
|
||||
color: (params: any) => getRankColorGradient(rankData[params.dataIndex])
|
||||
color: (params: any) => getRankColorGradient(rankData[params.dataIndex]),
|
||||
borderColor: 'rgba(255,255,255,0.2)',
|
||||
borderWidth: 0.5
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
fontSize: 10,
|
||||
color: '#666',
|
||||
formatter: (params: any) => `${params.data}万元 (${ratioData[params.dataIndex]}%)`
|
||||
color: '#40c4ff',
|
||||
formatter: (params: any) => `${params.data}万元 (${ratioData[params.dataIndex]}%)`,
|
||||
textShadowColor: 'rgba(0,0,0,0.3)',
|
||||
textShadowBlur: 1
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: (params: any) => getRankEmphasisColor(rankData[params.dataIndex])
|
||||
color: (params: any) => getRankEmphasisColor(rankData[params.dataIndex]),
|
||||
shadowColor: 'rgba(255,255,255,0.2)',
|
||||
shadowBlur: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 设置配置项,强制更新(关键:第二个参数为true)
|
||||
chartInstance.setOption(option, true);
|
||||
};
|
||||
|
||||
// 防抖调整图表大小
|
||||
const resizeChart = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize({
|
||||
@@ -262,7 +251,6 @@ const debounceResize = () => {
|
||||
resizeTimer = window.setTimeout(resizeChart, 100);
|
||||
};
|
||||
|
||||
// 监听formParams变化,重新获取数据
|
||||
watch(
|
||||
() => props.formParams,
|
||||
(newParams) => {
|
||||
@@ -273,28 +261,20 @@ watch(
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 监听数据变化,更新图表(使用watchEffect更高效)
|
||||
watchEffect(() => {
|
||||
// 当rawData或chartDom变化时,重新初始化图表
|
||||
if (rawData.value || chartDom.value) {
|
||||
initChart();
|
||||
}
|
||||
});
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 初始加载数据
|
||||
fetchDataList(props.formParams);
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', debounceResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理定时器
|
||||
if (resizeTimer) clearTimeout(resizeTimer);
|
||||
// 移除事件监听
|
||||
window.removeEventListener('resize', debounceResize);
|
||||
// 销毁图表实例
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
chartInstance = null;
|
||||
@@ -303,78 +283,66 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
padding: 0 !important;
|
||||
.expense-ranking-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 16px !important;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
background: rgba(9, 30, 58, 0.95) !important;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-bar) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.echarts-axis-line) {
|
||||
stroke: #e8e8e8;
|
||||
stroke: rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-text) {
|
||||
fill: #666;
|
||||
fill: #a0cfff !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,18 @@
|
||||
<template>
|
||||
<Card title="年度收支趋势及占比" style="width: 100%; height: 100%;">
|
||||
<div ref="chartDom" style="width: 100%; height: 100%;"></div>
|
||||
<Card title="年度趋势及占比" class="income-expense-trend-card">
|
||||
<div ref="chartDom" class="chart-container"></div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { Card } from 'ant-design-vue';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { BizItemInfo, bizItemInfoListAll } from '@jeesite/biz/api/biz/itemInfo';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const rawData = ref<BizItemInfo[]>([]);
|
||||
const chartDom = ref<HTMLDivElement | null>(null);
|
||||
let myChart: echarts.ECharts | null = null;
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const formatNumber = (num: string | undefined, decimal = 2): number => {
|
||||
if (!num) return 0;
|
||||
@@ -37,7 +35,6 @@ const formatWithThousandsSeparator = (num: string | number | undefined): string
|
||||
});
|
||||
};
|
||||
|
||||
// 新增:格式化百分比数据
|
||||
const formatPercentage = (num: string | undefined, decimal = 2): string => {
|
||||
const parsed = formatNumber(num, decimal);
|
||||
return `${parsed.toFixed(decimal)}%`;
|
||||
@@ -80,20 +77,17 @@ const calculateYAxisExtent = (data: number[]) => {
|
||||
return [minExtent, maxExtent];
|
||||
};
|
||||
|
||||
// 新增:计算百分比Y轴范围
|
||||
const calculatePercentYAxisExtent = (data: number[]) => {
|
||||
if (data.length === 0) return [0, 100];
|
||||
|
||||
const min = Math.min(...data);
|
||||
const max = Math.max(...data);
|
||||
|
||||
// 百分比数据的padding调整为5%
|
||||
const padding = Math.max(Math.abs(max - min) * 0.1, 5);
|
||||
|
||||
let minExtent = Math.floor(min - padding);
|
||||
let maxExtent = Math.ceil(max + padding);
|
||||
|
||||
// 确保最小值不小于0(百分比通常非负)
|
||||
if (minExtent < 0) minExtent = 0;
|
||||
|
||||
return [minExtent, maxExtent];
|
||||
@@ -129,17 +123,17 @@ const initChart = () => {
|
||||
|
||||
if (rawData.value.length === 0) {
|
||||
myChart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
// 移除环比,只保留收入、支出、占比
|
||||
legend: { data: ['收入', '支出', '占比'], top: 10, textStyle: { fontSize: 12 } },
|
||||
legend: { data: ['收入', '支出', '占比'], top: 10, textStyle: { fontSize: 12, color: '#a0cfff' } },
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40, // 增加右侧边距给百分比Y轴
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -148,51 +142,58 @@ const initChart = () => {
|
||||
data: [],
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
color: '#a0cfff',
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
lineHeight: 1.2
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
margin: 6
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
},
|
||||
splitLine: { show: false }, // 不显示百分比轴的分割线
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true,
|
||||
position: 'right', // 百分比轴显示在右侧
|
||||
max: 100 // 百分比最大100
|
||||
position: 'right',
|
||||
max: 100
|
||||
}
|
||||
],
|
||||
series: [],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无收入支出数据',
|
||||
textStyle: { fontSize: 16, color: '#666', fontWeight: '500' },
|
||||
textStyle: { fontSize: 16, color: '#a0cfff', fontWeight: '500' },
|
||||
position: 'center',
|
||||
effect: 'none'
|
||||
}
|
||||
@@ -203,12 +204,10 @@ const initChart = () => {
|
||||
const xAxisData = rawData.value.map(item => formatYear(item.xaxis));
|
||||
const incomeData = rawData.value.map(item => toTenThousandYuan(item.index01));
|
||||
const expenseData = rawData.value.map(item => toTenThousandYuan(item.index02));
|
||||
// 移除环比数据,只保留占比数据
|
||||
const proportionData = rawData.value.map(item => formatNumber(item.index04));
|
||||
|
||||
const amountData = [...incomeData, ...expenseData];
|
||||
const [amountMin, amountMax] = calculateYAxisExtent(amountData);
|
||||
// 只计算占比的百分比轴范围
|
||||
const [percentMin, percentMax] = calculatePercentYAxisExtent(proportionData);
|
||||
|
||||
const barLabelConfig = {
|
||||
@@ -217,13 +216,12 @@ const initChart = () => {
|
||||
distance: 3,
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
color: '#333',
|
||||
color: '#40c4ff',
|
||||
fontWeight: '500'
|
||||
},
|
||||
formatter: (params: any) => `${params.value.toFixed(2)}`
|
||||
};
|
||||
|
||||
// 新增:折线图标签配置
|
||||
const lineLabelConfig = {
|
||||
show: true,
|
||||
position: 'top',
|
||||
@@ -231,7 +229,8 @@ const initChart = () => {
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
fontWeight: '500',
|
||||
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.8)',
|
||||
color: '#40c4ff',
|
||||
padding: [2, 4],
|
||||
borderRadius: 2
|
||||
},
|
||||
@@ -239,46 +238,47 @@ const initChart = () => {
|
||||
};
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 18, color: '#333' }
|
||||
textStyle: { fontSize: 18, color: '#a0cfff' }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontSize: 12 },
|
||||
axisPointer: { type: 'shadow', lineStyle: { color: 'rgba(32, 160, 255, 0.5)' } },
|
||||
textStyle: { fontSize: 12, color: '#fff' },
|
||||
padding: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e8e8e8',
|
||||
backgroundColor: 'rgba(9, 30, 58, 0.95)',
|
||||
borderColor: 'rgba(32, 160, 255, 0.3)',
|
||||
borderWidth: 1,
|
||||
formatter: (params: any[]) => {
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px;">暂无数据</div>';
|
||||
if (!params || params.length === 0) return '<div style="padding: 8px; color: #fff;">暂无数据</div>';
|
||||
|
||||
const currentMonth = params[0]?.axisValue || '';
|
||||
const item = rawData.value.find(i => formatYear(i.xaxis) === currentMonth);
|
||||
|
||||
if (!item) return `<div style="padding: 8px;">${currentMonth}:暂无明细</div>`;
|
||||
if (!item) return `<div style="padding: 8px; color: #fff;">${currentMonth}:暂无明细</div>`;
|
||||
|
||||
const netProfit = calculateNetProfit(item.index01, item.index02);
|
||||
const netProfitColor = netProfit > 0 ? '#52c41a' : netProfit < 0 ? '#f5222d' : '#666';
|
||||
const netProfitColor = netProfit > 0 ? '#52c41a' : netProfit < 0 ? '#f5222d' : '#a0cfff';
|
||||
|
||||
return `
|
||||
<div style="font-weight: 600; color: #333; margin-bottom: 8px; text-align: center;">${currentMonth}</div>
|
||||
<div style="font-weight: 600; color: #40c4ff; margin-bottom: 8px; text-align: center;">${currentMonth}</div>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 11px; min-width: 400px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8f8f8;">
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; font-weight: 600;">占比</th>
|
||||
<tr style="background-color: rgba(18, 60, 110, 0.7);">
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总收入</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">总支出</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">净收益</th>
|
||||
<th style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; font-weight: 600; color: #a0cfff;">占比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #1890ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #f5222d;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid #e8e8e8; text-align: center; color: #722ed1;">${formatPercentage(item.index04)}</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #40c4ff;">${formatWithThousandsSeparator(item.index01)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #ff6b6b;">${formatWithThousandsSeparator(item.index02)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: ${netProfitColor};">${formatWithThousandsSeparator(netProfit)} 元</td>
|
||||
<td style="padding: 8px; border: 1px solid rgba(32, 160, 255, 0.3); text-align: center; color: #c4b5fd;">${formatPercentage(item.index04)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -286,16 +286,15 @@ const initChart = () => {
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
// 移除环比,只保留收入、支出、占比
|
||||
data: ['收入', '支出', '占比'],
|
||||
top: 10,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 11 }
|
||||
textStyle: { fontSize: 11, color: '#a0cfff' }
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 40, // 增加右侧边距给百分比Y轴
|
||||
bottom: 60,
|
||||
right: 20,
|
||||
bottom: 12,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
@@ -304,16 +303,18 @@ const initChart = () => {
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: 60,
|
||||
color: '#a0cfff',
|
||||
rotate: 45,
|
||||
overflow: 'truncate',
|
||||
width: 60,
|
||||
width: 50,
|
||||
lineHeight: 1.2,
|
||||
margin: 8
|
||||
margin: 6
|
||||
},
|
||||
axisLine: { onZero: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
length: 3
|
||||
length: 3,
|
||||
lineStyle: { color: 'rgba(32, 160, 255, 0.3)' }
|
||||
},
|
||||
boundaryGap: true
|
||||
},
|
||||
@@ -321,40 +322,42 @@ const initChart = () => {
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(万元)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (value: number) => value.toFixed(2)
|
||||
},
|
||||
min: amountMin,
|
||||
max: amountMax,
|
||||
axisLine: { onZero: true },
|
||||
splitLine: { lineStyle: { color: '#e8e8e8' } },
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { lineStyle: { color: 'rgba(32, 160, 255, 0.1)' } },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '百分比(%)',
|
||||
nameTextStyle: { fontSize: 11 },
|
||||
name: '%',
|
||||
nameTextStyle: { fontSize: 11, color: '#a0cfff' },
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
color: '#a0cfff',
|
||||
formatter: (value: number) => `${value.toFixed(1)}%`
|
||||
},
|
||||
min: percentMin,
|
||||
max: percentMax,
|
||||
axisLine: { onZero: true },
|
||||
splitLine: { show: false }, // 不显示百分比轴的分割线
|
||||
axisTick: { alignWithLabel: true },
|
||||
axisLine: { onZero: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true, lineStyle: { color: 'rgba(32, 160, 255, 0.3)' } },
|
||||
splitNumber: 5,
|
||||
scale: true,
|
||||
position: 'right', // 百分比轴显示在右侧
|
||||
position: 'right',
|
||||
}
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无数据',
|
||||
textStyle: { fontSize: 14, color: '#999' },
|
||||
textStyle: { fontSize: 14, color: '#a0cfff' },
|
||||
effect: 'bubble',
|
||||
effectOption: { effect: { n: 0 } }
|
||||
},
|
||||
@@ -407,23 +410,22 @@ const initChart = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
// 只保留占比曲线,移除环比曲线
|
||||
{
|
||||
name: '占比',
|
||||
type: 'line',
|
||||
data: proportionData,
|
||||
yAxisIndex: 1, // 使用右侧的百分比Y轴
|
||||
yAxisIndex: 1,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
smooth: true, // 平滑曲线
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#722ed1', // 紫色
|
||||
color: '#722ed1',
|
||||
type: 'solid'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#722ed1',
|
||||
borderColor: '#fff',
|
||||
borderColor: 'rgba(32, 160, 255, 0.5)',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: lineLabelConfig,
|
||||
@@ -478,73 +480,59 @@ watch(rawData, () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-filter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.status-item:not(.active):hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
padding: 0 !important;
|
||||
.income-expense-trend-card {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 0 15px rgba(32, 160, 255, 0.1) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: rgba(9, 30, 58, 0.8) !important;
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card:hover) {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
:deep(.ant-card-head) {
|
||||
padding: 0 16px !important;
|
||||
border-bottom: 1px solid rgba(32, 160, 255, 0.2) !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
background: rgba(18, 60, 110, 0.7) !important;
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-head-title) {
|
||||
color: #20a0ff !important;
|
||||
font-weight: 600 !important;
|
||||
text-shadow: 0 0 5px rgba(32, 160, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
height: calc(100% - 56px) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip) {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e8e8e8 !important;
|
||||
max-width: 500px; /* 加宽tooltip以容纳新增列 */
|
||||
border: 1px solid rgba(32, 160, 255, 0.3) !important;
|
||||
max-width: 500px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
background-color: rgba(9, 30, 58, 0.95) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip table) {
|
||||
@@ -555,19 +543,23 @@ watch(rawData, () => {
|
||||
:deep(.echarts-xaxis-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.2;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-yaxis-name) {
|
||||
margin-right: 3px;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-legend-item) {
|
||||
margin-right: 12px !important;
|
||||
color: #a0cfff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-label) {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
white-space: nowrap;
|
||||
color: #40c4ff !important;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar) {
|
||||
@@ -576,12 +568,18 @@ watch(rawData, () => {
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-thumb) {
|
||||
background: #d9d9d9;
|
||||
background: rgba(32, 160, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.echarts-tooltip::-webkit-scrollbar-track) {
|
||||
background: #f5f5f5;
|
||||
background: rgba(9, 30, 58, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.status-filter-container,
|
||||
.status-filter,
|
||||
.status-item {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
558
web-vue/pnpm-lock.yaml
generated
558
web-vue/pnpm-lock.yaml
generated
@@ -171,6 +171,9 @@ importers:
|
||||
rimraf:
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1
|
||||
sass-embedded:
|
||||
specifier: ^1.97.3
|
||||
version: 1.97.3
|
||||
stylelint:
|
||||
specifier: 16.25.0
|
||||
version: 16.25.0(typescript@5.9.3)
|
||||
@@ -203,10 +206,10 @@ importers:
|
||||
version: 5.9.3
|
||||
unocss:
|
||||
specifier: 66.5.3
|
||||
version: 66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite:
|
||||
specifier: 7.1.9
|
||||
version: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
version: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
packages/assets: {}
|
||||
|
||||
@@ -325,16 +328,16 @@ importers:
|
||||
version: 1.15.8
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
rollup-plugin-visualizer:
|
||||
specifier: 6.0.4
|
||||
version: 6.0.4(rollup@4.53.3)
|
||||
vite-plugin-dts:
|
||||
specifier: 4.5.4
|
||||
version: 4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-theme-vite3:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
|
||||
packages/dbm:
|
||||
dependencies:
|
||||
@@ -381,13 +384,13 @@ importers:
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
vite-plugin-dts:
|
||||
specifier: 4.5.4
|
||||
version: 4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vitest:
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
packages/erp:
|
||||
dependencies:
|
||||
@@ -478,13 +481,13 @@ importers:
|
||||
version: 11.0.4
|
||||
'@vitejs/plugin-legacy':
|
||||
specifier: 7.2.1
|
||||
version: 7.2.1(terser@5.44.1)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 7.2.1(terser@5.44.1)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
version: 6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
'@vitejs/plugin-vue-jsx':
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
version: 5.1.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))
|
||||
dotenv:
|
||||
specifier: 17.2.3
|
||||
version: 17.2.3
|
||||
@@ -499,28 +502,28 @@ importers:
|
||||
version: 6.0.4(rollup@4.53.3)
|
||||
unbuild:
|
||||
specifier: 3.6.1
|
||||
version: 3.6.1(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3))
|
||||
version: 3.6.1(sass@1.97.3)(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3))
|
||||
unocss:
|
||||
specifier: 66.5.3
|
||||
version: 66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-compression:
|
||||
specifier: 0.5.1
|
||||
version: 0.5.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 0.5.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-html:
|
||||
specifier: 3.2.2
|
||||
version: 3.2.2(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 3.2.2(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-mkcert:
|
||||
specifier: 1.17.9
|
||||
version: 1.17.9(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 1.17.9(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-monaco-editor-esm:
|
||||
specifier: 2.0.2
|
||||
version: 2.0.2(monaco-editor@0.54.0)
|
||||
vite-plugin-theme-vite3:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
vite-plugin-vue-setup-extend:
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
version: 0.4.0(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
|
||||
web: {}
|
||||
|
||||
@@ -1079,6 +1082,9 @@ packages:
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bufbuild/protobuf@2.11.0':
|
||||
resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==}
|
||||
|
||||
'@cacheable/memory@2.0.6':
|
||||
resolution: {integrity: sha512-7e8SScMocHxcAb8YhtkbMhGG+EKLRIficb1F5sjvhSYsWTZGxvg4KIDp8kgxnV2PUJ3ddPe6J9QESjKvBWRDkg==}
|
||||
|
||||
@@ -1777,6 +1783,94 @@ packages:
|
||||
'@paralleldrive/cuid2@2.3.1':
|
||||
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2931,6 +3025,10 @@ packages:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
citty@0.1.6:
|
||||
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
|
||||
|
||||
@@ -2977,6 +3075,9 @@ packages:
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
colorjs.io@0.5.2:
|
||||
resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -3261,6 +3362,10 @@ packages:
|
||||
resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-newline@4.0.1:
|
||||
resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -3776,6 +3881,7 @@ packages:
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
glob@11.1.0:
|
||||
@@ -3943,6 +4049,9 @@ packages:
|
||||
immer@9.0.21:
|
||||
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
|
||||
|
||||
immutable@5.1.4:
|
||||
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4507,6 +4616,9 @@ packages:
|
||||
no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
@@ -5068,6 +5180,10 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
regenerate-unicode-properties@10.2.2:
|
||||
resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5173,6 +5289,9 @@ packages:
|
||||
run-series@1.1.9:
|
||||
resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==}
|
||||
|
||||
rxjs@7.8.2:
|
||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
@@ -5183,6 +5302,128 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sass-embedded-all-unknown@1.97.3:
|
||||
resolution: {integrity: sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==}
|
||||
cpu: ['!arm', '!arm64', '!riscv64', '!x64']
|
||||
|
||||
sass-embedded-android-arm64@1.97.3:
|
||||
resolution: {integrity: sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
sass-embedded-android-arm@1.97.3:
|
||||
resolution: {integrity: sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
sass-embedded-android-riscv64@1.97.3:
|
||||
resolution: {integrity: sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [riscv64]
|
||||
os: [android]
|
||||
|
||||
sass-embedded-android-x64@1.97.3:
|
||||
resolution: {integrity: sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
sass-embedded-darwin-arm64@1.97.3:
|
||||
resolution: {integrity: sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
sass-embedded-darwin-x64@1.97.3:
|
||||
resolution: {integrity: sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
sass-embedded-linux-arm64@1.97.3:
|
||||
resolution: {integrity: sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-arm@1.97.3:
|
||||
resolution: {integrity: sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-musl-arm64@1.97.3:
|
||||
resolution: {integrity: sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-arm@1.97.3:
|
||||
resolution: {integrity: sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-riscv64@1.97.3:
|
||||
resolution: {integrity: sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-x64@1.97.3:
|
||||
resolution: {integrity: sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-riscv64@1.97.3:
|
||||
resolution: {integrity: sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-x64@1.97.3:
|
||||
resolution: {integrity: sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-unknown-all@1.97.3:
|
||||
resolution: {integrity: sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==}
|
||||
os: ['!android', '!darwin', '!linux', '!win32']
|
||||
|
||||
sass-embedded-win32-arm64@1.97.3:
|
||||
resolution: {integrity: sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
sass-embedded-win32-x64@1.97.3:
|
||||
resolution: {integrity: sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
sass-embedded@1.97.3:
|
||||
resolution: {integrity: sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
sass@1.97.3:
|
||||
resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
sax@1.4.3:
|
||||
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
|
||||
|
||||
@@ -5521,6 +5762,14 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
sync-child-process@1.0.2:
|
||||
resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
sync-message-port@1.2.0:
|
||||
resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
synckit@0.11.11:
|
||||
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@@ -5832,6 +6081,9 @@ packages:
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
varint@6.0.0:
|
||||
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -6908,6 +7160,8 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@bufbuild/protobuf@2.11.0': {}
|
||||
|
||||
'@cacheable/memory@2.0.6':
|
||||
dependencies:
|
||||
'@cacheable/utils': 2.3.2
|
||||
@@ -7489,6 +7743,67 @@ snapshots:
|
||||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
is-glob: 4.0.3
|
||||
node-addon-api: 7.1.1
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-x64': 2.5.6
|
||||
'@parcel/watcher-freebsd-x64': 2.5.6
|
||||
'@parcel/watcher-linux-arm-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm-musl': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-musl': 2.5.6
|
||||
'@parcel/watcher-linux-x64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-x64-musl': 2.5.6
|
||||
'@parcel/watcher-win32-arm64': 2.5.6
|
||||
'@parcel/watcher-win32-ia32': 2.5.6
|
||||
'@parcel/watcher-win32-x64': 2.5.6
|
||||
optional: true
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@@ -8082,13 +8397,13 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.49.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@unocss/astro@66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))':
|
||||
'@unocss/astro@66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@unocss/core': 66.5.3
|
||||
'@unocss/reset': 66.5.3
|
||||
'@unocss/vite': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@unocss/vite': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
optionalDependencies:
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
'@unocss/cli@66.5.3':
|
||||
dependencies:
|
||||
@@ -8239,7 +8554,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@unocss/core': 66.5.3
|
||||
|
||||
'@unocss/vite@66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))':
|
||||
'@unocss/vite@66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
'@unocss/config': 66.5.3
|
||||
@@ -8250,7 +8565,7 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
tinyglobby: 0.2.15
|
||||
unplugin-utils: 0.3.1
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
'@uppy/companion-client@2.2.2':
|
||||
dependencies:
|
||||
@@ -8281,7 +8596,7 @@ snapshots:
|
||||
'@uppy/utils': 4.1.3
|
||||
nanoid: 3.3.11
|
||||
|
||||
'@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))':
|
||||
'@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5)
|
||||
@@ -8296,26 +8611,26 @@ snapshots:
|
||||
regenerator-runtime: 0.14.1
|
||||
systemjs: 6.15.1
|
||||
terser: 5.44.1
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))':
|
||||
'@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
|
||||
'@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
|
||||
'@rolldown/pluginutils': 1.0.0-beta.54
|
||||
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
vue: 3.5.22(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue@6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))':
|
||||
'@vitejs/plugin-vue@6.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.22(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.29
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
vue: 3.5.22(typescript@5.9.3)
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
@@ -8326,13 +8641,13 @@ snapshots:
|
||||
chai: 5.3.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))':
|
||||
'@vitest/mocker@3.2.4(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -8961,6 +9276,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
optional: true
|
||||
|
||||
citty@0.1.6:
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
@@ -9009,6 +9329,8 @@ snapshots:
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
colorjs.io@0.5.2: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
@@ -9254,6 +9576,9 @@ snapshots:
|
||||
|
||||
detect-indent@7.0.2: {}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
detect-newline@4.0.1: {}
|
||||
|
||||
dezalgo@1.0.4:
|
||||
@@ -10043,6 +10368,8 @@ snapshots:
|
||||
|
||||
immer@9.0.21: {}
|
||||
|
||||
immutable@5.1.4: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
@@ -10506,7 +10833,7 @@ snapshots:
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mkdist@2.4.1(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3)):
|
||||
mkdist@2.4.1(sass@1.97.3)(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3)):
|
||||
dependencies:
|
||||
autoprefixer: 10.4.21(postcss@8.5.6)
|
||||
citty: 0.1.6
|
||||
@@ -10522,6 +10849,7 @@ snapshots:
|
||||
semver: 7.7.3
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
sass: 1.97.3
|
||||
typescript: 5.9.3
|
||||
vue: 3.5.22(typescript@5.9.3)
|
||||
vue-tsc: 3.1.1(typescript@5.9.3)
|
||||
@@ -10587,6 +10915,9 @@ snapshots:
|
||||
lower-case: 2.0.2
|
||||
tslib: 2.8.1
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-html-parser@5.4.2:
|
||||
@@ -11172,6 +11503,9 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
readdirp@4.1.2:
|
||||
optional: true
|
||||
|
||||
regenerate-unicode-properties@10.2.2:
|
||||
dependencies:
|
||||
regenerate: 1.4.2
|
||||
@@ -11290,6 +11624,10 @@ snapshots:
|
||||
|
||||
run-series@1.1.9: {}
|
||||
|
||||
rxjs@7.8.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-regex-test@1.1.0:
|
||||
@@ -11300,6 +11638,102 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
sass-embedded-all-unknown@1.97.3:
|
||||
dependencies:
|
||||
sass: 1.97.3
|
||||
optional: true
|
||||
|
||||
sass-embedded-android-arm64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-android-arm@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-android-riscv64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-android-x64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-darwin-arm64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-darwin-x64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-arm64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-arm@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-musl-arm64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-musl-arm@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-musl-riscv64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-musl-x64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-riscv64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-linux-x64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-unknown-all@1.97.3:
|
||||
dependencies:
|
||||
sass: 1.97.3
|
||||
optional: true
|
||||
|
||||
sass-embedded-win32-arm64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded-win32-x64@1.97.3:
|
||||
optional: true
|
||||
|
||||
sass-embedded@1.97.3:
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 2.11.0
|
||||
colorjs.io: 0.5.2
|
||||
immutable: 5.1.4
|
||||
rxjs: 7.8.2
|
||||
supports-color: 8.1.1
|
||||
sync-child-process: 1.0.2
|
||||
varint: 6.0.0
|
||||
optionalDependencies:
|
||||
sass-embedded-all-unknown: 1.97.3
|
||||
sass-embedded-android-arm: 1.97.3
|
||||
sass-embedded-android-arm64: 1.97.3
|
||||
sass-embedded-android-riscv64: 1.97.3
|
||||
sass-embedded-android-x64: 1.97.3
|
||||
sass-embedded-darwin-arm64: 1.97.3
|
||||
sass-embedded-darwin-x64: 1.97.3
|
||||
sass-embedded-linux-arm: 1.97.3
|
||||
sass-embedded-linux-arm64: 1.97.3
|
||||
sass-embedded-linux-musl-arm: 1.97.3
|
||||
sass-embedded-linux-musl-arm64: 1.97.3
|
||||
sass-embedded-linux-musl-riscv64: 1.97.3
|
||||
sass-embedded-linux-musl-x64: 1.97.3
|
||||
sass-embedded-linux-riscv64: 1.97.3
|
||||
sass-embedded-linux-x64: 1.97.3
|
||||
sass-embedded-unknown-all: 1.97.3
|
||||
sass-embedded-win32-arm64: 1.97.3
|
||||
sass-embedded-win32-x64: 1.97.3
|
||||
|
||||
sass@1.97.3:
|
||||
dependencies:
|
||||
chokidar: 4.0.3
|
||||
immutable: 5.1.4
|
||||
source-map-js: 1.2.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.6
|
||||
optional: true
|
||||
|
||||
sax@1.4.3: {}
|
||||
|
||||
scroll-into-view-if-needed@2.2.31:
|
||||
@@ -11655,6 +12089,12 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
sax: 1.4.3
|
||||
|
||||
sync-child-process@1.0.2:
|
||||
dependencies:
|
||||
sync-message-port: 1.2.0
|
||||
|
||||
sync-message-port@1.2.0: {}
|
||||
|
||||
synckit@0.11.11:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.2.9
|
||||
@@ -11843,7 +12283,7 @@ snapshots:
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
unbuild@3.6.1(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3)):
|
||||
unbuild@3.6.1(sass@1.97.3)(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@rollup/plugin-alias': 5.1.1(rollup@4.53.3)
|
||||
'@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
|
||||
@@ -11859,7 +12299,7 @@ snapshots:
|
||||
hookable: 5.5.3
|
||||
jiti: 2.6.1
|
||||
magic-string: 0.30.21
|
||||
mkdist: 2.4.1(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3))
|
||||
mkdist: 2.4.1(sass@1.97.3)(typescript@5.9.3)(vue-tsc@3.1.1(typescript@5.9.3))(vue@3.5.22(typescript@5.9.3))
|
||||
mlly: 1.8.0
|
||||
pathe: 2.0.3
|
||||
pkg-types: 2.3.0
|
||||
@@ -11911,9 +12351,9 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unocss@66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
unocss@66.5.3(postcss@8.5.6)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@unocss/astro': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@unocss/astro': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@unocss/cli': 66.5.3
|
||||
'@unocss/core': 66.5.3
|
||||
'@unocss/postcss': 66.5.3(postcss@8.5.6)
|
||||
@@ -11931,9 +12371,9 @@ snapshots:
|
||||
'@unocss/transformer-compile-class': 66.5.3
|
||||
'@unocss/transformer-directives': 66.5.3
|
||||
'@unocss/transformer-variant-group': 66.5.3
|
||||
'@unocss/vite': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@unocss/vite': 66.5.3(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
optionalDependencies:
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- postcss
|
||||
- supports-color
|
||||
@@ -11969,19 +12409,21 @@ snapshots:
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
||||
varint@6.0.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vditor@3.11.2:
|
||||
dependencies:
|
||||
diff-match-patch: 1.0.5
|
||||
|
||||
vite-node@3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2):
|
||||
vite-node@3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -11996,16 +12438,16 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-plugin-compression@0.5.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-compression@0.5.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
fs-extra: 10.1.0
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-dts@4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-dts@4.5.4(@types/node@24.7.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.55.2(@types/node@24.7.2)
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.53.3)
|
||||
@@ -12018,13 +12460,13 @@ snapshots:
|
||||
magic-string: 0.30.21
|
||||
typescript: 5.9.3
|
||||
optionalDependencies:
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-plugin-html@3.2.2(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-html@3.2.2(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
colorette: 2.0.20
|
||||
@@ -12038,14 +12480,14 @@ snapshots:
|
||||
html-minifier-terser: 6.1.0
|
||||
node-html-parser: 5.4.2
|
||||
pathe: 0.2.0
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
vite-plugin-mkcert@1.17.9(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-mkcert@1.17.9(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
axios: 1.12.2(debug@4.4.3)
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
picocolors: 1.1.1
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -12053,7 +12495,7 @@ snapshots:
|
||||
dependencies:
|
||||
monaco-editor: 0.54.0
|
||||
|
||||
vite-plugin-theme-vite3@2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-theme-vite3@2.0.1(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@types/node': 24.1.0
|
||||
'@types/tinycolor2': 1.4.6
|
||||
@@ -12063,17 +12505,17 @@ snapshots:
|
||||
esbuild-plugin-alias: 0.2.1
|
||||
picocolors: 1.1.1
|
||||
tinycolor2: 1.6.0
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-setup-extend@0.4.0(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)):
|
||||
vite-plugin-vue-setup-extend@0.4.0(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
magic-string: 0.25.9
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
|
||||
vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2):
|
||||
vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -12086,14 +12528,16 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
less: 4.4.2
|
||||
sass: 1.97.3
|
||||
sass-embedded: 1.97.3
|
||||
terser: 5.44.1
|
||||
yaml: 2.8.2
|
||||
|
||||
vitest@3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2):
|
||||
vitest@3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@vitest/mocker': 3.2.4(vite@7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -12111,8 +12555,8 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite: 7.1.9(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@24.7.2)(jiti@2.6.1)(less@4.4.2)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.44.1)(yaml@2.8.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.7.2
|
||||
|
||||
Reference in New Issue
Block a user