Merge - merge updates from gitmigor fork

This commit is contained in:
LINING-PC\lining
2025-08-12 22:38:30 +08:00
parent 63f03ec9d8
commit 221f20e322
6 changed files with 166 additions and 50 deletions

61
docs/GITHUB-PAGES-EN.md Normal file
View File

@@ -0,0 +1,61 @@
# GitHub Pages Deployment Guide
This project has been configured with automatic deployment of GitHub Pages, and users can experience the complete Gantt charting function online.
## 🌐 Online visit
**Demo Address**: https://nelson820125.github.io/jordium-gantt-vue3/
## 🔧 Deployment Configuration
### Automated deployment
Projects are automatically deployed to GitHub Pages using GitHub Actions:
- **Triggering condition**: Push to `main` or `master` branch
- **Build Command**: `npm run build:pages`
- **Deployment Directory**: `./dist`
- **Workflows**: `.github/workflows/deploy-github-pages.yml`
### Enable GitHub Pages manually
1. Enter the GitHub repository settings page
2. Find the "Pages" setting options
3. Select "Source" as "GitHub Actions"
4. Push code to the main branch and automatically trigger deployment
## 📁 Construct the product
- **Development and Construction**: `npm run build``dist/`
- **GitHub Pages**: `npm run build:pages``dist/` (contains the correct base path)
- **NPM package build**: `npm run build:lib``npm-package/dist/`
## 🛠️ Local Preview
```bash
# Installation dependencies
npm install
# Start the development server
npm run dev
# Build and preview the production version
npm run build:pages
npm run preview
```
## 🔄 Update deployment
GitHub Actions will automatically:
1. Check out the code
2. Install Node.js and dependencies
3. Build demo application
4. Deploy to GitHub Pages
## 📝 Notes
- Deployment usually takes 1-5 minutes to take effect
- Make sure GitHub Pages is enabled in repository settings
- Custom domain names need to be configured in the `demo/public/CNAME` file
- Static resource paths use relative paths to ensure normal loading in the Pages environment

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useI18n } from '../composables/useI18n'
const { t } = useI18n()
interface Props {
modelValue?: string | [string, string]
@@ -97,7 +99,7 @@ watch(
singleValue.value = (newValue as string) || ''
}
},
{ immediate: true },
{ immediate: true }
)
// 处理单日期输入变化(预留,当前版本不使用)
@@ -464,6 +466,7 @@ const yearList = computed(() => {
return years
})
/*
// 月份名称
const monthNames = [
'一月',
@@ -480,6 +483,7 @@ const monthNames = [
'十二月',
]
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
*/
// 生命周期
onMounted(() => {
@@ -736,7 +740,7 @@ const panelStyle = computed(() => {
&lt;&lt;
</button>
<span class="el-month-picker__header-label" @click="showYearSelector($event)">
{{ currentYear }}
{{ currentYear }}
</span>
<button type="button" class="el-picker-panel__icon-btn" @click="nextYear">
&gt;&gt;
@@ -744,7 +748,7 @@ const panelStyle = computed(() => {
</div>
<div class="el-month-picker__content">
<div
v-for="(monthName, index) in monthNames"
v-for="(monthName, index) in t.monthNames"
:key="index"
class="el-month-picker__item"
:class="{ 'is-current': index === currentMonth }"
@@ -766,10 +770,10 @@ const panelStyle = computed(() => {
</button>
<span class="el-date-picker__header-label">
<span class="el-date-picker__header-year" @click="showYearSelector($event)">
{{ currentYear }}
{{ currentYear }}
</span>
<span class="el-date-picker__header-month" @click="showMonthSelector($event)">
{{ monthNames[currentMonth] }}
{{ t.monthNames[currentMonth] }}
</span>
</span>
<button type="button" class="el-picker-panel__icon-btn" @click="nextMonth">
@@ -783,7 +787,7 @@ const panelStyle = computed(() => {
<div class="el-date-picker__content">
<!-- 星期标题 -->
<div class="el-date-table__header">
<div v-for="day in weekDays" :key="day" class="el-date-table__header-cell">
<div v-for="day in t.weekDays" :key="day" class="el-date-table__header-cell">
{{ day }}
</div>
</div>

View File

@@ -2,6 +2,12 @@
import { computed, ref, onUnmounted } from 'vue'
import type { Milestone } from '../models/classes/Milestone'
import { TimelineScale } from '../models/types/TimelineScale'
import { useI18n } from '../composables/useI18n'
const { getTranslation } = useI18n()
const t = (key: string): string => {
return getTranslation(key)
}
interface Props {
date: string // 里程碑日期
@@ -151,7 +157,7 @@ const handleMouseMove = (e: MouseEvent) => {
mouseX: e.clientX,
isDragging: isDragging.value,
},
}),
})
)
const deltaX = e.clientX - dragStartX.value
@@ -178,7 +184,7 @@ const handleMouseUp = () => {
mouseX: 0,
isDragging: false,
},
}),
})
)
// 只有在真正拖拽了(有临时数据)且状态为拖拽中时才触发更新
@@ -240,7 +246,7 @@ const handleMilestoneClick = (e: MouseEvent) => {
scrollLeft: targetScrollLeft,
smooth: true,
},
}),
})
)
}
}
@@ -274,13 +280,13 @@ const milestoneStyle = computed(() => {
const centerPosition = calculateMilestonePositionFromTimelineData(
milestoneDate,
props.timelineData,
props.currentTimeScale,
props.currentTimeScale
)
left = centerPosition - size / 2 // 从中心位置偏移到图标左上角
} else {
// 日视图:保持原有逻辑
const startDiff = Math.floor(
(milestoneDate.getTime() - props.startDate.getTime()) / (1000 * 60 * 60 * 24),
(milestoneDate.getTime() - props.startDate.getTime()) / (1000 * 60 * 60 * 24)
)
left = startDiff * props.dayWidth + props.dayWidth / 2 - size / 2
}
@@ -346,7 +352,7 @@ const milestoneVisibility = computed(() => {
if (iconRight <= leftBoundary + iconSize / 2) {
// 检查左侧是否有其他停靠的里程碑,需要判断推挤优先级
const leftStickyMilestones = otherMilestones.filter(
m => m.id !== currentId && m.stickyPosition === 'left' && m.isSticky,
m => m.id !== currentId && m.stickyPosition === 'left' && m.isSticky
)
// 如果有其他里程碑已经停靠在左侧,比较优先级决定推挤顺序
@@ -392,7 +398,7 @@ const milestoneVisibility = computed(() => {
if (iconLeft >= rightBoundary - iconSize / 2) {
// 检查右侧是否有其他停靠的里程碑,需要判断推挤优先级
const rightStickyMilestones = otherMilestones.filter(
m => m.id !== currentId && m.stickyPosition === 'right' && m.isSticky,
m => m.id !== currentId && m.stickyPosition === 'right' && m.isSticky
)
// 如果有其他里程碑已经停靠在右侧,比较优先级决定推挤顺序
@@ -478,26 +484,26 @@ const handleMilestoneMouseLeave = () => {
// 格式化日期显示
const formatDisplayDate = (dateStr: string): string => {
if (!dateStr) return '未设置'
if (!dateStr) return t('dateNotSet') //Not Set
try {
const date = new Date(dateStr)
if (isNaN(date.getTime())) return '未设置'
if (isNaN(date.getTime())) return t('dateNotSet')
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
} catch {
return '未设置'
return t('dateNotSet')
}
}
// Tooltip内容
const tooltipContent = computed(() => {
const milestoneName = props.name || props.milestone?.name || '里程碑'
const milestoneName = props.name || props.milestone?.name || t('milestone')
const targetDate = formatDisplayDate(props.date || props.milestone?.startDate || '')
return `里程碑:${milestoneName} - 目标日期${targetDate}`
return `${t('milestone')}${milestoneName} <br> ${t('targetDate')}${targetDate}`
})
// 组件销毁时清理事件监听器
@@ -527,7 +533,7 @@ const calculateMilestonePositionFromTimelineData = (
subDays: Array<{ date: Date; dayOfWeek?: number }>
}>
}>,
timeScale: TimelineScale,
timeScale: TimelineScale
) => {
let cumulativePosition = 0

View File

@@ -4,6 +4,8 @@ import type { Task } from '../models/classes/Task'
import { TimelineScale } from '../models/types/TimelineScale'
import TaskContextMenu from './TaskContextMenu.vue'
import { useI18n } from '../composables/useI18n'
interface Props {
task: Task
rowHeight: number
@@ -31,6 +33,11 @@ interface Props {
const props = defineProps<Props>()
const { getTranslation } = useI18n()
const t = (key: string): string => {
return getTranslation(key)
}
const emit = defineEmits([
'update:task',
'bar-mounted',
@@ -833,9 +840,9 @@ const handleBubbleMouseDown = (event: MouseEvent) => {
// 格式化日期显示
const formatDisplayDate = (dateStr: string | undefined): string => {
if (!dateStr) return '未设置'
if (!dateStr) return t('dateNotSet')
const date = createLocalDate(dateStr)
if (!date) return '未设置'
if (!date) return t('dateNotSet')
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
@@ -1120,23 +1127,23 @@ onUnmounted(() => {
<div class="tooltip-title">{{ task.name }}</div>
<div class="tooltip-content">
<div class="tooltip-row">
<span class="tooltip-label">计划开始:</span>
<span class="tooltip-label"> {{ t('startDate') }}:</span>
<span class="tooltip-value">{{ formatDisplayDate(task.startDate) }}</span>
</div>
<div class="tooltip-row">
<span class="tooltip-label">计划结束:</span>
<span class="tooltip-label">{{ t('endDate') }}:</span>
<span class="tooltip-value">{{ formatDisplayDate(task.endDate) }}</span>
</div>
<div class="tooltip-row">
<span class="tooltip-label">计划工时:</span>
<span class="tooltip-label">{{ t('estimatedHours') }}:</span>
<span class="tooltip-value">{{ workHourInfo.total }}h</span>
</div>
<div class="tooltip-row">
<span class="tooltip-label">已用工时:</span>
<span class="tooltip-label"> {{ t('actualHours') }}:</span>
<span class="tooltip-value">{{ workHourInfo.used }}h</span>
</div>
<div class="tooltip-row">
<span class="tooltip-label">完成率:</span>
<span class="tooltip-label"> {{ t('progress') }}:</span>
<span class="tooltip-value">{{ task.progress || 0 }}%</span>
</div>
</div>

View File

@@ -235,7 +235,7 @@ const computeAllMilestonesPositions = () => {
if (!isNaN(milestoneDate.getTime())) {
const startDiff = Math.floor(
(milestoneDate.getTime() - timelineConfig.value.startDate.getTime()) /
(1000 * 60 * 60 * 24),
(1000 * 60 * 60 * 24)
)
const left = startDiff * 30 + 30 / 2 - 12 // 30是dayWidth12是图标半径
@@ -269,7 +269,7 @@ const computeAllMilestonesPositions = () => {
if (!isNaN(milestoneDate.getTime())) {
const startDiff = Math.floor(
(milestoneDate.getTime() - timelineConfig.value.startDate.getTime()) /
(1000 * 60 * 60 * 24),
(1000 * 60 * 60 * 24)
)
const left = startDiff * 30 + 30 / 2 - 12
@@ -372,7 +372,7 @@ const handleTaskRowHover = (taskId: number | null) => {
window.dispatchEvent(
new CustomEvent('timeline-task-hover', {
detail: taskId,
}),
})
)
}
@@ -424,7 +424,7 @@ const handleMilestoneIconChange = (milestoneId: number, icon: string) => {
window.dispatchEvent(
new CustomEvent('milestone-icon-changed', {
detail: { milestoneId, icon },
}),
})
)
}
@@ -442,7 +442,7 @@ const handleMilestoneSave = (updatedMilestone: Milestone) => {
window.dispatchEvent(
new CustomEvent('milestone-data-updated', {
detail: { milestone: updatedMilestone },
}),
})
)
}
@@ -455,14 +455,14 @@ const handleMilestoneDelete = (milestoneId: number) => {
window.dispatchEvent(
new CustomEvent('milestone-deleted', {
detail: { milestoneId },
}),
})
)
// 广播里程碑数据变化事件确保Timeline重新渲染
window.dispatchEvent(
new CustomEvent('milestone-data-changed', {
detail: { milestoneId },
}),
})
)
}
@@ -477,7 +477,7 @@ const handleMilestoneUpdate = (updatedMilestone: Milestone) => {
window.dispatchEvent(
new CustomEvent('milestone-data-updated', {
detail: { milestone: updatedMilestone },
}),
})
)
}
@@ -683,7 +683,7 @@ watch(
if (!isUpdatingTimelineConfig) {
timelineData.value = generateTimelineData()
}
},
}
)
// 保证每次时间轴数据变化后都自动居中今日仅初始化和外部props变更时触发不因任务/里程碑变更触发)
@@ -698,7 +698,7 @@ watch(
})
}
},
{ deep: true },
{ deep: true }
)
// 将今日定位到时间线中间位置
@@ -715,7 +715,7 @@ const scrollToTodayCenter = (retry = 0) => {
const startNormalized = new Date(
timelineStart.getFullYear(),
timelineStart.getMonth(),
timelineStart.getDate(),
timelineStart.getDate()
)
// 计算今天距离时间线开始日期的天数
@@ -806,7 +806,7 @@ const scrollToToday = () => {
const startNormalized = new Date(
timelineStart.getFullYear(),
timelineStart.getMonth(),
timelineStart.getDate(),
timelineStart.getDate()
)
// 计算今天距离时间线开始日期的天数
@@ -857,7 +857,7 @@ const updateTask = (updatedTask: Task) => {
window.dispatchEvent(
new CustomEvent('task-updated', {
detail: updatedTask,
}),
})
)
}
@@ -917,7 +917,7 @@ const handleTaskBarContextMenu = (event: { task: Task; position: { x: number; y:
window.dispatchEvent(
new CustomEvent('context-menu', {
detail: event,
}),
})
)
}
@@ -1000,7 +1000,7 @@ onMounted(() => {
// 监听TaskList的垂直滚动事件
window.addEventListener(
'task-list-vertical-scroll',
handleTaskListVerticalScroll as EventListener,
handleTaskListVerticalScroll as EventListener
)
// 监听语言变化
window.addEventListener('locale-changed', handleLocaleChange as EventListener)
@@ -1010,7 +1010,7 @@ onMounted(() => {
// 监听Timeline容器resize事件TaskList切换等
window.addEventListener(
'timeline-container-resized',
handleTimelineContainerResized as EventListener,
handleTimelineContainerResized as EventListener
)
// 监听里程碑点击定位事件
@@ -1100,7 +1100,7 @@ const handleTimelineBodyScroll = (event: Event) => {
window.dispatchEvent(
new CustomEvent('timeline-vertical-scroll', {
detail: { scrollTop },
}),
})
)
}
}
@@ -1113,7 +1113,7 @@ watch(
updateSvgSize()
})
},
{ immediate: true },
{ immediate: true }
)
// 拖拽滑动相关状态
@@ -1281,7 +1281,7 @@ const startAutoScroll = (direction: 'left' | 'right') => {
window.dispatchEvent(
new CustomEvent('timeline-auto-scroll', {
detail: { scrollDelta: newScrollLeft - currentScrollLeft },
}),
})
)
autoScrollTimer = window.setTimeout(scroll, 16) // 约60fps
@@ -1335,14 +1335,14 @@ onUnmounted(() => {
window.removeEventListener('task-list-hover', handleTaskListHover as EventListener)
window.removeEventListener(
'task-list-vertical-scroll',
handleTaskListVerticalScroll as EventListener,
handleTaskListVerticalScroll as EventListener
)
window.removeEventListener('locale-changed', handleLocaleChange as EventListener)
window.removeEventListener('splitter-drag-start', handleSplitterDragStart as EventListener)
window.removeEventListener('splitter-drag-end', handleSplitterDragEnd as EventListener)
window.removeEventListener(
'timeline-container-resized',
handleTimelineContainerResized as EventListener,
handleTimelineContainerResized as EventListener
)
window.removeEventListener('milestone-click-locate', handleMilestoneClickLocate as EventListener)
window.removeEventListener('drag-boundary-check', handleDragBoundaryCheck as EventListener)
@@ -1436,7 +1436,7 @@ watch(
}
})
},
{ deep: true },
{ deep: true }
)
// 处理里程碑点击定位事件
@@ -1620,7 +1620,7 @@ const handleAddSuccessor = (task: Task) => {
class="timeline-year"
:style="{ width: '719px' }"
>
<div class="year-label">{{ yearValue }}</div>
<div class="year-label">{{ yearValue }}</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@ export type Locale = 'zh-CN' | 'en-US'
// 多语言配置
const messages = {
'zh-CN': {
dateNotSet: '未设置',
// TaskList Header
taskName: '任务名称',
predecessor: '前置任务',
@@ -38,8 +39,26 @@ const messages = {
// 月份格式
monthFormat: (month: number) => `${month}`,
// 月份名称
monthNames: [
'一月',
'二月',
'三月',
'四月',
'五月',
'六月',
'七月',
'八月',
'九月',
'十月',
'十一月',
'十二月',
],
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
// 其他
milestone: '里程碑',
targetDate: '目标日期',
today: '今天',
// 里程碑对话框
milestoneDetails: '里程碑详情',
@@ -169,6 +188,7 @@ const messages = {
timerConfirmPlaceholder: '请输入计时说明',
},
'en-US': {
dateNotSet: 'Not set',
// TaskList Header
taskName: 'Task Name',
predecessor: 'Predecessor',
@@ -199,9 +219,27 @@ const messages = {
// 月份格式
monthFormat: (month: number) => `M${String(month).padStart(2, '0')}`,
// Month name
monthNames: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
weekDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
// 其他
milestone: 'Milestone',
today: 'Today',
targetDate: 'Target Date',
// 里程碑对话框
milestoneDetails: 'Milestone Details',
milestoneName: 'Milestone Name',
@@ -387,7 +425,7 @@ export function useI18n() {
window.dispatchEvent(
new CustomEvent('locale-changed', {
detail: { locale },
}),
})
)
}