Commit a6337052 authored by 仇晓婷's avatar 仇晓婷

甘特图

parent a5f4d12f
<!--
/**
* @fileoverview Header component
* @license MIT
* @author Rafal Pospiech <neuronet.io@gmail.com>
* @package GanttElasticHeader
*/
-->
<template>
<div class="gantt-elastic__header" :style="{ ...style['header'] }">
<div class="gantt-elastic__header-title" :style="{ ...style['header-title'] }">
<div
class="gantt-elastic__header-title--text"
:style="{ ...style['header-title--text'] }"
v-if="!opts.title.html"
>{{ opts.title.label }}</div>
<div
class="gantt-elastic__header-title--html"
:style="{ ...style['header-title--html'] }"
v-if="opts.title.html"
v-html="opts.title.label"
></div>
</div>
<div class="gantt-elastic__header-options" :style="{ ...style['header-options'] }">
<!-- <button
class="gantt-elastic__header-btn-recenter"
:style="{ ...style['header-btn-recenter'] }"
@click.prevent="recenterPosition"
>
{{ opts.locale.Now }}
</button>-->
<label class="gantt-elastic__header-label" :style="{ ...style['header-label'] }">
{{ opts.locale["X-Scale"] }}
<div
class="gantt-elastic__header-slider-wrapper"
:style="{ ...style['header-slider-wrapper'] }"
>
<vue-slider
class="gantt-elastic__header-slider"
tooltip="none"
:style="{ ...style['header-slider'] }"
:process-style="{ ...style['header-slider--process'] }"
:slider-style="{ ...style['header-slider--slider'] }"
v-model="scale"
:max="24"
:min="2"
width="100px"
></vue-slider>
</div>
</label>
<label class="gantt-elastic__header-label" :style="{ ...style['header-label'] }">
{{ opts.locale["Y-Scale"] }}
<div
class="gantt-elastic__header-slider-wrapper"
:style="{ ...style['header-slider-wrapper'] }"
>
<vue-slider
class="gantt-elastic__header-slider"
tooltip="none"
:style="{ ...style['header-slider'] }"
:process-style="{ ...style['header-slider--process'] }"
:slider-style="{ ...style['header-slider--slider'] }"
v-model="height"
:max="100"
:min="7"
width="100px"
></vue-slider>
</div>
</label>
<label class="gantt-elastic__header-label" :style="{ ...style['header-label'] }">
{{ opts.locale["Before/After"] }}
<div
class="gantt-elastic__header-slider-wrapper"
:style="{ ...style['header-slider-wrapper'] }"
>
<vue-slider
class="gantt-elastic__header-slider"
tooltip="none"
:style="{ ...style['header-slider'] }"
:process-style="{ ...style['header-slider--process'] }"
:slider-style="{ ...style['header-slider--slider'] }"
v-model="scope"
:max="31"
:min="0"
width="100px"
></vue-slider>
</div>
</label>
<label class="gantt-elastic__header-label" :style="{ ...style['header-label'] }">
{{ opts.locale["Task list width"] }}
<div
class="gantt-elastic__header-slider-wrapper"
:style="{ ...style['header-slider-wrapper'] }"
>
<vue-slider
class="gantt-elastic__header-slider"
tooltip="none"
:style="{ ...style['header-slider'] }"
:process-style="{ ...style['header-slider--process'] }"
:slider-style="{ ...style['header-slider--slider'] }"
v-model="divider"
:max="100"
:min="0"
width="100px"
></vue-slider>
</div>
</label>
<label
class="gantt-elastic__header-task-list-switch--wrapper"
:style="{ ...style['header-task-list-switch--label'] }"
>
<switches
class="gantt-elastic__header-task-list-switch"
:style="{ ...style['header-task-list-switch'] }"
v-model="root.state.options.taskList.display"
></switches>
{{ opts.locale["Display task list"] }}
</label>
</div>
</div>
</template>
<script>
import vueSlider from "vue-slider-component";
import "vue-slider-component/theme/default.css";
import Switches from "vue-switches";
const defaultStyle = {
header: {
margin: "0px auto",
background: "#f3f5f747",
padding: "10px",
overflow: "hidden",
clear: "both",
display: "flex",
"justify-content": "space-between"
},
"header-title": { float: "left" },
"header-options": { float: "right" },
"header-title--text": {
"font-size": "20px",
"vertical-align": "middle",
"font-weight": "400",
"line-height": "35px",
"padding-left": "22px",
"letter-spacing": "1px"
},
"header-title--html": {
"font-size": "20px",
"vertical-align": "middle",
"font-weight": "400",
"line-height": "35px",
"padding-left": "22px",
"letter-spacing": "1px"
},
"header-btn-recenter": {
background: "#95A5A6",
border: "none",
outline: "none",
cursor: "pointer",
color: "white",
"border-radius": "3px",
"margin-right": "27px",
"font-size": "16px",
padding: "8px 12px"
},
"header-slider": {
"box-sizing": "content-box"
},
"header-slider-wrapper": {
display: "inline-block",
"vertical-align": "middle"
},
"header-slider--slider": { "box-sizing": "content-box" },
"header-slider--process": { "box-sizing": "content-box" },
"header-task-list-switch--label": { "box-sizing": "content-box" },
"header-task-list-switch": {
margin: "0px 15px",
"vertical-align": "middle"
},
"header-label": {}
};
const defaultOptions = {
title: {
label: "任务甘特图",
html: false
},
locale: {
Now: "Now",
"X-Scale": "缩放-X",
"Y-Scale": "缩放-Y",
"Task list width": "任务列表",
"Before/After": "展开日期",
"Display task list": "是否展开任务列表"
}
};
export default {
name: "GanttHeader",
components: {
vueSlider,
Switches
},
props: ["options", "dynamicStyle"],
inject: ["root"],
data() {
return {
scaleTimeoutId: null,
firstScale: false,
localScale: 0,
localHeight: 0,
localBefore: 0,
localPercent: 0,
sliderOptions: {
xScale: {
value: 0
}
},
style: {},
opts: {}
};
},
created() {
this.localScale = this.root.state.options.times.timeZoom;
this.localHeight = this.root.state.options.row.height;
this.localBefore = this.root.state.options.scope.before;
this.localPercent = this.root.state.options.taskList.percent;
this.sliderOptions.xScale.value = this.root.state.options.times.timeZoom;
this.style = this.root.mergeDeep({}, defaultStyle, this.dynamicStyle);
this.opts = this.root.mergeDeep({}, defaultOptions, this.options);
},
methods: {
getImage() {
this.root.getImage("image/png").then(imgB64 => {
const link = document.createElement("a");
link.href = imgB64;
link.download = "gantt-elastic.png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
},
recenterPosition() {
this.root.$emit("recenterPosition");
},
setScale(value) {
if (this.scaleTimeoutId !== null) {
clearTimeout(this.scaleTimeoutId);
this.scaleTimeoutId = null;
}
// debouncing
if (this.firstScale) {
this.scaleTimeoutId = setTimeout(() => {
this.root.$emit("times-timeZoom-change", value);
this.scaleTimeoutId = null;
}, 50);
} else {
this.root.$emit("times-timeZoom-change", value);
this.firstScale = true;
}
}
},
computed: {
/**
* If there is a component slot specified for header
* @returns {bool}
*/
beforeOptionsIsComponent() {
const headerSlot = this.options.slots.header;
if (
typeof headerSlot.beforeOptions === "object" &&
!Array.isArray(headerSlot.beforeOptions)
) {
return true;
}
return false;
},
/**
* If there is a slot with beforeOptions html content
* @returns {bool}
*/
beforeOptionsIsHtml() {
if (typeof this.options.slots.header.beforeOptions === "string") {
return true;
}
return false;
},
scale: {
get() {
return this.localScale;
},
set(value) {
this.localScale = Number(value);
this.setScale(this.localScale);
}
},
height: {
get() {
return this.localHeight;
},
set(value) {
this.localHeight = Number(value);
this.root.$emit("row-height-change", Number(value));
}
},
scope: {
get() {
return this.localBefore;
},
set(value) {
this.localBefore = Number(value);
this.root.$emit("scope-change", Number(value));
}
},
divider: {
get() {
return this.localPercent;
},
set(value) {
this.localPercent = Number(value);
this.root.$emit("taskList-width-change", Number(value));
}
}
}
};
</script>
<template>
<div class="q-pa-sm">
<gantt-elastic
:options="options"
:tasks="tasks"
@tasks-changed="tasksUpdate"
@options-changed="optionsUpdate"
@dynamic-style-changed="styleUpdate"
>
<gantt-header slot="header"></gantt-header>
</gantt-elastic>
<div class="q-mt-md" />
<!-- <q-btn @click="addTask" icon="mdi-plus" label="Add task" /> -->
</div>
</template>
<style>
</style>
<script>
import GanttElastic from "gantt-elastic";
import GanttHeader from "./components/gantt-header";
import dayjs from "dayjs";
// just helper to get current dates
function getDate(hours) {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
const currentDay = currentDate.getDate();
const timeStamp = new Date(
currentYear,
currentMonth,
currentDay,
0,
0,
0
).getTime();
return new Date(timeStamp + hours * 60 * 60 * 1000).getTime();
}
let tasks = [
{
id: 1,
label: "任务1",
user:
'<a href="https://www.google.com/search?q=John+Doe" target="_blank" style="color:#0077c0;">John Doe</a>',
start: getDate(-24 * 5),
duration: 15 * 24 * 60 * 60 * 1000,
percent: 85,
type: "project"
//collapsed: true,
},
{
id: 2,
label: "任务2",
user:
'<a href="https://www.google.com/search?q=Peter+Parker" target="_blank" style="color:#0077c0;">Peter Parker</a>',
parentId: 1,
start: getDate(-24 * 4),
duration: 4 * 24 * 60 * 60 * 1000,
percent: 50,
type: "milestone",
collapsed: true,
style: {
base: {
fill: "#1EBC61",
stroke: "#0EAC51"
}
}
},
{
id: 3,
label: "任务3",
user:
'<a href="https://www.google.com/search?q=John+Wayne" target="_blank" style="color:#0077c0;">John Wayne</a>',
parentId: 2,
start: getDate(-24 * 3),
duration: 2 * 24 * 60 * 60 * 1000,
percent: 100,
type: "task"
},
{
id: 4,
label: "任务4",
user:
'<a href="https://www.google.com/search?q=Clark+Kent" target="_blank" style="color:#0077c0;">Clark Kent</a>',
start: getDate(-24 * 2),
duration: 2 * 24 * 60 * 60 * 1000,
percent: 50,
type: "task",
dependentOn: [3]
},
{
id: 5,
label:
"任务5",
user:
'<a href="https://www.google.com/search?q=Austin+Powers" target="_blank" style="color:#0077c0;">Austin Powers</a>',
parentId: 4,
start: getDate(0),
duration: 2 * 24 * 60 * 60 * 1000,
percent: 10,
type: "milestone",
style: {
base: {
fill: "#0287D0",
stroke: "#0077C0"
}
}
},
{
id: 6,
label: "任务6",
user:
'<a href="https://www.google.com/search?q=Mario+Bros" target="_blank" style="color:#0077c0;">Mario Bros</a>',
parentId: 5,
start: getDate(24),
duration: 1 * 24 * 60 * 60 * 1000,
percent: 50,
type: "task",
collapsed: true,
style: {
base: {
fill: "#8E44AD",
stroke: "#7E349D"
}
}
},
{
id: 7,
label: "任务7",
user:
'<a href="https://www.google.com/search?q=Knight+Rider" target="_blank" style="color:#0077c0;">Knight Rider</a>',
parentId: 2,
dependentOn: [6],
start: getDate(24 * 2),
duration: 4 * 60 * 60 * 1000,
percent: 20,
type: "task",
collapsed: true
},
{
id: 8,
label: "任务8",
user:
'<a href="https://www.google.com/search?q=Johhny+Bravo" target="_blank" style="color:#0077c0;">Johhny Bravo</a>',
parentId: 7,
dependentOn: [7],
start: getDate(24 * 3),
duration: 1 * 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
},
{
id: 9,
label:
"任务9",
user:
'<a href="https://www.google.com/search?q=Dexter\'s+Laboratory" target="_blank" style="color:#0077c0;">Dexter\'s Laboratory</a>',
parentId: 8,
dependentOn: [8, 7],
start: getDate(24 * 4),
duration: 4 * 60 * 60 * 1000,
percent: 20,
type: "task",
style: {
base: {
fill: "#8E44AD",
stroke: "#7E349D"
}
}
},
{
id: 10,
label: "任务10",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 5),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
},
{
id: 11,
label: "任务11",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 6),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
},
{
id: 12,
label: "任务12",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 7),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task",
parentId: 11
},
{
id: 13,
label: "任务13",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 8),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
},
{
id: 14,
label: "任务14",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 9),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
},
{
id: 15,
label: "任务15",
user:
'<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
start: getDate(24 * 16),
duration: 24 * 60 * 60 * 1000,
percent: 0,
type: "task"
}
];
let options = {
taskMapping: {
progress: "percent"
},
maxRows: 100,
maxHeight: 500,
title: {
label: "Your project title as html (link or whatever...)",
html: false
},
row: {
height: 24
},
calendar: {
hour: {
display: false
}
},
chart: {
progress: {
bar: false
},
expander: {
display: true
}
},
taskList: {
expander: {
straight: false
},
columns: [
{
id: 1,
label: "ID",
value: "id",
width: 40
},
{
id: 2,
label: "任务",
value: "label",
width: 200,
expander: true,
html: true,
events: {
click({ data, column }) {
alert("description clicked!\n" + data.label);
}
}
},
{
id: 3,
label: "责任人",
value: "user",
width: 130,
html: true
},
{
id: 3,
label: "开始时间",
value: task => dayjs(task.start).format("YYYY-MM-DD"),
width: 78
},
{
id: 4,
label: "类型",
value: "type",
width: 68
},
{
id: 5,
label: "%",
value: "progress",
width: 35,
style: {
"task-list-header-label": {
"text-align": "center",
width: "100%"
},
"task-list-item-value-container": {
"text-align": "center",
width: "100%"
}
}
}
]
},
locale: {
weekdays: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
months: [
"一月",
"二月",
"三月",
"四月",
"五月",
"六月",
"七月",
"八月",
"九月",
"十月",
"十一月",
"十二月"
],
// name: "en",
// Now: "Now",
// "X-Scale": "Zoom-X",
// "Y-Scale": "Zoom-Y",
// "Task list width": "Task list",
// "Before/After": "Expand",
// "Display task list": "Task list"
}
};
export default {
name: "Gantt",
components: {
GanttElastic,
GanttHeader
},
data() {
return {
tasks,
options,
dynamicStyle: {},
lastId: 16
};
},
methods: {
addTask() {
this.tasks.push({
id: this.lastId++,
label:
'<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Yeaahh! you have added a task bro!</a>',
user:
'<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Awesome!</a>',
start: getDate(24 * 3),
duration: 1 * 24 * 60 * 60 * 1000,
percent: 50,
type: "project"
});
},
tasksUpdate(tasks) {
this.tasks = tasks;
},
optionsUpdate(options) {
this.options = options;
},
styleUpdate(style) {
this.dynamicStyle = style;
}
}
};
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment