@idooel/components 0.0.2-beta.29 → 0.0.2-beta.30
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/@idooel/components.esm.js +463 -40
- package/dist/@idooel/components.umd.js +463 -40
- package/package.json +1 -1
- package/packages/models/tree-table-model/README.md +0 -0
- package/packages/models/tree-table-model/src/index.vue +131 -10
- package/packages/table/src/index.vue +329 -22
- package/packages/utils/README.md +172 -0
|
@@ -2129,7 +2129,8 @@ var script$D = {
|
|
|
2129
2129
|
type: Number
|
|
2130
2130
|
},
|
|
2131
2131
|
x: {
|
|
2132
|
-
|
|
2132
|
+
// ant-design-vue: scroll.x 支持 number | string(如 'max-content')
|
|
2133
|
+
type: [Number, String],
|
|
2133
2134
|
default: 1200
|
|
2134
2135
|
},
|
|
2135
2136
|
y: {
|
|
@@ -2182,7 +2183,11 @@ var script$D = {
|
|
|
2182
2183
|
innerPageSize: 10,
|
|
2183
2184
|
innerCurrentPage: 1,
|
|
2184
2185
|
tableContentHeight: 0,
|
|
2185
|
-
obs: []
|
|
2186
|
+
obs: [],
|
|
2187
|
+
// 容器宽度,用于智能判断是否需要 fixed 列
|
|
2188
|
+
containerWidth: 0,
|
|
2189
|
+
// 用于强制重新渲染表格(当 fixed 列状态切换时)
|
|
2190
|
+
tableRenderKey: 0
|
|
2186
2191
|
};
|
|
2187
2192
|
},
|
|
2188
2193
|
computed: {
|
|
@@ -2217,12 +2222,120 @@ var script$D = {
|
|
|
2217
2222
|
isFlexColumn() {
|
|
2218
2223
|
return this.columns.every(col => !col.width);
|
|
2219
2224
|
},
|
|
2225
|
+
/**
|
|
2226
|
+
* 计算所有列的总宽度(包括 rowSelection 的 checkbox 列和操作列)
|
|
2227
|
+
*/
|
|
2228
|
+
totalColumnsWidth() {
|
|
2229
|
+
const cols = this.innerColumns || [];
|
|
2230
|
+
let total = cols.reduce((sum, col) => {
|
|
2231
|
+
const w = col && col.width;
|
|
2232
|
+
return sum + (typeof w === 'number' ? w : 0);
|
|
2233
|
+
}, 0);
|
|
2234
|
+
// rowSelection 的 checkbox/radio 列,antd 默认约 60px
|
|
2235
|
+
if (this.rowSelection) total += 60;
|
|
2236
|
+
// 操作列(operations)的宽度
|
|
2237
|
+
if (this.operations && this.operations.width && typeof this.operations.width === 'number') {
|
|
2238
|
+
total += this.operations.width;
|
|
2239
|
+
}
|
|
2240
|
+
return total;
|
|
2241
|
+
},
|
|
2242
|
+
/**
|
|
2243
|
+
* 是否需要横向滚动:容器宽度 < 列总宽度
|
|
2244
|
+
*/
|
|
2245
|
+
needHorizontalScroll() {
|
|
2246
|
+
// 未获取容器宽度前,先假定需要滚动(保守策略)
|
|
2247
|
+
if (!this.containerWidth) return true;
|
|
2248
|
+
// 加一点容差
|
|
2249
|
+
return this.containerWidth < this.totalColumnsWidth - 5;
|
|
2250
|
+
},
|
|
2251
|
+
/**
|
|
2252
|
+
* 智能列配置:
|
|
2253
|
+
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2254
|
+
* - 当容器足够宽时,移除 fixed 属性,让表格自动铺满
|
|
2255
|
+
*/
|
|
2256
|
+
smartColumns() {
|
|
2257
|
+
if (this.needHorizontalScroll) {
|
|
2258
|
+
// 需要滚动,保留原始配置
|
|
2259
|
+
return this.innerColumns;
|
|
2260
|
+
}
|
|
2261
|
+
// 不需要滚动,移除所有 fixed 属性
|
|
2262
|
+
return this.innerColumns.map(col => {
|
|
2263
|
+
if (col.fixed) {
|
|
2264
|
+
const {
|
|
2265
|
+
fixed,
|
|
2266
|
+
...rest
|
|
2267
|
+
} = col;
|
|
2268
|
+
return rest;
|
|
2269
|
+
}
|
|
2270
|
+
return col;
|
|
2271
|
+
});
|
|
2272
|
+
},
|
|
2273
|
+
/**
|
|
2274
|
+
* 智能 scroll 配置:
|
|
2275
|
+
* - 当需要横向滚动时,设置 scroll.x
|
|
2276
|
+
* - 当容器足够宽时,不设置 scroll.x,避免产生空白区域
|
|
2277
|
+
*/
|
|
2278
|
+
smartScroll() {
|
|
2279
|
+
if (!this.needHorizontalScroll) {
|
|
2280
|
+
// 不需要横向滚动,只保留 y 方向(如果需要)
|
|
2281
|
+
if (this.height && this.needScrollY) {
|
|
2282
|
+
const availableHeight = this.tableHeaderHeight && this.paginationHeight ? this.getScrollHeightByHeight : this.height - 100;
|
|
2283
|
+
if (availableHeight > 50) {
|
|
2284
|
+
return {
|
|
2285
|
+
y: availableHeight
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
return {};
|
|
2290
|
+
}
|
|
2291
|
+
// 需要横向滚动,使用原有逻辑
|
|
2292
|
+
return this.getScroll;
|
|
2293
|
+
},
|
|
2294
|
+
/**
|
|
2295
|
+
* 智能 rowSelection 配置:
|
|
2296
|
+
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2297
|
+
* - 当容器足够宽时,移除 fixed 属性
|
|
2298
|
+
*/
|
|
2299
|
+
smartRowSelection() {
|
|
2300
|
+
if (!this.rowSelection) return null;
|
|
2301
|
+
if (this.needHorizontalScroll) {
|
|
2302
|
+
return this.rowSelection;
|
|
2303
|
+
}
|
|
2304
|
+
// 不需要滚动,移除 fixed 属性
|
|
2305
|
+
if (this.rowSelection.fixed) {
|
|
2306
|
+
const {
|
|
2307
|
+
fixed,
|
|
2308
|
+
...rest
|
|
2309
|
+
} = this.rowSelection;
|
|
2310
|
+
return rest;
|
|
2311
|
+
}
|
|
2312
|
+
return this.rowSelection;
|
|
2313
|
+
},
|
|
2220
2314
|
getScroll() {
|
|
2221
2315
|
if (this.scroll) {
|
|
2222
2316
|
return this.scroll;
|
|
2223
2317
|
} else {
|
|
2224
2318
|
// 固定列需要 scroll.x 才能正确同步行高,始终设置一个有效值
|
|
2225
|
-
|
|
2319
|
+
let baseX = this.x === '' || this.x === null || this.x === undefined ? 1200 : this.x;
|
|
2320
|
+
|
|
2321
|
+
// 解决“x 给太大导致操作列前出现大块空白”的问题:
|
|
2322
|
+
// 当所有列都给了明确 width 时,scroll.x 取列宽总和最合理;大于总和会产生多余区域。
|
|
2323
|
+
if (typeof baseX === 'number') {
|
|
2324
|
+
const cols = this.innerColumns || [];
|
|
2325
|
+
const total = cols.reduce((sum, col) => {
|
|
2326
|
+
const w = col && col.width;
|
|
2327
|
+
return sum + (typeof w === 'number' ? w : 0);
|
|
2328
|
+
}, 0);
|
|
2329
|
+
|
|
2330
|
+
// rowSelection 的 checkbox/radio 列是 antd 自动加的,给一个经验宽度避免误差
|
|
2331
|
+
const selectionWidth = this.rowSelection ? 60 : 0;
|
|
2332
|
+
|
|
2333
|
+
// 只有当所有列都明确给了宽度(total > 0)时才 clamp
|
|
2334
|
+
if (total > 0) {
|
|
2335
|
+
const minX = total + selectionWidth;
|
|
2336
|
+
if (baseX > minX) baseX = minX;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2226
2339
|
if (this.height && this.needScrollY) {
|
|
2227
2340
|
const availableHeight = this.tableHeaderHeight && this.paginationHeight ? this.getScrollHeightByHeight : this.height - 100;
|
|
2228
2341
|
if (availableHeight > 50) {
|
|
@@ -2244,6 +2357,20 @@ var script$D = {
|
|
|
2244
2357
|
this.innerPageSize = pageSize;
|
|
2245
2358
|
},
|
|
2246
2359
|
immediate: true
|
|
2360
|
+
},
|
|
2361
|
+
/**
|
|
2362
|
+
* 监听 needHorizontalScroll 变化,强制重新渲染表格
|
|
2363
|
+
* 当从"需要固定列"切换到"不需要固定列"或反之时,antd 的 a-table 需要完全重新渲染
|
|
2364
|
+
*/
|
|
2365
|
+
needHorizontalScroll(newVal, oldVal) {
|
|
2366
|
+
if (newVal !== oldVal) {
|
|
2367
|
+
// 更新 key 强制 Vue 销毁并重建 a-table 组件
|
|
2368
|
+
this.tableRenderKey++;
|
|
2369
|
+
// 使用重试机制确保固定列完全渲染
|
|
2370
|
+
this.$nextTick(() => {
|
|
2371
|
+
this.retrySyncFixedColumns(newVal);
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2247
2374
|
}
|
|
2248
2375
|
},
|
|
2249
2376
|
methods: {
|
|
@@ -2285,19 +2412,195 @@ var script$D = {
|
|
|
2285
2412
|
this.innerCurrentPage = page;
|
|
2286
2413
|
this.innerPageSize = pageSize;
|
|
2287
2414
|
this.$emit('change-page', page, pageSize);
|
|
2415
|
+
},
|
|
2416
|
+
syncFixedColumns() {
|
|
2417
|
+
// 强制 ant-design-vue 重新计算固定列的宽度
|
|
2418
|
+
this.$nextTick(() => {
|
|
2419
|
+
const tableEl = this.$el.querySelector('.ant-table');
|
|
2420
|
+
if (tableEl) {
|
|
2421
|
+
// 触发窗口 resize 事件,让 ant-design-vue 重新计算
|
|
2422
|
+
window.dispatchEvent(new Event('resize'));
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
},
|
|
2426
|
+
/**
|
|
2427
|
+
* 带重试的固定列同步机制
|
|
2428
|
+
* antd 的 a-table 在 key 变化后重新渲染固定列需要一定时间,
|
|
2429
|
+
* 这里通过重试确保固定列渲染完成后再同步。
|
|
2430
|
+
* @param {boolean} needFixed - 是否需要固定列
|
|
2431
|
+
* @param {number} retries - 当前重试次数
|
|
2432
|
+
*/
|
|
2433
|
+
retrySyncFixedColumns(needFixed, retries = 0) {
|
|
2434
|
+
const MAX_RETRIES = 5;
|
|
2435
|
+
const DELAY = 100; // 每次延迟 100ms
|
|
2436
|
+
|
|
2437
|
+
setTimeout(() => {
|
|
2438
|
+
this.syncFixedColumns();
|
|
2439
|
+
this.syncHeaderTableWidth();
|
|
2440
|
+
this.bindScrollSync();
|
|
2441
|
+
|
|
2442
|
+
// 如果需要固定列,检查是否已渲染
|
|
2443
|
+
if (needFixed && retries < MAX_RETRIES) {
|
|
2444
|
+
const hasFixedLeft = this.$el.querySelector('.ant-table-fixed-left .ant-table-body tbody tr');
|
|
2445
|
+
const hasFixedRight = this.$el.querySelector('.ant-table-fixed-right .ant-table-body tbody tr');
|
|
2446
|
+
|
|
2447
|
+
// 如果有固定列配置但还没渲染出来,继续重试
|
|
2448
|
+
const hasFixedConfig = this.innerColumns.some(c => c.fixed) || this.rowSelection && this.rowSelection.fixed;
|
|
2449
|
+
if (hasFixedConfig && !hasFixedLeft && !hasFixedRight) {
|
|
2450
|
+
this.retrySyncFixedColumns(needFixed, retries + 1);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}, DELAY);
|
|
2454
|
+
},
|
|
2455
|
+
/**
|
|
2456
|
+
* 修复 x: 'max-content' 场景下表头不跟着横向滚动的问题。
|
|
2457
|
+
* 原因:ant-design-vue 的 header table 和 body table 各自算 max-content,
|
|
2458
|
+
* header 按表头文字算、body 按实际数据算,二者宽度不同时 header 就"滚不动"。
|
|
2459
|
+
* 方案:数据渲染后把 header table 的 width 强制设成和 body table 一样。
|
|
2460
|
+
*/
|
|
2461
|
+
syncHeaderTableWidth() {
|
|
2462
|
+
this.$nextTick(() => {
|
|
2463
|
+
const headerTable = this.$el.querySelector('.ant-table-scroll .ant-table-header table');
|
|
2464
|
+
const bodyTable = this.$el.querySelector('.ant-table-scroll .ant-table-body table');
|
|
2465
|
+
if (!headerTable || !bodyTable) return;
|
|
2466
|
+
const bodyW = bodyTable.getBoundingClientRect().width;
|
|
2467
|
+
const headerW = headerTable.getBoundingClientRect().width;
|
|
2468
|
+
|
|
2469
|
+
// 始终同步表头宽度到表体宽度,确保窗口变大/变小时都能正确响应
|
|
2470
|
+
// 只有当宽度差异超过 2px 时才更新,避免频繁设置样式
|
|
2471
|
+
if (Math.abs(bodyW - headerW) > 2) {
|
|
2472
|
+
headerTable.style.width = `${bodyW}px`;
|
|
2473
|
+
headerTable.style.minWidth = `${bodyW}px`;
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2476
|
+
},
|
|
2477
|
+
/**
|
|
2478
|
+
* 监听表体横向滚动,同步到表头(防止 antd 自带同步失效)
|
|
2479
|
+
*/
|
|
2480
|
+
bindScrollSync() {
|
|
2481
|
+
const body = this.$el.querySelector('.ant-table-scroll .ant-table-body');
|
|
2482
|
+
const header = this.$el.querySelector('.ant-table-scroll .ant-table-header');
|
|
2483
|
+
if (!body || !header) return;
|
|
2484
|
+
if (this._scrollHandler) return; // 已绑定
|
|
2485
|
+
this._scrollHandler = () => {
|
|
2486
|
+
header.scrollLeft = body.scrollLeft;
|
|
2487
|
+
};
|
|
2488
|
+
body.addEventListener('scroll', this._scrollHandler, {
|
|
2489
|
+
passive: true
|
|
2490
|
+
});
|
|
2491
|
+
},
|
|
2492
|
+
unbindScrollSync() {
|
|
2493
|
+
const body = this.$el.querySelector('.ant-table-scroll .ant-table-body');
|
|
2494
|
+
if (body && this._scrollHandler) {
|
|
2495
|
+
body.removeEventListener('scroll', this._scrollHandler);
|
|
2496
|
+
this._scrollHandler = null;
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
/**
|
|
2500
|
+
* 测量容器宽度
|
|
2501
|
+
*/
|
|
2502
|
+
measureContainerWidth() {
|
|
2503
|
+
const wrapper = this.$refs.tableWrapper;
|
|
2504
|
+
if (wrapper) {
|
|
2505
|
+
this.containerWidth = wrapper.clientWidth;
|
|
2506
|
+
}
|
|
2507
|
+
},
|
|
2508
|
+
/**
|
|
2509
|
+
* 使用 ResizeObserver 监听容器宽度变化
|
|
2510
|
+
*/
|
|
2511
|
+
observeContainerWidth() {
|
|
2512
|
+
const wrapper = this.$refs.tableWrapper;
|
|
2513
|
+
if (!wrapper || typeof ResizeObserver === 'undefined') return;
|
|
2514
|
+
if (this._containerResizeObserver) return; // 已绑定
|
|
2515
|
+
|
|
2516
|
+
this._containerResizeObserver = new ResizeObserver(entries => {
|
|
2517
|
+
for (const entry of entries) {
|
|
2518
|
+
const newWidth = entry.contentRect.width;
|
|
2519
|
+
// 只有宽度变化超过阈值才触发更新,避免微小变化导致频繁重渲染
|
|
2520
|
+
if (Math.abs(newWidth - this.containerWidth) > 10) {
|
|
2521
|
+
const oldNeedScroll = this.needHorizontalScroll;
|
|
2522
|
+
this.containerWidth = newWidth;
|
|
2523
|
+
// 容器宽度变化会触发 needHorizontalScroll 的重新计算
|
|
2524
|
+
// needHorizontalScroll 的 watcher 会处理表格重新渲染和列宽同步
|
|
2525
|
+
// 这里不需要直接调用 syncFixedColumns,避免与 watcher 的执行时机冲突
|
|
2526
|
+
// 无论 needHorizontalScroll 是否变化,都立即同步表头宽度
|
|
2527
|
+
// 确保窗口变大/变小时表头都能及时响应
|
|
2528
|
+
this.$nextTick(() => {
|
|
2529
|
+
// 使用 requestAnimationFrame 确保在浏览器重绘后同步表头宽度
|
|
2530
|
+
// 这样能确保表体宽度已经更新完成
|
|
2531
|
+
requestAnimationFrame(() => {
|
|
2532
|
+
this.syncHeaderTableWidth();
|
|
2533
|
+
});
|
|
2534
|
+
|
|
2535
|
+
// 如果 needHorizontalScroll 状态没有变化,说明只是列宽需要调整,直接同步
|
|
2536
|
+
// 如果状态变化了,watcher 会处理重新渲染
|
|
2537
|
+
if (this.needHorizontalScroll === oldNeedScroll) {
|
|
2538
|
+
// 防抖:延迟同步固定列,避免频繁调用
|
|
2539
|
+
if (this._resizeDebounceTimer) {
|
|
2540
|
+
clearTimeout(this._resizeDebounceTimer);
|
|
2541
|
+
}
|
|
2542
|
+
this._resizeDebounceTimer = setTimeout(() => {
|
|
2543
|
+
this.syncFixedColumns();
|
|
2544
|
+
this.syncHeaderTableWidth();
|
|
2545
|
+
this.bindScrollSync();
|
|
2546
|
+
}, 150);
|
|
2547
|
+
}
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2552
|
+
this._containerResizeObserver.observe(wrapper);
|
|
2553
|
+
},
|
|
2554
|
+
/**
|
|
2555
|
+
* 断开容器宽度监听
|
|
2556
|
+
*/
|
|
2557
|
+
unobserveContainerWidth() {
|
|
2558
|
+
if (this._containerResizeObserver) {
|
|
2559
|
+
this._containerResizeObserver.disconnect();
|
|
2560
|
+
this._containerResizeObserver = null;
|
|
2561
|
+
}
|
|
2562
|
+
// 清理防抖定时器
|
|
2563
|
+
if (this._resizeDebounceTimer) {
|
|
2564
|
+
clearTimeout(this._resizeDebounceTimer);
|
|
2565
|
+
this._resizeDebounceTimer = null;
|
|
2566
|
+
}
|
|
2288
2567
|
}
|
|
2289
2568
|
},
|
|
2290
2569
|
mounted() {
|
|
2291
2570
|
this.$nextTick(() => {
|
|
2571
|
+
// 先测量容器宽度,用于智能判断是否需要 fixed 列
|
|
2572
|
+
this.measureContainerWidth();
|
|
2573
|
+
this.observeContainerWidth();
|
|
2292
2574
|
this.setPaginationHeight();
|
|
2293
2575
|
setTimeout(() => {
|
|
2294
2576
|
this.setTableTbodyHeight();
|
|
2295
2577
|
this.setPaginationHeight();
|
|
2578
|
+
// 强制同步固定列和主表的列宽
|
|
2579
|
+
this.syncFixedColumns();
|
|
2580
|
+
// 同步表头 table 宽度(修复 max-content 场景)
|
|
2581
|
+
this.syncHeaderTableWidth();
|
|
2582
|
+
// 绑定横向滚动同步
|
|
2583
|
+
this.bindScrollSync();
|
|
2296
2584
|
}, 200);
|
|
2297
2585
|
});
|
|
2586
|
+
|
|
2587
|
+
// 监听数据变化,重新同步列宽
|
|
2588
|
+
this.$watch('dataSource', () => {
|
|
2589
|
+
this.$nextTick(() => {
|
|
2590
|
+
setTimeout(() => {
|
|
2591
|
+
this.syncFixedColumns();
|
|
2592
|
+
this.syncHeaderTableWidth();
|
|
2593
|
+
this.bindScrollSync();
|
|
2594
|
+
}, 100);
|
|
2595
|
+
});
|
|
2596
|
+
}, {
|
|
2597
|
+
deep: true
|
|
2598
|
+
});
|
|
2298
2599
|
},
|
|
2299
2600
|
destroyed() {
|
|
2300
2601
|
this.obs.forEach(ob => ob.disconnect());
|
|
2602
|
+
this.unbindScrollSync();
|
|
2603
|
+
this.unobserveContainerWidth();
|
|
2301
2604
|
}
|
|
2302
2605
|
};
|
|
2303
2606
|
|
|
@@ -2311,20 +2614,25 @@ var __vue_render__$D = function () {
|
|
|
2311
2614
|
var _c = _vm._self._c || _h;
|
|
2312
2615
|
return _c(
|
|
2313
2616
|
"div",
|
|
2314
|
-
{
|
|
2617
|
+
{
|
|
2618
|
+
ref: "tableWrapper",
|
|
2619
|
+
staticClass: "g-table__wrapper",
|
|
2620
|
+
style: _vm.wrapperStyle,
|
|
2621
|
+
},
|
|
2315
2622
|
[
|
|
2316
2623
|
_c("a-table", {
|
|
2624
|
+
key: _vm.tableRenderKey,
|
|
2317
2625
|
class: [_vm.isNoData && "g-table__no-data"],
|
|
2318
2626
|
attrs: {
|
|
2319
2627
|
bordered: _vm.bordered,
|
|
2320
2628
|
pagination: false,
|
|
2321
2629
|
loading: _vm.loading,
|
|
2322
2630
|
size: "middle",
|
|
2323
|
-
columns: _vm.
|
|
2324
|
-
"row-selection": _vm.
|
|
2631
|
+
columns: _vm.smartColumns,
|
|
2632
|
+
"row-selection": _vm.smartRowSelection,
|
|
2325
2633
|
"row-class-name": _vm.setRowClassName,
|
|
2326
2634
|
"data-source": _vm.dataSource,
|
|
2327
|
-
scroll: _vm.
|
|
2635
|
+
scroll: _vm.smartScroll,
|
|
2328
2636
|
},
|
|
2329
2637
|
scopedSlots: _vm._u([
|
|
2330
2638
|
{
|
|
@@ -2371,6 +2679,7 @@ var __vue_render__$D = function () {
|
|
|
2371
2679
|
pageSize: _vm.innerPageSize,
|
|
2372
2680
|
current: _vm.innerCurrentPage,
|
|
2373
2681
|
pageSizeOptions: _vm.pageSizeOptions,
|
|
2682
|
+
data: _vm.dataSource.length,
|
|
2374
2683
|
total: _vm.total,
|
|
2375
2684
|
},
|
|
2376
2685
|
on: {
|
|
@@ -2391,11 +2700,11 @@ __vue_render__$D._withStripped = true;
|
|
|
2391
2700
|
/* style */
|
|
2392
2701
|
const __vue_inject_styles__$D = function (inject) {
|
|
2393
2702
|
if (!inject) return
|
|
2394
|
-
inject("data-v-9fb141ac_0", { source: "@charset \"UTF-8\";\n.g-table__wrapper[data-v-9fb141ac] {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper[data-v-9fb141ac] .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper[data-v-9fb141ac] {\n /* 固定列使用相同的行高 */\n}\n.g-table__wrapper[data-v-9fb141ac] .ant-table-fixed .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper[data-v-9fb141ac] {\n /* 表头也统一高度 */\n}\n.g-table__wrapper[data-v-9fb141ac] .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper[data-v-9fb141ac] .ant-table-fixed .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper[data-v-9fb141ac] {\n /* 分页区域固定在底部 */\n}\n.g-table__wrapper .g-table__pagination[data-v-9fb141ac] {\n display: flex;\n flex-direction: row;\n justify-content: end;\n border-top: unset;\n padding-top: 8px;\n padding-bottom: 8px;\n background: #fff;\n}\n.g-table__wrapper[data-v-9fb141ac] {\n /* 空数据状态顶部显示 */\n}\n.g-table__wrapper .g-table__no-data[data-v-9fb141ac] {\n position: relative;\n}\n.g-table__wrapper .g-table__no-data[data-v-9fb141ac] .ant-table-placeholder {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -40%);\n width: 100%;\n height: 100%;\n text-align: center;\n color: #999;\n font-size: 14px;\n font-weight: normal;\n line-height: 20px;\n overflow: hidden;\n border: unset;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["index.vue","E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\table\\src\\index.vue"],"names":[],"mappings":"AAAA,gBAAgB;AC2OhB;EACA,sBAAA;ADzOA;AC0OA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;ADxOA;ACkOA;EASA,eAAA;ADxOA;ACyOA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;ADvOA;ACyNA;EAiBA,YAAA;ADvOA;ACwOA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;ADtOA;ACyOA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;ADvOA;AC0MA;EAgCA,cAAA;ADvOA;ACwOA;EACA,aAAA;EACA,mBAAA;EACA,oBAAA;EACA,iBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;ADtOA;AC8LA;EA2CA,cAAA;ADtOA;ACuOA;EACA,kBAAA;ADrOA;ACsOA;EACA,kBAAA;EACA,QAAA;EACA,SAAA;EACA,gCAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,gBAAA;EACA,aAAA;ADpOA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["@charset \"UTF-8\";\n.g-table__wrapper {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper ::v-deep .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper {\n /* 固定列使用相同的行高 */\n}\n.g-table__wrapper ::v-deep .ant-table-fixed .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper {\n /* 表头也统一高度 */\n}\n.g-table__wrapper ::v-deep .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper ::v-deep .ant-table-fixed .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n}\n.g-table__wrapper {\n /* 分页区域固定在底部 */\n}\n.g-table__wrapper .g-table__pagination {\n display: flex;\n flex-direction: row;\n justify-content: end;\n border-top: unset;\n padding-top: 8px;\n padding-bottom: 8px;\n background: #fff;\n}\n.g-table__wrapper {\n /* 空数据状态顶部显示 */\n}\n.g-table__wrapper .g-table__no-data {\n position: relative;\n}\n.g-table__wrapper .g-table__no-data ::v-deep .ant-table-placeholder {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -40%);\n width: 100%;\n height: 100%;\n text-align: center;\n color: #999;\n font-size: 14px;\n font-weight: normal;\n line-height: 20px;\n overflow: hidden;\n border: unset;\n}\n\n/*# sourceMappingURL=index.vue.map */","<template>\r\n <div class=\"g-table__wrapper\" :style=\"wrapperStyle\">\r\n <a-table\r\n :bordered=\"bordered\"\r\n :class=\"[isNoData && 'g-table__no-data']\"\r\n :pagination=\"false\"\r\n :loading=\"loading\"\r\n size=\"middle\"\r\n :columns=\"innerColumns\"\r\n :row-selection=\"rowSelection\"\r\n :row-class-name=\"setRowClassName\"\r\n :data-source=\"dataSource\"\r\n :scroll=\"getScroll\">\r\n <template slot=\"action\" slot-scope=\"record\">\r\n <Actions v-on=\"$listeners\" :data-source=\"actions\" :record=\"record\"></Actions>\r\n </template>\r\n </a-table>\r\n <div class=\"g-table__pagination\">\r\n <a-pagination\r\n :show-total=\"all => `共 ${all} 条数据`\"\r\n v-if=\"mode === 'default'\"\r\n show-size-changer \r\n show-quick-jumper\r\n :pageSize=\"innerPageSize\"\r\n :current=\"innerCurrentPage\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n @change=\"onChangePagination\"\r\n @showSizeChange=\"onShowSizeChange\"\r\n :total=\"total\">\r\n </a-pagination>\r\n <ele-pagination\r\n v-else\r\n :pageSize=\"innerPageSize\"\r\n :current=\"innerCurrentPage\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n @change=\"onChangePagination\"\r\n @showSizeChange=\"onShowSizeChange\"\r\n :total=\"total\"\r\n ></ele-pagination>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport Actions from './action.vue'\r\nexport default {\r\n name: 'ele-table',\r\n components: {\r\n Actions\r\n },\r\n props: {\r\n mode: {\r\n type: String,\r\n default: 'default',\r\n validator: (value) => {\r\n return ['default', 'next-cursor'].includes(value)\r\n }\r\n },\r\n // ant table wrapper\r\n height: {\r\n type: Number\r\n },\r\n width: {\r\n type: Number\r\n },\r\n x: {\r\n type: Number,\r\n default: 1200\r\n },\r\n y: {\r\n type: Number,\r\n default: 200\r\n },\r\n scroll: {\r\n type: Object\r\n },\r\n rowSelection: {\r\n type: Object\r\n },\r\n actions: {\r\n type: Array,\r\n default: () => []\r\n },\r\n total: {\r\n type: Number,\r\n default: 0\r\n },\r\n loading: {\r\n type: Boolean,\r\n default: false\r\n },\r\n columns: {\r\n type: Array,\r\n default: () => []\r\n },\r\n dataSource: {\r\n type: Array,\r\n default: () => []\r\n },\r\n pageSize: {\r\n type: [Number, String],\r\n default: 10\r\n },\r\n pageSizeOptions: {\r\n type: Array,\r\n default: () => ['10', '20', '30', '40']\r\n },\r\n bordered: {\r\n type: Boolean,\r\n default: true\r\n }\r\n },\r\n data() {\r\n return {\r\n tableHeaderHeight: 0,\r\n paginationHeight: 0,\r\n innerPageSize: 10,\r\n innerCurrentPage: 1,\r\n tableContentHeight: 0,\r\n obs: []\r\n }\r\n },\r\n computed: {\r\n wrapperStyle () {\r\n // 外层容器样式\r\n if (!this.height) return {}\r\n return { height: `${this.height}px` }\r\n },\r\n needScrollY () {\r\n // 判断是否需要 y 轴滚动:基于数据行数与可用高度预估\r\n if (!this.height) return false\r\n \r\n const availableHeight = this.getScrollHeightByHeight\r\n if (availableHeight <= 0) return false\r\n \r\n // 预估每行高度(包含边框),antd 默认约 54px\r\n const estimatedRowHeight = 54\r\n const estimatedTableHeight = this.dataSource.length * estimatedRowHeight\r\n \r\n return estimatedTableHeight > availableHeight\r\n },\r\n innerColumns () {\r\n return this.columns.filter(col => !Object.keys(col).includes('multiple'))\r\n },\r\n isNoData () {\r\n return !this.dataSource.length\r\n },\r\n getScrollHeightByHeight () {\r\n // 始终返回可用的剩余高度,让表格内容不足时也能占满容器\r\n return this.height - this.tableHeaderHeight - this.paginationHeight\r\n },\r\n isFlexColumn () {\r\n return this.columns.every(col => !col.width)\r\n },\r\n getScroll () {\r\n if (this.scroll) {\r\n return this.scroll\r\n } else {\r\n // 固定列需要 scroll.x 才能正确同步行高,始终设置一个有效值\r\n const baseX = this.x || 1200\r\n \r\n if (this.height && this.needScrollY) {\r\n const availableHeight = this.tableHeaderHeight && this.paginationHeight \r\n ? this.getScrollHeightByHeight \r\n : this.height - 100\r\n \r\n if (availableHeight > 50) {\r\n return { x: baseX, y: availableHeight }\r\n }\r\n }\r\n return { x: baseX }\r\n }\r\n }\r\n },\r\n watch: {\r\n pageSize: {\r\n handler (pageSize) {\r\n this.innerPageSize = pageSize\r\n },\r\n immediate: true\r\n }\r\n },\r\n methods: {\r\n onShowSizeChange (current, pageSize) {\r\n this.innerCurrentPage = current\r\n this.innerPageSize = pageSize\r\n this.$emit('change-page', current, pageSize)\r\n },\r\n setPaginationHeight () {\r\n this.$nextTick(() => {\r\n const el = this.$el.querySelector('.g-table__pagination')\r\n if (el) {\r\n const { height } = el.getBoundingClientRect()\r\n this.paginationHeight = height\r\n }\r\n })\r\n },\r\n setTableHeaderHeight () {\r\n this.$nextTick(() => {\r\n const el = this.$el.querySelector('.ant-table-header')\r\n if (!el) return\r\n const { height } = el.getBoundingClientRect()\r\n this.tableHeaderHeight = height\r\n })\r\n },\r\n setTableTbodyHeight () {\r\n this.$nextTick(() => {\r\n this.setTableHeaderHeight()\r\n })\r\n },\r\n setRowClassName (record, idx) {\r\n return idx % 2 === 0 ? 'g-table__row--even' : 'g-table__row--odd'\r\n },\r\n onChangePagination (page, pageSize) {\r\n this.innerCurrentPage = page\r\n this.innerPageSize = pageSize\r\n this.$emit('change-page', page, pageSize)\r\n }\r\n },\r\n mounted() {\r\n this.$nextTick(() => {\r\n this.setPaginationHeight()\r\n setTimeout(() => {\r\n this.setTableTbodyHeight()\r\n this.setPaginationHeight()\r\n }, 200)\r\n })\r\n },\r\n destroyed () {\r\n this.obs.forEach(ob => ob.disconnect())\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.g-table__wrapper {\r\n /* 强制统一行高,确保主表和固定列对齐 */\r\n ::v-deep .ant-table-tbody > tr > td {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n }\r\n\r\n /* 固定列使用相同的行高 */\r\n ::v-deep .ant-table-fixed .ant-table-tbody > tr > td {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n }\r\n\r\n /* 表头也统一高度 */\r\n ::v-deep .ant-table-thead > tr > th {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n }\r\n\r\n ::v-deep .ant-table-fixed .ant-table-thead > tr > th {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n }\r\n\r\n /* 分页区域固定在底部 */\r\n .g-table__pagination {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: end;\r\n border-top: unset;\r\n padding-top: 8px;\r\n padding-bottom: 8px;\r\n background: #fff;\r\n }\r\n\r\n /* 空数据状态顶部显示 */\r\n .g-table__no-data {\r\n position: relative;\r\n ::v-deep .ant-table-placeholder {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -40%);\r\n width: 100%;\r\n height: 100%;\r\n text-align: center;\r\n color: #999;\r\n font-size: 14px;\r\n font-weight: normal;\r\n line-height: 20px;\r\n overflow: hidden;\r\n border: unset;\r\n }\r\n }\r\n}\r\n</style>"]}, media: undefined });
|
|
2703
|
+
inject("data-v-33440b92_0", { source: "@charset \"UTF-8\";\n.g-table__wrapper[data-v-33440b92] {\n /**\n * 修复\"宽屏下表格两侧出现空白\"问题:\n * 当视口宽度大于表格内容宽度时,主表(ant-table-scroll)不会自动拉伸,\n * 而固定列(fixed-left/fixed-right)是 position:absolute 定位在容器边缘,中间就出现空白。\n * 解决方案:让主表的 table 元素 min-width:100%,使其始终填满滚动容器。\n */\n}\n.g-table__wrapper[data-v-33440b92] .ant-table-scroll .ant-table-header table,\n.g-table__wrapper[data-v-33440b92] .ant-table-scroll .ant-table-body table {\n min-width: 100%;\n}\n.g-table__wrapper[data-v-33440b92] {\n /**\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\n */\n}\n.g-table__wrapper[data-v-33440b92] .ant-table-scroll .ant-table-header thead > tr > th.ant-table-fixed-columns-in-body.ant-table-row-cell-last,\n.g-table__wrapper[data-v-33440b92] .ant-table-scroll .ant-table-body tbody > tr > td.ant-table-fixed-columns-in-body.ant-table-row-cell-last {\n visibility: hidden;\n}\n.g-table__wrapper[data-v-33440b92] {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper[data-v-33440b92] .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n line-height: 38px;\n}\n.g-table__wrapper[data-v-33440b92] {\n /* 表头也统一高度和样式 */\n}\n.g-table__wrapper[data-v-33440b92] .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n line-height: 38px;\n}\n.g-table__wrapper[data-v-33440b92] {\n /* 分页区域固定在底部 */\n}\n.g-table__wrapper .g-table__pagination[data-v-33440b92] {\n display: flex;\n flex-direction: row;\n justify-content: end;\n border-top: unset;\n padding-top: 8px;\n padding-bottom: 8px;\n background: #fff;\n}\n.g-table__wrapper[data-v-33440b92] {\n /* 空数据状态顶部显示 */\n}\n.g-table__wrapper .g-table__no-data[data-v-33440b92] {\n position: relative;\n}\n.g-table__wrapper .g-table__no-data[data-v-33440b92] .ant-table-placeholder {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -40%);\n width: 100%;\n height: 100%;\n text-align: center;\n color: #999;\n font-size: 14px;\n font-weight: normal;\n line-height: 20px;\n overflow: hidden;\n border: unset;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["index.vue","E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\table\\src\\index.vue"],"names":[],"mappings":"AAAA,gBAAgB;ACshBhB;EACA;;;;;IAAA;AD/gBA;ACqhBA;;EAEA,eAAA;ADnhBA;AC0gBA;EAYA;;;;IAAA;AD/gBA;ACohBA;;EAEA,kBAAA;ADlhBA;AC+fA;EAsBA,sBAAA;ADlhBA;ACmhBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADjhBA;ACqfA;EA+BA,eAAA;ADjhBA;ACkhBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADhhBA;AC2eA;EAwCA,cAAA;ADhhBA;ACihBA;EACA,aAAA;EACA,mBAAA;EACA,oBAAA;EACA,iBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;AD/gBA;AC+dA;EAmDA,cAAA;AD/gBA;ACghBA;EACA,kBAAA;AD9gBA;AC+gBA;EACA,kBAAA;EACA,QAAA;EACA,SAAA;EACA,gCAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,gBAAA;EACA,aAAA;AD7gBA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["@charset \"UTF-8\";\n.g-table__wrapper {\n /**\n * 修复\"宽屏下表格两侧出现空白\"问题:\n * 当视口宽度大于表格内容宽度时,主表(ant-table-scroll)不会自动拉伸,\n * 而固定列(fixed-left/fixed-right)是 position:absolute 定位在容器边缘,中间就出现空白。\n * 解决方案:让主表的 table 元素 min-width:100%,使其始终填满滚动容器。\n */\n}\n.g-table__wrapper ::v-deep .ant-table-scroll .ant-table-header table,\n.g-table__wrapper ::v-deep .ant-table-scroll .ant-table-body table {\n min-width: 100%;\n}\n.g-table__wrapper {\n /**\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\n */\n}\n.g-table__wrapper ::v-deep .ant-table-scroll .ant-table-header thead > tr > th.ant-table-fixed-columns-in-body.ant-table-row-cell-last,\n.g-table__wrapper ::v-deep .ant-table-scroll .ant-table-body tbody > tr > td.ant-table-fixed-columns-in-body.ant-table-row-cell-last {\n visibility: hidden;\n}\n.g-table__wrapper {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper ::v-deep .ant-table-tbody > tr > td {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n line-height: 38px;\n}\n.g-table__wrapper {\n /* 表头也统一高度和样式 */\n}\n.g-table__wrapper ::v-deep .ant-table-thead > tr > th {\n height: 54px;\n padding: 8px 16px;\n vertical-align: middle;\n box-sizing: border-box;\n line-height: 38px;\n}\n.g-table__wrapper {\n /* 分页区域固定在底部 */\n}\n.g-table__wrapper .g-table__pagination {\n display: flex;\n flex-direction: row;\n justify-content: end;\n border-top: unset;\n padding-top: 8px;\n padding-bottom: 8px;\n background: #fff;\n}\n.g-table__wrapper {\n /* 空数据状态顶部显示 */\n}\n.g-table__wrapper .g-table__no-data {\n position: relative;\n}\n.g-table__wrapper .g-table__no-data ::v-deep .ant-table-placeholder {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -40%);\n width: 100%;\n height: 100%;\n text-align: center;\n color: #999;\n font-size: 14px;\n font-weight: normal;\n line-height: 20px;\n overflow: hidden;\n border: unset;\n}\n\n/*# sourceMappingURL=index.vue.map */","<template>\r\n <div class=\"g-table__wrapper\" ref=\"tableWrapper\" :style=\"wrapperStyle\">\r\n <a-table\r\n :key=\"tableRenderKey\"\r\n :bordered=\"bordered\"\r\n :class=\"[isNoData && 'g-table__no-data']\"\r\n :pagination=\"false\"\r\n :loading=\"loading\"\r\n size=\"middle\"\r\n :columns=\"smartColumns\"\r\n :row-selection=\"smartRowSelection\"\r\n :row-class-name=\"setRowClassName\"\r\n :data-source=\"dataSource\"\r\n :scroll=\"smartScroll\">\r\n <template slot=\"action\" slot-scope=\"record\">\r\n <Actions v-on=\"$listeners\" :data-source=\"actions\" :record=\"record\"></Actions>\r\n </template>\r\n </a-table>\r\n <div class=\"g-table__pagination\">\r\n <a-pagination\r\n :show-total=\"all => `共 ${all} 条数据`\"\r\n v-if=\"mode === 'default'\"\r\n show-size-changer \r\n show-quick-jumper\r\n :pageSize=\"innerPageSize\"\r\n :current=\"innerCurrentPage\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n @change=\"onChangePagination\"\r\n @showSizeChange=\"onShowSizeChange\"\r\n :total=\"total\">\r\n </a-pagination>\r\n <ele-pagination\r\n v-else\r\n :pageSize=\"innerPageSize\"\r\n :current=\"innerCurrentPage\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n :data=\"dataSource.length\"\r\n @change=\"onChangePagination\"\r\n @showSizeChange=\"onShowSizeChange\"\r\n :total=\"total\"\r\n ></ele-pagination>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport Actions from './action.vue'\r\nexport default {\r\n name: 'ele-table',\r\n components: {\r\n Actions\r\n },\r\n props: {\r\n mode: {\r\n type: String,\r\n default: 'default',\r\n validator: (value) => {\r\n return ['default', 'next-cursor'].includes(value)\r\n }\r\n },\r\n // ant table wrapper\r\n height: {\r\n type: Number\r\n },\r\n width: {\r\n type: Number\r\n },\r\n x: {\r\n // ant-design-vue: scroll.x 支持 number | string(如 'max-content')\r\n type: [Number, String],\r\n default: 1200\r\n },\r\n y: {\r\n type: Number,\r\n default: 200\r\n },\r\n scroll: {\r\n type: Object\r\n },\r\n rowSelection: {\r\n type: Object\r\n },\r\n actions: {\r\n type: Array,\r\n default: () => []\r\n },\r\n total: {\r\n type: Number,\r\n default: 0\r\n },\r\n loading: {\r\n type: Boolean,\r\n default: false\r\n },\r\n columns: {\r\n type: Array,\r\n default: () => []\r\n },\r\n dataSource: {\r\n type: Array,\r\n default: () => []\r\n },\r\n pageSize: {\r\n type: [Number, String],\r\n default: 10\r\n },\r\n pageSizeOptions: {\r\n type: Array,\r\n default: () => ['10', '20', '30', '40']\r\n },\r\n bordered: {\r\n type: Boolean,\r\n default: true\r\n }\r\n },\r\n data() {\r\n return {\r\n tableHeaderHeight: 0,\r\n paginationHeight: 0,\r\n innerPageSize: 10,\r\n innerCurrentPage: 1,\r\n tableContentHeight: 0,\r\n obs: [],\r\n // 容器宽度,用于智能判断是否需要 fixed 列\r\n containerWidth: 0,\r\n // 用于强制重新渲染表格(当 fixed 列状态切换时)\r\n tableRenderKey: 0\r\n }\r\n },\r\n computed: {\r\n wrapperStyle () {\r\n // 外层容器样式\r\n if (!this.height) return {}\r\n return { height: `${this.height}px` }\r\n },\r\n needScrollY () {\r\n // 判断是否需要 y 轴滚动:基于数据行数与可用高度预估\r\n if (!this.height) return false\r\n \r\n const availableHeight = this.getScrollHeightByHeight\r\n if (availableHeight <= 0) return false\r\n \r\n // 预估每行高度(包含边框),antd 默认约 54px\r\n const estimatedRowHeight = 54\r\n const estimatedTableHeight = this.dataSource.length * estimatedRowHeight\r\n \r\n return estimatedTableHeight > availableHeight\r\n },\r\n innerColumns () {\r\n return this.columns.filter(col => !Object.keys(col).includes('multiple'))\r\n },\r\n isNoData () {\r\n return !this.dataSource.length\r\n },\r\n getScrollHeightByHeight () {\r\n // 始终返回可用的剩余高度,让表格内容不足时也能占满容器\r\n return this.height - this.tableHeaderHeight - this.paginationHeight\r\n },\r\n isFlexColumn () {\r\n return this.columns.every(col => !col.width)\r\n },\r\n /**\r\n * 计算所有列的总宽度(包括 rowSelection 的 checkbox 列和操作列)\r\n */\r\n totalColumnsWidth () {\r\n const cols = this.innerColumns || []\r\n let total = cols.reduce((sum, col) => {\r\n const w = col && col.width\r\n return sum + (typeof w === 'number' ? w : 0)\r\n }, 0)\r\n // rowSelection 的 checkbox/radio 列,antd 默认约 60px\r\n if (this.rowSelection) total += 60\r\n // 操作列(operations)的宽度\r\n if (this.operations && this.operations.width && typeof this.operations.width === 'number') {\r\n total += this.operations.width\r\n }\r\n return total\r\n },\r\n /**\r\n * 是否需要横向滚动:容器宽度 < 列总宽度\r\n */\r\n needHorizontalScroll () {\r\n // 未获取容器宽度前,先假定需要滚动(保守策略)\r\n if (!this.containerWidth) return true\r\n // 加一点容差\r\n return this.containerWidth < this.totalColumnsWidth - 5\r\n },\r\n /**\r\n * 智能列配置:\r\n * - 当需要横向滚动时,保留原始 fixed 属性\r\n * - 当容器足够宽时,移除 fixed 属性,让表格自动铺满\r\n */\r\n smartColumns () {\r\n if (this.needHorizontalScroll) {\r\n // 需要滚动,保留原始配置\r\n return this.innerColumns\r\n }\r\n // 不需要滚动,移除所有 fixed 属性\r\n return this.innerColumns.map(col => {\r\n if (col.fixed) {\r\n const { fixed, ...rest } = col\r\n return rest\r\n }\r\n return col\r\n })\r\n },\r\n /**\r\n * 智能 scroll 配置:\r\n * - 当需要横向滚动时,设置 scroll.x\r\n * - 当容器足够宽时,不设置 scroll.x,避免产生空白区域\r\n */\r\n smartScroll () {\r\n if (!this.needHorizontalScroll) {\r\n // 不需要横向滚动,只保留 y 方向(如果需要)\r\n if (this.height && this.needScrollY) {\r\n const availableHeight = this.tableHeaderHeight && this.paginationHeight \r\n ? this.getScrollHeightByHeight \r\n : this.height - 100\r\n if (availableHeight > 50) {\r\n return { y: availableHeight }\r\n }\r\n }\r\n return {}\r\n }\r\n // 需要横向滚动,使用原有逻辑\r\n return this.getScroll\r\n },\r\n /**\r\n * 智能 rowSelection 配置:\r\n * - 当需要横向滚动时,保留原始 fixed 属性\r\n * - 当容器足够宽时,移除 fixed 属性\r\n */\r\n smartRowSelection () {\r\n if (!this.rowSelection) return null\r\n if (this.needHorizontalScroll) {\r\n return this.rowSelection\r\n }\r\n // 不需要滚动,移除 fixed 属性\r\n if (this.rowSelection.fixed) {\r\n const { fixed, ...rest } = this.rowSelection\r\n return rest\r\n }\r\n return this.rowSelection\r\n },\r\n getScroll () {\r\n if (this.scroll) {\r\n return this.scroll\r\n } else {\r\n // 固定列需要 scroll.x 才能正确同步行高,始终设置一个有效值\r\n let baseX = (this.x === '' || this.x === null || this.x === undefined) ? 1200 : this.x\r\n\r\n // 解决“x 给太大导致操作列前出现大块空白”的问题:\r\n // 当所有列都给了明确 width 时,scroll.x 取列宽总和最合理;大于总和会产生多余区域。\r\n if (typeof baseX === 'number') {\r\n const cols = this.innerColumns || []\r\n const total = cols.reduce((sum, col) => {\r\n const w = col && col.width\r\n return sum + (typeof w === 'number' ? w : 0)\r\n }, 0)\r\n\r\n // rowSelection 的 checkbox/radio 列是 antd 自动加的,给一个经验宽度避免误差\r\n const selectionWidth = this.rowSelection ? 60 : 0\r\n\r\n // 只有当所有列都明确给了宽度(total > 0)时才 clamp\r\n if (total > 0) {\r\n const minX = total + selectionWidth\r\n if (baseX > minX) baseX = minX\r\n }\r\n }\r\n \r\n if (this.height && this.needScrollY) {\r\n const availableHeight = this.tableHeaderHeight && this.paginationHeight \r\n ? this.getScrollHeightByHeight \r\n : this.height - 100\r\n \r\n if (availableHeight > 50) {\r\n return { x: baseX, y: availableHeight }\r\n }\r\n }\r\n return { x: baseX }\r\n }\r\n }\r\n },\r\n watch: {\r\n pageSize: {\r\n handler (pageSize) {\r\n this.innerPageSize = pageSize\r\n },\r\n immediate: true\r\n },\r\n /**\r\n * 监听 needHorizontalScroll 变化,强制重新渲染表格\r\n * 当从\"需要固定列\"切换到\"不需要固定列\"或反之时,antd 的 a-table 需要完全重新渲染\r\n */\r\n needHorizontalScroll (newVal, oldVal) {\r\n if (newVal !== oldVal) {\r\n // 更新 key 强制 Vue 销毁并重建 a-table 组件\r\n this.tableRenderKey++\r\n // 使用重试机制确保固定列完全渲染\r\n this.$nextTick(() => {\r\n this.retrySyncFixedColumns(newVal)\r\n })\r\n }\r\n }\r\n },\r\n methods: {\r\n onShowSizeChange (current, pageSize) {\r\n this.innerCurrentPage = current\r\n this.innerPageSize = pageSize\r\n this.$emit('change-page', current, pageSize)\r\n },\r\n setPaginationHeight () {\r\n this.$nextTick(() => {\r\n const el = this.$el.querySelector('.g-table__pagination')\r\n if (el) {\r\n const { height } = el.getBoundingClientRect()\r\n this.paginationHeight = height\r\n }\r\n })\r\n },\r\n setTableHeaderHeight () {\r\n this.$nextTick(() => {\r\n const el = this.$el.querySelector('.ant-table-header')\r\n if (!el) return\r\n const { height } = el.getBoundingClientRect()\r\n this.tableHeaderHeight = height\r\n })\r\n },\r\n setTableTbodyHeight () {\r\n this.$nextTick(() => {\r\n this.setTableHeaderHeight()\r\n })\r\n },\r\n setRowClassName (record, idx) {\r\n return idx % 2 === 0 ? 'g-table__row--even' : 'g-table__row--odd'\r\n },\r\n onChangePagination (page, pageSize) {\r\n this.innerCurrentPage = page\r\n this.innerPageSize = pageSize\r\n this.$emit('change-page', page, pageSize)\r\n },\r\n syncFixedColumns () {\r\n // 强制 ant-design-vue 重新计算固定列的宽度\r\n this.$nextTick(() => {\r\n const tableEl = this.$el.querySelector('.ant-table')\r\n if (tableEl) {\r\n // 触发窗口 resize 事件,让 ant-design-vue 重新计算\r\n window.dispatchEvent(new Event('resize'))\r\n }\r\n })\r\n },\r\n /**\r\n * 带重试的固定列同步机制\r\n * antd 的 a-table 在 key 变化后重新渲染固定列需要一定时间,\r\n * 这里通过重试确保固定列渲染完成后再同步。\r\n * @param {boolean} needFixed - 是否需要固定列\r\n * @param {number} retries - 当前重试次数\r\n */\r\n retrySyncFixedColumns (needFixed, retries = 0) {\r\n const MAX_RETRIES = 5\r\n const DELAY = 100 // 每次延迟 100ms\r\n\r\n setTimeout(() => {\r\n this.syncFixedColumns()\r\n this.syncHeaderTableWidth()\r\n this.bindScrollSync()\r\n\r\n // 如果需要固定列,检查是否已渲染\r\n if (needFixed && retries < MAX_RETRIES) {\r\n const hasFixedLeft = this.$el.querySelector('.ant-table-fixed-left .ant-table-body tbody tr')\r\n const hasFixedRight = this.$el.querySelector('.ant-table-fixed-right .ant-table-body tbody tr')\r\n \r\n // 如果有固定列配置但还没渲染出来,继续重试\r\n const hasFixedConfig = this.innerColumns.some(c => c.fixed) || (this.rowSelection && this.rowSelection.fixed)\r\n if (hasFixedConfig && !hasFixedLeft && !hasFixedRight) {\r\n this.retrySyncFixedColumns(needFixed, retries + 1)\r\n }\r\n }\r\n }, DELAY)\r\n },\r\n /**\r\n * 修复 x: 'max-content' 场景下表头不跟着横向滚动的问题。\r\n * 原因:ant-design-vue 的 header table 和 body table 各自算 max-content,\r\n * header 按表头文字算、body 按实际数据算,二者宽度不同时 header 就\"滚不动\"。\r\n * 方案:数据渲染后把 header table 的 width 强制设成和 body table 一样。\r\n */\r\n syncHeaderTableWidth () {\r\n this.$nextTick(() => {\r\n const headerTable = this.$el.querySelector('.ant-table-scroll .ant-table-header table')\r\n const bodyTable = this.$el.querySelector('.ant-table-scroll .ant-table-body table')\r\n if (!headerTable || !bodyTable) return\r\n\r\n const bodyW = bodyTable.getBoundingClientRect().width\r\n const headerW = headerTable.getBoundingClientRect().width\r\n\r\n // 始终同步表头宽度到表体宽度,确保窗口变大/变小时都能正确响应\r\n // 只有当宽度差异超过 2px 时才更新,避免频繁设置样式\r\n if (Math.abs(bodyW - headerW) > 2) {\r\n headerTable.style.width = `${bodyW}px`\r\n headerTable.style.minWidth = `${bodyW}px`\r\n }\r\n })\r\n },\r\n /**\r\n * 监听表体横向滚动,同步到表头(防止 antd 自带同步失效)\r\n */\r\n bindScrollSync () {\r\n const body = this.$el.querySelector('.ant-table-scroll .ant-table-body')\r\n const header = this.$el.querySelector('.ant-table-scroll .ant-table-header')\r\n if (!body || !header) return\r\n\r\n if (this._scrollHandler) return // 已绑定\r\n this._scrollHandler = () => {\r\n header.scrollLeft = body.scrollLeft\r\n }\r\n body.addEventListener('scroll', this._scrollHandler, { passive: true })\r\n },\r\n unbindScrollSync () {\r\n const body = this.$el.querySelector('.ant-table-scroll .ant-table-body')\r\n if (body && this._scrollHandler) {\r\n body.removeEventListener('scroll', this._scrollHandler)\r\n this._scrollHandler = null\r\n }\r\n },\r\n /**\r\n * 测量容器宽度\r\n */\r\n measureContainerWidth () {\r\n const wrapper = this.$refs.tableWrapper\r\n if (wrapper) {\r\n this.containerWidth = wrapper.clientWidth\r\n }\r\n },\r\n /**\r\n * 使用 ResizeObserver 监听容器宽度变化\r\n */\r\n observeContainerWidth () {\r\n const wrapper = this.$refs.tableWrapper\r\n if (!wrapper || typeof ResizeObserver === 'undefined') return\r\n\r\n if (this._containerResizeObserver) return // 已绑定\r\n\r\n this._containerResizeObserver = new ResizeObserver((entries) => {\r\n for (const entry of entries) {\r\n const newWidth = entry.contentRect.width\r\n // 只有宽度变化超过阈值才触发更新,避免微小变化导致频繁重渲染\r\n if (Math.abs(newWidth - this.containerWidth) > 10) {\r\n const oldNeedScroll = this.needHorizontalScroll\r\n this.containerWidth = newWidth\r\n // 容器宽度变化会触发 needHorizontalScroll 的重新计算\r\n // needHorizontalScroll 的 watcher 会处理表格重新渲染和列宽同步\r\n // 这里不需要直接调用 syncFixedColumns,避免与 watcher 的执行时机冲突\r\n // 无论 needHorizontalScroll 是否变化,都立即同步表头宽度\r\n // 确保窗口变大/变小时表头都能及时响应\r\n this.$nextTick(() => {\r\n // 使用 requestAnimationFrame 确保在浏览器重绘后同步表头宽度\r\n // 这样能确保表体宽度已经更新完成\r\n requestAnimationFrame(() => {\r\n this.syncHeaderTableWidth()\r\n })\r\n \r\n // 如果 needHorizontalScroll 状态没有变化,说明只是列宽需要调整,直接同步\r\n // 如果状态变化了,watcher 会处理重新渲染\r\n if (this.needHorizontalScroll === oldNeedScroll) {\r\n // 防抖:延迟同步固定列,避免频繁调用\r\n if (this._resizeDebounceTimer) {\r\n clearTimeout(this._resizeDebounceTimer)\r\n }\r\n this._resizeDebounceTimer = setTimeout(() => {\r\n this.syncFixedColumns()\r\n this.syncHeaderTableWidth()\r\n this.bindScrollSync()\r\n }, 150)\r\n }\r\n })\r\n }\r\n }\r\n })\r\n this._containerResizeObserver.observe(wrapper)\r\n },\r\n /**\r\n * 断开容器宽度监听\r\n */\r\n unobserveContainerWidth () {\r\n if (this._containerResizeObserver) {\r\n this._containerResizeObserver.disconnect()\r\n this._containerResizeObserver = null\r\n }\r\n // 清理防抖定时器\r\n if (this._resizeDebounceTimer) {\r\n clearTimeout(this._resizeDebounceTimer)\r\n this._resizeDebounceTimer = null\r\n }\r\n }\r\n },\r\n mounted() {\r\n this.$nextTick(() => {\r\n // 先测量容器宽度,用于智能判断是否需要 fixed 列\r\n this.measureContainerWidth()\r\n this.observeContainerWidth()\r\n\r\n this.setPaginationHeight()\r\n setTimeout(() => {\r\n this.setTableTbodyHeight()\r\n this.setPaginationHeight()\r\n // 强制同步固定列和主表的列宽\r\n this.syncFixedColumns()\r\n // 同步表头 table 宽度(修复 max-content 场景)\r\n this.syncHeaderTableWidth()\r\n // 绑定横向滚动同步\r\n this.bindScrollSync()\r\n }, 200)\r\n })\r\n \r\n // 监听数据变化,重新同步列宽\r\n this.$watch('dataSource', () => {\r\n this.$nextTick(() => {\r\n setTimeout(() => {\r\n this.syncFixedColumns()\r\n this.syncHeaderTableWidth()\r\n this.bindScrollSync()\r\n }, 100)\r\n })\r\n }, { deep: true })\r\n },\r\n destroyed () {\r\n this.obs.forEach(ob => ob.disconnect())\r\n this.unbindScrollSync()\r\n this.unobserveContainerWidth()\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.g-table__wrapper {\r\n /**\r\n * 修复\"宽屏下表格两侧出现空白\"问题:\r\n * 当视口宽度大于表格内容宽度时,主表(ant-table-scroll)不会自动拉伸,\r\n * 而固定列(fixed-left/fixed-right)是 position:absolute 定位在容器边缘,中间就出现空白。\r\n * 解决方案:让主表的 table 元素 min-width:100%,使其始终填满滚动容器。\r\n */\r\n ::v-deep .ant-table-scroll .ant-table-header table,\r\n ::v-deep .ant-table-scroll .ant-table-body table {\r\n min-width: 100%;\r\n }\r\n\r\n /**\r\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\r\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\r\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\r\n */\r\n ::v-deep .ant-table-scroll .ant-table-header thead > tr > th.ant-table-fixed-columns-in-body.ant-table-row-cell-last,\r\n ::v-deep .ant-table-scroll .ant-table-body tbody > tr > td.ant-table-fixed-columns-in-body.ant-table-row-cell-last {\r\n visibility: hidden;\r\n }\r\n\r\n /* 强制统一行高,确保主表和固定列对齐 */\r\n ::v-deep .ant-table-tbody > tr > td {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n line-height: 38px;\r\n }\r\n\r\n /* 表头也统一高度和样式 */\r\n ::v-deep .ant-table-thead > tr > th {\r\n height: 54px;\r\n padding: 8px 16px;\r\n vertical-align: middle;\r\n box-sizing: border-box;\r\n line-height: 38px;\r\n }\r\n\r\n /* 分页区域固定在底部 */\r\n .g-table__pagination {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: end;\r\n border-top: unset;\r\n padding-top: 8px;\r\n padding-bottom: 8px;\r\n background: #fff;\r\n }\r\n\r\n /* 空数据状态顶部显示 */\r\n .g-table__no-data {\r\n position: relative;\r\n ::v-deep .ant-table-placeholder {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -40%);\r\n width: 100%;\r\n height: 100%;\r\n text-align: center;\r\n color: #999;\r\n font-size: 14px;\r\n font-weight: normal;\r\n line-height: 20px;\r\n overflow: hidden;\r\n border: unset;\r\n }\r\n }\r\n}\r\n</style>"]}, media: undefined });
|
|
2395
2704
|
|
|
2396
2705
|
};
|
|
2397
2706
|
/* scoped */
|
|
2398
|
-
const __vue_scope_id__$D = "data-v-
|
|
2707
|
+
const __vue_scope_id__$D = "data-v-33440b92";
|
|
2399
2708
|
/* module identifier */
|
|
2400
2709
|
const __vue_module_identifier__$D = undefined;
|
|
2401
2710
|
/* functional template */
|
|
@@ -2690,6 +2999,7 @@ var script$B = {
|
|
|
2690
2999
|
total: 0,
|
|
2691
3000
|
tableQuerys: {},
|
|
2692
3001
|
resizeObserverModelTableWrapper: null,
|
|
3002
|
+
resizeObserverModelTableContainer: null,
|
|
2693
3003
|
modelTableWrapperHeight: 0,
|
|
2694
3004
|
currentTreeNodeData: {},
|
|
2695
3005
|
currentRowData: {},
|
|
@@ -2940,6 +3250,19 @@ var script$B = {
|
|
|
2940
3250
|
} = this.tableMeta;
|
|
2941
3251
|
return page.pageSize || 10;
|
|
2942
3252
|
},
|
|
3253
|
+
nextCursorConfig() {
|
|
3254
|
+
if (this.mode == 'next-cursor') {
|
|
3255
|
+
const {
|
|
3256
|
+
page = {}
|
|
3257
|
+
} = this.tableMeta;
|
|
3258
|
+
const {
|
|
3259
|
+
nextCursor = {}
|
|
3260
|
+
} = page;
|
|
3261
|
+
return nextCursor;
|
|
3262
|
+
} else {
|
|
3263
|
+
return void 0;
|
|
3264
|
+
}
|
|
3265
|
+
},
|
|
2943
3266
|
mode() {
|
|
2944
3267
|
const {
|
|
2945
3268
|
page = {}
|
|
@@ -3409,6 +3732,23 @@ var script$B = {
|
|
|
3409
3732
|
};
|
|
3410
3733
|
});
|
|
3411
3734
|
});
|
|
3735
|
+
if (this.nextCursorConfig) {
|
|
3736
|
+
const {
|
|
3737
|
+
count: {
|
|
3738
|
+
url: countUrl,
|
|
3739
|
+
requestType = 'GET',
|
|
3740
|
+
params = {},
|
|
3741
|
+
fieldMap = {}
|
|
3742
|
+
}
|
|
3743
|
+
} = this.nextCursorConfig;
|
|
3744
|
+
const countRet = await net[requestType.toLowerCase()](countUrl, Object.assign({}, this.tableQuerys, params)).then(resp => {
|
|
3745
|
+
const {
|
|
3746
|
+
data = 0
|
|
3747
|
+
} = resp || {};
|
|
3748
|
+
return data;
|
|
3749
|
+
});
|
|
3750
|
+
this.total = countRet;
|
|
3751
|
+
}
|
|
3412
3752
|
this.cleanCurrentModelEffect(false); // 不清空 currentRowData,除非明确需要
|
|
3413
3753
|
this.tableData = ret;
|
|
3414
3754
|
return ret;
|
|
@@ -3416,28 +3756,54 @@ var script$B = {
|
|
|
3416
3756
|
calculateTableHeight() {
|
|
3417
3757
|
const currentViewportHeight = window.innerHeight;
|
|
3418
3758
|
const tableRef = this.$refs[this.tableRef];
|
|
3759
|
+
if (!tableRef || !tableRef.$el) return;
|
|
3419
3760
|
const {
|
|
3420
3761
|
top: tableToTop,
|
|
3421
3762
|
width
|
|
3422
3763
|
} = tableRef.$el.getBoundingClientRect();
|
|
3423
3764
|
this.tableWidth = width;
|
|
3424
|
-
|
|
3765
|
+
|
|
3766
|
+
// 获取分页组件的高度(如果存在)
|
|
3767
|
+
let paginationHeight = 0;
|
|
3768
|
+
const paginationEl = tableRef.$el.querySelector('.g-table__pagination');
|
|
3769
|
+
if (paginationEl) {
|
|
3770
|
+
paginationHeight = paginationEl.getBoundingClientRect().height || 50;
|
|
3771
|
+
} else {
|
|
3772
|
+
// 如果分页组件还未渲染,使用默认高度
|
|
3773
|
+
paginationHeight = 50;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
// 计算表格高度:视口高度 - 表格顶部距离 - 分页高度 - 额外高度
|
|
3777
|
+
const calculatedHeight = currentViewportHeight - tableToTop - paginationHeight - this.overHeight - 20;
|
|
3778
|
+
// 确保最小高度,避免表格过小
|
|
3779
|
+
this.tableHeight = Math.max(calculatedHeight, 200);
|
|
3425
3780
|
},
|
|
3426
3781
|
calculateTreeHeight() {
|
|
3427
3782
|
if (!this.showTree) return;
|
|
3428
3783
|
const modelTableContainerRef = this.$refs[this.modelTableContainerRef];
|
|
3784
|
+
if (!modelTableContainerRef) return;
|
|
3429
3785
|
const {
|
|
3430
3786
|
height
|
|
3431
3787
|
} = modelTableContainerRef.getBoundingClientRect();
|
|
3788
|
+
// 确保树的高度和表格容器高度一致
|
|
3432
3789
|
this.treeWrapperHeight = height;
|
|
3790
|
+
|
|
3791
|
+
// 如果表格容器有标题,需要减去标题高度
|
|
3792
|
+
const titleEl = modelTableContainerRef.querySelector('.model__table--title');
|
|
3793
|
+
if (titleEl) {
|
|
3794
|
+
const titleHeight = titleEl.getBoundingClientRect().height;
|
|
3795
|
+
this.treeWrapperHeight = height - titleHeight;
|
|
3796
|
+
}
|
|
3433
3797
|
},
|
|
3434
3798
|
async keepAliveRefresh() {
|
|
3435
3799
|
// 重新计算表格高度(应对窗口大小变化)
|
|
3436
3800
|
this.$nextTick(() => {
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
this.
|
|
3440
|
-
|
|
3801
|
+
setTimeout(() => {
|
|
3802
|
+
this.calculateTableHeight();
|
|
3803
|
+
if (this.showTree) {
|
|
3804
|
+
this.calculateTreeHeight();
|
|
3805
|
+
}
|
|
3806
|
+
}, 200);
|
|
3441
3807
|
});
|
|
3442
3808
|
// 刷新列表数据
|
|
3443
3809
|
const {
|
|
@@ -3455,21 +3821,73 @@ var script$B = {
|
|
|
3455
3821
|
}
|
|
3456
3822
|
},
|
|
3457
3823
|
mounted() {
|
|
3458
|
-
|
|
3824
|
+
// 初始化时先设置一个默认高度,避免布局混乱
|
|
3825
|
+
this.tableHeight = 400;
|
|
3826
|
+
if (this.showTree) {
|
|
3827
|
+
this.treeWrapperHeight = 400;
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
// 延迟计算,确保所有组件都已渲染
|
|
3459
3831
|
this.$nextTick(() => {
|
|
3460
|
-
|
|
3832
|
+
setTimeout(() => {
|
|
3833
|
+
this.calculateTableHeight();
|
|
3834
|
+
this.calculateTreeHeight();
|
|
3835
|
+
}, 200);
|
|
3461
3836
|
});
|
|
3837
|
+
|
|
3838
|
+
// 使用 ResizeObserver 监听容器大小变化
|
|
3462
3839
|
this.resizeObserverModelTableWrapper = new ResizeObserver(entries => {
|
|
3463
3840
|
for (const _ of entries) {
|
|
3464
3841
|
requestAnimationFrame(() => {
|
|
3465
|
-
|
|
3842
|
+
// 延迟重新计算,确保分页组件高度已更新
|
|
3843
|
+
setTimeout(() => {
|
|
3844
|
+
this.calculateTableHeight();
|
|
3845
|
+
if (this.showTree) {
|
|
3846
|
+
this.calculateTreeHeight();
|
|
3847
|
+
}
|
|
3848
|
+
}, 100);
|
|
3466
3849
|
});
|
|
3467
3850
|
}
|
|
3468
3851
|
});
|
|
3469
|
-
|
|
3852
|
+
if (this.$refs[this.modelTableWrapper]) {
|
|
3853
|
+
this.resizeObserverModelTableWrapper.observe(this.$refs[this.modelTableWrapper]);
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
// 监听表格容器大小变化(用于同步树高度)
|
|
3857
|
+
if (this.showTree && this.$refs[this.modelTableContainerRef]) {
|
|
3858
|
+
this.resizeObserverModelTableContainer = new ResizeObserver(entries => {
|
|
3859
|
+
for (const _ of entries) {
|
|
3860
|
+
requestAnimationFrame(() => {
|
|
3861
|
+
this.calculateTreeHeight();
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
});
|
|
3865
|
+
this.resizeObserverModelTableContainer.observe(this.$refs[this.modelTableContainerRef]);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
// 监听窗口大小变化
|
|
3869
|
+
this.handleResize = () => {
|
|
3870
|
+
this.$nextTick(() => {
|
|
3871
|
+
setTimeout(() => {
|
|
3872
|
+
this.calculateTableHeight();
|
|
3873
|
+
if (this.showTree) {
|
|
3874
|
+
this.calculateTreeHeight();
|
|
3875
|
+
}
|
|
3876
|
+
}, 100);
|
|
3877
|
+
});
|
|
3878
|
+
};
|
|
3879
|
+
window.addEventListener('resize', this.handleResize);
|
|
3470
3880
|
},
|
|
3471
3881
|
destroyed() {
|
|
3472
|
-
this.resizeObserverModelTableWrapper
|
|
3882
|
+
if (this.resizeObserverModelTableWrapper) {
|
|
3883
|
+
this.resizeObserverModelTableWrapper.disconnect();
|
|
3884
|
+
}
|
|
3885
|
+
if (this.resizeObserverModelTableContainer) {
|
|
3886
|
+
this.resizeObserverModelTableContainer.disconnect();
|
|
3887
|
+
}
|
|
3888
|
+
if (this.handleResize) {
|
|
3889
|
+
window.removeEventListener('resize', this.handleResize);
|
|
3890
|
+
}
|
|
3473
3891
|
if (this.model) {
|
|
3474
3892
|
// 清理订阅
|
|
3475
3893
|
if (this.unsubscribe) {
|
|
@@ -3596,26 +4014,31 @@ var __vue_render__$B = function () {
|
|
|
3596
4014
|
_c(
|
|
3597
4015
|
"ele-table",
|
|
3598
4016
|
_vm._g(
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
4017
|
+
_vm._b(
|
|
4018
|
+
{
|
|
4019
|
+
ref: _vm.tableRef,
|
|
4020
|
+
attrs: {
|
|
4021
|
+
"row-selection": _vm.rowSelection,
|
|
4022
|
+
loading: _vm.loading,
|
|
4023
|
+
columns: _vm.columns,
|
|
4024
|
+
total: _vm.total,
|
|
4025
|
+
x: _vm.x,
|
|
4026
|
+
y: _vm.y,
|
|
4027
|
+
bordered: _vm.setBorder,
|
|
4028
|
+
height: _vm.tableHeight,
|
|
4029
|
+
width: _vm.tableWidth,
|
|
4030
|
+
actions: _vm.actions,
|
|
4031
|
+
pageSize: _vm.pageSize,
|
|
4032
|
+
pageSizeOptions: _vm.pageSizeOptions,
|
|
4033
|
+
"data-source": _vm.tableData,
|
|
4034
|
+
mode: _vm.mode,
|
|
4035
|
+
},
|
|
4036
|
+
on: { "change-page": _vm.onChangePage },
|
|
3616
4037
|
},
|
|
3617
|
-
|
|
3618
|
-
|
|
4038
|
+
"ele-table",
|
|
4039
|
+
_vm.pageConfig,
|
|
4040
|
+
false
|
|
4041
|
+
),
|
|
3619
4042
|
_vm.overrideTableEvent
|
|
3620
4043
|
)
|
|
3621
4044
|
),
|
|
@@ -3680,11 +4103,11 @@ __vue_render__$B._withStripped = true;
|
|
|
3680
4103
|
/* style */
|
|
3681
4104
|
const __vue_inject_styles__$B = function (inject) {
|
|
3682
4105
|
if (!inject) return
|
|
3683
|
-
inject("data-v-95429590_0", { source: ".ele.model__tree-table[data-v-95429590] {\n background: transparent;\n display: flex;\n flex-direction: row;\n width: 100%;\n}\n.ele.model__tree-table .model__tree-table--container .model__tree--wrapper[data-v-95429590] {\n width: 240px;\n background: #fff;\n flex-shrink: 0;\n padding: 16px;\n box-sizing: border-box;\n margin-right: 16px;\n overflow-y: auto;\n}\n.ele.model__tree-table .model__table--container[data-v-95429590] {\n width: 100%;\n min-width: 0;\n background: #fff;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--bar[data-v-95429590] {\n width: 100%;\n height: 8px;\n background: var(--idooel-primary-color);\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--text[data-v-95429590] {\n text-align: left;\n padding: 16px;\n font-size: 16px;\n font-weight: bold;\n background: #fff;\n border-bottom: 1px solid;\n border-color: var(--idoole-black-016);\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper[data-v-95429590] {\n background: #fff;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .button-row__area[data-v-95429590] {\n width: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n padding-top: 16px;\n padding-bottom: 8px;\n padding-right: 16px;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper .fsm[data-v-95429590] {\n cursor: pointer;\n color: var(--idooel-primary-color);\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\models\\tree-table-model\\src\\index.vue","index.vue"],"names":[],"mappings":"AA8wBA;EACA,uBAAA;EACA,aAAA;EACA,mBAAA;EACA,WAAA;AC7wBA;AD+wBA;EACA,YAAA;EACA,gBAAA;EACA,cAAA;EACA,aAAA;EACA,sBAAA;EACA,kBAAA;EACA,gBAAA;AC7wBA;ADgxBA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;AC9wBA;ADgxBA;EACA,WAAA;EACA,WAAA;EACA,uCAAA;EACA,2BAAA;EACA,4BAAA;AC9wBA;ADgxBA;EACA,gBAAA;EACA,aAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,qCAAA;AC9wBA;ADixBA;EACA,gBAAA;AC/wBA;ADgxBA;EACA,WAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,8BAAA;EACA,iBAAA;EACA,mBAAA;EACA,mBAAA;AC9wBA;ADixBA;EACA,eAAA;EACA,kCAAA;AC/wBA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\r\n <section class=\"ele model__tree-table\">\r\n <section class=\"model__tree-table--container\" v-if=\"showTree\">\r\n <div class=\"model__tree--title\"></div>\r\n <section :ref=\"modelTreeWrapper\" class=\"model__tree--wrapper\" :style=\"{height: `${treeWrapperHeight}px`}\">\r\n <ele-tree\r\n :tree-data=\"treeData\"\r\n :defaultExpandedKeys=\"defaultExpandedKeys\"\r\n :defaultSelectedKeys=\"defaultSelectedKeys\"\r\n @select=\"selectTreeNode\"\r\n :replace-fields=\"mapFields\">\r\n </ele-tree>\r\n </section>\r\n </section>\r\n <section class=\"model__table--container\" :ref=\"modelTableContainerRef\">\r\n <div class=\"model__table--title\" v-if=\"title\">\r\n <template v-if=\"titleMode\">\r\n <div :class=\"[`model__table-title--${titleMode}`]\"></div>\r\n </template>\r\n <template v-else>\r\n <div class=\"model__table-title--text\">{{ title }}</div>\r\n </template>\r\n </div>\r\n <section :ref=\"modelTableWrapper\" class=\"model__table--wrapper\">\r\n <ele-search-area :ref=\"searchArea\" @search=\"onSearch\" :data-source=\"searchMeta.elements\"></ele-search-area>\r\n <div class=\"button-row__area\">\r\n <ele-button-group class=\"model-table__button-group\" v-on=\"overrideButtonGroupEvent\" :ref=\"buttonGroup\" @click=\"handleClickButtonGroup\" :data-source=\"getButtonGroupElements\"></ele-button-group>\r\n <slot name=\"tags\"></slot>\r\n <slot v-if=\"$slots['sub-center']\" name=\"sub-center\"></slot>\r\n </div>\r\n <ele-table\r\n v-on=\"overrideTableEvent\"\r\n :ref=\"tableRef\"\r\n :row-selection=\"rowSelection\"\r\n :loading=\"loading\" \r\n :columns=\"columns\"\r\n :total=\"total\"\r\n :x=\"x\"\r\n :y=\"y\"\r\n :bordered=\"setBorder\"\r\n :height=\"tableHeight\"\r\n :width=\"tableWidth\"\r\n :actions=\"actions\"\r\n :pageSize=\"pageSize\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n :data-source=\"tableData\"\r\n :mode=\"mode\"\r\n @change-page=\"onChangePage\"\r\n ></ele-table>\r\n </section>\r\n </section>\r\n <ele-modal-form v-model=\"modalFormValue\" v-on=\"overrideModalFormEvent\" :meta=\"modalFormMeta\"></ele-modal-form>\r\n <ele-modal-fsm v-model=\"showFsmModal\" :contextProp=\"fsmContextProp\" :meta=\"fsmMeta\" @cancel=\"handleCloseFsmModal\"></ele-modal-fsm>\r\n <ele-modal-table\r\n :meta=\"modalTableMeta\"\r\n v-model=\"modalTableValue\"\r\n v-on=\"overrideModalTableEvent\"\r\n ></ele-modal-table>\r\n </section>\r\n</template>\r\n\r\n<script>\r\nimport { type, net } from '@idooel/shared'\r\nimport { v4 as uuidv4 } from 'uuid'\r\nimport { BUILT_IN_EVENT_NAMES, RESERVE_EVENT_NAMES, parseFieldMap, BUILT_IN_TRIGGER, CONTEXT } from '../../../utils'\r\nimport { createTreeTableModel } from '../../../utils/runtime-context'\r\nexport default {\r\n name: 'ele-tree-table-model',\r\n props: {\r\n title: {\r\n type: [Object, String]\r\n },\r\n overHeight: {\r\n type: Number,\r\n default: 0\r\n },\r\n treeMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n searchMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n buttonGroupMeta: {\r\n typeof: Object,\r\n default: () => ({})\r\n },\r\n tableMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n createMeta: {\r\n type: Object\r\n },\r\n editMeta: {\r\n type: Object\r\n }\r\n },\r\n provide () {\r\n return {\r\n requestTreeData: this.requestTreeData,\r\n requestTableData: this.requestTableData,\r\n keepAliveRefresh: this.keepAliveRefresh,\r\n [CONTEXT]: () => {\r\n return {\r\n exposed: this.exposed\r\n }\r\n }\r\n }\r\n },\r\n data () {\r\n return {\r\n tableHeight: 0,\r\n tableWidth: 0,\r\n modalFormMeta: {},\r\n modalFormValue: false,\r\n treeData: [],\r\n tableData: [],\r\n defaultExpandedKeys: [],\r\n defaultSelectedKeys: [],\r\n replaceFields: {\r\n title: 'title',\r\n children: 'children',\r\n key: 'id'\r\n },\r\n loading: false,\r\n total: 0,\r\n tableQuerys: {},\r\n resizeObserverModelTableWrapper: null,\r\n modelTableWrapperHeight: 0,\r\n currentTreeNodeData: {},\r\n currentRowData: {},\r\n treeWrapperHeight: 0,\r\n currentTableSelection: this.currentTableMode == 'radio' ? {} : [],\r\n showFsmModal: false,\r\n fsmMeta: {},\r\n fsmContextProp: {},\r\n modalTableValue: false,\r\n modalTableMeta: {},\r\n dataPoolManager: null\r\n }\r\n },\r\n computed: {\r\n setBorder () {\r\n return this.tableMeta.bordered === false ? false : true\r\n },\r\n rowSelection () {\r\n if (!this.currentTableMode) return void 0\r\n return {\r\n columnTitle: this.currentSelectionColumn.columnTitle,\r\n fixed: true,\r\n type: this.currentTableMode,\r\n onChange: this.onChangeTableSelection\r\n }\r\n },\r\n currentSelectionColumn () {\r\n const { multiple } = this.tableMeta\r\n const target = this.columns.find(item => Object.keys(item).includes('multiple'))\r\n const isGlobalExistMultiple = Object.keys(this.tableMeta).includes('multiple')\r\n if (target) {\r\n return target\r\n } else if (isGlobalExistMultiple) {\r\n return { multiple }\r\n }\r\n return void 0\r\n },\r\n x () {\r\n const { x } = this.tableMeta\r\n return x\r\n },\r\n y () {\r\n const { y } = this.tableMeta\r\n return y\r\n },\r\n currentTableMode () {\r\n if (!this.currentSelectionColumn) return void 0\r\n const { multiple } = this.currentSelectionColumn\r\n if (type.isBool(multiple)) {\r\n if (multiple) {\r\n return 'checkbox'\r\n } else {\r\n return 'radio'\r\n }\r\n } else {\r\n return void 0\r\n }\r\n },\r\n modelTableContainerRef () {\r\n return uuidv4()\r\n },\r\n titleMode () {\r\n if (type.isObject(this.title)) {\r\n const { mode = '' } = this.title\r\n return mode\r\n }\r\n return void 0\r\n },\r\n tableRef () {\r\n return uuidv4()\r\n },\r\n exposed () {\r\n return {\r\n showModalForm: this.showModalForm,\r\n closeModalForm: this.closeModalForm,\r\n showModalTable: this.showModalTable,\r\n closeModalTable: this.closeModalTable,\r\n currentTableSelection: this.currentTableSelection,\r\n currentTreeNode: this.currentTreeNodeData,\r\n requestTableData: this.requestTableData,\r\n keepAliveRefresh: this.keepAliveRefresh,\r\n refreshTreeData: this.refreshTreeData,\r\n querys: this.tableQuerys,\r\n currentRowData: this.getCurrentRowData(),\r\n getCurrentRowData: this.getCurrentRowData,\r\n setCurrentRowData: this.setCurrentRowData,\r\n setCurrentTableSelection: this.setCurrentTableSelection,\r\n getCurrentTableSelection: this.getCurrentTableSelection,\r\n cleanCurrentModelEffect: this.cleanCurrentModelEffect,\r\n route: this.$route,\r\n _route: this.$route.query,\r\n _routeMeta: this.$route.meta,\r\n dataPool: this.model ? this.model.getDataPool() : null,\r\n dataPoolManager: this.model || null\r\n }\r\n },\r\n overrideTableEvent () {\r\n const events = this.actions.reduce((ret, action) => {\r\n ret[action.eventName || action.key] = (e) => {\r\n this.setCurrentRowData(e.exposed.currentRowData)\r\n const { target } = action\r\n const targetMeta = this.findMetaByKey(target)\r\n const { mode } = targetMeta\r\n mode && this.dispatchTrigger({ mode, record: e.exposed.currentRowData, modeMeta: targetMeta })\r\n this.$emit(action.eventName || action.key, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: { ...this.exposed, ...e.exposed } })\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...this.$listeners,\r\n ...events,\r\n [BUILT_IN_EVENT_NAMES.EDIT]: this[BUILT_IN_EVENT_NAMES.EDIT],\r\n [BUILT_IN_EVENT_NAMES.SUBMIT]: this[BUILT_IN_EVENT_NAMES.SUBMIT]\r\n }\r\n },\r\n overrideModalFormEvent () {\r\n const { footerMeta } = this.modalFormMeta\r\n const { elements = [] } = footerMeta || {}\r\n const eles = type.isFunction(elements) ? elements.call(this) : elements\r\n const events = eles.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e = {}) => {\r\n if (ele.eventName === 'cancel') {\r\n this.closeModalForm()\r\n } else {\r\n const { exposed = {} } = e\r\n this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})\r\n }\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...events\r\n }\r\n },\r\n overrideModalTableEvent () {\r\n const { footerMeta } = this.modalTableMeta\r\n const { elements = [] } = footerMeta || {}\r\n const eles = type.isFunction(elements) ? elements.call(this) : elements\r\n const events = eles.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e = {}) => {\r\n if (ele.eventName === 'cancel') {\r\n this.closeModalTable()\r\n } else {\r\n const { exposed = {} } = e\r\n this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})\r\n }\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...events,\r\n exposed: this.exposed\r\n }\r\n },\r\n overrideButtonGroupEvent () {\r\n const events = this.getButtonGroupElements.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e) => {\r\n this.$emit(ele.eventName || 'click', { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, e.exposed || {}, this.exposed)})\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...this.$listeners,\r\n ...events,\r\n [BUILT_IN_EVENT_NAMES.CREATE]: this[BUILT_IN_EVENT_NAMES.CREATE],\r\n exposed: this.exposed\r\n }\r\n },\r\n showTree () {\r\n return !!Object.keys(this.treeMeta).length\r\n },\r\n buttonGroup () {\r\n return uuidv4()\r\n },\r\n searchArea () {\r\n return uuidv4()\r\n },\r\n modelTreeWrapper () {\r\n return uuidv4()\r\n },\r\n modelTableWrapper () {\r\n return uuidv4()\r\n },\r\n actions () {\r\n const { operations } = this.tableMeta\r\n if (operations) {\r\n return operations.elements\r\n } else {\r\n return []\r\n }\r\n },\r\n pageSize () {\r\n const { page = {} } = this.tableMeta\r\n return page.pageSize || 10\r\n },\r\n mode () {\r\n const { page = {} } = this.tableMeta\r\n return page.mode\r\n },\r\n pageSizeOptions () {\r\n const { page = {} } = this.tableMeta\r\n return page.pageSizeOptions || ['10', '20', '30', '40']\r\n },\r\n columns () {\r\n const { columns, operations } = this.tableMeta\r\n if (type.get(columns) === 'array') {\r\n const columnsOptions = columns.map(item => {\r\n const { mode = 'text' } = item\r\n if (item.render) {\r\n return {\r\n ...item,\r\n customRender: (text, record, index) => {\r\n const { $createElement } = this\r\n return item.render.call(this, \r\n { h: $createElement, ctx: this },\r\n text ? typeof text == 'object' ? text[item.dataIndex] : text : '', \r\n record, index)\r\n }\r\n }\r\n } else if (mode !== BUILT_IN_TRIGGER.TEXT) {\r\n const { [`${mode}Meta`]: modeMeta } = item\r\n return {\r\n ...item,\r\n customRender: (text, record, index) => {\r\n return <span onClick={() => this.dispatchTrigger({ mode, record, modeMeta, index })} class={ mode }>{ text }</span>\r\n }\r\n }\r\n }\r\n return {\r\n ...item\r\n }\r\n })\r\n if (operations) {\r\n return [\r\n ...columnsOptions,\r\n {\r\n title: '操作',\r\n width: operations.width,\r\n key: 'action',\r\n fixed: 'right',\r\n scopedSlots: { customRender: 'action' }\r\n }\r\n ]\r\n }\r\n return columnsOptions\r\n } else {\r\n console.error('Error: columns is invalid, please check it')\r\n return []\r\n }\r\n },\r\n getButtonGroupElements () {\r\n const { elements } = this.buttonGroupMeta\r\n if (type.get(elements) === 'function') {\r\n return elements.call(this)\r\n } else if (type.get(elements) === 'array') {\r\n return elements\r\n } else {\r\n return []\r\n }\r\n },\r\n mapFields () {\r\n const { replaceFields = {} } = this.treeMeta\r\n const mapFields = type.isEmpty(replaceFields) ? this.replaceFields : replaceFields\r\n return mapFields\r\n }\r\n },\r\n async created () {\r\n // onSearch会初始化请求表格数据,所以不需要在这里请求表格数据\r\n // 确保全局数据池已初始化\r\n if (!window.__idooel_data_pool__) {\r\n console.error('Global data pool not initialized. Please check if runtime-context/globalDataPool.js is properly imported.')\r\n return\r\n }\r\n \r\n // 初始化数据池管理器(使用新的 runtime-context)\r\n try {\r\n this.model = createTreeTableModel('treeTableModel')\r\n \r\n if (!this.model) {\r\n throw new Error('Failed to create tree table model')\r\n }\r\n } catch (error) {\r\n console.error('Error creating tree table model:', error)\r\n this.model = null\r\n return\r\n }\r\n \r\n // 初始化 currentRowData(尝试从共享命名空间获取)\r\n this.initializeCurrentRowData()\r\n \r\n // 订阅数据池变化\r\n this.unsubscribe = this.model.subscribe('currentRowData', (event) => {\r\n this.currentRowData = event.value || {}\r\n this.$forceUpdate()\r\n })\r\n \r\n // 订阅共享数据变化\r\n this.unsubscribeShared = this.model.subscribeShared((event) => {\r\n // 当有新的共享数据时,更新当前组件的 currentRowData\r\n if (event.value && Object.keys(event.value).length > 0) {\r\n this.setCurrentRowData(event.value)\r\n }\r\n })\r\n \r\n if (this.showTree) {\r\n this.treeData = await this.requestTreeData()\r\n const [defaultTreeNode = {}] = this.treeData\r\n this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.currentTreeNodeData = defaultTreeNode\r\n const { params = {}, fieldMap = {}, overrideInit = false } = this.tableMeta\r\n const currentRowData = this.getCurrentRowData()\r\n const ctx = { \r\n ...this.currentTreeNodeData, \r\n _route: this.$route.query, \r\n currentRowData: currentRowData\r\n }\r\n \r\n const initQuerys = Object.assign({}, params, parseFieldMap(fieldMap, ctx))\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })\r\n } else {\r\n this.tableData = await this.requestTableData(initQuerys)\r\n }\r\n } else {\r\n const { params = {}, fieldMap = {} } = this.tableMeta\r\n const currentRowData = this.getCurrentRowData()\r\n const ctx = { \r\n _route: this.$route.query, \r\n currentRowData: currentRowData\r\n }\r\n this.tableQuerys = Object.assign({}, params, parseFieldMap(fieldMap, ctx))\r\n }\r\n },\r\n methods: {\r\n initializeCurrentRowData () {\r\n if (!this.model) {\r\n console.warn('Model not initialized, skipping currentRowData initialization')\r\n return\r\n }\r\n \r\n // 检查是否有来自父组件的共享数据(比如 modal table 场景)\r\n const parentData = this.model.getSharedData()\r\n \r\n if (parentData && Object.keys(parentData).length > 0) {\r\n this.setCurrentRowData(parentData)\r\n return\r\n }\r\n \r\n // 可以根据路由参数、props 或其他来源设置初始的 currentRowData\r\n const { query } = this.$route\r\n \r\n // 示例:如果路由中有特定参数,可以设置为 currentRowData\r\n if (query.rowId || query.selectedId) {\r\n // 这里可以根据实际需求从服务端或其他地方获取数据\r\n // const presetData = { id: query.rowId, /* 其他字段 */ }\r\n // this.setCurrentRowData(presetData)\r\n }\r\n \r\n // 目前保持空对象,等待用户选择行数据\r\n },\r\n async refreshTreeData () {\r\n this.treeData = await this.requestTreeData()\r\n const [defaultTreeNode = {}] = this.treeData\r\n this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.currentTreeNodeData = defaultTreeNode\r\n },\r\n dispatchTrigger ({ mode, record = {}, modeMeta = { } }) {\r\n switch (mode) {\r\n case BUILT_IN_TRIGGER.FSM:\r\n this[`${BUILT_IN_TRIGGER.FSM}Trigger`](record, modeMeta = type.isEmpty(modeMeta) ? { \r\n url: 'api-fsm/workbench/fsm/auditFlow',\r\n requestType: 'GET',\r\n fieldMap: {\r\n modelCode: 'modelCode',\r\n businessId: 'businessId'\r\n }\r\n } : modeMeta)\r\n break\r\n case BUILT_IN_TRIGGER.ELE_MODAL_FORM:\r\n this.modalFormMeta = modeMeta\r\n this.showModalForm(modeMeta)\r\n break\r\n case BUILT_IN_TRIGGER.ELE_MODAL_TABLE:\r\n this.modalTableMeta = modeMeta\r\n // 将当前行的 record 数据传递给 modal table\r\n this.showModalTable(modeMeta, record)\r\n break\r\n default:\r\n break\r\n }\r\n },\r\n handleCloseFsmModal () {\r\n this.showFsmModal = false\r\n },\r\n [`${BUILT_IN_TRIGGER.FSM}Trigger`] (record, meta) {\r\n this.fsmMeta = meta\r\n this.fsmContextProp = record\r\n this.showFsmModal = true\r\n },\r\n onChangeTableSelection (_, selectedRows = []) {\r\n if (this.currentTableMode === 'radio') {\r\n this.setCurrentTableSelection(selectedRows)\r\n this.$emit('on-change-table-selection', this.currentTableSelection)\r\n this.$emit('x:refresh-exposed', { exposed: this.exposed })\r\n } else {\r\n this.setCurrentTableSelection(selectedRows)\r\n this.$emit('on-change-table-selection', this.currentTableSelection)\r\n this.$emit('x:refresh-exposed', { exposed: this.exposed })\r\n }\r\n },\r\n setCurrentTableSelection (props = {}) {\r\n if (this.currentTableMode === 'radio') {\r\n this.$set(this, 'currentTableSelection', (type.isArray(props) && props.length > 0) ? props[0] : type.isObject(props) ? props : {})\r\n } else {\r\n this.$set(this, 'currentTableSelection', type.isArray(props) ? props : [])\r\n }\r\n },\r\n getCurrentTableSelection () {\r\n return this.currentTableSelection\r\n },\r\n setCurrentRowData (props = {}) {\r\n this.currentRowData = props\r\n if (this.model) {\r\n this.model.setCurrentRowData(props)\r\n } else {\r\n console.warn('Model not initialized, cannot setCurrentRowData in model')\r\n }\r\n },\r\n getCurrentRowData () {\r\n if (this.model) {\r\n return this.model.getCurrentRowData()\r\n }\r\n console.warn('Model not initialized, getCurrentRowData returning local data')\r\n return this.currentRowData || {}\r\n },\r\n cleanCurrentModelEffect (clearRowData = true) {\r\n this.setCurrentTableSelection()\r\n if (clearRowData) {\r\n this.setCurrentRowData({})\r\n }\r\n },\r\n [BUILT_IN_EVENT_NAMES.SUBMIT] (props = {}) {\r\n this.cleanCurrentModelEffect()\r\n this.requestTableData()\r\n },\r\n [BUILT_IN_EVENT_NAMES.EDIT] (props = {}) {\r\n const { record = {} } = props\r\n this.setCurrentRowData(record)\r\n this.modalFormMeta = this.editMeta\r\n this.modalFormValue = true\r\n },\r\n [BUILT_IN_EVENT_NAMES.CREATE] () {\r\n this.modalFormMeta = this.createMeta\r\n this.modalFormValue = true\r\n },\r\n showModalForm (modeMeta = {}) {\r\n if (type.isStr(modeMeta)) {\r\n const targetMeta = this.findMetaByKey(modeMeta)\r\n this.modalFormMeta = targetMeta\r\n } else {\r\n this.modalFormMeta = modeMeta\r\n }\r\n this.modalFormValue = true\r\n },\r\n showModalTable (modeMeta = {}, record = null) {\r\n // 获取当前行数据并设置到共享命名空间\r\n const currentRowData = record || this.getCurrentRowData()\r\n if (this.model) {\r\n this.model.setSharedData(currentRowData)\r\n } else {\r\n console.warn('Model not initialized, cannot setSharedData')\r\n }\r\n \r\n let targetMeta = modeMeta\r\n if (type.isStr(modeMeta)) {\r\n targetMeta = this.findMetaByKey(modeMeta)\r\n }\r\n \r\n // 解析 fieldMap 参数,使用完整的上下文包括 currentRowData\r\n if (targetMeta && targetMeta.fieldMap) {\r\n const { fieldMap, params = {} } = targetMeta\r\n \r\n const ctx = { \r\n ...this.currentTreeNodeData, \r\n _route: this.$route.query, \r\n ...currentRowData \r\n }\r\n const parsedParams = parseFieldMap(fieldMap, ctx)\r\n \r\n // 将当前的 currentRowData 传递给 modal table,通过 params\r\n targetMeta = {\r\n ...targetMeta,\r\n params: {\r\n ...params,\r\n ...parsedParams\r\n }\r\n }\r\n }\r\n \r\n this.modalTableMeta = targetMeta\r\n this.modalTableValue = true\r\n },\r\n closeModalForm () {\r\n this.modalFormValue = false\r\n },\r\n closeModalTable () {\r\n this.modalTableValue = false\r\n },\r\n findMetaByKey (key) {\r\n return this.$attrs[key] || {}\r\n },\r\n handleClickButtonGroup (props) {\r\n const { eventName, target } = props\r\n const targetMeta = this.findMetaByKey(target)\r\n const { mode } = targetMeta\r\n mode && this.dispatchTrigger({ mode, modeMeta: targetMeta })\r\n this.$emit(eventName || 'click', { currentTreeNode: this.currentTreeNodeData })\r\n },\r\n async onSearch (props) {\r\n const { overrideInit = false } = this.tableMeta\r\n this.tableQuerys = Object.assign(this.tableQuerys, props)\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })\r\n } else {\r\n const { initSearch = false } = props\r\n if (this.showTree && initSearch) return\r\n this.tableData = await this.requestTableData()\r\n }\r\n },\r\n async selectTreeNode (selectedKeys, e) {\r\n const { fieldMap } = this.tableMeta\r\n this.currentTreeNodeData = e.node.$vnode.data.props.dataRef || {}\r\n //@deprecated '_' namespace is deprecated, please use 'exposed' instead\r\n const execFieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, exposed: this.exposed, _route: this.exposed._route })\r\n const { overrideInit = false } = this.tableMeta\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })\r\n } else {\r\n this.tableData = await this.requestTableData(execFieldMapRet)\r\n }\r\n },\r\n async requestTreeData () {\r\n const { url, requestType = 'GET', params = {}, fieldMap = {} } = this.treeMeta\r\n const fieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, _route: this.$route.query })\r\n const ret = await net[requestType.toLowerCase()](\r\n url,\r\n { ...params, ...fieldMapRet }\r\n ).then(resp => {\r\n const { data } = resp || {}\r\n return data\r\n })\r\n return ret\r\n },\r\n async onChangePage (page, pageSize) {\r\n this.tableData = await this.requestTableData({ currentPage: page, pageSize })\r\n },\r\n async requestTableData (props = {}) {\r\n const { url, requestType = 'GET', page = {} } = this.tableMeta\r\n this.tableQuerys = Object.assign(this.tableQuerys, { currentPage: this.tableQuerys.currentPage || 1, pageSize: this.tableQuerys.pageSize || page.pageSize || 10 }, props)\r\n this.$emit(RESERVE_EVENT_NAMES.WATCH, { ...this.exposed })\r\n this.loading = true\r\n const ret = await net[requestType.toLowerCase()](\r\n url,\r\n this.tableQuerys\r\n ).then(resp => {\r\n const { data = [], count } = resp || {}\r\n this.total = count\r\n this.loading = false\r\n return (data || []).map(item => {\r\n delete item.children\r\n return {\r\n key: uuidv4(),\r\n ...item\r\n }\r\n })\r\n })\r\n this.cleanCurrentModelEffect(false) // 不清空 currentRowData,除非明确需要\r\n this.tableData = ret\r\n return ret\r\n },\r\n calculateTableHeight () {\r\n const currentViewportHeight = window.innerHeight\r\n const tableRef = this.$refs[this.tableRef]\r\n const { top: tableToTop, width } = tableRef.$el.getBoundingClientRect()\r\n this.tableWidth = width\r\n this.tableHeight = currentViewportHeight - tableToTop - this.overHeight\r\n },\r\n calculateTreeHeight () {\r\n if (!this.showTree) return\r\n const modelTableContainerRef = this.$refs[this.modelTableContainerRef]\r\n const { height } = modelTableContainerRef.getBoundingClientRect()\r\n this.treeWrapperHeight = height\r\n },\r\n async keepAliveRefresh () {\r\n // 重新计算表格高度(应对窗口大小变化)\r\n this.$nextTick(() => {\r\n this.calculateTableHeight()\r\n if (this.showTree) {\r\n this.calculateTreeHeight()\r\n }\r\n })\r\n // 刷新列表数据\r\n const { overrideInit = false } = this.tableMeta\r\n if (overrideInit) {\r\n // 如果使用自定义初始化模式,触发 INIT 事件\r\n this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })\r\n } else {\r\n // 使用当前查询参数刷新表格数据\r\n await this.requestTableData(this.tableQuerys)\r\n }\r\n }\r\n },\r\n mounted () {\r\n this.calculateTableHeight()\r\n this.$nextTick(() => {\r\n this.calculateTreeHeight()\r\n })\r\n this.resizeObserverModelTableWrapper = new ResizeObserver(entries => {\r\n for (const _ of entries) {\r\n requestAnimationFrame(() => {\r\n this.calculateTableHeight()\r\n })\r\n }\r\n })\r\n this.resizeObserverModelTableWrapper.observe(this.$refs[this.modelTableWrapper])\r\n },\r\n destroyed () {\r\n this.resizeObserverModelTableWrapper.disconnect()\r\n if (this.model) {\r\n // 清理订阅\r\n if (this.unsubscribe) {\r\n this.unsubscribe()\r\n }\r\n if (this.unsubscribeShared) {\r\n this.unsubscribeShared()\r\n }\r\n // 清理模型数据\r\n if (this.model) {\r\n this.model.cleanup()\r\n }\r\n }\r\n },\r\n async activated () {\r\n await this.keepAliveRefresh()\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.ele {\r\n &.model__tree-table {\r\n background: transparent; \r\n display: flex;\r\n flex-direction: row;\r\n width: 100%;\r\n .model__tree-table--container {\r\n .model__tree--wrapper {\r\n width: 240px;\r\n background: #fff;\r\n flex-shrink: 0;\r\n padding: 16px;\r\n box-sizing: border-box;\r\n margin-right: 16px;\r\n overflow-y: auto;\r\n }\r\n }\r\n .model__table--container {\r\n width: 100%;\r\n min-width: 0;\r\n background: #fff;\r\n .model__table--title {\r\n .model__table-title--bar {\r\n width: 100%;\r\n height: 8px;\r\n background: var(--idooel-primary-color);\r\n border-top-left-radius: 4px;\r\n border-top-right-radius: 4px;\r\n }\r\n .model__table-title--text {\r\n text-align: left;\r\n padding: 16px;\r\n font-size: 16px;\r\n font-weight: bold;\r\n background: #fff;\r\n border-bottom: 1px solid;\r\n border-color: var(--idoole-black-016);\r\n }\r\n }\r\n .model__table--wrapper {\r\n background: #fff;\r\n .button-row__area {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding-top: 16px;\r\n padding-bottom: 8px;\r\n padding-right: 16px;\r\n }\r\n .g-table__wrapper {\r\n .fsm {\r\n cursor: pointer;\r\n color: var(--idooel-primary-color);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n</style>\r\n",".ele.model__tree-table {\n background: transparent;\n display: flex;\n flex-direction: row;\n width: 100%;\n}\n.ele.model__tree-table .model__tree-table--container .model__tree--wrapper {\n width: 240px;\n background: #fff;\n flex-shrink: 0;\n padding: 16px;\n box-sizing: border-box;\n margin-right: 16px;\n overflow-y: auto;\n}\n.ele.model__tree-table .model__table--container {\n width: 100%;\n min-width: 0;\n background: #fff;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--bar {\n width: 100%;\n height: 8px;\n background: var(--idooel-primary-color);\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--text {\n text-align: left;\n padding: 16px;\n font-size: 16px;\n font-weight: bold;\n background: #fff;\n border-bottom: 1px solid;\n border-color: var(--idoole-black-016);\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper {\n background: #fff;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .button-row__area {\n width: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n padding-top: 16px;\n padding-bottom: 8px;\n padding-right: 16px;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper .fsm {\n cursor: pointer;\n color: var(--idooel-primary-color);\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
4106
|
+
inject("data-v-72d2cdcc_0", { source: ".ele.model__tree-table[data-v-72d2cdcc] {\n background: transparent;\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__tree-table--container[data-v-72d2cdcc] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n.ele.model__tree-table .model__tree-table--container .model__tree--wrapper[data-v-72d2cdcc] {\n width: 240px;\n background: #fff;\n flex-shrink: 0;\n padding: 16px;\n box-sizing: border-box;\n margin-right: 16px;\n overflow-y: auto;\n overflow-x: hidden;\n}\n.ele.model__tree-table .model__table--container[data-v-72d2cdcc] {\n width: 100%;\n min-width: 0;\n background: #fff;\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--bar[data-v-72d2cdcc] {\n width: 100%;\n height: 8px;\n background: var(--idooel-primary-color);\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--text[data-v-72d2cdcc] {\n text-align: left;\n padding: 16px;\n font-size: 16px;\n font-weight: bold;\n background: #fff;\n border-bottom: 1px solid;\n border-color: var(--idoole-black-016);\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper[data-v-72d2cdcc] {\n background: #fff;\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .button-row__area[data-v-72d2cdcc] {\n width: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n padding-top: 16px;\n padding-bottom: 8px;\n padding-right: 16px;\n flex-shrink: 0;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper[data-v-72d2cdcc] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper .fsm[data-v-72d2cdcc] {\n cursor: pointer;\n color: var(--idooel-primary-color);\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\models\\tree-table-model\\src\\index.vue","index.vue"],"names":[],"mappings":"AAq3BA;EACA,uBAAA;EACA,aAAA;EACA,mBAAA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;ACp3BA;ADq3BA;EACA,aAAA;EACA,sBAAA;EACA,YAAA;ACn3BA;ADo3BA;EACA,YAAA;EACA,gBAAA;EACA,cAAA;EACA,aAAA;EACA,sBAAA;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;ACl3BA;ADq3BA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,sBAAA;EACA,YAAA;EACA,gBAAA;ACn3BA;ADq3BA;EACA,WAAA;EACA,WAAA;EACA,uCAAA;EACA,2BAAA;EACA,4BAAA;ACn3BA;ADq3BA;EACA,gBAAA;EACA,aAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,qCAAA;ACn3BA;ADs3BA;EACA,gBAAA;EACA,aAAA;EACA,sBAAA;EACA,YAAA;EACA,gBAAA;ACp3BA;ADq3BA;EACA,WAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,8BAAA;EACA,iBAAA;EACA,mBAAA;EACA,mBAAA;EACA,cAAA;ACn3BA;ADq3BA;EACA,OAAA;EACA,aAAA;EACA,gBAAA;ACn3BA;ADo3BA;EACA,eAAA;EACA,kCAAA;ACl3BA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\r\n <section class=\"ele model__tree-table\">\r\n <section class=\"model__tree-table--container\" v-if=\"showTree\">\r\n <div class=\"model__tree--title\"></div>\r\n <section :ref=\"modelTreeWrapper\" class=\"model__tree--wrapper\" :style=\"{height: `${treeWrapperHeight}px`}\">\r\n <ele-tree\r\n :tree-data=\"treeData\"\r\n :defaultExpandedKeys=\"defaultExpandedKeys\"\r\n :defaultSelectedKeys=\"defaultSelectedKeys\"\r\n @select=\"selectTreeNode\"\r\n :replace-fields=\"mapFields\">\r\n </ele-tree>\r\n </section>\r\n </section>\r\n <section class=\"model__table--container\" :ref=\"modelTableContainerRef\">\r\n <div class=\"model__table--title\" v-if=\"title\">\r\n <template v-if=\"titleMode\">\r\n <div :class=\"[`model__table-title--${titleMode}`]\"></div>\r\n </template>\r\n <template v-else>\r\n <div class=\"model__table-title--text\">{{ title }}</div>\r\n </template>\r\n </div>\r\n <section :ref=\"modelTableWrapper\" class=\"model__table--wrapper\">\r\n <ele-search-area :ref=\"searchArea\" @search=\"onSearch\" :data-source=\"searchMeta.elements\"></ele-search-area>\r\n <div class=\"button-row__area\">\r\n <ele-button-group class=\"model-table__button-group\" v-on=\"overrideButtonGroupEvent\" :ref=\"buttonGroup\" @click=\"handleClickButtonGroup\" :data-source=\"getButtonGroupElements\"></ele-button-group>\r\n <slot name=\"tags\"></slot>\r\n <slot v-if=\"$slots['sub-center']\" name=\"sub-center\"></slot>\r\n </div>\r\n <ele-table\r\n v-on=\"overrideTableEvent\"\r\n :ref=\"tableRef\"\r\n :row-selection=\"rowSelection\"\r\n :loading=\"loading\" \r\n :columns=\"columns\"\r\n :total=\"total\"\r\n :x=\"x\"\r\n :y=\"y\"\r\n :bordered=\"setBorder\"\r\n :height=\"tableHeight\"\r\n :width=\"tableWidth\"\r\n :actions=\"actions\"\r\n :pageSize=\"pageSize\"\r\n :pageSizeOptions=\"pageSizeOptions\"\r\n :data-source=\"tableData\"\r\n :mode=\"mode\"\r\n v-bind=\"pageConfig\"\r\n @change-page=\"onChangePage\"\r\n ></ele-table>\r\n </section>\r\n </section>\r\n <ele-modal-form v-model=\"modalFormValue\" v-on=\"overrideModalFormEvent\" :meta=\"modalFormMeta\"></ele-modal-form>\r\n <ele-modal-fsm v-model=\"showFsmModal\" :contextProp=\"fsmContextProp\" :meta=\"fsmMeta\" @cancel=\"handleCloseFsmModal\"></ele-modal-fsm>\r\n <ele-modal-table\r\n :meta=\"modalTableMeta\"\r\n v-model=\"modalTableValue\"\r\n v-on=\"overrideModalTableEvent\"\r\n ></ele-modal-table>\r\n </section>\r\n</template>\r\n\r\n<script>\r\nimport { type, net } from '@idooel/shared'\r\nimport { v4 as uuidv4 } from 'uuid'\r\nimport { BUILT_IN_EVENT_NAMES, RESERVE_EVENT_NAMES, parseFieldMap, BUILT_IN_TRIGGER, CONTEXT } from '../../../utils'\r\nimport { createTreeTableModel } from '../../../utils/runtime-context'\r\nexport default {\r\n name: 'ele-tree-table-model',\r\n props: {\r\n title: {\r\n type: [Object, String]\r\n },\r\n overHeight: {\r\n type: Number,\r\n default: 0\r\n },\r\n treeMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n searchMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n buttonGroupMeta: {\r\n typeof: Object,\r\n default: () => ({})\r\n },\r\n tableMeta: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n createMeta: {\r\n type: Object\r\n },\r\n editMeta: {\r\n type: Object\r\n }\r\n },\r\n provide () {\r\n return {\r\n requestTreeData: this.requestTreeData,\r\n requestTableData: this.requestTableData,\r\n keepAliveRefresh: this.keepAliveRefresh,\r\n [CONTEXT]: () => {\r\n return {\r\n exposed: this.exposed\r\n }\r\n }\r\n }\r\n },\r\n data () {\r\n return {\r\n tableHeight: 0,\r\n tableWidth: 0,\r\n modalFormMeta: {},\r\n modalFormValue: false,\r\n treeData: [],\r\n tableData: [],\r\n defaultExpandedKeys: [],\r\n defaultSelectedKeys: [],\r\n replaceFields: {\r\n title: 'title',\r\n children: 'children',\r\n key: 'id'\r\n },\r\n loading: false,\r\n total: 0,\r\n tableQuerys: {},\r\n resizeObserverModelTableWrapper: null,\r\n resizeObserverModelTableContainer: null,\r\n modelTableWrapperHeight: 0,\r\n currentTreeNodeData: {},\r\n currentRowData: {},\r\n treeWrapperHeight: 0,\r\n currentTableSelection: this.currentTableMode == 'radio' ? {} : [],\r\n showFsmModal: false,\r\n fsmMeta: {},\r\n fsmContextProp: {},\r\n modalTableValue: false,\r\n modalTableMeta: {},\r\n dataPoolManager: null\r\n }\r\n },\r\n computed: {\r\n setBorder () {\r\n return this.tableMeta.bordered === false ? false : true\r\n },\r\n rowSelection () {\r\n if (!this.currentTableMode) return void 0\r\n return {\r\n columnTitle: this.currentSelectionColumn.columnTitle,\r\n fixed: true,\r\n type: this.currentTableMode,\r\n onChange: this.onChangeTableSelection\r\n }\r\n },\r\n currentSelectionColumn () {\r\n const { multiple } = this.tableMeta\r\n const target = this.columns.find(item => Object.keys(item).includes('multiple'))\r\n const isGlobalExistMultiple = Object.keys(this.tableMeta).includes('multiple')\r\n if (target) {\r\n return target\r\n } else if (isGlobalExistMultiple) {\r\n return { multiple }\r\n }\r\n return void 0\r\n },\r\n x () {\r\n const { x } = this.tableMeta\r\n return x\r\n },\r\n y () {\r\n const { y } = this.tableMeta\r\n return y\r\n },\r\n currentTableMode () {\r\n if (!this.currentSelectionColumn) return void 0\r\n const { multiple } = this.currentSelectionColumn\r\n if (type.isBool(multiple)) {\r\n if (multiple) {\r\n return 'checkbox'\r\n } else {\r\n return 'radio'\r\n }\r\n } else {\r\n return void 0\r\n }\r\n },\r\n modelTableContainerRef () {\r\n return uuidv4()\r\n },\r\n titleMode () {\r\n if (type.isObject(this.title)) {\r\n const { mode = '' } = this.title\r\n return mode\r\n }\r\n return void 0\r\n },\r\n tableRef () {\r\n return uuidv4()\r\n },\r\n exposed () {\r\n return {\r\n showModalForm: this.showModalForm,\r\n closeModalForm: this.closeModalForm,\r\n showModalTable: this.showModalTable,\r\n closeModalTable: this.closeModalTable,\r\n currentTableSelection: this.currentTableSelection,\r\n currentTreeNode: this.currentTreeNodeData,\r\n requestTableData: this.requestTableData,\r\n keepAliveRefresh: this.keepAliveRefresh,\r\n refreshTreeData: this.refreshTreeData,\r\n querys: this.tableQuerys,\r\n currentRowData: this.getCurrentRowData(),\r\n getCurrentRowData: this.getCurrentRowData,\r\n setCurrentRowData: this.setCurrentRowData,\r\n setCurrentTableSelection: this.setCurrentTableSelection,\r\n getCurrentTableSelection: this.getCurrentTableSelection,\r\n cleanCurrentModelEffect: this.cleanCurrentModelEffect,\r\n route: this.$route,\r\n _route: this.$route.query,\r\n _routeMeta: this.$route.meta,\r\n dataPool: this.model ? this.model.getDataPool() : null,\r\n dataPoolManager: this.model || null\r\n }\r\n },\r\n overrideTableEvent () {\r\n const events = this.actions.reduce((ret, action) => {\r\n ret[action.eventName || action.key] = (e) => {\r\n this.setCurrentRowData(e.exposed.currentRowData)\r\n const { target } = action\r\n const targetMeta = this.findMetaByKey(target)\r\n const { mode } = targetMeta\r\n mode && this.dispatchTrigger({ mode, record: e.exposed.currentRowData, modeMeta: targetMeta })\r\n this.$emit(action.eventName || action.key, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: { ...this.exposed, ...e.exposed } })\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...this.$listeners,\r\n ...events,\r\n [BUILT_IN_EVENT_NAMES.EDIT]: this[BUILT_IN_EVENT_NAMES.EDIT],\r\n [BUILT_IN_EVENT_NAMES.SUBMIT]: this[BUILT_IN_EVENT_NAMES.SUBMIT]\r\n }\r\n },\r\n overrideModalFormEvent () {\r\n const { footerMeta } = this.modalFormMeta\r\n const { elements = [] } = footerMeta || {}\r\n const eles = type.isFunction(elements) ? elements.call(this) : elements\r\n const events = eles.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e = {}) => {\r\n if (ele.eventName === 'cancel') {\r\n this.closeModalForm()\r\n } else {\r\n const { exposed = {} } = e\r\n this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})\r\n }\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...events\r\n }\r\n },\r\n overrideModalTableEvent () {\r\n const { footerMeta } = this.modalTableMeta\r\n const { elements = [] } = footerMeta || {}\r\n const eles = type.isFunction(elements) ? elements.call(this) : elements\r\n const events = eles.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e = {}) => {\r\n if (ele.eventName === 'cancel') {\r\n this.closeModalTable()\r\n } else {\r\n const { exposed = {} } = e\r\n this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})\r\n }\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...events,\r\n exposed: this.exposed\r\n }\r\n },\r\n overrideButtonGroupEvent () {\r\n const events = this.getButtonGroupElements.reduce((ret, ele) => {\r\n ret[ele.eventName] = (e) => {\r\n this.$emit(ele.eventName || 'click', { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, e.exposed || {}, this.exposed)})\r\n }\r\n return ret\r\n }, {})\r\n return {\r\n ...this.$listeners,\r\n ...events,\r\n [BUILT_IN_EVENT_NAMES.CREATE]: this[BUILT_IN_EVENT_NAMES.CREATE],\r\n exposed: this.exposed\r\n }\r\n },\r\n showTree () {\r\n return !!Object.keys(this.treeMeta).length\r\n },\r\n buttonGroup () {\r\n return uuidv4()\r\n },\r\n searchArea () {\r\n return uuidv4()\r\n },\r\n modelTreeWrapper () {\r\n return uuidv4()\r\n },\r\n modelTableWrapper () {\r\n return uuidv4()\r\n },\r\n actions () {\r\n const { operations } = this.tableMeta\r\n if (operations) {\r\n return operations.elements\r\n } else {\r\n return []\r\n }\r\n },\r\n pageSize () {\r\n const { page = {} } = this.tableMeta\r\n return page.pageSize || 10\r\n },\r\n nextCursorConfig () {\r\n if (this.mode == 'next-cursor') {\r\n const { page = {} } = this.tableMeta\r\n const { nextCursor = {} } = page\r\n return nextCursor\r\n } else {\r\n return void 0\r\n }\r\n },\r\n mode () {\r\n const { page = {} } = this.tableMeta\r\n return page.mode\r\n },\r\n pageSizeOptions () {\r\n const { page = {} } = this.tableMeta\r\n return page.pageSizeOptions || ['10', '20', '30', '40']\r\n },\r\n columns () {\r\n const { columns, operations } = this.tableMeta\r\n if (type.get(columns) === 'array') {\r\n const columnsOptions = columns.map(item => {\r\n const { mode = 'text' } = item\r\n if (item.render) {\r\n return {\r\n ...item,\r\n customRender: (text, record, index) => {\r\n const { $createElement } = this\r\n return item.render.call(this, \r\n { h: $createElement, ctx: this },\r\n text ? typeof text == 'object' ? text[item.dataIndex] : text : '', \r\n record, index)\r\n }\r\n }\r\n } else if (mode !== BUILT_IN_TRIGGER.TEXT) {\r\n const { [`${mode}Meta`]: modeMeta } = item\r\n return {\r\n ...item,\r\n customRender: (text, record, index) => {\r\n return <span onClick={() => this.dispatchTrigger({ mode, record, modeMeta, index })} class={ mode }>{ text }</span>\r\n }\r\n }\r\n }\r\n return {\r\n ...item\r\n }\r\n })\r\n if (operations) {\r\n return [\r\n ...columnsOptions,\r\n {\r\n title: '操作',\r\n width: operations.width,\r\n key: 'action',\r\n fixed: 'right',\r\n scopedSlots: { customRender: 'action' }\r\n }\r\n ]\r\n }\r\n return columnsOptions\r\n } else {\r\n console.error('Error: columns is invalid, please check it')\r\n return []\r\n }\r\n },\r\n getButtonGroupElements () {\r\n const { elements } = this.buttonGroupMeta\r\n if (type.get(elements) === 'function') {\r\n return elements.call(this)\r\n } else if (type.get(elements) === 'array') {\r\n return elements\r\n } else {\r\n return []\r\n }\r\n },\r\n mapFields () {\r\n const { replaceFields = {} } = this.treeMeta\r\n const mapFields = type.isEmpty(replaceFields) ? this.replaceFields : replaceFields\r\n return mapFields\r\n }\r\n },\r\n async created () {\r\n // onSearch会初始化请求表格数据,所以不需要在这里请求表格数据\r\n // 确保全局数据池已初始化\r\n if (!window.__idooel_data_pool__) {\r\n console.error('Global data pool not initialized. Please check if runtime-context/globalDataPool.js is properly imported.')\r\n return\r\n }\r\n \r\n // 初始化数据池管理器(使用新的 runtime-context)\r\n try {\r\n this.model = createTreeTableModel('treeTableModel')\r\n \r\n if (!this.model) {\r\n throw new Error('Failed to create tree table model')\r\n }\r\n } catch (error) {\r\n console.error('Error creating tree table model:', error)\r\n this.model = null\r\n return\r\n }\r\n \r\n // 初始化 currentRowData(尝试从共享命名空间获取)\r\n this.initializeCurrentRowData()\r\n \r\n // 订阅数据池变化\r\n this.unsubscribe = this.model.subscribe('currentRowData', (event) => {\r\n this.currentRowData = event.value || {}\r\n this.$forceUpdate()\r\n })\r\n \r\n // 订阅共享数据变化\r\n this.unsubscribeShared = this.model.subscribeShared((event) => {\r\n // 当有新的共享数据时,更新当前组件的 currentRowData\r\n if (event.value && Object.keys(event.value).length > 0) {\r\n this.setCurrentRowData(event.value)\r\n }\r\n })\r\n \r\n if (this.showTree) {\r\n this.treeData = await this.requestTreeData()\r\n const [defaultTreeNode = {}] = this.treeData\r\n this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.currentTreeNodeData = defaultTreeNode\r\n const { params = {}, fieldMap = {}, overrideInit = false } = this.tableMeta\r\n const currentRowData = this.getCurrentRowData()\r\n const ctx = { \r\n ...this.currentTreeNodeData, \r\n _route: this.$route.query, \r\n currentRowData: currentRowData\r\n }\r\n \r\n const initQuerys = Object.assign({}, params, parseFieldMap(fieldMap, ctx))\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })\r\n } else {\r\n this.tableData = await this.requestTableData(initQuerys)\r\n }\r\n } else {\r\n const { params = {}, fieldMap = {} } = this.tableMeta\r\n const currentRowData = this.getCurrentRowData()\r\n const ctx = { \r\n _route: this.$route.query, \r\n currentRowData: currentRowData\r\n }\r\n this.tableQuerys = Object.assign({}, params, parseFieldMap(fieldMap, ctx))\r\n }\r\n },\r\n methods: {\r\n initializeCurrentRowData () {\r\n if (!this.model) {\r\n console.warn('Model not initialized, skipping currentRowData initialization')\r\n return\r\n }\r\n \r\n // 检查是否有来自父组件的共享数据(比如 modal table 场景)\r\n const parentData = this.model.getSharedData()\r\n \r\n if (parentData && Object.keys(parentData).length > 0) {\r\n this.setCurrentRowData(parentData)\r\n return\r\n }\r\n \r\n // 可以根据路由参数、props 或其他来源设置初始的 currentRowData\r\n const { query } = this.$route\r\n \r\n // 示例:如果路由中有特定参数,可以设置为 currentRowData\r\n if (query.rowId || query.selectedId) {\r\n // 这里可以根据实际需求从服务端或其他地方获取数据\r\n // const presetData = { id: query.rowId, /* 其他字段 */ }\r\n // this.setCurrentRowData(presetData)\r\n }\r\n \r\n // 目前保持空对象,等待用户选择行数据\r\n },\r\n async refreshTreeData () {\r\n this.treeData = await this.requestTreeData()\r\n const [defaultTreeNode = {}] = this.treeData\r\n this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]\r\n this.currentTreeNodeData = defaultTreeNode\r\n },\r\n dispatchTrigger ({ mode, record = {}, modeMeta = { } }) {\r\n switch (mode) {\r\n case BUILT_IN_TRIGGER.FSM:\r\n this[`${BUILT_IN_TRIGGER.FSM}Trigger`](record, modeMeta = type.isEmpty(modeMeta) ? { \r\n url: 'api-fsm/workbench/fsm/auditFlow',\r\n requestType: 'GET',\r\n fieldMap: {\r\n modelCode: 'modelCode',\r\n businessId: 'businessId'\r\n }\r\n } : modeMeta)\r\n break\r\n case BUILT_IN_TRIGGER.ELE_MODAL_FORM:\r\n this.modalFormMeta = modeMeta\r\n this.showModalForm(modeMeta)\r\n break\r\n case BUILT_IN_TRIGGER.ELE_MODAL_TABLE:\r\n this.modalTableMeta = modeMeta\r\n // 将当前行的 record 数据传递给 modal table\r\n this.showModalTable(modeMeta, record)\r\n break\r\n default:\r\n break\r\n }\r\n },\r\n handleCloseFsmModal () {\r\n this.showFsmModal = false\r\n },\r\n [`${BUILT_IN_TRIGGER.FSM}Trigger`] (record, meta) {\r\n this.fsmMeta = meta\r\n this.fsmContextProp = record\r\n this.showFsmModal = true\r\n },\r\n onChangeTableSelection (_, selectedRows = []) {\r\n if (this.currentTableMode === 'radio') {\r\n this.setCurrentTableSelection(selectedRows)\r\n this.$emit('on-change-table-selection', this.currentTableSelection)\r\n this.$emit('x:refresh-exposed', { exposed: this.exposed })\r\n } else {\r\n this.setCurrentTableSelection(selectedRows)\r\n this.$emit('on-change-table-selection', this.currentTableSelection)\r\n this.$emit('x:refresh-exposed', { exposed: this.exposed })\r\n }\r\n },\r\n setCurrentTableSelection (props = {}) {\r\n if (this.currentTableMode === 'radio') {\r\n this.$set(this, 'currentTableSelection', (type.isArray(props) && props.length > 0) ? props[0] : type.isObject(props) ? props : {})\r\n } else {\r\n this.$set(this, 'currentTableSelection', type.isArray(props) ? props : [])\r\n }\r\n },\r\n getCurrentTableSelection () {\r\n return this.currentTableSelection\r\n },\r\n setCurrentRowData (props = {}) {\r\n this.currentRowData = props\r\n if (this.model) {\r\n this.model.setCurrentRowData(props)\r\n } else {\r\n console.warn('Model not initialized, cannot setCurrentRowData in model')\r\n }\r\n },\r\n getCurrentRowData () {\r\n if (this.model) {\r\n return this.model.getCurrentRowData()\r\n }\r\n console.warn('Model not initialized, getCurrentRowData returning local data')\r\n return this.currentRowData || {}\r\n },\r\n cleanCurrentModelEffect (clearRowData = true) {\r\n this.setCurrentTableSelection()\r\n if (clearRowData) {\r\n this.setCurrentRowData({})\r\n }\r\n },\r\n [BUILT_IN_EVENT_NAMES.SUBMIT] (props = {}) {\r\n this.cleanCurrentModelEffect()\r\n this.requestTableData()\r\n },\r\n [BUILT_IN_EVENT_NAMES.EDIT] (props = {}) {\r\n const { record = {} } = props\r\n this.setCurrentRowData(record)\r\n this.modalFormMeta = this.editMeta\r\n this.modalFormValue = true\r\n },\r\n [BUILT_IN_EVENT_NAMES.CREATE] () {\r\n this.modalFormMeta = this.createMeta\r\n this.modalFormValue = true\r\n },\r\n showModalForm (modeMeta = {}) {\r\n if (type.isStr(modeMeta)) {\r\n const targetMeta = this.findMetaByKey(modeMeta)\r\n this.modalFormMeta = targetMeta\r\n } else {\r\n this.modalFormMeta = modeMeta\r\n }\r\n this.modalFormValue = true\r\n },\r\n showModalTable (modeMeta = {}, record = null) {\r\n // 获取当前行数据并设置到共享命名空间\r\n const currentRowData = record || this.getCurrentRowData()\r\n if (this.model) {\r\n this.model.setSharedData(currentRowData)\r\n } else {\r\n console.warn('Model not initialized, cannot setSharedData')\r\n }\r\n \r\n let targetMeta = modeMeta\r\n if (type.isStr(modeMeta)) {\r\n targetMeta = this.findMetaByKey(modeMeta)\r\n }\r\n \r\n // 解析 fieldMap 参数,使用完整的上下文包括 currentRowData\r\n if (targetMeta && targetMeta.fieldMap) {\r\n const { fieldMap, params = {} } = targetMeta\r\n \r\n const ctx = { \r\n ...this.currentTreeNodeData, \r\n _route: this.$route.query, \r\n ...currentRowData \r\n }\r\n const parsedParams = parseFieldMap(fieldMap, ctx)\r\n \r\n // 将当前的 currentRowData 传递给 modal table,通过 params\r\n targetMeta = {\r\n ...targetMeta,\r\n params: {\r\n ...params,\r\n ...parsedParams\r\n }\r\n }\r\n }\r\n \r\n this.modalTableMeta = targetMeta\r\n this.modalTableValue = true\r\n },\r\n closeModalForm () {\r\n this.modalFormValue = false\r\n },\r\n closeModalTable () {\r\n this.modalTableValue = false\r\n },\r\n findMetaByKey (key) {\r\n return this.$attrs[key] || {}\r\n },\r\n handleClickButtonGroup (props) {\r\n const { eventName, target } = props\r\n const targetMeta = this.findMetaByKey(target)\r\n const { mode } = targetMeta\r\n mode && this.dispatchTrigger({ mode, modeMeta: targetMeta })\r\n this.$emit(eventName || 'click', { currentTreeNode: this.currentTreeNodeData })\r\n },\r\n async onSearch (props) {\r\n const { overrideInit = false } = this.tableMeta\r\n this.tableQuerys = Object.assign(this.tableQuerys, props)\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })\r\n } else {\r\n const { initSearch = false } = props\r\n if (this.showTree && initSearch) return\r\n this.tableData = await this.requestTableData()\r\n }\r\n },\r\n async selectTreeNode (selectedKeys, e) {\r\n const { fieldMap } = this.tableMeta\r\n this.currentTreeNodeData = e.node.$vnode.data.props.dataRef || {}\r\n //@deprecated '_' namespace is deprecated, please use 'exposed' instead\r\n const execFieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, exposed: this.exposed, _route: this.exposed._route })\r\n const { overrideInit = false } = this.tableMeta\r\n if (overrideInit) {\r\n this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })\r\n } else {\r\n this.tableData = await this.requestTableData(execFieldMapRet)\r\n }\r\n },\r\n async requestTreeData () {\r\n const { url, requestType = 'GET', params = {}, fieldMap = {} } = this.treeMeta\r\n const fieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, _route: this.$route.query })\r\n const ret = await net[requestType.toLowerCase()](\r\n url,\r\n { ...params, ...fieldMapRet }\r\n ).then(resp => {\r\n const { data } = resp || {}\r\n return data\r\n })\r\n return ret\r\n },\r\n async onChangePage (page, pageSize) {\r\n this.tableData = await this.requestTableData({ currentPage: page, pageSize })\r\n },\r\n async requestTableData (props = {}) {\r\n const { url, requestType = 'GET', page = {} } = this.tableMeta\r\n this.tableQuerys = Object.assign(this.tableQuerys, { currentPage: this.tableQuerys.currentPage || 1, pageSize: this.tableQuerys.pageSize || page.pageSize || 10 }, props)\r\n this.$emit(RESERVE_EVENT_NAMES.WATCH, { ...this.exposed })\r\n this.loading = true\r\n const ret = await net[requestType.toLowerCase()](\r\n url,\r\n this.tableQuerys\r\n ).then(resp => {\r\n const { data = [], count } = resp || {}\r\n this.total = count\r\n this.loading = false\r\n return (data || []).map(item => {\r\n delete item.children\r\n return {\r\n key: uuidv4(),\r\n ...item\r\n }\r\n })\r\n })\r\n if (this.nextCursorConfig) {\r\n const { count: { url: countUrl, requestType = 'GET', params = {}, fieldMap = {} } } = this.nextCursorConfig\r\n const countRet = await net[requestType.toLowerCase()](\r\n countUrl,\r\n Object.assign({}, this.tableQuerys, params)\r\n ).then(resp => {\r\n const { data = 0 } = resp || {}\r\n return data\r\n })\r\n this.total = countRet\r\n }\r\n this.cleanCurrentModelEffect(false) // 不清空 currentRowData,除非明确需要\r\n this.tableData = ret\r\n return ret\r\n },\r\n calculateTableHeight () {\r\n const currentViewportHeight = window.innerHeight\r\n const tableRef = this.$refs[this.tableRef]\r\n if (!tableRef || !tableRef.$el) return\r\n \r\n const { top: tableToTop, width } = tableRef.$el.getBoundingClientRect()\r\n this.tableWidth = width\r\n \r\n // 获取分页组件的高度(如果存在)\r\n let paginationHeight = 0\r\n const paginationEl = tableRef.$el.querySelector('.g-table__pagination')\r\n if (paginationEl) {\r\n paginationHeight = paginationEl.getBoundingClientRect().height || 50\r\n } else {\r\n // 如果分页组件还未渲染,使用默认高度\r\n paginationHeight = 50\r\n }\r\n \r\n // 计算表格高度:视口高度 - 表格顶部距离 - 分页高度 - 额外高度\r\n const calculatedHeight = currentViewportHeight - tableToTop - paginationHeight - this.overHeight - 20\r\n // 确保最小高度,避免表格过小\r\n this.tableHeight = Math.max(calculatedHeight, 200)\r\n },\r\n calculateTreeHeight () {\r\n if (!this.showTree) return\r\n const modelTableContainerRef = this.$refs[this.modelTableContainerRef]\r\n if (!modelTableContainerRef) return\r\n \r\n const { height } = modelTableContainerRef.getBoundingClientRect()\r\n // 确保树的高度和表格容器高度一致\r\n this.treeWrapperHeight = height\r\n \r\n // 如果表格容器有标题,需要减去标题高度\r\n const titleEl = modelTableContainerRef.querySelector('.model__table--title')\r\n if (titleEl) {\r\n const titleHeight = titleEl.getBoundingClientRect().height\r\n this.treeWrapperHeight = height - titleHeight\r\n }\r\n },\r\n async keepAliveRefresh () {\r\n // 重新计算表格高度(应对窗口大小变化)\r\n this.$nextTick(() => {\r\n setTimeout(() => {\r\n this.calculateTableHeight()\r\n if (this.showTree) {\r\n this.calculateTreeHeight()\r\n }\r\n }, 200)\r\n })\r\n // 刷新列表数据\r\n const { overrideInit = false } = this.tableMeta\r\n if (overrideInit) {\r\n // 如果使用自定义初始化模式,触发 INIT 事件\r\n this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })\r\n } else {\r\n // 使用当前查询参数刷新表格数据\r\n await this.requestTableData(this.tableQuerys)\r\n }\r\n }\r\n },\r\n mounted () {\r\n // 初始化时先设置一个默认高度,避免布局混乱\r\n this.tableHeight = 400\r\n if (this.showTree) {\r\n this.treeWrapperHeight = 400\r\n }\r\n \r\n // 延迟计算,确保所有组件都已渲染\r\n this.$nextTick(() => {\r\n setTimeout(() => {\r\n this.calculateTableHeight()\r\n this.calculateTreeHeight()\r\n }, 200)\r\n })\r\n \r\n // 使用 ResizeObserver 监听容器大小变化\r\n this.resizeObserverModelTableWrapper = new ResizeObserver(entries => {\r\n for (const _ of entries) {\r\n requestAnimationFrame(() => {\r\n // 延迟重新计算,确保分页组件高度已更新\r\n setTimeout(() => {\r\n this.calculateTableHeight()\r\n if (this.showTree) {\r\n this.calculateTreeHeight()\r\n }\r\n }, 100)\r\n })\r\n }\r\n })\r\n \r\n if (this.$refs[this.modelTableWrapper]) {\r\n this.resizeObserverModelTableWrapper.observe(this.$refs[this.modelTableWrapper])\r\n }\r\n \r\n // 监听表格容器大小变化(用于同步树高度)\r\n if (this.showTree && this.$refs[this.modelTableContainerRef]) {\r\n this.resizeObserverModelTableContainer = new ResizeObserver(entries => {\r\n for (const _ of entries) {\r\n requestAnimationFrame(() => {\r\n this.calculateTreeHeight()\r\n })\r\n }\r\n })\r\n this.resizeObserverModelTableContainer.observe(this.$refs[this.modelTableContainerRef])\r\n }\r\n \r\n // 监听窗口大小变化\r\n this.handleResize = () => {\r\n this.$nextTick(() => {\r\n setTimeout(() => {\r\n this.calculateTableHeight()\r\n if (this.showTree) {\r\n this.calculateTreeHeight()\r\n }\r\n }, 100)\r\n })\r\n }\r\n window.addEventListener('resize', this.handleResize)\r\n },\r\n destroyed () {\r\n if (this.resizeObserverModelTableWrapper) {\r\n this.resizeObserverModelTableWrapper.disconnect()\r\n }\r\n if (this.resizeObserverModelTableContainer) {\r\n this.resizeObserverModelTableContainer.disconnect()\r\n }\r\n if (this.handleResize) {\r\n window.removeEventListener('resize', this.handleResize)\r\n }\r\n if (this.model) {\r\n // 清理订阅\r\n if (this.unsubscribe) {\r\n this.unsubscribe()\r\n }\r\n if (this.unsubscribeShared) {\r\n this.unsubscribeShared()\r\n }\r\n // 清理模型数据\r\n if (this.model) {\r\n this.model.cleanup()\r\n }\r\n }\r\n },\r\n async activated () {\r\n await this.keepAliveRefresh()\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.ele {\r\n &.model__tree-table {\r\n background: transparent; \r\n display: flex;\r\n flex-direction: row;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n .model__tree-table--container {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n .model__tree--wrapper {\r\n width: 240px;\r\n background: #fff;\r\n flex-shrink: 0;\r\n padding: 16px;\r\n box-sizing: border-box;\r\n margin-right: 16px;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n }\r\n }\r\n .model__table--container {\r\n width: 100%;\r\n min-width: 0;\r\n background: #fff;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n overflow: hidden;\r\n .model__table--title {\r\n .model__table-title--bar {\r\n width: 100%;\r\n height: 8px;\r\n background: var(--idooel-primary-color);\r\n border-top-left-radius: 4px;\r\n border-top-right-radius: 4px;\r\n }\r\n .model__table-title--text {\r\n text-align: left;\r\n padding: 16px;\r\n font-size: 16px;\r\n font-weight: bold;\r\n background: #fff;\r\n border-bottom: 1px solid;\r\n border-color: var(--idoole-black-016);\r\n }\r\n }\r\n .model__table--wrapper {\r\n background: #fff;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n overflow: hidden;\r\n .button-row__area {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding-top: 16px;\r\n padding-bottom: 8px;\r\n padding-right: 16px;\r\n flex-shrink: 0;\r\n }\r\n .g-table__wrapper {\r\n flex: 1;\r\n min-height: 0;\r\n overflow: hidden;\r\n .fsm {\r\n cursor: pointer;\r\n color: var(--idooel-primary-color);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n</style>\r\n",".ele.model__tree-table {\n background: transparent;\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__tree-table--container {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n.ele.model__tree-table .model__tree-table--container .model__tree--wrapper {\n width: 240px;\n background: #fff;\n flex-shrink: 0;\n padding: 16px;\n box-sizing: border-box;\n margin-right: 16px;\n overflow-y: auto;\n overflow-x: hidden;\n}\n.ele.model__tree-table .model__table--container {\n width: 100%;\n min-width: 0;\n background: #fff;\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--bar {\n width: 100%;\n height: 8px;\n background: var(--idooel-primary-color);\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.ele.model__tree-table .model__table--container .model__table--title .model__table-title--text {\n text-align: left;\n padding: 16px;\n font-size: 16px;\n font-weight: bold;\n background: #fff;\n border-bottom: 1px solid;\n border-color: var(--idoole-black-016);\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper {\n background: #fff;\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .button-row__area {\n width: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n padding-top: 16px;\n padding-bottom: 8px;\n padding-right: 16px;\n flex-shrink: 0;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n.ele.model__tree-table .model__table--container .model__table--wrapper .g-table__wrapper .fsm {\n cursor: pointer;\n color: var(--idooel-primary-color);\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
3684
4107
|
|
|
3685
4108
|
};
|
|
3686
4109
|
/* scoped */
|
|
3687
|
-
const __vue_scope_id__$B = "data-v-
|
|
4110
|
+
const __vue_scope_id__$B = "data-v-72d2cdcc";
|
|
3688
4111
|
/* module identifier */
|
|
3689
4112
|
const __vue_module_identifier__$B = undefined;
|
|
3690
4113
|
/* functional template */
|