Compare commits
No commits in common. "66d4a1bbfe7b4204d0dc9c792dd58dfab209d4e9" and "4ea535b82f0f0db332eed463869a6d9293e7c112" have entirely different histories.
66d4a1bbfe
...
4ea535b82f
|
@ -1,51 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<GiPageLayout>
|
<GiPageLayout>
|
||||||
<div class="gantt-container">
|
<div class="gantt-container">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2 class="page-title">人力甘特图页面</h2>
|
<h2 class="page-title">人力甘特图页面</h2>
|
||||||
|
|
||||||
<!-- 时间刻度切换 -->
|
|
||||||
<div class="scale-switch">
|
|
||||||
<button
|
|
||||||
v-for="scale in timeScales"
|
|
||||||
:key="scale.value"
|
|
||||||
:class="['scale-btn', { active: scale.value === currentScale }]"
|
|
||||||
@click="setScale(scale.value)"
|
|
||||||
>
|
|
||||||
{{ scale.label }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 左右翻页按钮 -->
|
|
||||||
<button class="nav-btn" @click="shiftRange(-1)">←</button>
|
|
||||||
<button class="nav-btn" @click="shiftRange(1)">→</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 人员列表 -->
|
<!-- 人员列表区域 -->
|
||||||
<div class="person-container">
|
<div class="person-container">
|
||||||
<div
|
<div v-for="(person, index) in personList" :key="person.id" class="person-gantt">
|
||||||
v-for="(person, index) in personList"
|
<!-- 人员标题栏 -->
|
||||||
:key="person.id"
|
|
||||||
class="person-gantt"
|
|
||||||
>
|
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="person-header" @click="togglePerson(index)">
|
<div class="person-header" @click="togglePerson(index)">
|
||||||
<div class="name-container">
|
<div class="name-container">
|
||||||
<span class="avatar">{{ person.name.charAt(0) }}</span>
|
<span class="avatar">{{ person.name.charAt(0) }}</span>
|
||||||
<span>{{ person.name }}</span>
|
<span>{{ person.name }}</span>
|
||||||
<span class="task-count">({{ person.tasks.length }}个任务)</span>
|
<span class="task-count">({{ person.tasks.length }}个项目)</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="expand-button">
|
<button class="expand-button">
|
||||||
<i :class="person.expanded ? 'menu-fold-icon' : 'menu-unfold-icon'"></i>
|
<i :class="person.expanded ? 'collapse-icon' : 'expand-icon'"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 甘特图 -->
|
<!-- 甘特图容器 - 根据展开状态控制显示 -->
|
||||||
<div v-show="person.expanded" class="chart-container">
|
<div v-show="person.expanded" class="chart-container">
|
||||||
<div
|
<div :ref="el => chartRefs[index] = el" class="progress-chart"></div>
|
||||||
:ref="el => chartRefs[index] = el"
|
|
||||||
class="progress-chart"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,207 +30,205 @@
|
||||||
</GiPageLayout>
|
</GiPageLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang='ts'>
|
||||||
import * as echarts from "echarts";
|
import * as echarts from 'echarts'
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
|
||||||
const timeScales = [
|
|
||||||
{ label: "周", value: "week", range: 14 },
|
|
||||||
{ label: "月", value: "month", range: 30 },
|
|
||||||
{ label: "季", value: "quarter", range: 90 },
|
|
||||||
{ label: "年", value: "year", range: 365 }
|
|
||||||
];
|
|
||||||
const currentScale = ref("month");
|
|
||||||
const currentStartDate = ref(new Date()); // 当前显示范围起始时间
|
|
||||||
|
|
||||||
|
// 人员数据
|
||||||
const personList = ref([
|
const personList = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "张三",
|
name: '张三',
|
||||||
expanded: true,
|
expanded: true,
|
||||||
tasks: [
|
tasks: [
|
||||||
{ name: "项目一", startDate: "2025-08-01", days: 5, expectedEndDate: "2025-08-07", color: "#5470C6" },
|
{ name: '项目一', startDate: '2025-08-01', days: 5, color: '#5470C6' },
|
||||||
{ name: "项目二", startDate: "2025-08-10", days: 7, expectedEndDate: "2025-08-18", color: "#91CC75" },
|
{ name: '项目二', startDate: '2025-08-10', days: 7, color: '#91CC75' },
|
||||||
{ name: "项目三", startDate: "2025-08-20", days: 4, expectedEndDate: "2025-08-25", color: "#FAC858" }
|
{ name: '项目三', startDate: '2025-08-20', days: 4, color: '#FAC858' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "李四",
|
name: '李四',
|
||||||
expanded: true,
|
expanded: true,
|
||||||
tasks: [
|
tasks: [
|
||||||
{ name: "产品设计", startDate: "2025-08-05", days: 8, expectedEndDate: "2025-08-15", color: "#EE6666" },
|
{ name: '产品设计', startDate: '2025-08-05', days: 8, color: '#EE6666' },
|
||||||
{ name: "技术评审", startDate: "2025-08-15", days: 3, expectedEndDate: "2025-08-19", color: "#73C0DE" },
|
{ name: '技术评审', startDate: '2025-08-15', days: 3, color: '#73C0DE' },
|
||||||
{ name: "系统测试", startDate: "2025-08-18", days: 6, expectedEndDate: "2025-08-27", color: "#3BA272" }
|
{ name: '系统测试', startDate: '2025-08-18', days: 6, color: '#3BA272' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '王五',
|
||||||
|
expanded: true,
|
||||||
|
tasks: [
|
||||||
|
{ name: '需求分析', startDate: '2025-08-02', days: 4, color: '#FC8452' },
|
||||||
|
{ name: '前端开发', startDate: '2025-08-08', days: 7, color: '#9A60B4' },
|
||||||
|
{ name: '后端对接', startDate: '2025-08-17', days: 5, color: '#EA7CCC' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '赵六',
|
||||||
|
expanded: false,
|
||||||
|
tasks: [
|
||||||
|
{ name: '文档编写', startDate: '2025-08-03', days: 6, color: '#5470C6' },
|
||||||
|
{ name: '用户培训', startDate: '2025-08-12', days: 4, color: '#91CC75' },
|
||||||
|
{ name: '上线支持', startDate: '2025-08-22', days: 7, color: '#FAC858' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
])
|
||||||
|
|
||||||
|
// 图表引用数组
|
||||||
const chartRefs = ref<HTMLElement[]>([]);
|
const chartRefs = ref<HTMLElement[]>([]);
|
||||||
|
// 图表实例数组
|
||||||
const chartInstances = ref<echarts.ECharts[]>([]);
|
const chartInstances = ref<echarts.ECharts[]>([]);
|
||||||
|
|
||||||
const setScale = (scale: string) => {
|
// 辅助函数:格式化日期为YYYY-MM-DD
|
||||||
currentScale.value = scale;
|
const formatDate = (date: Date): string => {
|
||||||
currentStartDate.value = new Date();
|
const year = date.getFullYear()
|
||||||
renderAllCharts();
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
};
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
// 左右翻页
|
}
|
||||||
const shiftRange = (direction: number) => {
|
|
||||||
const rangeDays = timeScales.find(s => s.value === currentScale.value)?.range || 30;
|
|
||||||
const newDate = new Date(currentStartDate.value);
|
|
||||||
newDate.setDate(newDate.getDate() + direction * rangeDays);
|
|
||||||
currentStartDate.value = newDate;
|
|
||||||
renderAllCharts();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 切换人员展开/收起状态
|
||||||
const togglePerson = (index: number) => {
|
const togglePerson = (index: number) => {
|
||||||
personList.value[index].expanded = !personList.value[index].expanded;
|
personList.value[index].expanded = !personList.value[index].expanded;
|
||||||
|
// 如果展开,重新渲染图表
|
||||||
if (personList.value[index].expanded) {
|
if (personList.value[index].expanded) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initChart(index);
|
if (chartRefs.value[index] && chartInstances.value[index]) {
|
||||||
|
chartInstances.value[index].resize();
|
||||||
|
} else {
|
||||||
|
initChart(index);
|
||||||
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 初始化特定人员的甘特图
|
||||||
const initChart = (personIndex: number) => {
|
const initChart = (personIndex: number) => {
|
||||||
const container = chartRefs.value[personIndex];
|
const container = chartRefs.value[personIndex];
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// 如果实例已存在,先销毁
|
||||||
if (chartInstances.value[personIndex]) {
|
if (chartInstances.value[personIndex]) {
|
||||||
chartInstances.value[personIndex].dispose();
|
chartInstances.value[personIndex].dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
const person = personList.value[personIndex];
|
const person = personList.value[personIndex];
|
||||||
const sortedTasks = [...person.tasks].sort(
|
const tasks = person.tasks;
|
||||||
(a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
|
||||||
);
|
// 计算日期 - 以当前日期为基准
|
||||||
|
const today = new Date(2025, 7, 14);
|
||||||
const rangeDays = timeScales.find(s => s.value === currentScale.value)?.range || 30;
|
|
||||||
const startTime = currentStartDate.value.getTime();
|
// 准备数据
|
||||||
const minTime = startTime;
|
const projectNames: string[] = [];
|
||||||
const maxTime = startTime + rangeDays * 86400000;
|
const dataItems: any[] = [];
|
||||||
|
const colors: string[] = [];
|
||||||
const projectNames = sortedTasks.map(t => t.name);
|
|
||||||
|
tasks.forEach((task) => {
|
||||||
// 预期任务条数据
|
const startDate = new Date(task.startDate);
|
||||||
const expectedData = sortedTasks.map((task, idx) => {
|
const endDate = new Date(startDate);
|
||||||
let start = new Date(task.startDate).getTime();
|
endDate.setDate(startDate.getDate() + task.days);
|
||||||
let end = new Date(task.expectedEndDate).getTime();
|
|
||||||
|
projectNames.push(task.name);
|
||||||
if (start < minTime) start = minTime;
|
colors.push(task.color);
|
||||||
if (end > maxTime) end = maxTime;
|
|
||||||
|
dataItems.push({
|
||||||
return {
|
|
||||||
name: task.name + " (预期)",
|
|
||||||
value: [start, end, (end - start) / 86400000, idx, task.color]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 实际任务条数据
|
|
||||||
const actualData = sortedTasks.map((task, idx) => {
|
|
||||||
let start = new Date(task.startDate).getTime();
|
|
||||||
let end = start + task.days * 86400000;
|
|
||||||
|
|
||||||
if (start < minTime) start = minTime;
|
|
||||||
if (end > maxTime) end = maxTime;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: task.name,
|
name: task.name,
|
||||||
value: [start, end, (end - start) / 86400000, idx, task.color]
|
value: [
|
||||||
};
|
formatDate(startDate),
|
||||||
|
formatDate(endDate),
|
||||||
|
task.days
|
||||||
|
],
|
||||||
|
itemStyle: {
|
||||||
|
color: task.color
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化图表实例
|
||||||
|
const chart = echarts.init(container);
|
||||||
|
|
||||||
|
// 图表配置
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
formatter: (params: any) => `
|
trigger: 'item',
|
||||||
<strong>${params.data.name}</strong><br>
|
formatter: (params: any) => {
|
||||||
开始: ${echarts.time.format(params.data.value[0], "{yyyy}-{MM}-{dd}", false)}<br>
|
const task = params.data;
|
||||||
结束: ${echarts.time.format(params.data.value[1], "{yyyy}-{MM}-{dd}", false)}<br>
|
return `
|
||||||
耗时: ${params.data.value[2]}天
|
<div class="task-tooltip">
|
||||||
`
|
<strong>${task.name}</strong><br>
|
||||||
|
开始: ${params.value[0]}<br>
|
||||||
|
结束: ${params.value[1]}<br>
|
||||||
|
耗时: ${task.value[2]}天
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
},
|
},
|
||||||
grid: { left: 80, right: 30, top: 20, bottom: 20 },
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: "time",
|
type: 'time',
|
||||||
min: minTime,
|
min: formatDate(new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)),
|
||||||
max: maxTime,
|
max: formatDate(new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000)),
|
||||||
splitNumber: 10,
|
axisLabel: {
|
||||||
splitLine: { show: true, lineStyle: { type: "solid", opacity: 0.3 } }
|
formatter: function (value: number) {
|
||||||
|
return echarts.time.format(value, '{MM}/{dd}', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
opacity: 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: "category",
|
type: 'category',
|
||||||
data: projectNames,
|
data: projectNames,
|
||||||
axisTick: { show: false }
|
axisLine: {
|
||||||
},
|
show: true
|
||||||
series: [
|
|
||||||
// 预期时间条
|
|
||||||
{
|
|
||||||
type: "custom",
|
|
||||||
renderItem: (params: any, api: any) => {
|
|
||||||
const categoryIndex = api.value(3);
|
|
||||||
const startCoord = api.coord([api.value(0), categoryIndex]);
|
|
||||||
const endCoord = api.coord([api.value(1), categoryIndex]);
|
|
||||||
const barHeight = 20;
|
|
||||||
return {
|
|
||||||
type: "rect",
|
|
||||||
shape: {
|
|
||||||
x: startCoord[0],
|
|
||||||
y: startCoord[1] - barHeight / 2,
|
|
||||||
width: Math.max(0, endCoord[0] - startCoord[0]),
|
|
||||||
height: barHeight
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fill: api.value(4),
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
encode: { x: [0, 1], y: 3 },
|
|
||||||
data: expectedData,
|
|
||||||
z: 1
|
|
||||||
},
|
},
|
||||||
// 实际任务条
|
axisTick: {
|
||||||
{
|
show: false
|
||||||
type: "custom",
|
},
|
||||||
renderItem: (params: any, api: any) => {
|
axisLabel: {
|
||||||
const categoryIndex = api.value(3);
|
margin: 16
|
||||||
const startCoord = api.coord([api.value(0), categoryIndex]);
|
|
||||||
const endCoord = api.coord([api.value(1), categoryIndex]);
|
|
||||||
const barHeight = 12;
|
|
||||||
return {
|
|
||||||
type: "rect",
|
|
||||||
shape: {
|
|
||||||
x: startCoord[0],
|
|
||||||
y: startCoord[1] - barHeight / 2,
|
|
||||||
width: Math.max(0, endCoord[0] - startCoord[0]),
|
|
||||||
height: barHeight
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fill: api.value(4)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
encode: { x: [0, 1], y: 3 },
|
|
||||||
data: actualData,
|
|
||||||
z: 2
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
dataZoom: [{
|
||||||
|
type: 'inside',
|
||||||
|
start: 20,
|
||||||
|
end: 100
|
||||||
|
}],
|
||||||
|
series: [{
|
||||||
|
name: '项目进度',
|
||||||
|
type: 'bar',
|
||||||
|
data: dataItems,
|
||||||
|
barCategoryGap: '40%',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'inside',
|
||||||
|
formatter: '{c[2]}天'
|
||||||
|
},
|
||||||
|
barWidth: '60%'
|
||||||
|
}],
|
||||||
|
animation: true,
|
||||||
|
animationDuration: 800
|
||||||
};
|
};
|
||||||
|
|
||||||
const chart = echarts.init(container);
|
|
||||||
chart.setOption(option);
|
chart.setOption(option);
|
||||||
chartInstances.value[personIndex] = chart;
|
chartInstances.value[personIndex] = chart;
|
||||||
};
|
}
|
||||||
|
|
||||||
const renderAllCharts = () => {
|
|
||||||
personList.value.forEach((_, index) => {
|
|
||||||
if (personList.value[index].expanded) {
|
|
||||||
initChart(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 窗口大小改变时重绘图表
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
chartInstances.value.forEach((chart, index) => {
|
chartInstances.value.forEach((chart, index) => {
|
||||||
if (chart && personList.value[index].expanded) {
|
if (chart && personList.value[index].expanded) {
|
||||||
|
@ -262,98 +237,160 @@ const handleResize = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 组件挂载时初始化所有展开人员的图表
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
renderAllCharts();
|
personList.value.forEach((_, index) => {
|
||||||
|
if (personList.value[index].expanded) {
|
||||||
|
initChart(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
chartInstances.value.forEach(chart => chart.dispose());
|
chartInstances.value.forEach(chart => {
|
||||||
|
if (chart) {
|
||||||
|
chart.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang='scss' scoped>
|
||||||
.gantt-container {
|
.gantt-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
.page-title {
|
||||||
align-items: center;
|
margin: 0;
|
||||||
}
|
font-size: 1.5rem;
|
||||||
.scale-switch {
|
color: #2c3e50;
|
||||||
display: flex;
|
font-weight: 600;
|
||||||
align-items: center;
|
}
|
||||||
}
|
|
||||||
.scale-btn {
|
|
||||||
padding: 6px 12px;
|
|
||||||
margin-left: 6px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.scale-btn.active {
|
|
||||||
background: #3a7afe;
|
|
||||||
color: white;
|
|
||||||
border-color: #3a7afe;
|
|
||||||
}
|
|
||||||
.nav-btn {
|
|
||||||
padding: 6px 12px;
|
|
||||||
margin-left: 6px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background: #f5f5f5;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.person-container {
|
.person-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #c2c6cc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.person-gantt {
|
.person-gantt {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: white;
|
overflow: hidden;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.person-header {
|
.person-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-right: 10px;
|
||||||
|
background-color: #3a7afe;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-count {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #868e96;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
border-width: 0 8px 10px 8px;
|
||||||
|
border-color: transparent transparent #495057 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
border-width: 10px 8px 0 8px;
|
||||||
|
border-color: #495057 transparent transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.name-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.avatar {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
margin-right: 10px;
|
|
||||||
background-color: #3a7afe;
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.task-count {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #868e96;
|
|
||||||
}
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-chart {
|
.progress-chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue