feat: 完成空间管理模块
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user