This commit is contained in:
陈裕财
2024-08-24 23:24:30 +08:00
parent bcebbb9162
commit 5593271da3
3 changed files with 334 additions and 186 deletions

View File

@@ -147,5 +147,7 @@
</div>
</div>
<script type="module" src="./src/main.ts"></script>
<script src="dist/frappe-gantt.min.js"></script>
<link rel="stylesheet" href="dist/frappe-gantt.css">
</body>
</html>

View File

@@ -27,56 +27,56 @@
},
"dependencies": {
"@dataview/datav-vue3": "0.0.0-test.1672506674342",
"@element-plus/icons-vue": "^2.1.0",
"@form-create/component-wangeditor": "^3.1",
"@form-create/element-ui": "^3.1.29",
"@form-create/utils": "^3.1.23",
"@element-plus/icons-vue": "^2.3.1",
"@form-create/component-wangeditor": "^3.2.4",
"@form-create/element-ui": "^3.2.7",
"@form-create/utils": "^3.2.0",
"@iconify/iconify": "^3.1.1",
"@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^10.9.0",
"@vueuse/core": "^10.11.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
"@wangeditor/editor-for-vue": "^5.1.12",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "^1.6.7",
"axios": "^1.7.5",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"diagram-js": "^12.8.0",
"dayjs": "^1.11.13",
"diagram-js": "^12.8.1",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts": "^5.5.1",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.5.3",
"fast-xml-parser": "^4.3.2",
"fast-xml-parser": "^4.4.1",
"file-saver": "^2.0.5",
"gantt-schedule-timeline-calendar": "^3.37.5",
"highlight.js": "^11.9.0",
"frappe-gantt": "^0.6.1",
"highlight.js": "^11.10.0",
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"jsencrypt": "^3.3.2",
"lodash-es": "^4.17.21",
"min-dash": "^4.1.1",
"min-dash": "^4.2.1",
"mitt": "^3.0.1",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"pinia": "^2.2.2",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.4",
"qs": "^6.13.0",
"steady-xml": "^0.1.0",
"url": "^0.11.3",
"v-region": "^3.0.0",
"video.js": "^7.21.5",
"url": "^0.11.4",
"v-region": "^3.1.0",
"video.js": "^7.21.6",
"vue": "3.4.20",
"vue-clipboard3": "^2.0.0",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.9.1",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.3.0",
"vue-types": "^5.1.1",
"vue-router": "^4.4.3",
"vue-types": "^5.1.3",
"vue3-tree-org": "^4.2.2",
"vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1",
@@ -84,51 +84,51 @@
"xml-js": "^1.6.11"
},
"devDependencies": {
"@commitlint/cli": "^19.0.1",
"@commitlint/config-conventional": "^19.0.0",
"@iconify/json": "^2.2.187",
"@commitlint/cli": "^19.4.0",
"@commitlint/config-conventional": "^19.2.2",
"@iconify/json": "^2.2.241",
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@purge-icons/generated": "^0.9.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.21",
"@types/node": "^20.16.1",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.12",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@unocss/eslint-config": "^0.57.4",
"@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-legacy": "^5.3.1",
"@vitejs/plugin-vue": "^5.0.4",
"@types/qs": "^6.9.15",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@unocss/eslint-config": "^0.57.7",
"@unocss/transformer-variant-group": "^0.58.9",
"@vitejs/plugin-legacy": "^5.4.2",
"@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.17",
"autoprefixer": "^10.4.20",
"bpmn-js": "8.9.0",
"bpmn-js-properties-panel": "0.46.0",
"codemirror": "^5.60.0",
"codemirror": "^5.65.17",
"consola": "^3.2.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.22.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"jsonlint-mod": "^1.7.6",
"less": "^4.2.0",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss-html": "^1.6.0",
"lint-staged": "^15.2.9",
"postcss": "^8.4.41",
"postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"rollup": "^4.12.0",
"sass": "^1.69.5",
"stylelint": "^16.2.1",
"prettier": "^3.3.3",
"rimraf": "^5.0.10",
"rollup": "^4.21.0",
"sass": "^1.77.8",
"stylelint": "^16.8.2",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
"terser": "^5.28.1",
"terser": "^5.31.6",
"typescript": "5.3.3",
"unocss": "^0.58.5",
"unocss": "^0.58.9",
"unplugin-auto-import": "^0.16.7",
"unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.2",
@@ -139,8 +139,8 @@
"vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-top-level-await": "^1.3.1",
"vue-eslint-parser": "^9.3.2",
"vite-plugin-top-level-await": "^1.4.4",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^1.8.27"
},
"license": "MIT",

