@mx-sose-front/mx-sose-graph 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/edgeWorker-b57ca007.js +2 -0
- package/dist/assets/edgeWorker-b57ca007.js.map +1 -0
- package/dist/index.d.ts +633 -30
- package/dist/index.esm.js +8728 -4734
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Common/Tree.vue +451 -0
- package/src/components/Common/index.ts +2 -0
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +1 -2
- package/src/components/Edge/Edge.vue +172 -169
- package/src/components/Gantt/Gantt.vue +1544 -0
- package/src/components/GanttContextMenu/GanttContextMenu.vue +304 -0
- package/src/components/InteractionLayer.vue +343 -147
- package/src/components/Matrix/Matrix.vue +828 -0
- package/src/components/Matrix/index.ts +168 -0
- package/src/components/Shape/ConceptualRole.vue +2 -34
- package/src/components/Table/Table.vue +970 -0
- package/src/constants/edgeShapeKeys.ts +8 -5
- package/src/constants/index.ts +259 -45
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useChartRowSelection.ts +456 -0
- package/src/hooks/useResize.ts +2 -2
- package/src/hooks/useVirtualScroll.ts +258 -0
- package/src/index.ts +1 -1
- package/src/render/shape-renderer.ts +62 -2
- package/src/statics/icons/childIcons//345/221/275/344/273/244@3x.png +0 -0
- package/src/statics/icons/childIcons//346/210/230/347/225/245/346/246/202/345/277/265/350/241/250@3x.png +0 -0
- package/src/statics/icons/childIcons//346/216/247/345/210/266@3x.png +0 -0
- package/src/statics/icons/createMenu/down.png +0 -0
- package/src/statics/icons/createMenu/remove.png +0 -0
- package/src/statics/icons/createMenu/up.png +0 -0
- package/src/store/graphStore.ts +217 -44
- package/src/types/index.ts +86 -4
- package/src/utils/batchAutoExpand.ts +9 -10
- package/src/utils/containers.ts +72 -17
- package/src/utils/contextMenuUtils.ts +7 -7
- package/src/utils/dateUtils.ts +160 -0
- package/src/utils/diagram.ts +10 -8
- package/src/utils/drag.ts +6 -5
- package/src/utils/edgeUtils.ts +344 -427
- package/src/utils/edgeWorker.ts +471 -0
- package/src/utils/hittest.ts +37 -38
- package/src/utils/index.ts +3 -0
- package/src/utils/keyboardUtils.ts +5 -5
- package/src/utils/packageOutline.ts +96 -0
- package/src/utils/rafThrottle.ts +162 -0
- package/src/utils/workerManager.ts +335 -0
- package/src/view/graph.vue +47 -33
- /package/src/statics/icons/childIcons//346/210/230/347/225/{245@3x.png" → 245/345/261/202@3x.png"} +0 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="gantt-container" @click="onContainerClick">
|
|
3
|
+
<!-- 左侧表格 -->
|
|
4
|
+
<div class="gantt-left" :class="{ 'with-search-bar': showSearchBar }" ref="tableContainerRef" @scroll="() => syncVirtualViewportState()">
|
|
5
|
+
<table class="gantt-table" :style="{ width: tableWidth + 'px' }">
|
|
6
|
+
<!-- 用 colgroup 统一控制整列宽度 -->
|
|
7
|
+
<colgroup>
|
|
8
|
+
<col v-for="col in displayColumns" :key="col.configId"
|
|
9
|
+
:style="{ width: getColumnWidth(col.configId) + 'px', }" />
|
|
10
|
+
</colgroup>
|
|
11
|
+
<thead>
|
|
12
|
+
<tr class="header-row-2">
|
|
13
|
+
<th v-for="col in displayColumns" :key="col.id" class="col-date">
|
|
14
|
+
<div class="th-content">
|
|
15
|
+
<div v-if="col.configId == 'index'" style="text-align: center;">{{ col.name }}</div>
|
|
16
|
+
<div v-else class="th-label">{{ col.name }}</div>
|
|
17
|
+
<div v-if="col.configId !== 'index'" class="col-resizer"
|
|
18
|
+
@mousedown="startColumnResize(col.configId, $event)"></div>
|
|
19
|
+
</div>
|
|
20
|
+
</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
|
|
24
|
+
<tbody>
|
|
25
|
+
<!-- 上方占位空间 -->
|
|
26
|
+
<tr v-if="virtualTopSpacerHeight > 0" class="table-spacer-row" aria-hidden="true">
|
|
27
|
+
<td :colspan="visibleColumnSpan">
|
|
28
|
+
<div class="table-spacer" :style="{ height: virtualTopSpacerHeight + 'px' }"></div>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
|
|
32
|
+
<!-- 虚拟行 -->
|
|
33
|
+
<tr v-for="row in virtualRows" :key="row.item.id" class="gantt-row"
|
|
34
|
+
:class="{ 'row-selected': selectedRowIds.has(row.item.id) }"
|
|
35
|
+
@click.stop="selectRow(row.item.id, $event)" @contextmenu="handleContextMenu(row.item, $event)">
|
|
36
|
+
<td v-for="col in displayColumns" :key="col.configId" class="col-date"
|
|
37
|
+
:title="getDisplayCellValue(row.item, col)"
|
|
38
|
+
@dblclick.stop="col.configId !== 'index' && openCellEditor(row.item, col)">
|
|
39
|
+
<!-- 编辑态:根据列的 valueType 渲染不同编辑控件(Element Plus) -->
|
|
40
|
+
<template v-if="isCellEditing(row.item.id, col.configId)">
|
|
41
|
+
<!-- 布尔:开关 -->
|
|
42
|
+
<el-switch
|
|
43
|
+
v-if="editingTextCell?.editorType === 'Boolean'"
|
|
44
|
+
v-model="editingTextCell!.value"
|
|
45
|
+
size="small"
|
|
46
|
+
class="cell-switch"
|
|
47
|
+
@click.stop
|
|
48
|
+
@change="saveTextEdit(row.item, col.configId)"
|
|
49
|
+
/>
|
|
50
|
+
<!-- 日期:日期时间选择器 -->
|
|
51
|
+
<el-date-picker
|
|
52
|
+
v-else-if="editingTextCell?.editorType === 'Date'"
|
|
53
|
+
:ref="setEditingInputRef"
|
|
54
|
+
v-model="editingTextCell!.value"
|
|
55
|
+
type="datetime"
|
|
56
|
+
size="small"
|
|
57
|
+
class="cell-date"
|
|
58
|
+
format="YYYY-MM-DD HH:mm:ss"
|
|
59
|
+
value-format="YYYY-MM-DD HH:mm:ss"
|
|
60
|
+
:teleported="false"
|
|
61
|
+
:clearable="true"
|
|
62
|
+
:editable="false"
|
|
63
|
+
@click.stop
|
|
64
|
+
@change="saveTextEdit(row.item, col.configId)"
|
|
65
|
+
@blur="saveTextEdit(row.item, col.configId)"
|
|
66
|
+
@keydown.esc.prevent="closeCellEditor"
|
|
67
|
+
/>
|
|
68
|
+
<!-- 枚举:下拉选择,选项来自后端 literals -->
|
|
69
|
+
<el-select
|
|
70
|
+
v-else-if="editingTextCell?.editorType === 'Enumeration' && col.literals && col.literals.length"
|
|
71
|
+
v-model="editingTextCell!.value"
|
|
72
|
+
size="small"
|
|
73
|
+
class="cell-select"
|
|
74
|
+
@click.stop
|
|
75
|
+
@change="saveTextEdit(row.item, col.configId)"
|
|
76
|
+
@blur="saveTextEdit(row.item, col.configId)"
|
|
77
|
+
>
|
|
78
|
+
<el-option
|
|
79
|
+
v-for="opt in col.literals"
|
|
80
|
+
:key="opt.value"
|
|
81
|
+
:label="opt.key"
|
|
82
|
+
:value="opt.value"
|
|
83
|
+
/>
|
|
84
|
+
</el-select>
|
|
85
|
+
<!-- 富文本:先用 textarea(属性面板是弹窗富文本,这里做轻量版) -->
|
|
86
|
+
<el-input
|
|
87
|
+
v-else-if="editingTextCell?.editorType === 'Html'"
|
|
88
|
+
:ref="setEditingInputRef"
|
|
89
|
+
v-model="editingTextCell!.value"
|
|
90
|
+
type="textarea"
|
|
91
|
+
autosize
|
|
92
|
+
size="small"
|
|
93
|
+
class="cell-textarea"
|
|
94
|
+
@click.stop
|
|
95
|
+
@blur="saveTextEdit(row.item, col.configId)"
|
|
96
|
+
@keydown.esc.prevent="closeCellEditor"
|
|
97
|
+
/>
|
|
98
|
+
<!-- 其他(String、Integer 等):文本/数字输入框 -->
|
|
99
|
+
<el-input
|
|
100
|
+
v-else
|
|
101
|
+
:ref="setEditingInputRef"
|
|
102
|
+
v-model="editingTextCell!.value"
|
|
103
|
+
:type="editingTextCell?.editorType === 'Integer' ? 'number' : 'text'"
|
|
104
|
+
size="small"
|
|
105
|
+
class="cell-input"
|
|
106
|
+
@click.stop
|
|
107
|
+
@blur="saveTextEdit(row.item, col.configId)"
|
|
108
|
+
@keydown.enter.prevent="saveTextEdit(row.item, col.configId)"
|
|
109
|
+
@keydown.esc.prevent="closeCellEditor"
|
|
110
|
+
/>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<!-- 显示态 -->
|
|
114
|
+
<template v-else>
|
|
115
|
+
<div v-if="col.configId === 'index'" class="item-index" style="text-align: center;">
|
|
116
|
+
{{ row.index + 1 }}
|
|
117
|
+
</div>
|
|
118
|
+
<div v-else class="item-name">
|
|
119
|
+
<div v-if="getDisplayCellIcon(row.item, col)" class="expand-icon">
|
|
120
|
+
<img :src="getIcon('childIcons', getDisplayCellIcon(row.item, col))" alt=""
|
|
121
|
+
style="width: 16px; height: 16px; display: block;" />
|
|
122
|
+
</div>
|
|
123
|
+
<span class="cell-text">{{ getDisplayCellValue(row.item, col) }}</span>
|
|
124
|
+
</div>
|
|
125
|
+
</template>
|
|
126
|
+
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
|
|
129
|
+
<!-- 下方占位空间 -->
|
|
130
|
+
<tr v-if="virtualBottomSpacerHeight > 0" class="table-spacer-row" aria-hidden="true">
|
|
131
|
+
<td :colspan="visibleColumnSpan">
|
|
132
|
+
<div class="table-spacer" :style="{ height: virtualBottomSpacerHeight + 'px' }"></div>
|
|
133
|
+
</td>
|
|
134
|
+
</tr>
|
|
135
|
+
</tbody>
|
|
136
|
+
</table>
|
|
137
|
+
</div>
|
|
138
|
+
<!-- 右键菜单 -->
|
|
139
|
+
<GanttContextMenu v-model:visible="showContextMenu" :position="contextMenuPosition" :can-move-up="canMoveUp"
|
|
140
|
+
:can-move-down="canMoveDown" @property-config="handlePropertyConfig" @tree-highlight="handleTreeHighlight"
|
|
141
|
+
@move-up="handleMoveUpFromMenu" @move-down="handleMoveDownFromMenu" @delete="handleDeleteFromMenu"
|
|
142
|
+
@remove="handleRemoveFromMenu" />
|
|
143
|
+
</div>
|
|
144
|
+
</template>
|
|
145
|
+
|
|
146
|
+
<script setup lang="ts">
|
|
147
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
148
|
+
import type { Shape, GanttData, GanttColumn } from '../../types'
|
|
149
|
+
import { eventBus } from '../../store/eventBus'
|
|
150
|
+
import GanttContextMenu from '../GanttContextMenu/GanttContextMenu.vue'
|
|
151
|
+
import { getIcon } from '../../utils/iconLoader'
|
|
152
|
+
import { useChartRowSelection, useVirtualScroll } from '../../hooks'
|
|
153
|
+
/**
|
|
154
|
+
* 组件入参
|
|
155
|
+
*/
|
|
156
|
+
interface Props {
|
|
157
|
+
shape: Shape
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const props = defineProps<Props>()
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 前端内置固定列:序号列
|
|
164
|
+
*/
|
|
165
|
+
const indexColumn = {
|
|
166
|
+
configId: 'index',
|
|
167
|
+
name: '序号',
|
|
168
|
+
isShow: true,
|
|
169
|
+
isReadonly: false,
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 当前组件内部使用的行数据类型
|
|
173
|
+
* 允许动态字段,便于适配后端返回的任意列
|
|
174
|
+
*/
|
|
175
|
+
// 列宽度状态(动态调整,使用 configId 作为 key)
|
|
176
|
+
const columnWidths = ref<Record<string, number>>({})
|
|
177
|
+
/**
|
|
178
|
+
* 搜索输入框 ref
|
|
179
|
+
*/
|
|
180
|
+
const searchInputRef = ref<HTMLInputElement | null>(null)
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 表格容器 ref(用于虚拟滚动)
|
|
184
|
+
*/
|
|
185
|
+
const tableContainerRef = ref<HTMLDivElement | null>(null)
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 当前编辑中的输入框 ref
|
|
189
|
+
*/
|
|
190
|
+
const editingInputRef = ref<HTMLInputElement | null>(null)
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 搜索相关状态
|
|
194
|
+
*/
|
|
195
|
+
const showSearchBar = ref(false)
|
|
196
|
+
const searchKeyword = ref('')
|
|
197
|
+
const searchMatchedIds = ref<string[]>([])
|
|
198
|
+
const currentMatchIndex = ref(-1)
|
|
199
|
+
/**
|
|
200
|
+
* 默认列配置
|
|
201
|
+
* 没有后端列配置时,使用这组默认列
|
|
202
|
+
*/
|
|
203
|
+
const defaultColumns = ref<GanttColumn[]>([])
|
|
204
|
+
|
|
205
|
+
// 甘特图数据:使用后端数据
|
|
206
|
+
type GanttItem = GanttData & { hasChildren: boolean }
|
|
207
|
+
const ganttItems = ref<GanttItem[]>([])
|
|
208
|
+
/**
|
|
209
|
+
* 除序号列之外的第一列 configId(displayColumns[1])
|
|
210
|
+
*/
|
|
211
|
+
const firstDataColId = computed(() => {
|
|
212
|
+
return displayColumns.value[1]?.configId || ''
|
|
213
|
+
})
|
|
214
|
+
/**
|
|
215
|
+
* 实际表格数据
|
|
216
|
+
* 优先使用后端数据,没有则使用 mock 数据
|
|
217
|
+
*/
|
|
218
|
+
// const ganttItems = ref<GanttItem[]>([...mockData.value])
|
|
219
|
+
|
|
220
|
+
// 列配置:优先使用后端数据
|
|
221
|
+
const columnConfig = ref<any[]>([...defaultColumns.value])
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 当前可见列
|
|
225
|
+
*/
|
|
226
|
+
const visibleColumns = computed(() => {
|
|
227
|
+
return columnConfig.value.filter(col => col.isShow)
|
|
228
|
+
})
|
|
229
|
+
/**
|
|
230
|
+
* 最终渲染列
|
|
231
|
+
* 序号列固定在最左侧
|
|
232
|
+
*/
|
|
233
|
+
const displayColumns = computed(() => {
|
|
234
|
+
return [indexColumn, ...visibleColumns.value]
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 过滤后的数据(用于搜索)
|
|
239
|
+
* 如果没有搜索关键字,返回所有数据
|
|
240
|
+
*/
|
|
241
|
+
const filteredGanttItems = computed(() => {
|
|
242
|
+
const keyword = searchKeyword.value.trim().toLowerCase()
|
|
243
|
+
if (!keyword) {
|
|
244
|
+
return ganttItems.value
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (searchMatchedIds.value.length === 0) {
|
|
248
|
+
return []
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return ganttItems.value.filter(item => searchMatchedIds.value.includes(item.id))
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// ========== 虚拟滚动 ==========
|
|
255
|
+
const TABLE_HEADER_HEIGHT = 28
|
|
256
|
+
const TABLE_ROW_HEIGHT = 32
|
|
257
|
+
const TABLE_VIRTUAL_OVERSCAN = 8
|
|
258
|
+
|
|
259
|
+
const {
|
|
260
|
+
virtualRows,
|
|
261
|
+
virtualTopSpacerHeight,
|
|
262
|
+
virtualBottomSpacerHeight,
|
|
263
|
+
virtualStartIndex,
|
|
264
|
+
virtualEndIndex,
|
|
265
|
+
syncVirtualViewportState,
|
|
266
|
+
visibleColumnSpan,
|
|
267
|
+
} = useVirtualScroll(filteredGanttItems, {
|
|
268
|
+
itemHeight: TABLE_ROW_HEIGHT,
|
|
269
|
+
headerHeight: TABLE_HEADER_HEIGHT,
|
|
270
|
+
overscan: TABLE_VIRTUAL_OVERSCAN,
|
|
271
|
+
containerRef: tableContainerRef,
|
|
272
|
+
columnCount: computed(() => displayColumns.value.length),
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 监听虚拟索引变化,自动关闭不可见行的编辑状态
|
|
277
|
+
*/
|
|
278
|
+
watch([virtualStartIndex, virtualEndIndex], () => {
|
|
279
|
+
if (!editingTextCell.value) return
|
|
280
|
+
|
|
281
|
+
const renderedIds = new Set(virtualRows.value.map(({ item }) => item.id))
|
|
282
|
+
if (!renderedIds.has(editingTextCell.value.id)) {
|
|
283
|
+
closeCellEditor()
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 行交互状态
|
|
289
|
+
* 统一复用表格/甘特图共享的行选择与右键菜单逻辑
|
|
290
|
+
*/
|
|
291
|
+
const {
|
|
292
|
+
canMoveDown,
|
|
293
|
+
canMoveUp,
|
|
294
|
+
clearSelection,
|
|
295
|
+
contextMenuPosition,
|
|
296
|
+
deleteRows,
|
|
297
|
+
handleContextMenu,
|
|
298
|
+
handleDeleteFromMenu,
|
|
299
|
+
handleMoveDownFromMenu,
|
|
300
|
+
handleMoveUpFromMenu,
|
|
301
|
+
handlePropertyConfig,
|
|
302
|
+
handleRemoveFromMenu,
|
|
303
|
+
handleTreeHighlight,
|
|
304
|
+
moveDown,
|
|
305
|
+
moveUp,
|
|
306
|
+
onRemoveSuccess,
|
|
307
|
+
removeRows,
|
|
308
|
+
selectLastRow,
|
|
309
|
+
selectRow,
|
|
310
|
+
selectedRowIds,
|
|
311
|
+
showContextMenu,
|
|
312
|
+
} = useChartRowSelection<GanttItem>({
|
|
313
|
+
items: ganttItems,
|
|
314
|
+
rowSelectedEvent: 'table-row-selected',
|
|
315
|
+
onClearSelection: () => {
|
|
316
|
+
closeCellEditor()
|
|
317
|
+
},
|
|
318
|
+
onDeleteRows: () => {
|
|
319
|
+
closeCellEditor()
|
|
320
|
+
},
|
|
321
|
+
onAfterRemoveSuccess: () => {
|
|
322
|
+
closeCellEditor()
|
|
323
|
+
},
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 当前编辑中的单元格
|
|
328
|
+
* 增加 editorType / column 信息,便于根据 valueType 渲染不同编辑控件
|
|
329
|
+
*/
|
|
330
|
+
const editingTextCell = ref<{
|
|
331
|
+
id: string
|
|
332
|
+
field: string
|
|
333
|
+
value: any
|
|
334
|
+
editorType?: string
|
|
335
|
+
column?: any
|
|
336
|
+
} | null>(null)
|
|
337
|
+
|
|
338
|
+
// 根据 configId 获取默认列宽
|
|
339
|
+
function getDefaultColumnWidth(configId: string): number {
|
|
340
|
+
if (configId === 'index') {
|
|
341
|
+
return 50
|
|
342
|
+
}
|
|
343
|
+
// 其他列默认中等宽度
|
|
344
|
+
return 400
|
|
345
|
+
}
|
|
346
|
+
function getColumnWidth(configId: string): number {
|
|
347
|
+
if (configId === 'index') {
|
|
348
|
+
return 50
|
|
349
|
+
}
|
|
350
|
+
return columnWidths.value[configId] || getDefaultColumnWidth(configId)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 点击空白区域,清空选中并关闭编辑态
|
|
355
|
+
*/
|
|
356
|
+
function onContainerClick() {
|
|
357
|
+
clearSelection()
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* 新增行
|
|
361
|
+
*/
|
|
362
|
+
function addRow() {
|
|
363
|
+
eventBus.emit('table-add-row')
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 搜索结果显示文本
|
|
367
|
+
*/
|
|
368
|
+
const searchResultText = computed(() => {
|
|
369
|
+
if (!searchKeyword.value.trim()) return ''
|
|
370
|
+
if (searchMatchedIds.value.length === 0) return '0 of 0'
|
|
371
|
+
return `${currentMatchIndex.value + 1} of ${searchMatchedIds.value.length}`
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 上一条 / 下一条按钮状态
|
|
376
|
+
*/
|
|
377
|
+
const canGoPrev = computed(() => {
|
|
378
|
+
return searchMatchedIds.value.length > 0 && currentMatchIndex.value > 0
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
const canGoNext = computed(() => {
|
|
382
|
+
return (
|
|
383
|
+
searchMatchedIds.value.length > 0 &&
|
|
384
|
+
currentMatchIndex.value < searchMatchedIds.value.length - 1
|
|
385
|
+
)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 执行搜索
|
|
390
|
+
* 搜索范围为当前可见列
|
|
391
|
+
*/
|
|
392
|
+
function runSearch() {
|
|
393
|
+
const keyword = searchKeyword.value.trim().toLowerCase()
|
|
394
|
+
if (!keyword) {
|
|
395
|
+
searchMatchedIds.value = []
|
|
396
|
+
currentMatchIndex.value = -1
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 模糊匹配:搜索所有可见列的值
|
|
401
|
+
const matched = ganttItems.value.filter(item => {
|
|
402
|
+
return visibleColumns.value.some(col => {
|
|
403
|
+
const value = getDisplayCellValue(item, col)
|
|
404
|
+
return value.toLowerCase().includes(keyword)
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
searchMatchedIds.value = matched.map(item => item.id)
|
|
409
|
+
currentMatchIndex.value = searchMatchedIds.value.length > 0 ? 0 : -1
|
|
410
|
+
|
|
411
|
+
// 自动选中第一个匹配项
|
|
412
|
+
if (currentMatchIndex.value >= 0) {
|
|
413
|
+
selectRow(searchMatchedIds.value[0])
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* 搜索输入事件
|
|
419
|
+
*/
|
|
420
|
+
function onSearchInput() {
|
|
421
|
+
runSearch()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* 跳转上一条匹配
|
|
426
|
+
*/
|
|
427
|
+
function gotoPrevMatch() {
|
|
428
|
+
if (!canGoPrev.value) return
|
|
429
|
+
currentMatchIndex.value--
|
|
430
|
+
selectRow(searchMatchedIds.value[currentMatchIndex.value])
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 跳转下一条匹配
|
|
435
|
+
*/
|
|
436
|
+
function gotoNextMatch() {
|
|
437
|
+
if (!canGoNext.value) return
|
|
438
|
+
currentMatchIndex.value++
|
|
439
|
+
selectRow(searchMatchedIds.value[currentMatchIndex.value])
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 打开搜索栏
|
|
444
|
+
*/
|
|
445
|
+
function openSearch() {
|
|
446
|
+
showSearchBar.value = true
|
|
447
|
+
nextTick(() => {
|
|
448
|
+
searchInputRef.value?.focus()
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 关闭搜索栏
|
|
454
|
+
*/
|
|
455
|
+
function closeSearch() {
|
|
456
|
+
showSearchBar.value = false
|
|
457
|
+
searchKeyword.value = ''
|
|
458
|
+
searchMatchedIds.value = []
|
|
459
|
+
currentMatchIndex.value = -1
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 工具栏指令监听
|
|
464
|
+
*/
|
|
465
|
+
function onGanttToolbarAction(action: string) {
|
|
466
|
+
const actionMap: Record<string, () => void> = {
|
|
467
|
+
chartNewModel: addRow,
|
|
468
|
+
chartDelete: deleteRows,
|
|
469
|
+
chartRemove: removeRows,
|
|
470
|
+
chartMoveUp: moveUp,
|
|
471
|
+
chartMoveDown: moveDown,
|
|
472
|
+
chartSearch: openSearch,
|
|
473
|
+
}
|
|
474
|
+
const fn = actionMap[action]
|
|
475
|
+
if (!fn) return
|
|
476
|
+
fn()
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* 判断当前单元格是否正在编辑
|
|
480
|
+
*/
|
|
481
|
+
function isCellEditing(rowId: string, field: string) {
|
|
482
|
+
return editingTextCell.value?.id === rowId && editingTextCell.value?.field === field
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 设置当前编辑输入框 ref
|
|
487
|
+
*/
|
|
488
|
+
function setEditingInputRef(el: HTMLInputElement | null | any) {
|
|
489
|
+
editingInputRef.value = el
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* 打开单元格编辑
|
|
494
|
+
* 根据列的 valueType 选择合适的编辑控件
|
|
495
|
+
*/
|
|
496
|
+
function openCellEditor(item: GanttItem, col: any) {
|
|
497
|
+
if (col.isReadonly === true) return
|
|
498
|
+
|
|
499
|
+
// 原始值直接从数据行中读取,避免使用展示值(例如枚举的 label)
|
|
500
|
+
const rawValue = (item as any)[col.configId]
|
|
501
|
+
|
|
502
|
+
// 复杂类型:交给宿主应用使用“属性面板同款编辑器”处理
|
|
503
|
+
// 这样 graph 包保持轻量,不强依赖业务侧的弹窗/选择器实现
|
|
504
|
+
// 复杂值类型不走表格内联编辑,统一交给宿主侧弹出属性面板同款编辑器。
|
|
505
|
+
// Transfer 在业务上对应“选择用例”穿梭框,双击单元格时也要走这条链路。
|
|
506
|
+
const complexTypes = new Set([
|
|
507
|
+
'Element',
|
|
508
|
+
'Element_List',
|
|
509
|
+
'Relations_End',
|
|
510
|
+
'ValueSpecification',
|
|
511
|
+
'NoString',
|
|
512
|
+
'Transfer',
|
|
513
|
+
])
|
|
514
|
+
if (complexTypes.has(col.valueType)) {
|
|
515
|
+
eventBus.emit('table-cell-open-editor', {
|
|
516
|
+
item,
|
|
517
|
+
column: col,
|
|
518
|
+
configId: col.configId,
|
|
519
|
+
valueType: col.valueType,
|
|
520
|
+
value: rawValue ?? null,
|
|
521
|
+
})
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
editingTextCell.value = {
|
|
526
|
+
id: item.id,
|
|
527
|
+
field: col.configId,
|
|
528
|
+
value: rawValue ?? '',
|
|
529
|
+
editorType: col.valueType,
|
|
530
|
+
column: col,
|
|
531
|
+
}
|
|
532
|
+
nextTick(() => {
|
|
533
|
+
const inst = editingInputRef.value as any
|
|
534
|
+
inst?.focus?.()
|
|
535
|
+
inst?.select?.()
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function getRawCellValue(item: any, configId: string) {
|
|
540
|
+
const v = item?.[configId]
|
|
541
|
+
return v
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 保存单元格值
|
|
546
|
+
*/
|
|
547
|
+
function saveTextEdit(item: GanttItem, field: string) {
|
|
548
|
+
|
|
549
|
+
if (!editingTextCell.value || editingTextCell.value.value === undefined) {
|
|
550
|
+
editingTextCell.value = null
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const oldValue = getRawCellValue(item as any, field)
|
|
555
|
+
const newValue = editingTextCell.value.value
|
|
556
|
+
|
|
557
|
+
// 值未改变,直接关闭编辑
|
|
558
|
+
if (oldValue === newValue) {
|
|
559
|
+
editingTextCell.value = null
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 发送编辑事件到父组件(与 Gantt 使用相同的事件名)
|
|
564
|
+
eventBus.emit('gantt-cell-edit', {
|
|
565
|
+
item,
|
|
566
|
+
configId: field,
|
|
567
|
+
oldValue,
|
|
568
|
+
newValue
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
// 暂时关闭编辑状态(等待接口返回)
|
|
572
|
+
editingTextCell.value = null
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* 关闭单元格编辑
|
|
577
|
+
*/
|
|
578
|
+
function closeCellEditor() {
|
|
579
|
+
editingTextCell.value = null
|
|
580
|
+
editingInputRef.value = null
|
|
581
|
+
}
|
|
582
|
+
//监听获取后端列配置
|
|
583
|
+
watch(() => props.shape.attributeColumns, (columns) => {
|
|
584
|
+
if (columns && columns.length > 0) {
|
|
585
|
+
columnConfig.value = columns
|
|
586
|
+
// 初始化列宽(如果还没有设置)
|
|
587
|
+
columns.forEach((col: any) => {
|
|
588
|
+
if (col.configId !== 'index' && !columnWidths.value[col.configId]) {
|
|
589
|
+
columnWidths.value[col.configId] = getDefaultColumnWidth(col.configId)
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
// 同步到工具栏菜单
|
|
593
|
+
eventBus.emit('chart-columns-from-backend', columns)
|
|
594
|
+
} else {
|
|
595
|
+
// 没有后端数据时使用默认配置
|
|
596
|
+
columnConfig.value = [...defaultColumns.value]
|
|
597
|
+
}
|
|
598
|
+
}, { immediate: true })
|
|
599
|
+
// 统一提取对象/对象数组类型单元格的展示文本和图标,避免 Element_List 展示成 [object Object]。
|
|
600
|
+
function getCellDisplayMeta(value: any): { text: string; icon: string } {
|
|
601
|
+
if (Array.isArray(value)) {
|
|
602
|
+
const objectItems = value.filter((item) => item && typeof item === 'object')
|
|
603
|
+
const text = objectItems
|
|
604
|
+
.map((item: any) => item.nodeName || item.modelName || '')
|
|
605
|
+
.filter(Boolean)
|
|
606
|
+
.join('、')
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
text: text || String(value ?? ''),
|
|
610
|
+
icon: objectItems.find((item: any) => item.icon)?.icon || '',
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (value && typeof value === 'object') {
|
|
615
|
+
return {
|
|
616
|
+
text: value.nodeName || value.modelName || String(value ?? ''),
|
|
617
|
+
icon: value.icon || '',
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
text: String(value ?? ''),
|
|
623
|
+
icon: '',
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 根据列配置获取“展示值”(用于单元格展示 / title)
|
|
628
|
+
function getDisplayCellValue(item: GanttData & { hasChildren: boolean }, col: any): string {
|
|
629
|
+
const configId = col.configId
|
|
630
|
+
const value = (item as any)[configId]
|
|
631
|
+
|
|
632
|
+
if (col.valueType === 'Boolean') {
|
|
633
|
+
if (value === true) return '是'
|
|
634
|
+
if (value === false) return '否'
|
|
635
|
+
return ''
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 枚举类型:用 literals 中的 key 作为展示文案
|
|
639
|
+
if (col.valueType === 'Enumeration' && col.literals && Array.isArray(col.literals)) {
|
|
640
|
+
const matched = col.literals.find((opt: any) => opt.value === value)
|
|
641
|
+
return matched?.key ?? String(value ?? '')
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return getCellDisplayMeta(value).text
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// 获取单元格应展示的图标。
|
|
648
|
+
// 第一列仍然优先展示行本身图标,其他对象/对象数组列则展示值里的 icon。
|
|
649
|
+
function getDisplayCellIcon(item: GanttData & { hasChildren: boolean }, col: any): string {
|
|
650
|
+
if (col.configId === firstDataColId.value && item.icon) {
|
|
651
|
+
return item.icon
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return getCellDisplayMeta((item as any)[col.configId]).icon
|
|
655
|
+
}
|
|
656
|
+
// 根据 configId 获取“原始值”(用于比较 old/new、搜索等)
|
|
657
|
+
function getCellValue(item: GanttData & { hasChildren: boolean }, configId: string): string {
|
|
658
|
+
return getCellDisplayMeta((item as any)[configId]).text
|
|
659
|
+
}
|
|
660
|
+
// 列宽调整
|
|
661
|
+
function startColumnResize(columnKey: string, e: MouseEvent) {
|
|
662
|
+
if (columnKey === 'index') return
|
|
663
|
+
|
|
664
|
+
e.stopPropagation()
|
|
665
|
+
e.preventDefault()
|
|
666
|
+
|
|
667
|
+
const startX = e.clientX
|
|
668
|
+
const startWidth = getColumnWidth(columnKey)
|
|
669
|
+
|
|
670
|
+
const onMove = (ev: MouseEvent) => {
|
|
671
|
+
const delta = ev.clientX - startX
|
|
672
|
+
const nextWidth = Math.max(200, startWidth + delta)
|
|
673
|
+
columnWidths.value[columnKey] = nextWidth
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const onUp = () => {
|
|
677
|
+
document.removeEventListener('mousemove', onMove)
|
|
678
|
+
document.removeEventListener('mouseup', onUp)
|
|
679
|
+
document.body.style.cursor = ''
|
|
680
|
+
document.body.style.userSelect = ''
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
document.body.style.cursor = 'col-resize'
|
|
684
|
+
document.body.style.userSelect = 'none'
|
|
685
|
+
document.addEventListener('mousemove', onMove)
|
|
686
|
+
document.addEventListener('mouseup', onUp)
|
|
687
|
+
}
|
|
688
|
+
function handleCanvasColumnChange(columns: any[]) {
|
|
689
|
+
if (columns && columns.length > 0) {
|
|
690
|
+
columnConfig.value = columns
|
|
691
|
+
|
|
692
|
+
// 初始化列宽(如果还没有设置)
|
|
693
|
+
columns.forEach((col: any) => {
|
|
694
|
+
if (col.configId !== 'index' && !columnWidths.value[col.configId]) {
|
|
695
|
+
columnWidths.value[col.configId] = getDefaultColumnWidth(col.configId)
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
// 同步到工具栏菜单
|
|
700
|
+
// eventBus.emit('chart-columns-from-backend', columns)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// 监听后端数据变化
|
|
705
|
+
function handleCanvasDataChange(data: any[]) {
|
|
706
|
+
if (data && data.length > 0) {
|
|
707
|
+
ganttItems.value = data.map(d => ({ ...d, hasChildren: false })) as (GanttData & { hasChildren: boolean })[]
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// 监听列配置变更(来自工具栏菜单)
|
|
712
|
+
function onColumnConfigChange(config: Array<any>) {
|
|
713
|
+
columnConfig.value = config
|
|
714
|
+
}
|
|
715
|
+
const tableWidth = computed(() => {
|
|
716
|
+
return displayColumns.value.reduce((total, col) => {
|
|
717
|
+
return total + getColumnWidth(col.configId)
|
|
718
|
+
}, 0)
|
|
719
|
+
})
|
|
720
|
+
/**
|
|
721
|
+
* 生命周期:注册事件
|
|
722
|
+
*/
|
|
723
|
+
onMounted(() => {
|
|
724
|
+
eventBus.on('table-toolbar-action', onGanttToolbarAction)
|
|
725
|
+
eventBus.on('canvas-data-change', handleCanvasDataChange)
|
|
726
|
+
eventBus.on('chart-column-config', onColumnConfigChange)
|
|
727
|
+
eventBus.on('canvas-data-column', handleCanvasColumnChange)
|
|
728
|
+
//监听移除成功事件
|
|
729
|
+
eventBus.on('table-gantt-remove:success', onRemoveSuccess)
|
|
730
|
+
// 新增成功回执:直接选中最后一条
|
|
731
|
+
eventBus.on('table-gantt-add:success', selectLastRow)
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 生命周期:卸载事件
|
|
736
|
+
*/
|
|
737
|
+
onUnmounted(() => {
|
|
738
|
+
eventBus.off('table-toolbar-action', onGanttToolbarAction)
|
|
739
|
+
eventBus.off('canvas-data-change', handleCanvasDataChange)
|
|
740
|
+
eventBus.off('chart-column-config', onColumnConfigChange)
|
|
741
|
+
eventBus.off('canvas-data-column', handleCanvasColumnChange)
|
|
742
|
+
eventBus.off('table-gantt-remove:success', onRemoveSuccess)
|
|
743
|
+
eventBus.off('table-gantt-add:success', selectLastRow)
|
|
744
|
+
})
|
|
745
|
+
</script>
|
|
746
|
+
|
|
747
|
+
<style scoped lang="scss">
|
|
748
|
+
/* 甘特表格容器 */
|
|
749
|
+
.gantt-container {
|
|
750
|
+
--header-row-h: 28px;
|
|
751
|
+
--data-row-h: 32px;
|
|
752
|
+
|
|
753
|
+
position: relative;
|
|
754
|
+
display: flex;
|
|
755
|
+
width: 100%;
|
|
756
|
+
height: 100%;
|
|
757
|
+
border: 1px solid #dcdfe6;
|
|
758
|
+
background: #fff;
|
|
759
|
+
overflow: hidden;
|
|
760
|
+
font-size: 12px;
|
|
761
|
+
font-family: 'Source Han Sans SC', 'Microsoft YaHei', 'PingFang SC', Arial, sans-serif;
|
|
762
|
+
}
|
|
763
|
+
/* 左侧表格区域 */
|
|
764
|
+
.gantt-left {
|
|
765
|
+
width: 100%;
|
|
766
|
+
overflow: auto;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.gantt-left.with-search-bar {
|
|
770
|
+
margin-top: 36px;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/* 虚拟滚动占位行 */
|
|
774
|
+
.table-spacer-row td {
|
|
775
|
+
height: 0;
|
|
776
|
+
padding: 0;
|
|
777
|
+
border: none !important;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.table-spacer {
|
|
781
|
+
width: 1px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* 表格 */
|
|
785
|
+
.gantt-table {
|
|
786
|
+
border-collapse: separate;
|
|
787
|
+
border-spacing: 0;
|
|
788
|
+
table-layout: fixed;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.th-content {
|
|
792
|
+
position: relative;
|
|
793
|
+
display: flex;
|
|
794
|
+
align-items: center;
|
|
795
|
+
width: 100%;
|
|
796
|
+
height: 100%;
|
|
797
|
+
min-width: 0;
|
|
798
|
+
padding: 0 8px;
|
|
799
|
+
box-sizing: border-box;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.gantt-table th,
|
|
803
|
+
.gantt-table td {
|
|
804
|
+
box-sizing: border-box;
|
|
805
|
+
padding: 0 8px;
|
|
806
|
+
border: none;
|
|
807
|
+
border-bottom: 1px solid #ebeef5;
|
|
808
|
+
border-right: 1px solid #ebeef5;
|
|
809
|
+
text-align: left;
|
|
810
|
+
vertical-align: middle;
|
|
811
|
+
white-space: nowrap;
|
|
812
|
+
overflow: hidden;
|
|
813
|
+
text-overflow: ellipsis;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.gantt-table th:first-child,
|
|
817
|
+
.gantt-table td:first-child {
|
|
818
|
+
border-left: 1px solid #ebeef5;
|
|
819
|
+
width: 50px;
|
|
820
|
+
min-width: 50px;
|
|
821
|
+
max-width: 50px;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
/* 表头第二行 */
|
|
826
|
+
.header-row-2 th {
|
|
827
|
+
position: sticky;
|
|
828
|
+
z-index: 1;
|
|
829
|
+
height: var(--header-row-h);
|
|
830
|
+
background: #f5f7fa;
|
|
831
|
+
border-bottom-color: #dcdfe6;
|
|
832
|
+
font-weight: 500;
|
|
833
|
+
color: #606266;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/* 数据行 */
|
|
837
|
+
.gantt-table td {
|
|
838
|
+
height: var(--data-row-h);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/* 列样式 */
|
|
842
|
+
.col-name {
|
|
843
|
+
text-align: left;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.col-td {
|
|
847
|
+
text-align: center;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.col-index {
|
|
851
|
+
width: 60px !important;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.th-label {
|
|
855
|
+
font-weight: 600;
|
|
856
|
+
}
|
|
857
|
+
.cell-text {
|
|
858
|
+
flex: 1;
|
|
859
|
+
min-width: 0;
|
|
860
|
+
overflow: hidden;
|
|
861
|
+
text-overflow: ellipsis;
|
|
862
|
+
white-space: nowrap;
|
|
863
|
+
}
|
|
864
|
+
/* 展开图标 */
|
|
865
|
+
.expand-icon {
|
|
866
|
+
display: inline-block;
|
|
867
|
+
margin-right: 4px;
|
|
868
|
+
cursor: pointer;
|
|
869
|
+
user-select: none;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* 名称文本 */
|
|
873
|
+
.item-name {
|
|
874
|
+
display: inline-block;
|
|
875
|
+
width: 100%;
|
|
876
|
+
overflow: hidden;
|
|
877
|
+
text-overflow: ellipsis;
|
|
878
|
+
vertical-align: middle;
|
|
879
|
+
display: flex;
|
|
880
|
+
align-items: center;
|
|
881
|
+
min-width: 0;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.expand-icon {
|
|
885
|
+
flex: 0 0 auto;
|
|
886
|
+
display: inline-flex;
|
|
887
|
+
align-items: center;
|
|
888
|
+
justify-content: center;
|
|
889
|
+
margin-right: 5px;
|
|
890
|
+
cursor: pointer;
|
|
891
|
+
user-select: none;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.item-index {
|
|
895
|
+
text-align: center;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/* 单元格编辑输入框(Element Plus 包一层外壳,尽量贴合现有样式) */
|
|
899
|
+
.cell-input {
|
|
900
|
+
width: 100%;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.cell-input :deep(.el-input__wrapper) {
|
|
904
|
+
padding: 0 6px;
|
|
905
|
+
height: 24px;
|
|
906
|
+
box-shadow: 0 0 0 1px #409eff inset;
|
|
907
|
+
border-radius: 3px;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.cell-select {
|
|
911
|
+
width: 100%;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.cell-select :deep(.el-input__wrapper) {
|
|
915
|
+
min-height: 24px;
|
|
916
|
+
height: 24px;
|
|
917
|
+
padding: 0 6px;
|
|
918
|
+
border-radius: 3px;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.cell-switch {
|
|
922
|
+
display: inline-flex;
|
|
923
|
+
align-items: center;
|
|
924
|
+
height: 24px;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.cell-date {
|
|
928
|
+
width: 100%;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.cell-date :deep(.el-input__wrapper) {
|
|
932
|
+
min-height: 24px;
|
|
933
|
+
height: 24px;
|
|
934
|
+
padding: 0 6px;
|
|
935
|
+
border-radius: 3px;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.cell-textarea {
|
|
939
|
+
width: 100%;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.cell-textarea :deep(.el-textarea__inner) {
|
|
943
|
+
font-size: 12px;
|
|
944
|
+
line-height: 18px;
|
|
945
|
+
padding: 4px 6px;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.col-resizer {
|
|
949
|
+
position: absolute;
|
|
950
|
+
right: -8px;
|
|
951
|
+
top: 0;
|
|
952
|
+
bottom: 0;
|
|
953
|
+
width: 5px;
|
|
954
|
+
cursor: col-resize;
|
|
955
|
+
user-select: none;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.col-resizer:hover {
|
|
959
|
+
background-color: rgba(64, 158, 255, 0.1);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* 选中行高亮 */
|
|
963
|
+
.row-selected {
|
|
964
|
+
background-color: #ecf5ff !important;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.gantt-row.row-selected td {
|
|
968
|
+
background-color: #ecf5ff;
|
|
969
|
+
}
|
|
970
|
+
</style>
|