Open
Description
<template>
<div>
<div class="toolbox">
<el-button type="primary" plain @click="exports" style="margin-left: 20px">导出</el-button>
<el-button type="primary" plain @click="handleAdd" style="margin-left: 20px">增加</el-button>
<div class="toolbox-item" style="margin: 0 20px">
<el-select v-model="value" placeholder="请选择" id="zoom" @change="zoomChangeSelect" style="width: 100px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="toolbox-item">
<el-slider v-model="slider" id="zoom-range" :min="13" :max="25" @change='zoomChangeRange' style="width:200px" :step="3"></el-slider>
</div>
</div>
<div class="gstc-wrapper" ref="gstc" id="gstc"></div>
</div>
</template>
<script>
import GSTC from "gantt-schedule-timeline-calendar/dist/gstc.wasm.esm.min.js";
import { Plugin as TimelinePointer } from "gantt-schedule-timeline-calendar/dist/plugins/timeline-pointer.esm.min.js";
import { Plugin as Selection } from "gantt-schedule-timeline-calendar/dist/plugins/selection.esm.min.js";
import { Plugin as Bookmarks } from "gantt-schedule-timeline-calendar/dist/plugins/time-bookmarks.esm.min.js";
import { Plugin as ExportImage } from "gantt-schedule-timeline-calendar/dist/plugins/export-image.esm.min.js";
import { Plugin as ExportPDF } from "gantt-schedule-timeline-calendar/dist/plugins/export-pdf.esm.min.js";
import { Plugin as ProgressBar } from 'gantt-schedule-timeline-calendar/dist/plugins/progress-bar.esm.min.js';
import { Plugin as ItemMovement } from 'gantt-schedule-timeline-calendar/dist/plugins/item-movement.esm.min.js';
import { Plugin as ItemResizing } from 'gantt-schedule-timeline-calendar/dist/plugins/item-resizing.esm.min.js';
import "gantt-schedule-timeline-calendar/dist/style.css";
import "gantt-schedule-timeline-calendar/examples/reset.css";
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
let gstc, state;
let rows = {}
let items = {}
export default {
name: "GSTC",
props: {
rowsFromDB:{
type: Array,
required: true,
default: () => []
},
itemsFromDB:{
type: Array,
required: true,
default: () => []
},
columnsFromDB:{
type: Array,
required: true,
default: () => []
},
itemResizingOptions:{
type: Object,
required: true
},
},
data() {
return {
// rowsFromDB:[
// {
// id: "1", //行标识符
// label: "Row 1",
// },
// {
// id: "2",
// label: "Row 2",
// },
// {
// id: "3",
// label: "Row 3",
// },
// ],
// itemsFromDB:[
// {
// id: "1", // 商品 ID 由 GSTCID 函数包装
// label: "Item 1", // 项目标签
// rowId: "1", // 此项应出现在哪一行
// time: {
// // zoom: 21,
// start: GSTC.api.date("2020-01-01 09:10:00").startOf("minute").valueOf(),
// end: GSTC.api.date("2020-01-01 10:15:00").endOf("minute").valueOf(),
// // leftGlobal //我们当前距离左侧的时间(以毫秒为单位) - 当您滚动时,此值将会更新
// // leftGlobalDate // 与"leftGlobal"相同,但为dayjs日期而不是毫秒
// // rightGlobal // 我们当前距离右侧的时间(以毫秒为单位) - 当您滚动时,此值将会更新
// // rightGlobalDate // 与"rightGlobal"相同,但为 dayjs 日期而不是毫秒
// // onLevelDates // 您可以使用函数数组来控制日历中显示的日期
// // onCurrentViewLevelDates // 与“onLevelDates”相同,但仅限于当前显示的日期 - 水平滚动移动时触发(见下面的通知)
// // onDate // 创建新日历日期时将触发的函数数组
// },
// // classNames:['additional-custom-class'], // 您想要添加到当前项目的 css 类数组,函数也应该返回一个 classNames 数组({item, vido})=>['my-item-class']
// style: { background: this.getRandomColor() }, // 你可以定义项目的样式,例如{background:'red'}
// // linkedWith: "1" // 链接的其他项目数组,当您调整大小/移动其中一个时,另一个将跟随
// // height // 项目高度
// // top // 行内顶部偏移量
// // gap // 顶部和底部项目间隙
// // minWidth // 最小显示宽度
// // dependant // 其他依赖项的数组
// // overlap // 如果您希望项目与其他项目重叠 - 对于所有项目,请将此处留空,并将默认项目重叠选项设置为 true
// description: 'Lorem ipsum dolor sit amet',
// progress:20,
// },
// {
// id: "2",
// label: "Item 2",
// rowId: "1",
// time: {
// start: GSTC.api.date("2020-01-01 09:10:00").startOf("minute").valueOf(),
// end: GSTC.api.date("2020-01-01 09:35:00").endOf("minute").valueOf(),
// },
// description: 'Lorem ipsum dolor sit amet',
// style: { background: this.getRandomColor() },
// progress:30,
// },
// {
// id: "3",
// label: "Item 3",
// rowId: "2",
// time: {
// start: GSTC.api.date("2020-01-02 08:08:00").startOf("minute").valueOf(),
// end: GSTC.api.date("2020-01-02 09:15:00").endOf("minute").valueOf(),
// },
// description: 'Lorem ipsum dolor sit amet',
// style: { background: this.getRandomColor() },
// progress:40,
// },
// ],
// columnsFromDB:[
// {
// id: "id", //列的 ID
// data: ({ row }) => GSTC.api.sourceID(row.id), //string| function 因为string它是一个应该存在于行配置中的属性名称,并且将显示相应的值,如果数据是,function它将以{row, vido}对象作为参数执行 - 该函数应该返回一个字符串或lit-html模板
// sortable: ({ row }) => Number(GSTC.api.sourceID(row.id)), // 如果这是一个string则应该包含对行进行排序所需的行字段的名称
// width: 80, // 列宽
// // 列标题配置如下所述
// header: {
// content: "ID", //string|lit-html 此标题的标签可以是简单字符串或lit-html模板
// },
// hidden:false, // 隐藏具有此属性的列
// },
// {
// id: "label",
// data: "label",
// sortable: "label",
// isHTML: false, // 如果设置为 truedata选项将会呈现为 HTML,所以要小心,不要让用户注入任何不安全的东西!
// width: 230,
// header: {
// content: "Label",
// },
// },
// {
// id: "action",
// sortable: "action",
// isHTML: false, // 如果设置为 truedata选项将会呈现为 HTML,所以要小心,不要让用户注入任何不安全的东西!
// width: 230,
// header: {
// content: "操作",
// },
// data({ row,vido }) {
// return vido.html`<el-button type="text" class="split-button" data-id="${row.id}" style="margin-right: 10px;cursor: pointer">拆分</el-button>
// <el-button type="text" class="remove-button" data-id="${row.id}" style="cursor: pointer">删除</el-button>`;
// }
// },
// ],
// 项目是否可以上下移动
itemMovementOptions:{
// enabled: false,
snapToTime: {
start({ startTime }) {
return startTime;
},
end({ endTime }) {
return endTime;
},
},
autoScroll: {
speed: {
horizontal: 1, // turn off horizontal auto scroll
vertical: 1, // turn off vertical auto scroll
},
},
events:{
onMove({ items }) {
return items.before.map((beforeMovementItem, index) => {
const afterMovementItem = items.after[index];
const myItem = GSTC.api.merge({}, afterMovementItem);
if(myItem.resizable==false){
myItem.rowId = !beforeMovementItem.rowId;
}else{
myItem.rowId = beforeMovementItem.rowId;
}
return myItem;
});
},
onEnd:({ items })=>{
this.moveEnd(items)
return items.after;
},
},
},
// 项目在同一行是否可以发生碰撞
// itemResizingOptions:{
// enabled: true,
// // 项目前后拖动不按单元格拖动
// snapToTime: {
// start({ startTime }) {
// return startTime;
// },
// end({ endTime }) {
// return endTime;
// },
// },
// events:{
// onEnd({ items }) {
// this.resize(items)
// console.log(items,'items')
// console.log('Resizing done', items.after);
// return items.after;
// },
// },
// },
calendarLevel0:[
{
zoomTo: 13,
period: 'minute',
main: true,
periodIncrement: 15,
format({ timeStart }) {
return timeStart.format('HH:mm');
},
},
{
zoomTo: 16,
period: 'hour',
main: true,
periodIncrement: 1,
format({ timeStart }) {
return timeStart.format('HH:mm');
},
},
{
zoomTo: 19,
period: 'day',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-medium'],
format({ timeStart, className, vido }) {
return vido.html`<span class="${className}-content gstc-date-bold">${timeStart.format(
'DD'
)}</span> <span class="${className}-content gstc-date-thin">${timeStart.format('dddd')}19</span>`;
},
},
{
zoomTo: 22,
period: 'week',
main: true,
periodIncrement: 1,
format({ timeStart, timeEnd, className, vido }) {
return vido.html`<div class="${className}-content gstc-date-top">${timeStart.format('DD')} - ${timeEnd.format(
'DD'
)}</div><div class="${className}-content gstc-date-small gstc-date-thin">${timeStart.format(
'ddd'
)} - ${timeEnd.format('dd')}</div>`;
},
},
{
zoomTo: 25,
period: 'month',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-month-level-1'],
format({ timeStart, vido, className }) {
return vido.html`<div class="${className}-content gstc-date-top">${timeStart.format(
'MMM'
)}</div><div class="${className}-content gstc-date-small gstc-date-bottom">${timeStart.format('MM')}</div>`;
},
},
{
zoomTo: 27,
period: 'month',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-vertical'],
format({ timeStart, className, vido }) {
return vido.html`<div class="${className}-content gstc-date-top">${timeStart.format(
'MM'
)}</div><div class="${className}-content gstc-date-extra-small">${timeStart.format('MMM')}</div>`;
},
},
{
zoomTo: 28,
period: 'year',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-big'],
format({ timeStart }) {
return timeStart.format('YYYY');
},
},
{
zoomTo: 29,
period: 'year',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-medium'],
format({ timeStart }) {
return timeStart.format('YYYY');
},
},
{
zoomTo: 30,
period: 'year',
main: true,
periodIncrement: 1,
classNames: ['gstc-date-medium'],
format({ timeStart }) {
return timeStart.format('YY');
},
},
{
zoomTo: 100,
period: 'year',
main: true,
periodIncrement: 1,
format() {
return null;
},
},
],
top:[
{
zoomTo: 17,
period: "day",
periodIncrement: 1,
classNames: ["gstc-date-medium gstc-date-left"],
format({ timeStart }) {
return timeStart.format("YYYY-MM-DD (dddd)");
},
},
{
zoomTo: 23,
period: "month",
periodIncrement: 1,
format({ timeStart }) {
return timeStart.format("YYYY-MM");
},
},
{
zoomTo: 24,
period: "month",
periodIncrement: 1,
format({ timeStart }) {
return timeStart.format("YYYY-MM");
},
},
{
zoomTo: 25,
period: "month",
periodIncrement: 1,
format({ timeStart }) {
return timeStart.format("YYYY-MM");
},
},
{
zoomTo: 27,
period: "year",
periodIncrement: 1,
format({ timeStart }) {
return timeStart.format("YYYY");
},
},
{
zoomTo: 100,
period: "year",
periodIncrement: 1,
format() {
return null;
},
},
],
options: [
{
value: '13',
label: '刻视图'
}, {
value: '16',
label: '时视图'
}, {
value: '19',
label: '日视图'
}, {
value: '22',
label: '周视图'
}, {
value: '25',
label: '月视图'
}
],
value: '13',
slider:13,
from:null,
to:null,
}
},
mounted() {
// this.$EventBus.$on("gstcData", (columnsFromDB, rowsFromDB, itemsFromDB) => {
// this.gstcInit(columnsFromDB, rowsFromDB, itemsFromDB)
// });
},
beforeDestroy(){
//移除监听事件
this.$EventBus.$off("gstcData")
},
methods: {
gstcInit(columnsFromDB, rowsFromDB, itemsFromDB){
console.log(rowsFromDB,'rowsFromDB')
console.log(itemsFromDB,'itemsFromDB')
this.updateItemColors();
const config = {
licenseKey:'23',
plugins: [TimelinePointer(), Selection(), Bookmarks(),ExportImage(), ExportPDF(),ProgressBar(), ItemResizing(this.itemResizingOptions), ItemMovement(this.itemMovementOptions),],
list: {
columns: {
data: GSTC.api.fromArray(columnsFromDB),
},
// rows: GSTC.api.fromArray(rowsFromDB),
rows: rows,
},
chart: {
// items: GSTC.api.fromArray(itemsFromDB),
items: items,
calendarLevels:[this.top,this.calendarLevel0],
item: {
height: 50,
},
},
locale: {
name: "zh",
Now: "Now",
weekdays:["周日","周一","周二","周三","周四","周五","周六"],
months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
},
actions: {
'chart-timeline-items-row-item': [this.itemTippy],
},
scroll: {
// 甘特图水平滚动
horizontal: {
width: 20,
minInnerSize: 40,
multiplier: 3,
precise: false,
byPixels: false,
},
// 甘特图垂直滚动
vertical: {
width: 20,
minInnerSize: 40,
multiplier: 3,
precise: false,
byPixels: false,
},
},
slots: {
'chart-timeline-items-row-item': { content: [this.itemSlot] },
},
};
// 从配置对象生成GSTC状态
state = GSTC.api.stateFromConfig(config);
state.update('config.chart.time.zoom', this.slider);
// 创建实例并挂载组件
gstc = GSTC({
element: this.$refs.gstc,
state,
});
globalThis.gstc = gstc;
globalThis.state = state;
this.setupEventListeners();
},
setupEventListeners() {
// 给动态渲染的按钮绑定事件
document.querySelectorAll('.split-button').forEach(button => {
button.addEventListener('click', (event) => {
const rowId = event.target.getAttribute('data-id');
this.handleSplit(rowId); // 手动调用 handleSplit 方法
});
});
document.querySelectorAll('.remove-button').forEach(button => {
button.addEventListener('click', (event) => {
const rowId = event.target.getAttribute('data-id');
this.handleRemove(rowId); // 手动调用 handleRemove 方法
});
});
},
handleSplit(rowId) {
this.$emit('split',rowId)
// 执行拆分操作
},
handleRemove(rowId){
this.$emit('remove',rowId)
},
// 检查时间是否重叠
checkTimeOverlap(itemA, itemB) {
return itemA.time.start < itemB.time.end && itemB.time.start < itemA.time.end;
},
// 设置任务条颜色为红色(如果重叠)
updateItemColors() {
// 遍历所有的项目
for (let i = 0; i < this.itemsFromDB.length; i++) {
const itemA = this.itemsFromDB[i];
// 对于每个项目,检查和其他同一行的项目是否有时间重叠
for (let j = i + 1; j < this.itemsFromDB.length; j++) {
const itemB = this.itemsFromDB[j];
if(itemA.employeeName&&itemB.employeeName){
if (itemA.employeeId === itemB.employeeId && this.checkTimeOverlap(itemA, itemB)) {
// 如果时间重叠,设置为红色
itemA.style = { background: '#E74C3C' };
itemB.style = { background: '#E74C3C' };
}
}
}
}
},
/**
* @description:zoom滑块回调事件
*/
zoomChangeRange(ev) {
state.update('config.chart.time.zoom', ev);
},
/**
* @description:zoom选择回调事件
*/
zoomChangeSelect(ev) {
state.update('config.chart.time.zoom', ev);
},
/**
* @description:任务条显示toolTip信息方法
*/
itemTippy(element, data) {
let _this = this
_this.setTippyContent(element, data)
return {
update(element, data) {
if (element._tippy) element._tippy.destroy();
_this.setTippyContent(element, data);
},
destroy(element, data) {
if (element._tippy) element._tippy.destroy();
},
};
},
setTippyContent(element, data) {
if (!gstc) return
if ((!data || !data.item) && element._tippy) return element._tippy.destroy()
// const itemData = gstc.api.getItemData(data.item.id)
const itemData = data.itemData
if (!itemData) return element._tippy.destroy()
if (itemData.detached && element._tippy) return element._tippy.destroy()
if (!itemData.detached && !element._tippy) tippy(element, { trigger: 'mouseenter click' })
if (!element._tippy) return
const startDate = itemData.time.startDate
const endDate = itemData.time.endDate
const remark = data.item.remark?data.item.remark:''
const tooltipContent = `
工序:${data.item.technique}
型号:${data.item.itemCode}
操作工:${data.item.employee}
任务量:${data.item.taskLoadQty}
时间:${startDate.format('YYYY-MM-DD HH:mm')}至 ${endDate.format('YYYY-MM-DD HH:mm')}
备注:${remark}
`
element._tippy.setContent(tooltipContent)
},
/**
* @description:导出为PDF
*/
exports(){
gstc.api.plugins.ExportPDF.download('人员排班');
},
/**
* @description:任务条插槽
*/
itemSlot(vido, props) {
const { html, onChange, update } = vido;
let imageSrc = '';
let duration = '';
let employee = '';
onChange((newProps) => {
props = newProps;
if (!props || !props.item) return;
imageSrc = props.item.img;
duration = props.item.duration;
employee = props.item.employee;
update();
});
return (content) => {
if (!props || !props.item) return content;
return html`
<div class="item-text">
<div class="item-label">${employee}</div>
<div class="item-description" style="font-size:11px;margin-top:2px;color:#fffffff0;line-height:1em;">
${duration}
</div>
</div>`;
};
},
generateNewRows(row) {
console.log(row,'generateNewRows')
// let rows = {};
for (let i = 0; i < row.length; i++) {
const id = GSTC.api.GSTCID(String(this.rowsFromDB[i].id));
// const id = String(i);
rows[id] = {
id,
employeeName: row[i].employeeName,
};
}
return rows;
},
generateNewItems(item) {
console.log(item,'generateNewItems')
let rowsIds = Object.keys(rows);
console.log(rowsIds,'rowsIds')
let dateIncrement = 0;
for (let i = 0; i < item.length; i++) {
let rowId = GSTC.api.GSTCID(String(item[i].rowId));
let id = GSTC.api.GSTCID(String(item[i].id));
if (dateIncrement >= 30) dateIncrement = 0;
const startTime = item[i].time.start;
const endTime = item[i].time.end;
items[id] = {
id,
rowId,
label: `Item ${i + 1}`,
time: {
start: startTime,
end: endTime,
},
};
dateIncrement++;
}
return items;
},
handleAdd(){
console.log(this.generateNewRows(),'this.generateNewRows()')
console.log(this.generateNewItems(),'this.generateNewItems()')
state.update('config', (config) => {
config.list.rows = this.generateNewRows();
config.chart.items = this.generateNewItems();
return config;
});
},
add(row,item){
console.log(this.generateNewRows(row),'rows')
console.log(this.generateNewItems(item),'items')
console.log(state,'state')
state.update('config', (config) => {
config.list.rows = this.generateNewRows(row);
config.chart.items = this.generateNewItems(item);
return config;
});
setTimeout(()=> {
this.gstcInit(this.columnsFromDB, this.generateNewRows(row), this.generateNewItems(item))
},0)
},
// 增加进度条
addItems(item){
state.update(`config.list.rows.${item.id}`, item);
state.update(`config.chart.items.${item.id}`, item);
},
// 增加行
addRows(item){
state.update(`config.list.rows.${item.id}`, item);
},
// 删除进度条
delItems(item){
const selectedItems = [item]
state.update('config.chart.items', (items) => {
for (const item of selectedItems) {
delete items[item.id];
}
return items;
});
},
// 点击行跳转至当前任务
scrollToItem(row) {
const api = gstc.api;
const firstItem = gstc.state.get(`config.chart.items.${row.id}`);
api.scrollToTime(firstItem.time.start, false);
},
// 进度条整个拖拽回调
moveEnd(items){
this.$emit('moveEnd',items.after)
},
},
};
</script>
<style scoped>
.gstc-wrapper {
margin: 20px;
}
.toolbox {
display: flex;
align-items: center;
}
::v-deep .gstc__chart-timeline-items-row-item {
border-radius: 4px !important;
}
</style>
The add method in the code
Error message:Item with id "gstcid-1974152527788326943" does not exists in "$data.chart.items". TIP: For performance reasons, state is mutable, so try not to modify items from the "state.get" method before you copy them (with "gstc.api.clone(items)" for example).
Metadata
Assignees
Labels
No labels