leaser_front/src/views/business/space/house-atlas/components/cross-year-timeline.vue
2026-01-23 01:14:47 +08:00

272 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
class="cross-year-timeline-container"
:style="{
position: 'absolute',
left: '0',
width: `${yearRange.length * 200}px`,
height: '40px',
zIndex: 2,
}"
>
<!-- 年份分隔线 -->
<template v-for="(year, index) in yearRange" :key="`divider-${year}`">
<div
v-if="index < yearRange.length - 1"
class="year-divider"
:style="{
position: 'absolute',
left: `${(index + 1) * 200}px`,
top: '0',
bottom: '0',
width: '1px',
backgroundColor: '#e5e6eb',
zIndex: 1,
}"
></div>
</template>
<!-- 连续时间线(跨列显示) -->
<div
class="timeline-bar"
:style="{
position: 'absolute',
left: '0',
right: '0',
top: '0',
bottom: '0',
}"
>
<!-- 空置状态 -->
<div
v-for="(segment, index) in allSegments"
:key="`segment-${index}`"
class="timeline-segment"
:class="segment.type"
:style="{
position: 'absolute',
left: `${segment.startPercentage}%`,
width: `${segment.percentage}%`,
height: '100%',
backgroundColor:
segment.type === 'vacant'
? '#f2f3f5'
: segment.type === 'lease'
? '#00b42a'
: 'transparent',
backgroundImage:
segment.type === 'unarranged'
? 'repeating-linear-gradient(45deg, #f2f3f5, #f2f3f5 4px, #d9d9d9 4px, #d9d9d9 8px)'
: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
}"
>
<span
v-if="segment.label && segment.percentage > 5"
class="segment-label"
:style="{
fontSize: '12px',
color: segment.type === 'lease' ? '#fff' : '#86909c',
fontWeight: segment.type === 'lease' ? '500' : 'normal',
padding: '0 4px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}"
>
{{ segment.label }}
</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
interface YearData {
year: string;
allDays: number;
allData: Array<{
companyName: string;
days: number;
type: number; // 1: 在租, 其他可能是空置
}>;
}
interface Props {
yearsData?: YearData[];
yearRange: number[];
}
const props = withDefaults(defineProps<Props>(), {
yearsData: () => [],
yearRange: () => [],
});
// 计算总天数(5年)
const totalDays = computed(() => {
// 如果数据为空,使用默认的5年天数
if (!props.yearsData || props.yearsData.length === 0) {
return 365 * props.yearRange.length;
}
// 确保所有年份都有数据,缺失的年份使用365天
let total = 0;
props.yearRange.forEach((year) => {
const yearData = props.yearsData?.find((d) => d.year === String(year));
total += yearData?.allDays || 365;
});
return total;
});
// 合并所有年份的数据,创建连续的时间段
interface TimelineSegment {
type: 'vacant' | 'lease' | 'unarranged';
percentage: number;
startPercentage: number; // 在整个时间线中的起始位置百分比
days: number;
label?: string;
startDay: number; // 在整个5年时间线中的起始天数
endDay: number; // 在整个5年时间线中的结束天数
}
const allSegments = computed(() => {
if (!props.yearsData || props.yearsData.length === 0) {
// 如果没有数据,全部显示为未安排
return [
{
type: 'unarranged' as const,
percentage: 100,
startPercentage: 0,
days: totalDays.value,
startDay: 0,
endDay: totalDays.value,
},
];
}
const segments: TimelineSegment[] = [];
let globalOffset = 0; // 在整个5年时间线中的累计天数
// 按年份顺序处理数据,确保年份顺序正确
const sortedYearsData = [...props.yearsData].sort((a, b) => {
return Number(a.year) - Number(b.year);
});
// 创建年份数据映射
const yearDataMap = new Map<string, YearData>();
sortedYearsData.forEach((data) => {
yearDataMap.set(data.year, data);
});
// 按年份范围顺序处理
props.yearRange.forEach((year) => {
const yearStr = String(year);
const yearData = yearDataMap.get(yearStr);
const yearDays = yearData?.allDays || 365;
const yearStartDay = globalOffset;
const yearEndDay = globalOffset + yearDays;
if (!yearData || !yearData.allData || yearData.allData.length === 0) {
// 如果该年份没有数据,全部显示为未安排
segments.push({
type: 'unarranged',
percentage: (yearDays / totalDays.value) * 100,
startPercentage: (yearStartDay / totalDays.value) * 100,
days: yearDays,
startDay: yearStartDay,
endDay: yearEndDay,
});
globalOffset = yearEndDay;
return;
}
// 收集该年份的所有时间段,按顺序排列
const yearSegments: Array<{
type: 'vacant' | 'lease';
days: number;
label: string;
}> = [];
yearData.allData.forEach((item) => {
if (item.type === 1 && item.days > 0) {
// 在租
yearSegments.push({
type: 'lease',
days: item.days,
label: item.companyName || '',
});
} else if (item.type !== 1 && item.days > 0) {
// 空置
yearSegments.push({
type: 'vacant',
days: item.days,
label: `空置${item.days}天`,
});
}
});
// 计算已安排的天数
const arrangedDays = yearSegments.reduce((sum, seg) => sum + seg.days, 0);
const unarrangedDays = yearDays - arrangedDays;
// 添加已安排的时间段(连续显示)
let offset = 0;
yearSegments.forEach((seg) => {
const startDay = yearStartDay + offset;
const endDay = startDay + seg.days;
segments.push({
type: seg.type,
percentage: (seg.days / totalDays.value) * 100,
startPercentage: (startDay / totalDays.value) * 100,
days: seg.days,
label: seg.label,
startDay,
endDay,
});
offset += seg.days;
});
// 添加未安排的时间段
if (unarrangedDays > 0) {
const startDay = yearStartDay + offset;
segments.push({
type: 'unarranged',
percentage: (unarrangedDays / totalDays.value) * 100,
startPercentage: (startDay / totalDays.value) * 100,
days: unarrangedDays,
startDay,
endDay: yearEndDay,
});
}
globalOffset = yearEndDay;
});
return segments;
});
</script>
<style scoped>
.cross-year-timeline-container {
position: relative;
overflow: visible;
}
.timeline-bar {
border-radius: 2px;
overflow: visible;
}
.timeline-segment {
transition: all 0.3s;
min-width: 0;
}
.segment-label {
user-select: none;
}
</style>