View File

@@ -1,148 +1,294 @@
<template>
<div>
<div class="toolbox">
<button @click="updateFirstRow">Update first row</button>
<button @click="changeZoomLevel">Change zoom level</button>
</div>
<div class="gstc-wrapper" ref="gstcElement"></div>
<div class="gantt-container">
<el-row>
<el-col :span="8">
<el-table
:data="tasks"
stripe
style="width: 100%"
row-key="id"
>
<el-table-column prop="name" width="160" label="任务名称" show-overflow-tooltip>
<template #default="scope">
<el-popover placement="right-start"
:width="100" show-after="200">
<template #reference>
{{ scope.row.name }}
</template>
<template #default>
<el-space wrap>
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button
size="small"
type="danger"
@click="handleDependencies(scope.$index, scope.row)"
>添加子任务</el-button>
<el-button
v-if="scope.$index !== 0"
size="small"
type="danger"
@click="handleMove(scope.$index, scope.row)"
>上移一行</el-button>
</el-space>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column label="时间">
<el-table-column prop="start" width="140" label="开始日期" show-overflow-tooltip />
<el-table-column prop="end" width="140" label="结束日期" show-overflow-tooltip />
<el-table-column prop="date" width="140" label="持续时间" show-overflow-tooltip />
<el-table-column prop="task" width="80" label="完成" show-overflow-tooltip />
</el-table-column>
</el-table>
</el-col>
<el-col :span="16">
<el-space wrap class="padding">
<el-button @click="changeViewMode('Quarter Day')">1/4天</el-button>
<el-button @click="changeViewMode('Half Day')">半天</el-button>
<el-button @click="changeViewMode('Day')"></el-button>
<el-button @click="changeViewMode('Week')"></el-button>
<el-button @click="changeViewMode('Month')"></el-button>
</el-space>
<div class="gantt-target"></div>
</el-col>
</el-row>
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-button type="primary" @click="changeViewMode">日期模式</el-button>
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-button type="primary" @click="handleAdd">新增</el-button>
</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 ItemResizing } from "gantt-schedule-timeline-calendar/dist/plugins/item-resizing.esm.min.js";
import { Plugin as ItemMovement } from "gantt-schedule-timeline-calendar/dist/plugins/item-movement.esm.min.js";
import { Plugin as Bookmarks } from "gantt-schedule-timeline-calendar/dist/plugins/time-bookmarks.esm.min.js";
import "gantt-schedule-timeline-calendar/dist/style.css";
import { ref, onMounted, onBeforeUnmount } from "vue";
// helper functions
function generateRows() {
/**
* @type { import("gantt-schedule-timeline-calendar").Rows }
*/
const rows = {};
for (let i = 0; i < 100; i++) {
const id = GSTC.api.GSTCID(i.toString());
rows[id] = {
id,
label: `Row ${i}`,
};
}
return rows;
}
function generateItems() {
/**
* @type { import("gantt-schedule-timeline-calendar").Items }
*/
const items = {};
let start = GSTC.api.date().startOf("day").subtract(6, "day");
for (let i = 0; i < 100; i++) {
const id = GSTC.api.GSTCID(i.toString());
const rowId = GSTC.api.GSTCID(Math.floor(Math.random() * 100).toString());
start = start.add(1, "day");
items[id] = {
id,
label: `Item ${i}`,
rowId,
time: {
start: start.valueOf(),
end: start.add(1, "day").endOf("day").valueOf(),
},
};
}
return items;
}
// main component
import { reactive, toRefs, onMounted,defineEmits } from "vue";
export default {
name: "GSTC",
setup() {
let gstc, state;
const gstcElement = ref(null);
const emitss = defineEmits(['dateChange','progressChange','barClick','viewChange'])
onMounted(() => {
/**
* @type { import("gantt-schedule-timeline-calendar").Config }
*/
const config = {
licenseKey:
"====BEGIN LICENSE KEY====\nXOfH/lnVASM6et4Co473t9jPIvhmQ/l0X3Ewog30VudX6GVkOB0n3oDx42NtADJ8HjYrhfXKSNu5EMRb5KzCLvMt/pu7xugjbvpyI1glE7Ha6E5VZwRpb4AC8T1KBF67FKAgaI7YFeOtPFROSCKrW5la38jbE5fo+q2N6wAfEti8la2ie6/7U2V+SdJPqkm/mLY/JBHdvDHoUduwe4zgqBUYLTNUgX6aKdlhpZPuHfj2SMeB/tcTJfH48rN1mgGkNkAT9ovROwI7ReLrdlHrHmJ1UwZZnAfxAC3ftIjgTEHsd/f+JrjW6t+kL6Ef1tT1eQ2DPFLJlhluTD91AsZMUg==||U2FsdGVkX1/SWWqU9YmxtM0T6Nm5mClKwqTaoF9wgZd9rNw2xs4hnY8Ilv8DZtFyNt92xym3eB6WA605N5llLm0D68EQtU9ci1rTEDopZ1ODzcqtTVSoFEloNPFSfW6LTIC9+2LSVBeeHXoLEQiLYHWihHu10Xll3KsH9iBObDACDm1PT7IV4uWvNpNeuKJc\npY3C5SG+3sHRX1aeMnHlKLhaIsOdw2IexjvMqocVpfRpX4wnsabNA0VJ3k95zUPS3vTtSegeDhwbl6j+/FZcGk9i+gAy6LuetlKuARjPYn2LH5Be3Ah+ggSBPlxf3JW9rtWNdUoFByHTcFlhzlU9HnpnBUrgcVMhCQ7SAjN9h2NMGmCr10Rn4OE0WtelNqYVig7KmENaPvFT+k2I0cYZ4KWwxxsQNKbjEAxJxrzK4HkaczCvyQbzj4Ppxx/0q+Cns44OeyWcwYD/vSaJm4Kptwpr+L4y5BoSO/WeqhSUQQ85nvOhtE0pSH/ZXYo3pqjPdQRfNm6NFeBl2lwTmZUEuw==\n====END LICENSE KEY====",
plugins: [TimelinePointer(), Selection(), ItemResizing(), ItemMovement(), Bookmarks()],
list: {
columns: {
data: {
[GSTC.api.GSTCID("id")]: {
id: GSTC.api.GSTCID("id"),
width: 60,
data: ({ row }) => GSTC.api.sourceID(row.id),
header: {
content: "ID",
},
},
[GSTC.api.GSTCID("label")]: {
id: GSTC.api.GSTCID("label"),
width: 200,
data: "label",
header: {
content: "Label",
},
},
},
},
rows: generateRows(),
const vueConfig = reactive({
tasks: [ // 表格数据
{
start: "2023-04-01",
end: "2023-04-08",
name: "测试任务1测试任务1测试任务1测试任务1测试任务1测试任务1测试任务1测试任务1测试任务1测试任务1",
id: "1",
progress: 26,
task: "50%",
date: 3
},
chart: {
items: generateItems(),
{
start: "2023-04-03",
end: "2023-04-06",
name: "测试任务2",
id: "2",
progress: 0,
task: "50%",
date: 3,
dependencies: '1'
},
};
state = GSTC.api.stateFromConfig(config);
globalThis.state = state;
const element = gstcElement.value;
if (element) {
gstc = GSTC({
element,
state,
});
}
globalThis.gstc = gstc;
{
start: "2023-04-04",
end: "2023-04-08",
name: "测试任务3",
id: "3",
progress: 0,
task: "50%",
date: 3,
dependencies: '1'
},
{
start: "2023-04-08",
end: "2023-04-09",
name: "测试任务4",
id: "4",
progress: 0,
task: "50%",
date: 3,
children: []
// dependencies: '2'
},
{
start: "2023-04-08",
end: "2023-04-10",
name: "测试任务5",
id: "5",
progress: 50,
task: "50%",
date: 3,
dependencies: '2'
}
],
gantt: null,
});
onBeforeUnmount(() => {
if (gstc) gstc.destroy();
});
function updateFirstRow() {
state.update(`config.list.rows.${GSTC.api.GSTCID("0")}`, (row) => {
row.label = "Changed dynamically";
return row;
let handleAdd = () => {
console.log("新增按钮点击");
vueConfig.tasks.push({
start: "2023-04-08",
end: "2023-04-10",
name: "测试任务6",
id: "6",
progress: 0,
task: "50%",
date: 3
// dependencies: '2'
});
}
function changeZoomLevel() {
state.update("config.chart.time.zoom", 21);
}
return {
gstcElement,
updateFirstRow,
changeZoomLevel,
createGantt();
};
},
let handleEdit = item => {
console.log("编辑按钮点击");
vueConfig.tasks.forEach(element => {
if (element.id === item.id) {
element.start = "2022-04-02";
element.end = "2022-04-07";
element.date = 5;
element.task = "60%";
}
});
createGantt();
};
let handleDependencies = (index, item) => {
console.log("添加子任务按钮点击");
console.log(index, item);
vueConfig.tasks.forEach(element => {
if (element.id === item.id) {
element.children.push({
start: "2022-04-01",
end: "2022-04-08",
name: "测试任务子任务1",
id: "8",
progress: 0,
task: "50%",
date: 3,
dependencies: "1"
});
}
});
createGantt();
};
let handleMove = (index, item) => {
console.log("上移一行按钮点击");
const tempItem = vueConfig.tasks.splice(index, 1);
vueConfig.tasks.splice(index - 1, 0, tempItem[0]);
createGantt();
};
let changeViewMode = (mode) => {
vueConfig.gantt.change_view_mode(mode)
};
let createGantt = () => {
const gantt = new Gantt(".gantt-target", vueConfig.tasks, {
on_click: function(task) {
console.log("双击操作", task);
let cb=(data)=>{
// 将数据同步回甘特图
}
emitss('barClick',task,cb)
},
on_date_change: function(task, start, end) {
debugger
vueConfig.tasks.forEach(element => {
if (element.id === task.id) {
element.start = start;
element.end = end;
element.data = end - start;
}
});
emitss('dateChange',task,start,end)
},
on_progress_change: function(task, progress) {
console.log(task, progress);
emit('progressChange',task,progress)
},
on_view_change: function(mode) {
debugger
console.log(mode);
emitss('viewChange',mode)
},
// view_mode: 'Day',
language: "zh",
header_height: 68,
column_width: 90,
step: 24,
view_modes: ["Quarter Day", "Half Day", "Day", "Week", "Month"],
bar_height: 22,
bar_corner_radius: 10, // bar 的圆角度
arrow_curve: 10, //连接子任务的线条曲线度
padding: 18,
view_mode: "Day", // header的日期类型
date_format: "YYYY-MM-DD", // 日期格式
custom_popup_html: function(task) {
return `
<div class="details-container padding">
<h5>${task.name}</h5>
<p>Expected to finish by ${task.end}</p>
<p>${task.progress}% completed!</p>
</div>
`;
}
});
vueConfig.gantt=gantt
};
onMounted(() => {
createGantt();
});
return {
...toRefs(vueConfig),
handleAdd,
createGantt,
handleEdit,
handleDependencies,
handleMove,
changeViewMode,
};
}
};
</script>
<style scoped>
.gstc-component {
margin: 0;
padding: 0;
}
.toolbox {
padding: 10px;
}
</style>
<style lang="scss">
// .gantt-container {
// background-color: transparent;
// width: 100%;
// overflow: hidden;
// margin-left: -1px;
// }
// ::v-deep .el-table .el-table__cell {
// height: 80px;
// }
// ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
// background: rgb(245, 245, 245);
// }
// ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td {
// background: rgb(245, 245, 245);
// }
.details-container{
background-color: aliceblue;
width: 200px;
}
// .el-button--text {
// margin-right: 15px;
// }
// .el-select {
// width: 300px;
// }
// .el-input {
// width: 300px;
// }
// .dialog-footer button:first-child {
// margin-right: 10px;
// }
</style>