【Vue3组件示例】简单类甘特图组件

【Vue3组件示例】简单类甘特图组件

html 复制代码
<script setup lang="ts">
defineOptions({
  name: 'GanttTable'
})

interface TaskGroup {
  id: string
  text: string
  description?: string
  task: Task[]
}

interface Task {
  id: string
  text: string
  start: string
  end: string
}

// 颜色列表
const colors = ['#e5effe', '#ececfd', '#e4ecee', '#fce7f3', '#f3e8ff', '#ecfccb', '#ffedd5', '#e0f2fe']
// 日期范围
const dateRange = ref(['11-28', '11-29', '11-30', '12-01', '12-02', '12-03', '12-04'])

/**
 * 任务列表
 */
const data = ref<TaskGroup[]>([
  {
    id: '001',
    text: 'XXX分中心',
    description: '【2030】呈报XXXX领导,印送基本XXXX分中心、XXXXX分中心,通报XXXX区。',
    task: [
      {
        id: '001-001',
        text: '【0830】电话通知XXXXXX队、XXXXXXX队加强XXXX活动。',
        start: '11-29',
        end: '12-03'
      }
    ]
  },
  {
    id: '002',
    text: 'ZH员',
    description: '【2130】通报XXXXXX区上XXXXXXXXX,做好应对准备。',
    task: []
  },
  {
    id: '003',
    text: '山东省济南市高密县X区',
    description: '【2030】呈报XXXX领导,印送基本XXXX分中心、XXXXX分中心,通报XXXX区。',
    task: [
      {
        id: '003-001',
        text: '【1900】XXX005H位"XXXXXXXXX"号船、178号XXXX海域ZZC。',
        start: '11-30',
        end: '12-03'
      },
      {
        id: '003-002',
        text: '【0649】提醒ZZXXX,菲位XXXXXXXXXXXXXX等方向可能的侵权',
        start: '12-03',
        end: '12-04'
      }
    ]
  }
])

/**
 * 计算 task 的样式(宽度和左边距)
 * @param start 开始日期,如 '11-28'
 * @param end 结束日期,如 '12-04'
 * @returns { marginLeft: string, width: string }
 */
const getTaskStyle = (start: string, end: string) => {
  const totalCols = dateRange.value.length
  const startIndex = dateRange.value.indexOf(start)
  const endIndex = dateRange.value.indexOf(end)

  // 如果找不到日期,返回默认样式
  if (startIndex === -1 || endIndex === -1) {
    return {
      marginLeft: '0%',
      width: '100%'
    }
  }

  // 计算跨越的列数(包含起始和结束日期)
  const spanCols = endIndex - startIndex + 1

  // 计算百分比
  const marginLeftPercent = (startIndex / totalCols) * 100
  const widthPercent = (spanCols / totalCols) * 100

  return {
    marginLeft: `${marginLeftPercent}%`,
    width: `${widthPercent}%`
  }
}

// 全局计数器
let taskColorIndex = 0

// 缓存 task id 和颜色的映射
const taskColorMap = new Map<string, number>()

const getTaskColor = (taskId: string): string => {
  if (!taskColorMap.has(taskId)) {
    taskColorMap.set(taskId, taskColorIndex)
    taskColorIndex++
  }
  const index = taskColorMap.get(taskId)!
  return colors[index % colors.length] // 循环使用颜色
}
</script>

<template>
  <div class="box-border w-full">
    <div>
      <!-- 表头 -->
      <div class="flex h-8 divide-x divide-gray-400 border border-gray-400">
        <div class="w-30 shrink-0"></div>
        <div v-for="d1 in dateRange" :key="d1" class="flex flex-1 items-center justify-center">{{ d1 }}</div>
      </div>

      <!-- 数据行 -->
      <div v-for="d2 in data" :key="d2.id" class="flex min-h-8 divide-x divide-gray-400 border border-t-0 border-gray-400">
        <!-- 左侧标题列 -->
        <div class="flex w-30 shrink-0 items-center justify-center px-3">{{ d2.text }}</div>

        <!-- 右侧内容区域 -->
        <div class="flex-1 divide-y divide-dashed divide-blue-800">
          <!-- 描述行 -->
          <div class="flex w-full items-center px-2 py-1">
            <Icon icon="material-symbols:check-circle" width="16" height="16" color="#2FB665" class="shrink-0" />
            <span class="ml-1">{{ d2.description }}</span>
          </div>

          <!-- Task 行 -->
          <div v-for="d3 in d2.task" :key="d3.id" class="relative w-full">
            <div
              class="rounded bg-[#E5EFFE] p-2"
              :style="{
                marginLeft: getTaskStyle(d3.start, d3.end).marginLeft,
                width: getTaskStyle(d3.start, d3.end).width,
                backgroundColor: getTaskColor(d3.id)
              }"
            >
              <div class="flex items-center">
                <Icon icon="material-symbols:check-circle" width="16" height="16" color="#2FB665" class="shrink-0" />
                <span class="ml-1 text-sm">{{ d3.text }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
相关推荐
会跑的葫芦怪21 小时前
若依Vue 项目多子路径配置
前端·javascript·vue.js
xiaoqi9221 天前
React Native鸿蒙跨平台如何进行狗狗领养中心,实现基于唯一标识的事件透传方式是移动端列表开发的通用规范
javascript·react native·react.js·ecmascript·harmonyos
jin1233221 天前
React Native鸿蒙跨平台剧本杀组队消息与快捷入口组件,包含消息列表展示、快捷入口管理、快捷操作触发和消息详情预览四大核心功能
javascript·react native·react.js·ecmascript·harmonyos
烬头88211 天前
React Native鸿蒙跨平台实现二维码联系人APP(QRCodeContactApp)
javascript·react native·react.js·ecmascript·harmonyos
pas1361 天前
40-mini-vue 实现三种联合类型
前端·javascript·vue.js
2601_949833391 天前
flutter_for_openharmony口腔护理app实战+预约管理实现
android·javascript·flutter
军军君011 天前
Three.js基础功能学习十三:太阳系实例上
前端·javascript·vue.js·学习·3d·前端框架·three
xiaoqi9221 天前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos
qq_177767371 天前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
2603_949462101 天前
Flutter for OpenHarmony社团管理App实战:预算管理实现
android·javascript·flutter