项目需求、任务以及模块精简

This commit is contained in:
2026-04-06 19:53:22 +08:00
parent 477a08da9f
commit 743937a176
439 changed files with 138 additions and 137950 deletions

View File

@@ -334,6 +334,14 @@
white-space: nowrap;
color: rgb(71 85 105);
}
.el-loading-mask {
background-color: rgb(255 255 255 / 72%);
}
.el-loading-spinner .el-loading-text {
color: rgb(71 85 105);
}
}
}
}

View File

@@ -358,6 +358,14 @@
.cell {
line-height: 20px;
}
.el-loading-mask {
background-color: rgb(255 255 255 / 72%);
}
.el-loading-spinner .el-loading-text {
color: rgb(71 85 105);
}
}
}

View File

@@ -376,6 +376,14 @@
padding: 0;
font-weight: 500;
}
.el-loading-mask {
background-color: rgb(255 255 255 / 72%);
}
.el-loading-spinner .el-loading-text {
color: rgb(71 85 105);
}
}
}
}

View File

@@ -39,6 +39,7 @@
const statusDict = ref<DictData[]>([]);
const STATUS_COLORS = ['#3B82F6', '#10B981', '#F97316', '#EC4899', '#8B5CF6', '#06B6D4'];
const AMOUNT_LINE_COLOR = '#F59E0B';
let pieChartInstance: echarts.ECharts | null = null;
let barChartInstance: echarts.ECharts | null = null;
let resizeObserver: ResizeObserver | null = null;
@@ -48,35 +49,60 @@
router.push('/biz/myProjectContract/list');
}
function getAmountValue(amount?: number | string) {
const value = Number(amount || 0);
return Number.isFinite(value) ? value : 0;
}
function formatAmountYuan(amount?: number) {
return `${Math.round(getAmountValue(amount)).toLocaleString('zh-CN')}`;
}
function formatAmountWan(amount?: number, digits = 1) {
const wan = getAmountValue(amount) / 10000;
return Number(wan.toFixed(digits));
}
const pieChartData = computed(() => {
if (!statusDict.value.length) {
const fallbackMap = new Map<string, number>();
const fallbackMap = new Map<string, { count: number; amount: number }>();
sourceData.value.forEach((item) => {
const label = item.contractStatus || '未设置';
fallbackMap.set(label, (fallbackMap.get(label) || 0) + 1);
const current = fallbackMap.get(label) || { count: 0, amount: 0 };
current.count += 1;
current.amount += getAmountValue(item.contractAmount);
fallbackMap.set(label, current);
});
return Array.from(fallbackMap.entries()).map(([name, value], index) => ({
name,
value,
value: value.count,
amount: value.amount,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}));
}
const countMap = new Map<string, number>();
const amountMap = new Map<string, number>();
sourceData.value.forEach((item) => {
const key = item.contractStatus || 'unknown';
countMap.set(key, (countMap.get(key) || 0) + 1);
amountMap.set(key, (amountMap.get(key) || 0) + getAmountValue(item.contractAmount));
});
return statusDict.value
.map((item, index) => ({
name: item.dictLabelRaw,
value: countMap.get(item.dictValue || '') || 0,
amount: amountMap.get(item.dictValue || '') || 0,
itemStyle: { color: STATUS_COLORS[index % STATUS_COLORS.length] },
}))
.filter((item) => item.value > 0);
});
const totalContractAmount = computed(() => {
return sourceData.value.reduce((sum, item) => sum + getAmountValue(item.contractAmount), 0);
});
const barChartData = computed(() => {
const statusItems = statusDict.value.length
? statusDict.value.map((item, index) => ({
@@ -88,6 +114,7 @@
const monthStatusMap = new Map<string, Map<string, number>>();
const monthOrderMap = new Map<string, number>();
const monthAmountMap = new Map<string, number>();
sourceData.value.forEach((item) => {
if (!item.signDate) return;
@@ -101,6 +128,7 @@
}
const monthMap = monthStatusMap.get(monthLabel)!;
monthMap.set(statusKey, (monthMap.get(statusKey) || 0) + 1);
monthAmountMap.set(monthLabel, (monthAmountMap.get(monthLabel) || 0) + getAmountValue(item.contractAmount));
});
const months = Array.from(monthStatusMap.keys()).sort((a, b) => {
@@ -129,6 +157,7 @@
return {
months,
totals: totalByMonth,
amounts: months.map((month) => monthAmountMap.get(month) || 0),
statusItems: statusItems.length ? statusItems : fallbackSeries,
monthStatusMap,
};
@@ -240,9 +269,10 @@
textStyle: {
color: isDark ? '#e2e8f0' : '#334155',
},
formatter: ({ name, value, percent }) => {
formatter: ({ name, value, percent, data }) => {
const amount = (data as { amount?: number })?.amount || 0;
if (!total) return `${name}<br/>数量0`;
return `${name}<br/>数量:${value}<br/>占比:${percent}%`;
return `${name}<br/>数量:${value}项<br/>金额:${formatAmountYuan(amount)}<br/>占比:${percent}%`;
},
},
legend: {
@@ -281,6 +311,17 @@
fontWeight: 700,
},
},
{
type: 'text',
left: 'center',
top: '51%',
style: {
text: `金额 ${formatAmountWan(totalContractAmount.value, 2)}万元`,
fill: isDark ? '#94a3b8' : '#64748b',
fontSize: 12,
fontWeight: 500,
},
},
]
: [],
});
@@ -293,10 +334,10 @@
}
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const { months, totals, statusItems, monthStatusMap } = barChartData.value;
const { months, totals, amounts, statusItems, monthStatusMap } = barChartData.value;
const hasData = totals.some((item) => item > 0);
const categories = months.length ? months : [];
const series = statusItems.length
const series: echarts.SeriesOption[] = statusItems.length
? statusItems.map((status) => ({
name: status.label,
type: 'bar',
@@ -353,6 +394,30 @@
data: categories.map(() => 0),
z: 10,
});
series.push({
name: '合同金额',
type: 'line',
yAxisIndex: 1,
smooth: true,
symbol: 'circle',
symbolSize: 6,
itemStyle: {
color: AMOUNT_LINE_COLOR,
},
lineStyle: {
color: AMOUNT_LINE_COLOR,
width: 2,
},
emphasis: {
focus: 'series',
},
tooltip: {
show: false,
},
data: amounts.map((amount) => formatAmountWan(amount, 2)),
z: 11,
});
}
barChartInstance.setOption({
@@ -377,9 +442,15 @@
formatter: (params) => {
const dataIndex = params[0]?.dataIndex || 0;
if (!hasData) return '';
const amountItem = params.find((item) => item.seriesName === '合同金额');
const lines = params
.filter((item) => item.seriesName !== '总数' && Number(item.value) > 0)
.filter((item) => item.seriesName !== '总数' && item.seriesName !== '合同金额' && Number(item.value) > 0)
.map((item) => `${item.marker}${item.seriesName}${item.value}`);
if (amounts[dataIndex] > 0) {
lines.push(
`${amountItem?.marker || `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${AMOUNT_LINE_COLOR};"></span>`}合同金额:${formatAmountYuan(amounts[dataIndex])}`,
);
}
lines.push(`总数:${totals[dataIndex]}`);
return `${categories[dataIndex]}<br/>${lines.join('<br/>')}`;
},
@@ -388,7 +459,7 @@
top: 6,
left: 'center',
itemGap: 16,
data: statusItems.map((item) => item.label),
data: [...statusItems.map((item) => item.label), ...(hasData ? ['合同金额'] : [])],
textStyle: {
color: isDark ? '#e2e8f0' : '#475569',
},
@@ -412,23 +483,42 @@
rotate: 30,
},
},
yAxis: {
type: 'value',
minInterval: 1,
name: '数量',
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',
padding: [0, 0, 2, 0],
},
splitLine: {
lineStyle: {
color: isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)',
yAxis: [
{
type: 'value',
interval: 1,
minInterval: 1,
name: '数量',
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',
padding: [0, 0, 2, 0],
},
splitLine: {
lineStyle: {
color: isDark ? 'rgba(71, 85, 105, 0.35)' : 'rgba(203, 213, 225, 0.55)',
},
},
axisLabel: {
color: isDark ? '#94a3b8' : '#64748b',
},
},
axisLabel: {
color: isDark ? '#94a3b8' : '#64748b',
{
type: 'value',
name: '金额(万元)',
min: 0,
splitLine: {
show: false,
},
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',
padding: [0, 0, 2, 0],
},
axisLabel: {
color: isDark ? '#94a3b8' : '#64748b',
formatter: (value) => `${value}`,
},
},
},
],
series,
});
}

View File

@@ -414,6 +414,7 @@
},
yAxis: {
type: 'value',
interval: 1,
name: '数量',
nameTextStyle: {
color: isDark ? '#94a3b8' : '#64748b',