v1.4.2 - enhancements
This commit is contained in:
6
npm-demo/.gitignore
vendored
Normal file
6
npm-demo/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
.dscode/
|
||||
.vscode/
|
||||
dist/
|
||||
.idea/
|
||||
*.log
|
||||
63
npm-demo/README.md
Normal file
63
npm-demo/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Gantt Demo App
|
||||
|
||||
基于 Vite + Vue3 的示例项目,用于本地调试 `jordium-gantt-vue3` 组件包。
|
||||
|
||||
## 调试方式
|
||||
支持两种:
|
||||
1. npm link (推荐开发阶段)
|
||||
2. 打包 tgz 后 file: 引用(已改为 link 模式,可回退)
|
||||
|
||||
## 使用 npm link 调试流程
|
||||
在本地库目录(例如:`D:/02 Project/01 久元鼎晟/300 框架集/02 Plugins/01 Gantt/npm-package`)执行:
|
||||
```powershell
|
||||
# 进入库源码根目录
|
||||
npm install
|
||||
npm run build:lib # 若需要生成 dist
|
||||
npm link # 全局注册该包
|
||||
```
|
||||
回到示例项目目录执行:
|
||||
```powershell
|
||||
npm uninstall jordium-gantt-vue3 -D -S 2>$null | Out-Null # 忽略不存在情形
|
||||
npm install
|
||||
npm link jordium-gantt-vue3
|
||||
npm run dev
|
||||
```
|
||||
如果已经 link 过,只需:
|
||||
```powershell
|
||||
npm run dev
|
||||
```
|
||||
修改库源码后,可在库目录重新 `npm run build:lib`,示例项目无需重新安装,只要刷新浏览器(若是构建产物路径不变)。
|
||||
|
||||
## 回退为 tgz 引用
|
||||
将 `package.json` 中:
|
||||
```json
|
||||
"jordium-gantt-vue3": "1.4.0"
|
||||
```
|
||||
改回:
|
||||
```json
|
||||
"jordium-gantt-vue3": "file:绝对路径/jordium-gantt-vue3-1.4.0.tgz"
|
||||
```
|
||||
然后:
|
||||
```powershell
|
||||
npm install --force
|
||||
```
|
||||
|
||||
## 运行
|
||||
```powershell
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
访问: http://localhost:5173/
|
||||
|
||||
## 占位组件
|
||||
当真实库无法加载时,显示 `StubGantt.vue` 占位以避免报错。
|
||||
|
||||
## 常见问题
|
||||
| 现象 | 处理 |
|
||||
|------|------|
|
||||
| Failed to resolve entry | 确认 dist 产物与 main/module 指向正确,重新 build:lib |
|
||||
| 样式缺失 | 确认是否需要单独引入 css,如 `import 'jordium-gantt-vue3/dist/style.css'` |
|
||||
| 代码未热更新 | 重新 build:lib 或使用源码 symlink + Vite optimizeDeps.exclude |
|
||||
|
||||
## License
|
||||
仅内部开发调试使用。
|
||||
12
npm-demo/index.html
Normal file
12
npm-demo/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Gantt Demo</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1302
npm-demo/package-lock.json
generated
Normal file
1302
npm-demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
npm-demo/package.json
Normal file
19
npm-demo/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "gantt-demo-app",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-plus": "^2.11.5",
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
15
npm-demo/src/App.vue
Normal file
15
npm-demo/src/App.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import GanttTest from './components/GanttTest.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-wrapper">
|
||||
<h1>Gantt 组件演示</h1>
|
||||
<GanttTest />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-wrapper { padding: 16px; font-family: system-ui, sans-serif; }
|
||||
h1 { font-size: 20px; margin-bottom: 12px; }
|
||||
</style>
|
||||
345
npm-demo/src/components/GanttTest.vue
Normal file
345
npm-demo/src/components/GanttTest.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { GanttChart } from 'jordium-gantt-vue3'
|
||||
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
||||
|
||||
const tasks = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '项目启动',
|
||||
startDate: '2025-10-30',
|
||||
endDate: '2025-11-5',
|
||||
progress: 100,
|
||||
department: '管理部',
|
||||
departmentCode: 'D001',
|
||||
type: 'task',
|
||||
},
|
||||
])
|
||||
|
||||
const milestones = ref([
|
||||
{
|
||||
id: 101,
|
||||
name: '项目立项',
|
||||
startDate: '2025-10-29',
|
||||
type: 'milestone',
|
||||
icon: 'diamond',
|
||||
},
|
||||
])
|
||||
|
||||
const customMessages = {
|
||||
'zh-CN': {
|
||||
department: '部门',
|
||||
departmentCode: '部门编号',
|
||||
},
|
||||
'en-US': {
|
||||
department: 'Department',
|
||||
departmentCode: 'Department Code',
|
||||
},
|
||||
}
|
||||
// const tasks = ref([])
|
||||
|
||||
// const milestones = ref([])
|
||||
const showAddTaskDrawer = ref(false)
|
||||
const showAddMilestoneDialog = ref(false)
|
||||
|
||||
// 定义可动态配置的列
|
||||
const availableColumns = ref<TaskListColumnConfig[]>([
|
||||
{ key: 'startDate', label: '开始日期', visible: true },
|
||||
{ key: 'endDate', label: '结束日期', visible: true },
|
||||
{ key: 'progress', label: '进度', visible: true },
|
||||
{ key: 'department', label: '部门', visible: true, width: 120 },
|
||||
{ key: 'departmentCode', label: '部门编号', visible: true },
|
||||
])
|
||||
|
||||
// TaskList宽度配置示例
|
||||
const taskListConfig = {
|
||||
defaultWidth: 400, // 默认展开宽度400px(默认320px)
|
||||
minWidth: 300, // 最小宽度300px(默认280px)
|
||||
maxWidth: 1200, // 最大宽度1200px(默认1160px)
|
||||
columns: availableColumns.value,
|
||||
}
|
||||
|
||||
// toolbar配置示例
|
||||
const toolbarConfig: ToolbarConfig = {
|
||||
showAddTask: true, // 显示添加任务按钮
|
||||
showAddMilestone: true, // 显示添加里程碑按钮
|
||||
showTodayLocate: true, // 显示定位到今天按钮
|
||||
showExportCsv: true, // 显示导出CSV按钮
|
||||
showExportPdf: true, // 显示导出PDF按钮
|
||||
showLanguage: true, // 显示语言切换按钮
|
||||
showTheme: true, // 显示主题切换按钮
|
||||
showFullscreen: true, // 显示全屏按钮
|
||||
showTimeScale: true, // 显示时间刻度按钮组
|
||||
timeScaleDimensions: [ // 显示所有时间刻度维度
|
||||
'hour', 'day', 'week', 'month', 'quarter', 'year',
|
||||
],
|
||||
defaultTimeScale: 'week', // 默认选中周视图
|
||||
showExpandCollapse: false, // 显示展开/折叠按钮
|
||||
}
|
||||
|
||||
const newTask = ref({
|
||||
name: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
})
|
||||
|
||||
const addTask = () => {
|
||||
tasks.value.push({
|
||||
id: tasks.value.length + 1,
|
||||
name: newTask.value.name,
|
||||
startDate: newTask.value.startDate,
|
||||
endDate: newTask.value.endDate,
|
||||
progress: 0,
|
||||
})
|
||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
||||
showAddTaskDrawer.value = false
|
||||
}
|
||||
|
||||
const addMilestone = () => {
|
||||
milestones.value.push({
|
||||
id: milestones.value.length + 1,
|
||||
name: newTask.value.name,
|
||||
startDate: newTask.value.startDate,
|
||||
progress: 0,
|
||||
type: 'milestone',
|
||||
icon: 'diamond',
|
||||
})
|
||||
console.log('milestones: ', milestones.value)
|
||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
||||
showAddMilestoneDialog.value = false
|
||||
}
|
||||
|
||||
const onTaskDblclick = (task) => {
|
||||
alert(`双击任务: ${task.name}`)
|
||||
}
|
||||
const onMilestoneDblclick = (milestone) => {
|
||||
alert(`双击里程碑: ${milestone.name}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="height: 600px;">
|
||||
<GanttChart
|
||||
:tasks="tasks"
|
||||
:milestones="milestones"
|
||||
:task-list-config="taskListConfig"
|
||||
:toolbar-config="toolbarConfig"
|
||||
:use-default-drawer="false"
|
||||
:use-default-milestone-dialog="false"
|
||||
:locale-messages="customMessages"
|
||||
@add-task="showAddTaskDrawer = true"
|
||||
@add-milestone="showAddMilestoneDialog = true"
|
||||
@task-double-click="onTaskDblclick"
|
||||
@milestone-double-click="onMilestoneDblclick"
|
||||
>
|
||||
</GanttChart>
|
||||
</div>
|
||||
|
||||
<!-- 自定义抽屉组件 (原生HTML替代 el-drawer) -->
|
||||
<div v-if="showAddTaskDrawer" class="drawer-overlay" @click="showAddTaskDrawer = false">
|
||||
<div class="drawer-container" @click.stop>
|
||||
<div class="drawer-header">
|
||||
<h3>自定义添加任务组件</h3>
|
||||
<button class="close-btn" @click="showAddTaskDrawer = false">×</button>
|
||||
</div>
|
||||
|
||||
<div class="drawer-body">
|
||||
<div class="form-item">
|
||||
<label>任务名称:</label>
|
||||
<input v-model="newTask.name" type="text" placeholder="请输入任务名称" />
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>开始日期:</label>
|
||||
<input v-model="newTask.startDate" type="date" />
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>结束日期:</label>
|
||||
<input v-model="newTask.endDate" type="date" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-footer">
|
||||
<button class="btn btn-primary" @click="addTask">确定</button>
|
||||
<button class="btn btn-default" @click="showAddTaskDrawer = false">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义Dialog组件基于element plus -->
|
||||
<el-dialog
|
||||
v-model="showAddMilestoneDialog"
|
||||
title="自定义添加里程碑组件 - Element Plus"
|
||||
width="400px"
|
||||
@close="newTask = { name: '', startDate: '', endDate: '' }"
|
||||
>
|
||||
<template #default>
|
||||
<div class="form-item">
|
||||
<label>任务名称:</label>
|
||||
<el-input v-model="newTask.name" placeholder="请输入任务名称" />
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>日期:</label>
|
||||
<el-date-picker v-model="newTask.startDate" type="date" value-format="YYYY-MM-DD" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="addMilestone">确定</el-button>
|
||||
<el-button @click="showAddMilestoneDialog = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 抽屉遮罩层 */
|
||||
.drawer-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
/* 抽屉容器 */
|
||||
.drawer-container {
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
background: white;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
animation: slideIn 0.3s;
|
||||
}
|
||||
|
||||
/* 抽屉头部 */
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.drawer-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 抽屉主体 */
|
||||
.drawer-body {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-item input:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
/* 抽屉底部 */
|
||||
.drawer-footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #dcdfe6;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #66b1ff;
|
||||
border-color: #66b1ff;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background: white;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.btn-default:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
</style>
|
||||
29
npm-demo/src/components/StubGantt.vue
Normal file
29
npm-demo/src/components/StubGantt.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
tasks: { type: Array, default: () => [] },
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="stub-gantt">
|
||||
<p style="margin:0 0 8px;font-weight:600;">(占位) jordium-gantt-vue3 未正确打包,显示 Stub 组件。</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>任务ID</th><th>名称</th><th>开始</th><th>结束</th><th>进度</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="t in tasks" :key="t.id">
|
||||
<td>{{ t.id }}</td>
|
||||
<td>{{ t.text }}</td>
|
||||
<td>{{ t.start }}</td>
|
||||
<td>{{ t.end }}</td>
|
||||
<td>{{ (t.progress*100).toFixed(0) }}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.stub-gantt { font-size:12px; background:#fffbe6; padding:8px; border:1px dashed #faad14; }
|
||||
.stub-gantt table { width:100%; border-collapse: collapse; }
|
||||
.stub-gantt th, .stub-gantt td { border:1px solid #ddd; padding:4px; }
|
||||
</style>
|
||||
39
npm-demo/src/components/ThumbnailHtmlContent.vue
Normal file
39
npm-demo/src/components/ThumbnailHtmlContent.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
task: any
|
||||
type: string // 'task-row' | 'task-bar'
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
// console.error('props', props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="html-content-card">
|
||||
<div v-if="props.type === 'task-row'" class="task-row" v-html="props.task.thumbnail" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.html-content-card {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.task-row {
|
||||
flex: 1;
|
||||
padding: 0 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-bar {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
9
npm-demo/src/main.js
Normal file
9
npm-demo/src/main.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
6
npm-demo/vite.config.js
Normal file
6
npm-demo/vite.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
});
|
||||
Reference in New Issue
Block a user