diff --git a/common/changes/@visactor/vtable/feature-milestoneText_2025-04-27-03-18.json b/common/changes/@visactor/vtable/feature-milestoneText_2025-04-27-03-18.json new file mode 100644 index 0000000000..791e2c9710 --- /dev/null +++ b/common/changes/@visactor/vtable/feature-milestoneText_2025-04-27-03-18.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add milestone text\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "rem2248668357@163.com" +} \ No newline at end of file diff --git a/docs/assets/demo/en/gantt/gantt-milestone.md b/docs/assets/demo/en/gantt/gantt-milestone.md new file mode 100644 index 0000000000..149d28dc8d --- /dev/null +++ b/docs/assets/demo/en/gantt/gantt-milestone.md @@ -0,0 +1,305 @@ +--- +category: examples +group: gantt +title: Gantt Milestone +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/gantt-label-symbol.jpeg +link: gantt/introduction +option: Gantt#taskBar.milestoneStyle +--- + +# Gantt Milestone + +Milestones in Gantt charts are used to mark important time points in a project. Unlike regular tasks that span over a period, milestones are represented by a distinctive diamond symbol. This example demonstrates milestones in different states, helping you better track key project points. + +## Key Configurations + +- `Gantt#taskBar.milestoneStyle`: Configure milestone styles such as size, color, etc. +- Set `type: 'milestone'` in data to mark a task as milestone +- Milestone tasks only need to specify the `start` time + +## Code Demo + +```javascript livedemo template=vtable +const records = [ + { + id: '1', + title: '项目启动会议', + start: '2024-07-01', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '2', + title: '项目启动与规划', + start: '2024-07-01', + end: '2024-07-6', + progress: 100, + parent: '0' + }, + { + id: '3', + title: '需求评审完成', + start: '2024-07-6', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '4', + title: '技术方案设计', + start: '2024-07-6', + end: '2024-07-11', + progress: 80, + parent: '0' + }, + { + id: '5', + title: '开发环境搭建完成', + start: '2024-07-11', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '6', + title: '核心功能开发', + start: '2024-07-12', + end: '2024-07-18', + progress: 60, + parent: '0' + }, + { + id: '7', + title: 'Beta版本发布', + start: '2024-07-18', + type: 'milestone', + progress: 0, + parent: '0' + }, + { + id: '8', + title: '系统测试', + start: '2024-07-19', + end: '2024-07-23', + progress: 60, + parent: '0' + }, + { + id: '9', + title: '性能测试完成', + start: '2024-07-24', + type: 'milestone', + progress: 0, + parent: '0' + }, + { + id: '10', + title: '正式版本发布', + start: '2024-07-27', + type: 'milestone', + progress: 0, + parent: '0' + } +]; + +const columns = [ + { + field: 'title', + title: '任务名称', + width: 200, + style: { + fontFamily: 'PingFang SC', + padding: [8, 16] + } + }, + { + field: 'progress', + title: '进度', + width: 100, + style: { + fontFamily: 'PingFang SC', + padding: [8, 16], + textAlign: 'center', + color: value => (value >= 80 ? '#52c41a' : value >= 30 ? '#1890ff' : '#595959') + } + } +]; + +const option = { + records, + taskListTable: { + columns, + tableWidth: 280, + minTableWidth: 100, + maxTableWidth: 600, + theme: { + headerStyle: { + borderColor: '#f0f0f0', + fontSize: 13, + fontFamily: 'PingFang SC', + fontWeight: 500, + color: '#262626', + bgColor: '#fafafa', + padding: [12, 16] + }, + bodyStyle: { + fontSize: 13, + fontFamily: 'PingFang SC', + color: '#595959', + bgColor: '#ffffff', + borderColor: '#f0f0f0', + padding: [0, 16] + } + } + }, + frame: { + outerFrameStyle: { + borderColor: '#ebedf0', + borderLineWidth: 1, + cornerRadius: 12, + padding: [1, 1, 1, 1] + }, + verticalSplitLine: { + lineColor: '#f0f0f0', + lineWidth: 1 + } + }, + grid: { + backgroundColor: '#fafaff', + weekendBackgroundColor: 'rgba(94, 180, 245, 0.10)', + verticalLine: { + lineWidth: 1, + lineColor: '#f5f5f5' + }, + horizontalLine: { + lineWidth: 1, + lineColor: '#f5f5f5' + } + }, + headerRowHeight: 42, + rowHeight: 40, + taskBar: { + startDateField: 'start', + endDateField: 'end', + progressField: 'progress', + moveable: true, + hoverBarStyle: { + barOverlayColor: 'rgba(99, 144, 0, 0.2)' + }, + labelText: '{title} {progress}%', + labelTextStyle: { + // padding: 2, + fontFamily: 'Arial', + fontSize: 16, + textAlign: 'left', + textOverflow: 'ellipsis', + color: 'rgb(240, 246, 251)' + }, + barStyle: { + width: 24, + barColor: '#d6e4ff', + completedBarColor: '#597ef7', + cornerRadius: 12, + borderLineWidth: 2, + borderColor: 'rgb(7, 88, 150)' + }, + milestoneStyle: { + width: 16, + fillColor: value => (value.record.progress >= 100 ? '#597ef7' : '#d6e4ff'), + borderColor: '#597ef7', + borderLineWidth: 0, + labelText: '{title}', + labelTextStyle: { + fontSize: 16, + color: 'rgb(1, 43, 75)' + } + } + }, + timelineHeader: { + colWidth: 50, + backgroundColor: '#fafafa', + horizontalLine: { + lineWidth: 1, + lineColor: '#f0f0f0' + }, + verticalLine: { + lineWidth: 1, + lineColor: '#f0f0f0' + }, + scales: [ + { + unit: 'week', + step: 1, + format(date) { + return `第${date.dateIndex}周`; + }, + style: { + fontSize: 12, + fontFamily: 'PingFang SC', + textAlign: 'center', + textBaseline: 'middle', + color: '#262626', + padding: [8, 0] + } + }, + { + unit: 'day', + step: 1, + format(date) { + return date.dateIndex.toString(); + }, + style: { + fontSize: 12, + fontFamily: 'PingFang SC', + textAlign: 'center', + textBaseline: 'middle', + color: '#8c8c8c', + padding: [8, 0] + } + } + ] + }, + markLine: [ + { + date: '2024-07-11', + style: { + lineWidth: 1, + lineColor: 'blue', + lineDash: [8, 4] + } + }, + { + date: '2024-07-22', + style: { + lineWidth: 2, + lineColor: 'red', + lineDash: [8, 4] + } + } + ], + rowSeriesNumber: { + title: '行号', + dragOrder: true, + headerStyle: { + bgColor: '#EEF1F5', + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8' + } + }, + scrollStyle: { + scrollRailColor: '#f5f5f5', + visible: 'hover', + width: 5, + scrollSliderColor: '#ccc', + hover: { + scrollSliderColor: '#bbb' + } + } +}; + +ganttInstance = new VTableGantt.Gantt(document.getElementById(CONTAINER_ID), option); +window['ganttInstance'] = ganttInstance; +``` diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index 690228b7c5..9d26a83d29 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -273,6 +273,13 @@ "en": "Gantt Edit" } }, + { + "path": "gantt-milestone", + "title": { + "zh": "甘特图里程碑", + "en": "Gantt Milestone" + } + }, { "path": "gantt-dependency-link-line", "title": { diff --git a/docs/assets/demo/zh/gantt/gantt-milestone.md b/docs/assets/demo/zh/gantt/gantt-milestone.md new file mode 100644 index 0000000000..fc98a16acd --- /dev/null +++ b/docs/assets/demo/zh/gantt/gantt-milestone.md @@ -0,0 +1,305 @@ +--- +category: examples +group: gantt +title: 甘特图里程碑(Milestone) +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/gantt-label-symbol.jpeg +link: gantt/introduction +option: Gantt#taskBar.milestoneStyle +--- + +# 甘特图里程碑(Milestone) + +甘特图中的里程碑(Milestone)用于标记项目中的重要时间节点。它不同于普通任务,通常以一个醒目的菱形符号表示,而不是时间跨度的任务条。本示例展示了里程碑的基础用法,帮助您更好地标记和追踪项目关键节点。 + +## 关键配置 + +- `Gantt#taskBar.milestoneStyle`: 配置里程碑的样式,如大小、颜色等 +- 数据中设置 `type: 'milestone'` 来标记里程碑任务 +- 里程碑任务只需要设置 `start` 时间即可 + +## 代码演示 + +```javascript livedemo template=vtable +const records = [ + { + id: '1', + title: '项目启动会议', + start: '2024-07-01', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '2', + title: '项目启动与规划', + start: '2024-07-01', + end: '2024-07-6', + progress: 100, + parent: '0' + }, + { + id: '3', + title: '需求评审完成', + start: '2024-07-6', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '4', + title: '技术方案设计', + start: '2024-07-6', + end: '2024-07-11', + progress: 80, + parent: '0' + }, + { + id: '5', + title: '开发环境搭建完成', + start: '2024-07-11', + type: 'milestone', + progress: 100, + parent: '0' + }, + { + id: '6', + title: '核心功能开发', + start: '2024-07-12', + end: '2024-07-18', + progress: 60, + parent: '0' + }, + { + id: '7', + title: 'Beta版本发布', + start: '2024-07-18', + type: 'milestone', + progress: 0, + parent: '0' + }, + { + id: '8', + title: '系统测试', + start: '2024-07-19', + end: '2024-07-23', + progress: 60, + parent: '0' + }, + { + id: '9', + title: '性能测试完成', + start: '2024-07-24', + type: 'milestone', + progress: 0, + parent: '0' + }, + { + id: '10', + title: '正式版本发布', + start: '2024-07-27', + type: 'milestone', + progress: 0, + parent: '0' + } +]; + +const columns = [ + { + field: 'title', + title: '任务名称', + width: 200, + style: { + fontFamily: 'PingFang SC', + padding: [8, 16] + } + }, + { + field: 'progress', + title: '进度', + width: 100, + style: { + fontFamily: 'PingFang SC', + padding: [8, 16], + textAlign: 'center', + color: value => (value >= 80 ? '#52c41a' : value >= 30 ? '#1890ff' : '#595959') + } + } +]; + +const option = { + records, + taskListTable: { + columns, + tableWidth: 280, + minTableWidth: 100, + maxTableWidth: 600, + theme: { + headerStyle: { + borderColor: '#f0f0f0', + fontSize: 13, + fontFamily: 'PingFang SC', + fontWeight: 500, + color: '#262626', + bgColor: '#fafafa', + padding: [12, 16] + }, + bodyStyle: { + fontSize: 13, + fontFamily: 'PingFang SC', + color: '#595959', + bgColor: '#ffffff', + borderColor: '#f0f0f0', + padding: [0, 16] + } + } + }, + frame: { + outerFrameStyle: { + borderColor: '#ebedf0', + borderLineWidth: 1, + cornerRadius: 12, + padding: [1, 1, 1, 1] + }, + verticalSplitLine: { + lineColor: '#f0f0f0', + lineWidth: 1 + } + }, + grid: { + backgroundColor: '#fafaff', + weekendBackgroundColor: 'rgba(94, 180, 245, 0.10)', + verticalLine: { + lineWidth: 1, + lineColor: '#f5f5f5' + }, + horizontalLine: { + lineWidth: 1, + lineColor: '#f5f5f5' + } + }, + headerRowHeight: 42, + rowHeight: 40, + taskBar: { + startDateField: 'start', + endDateField: 'end', + progressField: 'progress', + moveable: true, + hoverBarStyle: { + barOverlayColor: 'rgba(99, 144, 0, 0.2)' + }, + labelText: '{title} {progress}%', + labelTextStyle: { + // padding: 2, + fontFamily: 'Arial', + fontSize: 16, + textAlign: 'left', + textOverflow: 'ellipsis', + color: 'rgb(240, 246, 251)' + }, + barStyle: { + width: 24, + barColor: '#d6e4ff', + completedBarColor: '#597ef7', + cornerRadius: 12, + borderLineWidth: 2, + borderColor: 'rgb(7, 88, 150)' + }, + milestoneStyle: { + width: 16, + fillColor: value => (value.record.progress >= 100 ? '#597ef7' : '#d6e4ff'), + borderColor: '#597ef7', + borderLineWidth: 0, + labelText: '{title}', + labelTextStyle: { + fontSize: 16, + color: 'rgb(1, 43, 75)' + } + } + }, + timelineHeader: { + colWidth: 50, + backgroundColor: '#fafafa', + horizontalLine: { + lineWidth: 1, + lineColor: '#f0f0f0' + }, + verticalLine: { + lineWidth: 1, + lineColor: '#f0f0f0' + }, + scales: [ + { + unit: 'week', + step: 1, + format(date) { + return `第${date.dateIndex}周`; + }, + style: { + fontSize: 12, + fontFamily: 'PingFang SC', + textAlign: 'center', + textBaseline: 'middle', + color: '#262626', + padding: [8, 0] + } + }, + { + unit: 'day', + step: 1, + format(date) { + return date.dateIndex.toString(); + }, + style: { + fontSize: 12, + fontFamily: 'PingFang SC', + textAlign: 'center', + textBaseline: 'middle', + color: '#8c8c8c', + padding: [8, 0] + } + } + ] + }, + markLine: [ + { + date: '2024-07-11', + style: { + lineWidth: 1, + lineColor: 'blue', + lineDash: [8, 4] + } + }, + { + date: '2024-07-22', + style: { + lineWidth: 2, + lineColor: 'red', + lineDash: [8, 4] + } + } + ], + rowSeriesNumber: { + title: '行号', + dragOrder: true, + headerStyle: { + bgColor: '#EEF1F5', + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8' + } + }, + scrollStyle: { + scrollRailColor: '#f5f5f5', + visible: 'hover', + width: 5, + scrollSliderColor: '#ccc', + hover: { + scrollSliderColor: '#bbb' + } + } +}; + +ganttInstance = new VTableGantt.Gantt(document.getElementById(CONTAINER_ID), option); +window['ganttInstance'] = ganttInstance; +``` diff --git a/docs/assets/option/en/common/gantt/milestone-style.md b/docs/assets/option/en/common/gantt/milestone-style.md index 20be915374..ef7170f03c 100644 --- a/docs/assets/option/en/common/gantt/milestone-style.md +++ b/docs/assets/option/en/common/gantt/milestone-style.md @@ -14,4 +14,16 @@ export interface IMilestoneStyle { cornerRadius?: number; /** The milestone is a square by default, and this width configures the length of the square edge */ width?: number; + /** The milestone label. Supports either a fixed string or a template string */ + labelText?: ITaskBarLabelText; + /** Milestone Text Style */ + labelTextStyle:{ + fontFamily?: string; + fontSize?: number; + color?: string; + padding?: number | number[]; + } + /** Position of text relative to milestone */ + textOrient?: 'left' | 'top' | 'right' | 'bottom'; } +``` diff --git a/docs/assets/option/zh/common/gantt/milestone-style.md b/docs/assets/option/zh/common/gantt/milestone-style.md index 0926c212fb..76f1dcdc53 100644 --- a/docs/assets/option/zh/common/gantt/milestone-style.md +++ b/docs/assets/option/zh/common/gantt/milestone-style.md @@ -14,5 +14,16 @@ export interface IMilestoneStyle { cornerRadius?: number; /** 里程碑默认是个正方形,这个width配置正方形的边长 */ width?: number; + /** 里程碑展示文字。可以配置固定文本 或者 字符串模版 */ + labelText?: ITaskBarLabelText; + /** 里程碑文字样式 */ + labelTextStyle:{ + fontFamily?: string; + fontSize?: number; + color?: string; + padding?: number | number[]; + } + /** 文字相对于里程碑的位置 */ + textOrient?: 'left' | 'top' | 'right' | 'bottom'; } ``` diff --git a/packages/vtable-gantt/examples/gantt/gantt-milestone.ts b/packages/vtable-gantt/examples/gantt/gantt-milestone.ts index 42d7c9354b..ceda756160 100644 --- a/packages/vtable-gantt/examples/gantt/gantt-milestone.ts +++ b/packages/vtable-gantt/examples/gantt/gantt-milestone.ts @@ -18,10 +18,28 @@ export function createTable() { }, { id: 2, - title: 'Scope', + title: 'Project Started', developer: 'liufangfang.jane@bytedance.com', start: '2024-07-05', - progress: 60, + progress: 100, + priority: 'P0', + type: 'milestone' + }, + { + id: 3, + title: 'Design Complete', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-08', + progress: 100, + priority: 'P1', + type: 'milestone' + }, + { + id: 4, + title: 'Phase 1 Complete', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-15', + progress: 100, priority: 'P0', type: 'milestone' }, @@ -348,7 +366,12 @@ export function createTable() { borderLineWidth: 1, fillColor: 'green', cornerRadius: 0, - width: 15 + width: 15, + labelText: '{title}', + labelTextStyle: { + fontFamily: 'Arial', + padding: 8 + } }, selectedBarStyle: { shadowBlur: 5, //阴影宽度 diff --git a/packages/vtable-gantt/src/gantt-helper.ts b/packages/vtable-gantt/src/gantt-helper.ts index 930d0e1071..e524943214 100644 --- a/packages/vtable-gantt/src/gantt-helper.ts +++ b/packages/vtable-gantt/src/gantt-helper.ts @@ -54,6 +54,7 @@ export function generateMarkLine(markLine?: boolean | IMarkLine | IMarkLine[]): return [ { date: createDateAtMidnight().toLocaleDateString(), + content: '', scrollToMarkLine: true, position: 'left', style: { @@ -209,6 +210,15 @@ export function initOptions(gantt: Gantt) { options?.taskBar?.barStyle && typeof options?.taskBar?.barStyle === 'function' ? options.taskBar.barStyle : Object.assign({}, defaultTaskBarStyle, options?.taskBar?.barStyle); + + const defaultMilestoneStyle = { + labelTextStyle: { + fontSize: 16, + color: 'red', + fontFamily: 'Arial', + padding: 4 + } + }; gantt.parsedOptions.taskBarMilestoneStyle = Object.assign( typeof gantt.parsedOptions.taskBarStyle === 'function' ? {} @@ -219,8 +229,10 @@ export function initOptions(gantt: Gantt) { fillColor: gantt.parsedOptions.taskBarStyle.barColor, cornerRadius: 0 }, + defaultMilestoneStyle, options?.taskBar?.milestoneStyle ); + gantt.parsedOptions.taskBarMilestoneHypotenuse = gantt.parsedOptions.taskBarMilestoneStyle.width * Math.sqrt(2); gantt.parsedOptions.dateFormat = options?.dateFormat; diff --git a/packages/vtable-gantt/src/scenegraph/gantt-node.ts b/packages/vtable-gantt/src/scenegraph/gantt-node.ts index 1ae695b27f..e92027d0f0 100644 --- a/packages/vtable-gantt/src/scenegraph/gantt-node.ts +++ b/packages/vtable-gantt/src/scenegraph/gantt-node.ts @@ -7,6 +7,8 @@ import { isValid } from '@visactor/vutils'; import { textMeasure } from '@visactor/vtable'; export class GanttTaskBarNode extends Group { + milestoneTextLabel?: IText; + milestoneTextContainer?: Group; clipGroupBox: Group; barRect?: IRect; progressRect?: IRect; diff --git a/packages/vtable-gantt/src/scenegraph/task-bar.ts b/packages/vtable-gantt/src/scenegraph/task-bar.ts index b6cc4f80db..7989fab13b 100644 --- a/packages/vtable-gantt/src/scenegraph/task-bar.ts +++ b/packages/vtable-gantt/src/scenegraph/task-bar.ts @@ -26,6 +26,83 @@ const TASKBAR_HOVER_ICON = `