@idooel/components 0.0.2-beta.32 → 0.0.2-beta.34
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 +81 -42
- package/dist/@idooel/components.umd.js +81 -42
- package/package.json +1 -1
- package/packages/form/src/index.vue +11 -0
- package/packages/table/src/index.vue +611 -610
|
@@ -2213,6 +2213,9 @@
|
|
|
2213
2213
|
const availableHeight = this.getScrollHeightByHeight;
|
|
2214
2214
|
if (availableHeight <= 0) return false;
|
|
2215
2215
|
|
|
2216
|
+
// 无数据时也需设置 scroll.y,使表格体有高度,否则“暂无数据”占位区域会塌陷无法正常显示
|
|
2217
|
+
if (!this.dataSource.length) return true;
|
|
2218
|
+
|
|
2216
2219
|
// 预估每行高度(包含边框),antd 默认约 54px
|
|
2217
2220
|
const estimatedRowHeight = 54;
|
|
2218
2221
|
const estimatedTableHeight = this.dataSource.length * estimatedRowHeight;
|
|
@@ -2231,8 +2234,8 @@
|
|
|
2231
2234
|
isFlexColumn() {
|
|
2232
2235
|
return this.columns.every(col => !col.width);
|
|
2233
2236
|
},
|
|
2234
|
-
/**
|
|
2235
|
-
* 计算所有列的总宽度(包括 rowSelection 的 checkbox 列和操作列)
|
|
2237
|
+
/**
|
|
2238
|
+
* 计算所有列的总宽度(包括 rowSelection 的 checkbox 列和操作列)
|
|
2236
2239
|
*/
|
|
2237
2240
|
totalColumnsWidth() {
|
|
2238
2241
|
const cols = this.innerColumns || [];
|
|
@@ -2248,8 +2251,8 @@
|
|
|
2248
2251
|
}
|
|
2249
2252
|
return total;
|
|
2250
2253
|
},
|
|
2251
|
-
/**
|
|
2252
|
-
* 是否需要横向滚动:容器宽度 < 列总宽度
|
|
2254
|
+
/**
|
|
2255
|
+
* 是否需要横向滚动:容器宽度 < 列总宽度
|
|
2253
2256
|
*/
|
|
2254
2257
|
needHorizontalScroll() {
|
|
2255
2258
|
// 未获取容器宽度前,先假定需要滚动(保守策略)
|
|
@@ -2257,10 +2260,10 @@
|
|
|
2257
2260
|
// 加一点容差
|
|
2258
2261
|
return this.containerWidth < this.totalColumnsWidth - 5;
|
|
2259
2262
|
},
|
|
2260
|
-
/**
|
|
2261
|
-
* 智能列配置:
|
|
2262
|
-
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2263
|
-
* - 当容器足够宽时,移除 fixed 属性,让表格自动铺满
|
|
2263
|
+
/**
|
|
2264
|
+
* 智能列配置:
|
|
2265
|
+
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2266
|
+
* - 当容器足够宽时,移除 fixed 属性,让表格自动铺满
|
|
2264
2267
|
*/
|
|
2265
2268
|
smartColumns() {
|
|
2266
2269
|
if (this.needHorizontalScroll) {
|
|
@@ -2279,10 +2282,10 @@
|
|
|
2279
2282
|
return col;
|
|
2280
2283
|
});
|
|
2281
2284
|
},
|
|
2282
|
-
/**
|
|
2283
|
-
* 智能 scroll 配置:
|
|
2284
|
-
* - 当需要横向滚动时,设置 scroll.x
|
|
2285
|
-
* - 当容器足够宽时,不设置 scroll.x,避免产生空白区域
|
|
2285
|
+
/**
|
|
2286
|
+
* 智能 scroll 配置:
|
|
2287
|
+
* - 当需要横向滚动时,设置 scroll.x
|
|
2288
|
+
* - 当容器足够宽时,不设置 scroll.x,避免产生空白区域
|
|
2286
2289
|
*/
|
|
2287
2290
|
smartScroll() {
|
|
2288
2291
|
if (!this.needHorizontalScroll) {
|
|
@@ -2300,10 +2303,10 @@
|
|
|
2300
2303
|
// 需要横向滚动,使用原有逻辑
|
|
2301
2304
|
return this.getScroll;
|
|
2302
2305
|
},
|
|
2303
|
-
/**
|
|
2304
|
-
* 智能 rowSelection 配置:
|
|
2305
|
-
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2306
|
-
* - 当容器足够宽时,移除 fixed 属性
|
|
2306
|
+
/**
|
|
2307
|
+
* 智能 rowSelection 配置:
|
|
2308
|
+
* - 当需要横向滚动时,保留原始 fixed 属性
|
|
2309
|
+
* - 当容器足够宽时,移除 fixed 属性
|
|
2307
2310
|
*/
|
|
2308
2311
|
smartRowSelection() {
|
|
2309
2312
|
if (!this.rowSelection) return null;
|
|
@@ -2367,9 +2370,9 @@
|
|
|
2367
2370
|
},
|
|
2368
2371
|
immediate: true
|
|
2369
2372
|
},
|
|
2370
|
-
/**
|
|
2371
|
-
* 监听 needHorizontalScroll 变化,强制重新渲染表格
|
|
2372
|
-
* 当从"需要固定列"切换到"不需要固定列"或反之时,antd 的 a-table 需要完全重新渲染
|
|
2373
|
+
/**
|
|
2374
|
+
* 监听 needHorizontalScroll 变化,强制重新渲染表格
|
|
2375
|
+
* 当从"需要固定列"切换到"不需要固定列"或反之时,antd 的 a-table 需要完全重新渲染
|
|
2373
2376
|
*/
|
|
2374
2377
|
needHorizontalScroll(newVal, oldVal) {
|
|
2375
2378
|
if (newVal !== oldVal) {
|
|
@@ -2432,12 +2435,12 @@
|
|
|
2432
2435
|
}
|
|
2433
2436
|
});
|
|
2434
2437
|
},
|
|
2435
|
-
/**
|
|
2436
|
-
* 带重试的固定列同步机制
|
|
2437
|
-
* antd 的 a-table 在 key 变化后重新渲染固定列需要一定时间,
|
|
2438
|
-
* 这里通过重试确保固定列渲染完成后再同步。
|
|
2439
|
-
* @param {boolean} needFixed - 是否需要固定列
|
|
2440
|
-
* @param {number} retries - 当前重试次数
|
|
2438
|
+
/**
|
|
2439
|
+
* 带重试的固定列同步机制
|
|
2440
|
+
* antd 的 a-table 在 key 变化后重新渲染固定列需要一定时间,
|
|
2441
|
+
* 这里通过重试确保固定列渲染完成后再同步。
|
|
2442
|
+
* @param {boolean} needFixed - 是否需要固定列
|
|
2443
|
+
* @param {number} retries - 当前重试次数
|
|
2441
2444
|
*/
|
|
2442
2445
|
retrySyncFixedColumns(needFixed, retries = 0) {
|
|
2443
2446
|
const MAX_RETRIES = 5;
|
|
@@ -2461,11 +2464,11 @@
|
|
|
2461
2464
|
}
|
|
2462
2465
|
}, DELAY);
|
|
2463
2466
|
},
|
|
2464
|
-
/**
|
|
2465
|
-
* 修复 x: 'max-content' 场景下表头不跟着横向滚动的问题。
|
|
2466
|
-
* 原因:ant-design-vue 的 header table 和 body table 各自算 max-content,
|
|
2467
|
-
* header 按表头文字算、body 按实际数据算,二者宽度不同时 header 就"滚不动"。
|
|
2468
|
-
* 方案:数据渲染后把 header table 的 width 强制设成和 body table 一样。
|
|
2467
|
+
/**
|
|
2468
|
+
* 修复 x: 'max-content' 场景下表头不跟着横向滚动的问题。
|
|
2469
|
+
* 原因:ant-design-vue 的 header table 和 body table 各自算 max-content,
|
|
2470
|
+
* header 按表头文字算、body 按实际数据算,二者宽度不同时 header 就"滚不动"。
|
|
2471
|
+
* 方案:数据渲染后把 header table 的 width 强制设成和 body table 一样。
|
|
2469
2472
|
*/
|
|
2470
2473
|
syncHeaderTableWidth() {
|
|
2471
2474
|
this.$nextTick(() => {
|
|
@@ -2483,8 +2486,8 @@
|
|
|
2483
2486
|
}
|
|
2484
2487
|
});
|
|
2485
2488
|
},
|
|
2486
|
-
/**
|
|
2487
|
-
* 监听表体横向滚动,同步到表头(防止 antd 自带同步失效)
|
|
2489
|
+
/**
|
|
2490
|
+
* 监听表体横向滚动,同步到表头(防止 antd 自带同步失效)
|
|
2488
2491
|
*/
|
|
2489
2492
|
bindScrollSync() {
|
|
2490
2493
|
const body = this.$el.querySelector('.ant-table-scroll .ant-table-body');
|
|
@@ -2505,8 +2508,8 @@
|
|
|
2505
2508
|
this._scrollHandler = null;
|
|
2506
2509
|
}
|
|
2507
2510
|
},
|
|
2508
|
-
/**
|
|
2509
|
-
* 测量容器宽度
|
|
2511
|
+
/**
|
|
2512
|
+
* 测量容器宽度
|
|
2510
2513
|
*/
|
|
2511
2514
|
measureContainerWidth() {
|
|
2512
2515
|
const wrapper = this.$refs.tableWrapper;
|
|
@@ -2514,8 +2517,8 @@
|
|
|
2514
2517
|
this.containerWidth = wrapper.clientWidth;
|
|
2515
2518
|
}
|
|
2516
2519
|
},
|
|
2517
|
-
/**
|
|
2518
|
-
* 使用 ResizeObserver 监听容器宽度变化
|
|
2520
|
+
/**
|
|
2521
|
+
* 使用 ResizeObserver 监听容器宽度变化
|
|
2519
2522
|
*/
|
|
2520
2523
|
observeContainerWidth() {
|
|
2521
2524
|
const wrapper = this.$refs.tableWrapper;
|
|
@@ -2560,8 +2563,8 @@
|
|
|
2560
2563
|
});
|
|
2561
2564
|
this._containerResizeObserver.observe(wrapper);
|
|
2562
2565
|
},
|
|
2563
|
-
/**
|
|
2564
|
-
* 断开容器宽度监听
|
|
2566
|
+
/**
|
|
2567
|
+
* 断开容器宽度监听
|
|
2565
2568
|
*/
|
|
2566
2569
|
unobserveContainerWidth() {
|
|
2567
2570
|
if (this._containerResizeObserver) {
|
|
@@ -2642,6 +2645,7 @@
|
|
|
2642
2645
|
"row-class-name": _vm.setRowClassName,
|
|
2643
2646
|
"data-source": _vm.dataSource,
|
|
2644
2647
|
scroll: _vm.smartScroll,
|
|
2648
|
+
locale: { emptyText: "暂无数据" },
|
|
2645
2649
|
},
|
|
2646
2650
|
scopedSlots: _vm._u([
|
|
2647
2651
|
{
|
|
@@ -2710,11 +2714,11 @@
|
|
|
2710
2714
|
/* style */
|
|
2711
2715
|
const __vue_inject_styles__$D = function (inject) {
|
|
2712
2716
|
if (!inject) return
|
|
2713
|
-
inject("data-v-483eea17_0", { source: "@charset \"UTF-8\";\n.g-table__wrapper[data-v-483eea17] {\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-483eea17] .ant-table-scroll .ant-table-header table,\n.g-table__wrapper[data-v-483eea17] .ant-table-scroll .ant-table-body table {\n min-width: 100%;\n}\n.g-table__wrapper[data-v-483eea17] {\n /**\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\n */\n}\n.g-table__wrapper[data-v-483eea17] .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-483eea17] .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-483eea17] {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper[data-v-483eea17] .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-483eea17] {\n /* 表头也统一高度和样式 */\n}\n.g-table__wrapper[data-v-483eea17] .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-483eea17] {\n /* 分页区域固定在底部 */\n}\n.g-table__wrapper .g-table__pagination[data-v-483eea17] {\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-483eea17] {\n /* 空数据状态顶部显示 */\n}\n.g-table__wrapper .g-table__no-data[data-v-483eea17] {\n position: relative;\n}\n.g-table__wrapper .g-table__no-data[data-v-483eea17] .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;AC2hBhB;EACA;;;;;IAAA;ADphBA;AC0hBA;;EAEA,eAAA;ADxhBA;AC+gBA;EAYA;;;;IAAA;ADphBA;ACyhBA;;EAEA,kBAAA;ADvhBA;ACogBA;EAsBA,sBAAA;ADvhBA;ACwhBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADthBA;AC0fA;EA+BA,eAAA;ADthBA;ACuhBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADrhBA;ACgfA;EAwCA,cAAA;ADrhBA;ACshBA;EACA,aAAA;EACA,mBAAA;EACA,oBAAA;EACA,iBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;ADphBA;ACoeA;EAmDA,cAAA;ADphBA;ACqhBA;EACA,kBAAA;ADnhBA;ACohBA;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;ADlhBA;;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\"\r\n :loading=\"countLoading\"\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 countLoading: {\r\n type: Boolean,\r\n default: false\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 });
|
|
2717
|
+
inject("data-v-3fe308bc_0", { source: "@charset \"UTF-8\";\n.g-table__wrapper[data-v-3fe308bc] {\n display: flex;\n flex-direction: column;\n /* 表格区域占据剩余空间并可滚动,分页始终在底部可见 */\n}\n.g-table__wrapper > *[data-v-3fe308bc]:first-child {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n.g-table__wrapper[data-v-3fe308bc] {\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-3fe308bc] .ant-table-scroll .ant-table-header table,\n.g-table__wrapper[data-v-3fe308bc] .ant-table-scroll .ant-table-body table {\n min-width: 100%;\n}\n.g-table__wrapper[data-v-3fe308bc] {\n /**\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\n */\n}\n.g-table__wrapper[data-v-3fe308bc] .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-3fe308bc] .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-3fe308bc] {\n /* 强制统一行高,确保主表和固定列对齐 */\n}\n.g-table__wrapper[data-v-3fe308bc] .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-3fe308bc] {\n /* 表头也统一高度和样式 */\n}\n.g-table__wrapper[data-v-3fe308bc] .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-3fe308bc] {\n /* 分页区域固定在底部,不被挤出视口 */\n}\n.g-table__wrapper .g-table__pagination[data-v-3fe308bc] {\n flex-shrink: 0;\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-3fe308bc] {\n /* 空数据状态:使用表格内置“暂无数据”,保留表头 + 占位行,不覆盖、不绝对定位 */\n}\n.g-table__wrapper .g-table__no-data[data-v-3fe308bc] .ant-table-placeholder {\n color: rgba(0, 0, 0, 0.45);\n font-size: 14px;\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;AC+hBhB;EACA,aAAA;EACA,sBAAA;EACA,6BAAA;AD7hBA;AC8hBA;EACA,OAAA;EACA,aAAA;EACA,gBAAA;AD5hBA;ACqhBA;EASA;;;;;IAAA;ADthBA;AC4hBA;;EAEA,eAAA;AD1hBA;ACygBA;EAoBA;;;;IAAA;ADthBA;AC2hBA;;EAEA,kBAAA;ADzhBA;AC8fA;EA8BA,sBAAA;ADzhBA;AC0hBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADxhBA;ACofA;EAuCA,eAAA;ADxhBA;ACyhBA;EACA,YAAA;EACA,iBAAA;EACA,sBAAA;EACA,sBAAA;EACA,iBAAA;ADvhBA;AC0eA;EAgDA,qBAAA;ADvhBA;ACwhBA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,oBAAA;EACA,iBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;ADthBA;AC6dA;EA4DA,4CAAA;ADthBA;ACwhBA;EACA,0BAAA;EACA,eAAA;ADthBA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["@charset \"UTF-8\";\n.g-table__wrapper {\n display: flex;\n flex-direction: column;\n /* 表格区域占据剩余空间并可滚动,分页始终在底部可见 */\n}\n.g-table__wrapper > *:first-child {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\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 flex-shrink: 0;\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 ::v-deep .ant-table-placeholder {\n color: rgba(0, 0, 0, 0.45);\n font-size: 14px;\n}\n\n/*# sourceMappingURL=index.vue.map */","<template>\n <div class=\"g-table__wrapper\" ref=\"tableWrapper\" :style=\"wrapperStyle\">\n <a-table\n :key=\"tableRenderKey\"\n :bordered=\"bordered\"\n :class=\"[isNoData && 'g-table__no-data']\"\n :pagination=\"false\"\n :loading=\"loading\"\n size=\"middle\"\n :columns=\"smartColumns\"\n :row-selection=\"smartRowSelection\"\n :row-class-name=\"setRowClassName\"\n :data-source=\"dataSource\"\n :scroll=\"smartScroll\"\n :locale=\"{ emptyText: '暂无数据' }\">\n <template slot=\"action\" slot-scope=\"record\">\n <Actions v-on=\"$listeners\" :data-source=\"actions\" :record=\"record\"></Actions>\n </template>\n </a-table>\n <div class=\"g-table__pagination\">\n <a-pagination\n :show-total=\"all => `共 ${all} 条数据`\"\n v-if=\"mode === 'default'\"\n show-size-changer \n show-quick-jumper\n :pageSize=\"innerPageSize\"\n :current=\"innerCurrentPage\"\n :pageSizeOptions=\"pageSizeOptions\"\n @change=\"onChangePagination\"\n @showSizeChange=\"onShowSizeChange\"\n :total=\"total\">\n </a-pagination>\n <ele-pagination\n v-else\n :pageSize=\"innerPageSize\"\n :current=\"innerCurrentPage\"\n :pageSizeOptions=\"pageSizeOptions\"\n :data=\"dataSource\"\n :loading=\"countLoading\"\n @change=\"onChangePagination\"\n @showSizeChange=\"onShowSizeChange\"\n :total=\"total\"\n ></ele-pagination>\n </div>\n </div>\n</template>\n\n<script>\nimport Actions from './action.vue'\nexport default {\n name: 'ele-table',\n components: {\n Actions\n },\n props: {\n mode: {\n type: String,\n default: 'default',\n validator: (value) => {\n return ['default', 'next-cursor'].includes(value)\n }\n },\n // ant table wrapper\n height: {\n type: Number\n },\n width: {\n type: Number\n },\n x: {\n // ant-design-vue: scroll.x 支持 number | string(如 'max-content')\n type: [Number, String],\n default: 1200\n },\n y: {\n type: Number,\n default: 200\n },\n scroll: {\n type: Object\n },\n rowSelection: {\n type: Object\n },\n actions: {\n type: Array,\n default: () => []\n },\n total: {\n type: Number,\n default: 0\n },\n loading: {\n type: Boolean,\n default: false\n },\n columns: {\n type: Array,\n default: () => []\n },\n dataSource: {\n type: Array,\n default: () => []\n },\n pageSize: {\n type: [Number, String],\n default: 10\n },\n pageSizeOptions: {\n type: Array,\n default: () => ['10', '20', '30', '40']\n },\n bordered: {\n type: Boolean,\n default: true\n },\n countLoading: {\n type: Boolean,\n default: false\n }\n },\n data() {\n return {\n tableHeaderHeight: 0,\n paginationHeight: 0,\n innerPageSize: 10,\n innerCurrentPage: 1,\n tableContentHeight: 0,\n obs: [],\n // 容器宽度,用于智能判断是否需要 fixed 列\n containerWidth: 0,\n // 用于强制重新渲染表格(当 fixed 列状态切换时)\n tableRenderKey: 0\n }\n },\n computed: {\n wrapperStyle () {\n // 外层容器样式\n if (!this.height) return {}\n return { height: `${this.height}px` }\n },\n needScrollY () {\n // 判断是否需要 y 轴滚动:基于数据行数与可用高度预估\n if (!this.height) return false\n \n const availableHeight = this.getScrollHeightByHeight\n if (availableHeight <= 0) return false\n \n // 无数据时也需设置 scroll.y,使表格体有高度,否则“暂无数据”占位区域会塌陷无法正常显示\n if (!this.dataSource.length) return true\n \n // 预估每行高度(包含边框),antd 默认约 54px\n const estimatedRowHeight = 54\n const estimatedTableHeight = this.dataSource.length * estimatedRowHeight\n \n return estimatedTableHeight > availableHeight\n },\n innerColumns () {\n return this.columns.filter(col => !Object.keys(col).includes('multiple'))\n },\n isNoData () {\n return !this.dataSource.length\n },\n getScrollHeightByHeight () {\n // 始终返回可用的剩余高度,让表格内容不足时也能占满容器\n return this.height - this.tableHeaderHeight - this.paginationHeight\n },\n isFlexColumn () {\n return this.columns.every(col => !col.width)\n },\n /**\n * 计算所有列的总宽度(包括 rowSelection 的 checkbox 列和操作列)\n */\n totalColumnsWidth () {\n const cols = this.innerColumns || []\n let total = cols.reduce((sum, col) => {\n const w = col && col.width\n return sum + (typeof w === 'number' ? w : 0)\n }, 0)\n // rowSelection 的 checkbox/radio 列,antd 默认约 60px\n if (this.rowSelection) total += 60\n // 操作列(operations)的宽度\n if (this.operations && this.operations.width && typeof this.operations.width === 'number') {\n total += this.operations.width\n }\n return total\n },\n /**\n * 是否需要横向滚动:容器宽度 < 列总宽度\n */\n needHorizontalScroll () {\n // 未获取容器宽度前,先假定需要滚动(保守策略)\n if (!this.containerWidth) return true\n // 加一点容差\n return this.containerWidth < this.totalColumnsWidth - 5\n },\n /**\n * 智能列配置:\n * - 当需要横向滚动时,保留原始 fixed 属性\n * - 当容器足够宽时,移除 fixed 属性,让表格自动铺满\n */\n smartColumns () {\n if (this.needHorizontalScroll) {\n // 需要滚动,保留原始配置\n return this.innerColumns\n }\n // 不需要滚动,移除所有 fixed 属性\n return this.innerColumns.map(col => {\n if (col.fixed) {\n const { fixed, ...rest } = col\n return rest\n }\n return col\n })\n },\n /**\n * 智能 scroll 配置:\n * - 当需要横向滚动时,设置 scroll.x\n * - 当容器足够宽时,不设置 scroll.x,避免产生空白区域\n */\n smartScroll () {\n if (!this.needHorizontalScroll) {\n // 不需要横向滚动,只保留 y 方向(如果需要)\n if (this.height && this.needScrollY) {\n const availableHeight = this.tableHeaderHeight && this.paginationHeight \n ? this.getScrollHeightByHeight \n : this.height - 100\n if (availableHeight > 50) {\n return { y: availableHeight }\n }\n }\n return {}\n }\n // 需要横向滚动,使用原有逻辑\n return this.getScroll\n },\n /**\n * 智能 rowSelection 配置:\n * - 当需要横向滚动时,保留原始 fixed 属性\n * - 当容器足够宽时,移除 fixed 属性\n */\n smartRowSelection () {\n if (!this.rowSelection) return null\n if (this.needHorizontalScroll) {\n return this.rowSelection\n }\n // 不需要滚动,移除 fixed 属性\n if (this.rowSelection.fixed) {\n const { fixed, ...rest } = this.rowSelection\n return rest\n }\n return this.rowSelection\n },\n getScroll () {\n if (this.scroll) {\n return this.scroll\n } else {\n // 固定列需要 scroll.x 才能正确同步行高,始终设置一个有效值\n let baseX = (this.x === '' || this.x === null || this.x === undefined) ? 1200 : this.x\n\n // 解决“x 给太大导致操作列前出现大块空白”的问题:\n // 当所有列都给了明确 width 时,scroll.x 取列宽总和最合理;大于总和会产生多余区域。\n if (typeof baseX === 'number') {\n const cols = this.innerColumns || []\n const total = cols.reduce((sum, col) => {\n const w = col && col.width\n return sum + (typeof w === 'number' ? w : 0)\n }, 0)\n\n // rowSelection 的 checkbox/radio 列是 antd 自动加的,给一个经验宽度避免误差\n const selectionWidth = this.rowSelection ? 60 : 0\n\n // 只有当所有列都明确给了宽度(total > 0)时才 clamp\n if (total > 0) {\n const minX = total + selectionWidth\n if (baseX > minX) baseX = minX\n }\n }\n \n if (this.height && this.needScrollY) {\n const availableHeight = this.tableHeaderHeight && this.paginationHeight \n ? this.getScrollHeightByHeight \n : this.height - 100\n \n if (availableHeight > 50) {\n return { x: baseX, y: availableHeight }\n }\n }\n return { x: baseX }\n }\n }\n },\n watch: {\n pageSize: {\n handler (pageSize) {\n this.innerPageSize = pageSize\n },\n immediate: true\n },\n /**\n * 监听 needHorizontalScroll 变化,强制重新渲染表格\n * 当从\"需要固定列\"切换到\"不需要固定列\"或反之时,antd 的 a-table 需要完全重新渲染\n */\n needHorizontalScroll (newVal, oldVal) {\n if (newVal !== oldVal) {\n // 更新 key 强制 Vue 销毁并重建 a-table 组件\n this.tableRenderKey++\n // 使用重试机制确保固定列完全渲染\n this.$nextTick(() => {\n this.retrySyncFixedColumns(newVal)\n })\n }\n }\n },\n methods: {\n onShowSizeChange (current, pageSize) {\n this.innerCurrentPage = current\n this.innerPageSize = pageSize\n this.$emit('change-page', current, pageSize)\n },\n setPaginationHeight () {\n this.$nextTick(() => {\n const el = this.$el.querySelector('.g-table__pagination')\n if (el) {\n const { height } = el.getBoundingClientRect()\n this.paginationHeight = height\n }\n })\n },\n setTableHeaderHeight () {\n this.$nextTick(() => {\n const el = this.$el.querySelector('.ant-table-header')\n if (!el) return\n const { height } = el.getBoundingClientRect()\n this.tableHeaderHeight = height\n })\n },\n setTableTbodyHeight () {\n this.$nextTick(() => {\n this.setTableHeaderHeight()\n })\n },\n setRowClassName (record, idx) {\n return idx % 2 === 0 ? 'g-table__row--even' : 'g-table__row--odd'\n },\n onChangePagination (page, pageSize) {\n this.innerCurrentPage = page\n this.innerPageSize = pageSize\n this.$emit('change-page', page, pageSize)\n },\n syncFixedColumns () {\n // 强制 ant-design-vue 重新计算固定列的宽度\n this.$nextTick(() => {\n const tableEl = this.$el.querySelector('.ant-table')\n if (tableEl) {\n // 触发窗口 resize 事件,让 ant-design-vue 重新计算\n window.dispatchEvent(new Event('resize'))\n }\n })\n },\n /**\n * 带重试的固定列同步机制\n * antd 的 a-table 在 key 变化后重新渲染固定列需要一定时间,\n * 这里通过重试确保固定列渲染完成后再同步。\n * @param {boolean} needFixed - 是否需要固定列\n * @param {number} retries - 当前重试次数\n */\n retrySyncFixedColumns (needFixed, retries = 0) {\n const MAX_RETRIES = 5\n const DELAY = 100 // 每次延迟 100ms\n\n setTimeout(() => {\n this.syncFixedColumns()\n this.syncHeaderTableWidth()\n this.bindScrollSync()\n\n // 如果需要固定列,检查是否已渲染\n if (needFixed && retries < MAX_RETRIES) {\n const hasFixedLeft = this.$el.querySelector('.ant-table-fixed-left .ant-table-body tbody tr')\n const hasFixedRight = this.$el.querySelector('.ant-table-fixed-right .ant-table-body tbody tr')\n \n // 如果有固定列配置但还没渲染出来,继续重试\n const hasFixedConfig = this.innerColumns.some(c => c.fixed) || (this.rowSelection && this.rowSelection.fixed)\n if (hasFixedConfig && !hasFixedLeft && !hasFixedRight) {\n this.retrySyncFixedColumns(needFixed, retries + 1)\n }\n }\n }, DELAY)\n },\n /**\n * 修复 x: 'max-content' 场景下表头不跟着横向滚动的问题。\n * 原因:ant-design-vue 的 header table 和 body table 各自算 max-content,\n * header 按表头文字算、body 按实际数据算,二者宽度不同时 header 就\"滚不动\"。\n * 方案:数据渲染后把 header table 的 width 强制设成和 body table 一样。\n */\n syncHeaderTableWidth () {\n this.$nextTick(() => {\n const headerTable = this.$el.querySelector('.ant-table-scroll .ant-table-header table')\n const bodyTable = this.$el.querySelector('.ant-table-scroll .ant-table-body table')\n if (!headerTable || !bodyTable) return\n\n const bodyW = bodyTable.getBoundingClientRect().width\n const headerW = headerTable.getBoundingClientRect().width\n\n // 始终同步表头宽度到表体宽度,确保窗口变大/变小时都能正确响应\n // 只有当宽度差异超过 2px 时才更新,避免频繁设置样式\n if (Math.abs(bodyW - headerW) > 2) {\n headerTable.style.width = `${bodyW}px`\n headerTable.style.minWidth = `${bodyW}px`\n }\n })\n },\n /**\n * 监听表体横向滚动,同步到表头(防止 antd 自带同步失效)\n */\n bindScrollSync () {\n const body = this.$el.querySelector('.ant-table-scroll .ant-table-body')\n const header = this.$el.querySelector('.ant-table-scroll .ant-table-header')\n if (!body || !header) return\n\n if (this._scrollHandler) return // 已绑定\n this._scrollHandler = () => {\n header.scrollLeft = body.scrollLeft\n }\n body.addEventListener('scroll', this._scrollHandler, { passive: true })\n },\n unbindScrollSync () {\n const body = this.$el.querySelector('.ant-table-scroll .ant-table-body')\n if (body && this._scrollHandler) {\n body.removeEventListener('scroll', this._scrollHandler)\n this._scrollHandler = null\n }\n },\n /**\n * 测量容器宽度\n */\n measureContainerWidth () {\n const wrapper = this.$refs.tableWrapper\n if (wrapper) {\n this.containerWidth = wrapper.clientWidth\n }\n },\n /**\n * 使用 ResizeObserver 监听容器宽度变化\n */\n observeContainerWidth () {\n const wrapper = this.$refs.tableWrapper\n if (!wrapper || typeof ResizeObserver === 'undefined') return\n\n if (this._containerResizeObserver) return // 已绑定\n\n this._containerResizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const newWidth = entry.contentRect.width\n // 只有宽度变化超过阈值才触发更新,避免微小变化导致频繁重渲染\n if (Math.abs(newWidth - this.containerWidth) > 10) {\n const oldNeedScroll = this.needHorizontalScroll\n this.containerWidth = newWidth\n // 容器宽度变化会触发 needHorizontalScroll 的重新计算\n // needHorizontalScroll 的 watcher 会处理表格重新渲染和列宽同步\n // 这里不需要直接调用 syncFixedColumns,避免与 watcher 的执行时机冲突\n // 无论 needHorizontalScroll 是否变化,都立即同步表头宽度\n // 确保窗口变大/变小时表头都能及时响应\n this.$nextTick(() => {\n // 使用 requestAnimationFrame 确保在浏览器重绘后同步表头宽度\n // 这样能确保表体宽度已经更新完成\n requestAnimationFrame(() => {\n this.syncHeaderTableWidth()\n })\n \n // 如果 needHorizontalScroll 状态没有变化,说明只是列宽需要调整,直接同步\n // 如果状态变化了,watcher 会处理重新渲染\n if (this.needHorizontalScroll === oldNeedScroll) {\n // 防抖:延迟同步固定列,避免频繁调用\n if (this._resizeDebounceTimer) {\n clearTimeout(this._resizeDebounceTimer)\n }\n this._resizeDebounceTimer = setTimeout(() => {\n this.syncFixedColumns()\n this.syncHeaderTableWidth()\n this.bindScrollSync()\n }, 150)\n }\n })\n }\n }\n })\n this._containerResizeObserver.observe(wrapper)\n },\n /**\n * 断开容器宽度监听\n */\n unobserveContainerWidth () {\n if (this._containerResizeObserver) {\n this._containerResizeObserver.disconnect()\n this._containerResizeObserver = null\n }\n // 清理防抖定时器\n if (this._resizeDebounceTimer) {\n clearTimeout(this._resizeDebounceTimer)\n this._resizeDebounceTimer = null\n }\n }\n },\n mounted() {\n this.$nextTick(() => {\n // 先测量容器宽度,用于智能判断是否需要 fixed 列\n this.measureContainerWidth()\n this.observeContainerWidth()\n\n this.setPaginationHeight()\n setTimeout(() => {\n this.setTableTbodyHeight()\n this.setPaginationHeight()\n // 强制同步固定列和主表的列宽\n this.syncFixedColumns()\n // 同步表头 table 宽度(修复 max-content 场景)\n this.syncHeaderTableWidth()\n // 绑定横向滚动同步\n this.bindScrollSync()\n }, 200)\n })\n \n // 监听数据变化,重新同步列宽\n this.$watch('dataSource', () => {\n this.$nextTick(() => {\n setTimeout(() => {\n this.syncFixedColumns()\n this.syncHeaderTableWidth()\n this.bindScrollSync()\n }, 100)\n })\n }, { deep: true })\n },\n destroyed () {\n this.obs.forEach(ob => ob.disconnect())\n this.unbindScrollSync()\n this.unobserveContainerWidth()\n }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.g-table__wrapper {\n display: flex;\n flex-direction: column;\n /* 表格区域占据剩余空间并可滚动,分页始终在底部可见 */\n & > *:first-child {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n /**\n * 修复\"宽屏下表格两侧出现空白\"问题:\n * 当视口宽度大于表格内容宽度时,主表(ant-table-scroll)不会自动拉伸,\n * 而固定列(fixed-left/fixed-right)是 position:absolute 定位在容器边缘,中间就出现空白。\n * 解决方案:让主表的 table 元素 min-width:100%,使其始终填满滚动容器。\n */\n ::v-deep .ant-table-scroll .ant-table-header table,\n ::v-deep .ant-table-scroll .ant-table-body table {\n min-width: 100%;\n }\n\n /**\n * 修复\"固定列 + scroll.x\"场景下,主表(ant-table-scroll)里会渲染一份 fixed 列的占位 header/cell。\n * 这份占位本来只用于计算宽度,但在某些布局下会被看见,表现为\"操作列前多了一大块空白/空列\"。\n * 这里用 visibility:hidden 隐藏占位(不影响占位宽度与 fixed 计算),避免视觉空白。\n */\n ::v-deep .ant-table-scroll .ant-table-header thead > tr > th.ant-table-fixed-columns-in-body.ant-table-row-cell-last,\n ::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\n /* 强制统一行高,确保主表和固定列对齐 */\n ::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\n /* 表头也统一高度和样式 */\n ::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\n /* 分页区域固定在底部,不被挤出视口 */\n .g-table__pagination {\n flex-shrink: 0;\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\n /* 空数据状态:使用表格内置“暂无数据”,保留表头 + 占位行,不覆盖、不绝对定位 */\n .g-table__no-data {\n ::v-deep .ant-table-placeholder {\n color: rgba(0, 0, 0, 0.45);\n font-size: 14px;\n }\n }\n}\n</style>"]}, media: undefined });
|
|
2714
2718
|
|
|
2715
2719
|
};
|
|
2716
2720
|
/* scoped */
|
|
2717
|
-
const __vue_scope_id__$D = "data-v-
|
|
2721
|
+
const __vue_scope_id__$D = "data-v-3fe308bc";
|
|
2718
2722
|
/* module identifier */
|
|
2719
2723
|
const __vue_module_identifier__$D = undefined;
|
|
2720
2724
|
/* functional template */
|
|
@@ -5858,6 +5862,41 @@
|
|
|
5858
5862
|
1
|
|
5859
5863
|
),
|
|
5860
5864
|
]
|
|
5865
|
+
: ele.type == "ele-month"
|
|
5866
|
+
? [
|
|
5867
|
+
_c(
|
|
5868
|
+
"a-form-item",
|
|
5869
|
+
{
|
|
5870
|
+
attrs: {
|
|
5871
|
+
label: ele.label + ":",
|
|
5872
|
+
required: ele._required,
|
|
5873
|
+
},
|
|
5874
|
+
},
|
|
5875
|
+
[
|
|
5876
|
+
_c("a-month-picker", {
|
|
5877
|
+
directives: [
|
|
5878
|
+
{
|
|
5879
|
+
name: "decorator",
|
|
5880
|
+
rawName: "v-decorator",
|
|
5881
|
+
value: [
|
|
5882
|
+
ele.name,
|
|
5883
|
+
{ rules: _vm.rebuildRules(ele) },
|
|
5884
|
+
],
|
|
5885
|
+
expression:
|
|
5886
|
+
"[ele.name, { rules: rebuildRules(ele) }]",
|
|
5887
|
+
},
|
|
5888
|
+
],
|
|
5889
|
+
staticStyle: { width: "100%" },
|
|
5890
|
+
attrs: {
|
|
5891
|
+
format: ele.format,
|
|
5892
|
+
"value-format": ele.valueFormat,
|
|
5893
|
+
disabled: ele._disabled,
|
|
5894
|
+
},
|
|
5895
|
+
}),
|
|
5896
|
+
],
|
|
5897
|
+
1
|
|
5898
|
+
),
|
|
5899
|
+
]
|
|
5861
5900
|
: ele.type == "ele-select-entity" ||
|
|
5862
5901
|
ele.type == "SelectEntity"
|
|
5863
5902
|
? [
|
|
@@ -6172,11 +6211,11 @@
|
|
|
6172
6211
|
/* style */
|
|
6173
6212
|
const __vue_inject_styles__$w = function (inject) {
|
|
6174
6213
|
if (!inject) return
|
|
6175
|
-
inject("data-v-7561ae99_0", { source: ".ele__form--wrapper[data-v-7561ae99] {\n background: #fff;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\form\\src\\index.vue","index.vue"],"names":[],"mappings":"AAoYA;EACA,gBAAA;ACnYA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\r\n <div class=\"ele__form--wrapper\">\r\n <a-form :form=\"form\" layout=\"vertical\" class=\"ant-advanced-search-form\">\r\n <a-row :gutter=\"24\">\r\n <template v-for=\"ele in elements\">\r\n <a-col v-if=\"ele._show\" :span=\"ele.span\" :key=\"`${ele.name}_${ele._rulesVersion || 0}`\">\r\n <template v-if=\"ele.type == 'ele-input' || ele.type == 'Input'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-input \r\n @change=\"onChange($event, ele)\" \r\n :disabled=\"ele._disabled\" \r\n :max-length=\"ele.maxLength\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-input>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-textarea' || ele.type == 'Textarea'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-textarea \r\n @change=\"onChange($event, ele)\"\r\n :max-length=\"ele.maxLength\"\r\n :autosize=\"ele.autosize\"\r\n :disabled=\"ele._disabled\"\r\n :allow-clear=\"ele.allowClear\"\r\n :placeholder=\"ele.placeholder\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\"\r\n style=\"width:100%;\">\r\n </ele-textarea>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select' || ele.type == 'Select'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select \r\n :data-source=\"ele.optionList\" \r\n :disabled=\"ele._disabled\"\r\n :multiple=\"ele.multiple\"\r\n :mode=\"ele.mode\"\r\n :code=\"ele.code\"\r\n :init=\"ele.init\"\r\n :url=\"ele.url\"\r\n :params=\"ele.params\" \r\n @change=\"onChange($event, ele)\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-upload'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-upload v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n :ext=\"ele.ext\"\r\n :multiple=\"ele.multiple\"\r\n :accept=\"ele.accept\"\r\n :extensions=\"ele.extensions\"\r\n :size=\"ele.size\"\r\n :icon=\"ele.icon\"\r\n :url=\"ele.url\"\r\n :message=\"ele.message\" \r\n style=\"width:100%;\">\r\n </ele-upload>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-date-range'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-date-range \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-date-range>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-date'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-date\r\n :format=\"ele.format\"\r\n :mode=\"ele.mode\"\r\n :show-time=\"ele.showTime\"\r\n :show-today=\"ele.showToday\"\r\n :value-format=\"ele.valueFormat\"\r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-date>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select-entity' || ele.type == 'SelectEntity'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select-entity \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select-entity>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'InputNumber' || ele.type == 'ele-input-number'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-input-number \r\n @change=\"onChange($event, ele)\" \r\n :precision=\"ele.precision\" \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n v-bind=\"ele.props\" \r\n style=\"width:100%;\">\r\n </ele-input-number>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-Checkbox' || ele.type == 'Checkbox'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-checkbox \r\n :data-source=\"ele.optionList\" \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-checkbox>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-radio' || ele.type == 'Radio'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-radio \r\n @change=\"onChange($event, ele)\" \r\n :disabled=\"ele._disabled\" \r\n :data-source=\"ele.optionList\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-radio>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-form-img-crop'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-form-img-crop\r\n @change=\"onChange($event, ele)\"\r\n :width=\"ele.width\"\r\n :height=\"ele.height\"\r\n :modal-title=\"ele.modalTitle\"\r\n :cropper-config=\"ele.cropperConfig\"\r\n :uploadFileConfig=\"ele.uploadFileConfig\"\r\n :oper-text=\"ele.operText\"\r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-form-img-crop>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-tree-select'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-tree-select\r\n @change=\"onChange($event, ele)\"\r\n :disabled=\"ele._disabled\"\r\n v-bind=\"ele.meta\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-tree-select>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select-entity-modal-table'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select-entity-modal-table\r\n @change=\"onChange($event, ele)\"\r\n :disabled=\"ele._disabled\"\r\n v-bind=\"ele.meta\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select-entity-modal-table>\r\n </a-form-item>\r\n </template>\r\n </a-col>\r\n </template>\r\n </a-row>\r\n </a-form>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport { parse } from '@idooel/expression'\r\nimport { type } from '@idooel/shared'\r\nimport { CONTEXT } from '../../utils'\r\nexport default {\r\n name: 'ele-form',\r\n props: {\r\n value: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n disabled: {\r\n type: [Boolean, String],\r\n default: '_routeMeta.disabled'\r\n },\r\n formName: {\r\n type: [Number, String],\r\n default: 'coordinated'\r\n },\r\n elements: {\r\n type: Array,\r\n default: () => []\r\n }\r\n },\r\n data () {\r\n return {\r\n formModel: {}\r\n }\r\n },\r\n inject: {\r\n [CONTEXT]: {\r\n default: () => (() => ({}))\r\n }\r\n },\r\n computed: {\r\n contextData () {\r\n return this[CONTEXT].call(this)\r\n },\r\n globalDisabled () {\r\n return this.executeExpression(this.disabled)\r\n },\r\n form () {\r\n const ref = this.$form.createForm(this, { name: this.formName, onFieldsChange: this.onFieldsChange })\r\n return ref\r\n },\r\n exposedMethods () {\r\n return {\r\n setFieldsValue: this.setFieldsValue,\r\n getFieldsValue: this.getFieldsValue\r\n }\r\n }\r\n },\r\n created () {\r\n this.setDefaultValues()\r\n },\r\n methods: {\r\n onFieldsChange () {\r\n this.executeOptionListExpression()\r\n this.dispatchExpression()\r\n },\r\n executeOptionListExpression () {\r\n this.elements.forEach(ele => {\r\n if (ele.optionList) {\r\n const optionList = this.evalOptionListExpression(ele.optionList)\r\n this.$set(ele, 'optionList', optionList)\r\n }\r\n })\r\n },\r\n evalOptionListExpression (optionList = []) {\r\n const executeExpression = (expression) => {\r\n if (type.isBool(expression)) return expression\r\n if (type.isEmpty(expression)) return true\r\n const ret = parse(expression, { ...this.formModel, ...this.getFieldsValue(), _route: this.$route.query, _routeMeta: this.$route.meta })\r\n return ret\r\n }\r\n return optionList.map(item => {\r\n const { show } = item\r\n return { ...item, _show: executeExpression(show) }\r\n })\r\n },\r\n setFormModel (props = {}) {\r\n this.formModel = props\r\n this.dispatchExpression()\r\n },\r\n rebuildRules (ele) {\r\n const { rules = [] } = ele\r\n const nextRules = rules.map(rule => {\r\n const nextRule = { ...rule }\r\n const { validator, required } = nextRule\r\n // 支持 validator 函数\r\n if (validator) {\r\n nextRule.validator = (rule, value, cb) => {\r\n validator.formModel = this.getFieldsValue()\r\n validator.exposed = this.exposedMethods\r\n return validator(rule, value, cb)\r\n }\r\n }\r\n // 支持 rules 内部的 required 表达式\r\n if (required !== undefined && type.isStr(required)) {\r\n nextRule.required = this.executeExpression(required)\r\n }\r\n return nextRule\r\n })\r\n // 支持顶层 required 表达式(优先级高于 rules 内部)\r\n const { required } = ele\r\n if (required !== undefined) {\r\n const isRequired = this.executeExpression(required)\r\n const hasRequiredRule = nextRules.some(r => Object.keys(r).includes('required'))\r\n if (!hasRequiredRule) {\r\n nextRules.unshift({ required: isRequired, message: `${ele.label}是必填项` })\r\n } else {\r\n nextRules.forEach(r => {\r\n if (Object.keys(r).includes('required')) {\r\n r.required = isRequired\r\n }\r\n })\r\n }\r\n }\r\n return nextRules\r\n },\r\n dispatchExpression () {\r\n this.evalDisabledExpression()\r\n this.evalShowExpression()\r\n this.evalRequiredExpression()\r\n },\r\n evalRequiredExpression () {\r\n this.elements.forEach(ele => {\r\n // 优先使用顶层 required 表达式\r\n let requiredExpr = ele.required\r\n // 如果顶层没有,则从 rules 中查找\r\n if (requiredExpr === undefined && ele.rules) {\r\n const requiredRule = ele.rules.find(r => r.required !== undefined)\r\n if (requiredRule) {\r\n requiredExpr = requiredRule.required\r\n }\r\n }\r\n if (requiredExpr !== undefined) {\r\n const ret = this.executeExpression(requiredExpr)\r\n const oldRequired = ele._required\r\n this.$set(ele, '_required', ret)\r\n // 初始化或状态变化时更新版本号,强制组件重新渲染\r\n if (oldRequired === undefined || oldRequired !== ret) {\r\n const currentVersion = ele._rulesVersion || 0\r\n this.$set(ele, '_rulesVersion', currentVersion + 1)\r\n }\r\n }\r\n })\r\n },\r\n evalDisabledExpression () {\r\n this.elements.forEach(ele => {\r\n if (this.globalDisabled) return this.$set(ele, '_disabled', true)\r\n const { disabled } = ele\r\n const ret = this.executeExpression(disabled)\r\n this.$set(ele, '_disabled', ret)\r\n })\r\n },\r\n evalShowExpression () {\r\n this.elements.forEach(ele => {\r\n const { show = true } = ele\r\n const ret = this.executeExpression(show)\r\n this.$set(ele, '_show', ret)\r\n })\r\n },\r\n executeExpression (expression) {\r\n if (type.isBool(expression)) return expression\r\n if (type.isEmpty(expression)) return false\r\n const scope = { ...this.formModel, ...this.getFieldsValue(), _route: this.$route.query, _routeMeta: this.$route.meta }\r\n return parse(expression, scope)\r\n },\r\n onChange (value, props) {\r\n const { name } = props\r\n this.$set(this.formModel, name, value)\r\n this.dispatchExpression()\r\n this.setFieldsValue({ [name]: value })\r\n this.$emit('change', { value, props, exposed: { ...this.exposedMethods } })\r\n },\r\n collectDefaultValues () {\r\n const ret = this.elements.reduce((ret, props) => {\r\n const { name, defaultValue } = props\r\n if (defaultValue) {\r\n ret[name] = defaultValue\r\n }\r\n return ret\r\n }, {})\r\n return ret\r\n },\r\n //TODO deprecated in the future, need to be implemented in the outer component\r\n setDefaultValues () {\r\n const defaultValues = this.collectDefaultValues()\r\n this.setFormModel(defaultValues)\r\n this.setFieldsValue(defaultValues)\r\n },\r\n validateFields () {\r\n return new Promise((resolve) => {\r\n this.form.validateFields((error, values) => {\r\n resolve(!error)\r\n })\r\n })\r\n },\r\n setFieldsValue (props = {}) {\r\n this.$nextTick(() => {\r\n this.form.setFieldsValue(props)\r\n })\r\n },\r\n getFieldsValue (fieldNames) {\r\n return this.form.getFieldsValue(fieldNames)\r\n }\r\n },\r\n mounted() {\r\n this.$emit('x:mounted', { setFormModel: this.setFormModel })\r\n this.dispatchExpression()\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.ele__form--wrapper {\r\n background: #fff;\r\n .ant-form-item {\r\n }\r\n}\r\n</style>",".ele__form--wrapper {\n background: #fff;\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
6214
|
+
inject("data-v-e9f46c30_0", { source: ".ele__form--wrapper[data-v-e9f46c30] {\n background: #fff;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["E:\\code\\OnlineStudy-Base\\base-elearning-frontend-model\\packages\\components\\packages\\form\\src\\index.vue","index.vue"],"names":[],"mappings":"AA+YA;EACA,gBAAA;AC9YA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\r\n <div class=\"ele__form--wrapper\">\r\n <a-form :form=\"form\" layout=\"vertical\" class=\"ant-advanced-search-form\">\r\n <a-row :gutter=\"24\">\r\n <template v-for=\"ele in elements\">\r\n <a-col v-if=\"ele._show\" :span=\"ele.span\" :key=\"`${ele.name}_${ele._rulesVersion || 0}`\">\r\n <template v-if=\"ele.type == 'ele-input' || ele.type == 'Input'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-input \r\n @change=\"onChange($event, ele)\" \r\n :disabled=\"ele._disabled\" \r\n :max-length=\"ele.maxLength\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-input>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-textarea' || ele.type == 'Textarea'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-textarea \r\n @change=\"onChange($event, ele)\"\r\n :max-length=\"ele.maxLength\"\r\n :autosize=\"ele.autosize\"\r\n :disabled=\"ele._disabled\"\r\n :allow-clear=\"ele.allowClear\"\r\n :placeholder=\"ele.placeholder\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\"\r\n style=\"width:100%;\">\r\n </ele-textarea>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select' || ele.type == 'Select'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select \r\n :data-source=\"ele.optionList\" \r\n :disabled=\"ele._disabled\"\r\n :multiple=\"ele.multiple\"\r\n :mode=\"ele.mode\"\r\n :code=\"ele.code\"\r\n :init=\"ele.init\"\r\n :url=\"ele.url\"\r\n :params=\"ele.params\" \r\n @change=\"onChange($event, ele)\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-upload'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-upload v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n :ext=\"ele.ext\"\r\n :multiple=\"ele.multiple\"\r\n :accept=\"ele.accept\"\r\n :extensions=\"ele.extensions\"\r\n :size=\"ele.size\"\r\n :icon=\"ele.icon\"\r\n :url=\"ele.url\"\r\n :message=\"ele.message\" \r\n style=\"width:100%;\">\r\n </ele-upload>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-date-range'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-date-range \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-date-range>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-date'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-date\r\n :format=\"ele.format\"\r\n :mode=\"ele.mode\"\r\n :show-time=\"ele.showTime\"\r\n :show-today=\"ele.showToday\"\r\n :value-format=\"ele.valueFormat\"\r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-date>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-month'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <a-month-picker\r\n :format=\"ele.format\"\r\n :value-format=\"ele.valueFormat\"\r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </a-month-picker>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select-entity' || ele.type == 'SelectEntity'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select-entity \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select-entity>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'InputNumber' || ele.type == 'ele-input-number'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-input-number \r\n @change=\"onChange($event, ele)\" \r\n :precision=\"ele.precision\" \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n v-bind=\"ele.props\" \r\n style=\"width:100%;\">\r\n </ele-input-number>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-Checkbox' || ele.type == 'Checkbox'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-checkbox \r\n :data-source=\"ele.optionList\" \r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-checkbox>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-radio' || ele.type == 'Radio'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-radio \r\n @change=\"onChange($event, ele)\" \r\n :disabled=\"ele._disabled\" \r\n :data-source=\"ele.optionList\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-radio>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-form-img-crop'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-form-img-crop\r\n @change=\"onChange($event, ele)\"\r\n :width=\"ele.width\"\r\n :height=\"ele.height\"\r\n :modal-title=\"ele.modalTitle\"\r\n :cropper-config=\"ele.cropperConfig\"\r\n :uploadFileConfig=\"ele.uploadFileConfig\"\r\n :oper-text=\"ele.operText\"\r\n :disabled=\"ele._disabled\" \r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-form-img-crop>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-tree-select'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-tree-select\r\n @change=\"onChange($event, ele)\"\r\n :disabled=\"ele._disabled\"\r\n v-bind=\"ele.meta\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-tree-select>\r\n </a-form-item>\r\n </template>\r\n <template v-else-if=\"ele.type == 'ele-select-entity-modal-table'\">\r\n <a-form-item :label=\"`${ele.label}:`\" :required=\"ele._required\">\r\n <ele-select-entity-modal-table\r\n @change=\"onChange($event, ele)\"\r\n :disabled=\"ele._disabled\"\r\n v-bind=\"ele.meta\"\r\n v-decorator=\"[ele.name, { rules: rebuildRules(ele) }]\" \r\n style=\"width:100%;\">\r\n </ele-select-entity-modal-table>\r\n </a-form-item>\r\n </template>\r\n </a-col>\r\n </template>\r\n </a-row>\r\n </a-form>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport { parse } from '@idooel/expression'\r\nimport { type } from '@idooel/shared'\r\nimport { CONTEXT } from '../../utils'\r\nexport default {\r\n name: 'ele-form',\r\n props: {\r\n value: {\r\n type: Object,\r\n default: () => ({})\r\n },\r\n disabled: {\r\n type: [Boolean, String],\r\n default: '_routeMeta.disabled'\r\n },\r\n formName: {\r\n type: [Number, String],\r\n default: 'coordinated'\r\n },\r\n elements: {\r\n type: Array,\r\n default: () => []\r\n }\r\n },\r\n data () {\r\n return {\r\n formModel: {}\r\n }\r\n },\r\n inject: {\r\n [CONTEXT]: {\r\n default: () => (() => ({}))\r\n }\r\n },\r\n computed: {\r\n contextData () {\r\n return this[CONTEXT].call(this)\r\n },\r\n globalDisabled () {\r\n return this.executeExpression(this.disabled)\r\n },\r\n form () {\r\n const ref = this.$form.createForm(this, { name: this.formName, onFieldsChange: this.onFieldsChange })\r\n return ref\r\n },\r\n exposedMethods () {\r\n return {\r\n setFieldsValue: this.setFieldsValue,\r\n getFieldsValue: this.getFieldsValue\r\n }\r\n }\r\n },\r\n created () {\r\n this.setDefaultValues()\r\n },\r\n methods: {\r\n onFieldsChange () {\r\n this.executeOptionListExpression()\r\n this.dispatchExpression()\r\n },\r\n executeOptionListExpression () {\r\n this.elements.forEach(ele => {\r\n if (ele.optionList) {\r\n const optionList = this.evalOptionListExpression(ele.optionList)\r\n this.$set(ele, 'optionList', optionList)\r\n }\r\n })\r\n },\r\n evalOptionListExpression (optionList = []) {\r\n const executeExpression = (expression) => {\r\n if (type.isBool(expression)) return expression\r\n if (type.isEmpty(expression)) return true\r\n const ret = parse(expression, { ...this.formModel, ...this.getFieldsValue(), _route: this.$route.query, _routeMeta: this.$route.meta })\r\n return ret\r\n }\r\n return optionList.map(item => {\r\n const { show } = item\r\n return { ...item, _show: executeExpression(show) }\r\n })\r\n },\r\n setFormModel (props = {}) {\r\n this.formModel = props\r\n this.dispatchExpression()\r\n },\r\n rebuildRules (ele) {\r\n const { rules = [] } = ele\r\n const nextRules = rules.map(rule => {\r\n const nextRule = { ...rule }\r\n const { validator, required } = nextRule\r\n // 支持 validator 函数\r\n if (validator) {\r\n nextRule.validator = (rule, value, cb) => {\r\n validator.formModel = this.getFieldsValue()\r\n validator.exposed = this.exposedMethods\r\n return validator(rule, value, cb)\r\n }\r\n }\r\n // 支持 rules 内部的 required 表达式\r\n if (required !== undefined && type.isStr(required)) {\r\n nextRule.required = this.executeExpression(required)\r\n }\r\n return nextRule\r\n })\r\n // 支持顶层 required 表达式(优先级高于 rules 内部)\r\n const { required } = ele\r\n if (required !== undefined) {\r\n const isRequired = this.executeExpression(required)\r\n const hasRequiredRule = nextRules.some(r => Object.keys(r).includes('required'))\r\n if (!hasRequiredRule) {\r\n nextRules.unshift({ required: isRequired, message: `${ele.label}是必填项` })\r\n } else {\r\n nextRules.forEach(r => {\r\n if (Object.keys(r).includes('required')) {\r\n r.required = isRequired\r\n }\r\n })\r\n }\r\n }\r\n return nextRules\r\n },\r\n dispatchExpression () {\r\n this.evalDisabledExpression()\r\n this.evalShowExpression()\r\n this.evalRequiredExpression()\r\n },\r\n evalRequiredExpression () {\r\n this.elements.forEach(ele => {\r\n // 优先使用顶层 required 表达式\r\n let requiredExpr = ele.required\r\n // 如果顶层没有,则从 rules 中查找\r\n if (requiredExpr === undefined && ele.rules) {\r\n const requiredRule = ele.rules.find(r => r.required !== undefined)\r\n if (requiredRule) {\r\n requiredExpr = requiredRule.required\r\n }\r\n }\r\n if (requiredExpr !== undefined) {\r\n const ret = this.executeExpression(requiredExpr)\r\n const oldRequired = ele._required\r\n this.$set(ele, '_required', ret)\r\n // 初始化或状态变化时更新版本号,强制组件重新渲染\r\n if (oldRequired === undefined || oldRequired !== ret) {\r\n const currentVersion = ele._rulesVersion || 0\r\n this.$set(ele, '_rulesVersion', currentVersion + 1)\r\n }\r\n }\r\n })\r\n },\r\n evalDisabledExpression () {\r\n this.elements.forEach(ele => {\r\n if (this.globalDisabled) return this.$set(ele, '_disabled', true)\r\n const { disabled } = ele\r\n const ret = this.executeExpression(disabled)\r\n this.$set(ele, '_disabled', ret)\r\n })\r\n },\r\n evalShowExpression () {\r\n this.elements.forEach(ele => {\r\n const { show = true } = ele\r\n const ret = this.executeExpression(show)\r\n this.$set(ele, '_show', ret)\r\n })\r\n },\r\n executeExpression (expression) {\r\n if (type.isBool(expression)) return expression\r\n if (type.isEmpty(expression)) return false\r\n const scope = { ...this.formModel, ...this.getFieldsValue(), _route: this.$route.query, _routeMeta: this.$route.meta }\r\n return parse(expression, scope)\r\n },\r\n onChange (value, props) {\r\n const { name } = props\r\n this.$set(this.formModel, name, value)\r\n this.dispatchExpression()\r\n this.setFieldsValue({ [name]: value })\r\n this.$emit('change', { value, props, exposed: { ...this.exposedMethods } })\r\n },\r\n collectDefaultValues () {\r\n const ret = this.elements.reduce((ret, props) => {\r\n const { name, defaultValue } = props\r\n if (defaultValue) {\r\n ret[name] = defaultValue\r\n }\r\n return ret\r\n }, {})\r\n return ret\r\n },\r\n //TODO deprecated in the future, need to be implemented in the outer component\r\n setDefaultValues () {\r\n const defaultValues = this.collectDefaultValues()\r\n this.setFormModel(defaultValues)\r\n this.setFieldsValue(defaultValues)\r\n },\r\n validateFields () {\r\n return new Promise((resolve) => {\r\n this.form.validateFields((error, values) => {\r\n resolve(!error)\r\n })\r\n })\r\n },\r\n setFieldsValue (props = {}) {\r\n this.$nextTick(() => {\r\n this.form.setFieldsValue(props)\r\n })\r\n },\r\n getFieldsValue (fieldNames) {\r\n return this.form.getFieldsValue(fieldNames)\r\n }\r\n },\r\n mounted() {\r\n this.$emit('x:mounted', { setFormModel: this.setFormModel })\r\n this.dispatchExpression()\r\n }\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.ele__form--wrapper {\r\n background: #fff;\r\n .ant-form-item {\r\n }\r\n}\r\n</style>",".ele__form--wrapper {\n background: #fff;\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
6176
6215
|
|
|
6177
6216
|
};
|
|
6178
6217
|
/* scoped */
|
|
6179
|
-
const __vue_scope_id__$w = "data-v-
|
|
6218
|
+
const __vue_scope_id__$w = "data-v-e9f46c30";
|
|
6180
6219
|
/* module identifier */
|
|
6181
6220
|
const __vue_module_identifier__$w = undefined;
|
|
6182
6221
|
/* functional template */
|
package/package.json
CHANGED
|
@@ -84,6 +84,17 @@
|
|
|
84
84
|
</ele-date>
|
|
85
85
|
</a-form-item>
|
|
86
86
|
</template>
|
|
87
|
+
<template v-else-if="ele.type == 'ele-month'">
|
|
88
|
+
<a-form-item :label="`${ele.label}:`" :required="ele._required">
|
|
89
|
+
<a-month-picker
|
|
90
|
+
:format="ele.format"
|
|
91
|
+
:value-format="ele.valueFormat"
|
|
92
|
+
:disabled="ele._disabled"
|
|
93
|
+
v-decorator="[ele.name, { rules: rebuildRules(ele) }]"
|
|
94
|
+
style="width:100%;">
|
|
95
|
+
</a-month-picker>
|
|
96
|
+
</a-form-item>
|
|
97
|
+
</template>
|
|
87
98
|
<template v-else-if="ele.type == 'ele-select-entity' || ele.type == 'SelectEntity'">
|
|
88
99
|
<a-form-item :label="`${ele.label}:`" :required="ele._required">
|
|
89
100
|
<ele-select-entity
|