foucui 1.0.1
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/README.md +43 -0
- package/components/QiuAffix/index.vue +186 -0
- package/components/QiuAlert/index.vue +208 -0
- package/components/QiuAnchor/context.ts +18 -0
- package/components/QiuAnchor/index.vue +180 -0
- package/components/QiuAnchorLink/index.vue +67 -0
- package/components/QiuApp/index.vue +9 -0
- package/components/QiuAutocomplete/index.vue +408 -0
- package/components/QiuAvatar/index.vue +140 -0
- package/components/QiuBacktop/index.vue +121 -0
- package/components/QiuBadge/api/index.ts +389 -0
- package/components/QiuBadge/components/BadgeImagePickerDialog.vue +243 -0
- package/components/QiuBadge/components/BadgeLibraryManagerDialog.vue +30 -0
- package/components/QiuBadge/components/BadgeLibraryPanel.vue +182 -0
- package/components/QiuBadge/composables/useTableHostHeight.ts +53 -0
- package/components/QiuBadge/index.ts +18 -0
- package/components/QiuBadge/shared/BadgeTemplateRenderer.vue +32 -0
- package/components/QiuBadge/shared/assetUrl.ts +26 -0
- package/components/QiuBadge/shared/badgeTemplate.ts +309 -0
- package/components/QiuBadge/utils/chinaAreaData.ts +44 -0
- package/components/QiuBadge/utils/hostRegion.ts +22 -0
- package/components/QiuBadge/utils/pca-code.json +1 -0
- package/components/QiuBadge/utils/pdfFromElement.ts +348 -0
- package/components/QiuBadge/views/BadgeLibraryView.vue +32 -0
- package/components/QiuBadge/views/BadgeManageView.vue +1364 -0
- package/components/QiuBadge/views/BadgeTemplateEditorView.vue +10 -0
- package/components/QiuBadge/views/BadgeTemplateListView.vue +229 -0
- package/components/QiuBadge/views/DeskLabelTemplateListView.vue +227 -0
- package/components/QiuBadge/views/DeskLabelView.vue +683 -0
- package/components/QiuBadgeDot/index.vue +87 -0
- package/components/QiuButton/index.vue +247 -0
- package/components/QiuButtonGroup/index.vue +63 -0
- package/components/QiuCanvasKit/QiuCanvasEditor.vue +5503 -0
- package/components/QiuCanvasKit/QiuCanvasRenderer.vue +348 -0
- package/components/QiuCanvasKit/assetUrl.ts +11 -0
- package/components/QiuCanvasKit/components/DataModulesPanel.vue +352 -0
- package/components/QiuCanvasKit/export/html.ts +132 -0
- package/components/QiuCanvasKit/export/pdf.ts +48 -0
- package/components/QiuCanvasKit/export/png.ts +50 -0
- package/components/QiuCanvasKit/index.ts +20 -0
- package/components/QiuCanvasKit/modules/badgeCompat.ts +84 -0
- package/components/QiuCanvasKit/modules/dataRegistry.ts +207 -0
- package/components/QiuCanvasKit/modules/merge.ts +71 -0
- package/components/QiuCanvasKit/presets/badge.ts +67 -0
- package/components/QiuCanvasKit/presets/banner.ts +79 -0
- package/components/QiuCanvasKit/types.ts +138 -0
- package/components/QiuCard/index.vue +104 -0
- package/components/QiuCarousel/context.ts +16 -0
- package/components/QiuCarousel/index.vue +343 -0
- package/components/QiuCarouselItem/index.vue +47 -0
- package/components/QiuCascader/index.vue +879 -0
- package/components/QiuCascader/types.ts +48 -0
- package/components/QiuCheckbox/QiuCheckboxGroup.vue +78 -0
- package/components/QiuCheckbox/index.vue +231 -0
- package/components/QiuCheckbox/types.ts +8 -0
- package/components/QiuCode/index.vue +64 -0
- package/components/QiuCol/col.scss +55 -0
- package/components/QiuCol/index.vue +64 -0
- package/components/QiuCollapse/context.ts +10 -0
- package/components/QiuCollapse/index.vue +70 -0
- package/components/QiuCollapseItem/index.vue +167 -0
- package/components/QiuColorField/index.vue +296 -0
- package/components/QiuColorPicker/README.md +118 -0
- package/components/QiuColorPicker/index.vue +60 -0
- package/components/QiuColorfulCard/index.vue +101 -0
- package/components/QiuCombobox/index.vue +277 -0
- package/components/QiuConfigProvider/index.vue +31 -0
- package/components/QiuContextMenu/index.vue +209 -0
- package/components/QiuDate/index.vue +1134 -0
- package/components/QiuDescriptions/context.ts +20 -0
- package/components/QiuDescriptions/index.vue +115 -0
- package/components/QiuDescriptionsItem/index.vue +103 -0
- package/components/QiuDialog/index.vue +828 -0
- package/components/QiuDigitalCard/index.vue +159 -0
- package/components/QiuDivider/index.vue +185 -0
- package/components/QiuDot/index.vue +83 -0
- package/components/QiuDrawer/index.vue +350 -0
- package/components/QiuDropdown/QiuDropdownItem.vue +74 -0
- package/components/QiuDropdown/QiuDropdownMenu.vue +22 -0
- package/components/QiuDropdown/context.ts +9 -0
- package/components/QiuDropdown/index.vue +189 -0
- package/components/QiuEmpty/index.vue +73 -0
- package/components/QiuForm/index.vue +172 -0
- package/components/QiuForm/types.ts +39 -0
- package/components/QiuFormItem/index.vue +276 -0
- package/components/QiuFullscreen/index.vue +11 -0
- package/components/QiuGrid/index.vue +408 -0
- package/components/QiuHoverCard/index.vue +192 -0
- package/components/QiuIcon/index.vue +117 -0
- package/components/QiuIconPicker/iconify.libraries.ts +182 -0
- package/components/QiuIconPicker/icons.catalog.ts +3072 -0
- package/components/QiuIconPicker/index.vue +488 -0
- package/components/QiuImage/index.vue +177 -0
- package/components/QiuInput/index.vue +328 -0
- package/components/QiuInputNumber/index.vue +295 -0
- package/components/QiuInputOtp/index.vue +190 -0
- package/components/QiuKbd/index.vue +57 -0
- package/components/QiuLayoutContainer/index.vue +50 -0
- package/components/QiuLinkPreview/index.vue +224 -0
- package/components/QiuLoading/index.ts +176 -0
- package/components/QiuLoading/spinner.vue +820 -0
- package/components/QiuLoading/types.ts +44 -0
- package/components/QiuMention/index.vue +299 -0
- package/components/QiuMenu/QiuMenuRoot.vue +241 -0
- package/components/QiuMenu/components/QiuMenuItem.vue +340 -0
- package/components/QiuMenu/components/QiuSubMenu.vue +504 -0
- package/components/QiuMenu/context.ts +21 -0
- package/components/QiuMenu/index.vue +43 -0
- package/components/QiuMenuNav/QiuMenuNavItem.vue +100 -0
- package/components/QiuMenuNav/QiuMenuNavSub.vue +120 -0
- package/components/QiuMenuNav/context.ts +14 -0
- package/components/QiuMenuNav/index.vue +148 -0
- package/components/QiuMsg/QiuMsgItem.vue +180 -0
- package/components/QiuMsg/index.ts +158 -0
- package/components/QiuMsg/types.ts +23 -0
- package/components/QiuMsgBox/index.ts +147 -0
- package/components/QiuNotification/QiuNotificationItem.vue +168 -0
- package/components/QiuNotification/index.ts +155 -0
- package/components/QiuNotification/types.ts +14 -0
- package/components/QiuPage/index.vue +12 -0
- package/components/QiuPageHeader/index.vue +115 -0
- package/components/QiuPageMain/index.vue +78 -0
- package/components/QiuPagination/index.vue +421 -0
- package/components/QiuPasswordStrength/index.vue +101 -0
- package/components/QiuPopconfirm/index.vue +196 -0
- package/components/QiuPopover/index.vue +277 -0
- package/components/QiuProgress/index.vue +235 -0
- package/components/QiuQrcode/index.vue +302 -0
- package/components/QiuQueryForm/components/QiuQueryFormBottomPanel.vue +13 -0
- package/components/QiuQueryForm/components/QiuQueryFormLeftPanel.vue +37 -0
- package/components/QiuQueryForm/components/QiuQueryFormRightPanel.vue +37 -0
- package/components/QiuQueryForm/components/QiuQueryFormTopPanel.vue +13 -0
- package/components/QiuQueryForm/index.vue +117 -0
- package/components/QiuRadio/QiuRadioGroup.vue +75 -0
- package/components/QiuRadio/index.vue +191 -0
- package/components/QiuRadio/types.ts +9 -0
- package/components/QiuRate/index.vue +184 -0
- package/components/QiuRefresh/index.vue +32 -0
- package/components/QiuResult/index.vue +103 -0
- package/components/QiuRow/index.vue +60 -0
- package/components/QiuRow/types.ts +5 -0
- package/components/QiuScrollbar/index.vue +107 -0
- package/components/QiuSearchBar/index.vue +154 -0
- package/components/QiuSegmented/index.vue +120 -0
- package/components/QiuSelect/QiuOption.vue +93 -0
- package/components/QiuSelect/index.vue +452 -0
- package/components/QiuSelect/types.ts +15 -0
- package/components/QiuSkeleton/index.vue +78 -0
- package/components/QiuSlider/index.vue +266 -0
- package/components/QiuSpace/index.vue +95 -0
- package/components/QiuSparkline/index.vue +92 -0
- package/components/QiuStatistic/index.vue +4 -0
- package/components/QiuStatistics/index.vue +123 -0
- package/components/QiuStep/index.vue +220 -0
- package/components/QiuSteps/context.ts +25 -0
- package/components/QiuSteps/index.vue +84 -0
- package/components/QiuSwitch/index.vue +145 -0
- package/components/QiuTabBar/index.vue +218 -0
- package/components/QiuTabBar/types.ts +19 -0
- package/components/QiuTabPane/index.vue +82 -0
- package/components/QiuTable/columns.ts +16 -0
- package/components/QiuTable/export.ts +102 -0
- package/components/QiuTable/index.ts +24 -0
- package/components/QiuTable/index.vue +3503 -0
- package/components/QiuTable/registry.ts +75 -0
- package/components/QiuTable/styles/table.scss +1230 -0
- package/components/QiuTable/types.ts +413 -0
- package/components/QiuTable/useArea.ts +202 -0
- package/components/QiuTable/useColumns.ts +325 -0
- package/components/QiuTable/useEdit.ts +136 -0
- package/components/QiuTable/useExtras.ts +115 -0
- package/components/QiuTable/useHistory.ts +77 -0
- package/components/QiuTable/useMerge.ts +105 -0
- package/components/QiuTable/useResize.ts +86 -0
- package/components/QiuTable/useScrollSync.ts +152 -0
- package/components/QiuTable/useVirtual.ts +97 -0
- package/components/QiuTable/utils.ts +165 -0
- package/components/QiuTableColumn/index.vue +101 -0
- package/components/QiuTag/index.vue +143 -0
- package/components/QiuText/index.vue +99 -0
- package/components/QiuTime/index.vue +548 -0
- package/components/QiuTimeAgo/index.vue +103 -0
- package/components/QiuTimeline/index.vue +54 -0
- package/components/QiuTimelineItem/index.vue +140 -0
- package/components/QiuTips/index.vue +323 -0
- package/components/QiuTooltip/index.vue +237 -0
- package/components/QiuTransfer/index.vue +277 -0
- package/components/QiuTree/QiuTreeNode.vue +146 -0
- package/components/QiuTree/index.vue +143 -0
- package/components/QiuTree/types.ts +30 -0
- package/components/QiuTreeSelect/index.vue +259 -0
- package/components/QiuTrend/index.vue +92 -0
- package/components/QiuUpload/index.vue +628 -0
- package/components/QiuUploadCrop/index.vue +1267 -0
- package/components/QiuWatermark/index.vue +121 -0
- package/components/useBodyScrollLock.ts +28 -0
- package/dist/components/QiuAnchor/context.d.ts +15 -0
- package/dist/components/QiuAnchorLink/index.vue.d.ts +23 -0
- package/dist/components/QiuBadge/api/index.d.ts +110 -0
- package/dist/components/QiuBadge/components/BadgeImagePickerDialog.vue.d.ts +16 -0
- package/dist/components/QiuBadge/components/BadgeLibraryManagerDialog.vue.d.ts +9 -0
- package/dist/components/QiuBadge/components/BadgeLibraryPanel.vue.d.ts +5 -0
- package/dist/components/QiuBadge/composables/useTableHostHeight.d.ts +7 -0
- package/dist/components/QiuBadge/index.d.ts +18 -0
- package/dist/components/QiuBadge/shared/BadgeTemplateRenderer.vue.d.ts +14 -0
- package/dist/components/QiuBadge/shared/assetUrl.d.ts +1 -0
- package/dist/components/QiuBadge/shared/badgeTemplate.d.ts +132 -0
- package/dist/components/QiuBadge/utils/chinaAreaData.d.ts +9 -0
- package/dist/components/QiuBadge/utils/hostRegion.d.ts +3 -0
- package/dist/components/QiuBadge/utils/pca-code.json.d.ts +3 -0
- package/dist/components/QiuBadge/utils/pdfFromElement.d.ts +14 -0
- package/dist/components/QiuBadge/views/BadgeLibraryView.vue.d.ts +2 -0
- package/dist/components/QiuBadge/views/BadgeManageView.vue.d.ts +7 -0
- package/dist/components/QiuBadge/views/BadgeTemplateListView.vue.d.ts +4 -0
- package/dist/components/QiuBadge/views/DeskLabelTemplateListView.vue.d.ts +4 -0
- package/dist/components/QiuBadge/views/DeskLabelView.vue.d.ts +4 -0
- package/dist/components/QiuButton/index.vue.d.ts +52 -0
- package/dist/components/QiuCanvasKit/QiuCanvasEditor.vue.d.ts +24 -0
- package/dist/components/QiuCanvasKit/QiuCanvasRenderer.vue.d.ts +13 -0
- package/dist/components/QiuCanvasKit/assetUrl.d.ts +7 -0
- package/dist/components/QiuCanvasKit/components/DataModulesPanel.vue.d.ts +23 -0
- package/dist/components/QiuCanvasKit/export/html.d.ts +12 -0
- package/dist/components/QiuCanvasKit/export/pdf.d.ts +9 -0
- package/dist/components/QiuCanvasKit/export/png.d.ts +8 -0
- package/dist/components/QiuCanvasKit/index.d.ts +19 -0
- package/dist/components/QiuCanvasKit/modules/badgeCompat.d.ts +8 -0
- package/dist/components/QiuCanvasKit/modules/dataRegistry.d.ts +23 -0
- package/dist/components/QiuCanvasKit/modules/merge.d.ts +12 -0
- package/dist/components/QiuCanvasKit/presets/badge.d.ts +12 -0
- package/dist/components/QiuCanvasKit/presets/banner.d.ts +4 -0
- package/dist/components/QiuCanvasKit/types.d.ts +119 -0
- package/dist/components/QiuCarousel/context.d.ts +13 -0
- package/dist/components/QiuCarouselItem/index.vue.d.ts +20 -0
- package/dist/components/QiuCascader/types.d.ts +25 -0
- package/dist/components/QiuCheckbox/QiuCheckboxGroup.vue.d.ts +33 -0
- package/dist/components/QiuCheckbox/index.vue.d.ts +38 -0
- package/dist/components/QiuCheckbox/types.d.ts +7 -0
- package/dist/components/QiuCode/index.vue.d.ts +15 -0
- package/dist/components/QiuCollapse/context.d.ts +8 -0
- package/dist/components/QiuCollapseItem/index.vue.d.ts +30 -0
- package/dist/components/QiuColorField/index.vue.d.ts +23 -0
- package/dist/components/QiuColorPicker/index.vue.d.ts +21 -0
- package/dist/components/QiuCombobox/index.vue.d.ts +54 -0
- package/dist/components/QiuContextMenu/index.vue.d.ts +46 -0
- package/dist/components/QiuDescriptions/context.d.ts +16 -0
- package/dist/components/QiuDescriptionsItem/index.vue.d.ts +25 -0
- package/dist/components/QiuDigitalCard/index.vue.d.ts +45 -0
- package/dist/components/QiuDropdown/QiuDropdownItem.vue.d.ts +30 -0
- package/dist/components/QiuDropdown/QiuDropdownMenu.vue.d.ts +17 -0
- package/dist/components/QiuDropdown/context.d.ts +7 -0
- package/dist/components/QiuDropdown/index.vue.d.ts +46 -0
- package/dist/components/QiuForm/types.d.ts +31 -0
- package/dist/components/QiuHoverCard/index.vue.d.ts +44 -0
- package/dist/components/QiuIcon/index.vue.d.ts +18 -0
- package/dist/components/QiuIconPicker/iconify.libraries.d.ts +18 -0
- package/dist/components/QiuIconPicker/icons.catalog.d.ts +12 -0
- package/dist/components/QiuInput/index.vue.d.ts +71 -0
- package/dist/components/QiuInputNumber/index.vue.d.ts +38 -0
- package/dist/components/QiuInputOtp/index.vue.d.ts +28 -0
- package/dist/components/QiuKbd/index.vue.d.ts +22 -0
- package/dist/components/QiuLayoutContainer/index.vue.d.ts +29 -0
- package/dist/components/QiuLinkPreview/index.vue.d.ts +43 -0
- package/dist/components/QiuLoading/index.d.ts +26 -0
- package/dist/components/QiuLoading/spinner.vue.d.ts +14 -0
- package/dist/components/QiuLoading/types.d.ts +2 -0
- package/dist/components/QiuMention/index.vue.d.ts +35 -0
- package/dist/components/QiuMenu/QiuMenuRoot.vue.d.ts +36 -0
- package/dist/components/QiuMenu/context.d.ts +19 -0
- package/dist/components/QiuMenuNav/QiuMenuNavItem.vue.d.ts +26 -0
- package/dist/components/QiuMenuNav/QiuMenuNavSub.vue.d.ts +25 -0
- package/dist/components/QiuMenuNav/context.d.ts +12 -0
- package/dist/components/QiuMenuNav/index.vue.d.ts +35 -0
- package/dist/components/QiuMsg/QiuMsgItem.vue.d.ts +15 -0
- package/dist/components/QiuMsg/index.d.ts +30 -0
- package/dist/components/QiuMsg/types.d.ts +12 -0
- package/dist/components/QiuMsgBox/index.d.ts +14 -0
- package/dist/components/QiuNotification/QiuNotificationItem.vue.d.ts +16 -0
- package/dist/components/QiuNotification/index.d.ts +30 -0
- package/dist/components/QiuNotification/types.d.ts +12 -0
- package/dist/components/QiuPageMain/index.vue.d.ts +27 -0
- package/dist/components/QiuPagination/index.vue.d.ts +44 -0
- package/dist/components/QiuPasswordStrength/index.vue.d.ts +9 -0
- package/dist/components/QiuQrcode/index.vue.d.ts +20 -0
- package/dist/components/QiuRadio/QiuRadioGroup.vue.d.ts +33 -0
- package/dist/components/QiuRadio/types.d.ts +8 -0
- package/dist/components/QiuRow/types.d.ts +4 -0
- package/dist/components/QiuSearchBar/index.vue.d.ts +38 -0
- package/dist/components/QiuSelect/QiuOption.vue.d.ts +24 -0
- package/dist/components/QiuSelect/index.vue.d.ts +52 -0
- package/dist/components/QiuSelect/types.d.ts +13 -0
- package/dist/components/QiuSparkline/index.vue.d.ts +18 -0
- package/dist/components/QiuStep/index.vue.d.ts +31 -0
- package/dist/components/QiuSteps/context.d.ts +21 -0
- package/dist/components/QiuSwitch/index.vue.d.ts +25 -0
- package/dist/components/QiuTabBar/index.vue.d.ts +32 -0
- package/dist/components/QiuTabBar/types.d.ts +16 -0
- package/dist/components/QiuTabPane/index.vue.d.ts +25 -0
- package/dist/components/QiuTable/columns.d.ts +1 -0
- package/dist/components/QiuTable/export.d.ts +18 -0
- package/dist/components/QiuTable/index.d.ts +3 -0
- package/dist/components/QiuTable/index.vue.d.ts +322 -0
- package/dist/components/QiuTable/registry.d.ts +39 -0
- package/dist/components/QiuTable/types.d.ts +353 -0
- package/dist/components/QiuTable/useArea.d.ts +51 -0
- package/dist/components/QiuTable/useColumns.d.ts +67 -0
- package/dist/components/QiuTable/useEdit.d.ts +36 -0
- package/dist/components/QiuTable/useExtras.d.ts +38 -0
- package/dist/components/QiuTable/useHistory.d.ts +44 -0
- package/dist/components/QiuTable/useMerge.d.ts +28 -0
- package/dist/components/QiuTable/useResize.d.ts +19 -0
- package/dist/components/QiuTable/useScrollSync.d.ts +25 -0
- package/dist/components/QiuTable/useVirtual.d.ts +30 -0
- package/dist/components/QiuTable/utils.d.ts +54 -0
- package/dist/components/QiuTime/index.vue.d.ts +43 -0
- package/dist/components/QiuTimeAgo/index.vue.d.ts +35 -0
- package/dist/components/QiuTimelineItem/index.vue.d.ts +35 -0
- package/dist/components/QiuTooltip/index.vue.d.ts +38 -0
- package/dist/components/QiuTree/types.d.ts +13 -0
- package/dist/components/QiuTrend/index.vue.d.ts +34 -0
- package/dist/components/QiuUpload/index.vue.d.ts +86 -0
- package/dist/components/QiuUploadCrop/index.vue.d.ts +136 -0
- package/dist/components/useBodyScrollLock.d.ts +4 -0
- package/dist/foucui.css +1 -0
- package/dist/foucui.js +26509 -0
- package/dist/foucui.umd.cjs +125 -0
- package/dist/index.d.ts +46 -0
- package/dist/src/bridge/@/api/media.d.ts +7 -0
- package/dist/src/bridge/@/api/notice.d.ts +5 -0
- package/dist/src/bridge/@/config/index.d.ts +29 -0
- package/dist/src/bridge/@/config/media.config.d.ts +15 -0
- package/dist/src/bridge/@/config/security.config.d.ts +7 -0
- package/dist/src/bridge/@/i18n/index.d.ts +7 -0
- package/dist/src/bridge/@/store/modules/bing.d.ts +3 -0
- package/dist/src/bridge/@/store/modules/errorLog.d.ts +3 -0
- package/dist/src/bridge/@/store/modules/media.d.ts +4 -0
- package/dist/src/bridge/@/store/modules/routes.d.ts +3 -0
- package/dist/src/bridge/@/store/modules/settings.d.ts +3 -0
- package/dist/src/bridge/@/store/modules/tabs.d.ts +3 -0
- package/dist/src/bridge/@/store/modules/user.d.ts +3 -0
- package/dist/src/bridge/@/utils/convertToCamelCase.d.ts +2 -0
- package/dist/src/bridge/@/utils/index.d.ts +5 -0
- package/dist/src/bridge/@/utils/pageTitle.d.ts +1 -0
- package/dist/src/bridge/@/utils/routes.d.ts +11 -0
- package/dist/src/bridge/@/utils/validate.d.ts +13 -0
- package/dist/src/bridge/@qiu/plugins/shortcuts.d.ts +2 -0
- package/dist/src/i18n.d.ts +2 -0
- package/package.json +83 -0
- package/plugins/qiu.ts +83 -0
- package/plugins/shortcuts.ts +69 -0
- package/src/bridge/@/api/media.ts +8 -0
- package/src/bridge/@/api/notice.ts +5 -0
- package/src/bridge/@/config/index.ts +38 -0
- package/src/bridge/@/config/media.config.ts +37 -0
- package/src/bridge/@/config/security.config.ts +8 -0
- package/src/bridge/@/i18n/index.ts +6 -0
- package/src/bridge/@/store/modules/bing.ts +9 -0
- package/src/bridge/@/store/modules/errorLog.ts +9 -0
- package/src/bridge/@/store/modules/media.ts +10 -0
- package/src/bridge/@/store/modules/routes.ts +10 -0
- package/src/bridge/@/store/modules/settings.ts +9 -0
- package/src/bridge/@/store/modules/tabs.ts +9 -0
- package/src/bridge/@/store/modules/user.ts +9 -0
- package/src/bridge/@/utils/convertToCamelCase.ts +11 -0
- package/src/bridge/@/utils/index.ts +9 -0
- package/src/bridge/@/utils/pageTitle.ts +4 -0
- package/src/bridge/@/utils/routes.ts +14 -0
- package/src/bridge/@/utils/validate.ts +17 -0
- package/src/bridge/@qiu/plugins/shortcuts.ts +3 -0
- package/src/i18n.ts +8 -0
- package/src/icons/qiu-logo.svg +1 -0
- package/styles/dark.scss +194 -0
- package/styles/normalize.css +349 -0
- package/styles/plain.scss +423 -0
- package/styles/qiu-tokens.scss +40 -0
- package/styles/qiu.scss +1149 -0
- package/styles/technology.scss +452 -0
- package/styles/var.scss +382 -0
- package/styles/variables.scss +77 -0
|
@@ -0,0 +1,3503 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="rootRef"
|
|
4
|
+
class="qiu-table"
|
|
5
|
+
tabindex="0"
|
|
6
|
+
:class="[
|
|
7
|
+
{
|
|
8
|
+
'qiu-table--border': border === true || border === 'full' || border === 'default',
|
|
9
|
+
'qiu-table--border-full': border === 'full' || border === true,
|
|
10
|
+
'qiu-table--border-outer': border === 'outer',
|
|
11
|
+
'qiu-table--border-inner': border === 'inner',
|
|
12
|
+
'qiu-table--border-none': border === 'none' || border === false,
|
|
13
|
+
'qiu-table--stripe': stripe,
|
|
14
|
+
'qiu-table--round': round,
|
|
15
|
+
'qiu-table--fullscreen': isFullscreen,
|
|
16
|
+
'qiu-table--toolbar': showToolbarArea,
|
|
17
|
+
'qiu-table--hover': resolveRowHover(rowConfig),
|
|
18
|
+
'qiu-table--fixed-height': height != null || maxHeight != null,
|
|
19
|
+
'qiu-table--scroll-x': hasScrollX,
|
|
20
|
+
'qiu-table--scroll-y': scrollbarSize > 0,
|
|
21
|
+
},
|
|
22
|
+
size ? `qiu-table--${size}` : '',
|
|
23
|
+
]"
|
|
24
|
+
:style="rootTableStyle"
|
|
25
|
+
@keydown="onTableKeydown"
|
|
26
|
+
>
|
|
27
|
+
<div v-if="showToolbarArea" class="qiu-table__toolbar">
|
|
28
|
+
<div class="qiu-table__toolbar-left">
|
|
29
|
+
<slot name="toolbar-left" />
|
|
30
|
+
<slot name="toolbar" />
|
|
31
|
+
<div v-if="toolsPlacement === 'left'" class="qiu-table__toolbar-builtin">
|
|
32
|
+
<template v-for="tool in toolButtonList" :key="tool.name">
|
|
33
|
+
<button
|
|
34
|
+
v-if="tool.show"
|
|
35
|
+
type="button"
|
|
36
|
+
class="qiu-table__toolbar-icon"
|
|
37
|
+
:title="tool.title"
|
|
38
|
+
@click="tool.onClick"
|
|
39
|
+
>
|
|
40
|
+
<qiu-icon :icon="tool.icon" />
|
|
41
|
+
</button>
|
|
42
|
+
</template>
|
|
43
|
+
<qiu-dropdown v-if="copyEnabled" class="qiu-table__toolbar-dropdown">
|
|
44
|
+
<button type="button" class="qiu-table__toolbar-icon" :title="translate('复制')">
|
|
45
|
+
<qiu-icon icon="file-copy-line" />
|
|
46
|
+
</button>
|
|
47
|
+
<template #dropdown>
|
|
48
|
+
<qiu-dropdown-menu>
|
|
49
|
+
<qiu-dropdown-item @click="copyTableData('vertical')">
|
|
50
|
+
{{ translate('纵向复制') }}
|
|
51
|
+
</qiu-dropdown-item>
|
|
52
|
+
<qiu-dropdown-item @click="copyTableData('horizontal')">
|
|
53
|
+
{{ translate('横向复制') }}
|
|
54
|
+
</qiu-dropdown-item>
|
|
55
|
+
</qiu-dropdown-menu>
|
|
56
|
+
</template>
|
|
57
|
+
</qiu-dropdown>
|
|
58
|
+
</div>
|
|
59
|
+
<component
|
|
60
|
+
:is="tool.component"
|
|
61
|
+
v-for="tool in customToolsByPosition('left')"
|
|
62
|
+
:key="tool.name"
|
|
63
|
+
v-bind="tool.props"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="qiu-table__toolbar-center">
|
|
67
|
+
<slot name="toolbar-center" />
|
|
68
|
+
<div v-if="toolsPlacement === 'center'" class="qiu-table__toolbar-builtin">
|
|
69
|
+
<template v-for="tool in toolButtonList" :key="tool.name">
|
|
70
|
+
<button
|
|
71
|
+
v-if="tool.show"
|
|
72
|
+
type="button"
|
|
73
|
+
class="qiu-table__toolbar-icon"
|
|
74
|
+
:title="tool.title"
|
|
75
|
+
@click="tool.onClick"
|
|
76
|
+
>
|
|
77
|
+
<qiu-icon :icon="tool.icon" />
|
|
78
|
+
</button>
|
|
79
|
+
</template>
|
|
80
|
+
<qiu-dropdown v-if="copyEnabled" class="qiu-table__toolbar-dropdown">
|
|
81
|
+
<button type="button" class="qiu-table__toolbar-icon" :title="translate('复制')">
|
|
82
|
+
<qiu-icon icon="file-copy-line" />
|
|
83
|
+
</button>
|
|
84
|
+
<template #dropdown>
|
|
85
|
+
<qiu-dropdown-menu>
|
|
86
|
+
<qiu-dropdown-item @click="copyTableData('vertical')">
|
|
87
|
+
{{ translate('纵向复制') }}
|
|
88
|
+
</qiu-dropdown-item>
|
|
89
|
+
<qiu-dropdown-item @click="copyTableData('horizontal')">
|
|
90
|
+
{{ translate('横向复制') }}
|
|
91
|
+
</qiu-dropdown-item>
|
|
92
|
+
</qiu-dropdown-menu>
|
|
93
|
+
</template>
|
|
94
|
+
</qiu-dropdown>
|
|
95
|
+
</div>
|
|
96
|
+
<component
|
|
97
|
+
:is="tool.component"
|
|
98
|
+
v-for="tool in customToolsByPosition('center')"
|
|
99
|
+
:key="tool.name"
|
|
100
|
+
v-bind="tool.props"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="qiu-table__toolbar-right">
|
|
104
|
+
<slot name="toolbar-tools" />
|
|
105
|
+
<slot name="toolbar-right" />
|
|
106
|
+
<div v-if="toolsPlacement === 'right'" class="qiu-table__toolbar-builtin">
|
|
107
|
+
<template v-for="tool in toolButtonList" :key="tool.name">
|
|
108
|
+
<button
|
|
109
|
+
v-if="tool.show"
|
|
110
|
+
type="button"
|
|
111
|
+
class="qiu-table__toolbar-icon"
|
|
112
|
+
:title="tool.title"
|
|
113
|
+
@click="tool.onClick"
|
|
114
|
+
>
|
|
115
|
+
<qiu-icon :icon="tool.icon" />
|
|
116
|
+
</button>
|
|
117
|
+
</template>
|
|
118
|
+
<qiu-dropdown v-if="copyEnabled" class="qiu-table__toolbar-dropdown">
|
|
119
|
+
<button type="button" class="qiu-table__toolbar-icon" :title="translate('复制')">
|
|
120
|
+
<qiu-icon icon="file-copy-line" />
|
|
121
|
+
</button>
|
|
122
|
+
<template #dropdown>
|
|
123
|
+
<qiu-dropdown-menu>
|
|
124
|
+
<qiu-dropdown-item @click="copyTableData('vertical')">
|
|
125
|
+
{{ translate('纵向复制') }}
|
|
126
|
+
</qiu-dropdown-item>
|
|
127
|
+
<qiu-dropdown-item @click="copyTableData('horizontal')">
|
|
128
|
+
{{ translate('横向复制') }}
|
|
129
|
+
</qiu-dropdown-item>
|
|
130
|
+
</qiu-dropdown-menu>
|
|
131
|
+
</template>
|
|
132
|
+
</qiu-dropdown>
|
|
133
|
+
</div>
|
|
134
|
+
<component
|
|
135
|
+
:is="tool.component"
|
|
136
|
+
v-for="tool in customToolsByPosition('right')"
|
|
137
|
+
:key="tool.name"
|
|
138
|
+
v-bind="tool.props"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div v-if="customPanelOpen" class="qiu-table__custom-panel">
|
|
144
|
+
<label v-for="col in customizableColumns" :key="col._key" class="qiu-table__custom-item">
|
|
145
|
+
<input
|
|
146
|
+
type="checkbox"
|
|
147
|
+
:checked="columnVisibleMap[col._key] !== false"
|
|
148
|
+
@change="toggleColumnVisible(col._key, ($event.target as HTMLInputElement).checked)"
|
|
149
|
+
/>
|
|
150
|
+
<span>{{ col.label || col._key }}</span>
|
|
151
|
+
</label>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<qiu-dialog
|
|
155
|
+
v-model="exportDialogVisible"
|
|
156
|
+
:title="translate('导出数据')"
|
|
157
|
+
width="480px"
|
|
158
|
+
align-center
|
|
159
|
+
:show-minimize="false"
|
|
160
|
+
:show-fullscreen="false"
|
|
161
|
+
>
|
|
162
|
+
<div class="qiu-table__export-dialog">
|
|
163
|
+
<div class="qiu-table__export-section">
|
|
164
|
+
<div class="qiu-table__export-label">{{ translate('导出字段') }}</div>
|
|
165
|
+
<div class="qiu-table__export-fields">
|
|
166
|
+
<label class="qiu-table__export-field">
|
|
167
|
+
<input
|
|
168
|
+
type="checkbox"
|
|
169
|
+
:checked="exportAllChecked"
|
|
170
|
+
:indeterminate.prop="exportIndeterminate"
|
|
171
|
+
@change="toggleExportAll(($event.target as HTMLInputElement).checked)"
|
|
172
|
+
/>
|
|
173
|
+
<span>{{ translate('全选') }}</span>
|
|
174
|
+
</label>
|
|
175
|
+
<label
|
|
176
|
+
v-for="col in exportableColumns"
|
|
177
|
+
:key="col._key"
|
|
178
|
+
class="qiu-table__export-field"
|
|
179
|
+
>
|
|
180
|
+
<input
|
|
181
|
+
type="checkbox"
|
|
182
|
+
:checked="exportFieldKeys.includes(col._key)"
|
|
183
|
+
@change="
|
|
184
|
+
toggleExportField(col._key, ($event.target as HTMLInputElement).checked)
|
|
185
|
+
"
|
|
186
|
+
/>
|
|
187
|
+
<span>{{ col.label || resolveColumnProp(col) || col._key }}</span>
|
|
188
|
+
</label>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="qiu-table__export-section">
|
|
192
|
+
<div class="qiu-table__export-label">{{ translate('文件格式') }}</div>
|
|
193
|
+
<div class="qiu-table__export-formats">
|
|
194
|
+
<label
|
|
195
|
+
v-for="fmt in exportFormats"
|
|
196
|
+
:key="fmt.value"
|
|
197
|
+
class="qiu-table__export-format"
|
|
198
|
+
:class="{ 'is-active': exportFormat === fmt.value }"
|
|
199
|
+
>
|
|
200
|
+
<input v-model="exportFormat" type="radio" :value="fmt.value" />
|
|
201
|
+
<span>{{ fmt.label }}</span>
|
|
202
|
+
</label>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="qiu-table__export-section">
|
|
206
|
+
<div class="qiu-table__export-label">{{ translate('导出范围') }}</div>
|
|
207
|
+
<div class="qiu-table__export-modes">
|
|
208
|
+
<label
|
|
209
|
+
v-for="m in exportModeOptions"
|
|
210
|
+
:key="m.value"
|
|
211
|
+
class="qiu-table__export-mode"
|
|
212
|
+
:class="{ 'is-active': exportMode === m.value }"
|
|
213
|
+
>
|
|
214
|
+
<input v-model="exportMode" type="radio" :value="m.value" />
|
|
215
|
+
<span>{{ m.label }}</span>
|
|
216
|
+
</label>
|
|
217
|
+
</div>
|
|
218
|
+
<div v-if="exportMode === 'range'" class="qiu-table__export-range">
|
|
219
|
+
<span>{{ translate('页码') }}</span>
|
|
220
|
+
<input
|
|
221
|
+
v-model.number="exportStartPage"
|
|
222
|
+
type="number"
|
|
223
|
+
:min="1"
|
|
224
|
+
:max="pageCount"
|
|
225
|
+
class="qiu-table__export-range-input"
|
|
226
|
+
/>
|
|
227
|
+
<span>-</span>
|
|
228
|
+
<input
|
|
229
|
+
v-model.number="exportEndPage"
|
|
230
|
+
type="number"
|
|
231
|
+
:min="exportStartPage"
|
|
232
|
+
:max="pageCount"
|
|
233
|
+
class="qiu-table__export-range-input"
|
|
234
|
+
/>
|
|
235
|
+
<span>{{ translate('(共') }} {{ pageCount }} {{ translate('页)') }}</span>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<template #footer>
|
|
240
|
+
<qiu-button @click="exportDialogVisible = false">{{ translate('取消') }}</qiu-button>
|
|
241
|
+
<qiu-button type="primary" :disabled="!exportFieldKeys.length" @click="confirmExport">
|
|
242
|
+
{{ translate('下载') }}
|
|
243
|
+
</qiu-button>
|
|
244
|
+
</template>
|
|
245
|
+
</qiu-dialog>
|
|
246
|
+
|
|
247
|
+
<input
|
|
248
|
+
ref="importInputRef"
|
|
249
|
+
type="file"
|
|
250
|
+
accept=".csv,.xls,.xlsx,text/csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
251
|
+
class="qiu-table__import-input"
|
|
252
|
+
@change="onImportFile"
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
<div class="qiu-table__main" :style="mainStyle">
|
|
256
|
+
<QiuLoadingSpinner
|
|
257
|
+
v-if="loading"
|
|
258
|
+
class="qiu-table__loading"
|
|
259
|
+
:text="loadingText || `${translate('加载中')}...`"
|
|
260
|
+
:effect="loadingEffect"
|
|
261
|
+
:transparent="loadingTransparent"
|
|
262
|
+
/>
|
|
263
|
+
<!-- Header -->
|
|
264
|
+
<div
|
|
265
|
+
ref="headerWrapRef"
|
|
266
|
+
class="qiu-table__header-wrap"
|
|
267
|
+
>
|
|
268
|
+
<table class="qiu-table__inner qiu-table__header-table" :style="tableMinWidthStyle">
|
|
269
|
+
<colgroup>
|
|
270
|
+
<col v-if="resolvedShowExpand" style="width: 48px" />
|
|
271
|
+
<col v-if="resolvedShowRadio" style="width: 48px" />
|
|
272
|
+
<col v-if="resolvedShowSelection" style="width: 48px" />
|
|
273
|
+
<col v-if="resolvedShowIndex" style="width: 55px" />
|
|
274
|
+
<col
|
|
275
|
+
v-if="virtualXEnabled && virtualXPad.left > 0"
|
|
276
|
+
:style="{ width: `${virtualXPad.left}px` }"
|
|
277
|
+
/>
|
|
278
|
+
<col
|
|
279
|
+
v-for="col in displayLeafColumns"
|
|
280
|
+
:key="`hcol-${col._key}`"
|
|
281
|
+
:style="resolvedColStyle(col)"
|
|
282
|
+
/>
|
|
283
|
+
<col
|
|
284
|
+
v-if="virtualXEnabled && virtualXPad.right > 0"
|
|
285
|
+
:style="{ width: `${virtualXPad.right}px` }"
|
|
286
|
+
/>
|
|
287
|
+
</colgroup>
|
|
288
|
+
<thead>
|
|
289
|
+
<tr v-for="(row, ri) in headerRows" :key="`hr-${ri}`">
|
|
290
|
+
<th
|
|
291
|
+
v-if="resolvedShowExpand && ri === 0"
|
|
292
|
+
class="qiu-table__cell qiu-table__cell--header qiu-table__cell--expand qiu-table__cell--fixed-left"
|
|
293
|
+
:class="specialLeftLastClass('expand')"
|
|
294
|
+
:style="{ left: '0px' }"
|
|
295
|
+
:rowspan="headerRows.length"
|
|
296
|
+
/>
|
|
297
|
+
<th
|
|
298
|
+
v-if="resolvedShowRadio && ri === 0"
|
|
299
|
+
class="qiu-table__cell qiu-table__cell--header qiu-table__cell--radio qiu-table__cell--fixed-left"
|
|
300
|
+
:class="specialLeftLastClass('radio')"
|
|
301
|
+
:style="{ left: `${fixedLeftOffset('radio')}px` }"
|
|
302
|
+
:rowspan="headerRows.length"
|
|
303
|
+
/>
|
|
304
|
+
<th
|
|
305
|
+
v-if="resolvedShowSelection && ri === 0"
|
|
306
|
+
class="qiu-table__cell qiu-table__cell--header qiu-table__cell--selection qiu-table__cell--fixed-left"
|
|
307
|
+
:class="specialLeftLastClass('selection')"
|
|
308
|
+
:style="{ left: `${fixedLeftOffset('selection')}px` }"
|
|
309
|
+
:rowspan="headerRows.length"
|
|
310
|
+
>
|
|
311
|
+
<qiu-checkbox
|
|
312
|
+
:model-value="isAllSelected"
|
|
313
|
+
:indeterminate="isIndeterminate"
|
|
314
|
+
@change="(v) => toggleSelectAll(!!v)"
|
|
315
|
+
/>
|
|
316
|
+
</th>
|
|
317
|
+
<th
|
|
318
|
+
v-if="resolvedShowIndex && ri === 0"
|
|
319
|
+
class="qiu-table__cell qiu-table__cell--header qiu-table__cell--index qiu-table__cell--fixed-left"
|
|
320
|
+
:class="specialLeftLastClass('index')"
|
|
321
|
+
:style="{ left: `${fixedLeftOffset('index')}px` }"
|
|
322
|
+
:rowspan="headerRows.length"
|
|
323
|
+
>
|
|
324
|
+
{{ translate('序号') }}
|
|
325
|
+
</th>
|
|
326
|
+
<th
|
|
327
|
+
v-if="virtualXEnabled && virtualXPad.left > 0 && ri === 0"
|
|
328
|
+
:rowspan="headerRows.length"
|
|
329
|
+
class="qiu-table__cell qiu-table__cell--header"
|
|
330
|
+
/>
|
|
331
|
+
<th
|
|
332
|
+
v-for="(cell, ci) in row"
|
|
333
|
+
:key="`th-${ri}-${ci}-${cell.column._key}`"
|
|
334
|
+
class="qiu-table__cell qiu-table__cell--header"
|
|
335
|
+
:class="[
|
|
336
|
+
cell.column.className,
|
|
337
|
+
cell.column.headerClassName,
|
|
338
|
+
cell.column.headerAlign
|
|
339
|
+
? `is-align-${cell.column.headerAlign}`
|
|
340
|
+
: `is-align-${cell.column.align || headerAlign || align || 'left'}`,
|
|
341
|
+
{
|
|
342
|
+
'qiu-table__cell--sortable': cell.isLeaf && !!cell.column.sortable,
|
|
343
|
+
'qiu-table__cell--col-drag': cell.isLeaf && columnDragEnabled,
|
|
344
|
+
...(cell.isLeaf ? fixedCellClass(cell.column as any) : {}),
|
|
345
|
+
},
|
|
346
|
+
]"
|
|
347
|
+
:colspan="cell.colspan"
|
|
348
|
+
:rowspan="cell.rowspan"
|
|
349
|
+
:style="headerCellStyle(cell)"
|
|
350
|
+
:data-col-key="cell.isLeaf ? cell.column._key : undefined"
|
|
351
|
+
@click="cell.isLeaf && onHeaderClick(cell.column as any, $event)"
|
|
352
|
+
>
|
|
353
|
+
<div class="qiu-table__th">
|
|
354
|
+
<qiu-icon
|
|
355
|
+
v-if="cell.isLeaf && getShowIcon() && canEditCol(cell.column as any)"
|
|
356
|
+
icon="edit-line"
|
|
357
|
+
class="qiu-table__edit-icon"
|
|
358
|
+
title="edit"
|
|
359
|
+
/>
|
|
360
|
+
<span class="qiu-table__label">{{ cell.label }}</span>
|
|
361
|
+
<span
|
|
362
|
+
v-if="cell.isLeaf && cell.column.sortable"
|
|
363
|
+
class="qiu-table__sort"
|
|
364
|
+
:class="sortClass(cell.column as any)"
|
|
365
|
+
>
|
|
366
|
+
<i class="qiu-table__caret qiu-table__caret--up" />
|
|
367
|
+
<i class="qiu-table__caret qiu-table__caret--down" />
|
|
368
|
+
<em v-if="sortPriority(cell.column as any)" class="qiu-table__sort-num">
|
|
369
|
+
{{ sortPriority(cell.column as any) }}
|
|
370
|
+
</em>
|
|
371
|
+
</span>
|
|
372
|
+
<button
|
|
373
|
+
v-if="cell.isLeaf && isFilterable(cell.column as any)"
|
|
374
|
+
type="button"
|
|
375
|
+
class="qiu-table__filter-btn"
|
|
376
|
+
:class="{
|
|
377
|
+
'is-active':
|
|
378
|
+
isFilterActive(cell.column as any) || openFilterKey === cell.column._key,
|
|
379
|
+
}"
|
|
380
|
+
@click.stop="toggleFilterPanel(cell.column as any, $event)"
|
|
381
|
+
>
|
|
382
|
+
<svg viewBox="0 0 1024 1024" width="12" height="12">
|
|
383
|
+
<path
|
|
384
|
+
fill="currentColor"
|
|
385
|
+
d="M128 192h768l-288 352v256l-192 64V544L128 192z"
|
|
386
|
+
/>
|
|
387
|
+
</svg>
|
|
388
|
+
</button>
|
|
389
|
+
<span
|
|
390
|
+
v-if="cell.isLeaf && isColResizable(cell.column as any)"
|
|
391
|
+
class="qiu-table__resizer"
|
|
392
|
+
@mousedown="onColResizeStart(cell.column as any, $event)"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
</th>
|
|
396
|
+
<th
|
|
397
|
+
v-if="virtualXEnabled && virtualXPad.right > 0 && ri === 0"
|
|
398
|
+
:rowspan="headerRows.length"
|
|
399
|
+
class="qiu-table__cell qiu-table__cell--header"
|
|
400
|
+
/>
|
|
401
|
+
</tr>
|
|
402
|
+
</thead>
|
|
403
|
+
</table>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<!-- Body -->
|
|
407
|
+
<div
|
|
408
|
+
ref="bodyWrapRef"
|
|
409
|
+
class="qiu-table__body-wrap"
|
|
410
|
+
@scroll="onBodyScroll"
|
|
411
|
+
@contextmenu.prevent="onBodyContextMenu"
|
|
412
|
+
>
|
|
413
|
+
<table ref="tableRef" class="qiu-table__inner qiu-table__body-table" :style="tableMinWidthStyle">
|
|
414
|
+
<colgroup>
|
|
415
|
+
<col v-if="resolvedShowExpand" style="width: 48px" />
|
|
416
|
+
<col v-if="resolvedShowRadio" style="width: 48px" />
|
|
417
|
+
<col v-if="resolvedShowSelection" style="width: 48px" />
|
|
418
|
+
<col v-if="resolvedShowIndex" style="width: 55px" />
|
|
419
|
+
<col
|
|
420
|
+
v-if="virtualXEnabled && virtualXPad.left > 0"
|
|
421
|
+
:style="{ width: `${virtualXPad.left}px` }"
|
|
422
|
+
/>
|
|
423
|
+
<col
|
|
424
|
+
v-for="col in displayLeafColumns"
|
|
425
|
+
:key="`bcol-${col._key}`"
|
|
426
|
+
:style="resolvedColStyle(col)"
|
|
427
|
+
/>
|
|
428
|
+
<col
|
|
429
|
+
v-if="virtualXEnabled && virtualXPad.right > 0"
|
|
430
|
+
:style="{ width: `${virtualXPad.right}px` }"
|
|
431
|
+
/>
|
|
432
|
+
</colgroup>
|
|
433
|
+
<tbody ref="tbodyRef" class="qiu-table__body">
|
|
434
|
+
<tr
|
|
435
|
+
v-if="virtualEnabled && virtualRange.padTop > 0"
|
|
436
|
+
class="qiu-table__virtual-pad"
|
|
437
|
+
aria-hidden="true"
|
|
438
|
+
>
|
|
439
|
+
<td :colspan="totalColspan">
|
|
440
|
+
<div
|
|
441
|
+
class="qiu-table__virtual-pad-space"
|
|
442
|
+
:style="{ height: `${virtualRange.padTop}px` }"
|
|
443
|
+
/>
|
|
444
|
+
</td>
|
|
445
|
+
</tr>
|
|
446
|
+
<template v-for="item in displayRowItems" :key="getRowId(item.flat.row) || item.flat.flatIndex">
|
|
447
|
+
<tr
|
|
448
|
+
class="qiu-table__row"
|
|
449
|
+
:class="[
|
|
450
|
+
{
|
|
451
|
+
'qiu-table__row--striped': stripe && item.flat.flatIndex % 2 === 1,
|
|
452
|
+
'is-selected': isRowSelected(item.flat.row),
|
|
453
|
+
'is-current': currentRowId === getRowId(item.flat.row),
|
|
454
|
+
'is-radio-checked': isRadioChecked(item.flat.row),
|
|
455
|
+
'is-dirty': keepSourceEnabled && isUpdateByRow(item.flat.row),
|
|
456
|
+
},
|
|
457
|
+
resolveRowClassName(item.flat.row, item.rowIndex),
|
|
458
|
+
]"
|
|
459
|
+
:data-row-key="getRowId(item.flat.row)"
|
|
460
|
+
@click="onRowClick(item.flat.row)"
|
|
461
|
+
>
|
|
462
|
+
<td
|
|
463
|
+
v-if="resolvedShowExpand"
|
|
464
|
+
class="qiu-table__cell qiu-table__cell--expand qiu-table__cell--fixed-left"
|
|
465
|
+
:class="specialLeftLastClass('expand')"
|
|
466
|
+
:style="{ left: '0px' }"
|
|
467
|
+
@click.stop="toggleRowExpand(item.flat.row)"
|
|
468
|
+
>
|
|
469
|
+
<div class="qiu-table__cell-inner">
|
|
470
|
+
<button type="button" class="qiu-table__tree-toggle">
|
|
471
|
+
<qiu-icon
|
|
472
|
+
:icon="isRowExpand(item.flat.row) ? 'arrow-down-s-fill' : 'arrow-right-s-fill'"
|
|
473
|
+
/>
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
476
|
+
</td>
|
|
477
|
+
<td
|
|
478
|
+
v-if="resolvedShowRadio"
|
|
479
|
+
class="qiu-table__cell qiu-table__cell--radio qiu-table__cell--fixed-left"
|
|
480
|
+
:class="specialLeftLastClass('radio')"
|
|
481
|
+
:style="{ left: `${fixedLeftOffset('radio')}px` }"
|
|
482
|
+
@click.stop="onRadioChange(item.flat.row)"
|
|
483
|
+
>
|
|
484
|
+
<div class="qiu-table__cell-inner">
|
|
485
|
+
<input
|
|
486
|
+
type="radio"
|
|
487
|
+
class="qiu-table__radio"
|
|
488
|
+
:checked="isRadioChecked(item.flat.row)"
|
|
489
|
+
:name="radioGroupName"
|
|
490
|
+
@change="onRadioChange(item.flat.row)"
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
493
|
+
</td>
|
|
494
|
+
<td
|
|
495
|
+
v-if="resolvedShowSelection"
|
|
496
|
+
class="qiu-table__cell qiu-table__cell--selection qiu-table__cell--fixed-left"
|
|
497
|
+
:class="specialLeftLastClass('selection')"
|
|
498
|
+
:style="{ left: `${fixedLeftOffset('selection')}px` }"
|
|
499
|
+
@click.stop
|
|
500
|
+
>
|
|
501
|
+
<div class="qiu-table__cell-inner">
|
|
502
|
+
<qiu-checkbox
|
|
503
|
+
:model-value="isRowSelected(item.flat.row)"
|
|
504
|
+
@change="(v) => toggleRowSelection(item.flat.row, !!v)"
|
|
505
|
+
/>
|
|
506
|
+
</div>
|
|
507
|
+
</td>
|
|
508
|
+
<td
|
|
509
|
+
v-if="resolvedShowIndex"
|
|
510
|
+
class="qiu-table__cell qiu-table__cell--index qiu-table__cell--fixed-left"
|
|
511
|
+
:class="specialLeftLastClass('index')"
|
|
512
|
+
:style="{ left: `${fixedLeftOffset('index')}px` }"
|
|
513
|
+
>
|
|
514
|
+
<div class="qiu-table__cell-inner">{{ item.flat.flatIndex + 1 }}</div>
|
|
515
|
+
</td>
|
|
516
|
+
<td v-if="virtualXEnabled && virtualXPad.left > 0" class="qiu-table__cell" />
|
|
517
|
+
<template
|
|
518
|
+
v-for="(col, colIndex) in displayLeafColumns"
|
|
519
|
+
:key="`td-${col._key}-${getRowId(item.flat.row)}`"
|
|
520
|
+
>
|
|
521
|
+
<td
|
|
522
|
+
v-if="cellSpan(item.flat, item.rowIndex, col, realColIndex(col)).visible"
|
|
523
|
+
class="qiu-table__cell"
|
|
524
|
+
:class="[
|
|
525
|
+
col.className,
|
|
526
|
+
resolveCellClassName(item.flat.row, col, item.rowIndex, realColIndex(col)),
|
|
527
|
+
`is-align-${col.align || align || 'left'}`,
|
|
528
|
+
{
|
|
529
|
+
'qiu-table__cell--ellipsis': shouldEllipsis(col),
|
|
530
|
+
'is-editing': isEditing(item.flat.row, col),
|
|
531
|
+
'is-input-fill': isInputFill(item.flat.row, col),
|
|
532
|
+
'is-editable': canEditCol(col),
|
|
533
|
+
'is-valid-error': !!cellError(item.flat.row, col),
|
|
534
|
+
'is-active-cell': isActiveCell(item.flat.row, col),
|
|
535
|
+
'is-area-cell': isCellInArea(item.flat.row, col),
|
|
536
|
+
...fixedCellClass(col),
|
|
537
|
+
},
|
|
538
|
+
]"
|
|
539
|
+
:style="bodyCellStyle(col)"
|
|
540
|
+
:title="cellError(item.flat.row, col) || undefined"
|
|
541
|
+
:rowspan="cellSpan(item.flat, item.rowIndex, col, realColIndex(col)).rowspan"
|
|
542
|
+
:colspan="cellSpan(item.flat, item.rowIndex, col, realColIndex(col)).colspan"
|
|
543
|
+
@click="onCellClick(item.flat.row, col, $event)"
|
|
544
|
+
@dblclick="onCellDblclick(item.flat.row, col)"
|
|
545
|
+
>
|
|
546
|
+
<div
|
|
547
|
+
class="qiu-table__cell-inner"
|
|
548
|
+
:class="{
|
|
549
|
+
'qiu-table__cell-inner--input-fill': isInputFill(item.flat.row, col),
|
|
550
|
+
}"
|
|
551
|
+
>
|
|
552
|
+
<span v-if="col.dragSort" class="qiu-table__drag-handle">
|
|
553
|
+
<qiu-icon icon="drag-move-2-line" />
|
|
554
|
+
</span>
|
|
555
|
+
|
|
556
|
+
<span
|
|
557
|
+
v-else-if="tree && col.treeNode"
|
|
558
|
+
class="qiu-table__tree-cell"
|
|
559
|
+
:style="{ paddingLeft: `${item.flat.level * 18}px` }"
|
|
560
|
+
>
|
|
561
|
+
<button
|
|
562
|
+
v-if="item.flat.hasChildren"
|
|
563
|
+
type="button"
|
|
564
|
+
class="qiu-table__tree-toggle"
|
|
565
|
+
@click.stop="toggleExpand(item.flat.row)"
|
|
566
|
+
>
|
|
567
|
+
<qiu-icon
|
|
568
|
+
:icon="item.flat.expanded ? 'arrow-down-s-fill' : 'arrow-right-s-fill'"
|
|
569
|
+
/>
|
|
570
|
+
</button>
|
|
571
|
+
<span v-else class="qiu-table__tree-placeholder" />
|
|
572
|
+
<cell-content
|
|
573
|
+
:row="item.flat.row"
|
|
574
|
+
:col="col"
|
|
575
|
+
:index="item.flat.flatIndex"
|
|
576
|
+
/>
|
|
577
|
+
</span>
|
|
578
|
+
|
|
579
|
+
<slot
|
|
580
|
+
v-else-if="col.slot && $slots[col.slot]"
|
|
581
|
+
:name="col.slot"
|
|
582
|
+
v-bind="
|
|
583
|
+
mapSlotScope(
|
|
584
|
+
item.flat.row,
|
|
585
|
+
col,
|
|
586
|
+
item.flat.flatIndex,
|
|
587
|
+
item.flat.level
|
|
588
|
+
)
|
|
589
|
+
"
|
|
590
|
+
/>
|
|
591
|
+
|
|
592
|
+
<component
|
|
593
|
+
:is="col._defaultSlot"
|
|
594
|
+
v-else-if="col._defaultSlot"
|
|
595
|
+
v-bind="
|
|
596
|
+
mapSlotScope(
|
|
597
|
+
item.flat.row,
|
|
598
|
+
col,
|
|
599
|
+
item.flat.flatIndex,
|
|
600
|
+
item.flat.level
|
|
601
|
+
)
|
|
602
|
+
"
|
|
603
|
+
/>
|
|
604
|
+
|
|
605
|
+
<div
|
|
606
|
+
v-else
|
|
607
|
+
class="qiu-table__editor-host"
|
|
608
|
+
:class="{
|
|
609
|
+
'qiu-table__editor-host--input-fill': isInputFill(item.flat.row, col),
|
|
610
|
+
}"
|
|
611
|
+
>
|
|
612
|
+
<cell-content
|
|
613
|
+
:row="item.flat.row"
|
|
614
|
+
:col="col"
|
|
615
|
+
:index="item.flat.flatIndex"
|
|
616
|
+
/>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</td>
|
|
620
|
+
</template>
|
|
621
|
+
<td v-if="virtualXEnabled && virtualXPad.right > 0" class="qiu-table__cell" />
|
|
622
|
+
</tr>
|
|
623
|
+
<tr v-if="resolvedShowExpand && isRowExpand(item.flat.row)" class="qiu-table__expand-row">
|
|
624
|
+
<td :colspan="totalColspan" class="qiu-table__cell qiu-table__cell--expand-content">
|
|
625
|
+
<div class="qiu-table__expand-content">
|
|
626
|
+
<slot name="expand" :row="item.flat.row" :$index="item.flat.flatIndex">
|
|
627
|
+
<pre class="qiu-table__expand-json">{{ JSON.stringify(item.flat.row, null, 2) }}</pre>
|
|
628
|
+
</slot>
|
|
629
|
+
</div>
|
|
630
|
+
</td>
|
|
631
|
+
</tr>
|
|
632
|
+
</template>
|
|
633
|
+
<tr
|
|
634
|
+
v-if="virtualEnabled && virtualRange.padBottom > 0"
|
|
635
|
+
class="qiu-table__virtual-pad"
|
|
636
|
+
aria-hidden="true"
|
|
637
|
+
>
|
|
638
|
+
<td :colspan="totalColspan">
|
|
639
|
+
<div
|
|
640
|
+
class="qiu-table__virtual-pad-space"
|
|
641
|
+
:style="{ height: `${virtualRange.padBottom}px` }"
|
|
642
|
+
/>
|
|
643
|
+
</td>
|
|
644
|
+
</tr>
|
|
645
|
+
<tr v-if="!pagedRows.length" class="qiu-table__row qiu-table__row--empty">
|
|
646
|
+
<td class="qiu-table__cell qiu-table__cell--empty" :colspan="totalColspan">
|
|
647
|
+
<slot name="empty">{{ emptyText || translate('暂无数据') }}</slot>
|
|
648
|
+
</td>
|
|
649
|
+
</tr>
|
|
650
|
+
</tbody>
|
|
651
|
+
</table>
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<!-- Footer -->
|
|
655
|
+
<div
|
|
656
|
+
v-if="showFooter && footerRows.length"
|
|
657
|
+
ref="footerWrapRef"
|
|
658
|
+
class="qiu-table__footer-wrap"
|
|
659
|
+
>
|
|
660
|
+
<table class="qiu-table__inner qiu-table__footer-table" :style="tableMinWidthStyle">
|
|
661
|
+
<colgroup>
|
|
662
|
+
<col v-if="resolvedShowExpand" style="width: 48px" />
|
|
663
|
+
<col v-if="resolvedShowRadio" style="width: 48px" />
|
|
664
|
+
<col v-if="resolvedShowSelection" style="width: 48px" />
|
|
665
|
+
<col v-if="resolvedShowIndex" style="width: 55px" />
|
|
666
|
+
<col
|
|
667
|
+
v-if="virtualXEnabled && virtualXPad.left > 0"
|
|
668
|
+
:style="{ width: `${virtualXPad.left}px` }"
|
|
669
|
+
/>
|
|
670
|
+
<col
|
|
671
|
+
v-for="col in displayLeafColumns"
|
|
672
|
+
:key="`fcol-${col._key}`"
|
|
673
|
+
:style="resolvedColStyle(col)"
|
|
674
|
+
/>
|
|
675
|
+
<col
|
|
676
|
+
v-if="virtualXEnabled && virtualXPad.right > 0"
|
|
677
|
+
:style="{ width: `${virtualXPad.right}px` }"
|
|
678
|
+
/>
|
|
679
|
+
</colgroup>
|
|
680
|
+
<tbody>
|
|
681
|
+
<tr v-for="(frow, fi) in footerRows" :key="`fr-${fi}`" class="qiu-table__row">
|
|
682
|
+
<td
|
|
683
|
+
v-if="resolvedShowExpand"
|
|
684
|
+
class="qiu-table__cell qiu-table__cell--footer qiu-table__cell--fixed-left"
|
|
685
|
+
:class="specialLeftLastClass('expand')"
|
|
686
|
+
:style="{ left: '0px' }"
|
|
687
|
+
>
|
|
688
|
+
<div class="qiu-table__cell-inner" />
|
|
689
|
+
</td>
|
|
690
|
+
<td
|
|
691
|
+
v-if="resolvedShowRadio"
|
|
692
|
+
class="qiu-table__cell qiu-table__cell--footer qiu-table__cell--fixed-left"
|
|
693
|
+
:class="specialLeftLastClass('radio')"
|
|
694
|
+
:style="{ left: `${fixedLeftOffset('radio')}px` }"
|
|
695
|
+
>
|
|
696
|
+
<div class="qiu-table__cell-inner" />
|
|
697
|
+
</td>
|
|
698
|
+
<td
|
|
699
|
+
v-if="resolvedShowSelection"
|
|
700
|
+
class="qiu-table__cell qiu-table__cell--footer qiu-table__cell--selection qiu-table__cell--fixed-left"
|
|
701
|
+
:class="specialLeftLastClass('selection')"
|
|
702
|
+
:style="{ left: `${fixedLeftOffset('selection')}px` }"
|
|
703
|
+
>
|
|
704
|
+
<div class="qiu-table__cell-inner" />
|
|
705
|
+
</td>
|
|
706
|
+
<td
|
|
707
|
+
v-if="resolvedShowIndex"
|
|
708
|
+
class="qiu-table__cell qiu-table__cell--footer qiu-table__cell--index qiu-table__cell--fixed-left"
|
|
709
|
+
:class="specialLeftLastClass('index')"
|
|
710
|
+
:style="{ left: `${fixedLeftOffset('index')}px` }"
|
|
711
|
+
>
|
|
712
|
+
<div class="qiu-table__cell-inner" />
|
|
713
|
+
</td>
|
|
714
|
+
<td
|
|
715
|
+
v-for="(text, ci) in frow"
|
|
716
|
+
:key="`fd-${fi}-${ci}`"
|
|
717
|
+
class="qiu-table__cell qiu-table__cell--footer"
|
|
718
|
+
:class="[
|
|
719
|
+
`is-align-${leafColumns[ci]?.footerAlign || leafColumns[ci]?.align || footerAlign || align || 'left'}`,
|
|
720
|
+
leafColumns[ci]?.footerClassName,
|
|
721
|
+
fixedCellClass(leafColumns[ci]),
|
|
722
|
+
]"
|
|
723
|
+
:style="bodyCellStyle(leafColumns[ci])"
|
|
724
|
+
>
|
|
725
|
+
<div class="qiu-table__cell-inner">{{ text }}</div>
|
|
726
|
+
</td>
|
|
727
|
+
</tr>
|
|
728
|
+
</tbody>
|
|
729
|
+
</table>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
<div v-if="pagerEnabled" class="qiu-table__pager">
|
|
734
|
+
<qiu-pagination
|
|
735
|
+
v-model:current-page="innerPage"
|
|
736
|
+
v-model:page-size="innerPageSize"
|
|
737
|
+
:total="pagerTotal"
|
|
738
|
+
:page-sizes="pagerConfig?.pageSizes || [10, 20, 50, 100]"
|
|
739
|
+
:background="pagerConfig?.background !== false"
|
|
740
|
+
:small="pagerConfig?.small"
|
|
741
|
+
layout="total, sizes, prev, pager, next, jumper"
|
|
742
|
+
@current-change="onPageChange"
|
|
743
|
+
@size-change="onSizeChange"
|
|
744
|
+
/>
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
<Teleport to="body">
|
|
748
|
+
<div v-if="openFilterKey" class="qiu-table__filter-mask" @mousedown="closeFilterPanel" />
|
|
749
|
+
<div
|
|
750
|
+
v-if="openFilterKey && filterPanelPos"
|
|
751
|
+
class="qiu-table__filter-panel"
|
|
752
|
+
:style="{ left: `${filterPanelPos.left}px`, top: `${filterPanelPos.top}px` }"
|
|
753
|
+
@mousedown.stop
|
|
754
|
+
>
|
|
755
|
+
<div class="qiu-table__filter-head">
|
|
756
|
+
<span class="qiu-table__filter-title">{{ translate('筛选') }}</span>
|
|
757
|
+
<div v-if="openFilterOptions.length" class="qiu-table__filter-checkall">
|
|
758
|
+
<qiu-checkbox
|
|
759
|
+
:model-value="isFilterAllChecked"
|
|
760
|
+
:indeterminate="isFilterIndeterminate"
|
|
761
|
+
@change="(v) => toggleFilterAll(!!v)"
|
|
762
|
+
>
|
|
763
|
+
{{ translate('全选') }}
|
|
764
|
+
</qiu-checkbox>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
<div class="qiu-table__filter-list">
|
|
768
|
+
<div
|
|
769
|
+
v-for="opt in openFilterOptions"
|
|
770
|
+
:key="String(opt.value)"
|
|
771
|
+
class="qiu-table__filter-item"
|
|
772
|
+
>
|
|
773
|
+
<qiu-checkbox
|
|
774
|
+
:model-value="isFilterOptionChecked(openFilterKey, opt.value)"
|
|
775
|
+
@change="(v) => toggleFilterOption(openFilterKey!, opt.value, !!v)"
|
|
776
|
+
>
|
|
777
|
+
{{ opt.text }}
|
|
778
|
+
</qiu-checkbox>
|
|
779
|
+
</div>
|
|
780
|
+
<div v-if="!openFilterOptions.length" class="qiu-table__filter-empty">
|
|
781
|
+
{{ translate('无筛选项') }}
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
<div class="qiu-table__filter-actions">
|
|
785
|
+
<button type="button" class="qiu-table__filter-action is-plain" @click="resetOpenFilter">
|
|
786
|
+
{{ translate('重置') }}
|
|
787
|
+
</button>
|
|
788
|
+
<button type="button" class="qiu-table__filter-action" @click="confirmFilter">
|
|
789
|
+
{{ translate('确定') }}
|
|
790
|
+
</button>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
|
|
794
|
+
<div v-if="ctxMenu.visible" class="qiu-table__filter-mask" @mousedown="closeCtxMenu" />
|
|
795
|
+
<div
|
|
796
|
+
v-if="ctxMenu.visible"
|
|
797
|
+
class="qiu-table__ctx-menu"
|
|
798
|
+
:style="{ left: `${ctxMenu.x}px`, top: `${ctxMenu.y}px` }"
|
|
799
|
+
@mousedown.stop
|
|
800
|
+
>
|
|
801
|
+
<template v-for="(group, gi) in ctxMenu.options" :key="gi">
|
|
802
|
+
<div v-if="gi > 0" class="qiu-table__ctx-divider" />
|
|
803
|
+
<button
|
|
804
|
+
v-for="item in group"
|
|
805
|
+
:key="item.code"
|
|
806
|
+
type="button"
|
|
807
|
+
class="qiu-table__ctx-item"
|
|
808
|
+
:disabled="item.disabled"
|
|
809
|
+
@click="onMenuItemClick(item)"
|
|
810
|
+
>
|
|
811
|
+
{{ item.name }}
|
|
812
|
+
</button>
|
|
813
|
+
</template>
|
|
814
|
+
</div>
|
|
815
|
+
</Teleport>
|
|
816
|
+
|
|
817
|
+
<slot />
|
|
818
|
+
</div>
|
|
819
|
+
</template>
|
|
820
|
+
|
|
821
|
+
<script lang="ts" setup>
|
|
822
|
+
/**
|
|
823
|
+
* QiuTable — 方案 A 自研,零 vxe 运行时
|
|
824
|
+
* Phase1–3: 编辑/多级头/表尾/分页 + 列宽/合并 + Grid级导入打印/虚拟滚动/菜单/校验
|
|
825
|
+
*/
|
|
826
|
+
import Sortable from 'sortablejs'
|
|
827
|
+
import { translate } from '../../src/i18n'
|
|
828
|
+
import QiuCheckbox from '../QiuCheckbox/index.vue'
|
|
829
|
+
import QiuInput from '../QiuInput/index.vue'
|
|
830
|
+
import QiuInputNumber from '../QiuInputNumber/index.vue'
|
|
831
|
+
import QiuLoadingSpinner from '../QiuLoading/spinner.vue'
|
|
832
|
+
import type { QiuLoadingEffect } from '../QiuLoading/types'
|
|
833
|
+
import QiuPagination from '../QiuPagination/index.vue'
|
|
834
|
+
import QiuSelect from '../QiuSelect/index.vue'
|
|
835
|
+
import QiuSwitch from '../QiuSwitch/index.vue'
|
|
836
|
+
import QiuTime from '../QiuTime/index.vue'
|
|
837
|
+
import QiuTooltip from '../QiuTooltip/index.vue'
|
|
838
|
+
import { QiuTableEditRenderer, QiuTableFormats, QiuTableRenderer } from './registry'
|
|
839
|
+
import type {
|
|
840
|
+
QiuTableClipboardConfig,
|
|
841
|
+
QiuTableColumnConfig,
|
|
842
|
+
QiuTableColumnConfigExtra,
|
|
843
|
+
QiuTableEditConfig,
|
|
844
|
+
QiuTableExportConfig,
|
|
845
|
+
QiuTableFilterConfig,
|
|
846
|
+
QiuTableFlatRow,
|
|
847
|
+
QiuTableFooterMethod,
|
|
848
|
+
QiuTableHistoryConfig,
|
|
849
|
+
QiuTableKeyboardConfig,
|
|
850
|
+
QiuTableMergeCell,
|
|
851
|
+
QiuTablePagerConfig,
|
|
852
|
+
QiuTableRegisterContext,
|
|
853
|
+
QiuTableRegisteredColumn,
|
|
854
|
+
QiuTableRowConfig,
|
|
855
|
+
QiuTableSortConfig,
|
|
856
|
+
QiuTableSpanMethod,
|
|
857
|
+
QiuTableToolbarConfig,
|
|
858
|
+
QiuTableTooltipConfig,
|
|
859
|
+
} from './types'
|
|
860
|
+
import {
|
|
861
|
+
QIU_TABLE_REGISTER_KEY,
|
|
862
|
+
resolveColumnProp,
|
|
863
|
+
resolveEditRender,
|
|
864
|
+
resolveRowCurrent,
|
|
865
|
+
resolveRowHover,
|
|
866
|
+
} from './types'
|
|
867
|
+
import QiuDropdown from '../QiuDropdown/index.vue'
|
|
868
|
+
import QiuDropdownMenu from '../QiuDropdown/QiuDropdownMenu.vue'
|
|
869
|
+
import QiuDropdownItem from '../QiuDropdown/QiuDropdownItem.vue'
|
|
870
|
+
import { qiuMsg } from '../QiuMsg'
|
|
871
|
+
import {
|
|
872
|
+
buildHeaderRows,
|
|
873
|
+
computeRenderWidths,
|
|
874
|
+
computeTableMinWidth,
|
|
875
|
+
flattenLeafColumns,
|
|
876
|
+
getColumnSizeHints,
|
|
877
|
+
isFirstFixedRight,
|
|
878
|
+
isLastFixedLeft,
|
|
879
|
+
leftOffsetForColumn,
|
|
880
|
+
makeColumnKey,
|
|
881
|
+
parseWidth,
|
|
882
|
+
rightOffsetForColumn,
|
|
883
|
+
type LeafColumn,
|
|
884
|
+
} from './columns'
|
|
885
|
+
import { useTableArea } from './useArea'
|
|
886
|
+
import { useTableEdit } from './useEdit'
|
|
887
|
+
import {
|
|
888
|
+
compareRows,
|
|
889
|
+
useExpandRows,
|
|
890
|
+
useKeepSource,
|
|
891
|
+
useRadioRow,
|
|
892
|
+
type SortItem,
|
|
893
|
+
} from './useExtras'
|
|
894
|
+
import { useTableHistory } from './useHistory'
|
|
895
|
+
import { applyMergeHeaderCells, buildMergeCoveredSet, resolveCellSpan } from './useMerge'
|
|
896
|
+
import { bindAutoClearEdit, useColumnResize } from './useResize'
|
|
897
|
+
import { useScrollSync } from './useScrollSync'
|
|
898
|
+
import { useVirtualY } from './useVirtual'
|
|
899
|
+
import type {
|
|
900
|
+
QiuTableCustomConfig,
|
|
901
|
+
QiuTableEditRules,
|
|
902
|
+
QiuTableMenuConfig,
|
|
903
|
+
QiuTableMenuItem,
|
|
904
|
+
QiuTableValidConfig,
|
|
905
|
+
} from './utils'
|
|
906
|
+
import { csvToObjects, validateCell } from './utils'
|
|
907
|
+
import {
|
|
908
|
+
buildSheetMatrix,
|
|
909
|
+
exportSheet,
|
|
910
|
+
parseImportFile,
|
|
911
|
+
type QiuTableExportFormat,
|
|
912
|
+
} from './export'
|
|
913
|
+
|
|
914
|
+
defineOptions({ name: 'QiuTable' })
|
|
915
|
+
|
|
916
|
+
type FilterOption = { text: string; value: any }
|
|
917
|
+
|
|
918
|
+
const props = withDefaults(
|
|
919
|
+
defineProps<{
|
|
920
|
+
data: any[]
|
|
921
|
+
columns?: QiuTableColumnConfig[]
|
|
922
|
+
editable?: boolean
|
|
923
|
+
editTrigger?: 'click' | 'dblclick' | 'manual' | 'always'
|
|
924
|
+
editConfig?: QiuTableEditConfig
|
|
925
|
+
keyboardConfig?: QiuTableKeyboardConfig
|
|
926
|
+
clipboardConfig?: QiuTableClipboardConfig
|
|
927
|
+
tooltipConfig?: QiuTableTooltipConfig
|
|
928
|
+
exportConfig?: QiuTableExportConfig
|
|
929
|
+
historyConfig?: QiuTableHistoryConfig | boolean
|
|
930
|
+
sortConfig?: QiuTableSortConfig
|
|
931
|
+
filterConfig?: QiuTableFilterConfig
|
|
932
|
+
rowConfig?: QiuTableRowConfig
|
|
933
|
+
columnConfig?: QiuTableColumnConfigExtra
|
|
934
|
+
spanMethod?: QiuTableSpanMethod
|
|
935
|
+
mergeCells?: QiuTableMergeCell[]
|
|
936
|
+
/** 合并表头单元格(扁平表头额外合并;多级表头优先用 children) */
|
|
937
|
+
mergeHeaderCells?: QiuTableMergeCell[]
|
|
938
|
+
border?: boolean | 'full' | 'outer' | 'inner' | 'none' | 'default'
|
|
939
|
+
stripe?: boolean
|
|
940
|
+
round?: boolean
|
|
941
|
+
height?: string | number
|
|
942
|
+
maxHeight?: string | number
|
|
943
|
+
rowKey?: string | ((row: any) => string)
|
|
944
|
+
showIndex?: boolean
|
|
945
|
+
showSelection?: boolean
|
|
946
|
+
showRadio?: boolean
|
|
947
|
+
showExpand?: boolean
|
|
948
|
+
radioConfig?: import('./types').QiuTableRadioConfig
|
|
949
|
+
expandConfig?: import('./types').QiuTableExpandConfig
|
|
950
|
+
columnDragConfig?: import('./types').QiuTableColumnDragConfig
|
|
951
|
+
keepSource?: boolean
|
|
952
|
+
virtualXConfig?: boolean | { enabled?: boolean; gt?: number }
|
|
953
|
+
tree?: boolean
|
|
954
|
+
treeProps?: { children?: string; hasChildren?: string }
|
|
955
|
+
virtualY?: boolean
|
|
956
|
+
showToolbar?: boolean
|
|
957
|
+
showRefresh?: boolean
|
|
958
|
+
showExport?: boolean
|
|
959
|
+
showImport?: boolean
|
|
960
|
+
showPrint?: boolean
|
|
961
|
+
showCopy?: boolean
|
|
962
|
+
showFullscreen?: boolean
|
|
963
|
+
toolbarConfig?: QiuTableToolbarConfig | boolean
|
|
964
|
+
showFooter?: boolean
|
|
965
|
+
footerMethod?: QiuTableFooterMethod
|
|
966
|
+
footerData?: string[][]
|
|
967
|
+
pagerConfig?: QiuTablePagerConfig | boolean
|
|
968
|
+
gridOptions?: Record<string, any>
|
|
969
|
+
dragSort?: boolean
|
|
970
|
+
defaultExpandAll?: boolean
|
|
971
|
+
size?: 'large' | 'default' | 'small'
|
|
972
|
+
emptyText?: string
|
|
973
|
+
loading?: boolean
|
|
974
|
+
/** loading 文案 */
|
|
975
|
+
loadingText?: string
|
|
976
|
+
/** loading 动画,见 QIU_LOADING_EFFECTS(20 种) */
|
|
977
|
+
loadingEffect?: QiuLoadingEffect
|
|
978
|
+
/** loading 遮罩更透明 */
|
|
979
|
+
loadingTransparent?: boolean
|
|
980
|
+
/** 是否将剩余宽度分配给仅 minWidth 的弹性列(对齐 vxe fit) */
|
|
981
|
+
fit?: boolean
|
|
982
|
+
/** 表格级默认对齐 */
|
|
983
|
+
align?: 'left' | 'center' | 'right'
|
|
984
|
+
headerAlign?: 'left' | 'center' | 'right'
|
|
985
|
+
footerAlign?: 'left' | 'center' | 'right'
|
|
986
|
+
minHeight?: string | number
|
|
987
|
+
rowClassName?: string | ((params: { row: any; rowIndex: number }) => string)
|
|
988
|
+
cellClassName?:
|
|
989
|
+
| string
|
|
990
|
+
| ((params: { row: any; column: QiuTableColumnConfig; rowIndex: number; columnIndex: number }) => string)
|
|
991
|
+
customConfig?: QiuTableCustomConfig
|
|
992
|
+
menuConfig?: QiuTableMenuConfig
|
|
993
|
+
editRules?: QiuTableEditRules
|
|
994
|
+
validConfig?: QiuTableValidConfig
|
|
995
|
+
virtualYConfig?: boolean | { enabled?: boolean; gt?: number; oSize?: number; rowHeight?: number }
|
|
996
|
+
}>(),
|
|
997
|
+
{
|
|
998
|
+
columns: () => [],
|
|
999
|
+
editable: false,
|
|
1000
|
+
editTrigger: 'click',
|
|
1001
|
+
border: true,
|
|
1002
|
+
stripe: false,
|
|
1003
|
+
round: false,
|
|
1004
|
+
showIndex: false,
|
|
1005
|
+
showSelection: false,
|
|
1006
|
+
showRadio: false,
|
|
1007
|
+
showExpand: false,
|
|
1008
|
+
keepSource: false,
|
|
1009
|
+
fit: true,
|
|
1010
|
+
tree: false,
|
|
1011
|
+
treeProps: () => ({ children: 'children', hasChildren: 'hasChildren' }),
|
|
1012
|
+
virtualY: false,
|
|
1013
|
+
showToolbar: false,
|
|
1014
|
+
showRefresh: true,
|
|
1015
|
+
showExport: false,
|
|
1016
|
+
showImport: false,
|
|
1017
|
+
showPrint: false,
|
|
1018
|
+
showCopy: false,
|
|
1019
|
+
showFullscreen: true,
|
|
1020
|
+
showFooter: false,
|
|
1021
|
+
dragSort: false,
|
|
1022
|
+
defaultExpandAll: true,
|
|
1023
|
+
loading: false,
|
|
1024
|
+
loadingText: '',
|
|
1025
|
+
loadingEffect: 'spinner',
|
|
1026
|
+
loadingTransparent: false,
|
|
1027
|
+
mergeCells: () => [],
|
|
1028
|
+
mergeHeaderCells: () => [],
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
const emit = defineEmits<{
|
|
1033
|
+
edit: [payload: { row: any; column: QiuTableColumnConfig; value: any }]
|
|
1034
|
+
'cell-edit-start': [payload: { row: any; column: QiuTableColumnConfig }]
|
|
1035
|
+
'cell-edit-end': [payload: { row: any; column: QiuTableColumnConfig; value: any }]
|
|
1036
|
+
'row-dragend': [payload: any]
|
|
1037
|
+
'checkbox-change': [payload: any]
|
|
1038
|
+
'checkbox-all': [payload: any]
|
|
1039
|
+
'selection-change': [payload: any[]]
|
|
1040
|
+
'filter-change': [payload: Record<string, any[]>]
|
|
1041
|
+
'sort-change': [payload: { prop: string; order: 'asc' | 'desc' | null }]
|
|
1042
|
+
'page-change': [payload: { currentPage: number; pageSize: number }]
|
|
1043
|
+
'current-change': [payload: { row: any | null }]
|
|
1044
|
+
'resizable-change': [payload: { column: QiuTableColumnConfig; width: number }]
|
|
1045
|
+
'menu-click': [payload: { menu: QiuTableMenuItem; row?: any; column?: QiuTableColumnConfig }]
|
|
1046
|
+
'import-data': [payload: { rows: any[] }]
|
|
1047
|
+
'radio-change': [payload: { row: any | null }]
|
|
1048
|
+
'toggle-row-expand': [payload: { row: any; expanded: boolean }]
|
|
1049
|
+
'column-dragend': [payload: { oldIndex: number; newIndex: number; columnKeys: string[] }]
|
|
1050
|
+
refresh: []
|
|
1051
|
+
}>()
|
|
1052
|
+
|
|
1053
|
+
const rootRef = ref<HTMLElement | null>(null)
|
|
1054
|
+
const tableRef = ref<HTMLTableElement | null>(null)
|
|
1055
|
+
const tbodyRef = ref<HTMLTableSectionElement | null>(null)
|
|
1056
|
+
const headerWrapRef = ref<HTMLDivElement | null>(null)
|
|
1057
|
+
const bodyWrapRef = ref<HTMLDivElement | null>(null)
|
|
1058
|
+
const footerWrapRef = ref<HTMLDivElement | null>(null)
|
|
1059
|
+
const selectionRows = ref<any[]>([])
|
|
1060
|
+
const registeredColumns = ref<QiuTableRegisteredColumn[]>([])
|
|
1061
|
+
const expandedKeys = ref<Set<string>>(new Set())
|
|
1062
|
+
const sortState = ref<{ prop: string; order: 'asc' | 'desc' | null }>({ prop: '', order: null })
|
|
1063
|
+
const sortList = ref<SortItem[]>([])
|
|
1064
|
+
const columnOrderKeys = ref<string[]>([])
|
|
1065
|
+
const scrollLeftX = ref(0)
|
|
1066
|
+
const hasScrollX = ref(false)
|
|
1067
|
+
const updateHasScrollX = () => {
|
|
1068
|
+
const el = bodyWrapRef.value
|
|
1069
|
+
hasScrollX.value = !!el && el.scrollWidth > el.clientWidth
|
|
1070
|
+
}
|
|
1071
|
+
const radioGroupName = `qiu-table-radio-${Math.random().toString(36).slice(2, 8)}`
|
|
1072
|
+
const filterValues = reactive<Record<string, any[]>>({})
|
|
1073
|
+
const filterDraft = reactive<Record<string, any[]>>({})
|
|
1074
|
+
const openFilterKey = ref<string | null>(null)
|
|
1075
|
+
const filterPanelPos = ref<{ left: number; top: number } | null>(null)
|
|
1076
|
+
const isFullscreen = ref(false)
|
|
1077
|
+
const innerPage = ref(1)
|
|
1078
|
+
const innerPageSize = ref(10)
|
|
1079
|
+
const currentRowId = ref<string | null>(null)
|
|
1080
|
+
const customPanelOpen = ref(false)
|
|
1081
|
+
const exportDialogVisible = ref(false)
|
|
1082
|
+
const exportFormat = ref<QiuTableExportFormat>('xlsx')
|
|
1083
|
+
const exportFieldKeys = ref<string[]>([])
|
|
1084
|
+
const exportMode = ref<QiuTableExportConfig['mode']>('all')
|
|
1085
|
+
const exportStartPage = ref(1)
|
|
1086
|
+
const exportEndPage = ref(1)
|
|
1087
|
+
const exportFormats = [
|
|
1088
|
+
{ value: 'xlsx' as const, label: 'Excel (.xlsx)' },
|
|
1089
|
+
{ value: 'xls' as const, label: 'Excel (.xls)' },
|
|
1090
|
+
{ value: 'csv' as const, label: 'CSV (.csv)' },
|
|
1091
|
+
]
|
|
1092
|
+
const exportModeOptions = [
|
|
1093
|
+
{ value: 'all' as const, label: translate('全部数据') },
|
|
1094
|
+
{ value: 'current' as const, label: translate('当前页') },
|
|
1095
|
+
{ value: 'selected' as const, label: translate('选中行') },
|
|
1096
|
+
{ value: 'range' as const, label: translate('指定页码范围') },
|
|
1097
|
+
]
|
|
1098
|
+
const columnVisibleMap = reactive<Record<string, boolean>>({})
|
|
1099
|
+
const importInputRef = ref<HTMLInputElement | null>(null)
|
|
1100
|
+
const validErrors = reactive<Record<string, string>>({})
|
|
1101
|
+
const ctxMenu = reactive<{
|
|
1102
|
+
visible: boolean
|
|
1103
|
+
x: number
|
|
1104
|
+
y: number
|
|
1105
|
+
options: QiuTableMenuItem[][]
|
|
1106
|
+
row?: any
|
|
1107
|
+
column?: QiuTableColumnConfig
|
|
1108
|
+
}>({ visible: false, x: 0, y: 0, options: [] })
|
|
1109
|
+
|
|
1110
|
+
let sortableInst: Sortable | null = null
|
|
1111
|
+
let expandInitialized = false
|
|
1112
|
+
|
|
1113
|
+
const layoutTick = ref(0)
|
|
1114
|
+
|
|
1115
|
+
const { scrollbarSize, measureScrollbar, syncFromBody, scheduleLayout } = useScrollSync({
|
|
1116
|
+
headerWrapRef,
|
|
1117
|
+
bodyWrapRef,
|
|
1118
|
+
footerWrapRef,
|
|
1119
|
+
onLayoutChange: () => {
|
|
1120
|
+
layoutTick.value++
|
|
1121
|
+
updateHasScrollX()
|
|
1122
|
+
},
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
const { columnWidths, getWidth, onResizeStart, setColumnWidth } = useColumnResize()
|
|
1126
|
+
|
|
1127
|
+
const columnWidthDefaults = computed(() => ({
|
|
1128
|
+
minWidth: props.columnConfig?.minWidth ?? 80,
|
|
1129
|
+
maxWidth: props.columnConfig?.maxWidth ?? Number.POSITIVE_INFINITY,
|
|
1130
|
+
}))
|
|
1131
|
+
|
|
1132
|
+
/** 无稳定主键时用对象身份兜底,避免多行 rowId 同为 '' 导致整列同时进入编辑 */
|
|
1133
|
+
let rowUidSeq = 0
|
|
1134
|
+
const rowUidMap = new WeakMap<object, string>()
|
|
1135
|
+
|
|
1136
|
+
const getStableObjectId = (row: object) => {
|
|
1137
|
+
let uid = rowUidMap.get(row)
|
|
1138
|
+
if (!uid) {
|
|
1139
|
+
uid = `__qiu_row_${++rowUidSeq}`
|
|
1140
|
+
rowUidMap.set(row, uid)
|
|
1141
|
+
}
|
|
1142
|
+
return uid
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const getRowId = (row: any): string => {
|
|
1146
|
+
if (row == null) return ''
|
|
1147
|
+
if (typeof props.rowKey === 'function') {
|
|
1148
|
+
const v = props.rowKey(row)
|
|
1149
|
+
if (v != null && String(v) !== '') return String(v)
|
|
1150
|
+
} else {
|
|
1151
|
+
const key =
|
|
1152
|
+
props.rowConfig?.keyField ||
|
|
1153
|
+
(typeof props.rowKey === 'string' && props.rowKey ? props.rowKey : '') ||
|
|
1154
|
+
'id'
|
|
1155
|
+
const v = row?.[key]
|
|
1156
|
+
if (v != null && String(v) !== '') return String(v)
|
|
1157
|
+
}
|
|
1158
|
+
if (typeof row === 'object') return getStableObjectId(row)
|
|
1159
|
+
return String(row)
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const {
|
|
1163
|
+
isRowExpand,
|
|
1164
|
+
toggleRowExpand: toggleRowExpandRaw,
|
|
1165
|
+
setRowExpand,
|
|
1166
|
+
clearRowExpand,
|
|
1167
|
+
} = useExpandRows({ getRowId })
|
|
1168
|
+
|
|
1169
|
+
const toggleRowExpand = (row: any) => {
|
|
1170
|
+
if (props.expandConfig?.accordion) {
|
|
1171
|
+
clearRowExpand()
|
|
1172
|
+
}
|
|
1173
|
+
const before = isRowExpand(row)
|
|
1174
|
+
toggleRowExpandRaw(row)
|
|
1175
|
+
emit('toggle-row-expand', { row, expanded: !before })
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const { isRadioChecked, setRadioRow, clearRadioRow, radioRowId } = useRadioRow({ getRowId })
|
|
1179
|
+
|
|
1180
|
+
const keepSourceEnabled = computed(() => !!props.keepSource)
|
|
1181
|
+
const {
|
|
1182
|
+
snapshot: snapshotSource,
|
|
1183
|
+
isUpdateByRow,
|
|
1184
|
+
getUpdateRecords,
|
|
1185
|
+
getInsertRecords,
|
|
1186
|
+
reloadRow,
|
|
1187
|
+
} = useKeepSource({
|
|
1188
|
+
enabled: () => keepSourceEnabled.value,
|
|
1189
|
+
getRowId,
|
|
1190
|
+
data: () => props.data,
|
|
1191
|
+
})
|
|
1192
|
+
|
|
1193
|
+
const onRadioChange = (row: any) => {
|
|
1194
|
+
setRadioRow(row)
|
|
1195
|
+
emit('radio-change', { row })
|
|
1196
|
+
if (props.radioConfig?.highlight !== false) {
|
|
1197
|
+
currentRowId.value = getRowId(row)
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
const {
|
|
1202
|
+
editing,
|
|
1203
|
+
getColEditor,
|
|
1204
|
+
getEditRender,
|
|
1205
|
+
getProp,
|
|
1206
|
+
canEditCol,
|
|
1207
|
+
hasAlwaysEditor,
|
|
1208
|
+
isEditing,
|
|
1209
|
+
isInputFill,
|
|
1210
|
+
startEdit,
|
|
1211
|
+
clearEdit,
|
|
1212
|
+
commitEdit: baseCommitEdit,
|
|
1213
|
+
inEditGuard,
|
|
1214
|
+
getTrigger,
|
|
1215
|
+
getShowIcon,
|
|
1216
|
+
getAutoClear,
|
|
1217
|
+
} = useTableEdit({
|
|
1218
|
+
editable: () => props.editable,
|
|
1219
|
+
editTrigger: () => props.editTrigger,
|
|
1220
|
+
editConfig: () => props.editConfig,
|
|
1221
|
+
getRowId,
|
|
1222
|
+
emit: (event, payload) => emit(event as any, payload),
|
|
1223
|
+
})
|
|
1224
|
+
|
|
1225
|
+
const historyEnabled = () => {
|
|
1226
|
+
const h = props.historyConfig
|
|
1227
|
+
if (h === true) return true
|
|
1228
|
+
if (h && typeof h === 'object') return h.enabled !== false
|
|
1229
|
+
return false
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const {
|
|
1233
|
+
push: pushHistory,
|
|
1234
|
+
undo: undoEdit,
|
|
1235
|
+
redo: redoEdit,
|
|
1236
|
+
clear: clearHistory,
|
|
1237
|
+
canUndo,
|
|
1238
|
+
canRedo,
|
|
1239
|
+
} = useTableHistory({
|
|
1240
|
+
enabled: historyEnabled,
|
|
1241
|
+
size: () =>
|
|
1242
|
+
typeof props.historyConfig === 'object' && props.historyConfig?.size
|
|
1243
|
+
? props.historyConfig.size
|
|
1244
|
+
: 50,
|
|
1245
|
+
findRow: (id) => findRowById(props.data, id),
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
const {
|
|
1249
|
+
setActive,
|
|
1250
|
+
clearArea,
|
|
1251
|
+
isCellInArea,
|
|
1252
|
+
isActiveCell,
|
|
1253
|
+
moveActive,
|
|
1254
|
+
buildTsv,
|
|
1255
|
+
pasteTsv,
|
|
1256
|
+
clearAreaValues,
|
|
1257
|
+
activeCell,
|
|
1258
|
+
areaBounds,
|
|
1259
|
+
} = useTableArea({
|
|
1260
|
+
getRows: () => pagedRows.value.map((f) => f.row),
|
|
1261
|
+
getColumns: () => leafColumns.value,
|
|
1262
|
+
getRowId,
|
|
1263
|
+
})
|
|
1264
|
+
|
|
1265
|
+
const kb = () => props.keyboardConfig || {}
|
|
1266
|
+
const clip = () => props.clipboardConfig || {}
|
|
1267
|
+
|
|
1268
|
+
const shouldEllipsis = (col: QiuTableColumnConfig) => {
|
|
1269
|
+
if (col.showOverflowTooltip === false) return false
|
|
1270
|
+
if (props.tooltipConfig?.showAll === false) return false
|
|
1271
|
+
return true
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const resolveTooltipContent = (row: any, col: QiuTableColumnConfig) => {
|
|
1275
|
+
const cellValue = getProp(col) != null ? row?.[getProp(col)!] : undefined
|
|
1276
|
+
if (props.tooltipConfig?.contentMethod) {
|
|
1277
|
+
const r = props.tooltipConfig.contentMethod({ row, column: col, cellValue })
|
|
1278
|
+
if (r === null) return ''
|
|
1279
|
+
if (r !== undefined) return r
|
|
1280
|
+
}
|
|
1281
|
+
return formatCell(row, col)
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const commitEdit = async (row: any, col: QiuTableColumnConfig, value: any) => {
|
|
1285
|
+
if (inEditGuard()) return
|
|
1286
|
+
const prop = getProp(col)
|
|
1287
|
+
if (prop && props.editRules?.[prop]) {
|
|
1288
|
+
const err = await validateCell({
|
|
1289
|
+
rules: props.editRules[prop],
|
|
1290
|
+
cellValue: value,
|
|
1291
|
+
row,
|
|
1292
|
+
column: col,
|
|
1293
|
+
})
|
|
1294
|
+
const key = `${getRowId(row)}:${prop}`
|
|
1295
|
+
if (err) {
|
|
1296
|
+
validErrors[key] = err
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
delete validErrors[key]
|
|
1300
|
+
}
|
|
1301
|
+
if (prop != null) {
|
|
1302
|
+
const oldValue = row[prop]
|
|
1303
|
+
if (oldValue !== value) {
|
|
1304
|
+
pushHistory({
|
|
1305
|
+
rowId: getRowId(row),
|
|
1306
|
+
prop,
|
|
1307
|
+
oldValue,
|
|
1308
|
+
newValue: value,
|
|
1309
|
+
})
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
baseCommitEdit(row, col, value)
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const cellError = (row: any, col: QiuTableColumnConfig) => {
|
|
1316
|
+
const prop = getProp(col)
|
|
1317
|
+
if (!prop) return ''
|
|
1318
|
+
return validErrors[`${getRowId(row)}:${prop}`] || ''
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
bindAutoClearEdit({
|
|
1322
|
+
rootRef,
|
|
1323
|
+
enabled: () => props.editable && getAutoClear(),
|
|
1324
|
+
clearEdit,
|
|
1325
|
+
})
|
|
1326
|
+
|
|
1327
|
+
watch(
|
|
1328
|
+
() => props.sortConfig?.defaultSort,
|
|
1329
|
+
(ds) => {
|
|
1330
|
+
if (!ds) return
|
|
1331
|
+
const list = Array.isArray(ds) ? ds : [ds]
|
|
1332
|
+
const mapped = list
|
|
1333
|
+
.map((d) => ({
|
|
1334
|
+
prop: d.prop || d.field || '',
|
|
1335
|
+
order: (d.order || 'asc') as 'asc' | 'desc',
|
|
1336
|
+
}))
|
|
1337
|
+
.filter((d) => d.prop)
|
|
1338
|
+
sortList.value = mapped
|
|
1339
|
+
if (mapped[0]) sortState.value = { prop: mapped[0].prop, order: mapped[0].order }
|
|
1340
|
+
},
|
|
1341
|
+
{ immediate: true }
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
watch(
|
|
1345
|
+
() => props.data,
|
|
1346
|
+
() => snapshotSource(),
|
|
1347
|
+
{ deep: true, immediate: true }
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
watch(
|
|
1351
|
+
() => props.expandConfig?.expandAll,
|
|
1352
|
+
(v) => {
|
|
1353
|
+
if (v && resolvedShowExpand.value) setRowExpand(props.data || [], true)
|
|
1354
|
+
},
|
|
1355
|
+
{ immediate: true }
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
watch(
|
|
1359
|
+
() => props.radioConfig?.checkRowKey,
|
|
1360
|
+
(key) => {
|
|
1361
|
+
if (key == null) return
|
|
1362
|
+
const row = (props.data || []).find((r) => getRowId(r) === String(key))
|
|
1363
|
+
if (row) setRadioRow(row)
|
|
1364
|
+
},
|
|
1365
|
+
{ immediate: true }
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
const normalizeSize = (v: string | number) => {
|
|
1369
|
+
if (typeof v === 'number' && Number.isFinite(v)) return `${v}px`
|
|
1370
|
+
const s = String(v ?? '').trim()
|
|
1371
|
+
if (!s) return s
|
|
1372
|
+
// height="300" 这类无单位数字字符串需补 px,否则高度不生效、虚拟滚动失效
|
|
1373
|
+
if (/^\d+(\.\d+)?$/.test(s)) return `${s}px`
|
|
1374
|
+
return s
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const resolvedTreeProps = computed(() => ({
|
|
1378
|
+
children: props.treeProps?.children || 'children',
|
|
1379
|
+
hasChildren: props.treeProps?.hasChildren || 'hasChildren',
|
|
1380
|
+
}))
|
|
1381
|
+
|
|
1382
|
+
const configColumns = computed(() => {
|
|
1383
|
+
const walk = (cols: QiuTableColumnConfig[]): QiuTableColumnConfig[] =>
|
|
1384
|
+
cols.map((col) => ({
|
|
1385
|
+
...col,
|
|
1386
|
+
label: col.label || col.title,
|
|
1387
|
+
align: col.align || props.align || 'left',
|
|
1388
|
+
headerAlign: col.headerAlign || props.headerAlign || col.align || props.align || 'left',
|
|
1389
|
+
footerAlign: col.footerAlign || props.footerAlign || col.align || props.align || 'left',
|
|
1390
|
+
children: col.children?.length ? walk(col.children) : undefined,
|
|
1391
|
+
}))
|
|
1392
|
+
return walk(props.columns || [])
|
|
1393
|
+
})
|
|
1394
|
+
|
|
1395
|
+
const slotColumnsNormalized = computed((): LeafColumn[] =>
|
|
1396
|
+
registeredColumns.value.map((reg, i) => {
|
|
1397
|
+
const p = reg.props
|
|
1398
|
+
const fixed = p.fixed === true ? ('left' as const) : p.fixed || undefined
|
|
1399
|
+
return {
|
|
1400
|
+
label: p.label || p.title || '',
|
|
1401
|
+
title: p.title,
|
|
1402
|
+
prop: p.prop,
|
|
1403
|
+
field: p.field,
|
|
1404
|
+
width: p.width,
|
|
1405
|
+
minWidth: p.minWidth,
|
|
1406
|
+
maxWidth: p.maxWidth,
|
|
1407
|
+
fixed,
|
|
1408
|
+
sortable: p.sortable,
|
|
1409
|
+
filterable: p.filterable,
|
|
1410
|
+
align: p.align || props.align || 'left',
|
|
1411
|
+
headerAlign: p.headerAlign || props.headerAlign,
|
|
1412
|
+
footerAlign: p.footerAlign || props.footerAlign,
|
|
1413
|
+
editor: p.editor,
|
|
1414
|
+
editorProps: p.editorProps,
|
|
1415
|
+
options: p.options,
|
|
1416
|
+
formatter: p.formatter,
|
|
1417
|
+
editable: p.editable,
|
|
1418
|
+
filters: p.filters,
|
|
1419
|
+
filterMethod: p.filterMethod,
|
|
1420
|
+
type: p.type,
|
|
1421
|
+
treeNode: p.treeNode,
|
|
1422
|
+
dragSort: p.dragSort,
|
|
1423
|
+
showOverflowTooltip: p.showOverflowTooltip,
|
|
1424
|
+
className: p.className,
|
|
1425
|
+
headerClassName: p.headerClassName || p.labelClassName,
|
|
1426
|
+
children: p.children,
|
|
1427
|
+
aggFunc: p.aggFunc,
|
|
1428
|
+
_key: p.columnKey || p.prop || p.field || `slot-col-${i}`,
|
|
1429
|
+
_defaultSlot: reg.slots.default,
|
|
1430
|
+
_headerSlot: reg.slots.header,
|
|
1431
|
+
}
|
|
1432
|
+
})
|
|
1433
|
+
)
|
|
1434
|
+
|
|
1435
|
+
const mergedSourceColumns = computed((): QiuTableColumnConfig[] => {
|
|
1436
|
+
const fromSlots = slotColumnsNormalized.value.filter(
|
|
1437
|
+
(c) => c.type !== 'selection' && c.type !== 'index' && c.type !== 'seq'
|
|
1438
|
+
)
|
|
1439
|
+
return [...configColumns.value, ...fromSlots]
|
|
1440
|
+
})
|
|
1441
|
+
|
|
1442
|
+
const allLeafColumns = computed((): LeafColumn[] => {
|
|
1443
|
+
const leaves = flattenLeafColumns(mergedSourceColumns.value)
|
|
1444
|
+
return leaves.filter(
|
|
1445
|
+
(c) =>
|
|
1446
|
+
c.type !== 'selection' &&
|
|
1447
|
+
c.type !== 'checkbox' &&
|
|
1448
|
+
c.type !== 'index' &&
|
|
1449
|
+
c.type !== 'seq' &&
|
|
1450
|
+
c.type !== 'radio' &&
|
|
1451
|
+
c.type !== 'expand'
|
|
1452
|
+
)
|
|
1453
|
+
})
|
|
1454
|
+
|
|
1455
|
+
const leafColumns = computed((): LeafColumn[] => {
|
|
1456
|
+
const order = columnOrderKeys.value
|
|
1457
|
+
let leaves = allLeafColumns.value.filter((c) => {
|
|
1458
|
+
if (c.visible === false) return false
|
|
1459
|
+
if (columnVisibleMap[c._key] === false) return false
|
|
1460
|
+
return true
|
|
1461
|
+
})
|
|
1462
|
+
if (order.length) {
|
|
1463
|
+
const map = new Map(leaves.map((c) => [c._key, c]))
|
|
1464
|
+
const ordered: LeafColumn[] = []
|
|
1465
|
+
order.forEach((k) => {
|
|
1466
|
+
const col = map.get(k)
|
|
1467
|
+
if (col) {
|
|
1468
|
+
ordered.push(col)
|
|
1469
|
+
map.delete(k)
|
|
1470
|
+
}
|
|
1471
|
+
})
|
|
1472
|
+
map.forEach((c) => ordered.push(c))
|
|
1473
|
+
leaves = ordered
|
|
1474
|
+
}
|
|
1475
|
+
return leaves
|
|
1476
|
+
})
|
|
1477
|
+
|
|
1478
|
+
watch(
|
|
1479
|
+
allLeafColumns,
|
|
1480
|
+
(cols) => {
|
|
1481
|
+
const keys = cols.map((c) => c._key)
|
|
1482
|
+
if (!columnOrderKeys.value.length) {
|
|
1483
|
+
columnOrderKeys.value = keys
|
|
1484
|
+
return
|
|
1485
|
+
}
|
|
1486
|
+
const set = new Set(columnOrderKeys.value)
|
|
1487
|
+
keys.forEach((k) => {
|
|
1488
|
+
if (!set.has(k)) columnOrderKeys.value.push(k)
|
|
1489
|
+
})
|
|
1490
|
+
columnOrderKeys.value = columnOrderKeys.value.filter((k) => keys.includes(k))
|
|
1491
|
+
},
|
|
1492
|
+
{ immediate: true }
|
|
1493
|
+
)
|
|
1494
|
+
|
|
1495
|
+
const customizableColumns = computed(() =>
|
|
1496
|
+
allLeafColumns.value.filter((c) => {
|
|
1497
|
+
if (props.customConfig?.checkMethod) return props.customConfig.checkMethod(c)
|
|
1498
|
+
return true
|
|
1499
|
+
})
|
|
1500
|
+
)
|
|
1501
|
+
|
|
1502
|
+
const customEnabled = computed(() => props.customConfig?.enabled === true)
|
|
1503
|
+
|
|
1504
|
+
const resolvedToolbarConfig = computed((): Required<QiuTableToolbarConfig> => {
|
|
1505
|
+
if (props.toolbarConfig === false) {
|
|
1506
|
+
return {
|
|
1507
|
+
enabled: false,
|
|
1508
|
+
refresh: false,
|
|
1509
|
+
export: false,
|
|
1510
|
+
import: false,
|
|
1511
|
+
print: false,
|
|
1512
|
+
custom: false,
|
|
1513
|
+
zoom: false,
|
|
1514
|
+
copy: false,
|
|
1515
|
+
toolsPlacement: 'right',
|
|
1516
|
+
customTools: [],
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const cfg = (props.toolbarConfig && typeof props.toolbarConfig === 'object'
|
|
1520
|
+
? props.toolbarConfig
|
|
1521
|
+
: {}) as QiuTableToolbarConfig
|
|
1522
|
+
return {
|
|
1523
|
+
enabled: cfg.enabled !== false,
|
|
1524
|
+
refresh: cfg.refresh !== false,
|
|
1525
|
+
export: cfg.export !== false,
|
|
1526
|
+
import: cfg.import !== false,
|
|
1527
|
+
print: cfg.print !== false,
|
|
1528
|
+
custom: cfg.custom !== false,
|
|
1529
|
+
zoom: cfg.zoom !== false,
|
|
1530
|
+
copy: cfg.copy === true,
|
|
1531
|
+
toolsPlacement: cfg.toolsPlacement || 'right',
|
|
1532
|
+
customTools: cfg.customTools || [],
|
|
1533
|
+
}
|
|
1534
|
+
})
|
|
1535
|
+
|
|
1536
|
+
const slots = useSlots()
|
|
1537
|
+
const refreshEnabled = computed(() => resolvedToolbarConfig.value.refresh && props.showRefresh !== false)
|
|
1538
|
+
const exportEnabled = computed(() => resolvedToolbarConfig.value.export && props.showExport !== false)
|
|
1539
|
+
const importEnabled = computed(() => resolvedToolbarConfig.value.import && props.showImport === true)
|
|
1540
|
+
const printEnabled = computed(() => resolvedToolbarConfig.value.print && props.showPrint === true)
|
|
1541
|
+
const zoomEnabled = computed(() => resolvedToolbarConfig.value.zoom && props.showFullscreen !== false)
|
|
1542
|
+
const copyEnabled = computed(
|
|
1543
|
+
() => resolvedToolbarConfig.value.copy || (props.showCopy === true && props.toolbarConfig !== false)
|
|
1544
|
+
)
|
|
1545
|
+
const hasToolbarCustom = computed(
|
|
1546
|
+
() =>
|
|
1547
|
+
!!slots.toolbar ||
|
|
1548
|
+
!!slots['toolbar-left'] ||
|
|
1549
|
+
!!slots['toolbar-center'] ||
|
|
1550
|
+
!!slots['toolbar-right'] ||
|
|
1551
|
+
resolvedToolbarConfig.value.customTools.length > 0
|
|
1552
|
+
)
|
|
1553
|
+
const hasAnyToolbarContent = computed(
|
|
1554
|
+
() =>
|
|
1555
|
+
refreshEnabled.value ||
|
|
1556
|
+
exportEnabled.value ||
|
|
1557
|
+
importEnabled.value ||
|
|
1558
|
+
printEnabled.value ||
|
|
1559
|
+
copyEnabled.value ||
|
|
1560
|
+
zoomEnabled.value ||
|
|
1561
|
+
customEnabled.value ||
|
|
1562
|
+
hasToolbarCustom.value
|
|
1563
|
+
)
|
|
1564
|
+
const showToolbarArea = computed(
|
|
1565
|
+
() => props.showToolbar !== false && (props.showToolbar === true || hasAnyToolbarContent.value)
|
|
1566
|
+
)
|
|
1567
|
+
const toolsPlacement = computed(() => resolvedToolbarConfig.value.toolsPlacement)
|
|
1568
|
+
|
|
1569
|
+
const customToolsByPosition = (position: 'left' | 'center' | 'right') =>
|
|
1570
|
+
resolvedToolbarConfig.value.customTools
|
|
1571
|
+
.filter((t) => (t.position || 'right') === position)
|
|
1572
|
+
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
1573
|
+
|
|
1574
|
+
const toggleColumnVisible = (key: string, visible: boolean) => {
|
|
1575
|
+
columnVisibleMap[key] = visible
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const columnDragEnabled = computed(() => props.columnDragConfig?.enabled === true)
|
|
1579
|
+
|
|
1580
|
+
let colSortableInst: Sortable | null = null
|
|
1581
|
+
const initColumnSortable = () => {
|
|
1582
|
+
colSortableInst?.destroy()
|
|
1583
|
+
colSortableInst = null
|
|
1584
|
+
if (!columnDragEnabled.value) return
|
|
1585
|
+
nextTick(() => {
|
|
1586
|
+
const row = headerWrapRef.value?.querySelector('thead tr:last-child') as HTMLElement | null
|
|
1587
|
+
if (!row) return
|
|
1588
|
+
colSortableInst = Sortable.create(row, {
|
|
1589
|
+
draggable: 'th.qiu-table__cell--col-drag',
|
|
1590
|
+
animation: 150,
|
|
1591
|
+
onEnd: (evt) => {
|
|
1592
|
+
const keys = Array.from(row.querySelectorAll('th[data-col-key]'))
|
|
1593
|
+
.map((el) => el.getAttribute('data-col-key') || '')
|
|
1594
|
+
.filter(Boolean)
|
|
1595
|
+
if (keys.length) {
|
|
1596
|
+
columnOrderKeys.value = keys
|
|
1597
|
+
emit('column-dragend', {
|
|
1598
|
+
oldIndex: evt.oldIndex ?? 0,
|
|
1599
|
+
newIndex: evt.newIndex ?? 0,
|
|
1600
|
+
columnKeys: keys,
|
|
1601
|
+
})
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
})
|
|
1605
|
+
})
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
watch(
|
|
1609
|
+
() => [columnDragEnabled.value, leafColumns.value.map((c) => c._key).join(',')],
|
|
1610
|
+
() => initColumnSortable()
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
const resolvedShowRadio = computed(
|
|
1614
|
+
() =>
|
|
1615
|
+
props.showRadio || mergedSourceColumns.value.some((c) => c.type === 'radio')
|
|
1616
|
+
)
|
|
1617
|
+
|
|
1618
|
+
const resolvedShowExpand = computed(
|
|
1619
|
+
() =>
|
|
1620
|
+
props.showExpand || mergedSourceColumns.value.some((c) => c.type === 'expand')
|
|
1621
|
+
)
|
|
1622
|
+
|
|
1623
|
+
const fixedLeftOffset = ( whichtype: 'radio' | 'selection' | 'index') => {
|
|
1624
|
+
let left = 0
|
|
1625
|
+
if (resolvedShowExpand.value) left += 48
|
|
1626
|
+
if (whichtype === 'radio') return left
|
|
1627
|
+
if (resolvedShowRadio.value) left += 48
|
|
1628
|
+
if (whichtype === 'selection') return left
|
|
1629
|
+
if (resolvedShowSelection.value) left += 48
|
|
1630
|
+
return left
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const hasFixedLeftDataCol = computed(() =>
|
|
1634
|
+
leafColumns.value.some((c) => c.fixed === 'left' || c.fixed === true)
|
|
1635
|
+
)
|
|
1636
|
+
|
|
1637
|
+
/** 无数据左固定列时,特殊列最后一列需要阴影 */
|
|
1638
|
+
const lastSpecialLeftKind = computed(() => {
|
|
1639
|
+
if (hasFixedLeftDataCol.value) return null
|
|
1640
|
+
if (resolvedShowIndex.value) return 'index'
|
|
1641
|
+
if (resolvedShowSelection.value) return 'selection'
|
|
1642
|
+
if (resolvedShowRadio.value) return 'radio'
|
|
1643
|
+
if (resolvedShowExpand.value) return 'expand'
|
|
1644
|
+
return null
|
|
1645
|
+
})
|
|
1646
|
+
|
|
1647
|
+
const specialLeftLastClass = (kind: 'expand' | 'radio' | 'selection' | 'index') => ({
|
|
1648
|
+
'qiu-table__cell--fixed-left-last': lastSpecialLeftKind.value === kind,
|
|
1649
|
+
})
|
|
1650
|
+
|
|
1651
|
+
const headerSourceColumns = computed(() => {
|
|
1652
|
+
const visibleKeys = new Set(leafColumns.value.map((c) => c._key))
|
|
1653
|
+
const walk = (cols: QiuTableColumnConfig[], prefix = ''): QiuTableColumnConfig[] => {
|
|
1654
|
+
const out: QiuTableColumnConfig[] = []
|
|
1655
|
+
cols.forEach((col, i) => {
|
|
1656
|
+
if (
|
|
1657
|
+
col.type === 'selection' ||
|
|
1658
|
+
col.type === 'checkbox' ||
|
|
1659
|
+
col.type === 'index' ||
|
|
1660
|
+
col.type === 'seq'
|
|
1661
|
+
) {
|
|
1662
|
+
return
|
|
1663
|
+
}
|
|
1664
|
+
const key = makeColumnKey(col, i, prefix)
|
|
1665
|
+
if (col.children?.length) {
|
|
1666
|
+
const children = walk(col.children, key)
|
|
1667
|
+
if (children.length) out.push({ ...col, children })
|
|
1668
|
+
return
|
|
1669
|
+
}
|
|
1670
|
+
if (visibleKeys.has(key)) out.push(col)
|
|
1671
|
+
})
|
|
1672
|
+
return out
|
|
1673
|
+
}
|
|
1674
|
+
return walk(mergedSourceColumns.value)
|
|
1675
|
+
})
|
|
1676
|
+
|
|
1677
|
+
const headerRows = computed(() => {
|
|
1678
|
+
if (virtualXEnabled.value) {
|
|
1679
|
+
return [
|
|
1680
|
+
displayLeafColumns.value.map((col, i) => ({
|
|
1681
|
+
column: col,
|
|
1682
|
+
label: col.label || '',
|
|
1683
|
+
colspan: 1,
|
|
1684
|
+
rowspan: 1,
|
|
1685
|
+
isLeaf: true,
|
|
1686
|
+
colIndex: i,
|
|
1687
|
+
})),
|
|
1688
|
+
]
|
|
1689
|
+
}
|
|
1690
|
+
const rows = buildHeaderRows(headerSourceColumns.value)
|
|
1691
|
+
const base = rows.length ? rows : [[]]
|
|
1692
|
+
return applyMergeHeaderCells(base, props.mergeHeaderCells)
|
|
1693
|
+
})
|
|
1694
|
+
|
|
1695
|
+
const resolvedShowSelection = computed(
|
|
1696
|
+
() =>
|
|
1697
|
+
props.showSelection ||
|
|
1698
|
+
mergedSourceColumns.value.some((c) => c.type === 'selection' || c.type === 'checkbox')
|
|
1699
|
+
)
|
|
1700
|
+
|
|
1701
|
+
const resolvedShowIndex = computed(
|
|
1702
|
+
() =>
|
|
1703
|
+
props.showIndex ||
|
|
1704
|
+
mergedSourceColumns.value.some((c) => c.type === 'index' || c.type === 'seq')
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
const renderWidthMap = computed(() => {
|
|
1708
|
+
void layoutTick.value
|
|
1709
|
+
for (const k of Object.keys(columnWidths)) void columnWidths[k]
|
|
1710
|
+
const containerWidth = bodyWrapRef.value?.clientWidth || 0
|
|
1711
|
+
return computeRenderWidths(leafColumns.value, {
|
|
1712
|
+
containerWidth,
|
|
1713
|
+
showSelection: resolvedShowSelection.value,
|
|
1714
|
+
showIndex: resolvedShowIndex.value,
|
|
1715
|
+
showRadio: resolvedShowRadio.value,
|
|
1716
|
+
showExpand: resolvedShowExpand.value,
|
|
1717
|
+
fit: props.fit !== false,
|
|
1718
|
+
resized: columnWidths,
|
|
1719
|
+
defaults: columnWidthDefaults.value,
|
|
1720
|
+
})
|
|
1721
|
+
})
|
|
1722
|
+
|
|
1723
|
+
const tableMinWidthStyle = computed(() => {
|
|
1724
|
+
// 依赖拖拽列宽与布局 tick,保证三张表总宽始终一致
|
|
1725
|
+
void layoutTick.value
|
|
1726
|
+
for (const k of Object.keys(columnWidths)) void columnWidths[k]
|
|
1727
|
+
const total = Math.max(
|
|
1728
|
+
1,
|
|
1729
|
+
computeTableMinWidth(
|
|
1730
|
+
leafColumns.value,
|
|
1731
|
+
{
|
|
1732
|
+
showSelection: resolvedShowSelection.value,
|
|
1733
|
+
showIndex: resolvedShowIndex.value,
|
|
1734
|
+
showRadio: resolvedShowRadio.value,
|
|
1735
|
+
showExpand: resolvedShowExpand.value,
|
|
1736
|
+
},
|
|
1737
|
+
(col) => renderWidthMap.value[col._key] || getWidth(col._key),
|
|
1738
|
+
columnWidthDefaults.value
|
|
1739
|
+
)
|
|
1740
|
+
)
|
|
1741
|
+
// 100% 铺满窄容器;minWidth 保证列宽之和,超出时由 body-wrap 横向滚动
|
|
1742
|
+
return {
|
|
1743
|
+
width: '100%',
|
|
1744
|
+
minWidth: `${total}px`,
|
|
1745
|
+
}
|
|
1746
|
+
})
|
|
1747
|
+
|
|
1748
|
+
const rootSizeStyle = computed(() => {
|
|
1749
|
+
const style: Record<string, string> = {}
|
|
1750
|
+
if (props.height != null) {
|
|
1751
|
+
style.height = normalizeSize(props.height)
|
|
1752
|
+
style.minHeight = '0'
|
|
1753
|
+
}
|
|
1754
|
+
if (props.maxHeight != null) {
|
|
1755
|
+
style.maxHeight = normalizeSize(props.maxHeight)
|
|
1756
|
+
style.minHeight = '0'
|
|
1757
|
+
}
|
|
1758
|
+
if (props.minHeight != null) {
|
|
1759
|
+
style.minHeight = normalizeSize(props.minHeight)
|
|
1760
|
+
}
|
|
1761
|
+
if (props.rowConfig?.height != null) {
|
|
1762
|
+
style['--qiu-table-row-height'] = `${props.rowConfig.height}px`
|
|
1763
|
+
}
|
|
1764
|
+
return style
|
|
1765
|
+
})
|
|
1766
|
+
|
|
1767
|
+
/** 含纵向滚动条占位,供表头/表尾补齐右侧竖线 */
|
|
1768
|
+
const rootTableStyle = computed(() => ({
|
|
1769
|
+
...rootSizeStyle.value,
|
|
1770
|
+
'--qiu-table-v-scrollbar': `${scrollbarSize.value}px`,
|
|
1771
|
+
}))
|
|
1772
|
+
|
|
1773
|
+
/** main 在固定高度模式下吃掉剩余空间,自身不再写死 height,避免和 toolbar/pager 叠高 */
|
|
1774
|
+
const mainStyle = computed(() => {
|
|
1775
|
+
const style: Record<string, string> = {}
|
|
1776
|
+
if (props.rowConfig?.height != null) {
|
|
1777
|
+
style['--qiu-table-row-height'] = `${props.rowConfig.height}px`
|
|
1778
|
+
}
|
|
1779
|
+
return style
|
|
1780
|
+
})
|
|
1781
|
+
|
|
1782
|
+
const virtualXEnabled = computed(() => {
|
|
1783
|
+
const c = props.virtualXConfig
|
|
1784
|
+
if (c === true) return true
|
|
1785
|
+
if (c && typeof c === 'object') return c.enabled !== false
|
|
1786
|
+
return false
|
|
1787
|
+
})
|
|
1788
|
+
|
|
1789
|
+
const colWidthOf = (col: LeafColumn) =>
|
|
1790
|
+
renderWidthMap.value[col._key] ||
|
|
1791
|
+
getWidth(col._key) ||
|
|
1792
|
+
parseWidth(col.width) ||
|
|
1793
|
+
parseWidth(col.minWidth) ||
|
|
1794
|
+
columnWidthDefaults.value.minWidth ||
|
|
1795
|
+
100
|
|
1796
|
+
|
|
1797
|
+
const virtualXPad = computed(() => {
|
|
1798
|
+
void layoutTick.value
|
|
1799
|
+
if (!virtualXEnabled.value) return { left: 0, right: 0, start: 0, end: leafColumns.value.length }
|
|
1800
|
+
const cols = leafColumns.value
|
|
1801
|
+
const viewW = bodyWrapRef.value?.clientWidth || 800
|
|
1802
|
+
let acc = 0
|
|
1803
|
+
let start = 0
|
|
1804
|
+
const x = Math.max(0, scrollLeftX.value)
|
|
1805
|
+
for (let i = 0; i < cols.length; i++) {
|
|
1806
|
+
const w = colWidthOf(cols[i])
|
|
1807
|
+
if (acc + w >= x - 120) {
|
|
1808
|
+
start = i
|
|
1809
|
+
break
|
|
1810
|
+
}
|
|
1811
|
+
acc += w
|
|
1812
|
+
start = i + 1
|
|
1813
|
+
}
|
|
1814
|
+
let end = start
|
|
1815
|
+
let used = 0
|
|
1816
|
+
for (let i = start; i < cols.length; i++) {
|
|
1817
|
+
used += colWidthOf(cols[i])
|
|
1818
|
+
end = i + 1
|
|
1819
|
+
if (used > viewW + 240) break
|
|
1820
|
+
}
|
|
1821
|
+
let left = 0
|
|
1822
|
+
for (let i = 0; i < start; i++) left += colWidthOf(cols[i])
|
|
1823
|
+
let right = 0
|
|
1824
|
+
for (let i = end; i < cols.length; i++) right += colWidthOf(cols[i])
|
|
1825
|
+
return { left, right, start, end }
|
|
1826
|
+
})
|
|
1827
|
+
|
|
1828
|
+
const displayLeafColumns = computed(() => {
|
|
1829
|
+
if (!virtualXEnabled.value) return leafColumns.value
|
|
1830
|
+
return leafColumns.value.slice(virtualXPad.value.start, virtualXPad.value.end)
|
|
1831
|
+
})
|
|
1832
|
+
|
|
1833
|
+
const realColIndex = (col: LeafColumn) => leafColumns.value.findIndex((c) => c._key === col._key)
|
|
1834
|
+
|
|
1835
|
+
const totalColspan = computed(() => {
|
|
1836
|
+
let n = displayLeafColumns.value.length
|
|
1837
|
+
if (resolvedShowExpand.value) n += 1
|
|
1838
|
+
if (resolvedShowRadio.value) n += 1
|
|
1839
|
+
if (resolvedShowSelection.value) n += 1
|
|
1840
|
+
if (resolvedShowIndex.value) n += 1
|
|
1841
|
+
if (virtualXEnabled.value && virtualXPad.value.left > 0) n += 1
|
|
1842
|
+
if (virtualXEnabled.value && virtualXPad.value.right > 0) n += 1
|
|
1843
|
+
return Math.max(n, 1)
|
|
1844
|
+
})
|
|
1845
|
+
|
|
1846
|
+
const pagerEnabled = computed(() => {
|
|
1847
|
+
if (props.pagerConfig === true) return true
|
|
1848
|
+
if (props.pagerConfig && typeof props.pagerConfig === 'object') {
|
|
1849
|
+
return props.pagerConfig.enabled !== false
|
|
1850
|
+
}
|
|
1851
|
+
return false
|
|
1852
|
+
})
|
|
1853
|
+
|
|
1854
|
+
const pagerConfig = computed(() =>
|
|
1855
|
+
typeof props.pagerConfig === 'object' ? props.pagerConfig : undefined
|
|
1856
|
+
)
|
|
1857
|
+
|
|
1858
|
+
watch(
|
|
1859
|
+
() => pagerConfig.value,
|
|
1860
|
+
(cfg) => {
|
|
1861
|
+
if (cfg?.currentPage) innerPage.value = cfg.currentPage
|
|
1862
|
+
if (cfg?.pageSize) innerPageSize.value = cfg.pageSize
|
|
1863
|
+
},
|
|
1864
|
+
{ immediate: true }
|
|
1865
|
+
)
|
|
1866
|
+
|
|
1867
|
+
const dragEnabled = computed(
|
|
1868
|
+
() =>
|
|
1869
|
+
props.dragSort ||
|
|
1870
|
+
!!props.gridOptions?.rowConfig?.drag ||
|
|
1871
|
+
leafColumns.value.some((c) => c.dragSort)
|
|
1872
|
+
)
|
|
1873
|
+
|
|
1874
|
+
const getChildren = (row: any) => row?.[resolvedTreeProps.value.children] || []
|
|
1875
|
+
const hasRowChildren = (row: any) => {
|
|
1876
|
+
const children = getChildren(row)
|
|
1877
|
+
if (children.length) return true
|
|
1878
|
+
return !!row?.[resolvedTreeProps.value.hasChildren]
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
const walkRows = (rows: any[], fn: (row: any) => void) => {
|
|
1882
|
+
const childrenKey = resolvedTreeProps.value.children
|
|
1883
|
+
for (const row of rows || []) {
|
|
1884
|
+
fn(row)
|
|
1885
|
+
if (row?.[childrenKey]?.length) walkRows(row[childrenKey], fn)
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
const initExpandState = () => {
|
|
1890
|
+
if (!props.tree) return
|
|
1891
|
+
const next = new Set<string>()
|
|
1892
|
+
if (props.defaultExpandAll) {
|
|
1893
|
+
walkRows(props.data, (row) => {
|
|
1894
|
+
if (hasRowChildren(row)) next.add(getRowId(row))
|
|
1895
|
+
})
|
|
1896
|
+
}
|
|
1897
|
+
expandedKeys.value = next
|
|
1898
|
+
expandInitialized = true
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
const isExpanded = (row: any) => expandedKeys.value.has(getRowId(row))
|
|
1902
|
+
const toggleExpand = (row: any) => {
|
|
1903
|
+
const id = getRowId(row)
|
|
1904
|
+
const next = new Set(expandedKeys.value)
|
|
1905
|
+
if (next.has(id)) next.delete(id)
|
|
1906
|
+
else next.add(id)
|
|
1907
|
+
expandedKeys.value = next
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const flattenRows = (rows: any[], level = 0, acc: QiuTableFlatRow[] = []): QiuTableFlatRow[] => {
|
|
1911
|
+
for (const row of rows || []) {
|
|
1912
|
+
const hasChildren = hasRowChildren(row)
|
|
1913
|
+
const expanded = isExpanded(row)
|
|
1914
|
+
acc.push({ row, level, hasChildren, expanded, flatIndex: acc.length })
|
|
1915
|
+
if (hasChildren && expanded) flattenRows(getChildren(row), level + 1, acc)
|
|
1916
|
+
}
|
|
1917
|
+
return acc
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const rawFlatRows = computed(() => {
|
|
1921
|
+
if (props.tree) return flattenRows(props.data)
|
|
1922
|
+
return (props.data || []).map((row, i) => ({
|
|
1923
|
+
row,
|
|
1924
|
+
level: 0,
|
|
1925
|
+
hasChildren: false,
|
|
1926
|
+
expanded: false,
|
|
1927
|
+
flatIndex: i,
|
|
1928
|
+
}))
|
|
1929
|
+
})
|
|
1930
|
+
|
|
1931
|
+
const formatCell = (row: any, col: QiuTableColumnConfig) => {
|
|
1932
|
+
const prop = resolveColumnProp(col)
|
|
1933
|
+
const value = prop != null ? row?.[prop] : undefined
|
|
1934
|
+
if (col.formatter) return String(col.formatter(row, col, value) ?? '')
|
|
1935
|
+
if (col.format && QiuTableFormats.has(col.format)) {
|
|
1936
|
+
return QiuTableFormats.get(col.format)!({ cellValue: value, row, column: col })
|
|
1937
|
+
}
|
|
1938
|
+
const er = getEditRender(col)
|
|
1939
|
+
const options = er?.options || col.options
|
|
1940
|
+
const editor = er?.name || getColEditor(col)
|
|
1941
|
+
if (editor === 'select' && options?.length) {
|
|
1942
|
+
const hit = options.find((o) => o.value === value)
|
|
1943
|
+
return String(hit?.label ?? value ?? '')
|
|
1944
|
+
}
|
|
1945
|
+
if (value == null) return ''
|
|
1946
|
+
return String(value)
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const isFilterable = (col: LeafColumn) => !!col.filterable || !!(col.filters && col.filters.length)
|
|
1950
|
+
|
|
1951
|
+
const getFilterOptions = (col: LeafColumn): FilterOption[] => {
|
|
1952
|
+
if (col.filters?.length) return col.filters
|
|
1953
|
+
const prop = getProp(col)
|
|
1954
|
+
if (!prop || !col.filterable) return []
|
|
1955
|
+
const seen = new Set<string>()
|
|
1956
|
+
const opts: FilterOption[] = []
|
|
1957
|
+
for (const { row } of rawFlatRows.value) {
|
|
1958
|
+
const v = row?.[prop]
|
|
1959
|
+
const key = String(v)
|
|
1960
|
+
if (seen.has(key)) continue
|
|
1961
|
+
seen.add(key)
|
|
1962
|
+
opts.push({ text: formatCell(row, col) || key, value: v })
|
|
1963
|
+
}
|
|
1964
|
+
return opts
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const isFilterActive = (col: LeafColumn) => (filterValues[col._key]?.length ?? 0) > 0
|
|
1968
|
+
|
|
1969
|
+
const openFilterOptions = computed(() => {
|
|
1970
|
+
const key = openFilterKey.value
|
|
1971
|
+
if (!key) return [] as FilterOption[]
|
|
1972
|
+
const col = leafColumns.value.find((c) => c._key === key)
|
|
1973
|
+
return col ? getFilterOptions(col) : []
|
|
1974
|
+
})
|
|
1975
|
+
|
|
1976
|
+
const applyFilters = (rows: QiuTableFlatRow[]) => {
|
|
1977
|
+
if (props.filterConfig?.remote) return rows
|
|
1978
|
+
const active = leafColumns.value.filter((c) => (filterValues[c._key]?.length ?? 0) > 0)
|
|
1979
|
+
if (!active.length) return rows
|
|
1980
|
+
return rows.filter(({ row }) =>
|
|
1981
|
+
active.every((col) => {
|
|
1982
|
+
const vals = filterValues[col._key] || []
|
|
1983
|
+
if (col.filterMethod) return vals.some((v) => col.filterMethod!(v, row, col))
|
|
1984
|
+
const prop = getProp(col)
|
|
1985
|
+
if (!prop) return true
|
|
1986
|
+
return vals.some((v) => row[prop] === v)
|
|
1987
|
+
})
|
|
1988
|
+
)
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
const applySort = (rows: QiuTableFlatRow[]) => {
|
|
1992
|
+
if (props.sortConfig?.remote) return rows
|
|
1993
|
+
const sorts =
|
|
1994
|
+
props.sortConfig?.multiple && sortList.value.length
|
|
1995
|
+
? sortList.value
|
|
1996
|
+
: sortState.value.prop && sortState.value.order
|
|
1997
|
+
? [{ prop: sortState.value.prop, order: sortState.value.order }]
|
|
1998
|
+
: []
|
|
1999
|
+
if (!sorts.length) return rows
|
|
2000
|
+
const sorted = [...rows]
|
|
2001
|
+
sorted.sort((a, b) => compareRows(a.row, b.row, sorts))
|
|
2002
|
+
return sorted.map((r, i) => ({ ...r, flatIndex: i }))
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
const visibleRows = computed(() => applySort(applyFilters(rawFlatRows.value)))
|
|
2006
|
+
|
|
2007
|
+
const pagerTotal = computed(() => pagerConfig.value?.total ?? visibleRows.value.length)
|
|
2008
|
+
|
|
2009
|
+
const pageCount = computed(() => Math.max(1, Math.ceil(pagerTotal.value / innerPageSize.value)))
|
|
2010
|
+
|
|
2011
|
+
const pagedRows = computed(() => {
|
|
2012
|
+
if (!pagerEnabled.value) return visibleRows.value
|
|
2013
|
+
if (pagerConfig.value?.total != null) {
|
|
2014
|
+
return visibleRows.value.map((r, i) => ({ ...r, flatIndex: i }))
|
|
2015
|
+
}
|
|
2016
|
+
const start = (innerPage.value - 1) * innerPageSize.value
|
|
2017
|
+
return visibleRows.value
|
|
2018
|
+
.slice(start, start + innerPageSize.value)
|
|
2019
|
+
.map((r, i) => ({ ...r, flatIndex: start + i }))
|
|
2020
|
+
})
|
|
2021
|
+
|
|
2022
|
+
const virtualConfig = computed(() => {
|
|
2023
|
+
if (props.virtualYConfig != null) return props.virtualYConfig
|
|
2024
|
+
if (props.virtualY) return true
|
|
2025
|
+
return false
|
|
2026
|
+
})
|
|
2027
|
+
|
|
2028
|
+
const {
|
|
2029
|
+
enabled: virtualEnabled,
|
|
2030
|
+
range: virtualRange,
|
|
2031
|
+
onScroll: onVirtualScroll,
|
|
2032
|
+
measure: measureVirtual,
|
|
2033
|
+
} = useVirtualY({
|
|
2034
|
+
bodyWrapRef,
|
|
2035
|
+
rows: () => pagedRows.value,
|
|
2036
|
+
config: () => virtualConfig.value,
|
|
2037
|
+
defaultRowHeight: props.rowConfig?.height || 48,
|
|
2038
|
+
})
|
|
2039
|
+
|
|
2040
|
+
const displayRowItems = computed(() => {
|
|
2041
|
+
const rows = pagedRows.value
|
|
2042
|
+
if (!virtualEnabled.value) {
|
|
2043
|
+
return rows.map((flat, rowIndex) => ({ flat, rowIndex }))
|
|
2044
|
+
}
|
|
2045
|
+
const { start, end } = virtualRange.value
|
|
2046
|
+
return rows.slice(start, end).map((flat, i) => ({
|
|
2047
|
+
flat,
|
|
2048
|
+
rowIndex: start + i,
|
|
2049
|
+
}))
|
|
2050
|
+
})
|
|
2051
|
+
|
|
2052
|
+
const onBodyScroll = () => {
|
|
2053
|
+
syncFromBody()
|
|
2054
|
+
onVirtualScroll()
|
|
2055
|
+
scrollLeftX.value = bodyWrapRef.value?.scrollLeft || 0
|
|
2056
|
+
updateHasScrollX()
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
const buildDefaultFooter = (): string[][] => {
|
|
2060
|
+
// 必须用当前页数据:翻页后合计/平均应随之变化
|
|
2061
|
+
const data = pagedRows.value.map((f) => f.row)
|
|
2062
|
+
const sumRow: string[] = []
|
|
2063
|
+
const avgRow: string[] = []
|
|
2064
|
+
|
|
2065
|
+
leafColumns.value.forEach((col, i) => {
|
|
2066
|
+
const prop = getProp(col)
|
|
2067
|
+
if (i === 0) {
|
|
2068
|
+
sumRow.push(translate('合计'))
|
|
2069
|
+
avgRow.push(translate('平均'))
|
|
2070
|
+
return
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const nums = data
|
|
2074
|
+
.map((r) => Number(prop != null ? r[prop] : NaN))
|
|
2075
|
+
.filter((n) => !Number.isNaN(n))
|
|
2076
|
+
|
|
2077
|
+
const fmtMoney = (n: number) =>
|
|
2078
|
+
`¥ ${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
2079
|
+
|
|
2080
|
+
if (col.aggFunc === 'count') {
|
|
2081
|
+
sumRow.push(String(data.length))
|
|
2082
|
+
avgRow.push('')
|
|
2083
|
+
return
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
if (col.aggFunc === 'sum' || col.aggFunc === 'avg' || (nums.length > 0 && nums.length === data.length)) {
|
|
2087
|
+
const s = nums.reduce((a, b) => a + b, 0)
|
|
2088
|
+
const avg = nums.length ? s / nums.length : 0
|
|
2089
|
+
const useMoney = col.aggFunc != null || prop === 'jan' || prop === 'feb'
|
|
2090
|
+
if (col.aggFunc === 'avg') {
|
|
2091
|
+
sumRow.push('')
|
|
2092
|
+
avgRow.push(useMoney ? fmtMoney(avg) : avg.toFixed(2))
|
|
2093
|
+
return
|
|
2094
|
+
}
|
|
2095
|
+
sumRow.push(useMoney ? fmtMoney(s) : String(Number.isInteger(s) ? s : Number(s.toFixed(2))))
|
|
2096
|
+
avgRow.push(useMoney ? fmtMoney(avg) : avg.toFixed(2))
|
|
2097
|
+
return
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
sumRow.push('')
|
|
2101
|
+
avgRow.push('')
|
|
2102
|
+
})
|
|
2103
|
+
|
|
2104
|
+
return [sumRow, avgRow]
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
const footerRows = computed(() => {
|
|
2108
|
+
if (!props.showFooter) return [] as string[][]
|
|
2109
|
+
if (props.footerData?.length) return props.footerData
|
|
2110
|
+
const pageData = pagedRows.value.map((f) => f.row)
|
|
2111
|
+
if (props.footerMethod) {
|
|
2112
|
+
return props.footerMethod({
|
|
2113
|
+
columns: leafColumns.value,
|
|
2114
|
+
data: pageData,
|
|
2115
|
+
})
|
|
2116
|
+
}
|
|
2117
|
+
return buildDefaultFooter()
|
|
2118
|
+
})
|
|
2119
|
+
|
|
2120
|
+
const mapSlotScope = (row: any, col: QiuTableColumnConfig, index: number, level = 0) => ({
|
|
2121
|
+
row,
|
|
2122
|
+
column: col,
|
|
2123
|
+
$index: index,
|
|
2124
|
+
cellValue: getProp(col) != null ? row?.[getProp(col)!] : undefined,
|
|
2125
|
+
level,
|
|
2126
|
+
})
|
|
2127
|
+
|
|
2128
|
+
const editorSize = computed(() => props.size || 'small')
|
|
2129
|
+
|
|
2130
|
+
const toDateInputValue = (val: any, datetime: boolean) => {
|
|
2131
|
+
if (!val) return ''
|
|
2132
|
+
const s = String(val)
|
|
2133
|
+
if (datetime) return s.replace(' ', 'T').slice(0, 16)
|
|
2134
|
+
return s.slice(0, 10)
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
const renderEditor = (row: any, col: LeafColumn) => {
|
|
2138
|
+
const prop = getProp(col)!
|
|
2139
|
+
const val = row[prop]
|
|
2140
|
+
const onDone = (v: any) => commitEdit(row, col, v)
|
|
2141
|
+
const scheduleBlurCommit = (v: any) => {
|
|
2142
|
+
// 延后到下一帧,避开挂载/聚焦瞬间的伪 blur,以及下拉面板抢焦点
|
|
2143
|
+
requestAnimationFrame(() => {
|
|
2144
|
+
if (inEditGuard()) return
|
|
2145
|
+
if (!isEditing(row, col)) return
|
|
2146
|
+
onDone(v)
|
|
2147
|
+
})
|
|
2148
|
+
}
|
|
2149
|
+
const editRender = getEditRender(col) || resolveEditRender(col)
|
|
2150
|
+
const editor = editRender?.name || getColEditor(col)
|
|
2151
|
+
const editorProps = {
|
|
2152
|
+
...(col.editorProps || {}),
|
|
2153
|
+
...(editRender?.props || {}),
|
|
2154
|
+
...(editRender?.attrs || {}),
|
|
2155
|
+
}
|
|
2156
|
+
const editorOptions = editRender?.options || col.options || []
|
|
2157
|
+
const bindEvents = (base: Record<string, any>) => {
|
|
2158
|
+
const ev = editRender?.events
|
|
2159
|
+
if (!ev) return base
|
|
2160
|
+
const next = { ...base }
|
|
2161
|
+
Object.keys(ev).forEach((k) => {
|
|
2162
|
+
const userFn = ev[k]
|
|
2163
|
+
const prev = next[k]
|
|
2164
|
+
next[k] = (...args: any[]) => {
|
|
2165
|
+
userFn?.(...args)
|
|
2166
|
+
if (typeof prev === 'function') prev(...args)
|
|
2167
|
+
}
|
|
2168
|
+
})
|
|
2169
|
+
return next
|
|
2170
|
+
}
|
|
2171
|
+
const fill = editor === 'input'
|
|
2172
|
+
|
|
2173
|
+
// 自定义 editRender
|
|
2174
|
+
if (editor && QiuTableEditRenderer.has(editor)) {
|
|
2175
|
+
const vnode = QiuTableEditRenderer.get(editor)!({
|
|
2176
|
+
cellValue: val,
|
|
2177
|
+
row,
|
|
2178
|
+
column: col,
|
|
2179
|
+
editRender: editRender!,
|
|
2180
|
+
commit: onDone,
|
|
2181
|
+
update: (v) => {
|
|
2182
|
+
row[prop] = v
|
|
2183
|
+
},
|
|
2184
|
+
})
|
|
2185
|
+
if (vnode) return vnode
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
if (editor === 'switch') {
|
|
2189
|
+
return h(
|
|
2190
|
+
QiuSwitch,
|
|
2191
|
+
bindEvents({
|
|
2192
|
+
modelValue: !!val,
|
|
2193
|
+
size: editorSize.value === 'large' ? 'default' : 'small',
|
|
2194
|
+
...editorProps,
|
|
2195
|
+
'onUpdate:modelValue': onDone,
|
|
2196
|
+
onChange: onDone,
|
|
2197
|
+
})
|
|
2198
|
+
)
|
|
2199
|
+
}
|
|
2200
|
+
if (editor === 'select') {
|
|
2201
|
+
return h(
|
|
2202
|
+
QiuSelect,
|
|
2203
|
+
bindEvents({
|
|
2204
|
+
modelValue: val,
|
|
2205
|
+
options: editorOptions,
|
|
2206
|
+
size: editorSize.value,
|
|
2207
|
+
fit: true,
|
|
2208
|
+
...editorProps,
|
|
2209
|
+
'onUpdate:modelValue': (v: any) => {
|
|
2210
|
+
row[prop] = v
|
|
2211
|
+
},
|
|
2212
|
+
onChange: onDone,
|
|
2213
|
+
onVisibleChange: (open: boolean) => {
|
|
2214
|
+
if (!open) onDone(row[prop])
|
|
2215
|
+
},
|
|
2216
|
+
})
|
|
2217
|
+
)
|
|
2218
|
+
}
|
|
2219
|
+
if (editor === 'number') {
|
|
2220
|
+
return h(
|
|
2221
|
+
QiuInputNumber,
|
|
2222
|
+
bindEvents({
|
|
2223
|
+
modelValue: val,
|
|
2224
|
+
size: editorSize.value,
|
|
2225
|
+
controlsPosition: 'right',
|
|
2226
|
+
style: { width: '110px', maxWidth: '100%' },
|
|
2227
|
+
...editorProps,
|
|
2228
|
+
'onUpdate:modelValue': (v: number | null) => {
|
|
2229
|
+
row[prop] = v
|
|
2230
|
+
},
|
|
2231
|
+
onChange: onDone,
|
|
2232
|
+
onBlur: () => scheduleBlurCommit(row[prop]),
|
|
2233
|
+
})
|
|
2234
|
+
)
|
|
2235
|
+
}
|
|
2236
|
+
if (editor === 'date' || editor === 'datetime') {
|
|
2237
|
+
return h(
|
|
2238
|
+
'input',
|
|
2239
|
+
bindEvents({
|
|
2240
|
+
class: 'qiu-table__date-input',
|
|
2241
|
+
type: editor === 'datetime' ? 'datetime-local' : 'date',
|
|
2242
|
+
value: toDateInputValue(val, editor === 'datetime'),
|
|
2243
|
+
...editorProps,
|
|
2244
|
+
onChange: (e: Event) => onDone((e.target as HTMLInputElement).value),
|
|
2245
|
+
onKeydown: (e: KeyboardEvent) => {
|
|
2246
|
+
if (e.key === 'Enter') {
|
|
2247
|
+
e.preventDefault()
|
|
2248
|
+
e.stopPropagation()
|
|
2249
|
+
onDone((e.target as HTMLInputElement).value)
|
|
2250
|
+
}
|
|
2251
|
+
},
|
|
2252
|
+
onBlur: (e: FocusEvent) => scheduleBlurCommit((e.target as HTMLInputElement).value),
|
|
2253
|
+
})
|
|
2254
|
+
)
|
|
2255
|
+
}
|
|
2256
|
+
if (editor === 'time') {
|
|
2257
|
+
return h(
|
|
2258
|
+
QiuTime,
|
|
2259
|
+
bindEvents({
|
|
2260
|
+
modelValue: val ?? null,
|
|
2261
|
+
size: editorSize.value,
|
|
2262
|
+
fit: true,
|
|
2263
|
+
format: (editorProps as any)?.format || 'HH:mm:ss',
|
|
2264
|
+
...editorProps,
|
|
2265
|
+
'onUpdate:modelValue': (v: string | null) => {
|
|
2266
|
+
row[prop] = v
|
|
2267
|
+
},
|
|
2268
|
+
onChange: onDone,
|
|
2269
|
+
onVisibleChange: (open: boolean) => {
|
|
2270
|
+
if (!open && row[prop] != null) onDone(row[prop])
|
|
2271
|
+
},
|
|
2272
|
+
})
|
|
2273
|
+
)
|
|
2274
|
+
}
|
|
2275
|
+
return h(
|
|
2276
|
+
QiuInput,
|
|
2277
|
+
bindEvents({
|
|
2278
|
+
modelValue: val ?? '',
|
|
2279
|
+
size: fill ? undefined : editorSize.value,
|
|
2280
|
+
class: fill ? 'qiu-table__input-fill' : undefined,
|
|
2281
|
+
style: fill
|
|
2282
|
+
? { width: '100%', height: '100%', borderRadius: '0' }
|
|
2283
|
+
: { width: '100%' },
|
|
2284
|
+
...editorProps,
|
|
2285
|
+
'onUpdate:modelValue': (v: string | number) => {
|
|
2286
|
+
row[prop] = v
|
|
2287
|
+
},
|
|
2288
|
+
onKeydown: (e: KeyboardEvent) => {
|
|
2289
|
+
if (e.key === 'Enter') {
|
|
2290
|
+
e.preventDefault()
|
|
2291
|
+
e.stopPropagation()
|
|
2292
|
+
onDone(row[prop])
|
|
2293
|
+
} else if (e.key === 'Escape') {
|
|
2294
|
+
e.preventDefault()
|
|
2295
|
+
e.stopPropagation()
|
|
2296
|
+
clearEdit()
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
onBlur: () => scheduleBlurCommit(row[prop]),
|
|
2300
|
+
})
|
|
2301
|
+
)
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
const onCellClick = (row: any, col: QiuTableColumnConfig, e?: MouseEvent) => {
|
|
2305
|
+
setActive(row, col, !!e?.shiftKey)
|
|
2306
|
+
onRowClick(row)
|
|
2307
|
+
if (!props.editable) return
|
|
2308
|
+
const trigger = getTrigger()
|
|
2309
|
+
if (trigger === 'click') startEdit(row, col, bodyWrapRef.value)
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
const onCellDblclick = (row: any, col: QiuTableColumnConfig) => {
|
|
2313
|
+
setActive(row, col)
|
|
2314
|
+
if (!props.editable) return
|
|
2315
|
+
const trigger = getTrigger()
|
|
2316
|
+
if (trigger === 'dblclick') startEdit(row, col, bodyWrapRef.value)
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
const isRowSelected = (row: any) =>
|
|
2320
|
+
selectionRows.value.some((r) => getRowId(r) === getRowId(row))
|
|
2321
|
+
|
|
2322
|
+
const toggleRowSelection = (row: any, checked: boolean) => {
|
|
2323
|
+
const id = getRowId(row)
|
|
2324
|
+
let next = selectionRows.value.slice()
|
|
2325
|
+
if (checked) {
|
|
2326
|
+
if (!next.some((r) => getRowId(r) === id)) next.push(row)
|
|
2327
|
+
} else next = next.filter((r) => getRowId(r) !== id)
|
|
2328
|
+
selectionRows.value = next
|
|
2329
|
+
emit('selection-change', next)
|
|
2330
|
+
emit('checkbox-change', { records: next, row, checked })
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
const isAllSelected = computed(() => {
|
|
2334
|
+
const rows = pagedRows.value.map((f) => f.row)
|
|
2335
|
+
return rows.length > 0 && rows.every((r) => isRowSelected(r))
|
|
2336
|
+
})
|
|
2337
|
+
|
|
2338
|
+
const isIndeterminate = computed(() => {
|
|
2339
|
+
const rows = pagedRows.value.map((f) => f.row)
|
|
2340
|
+
const count = rows.filter((r) => isRowSelected(r)).length
|
|
2341
|
+
return count > 0 && count < rows.length
|
|
2342
|
+
})
|
|
2343
|
+
|
|
2344
|
+
const toggleSelectAll = (checked: boolean) => {
|
|
2345
|
+
const rows = pagedRows.value.map((f) => f.row)
|
|
2346
|
+
selectionRows.value = checked ? rows.slice() : []
|
|
2347
|
+
emit('selection-change', selectionRows.value)
|
|
2348
|
+
emit('checkbox-all', { records: selectionRows.value, checked })
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
const onHeaderClick = (col: LeafColumn, e?: MouseEvent) => {
|
|
2352
|
+
if (!col.sortable || col.sortable === 'custom') return
|
|
2353
|
+
const prop = getProp(col)
|
|
2354
|
+
if (!prop) return
|
|
2355
|
+
const multi = !!props.sortConfig?.multiple && !!(e?.shiftKey || e?.ctrlKey || e?.metaKey)
|
|
2356
|
+
|
|
2357
|
+
if (multi) {
|
|
2358
|
+
const idx = sortList.value.findIndex((s) => s.prop === prop)
|
|
2359
|
+
if (idx < 0) sortList.value = [...sortList.value, { prop, order: 'asc' }]
|
|
2360
|
+
else if (sortList.value[idx].order === 'asc') {
|
|
2361
|
+
const next = sortList.value.slice()
|
|
2362
|
+
next[idx] = { prop, order: 'desc' }
|
|
2363
|
+
sortList.value = next
|
|
2364
|
+
} else {
|
|
2365
|
+
sortList.value = sortList.value.filter((s) => s.prop !== prop)
|
|
2366
|
+
}
|
|
2367
|
+
const last = sortList.value[sortList.value.length - 1]
|
|
2368
|
+
sortState.value = last
|
|
2369
|
+
? { prop: last.prop, order: last.order }
|
|
2370
|
+
: { prop: '', order: null }
|
|
2371
|
+
emit('sort-change', {
|
|
2372
|
+
prop: sortState.value.prop,
|
|
2373
|
+
order: sortState.value.order,
|
|
2374
|
+
sorts: sortList.value.slice(),
|
|
2375
|
+
} as any)
|
|
2376
|
+
return
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
sortList.value = []
|
|
2380
|
+
if (sortState.value.prop !== prop) sortState.value = { prop, order: 'asc' }
|
|
2381
|
+
else if (sortState.value.order === 'asc') sortState.value = { prop, order: 'desc' }
|
|
2382
|
+
else sortState.value = { prop: '', order: null }
|
|
2383
|
+
if (sortState.value.prop && sortState.value.order) {
|
|
2384
|
+
sortList.value = [{ prop: sortState.value.prop, order: sortState.value.order }]
|
|
2385
|
+
}
|
|
2386
|
+
emit('sort-change', { ...sortState.value, sorts: sortList.value.slice() } as any)
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
const sortPriority = (col: LeafColumn) => {
|
|
2390
|
+
if (!props.sortConfig?.multiple || sortList.value.length < 2) return 0
|
|
2391
|
+
const prop = getProp(col)
|
|
2392
|
+
const idx = sortList.value.findIndex((s) => s.prop === prop)
|
|
2393
|
+
return idx >= 0 ? idx + 1 : 0
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
const sortClass = (col: LeafColumn) => {
|
|
2397
|
+
const prop = getProp(col)
|
|
2398
|
+
if (props.sortConfig?.multiple && sortList.value.length) {
|
|
2399
|
+
const hit = sortList.value.find((s) => s.prop === prop)
|
|
2400
|
+
if (!hit) return ''
|
|
2401
|
+
return hit.order === 'asc' ? 'is-asc' : 'is-desc'
|
|
2402
|
+
}
|
|
2403
|
+
if (sortState.value.prop !== prop) return ''
|
|
2404
|
+
return sortState.value.order === 'asc' ? 'is-asc' : 'is-desc'
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
const onRowClick = (row: any) => {
|
|
2408
|
+
if (!resolveRowCurrent(props.rowConfig)) return
|
|
2409
|
+
currentRowId.value = getRowId(row)
|
|
2410
|
+
emit('current-change', { row })
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
const mergeCovered = computed(() => buildMergeCoveredSet(props.mergeCells))
|
|
2414
|
+
|
|
2415
|
+
const cellSpan = (
|
|
2416
|
+
flat: QiuTableFlatRow,
|
|
2417
|
+
rowIndex: number,
|
|
2418
|
+
col: LeafColumn,
|
|
2419
|
+
columnIndex: number
|
|
2420
|
+
) =>
|
|
2421
|
+
resolveCellSpan({
|
|
2422
|
+
row: flat.row,
|
|
2423
|
+
column: col,
|
|
2424
|
+
rowIndex,
|
|
2425
|
+
columnIndex,
|
|
2426
|
+
data: pagedRows.value.map((f) => f.row),
|
|
2427
|
+
spanMethod: props.spanMethod,
|
|
2428
|
+
mergeCells: props.mergeCells,
|
|
2429
|
+
covered: mergeCovered.value,
|
|
2430
|
+
})
|
|
2431
|
+
|
|
2432
|
+
const isColResizable = (col: LeafColumn) => {
|
|
2433
|
+
if (col.resizable === false) return false
|
|
2434
|
+
if (col.resizable === true) return true
|
|
2435
|
+
return !!props.columnConfig?.resizable
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
const resolvedColStyle = (col: LeafColumn) => {
|
|
2439
|
+
void columnWidths[col._key]
|
|
2440
|
+
const w = colWidthOf(col)
|
|
2441
|
+
const hints = getColumnSizeHints(col, bodyWrapRef.value?.clientWidth || 0, columnWidthDefaults.value)
|
|
2442
|
+
return {
|
|
2443
|
+
width: `${w}px`,
|
|
2444
|
+
minWidth: `${hints.isFlex ? hints.minWidth : w}px`,
|
|
2445
|
+
...(hints.maxWidth < Number.POSITIVE_INFINITY ? { maxWidth: `${hints.maxWidth}px` } : {}),
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
const onColResizeStart = (col: LeafColumn, e: MouseEvent) => {
|
|
2450
|
+
const cur = colWidthOf(col)
|
|
2451
|
+
const hints = getColumnSizeHints(col, bodyWrapRef.value?.clientWidth || 0, columnWidthDefaults.value)
|
|
2452
|
+
onResizeStart(col._key, cur, e, {
|
|
2453
|
+
minWidth: hints.minWidth,
|
|
2454
|
+
maxWidth: hints.maxWidth < Number.POSITIVE_INFINITY ? hints.maxWidth : undefined,
|
|
2455
|
+
})
|
|
2456
|
+
const onUp = () => {
|
|
2457
|
+
emit('resizable-change', {
|
|
2458
|
+
column: col,
|
|
2459
|
+
width: columnWidths[col._key] || cur,
|
|
2460
|
+
})
|
|
2461
|
+
document.removeEventListener('mouseup', onUp)
|
|
2462
|
+
}
|
|
2463
|
+
document.addEventListener('mouseup', onUp)
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
const toggleFilterPanel = (col: LeafColumn, e: MouseEvent) => {
|
|
2467
|
+
if (openFilterKey.value === col._key) {
|
|
2468
|
+
closeFilterPanel()
|
|
2469
|
+
return
|
|
2470
|
+
}
|
|
2471
|
+
filterDraft[col._key] = [...(filterValues[col._key] || [])]
|
|
2472
|
+
openFilterKey.value = col._key
|
|
2473
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
|
2474
|
+
filterPanelPos.value = {
|
|
2475
|
+
left: Math.min(rect.left, window.innerWidth - 220),
|
|
2476
|
+
top: rect.bottom + 6,
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
const closeFilterPanel = () => {
|
|
2481
|
+
openFilterKey.value = null
|
|
2482
|
+
filterPanelPos.value = null
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const isFilterOptionChecked = (key: string, value: any) =>
|
|
2486
|
+
(filterDraft[key] || []).some((v) => v === value)
|
|
2487
|
+
|
|
2488
|
+
const toggleFilterOption = (key: string, value: any, checked: boolean) => {
|
|
2489
|
+
const cur = [...(filterDraft[key] || [])]
|
|
2490
|
+
const idx = cur.findIndex((v) => v === value)
|
|
2491
|
+
if (checked && idx < 0) cur.push(value)
|
|
2492
|
+
if (!checked && idx >= 0) cur.splice(idx, 1)
|
|
2493
|
+
filterDraft[key] = cur
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
const isFilterAllChecked = computed(() => {
|
|
2497
|
+
const key = openFilterKey.value
|
|
2498
|
+
if (!key || !openFilterOptions.value.length) return false
|
|
2499
|
+
return openFilterOptions.value.every((opt) => isFilterOptionChecked(key, opt.value))
|
|
2500
|
+
})
|
|
2501
|
+
|
|
2502
|
+
const isFilterIndeterminate = computed(() => {
|
|
2503
|
+
const key = openFilterKey.value
|
|
2504
|
+
if (!key || !openFilterOptions.value.length) return false
|
|
2505
|
+
const n = (filterDraft[key] || []).length
|
|
2506
|
+
return n > 0 && n < openFilterOptions.value.length
|
|
2507
|
+
})
|
|
2508
|
+
|
|
2509
|
+
const toggleFilterAll = (checked: boolean) => {
|
|
2510
|
+
const key = openFilterKey.value
|
|
2511
|
+
if (!key) return
|
|
2512
|
+
filterDraft[key] = checked ? openFilterOptions.value.map((o) => o.value) : []
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
const confirmFilter = () => {
|
|
2516
|
+
const key = openFilterKey.value
|
|
2517
|
+
if (key) {
|
|
2518
|
+
filterValues[key] = [...(filterDraft[key] || [])]
|
|
2519
|
+
emit('filter-change', { ...filterValues })
|
|
2520
|
+
}
|
|
2521
|
+
closeFilterPanel()
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
const resetOpenFilter = () => {
|
|
2525
|
+
const key = openFilterKey.value
|
|
2526
|
+
if (key) {
|
|
2527
|
+
filterDraft[key] = []
|
|
2528
|
+
filterValues[key] = []
|
|
2529
|
+
emit('filter-change', { ...filterValues })
|
|
2530
|
+
}
|
|
2531
|
+
closeFilterPanel()
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
const headerCellStyle = (cell: { column: LeafColumn; isLeaf: boolean }) => {
|
|
2535
|
+
const col = cell.column
|
|
2536
|
+
// 分组表头不写 width,避免 colspan 单元格与 colgroup 抢宽度导致错线
|
|
2537
|
+
const style: Record<string, string> = cell.isLeaf ? {} : {}
|
|
2538
|
+
if (!cell.isLeaf) return style
|
|
2539
|
+
if (col.fixed === 'left' || col.fixed === true) {
|
|
2540
|
+
style.left = `${leftOffsetForColumn(
|
|
2541
|
+
leafColumns.value,
|
|
2542
|
+
col._key,
|
|
2543
|
+
{
|
|
2544
|
+
showSelection: resolvedShowSelection.value,
|
|
2545
|
+
showIndex: resolvedShowIndex.value,
|
|
2546
|
+
showRadio: resolvedShowRadio.value,
|
|
2547
|
+
showExpand: resolvedShowExpand.value,
|
|
2548
|
+
},
|
|
2549
|
+
colWidthOf
|
|
2550
|
+
)}px`
|
|
2551
|
+
}
|
|
2552
|
+
if (col.fixed === 'right') {
|
|
2553
|
+
style.right = `${rightOffsetForColumn(leafColumns.value, col._key, colWidthOf)}px`
|
|
2554
|
+
}
|
|
2555
|
+
return style
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
const bodyCellStyle = (col?: LeafColumn) => {
|
|
2559
|
+
if (!col) return {}
|
|
2560
|
+
// 列宽只走 colgroup,避免 th/td 与 col 双重指定在缩放后分叉
|
|
2561
|
+
const style: Record<string, string> = {}
|
|
2562
|
+
if (col.fixed === 'left' || col.fixed === true) {
|
|
2563
|
+
style.left = `${leftOffsetForColumn(
|
|
2564
|
+
leafColumns.value,
|
|
2565
|
+
col._key,
|
|
2566
|
+
{
|
|
2567
|
+
showSelection: resolvedShowSelection.value,
|
|
2568
|
+
showIndex: resolvedShowIndex.value,
|
|
2569
|
+
showRadio: resolvedShowRadio.value,
|
|
2570
|
+
showExpand: resolvedShowExpand.value,
|
|
2571
|
+
},
|
|
2572
|
+
colWidthOf
|
|
2573
|
+
)}px`
|
|
2574
|
+
}
|
|
2575
|
+
if (col.fixed === 'right') {
|
|
2576
|
+
style.right = `${rightOffsetForColumn(leafColumns.value, col._key, colWidthOf)}px`
|
|
2577
|
+
}
|
|
2578
|
+
return style
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
const fixedCellClass = (col?: LeafColumn) => {
|
|
2582
|
+
if (!col) return {}
|
|
2583
|
+
const isLeft = col.fixed === 'left' || col.fixed === true
|
|
2584
|
+
const isRight = col.fixed === 'right'
|
|
2585
|
+
return {
|
|
2586
|
+
'qiu-table__cell--fixed-left': isLeft,
|
|
2587
|
+
'qiu-table__cell--fixed-right': isRight,
|
|
2588
|
+
'qiu-table__cell--fixed-left-last':
|
|
2589
|
+
isLeft && isLastFixedLeft(leafColumns.value, col._key),
|
|
2590
|
+
'qiu-table__cell--fixed-right-first':
|
|
2591
|
+
isRight && isFirstFixedRight(leafColumns.value, col._key),
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
const toggleFullscreen = async () => {
|
|
2596
|
+
isFullscreen.value = !isFullscreen.value
|
|
2597
|
+
await nextTick()
|
|
2598
|
+
scheduleLayout()
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
const onPageChange = (page: number) => {
|
|
2602
|
+
innerPage.value = page
|
|
2603
|
+
emit('page-change', { currentPage: page, pageSize: innerPageSize.value })
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
const onSizeChange = (size: number) => {
|
|
2607
|
+
innerPageSize.value = size
|
|
2608
|
+
innerPage.value = 1
|
|
2609
|
+
emit('page-change', { currentPage: 1, pageSize: size })
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
const setAllTreeExpand = (checked: boolean) => {
|
|
2613
|
+
const next = new Set<string>()
|
|
2614
|
+
if (checked) {
|
|
2615
|
+
walkRows(props.data, (row) => {
|
|
2616
|
+
if (hasRowChildren(row)) next.add(getRowId(row))
|
|
2617
|
+
})
|
|
2618
|
+
}
|
|
2619
|
+
expandedKeys.value = next
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
const getCheckboxRecords = () => selectionRows.value.slice()
|
|
2623
|
+
|
|
2624
|
+
const exportableColumns = computed(() =>
|
|
2625
|
+
leafColumns.value.filter((c) => getProp(c) && !c.dragSort && c.type !== 'selection' && c.type !== 'index' && c.type !== 'seq' && c.type !== 'radio' && c.type !== 'expand')
|
|
2626
|
+
)
|
|
2627
|
+
|
|
2628
|
+
const exportAllChecked = computed(
|
|
2629
|
+
() =>
|
|
2630
|
+
exportableColumns.value.length > 0 &&
|
|
2631
|
+
exportableColumns.value.every((c) => exportFieldKeys.value.includes(c._key))
|
|
2632
|
+
)
|
|
2633
|
+
|
|
2634
|
+
const exportIndeterminate = computed(() => {
|
|
2635
|
+
const n = exportFieldKeys.value.length
|
|
2636
|
+
return n > 0 && n < exportableColumns.value.length
|
|
2637
|
+
})
|
|
2638
|
+
|
|
2639
|
+
const openExportDialog = () => {
|
|
2640
|
+
const cfg = props.exportConfig
|
|
2641
|
+
const all = exportableColumns.value.filter((c) =>
|
|
2642
|
+
cfg?.columnFilterMethod ? cfg.columnFilterMethod({ column: c }) !== false : true
|
|
2643
|
+
)
|
|
2644
|
+
exportFieldKeys.value = all.map((c) => c._key)
|
|
2645
|
+
exportFormat.value = (cfg?.type as QiuTableExportFormat) || 'xlsx'
|
|
2646
|
+
exportMode.value = cfg?.mode || 'all'
|
|
2647
|
+
exportStartPage.value = Math.max(1, cfg?.startPage ?? innerPage.value)
|
|
2648
|
+
exportEndPage.value = Math.max(
|
|
2649
|
+
exportStartPage.value,
|
|
2650
|
+
Math.min(pageCount.value, cfg?.endPage ?? pageCount.value)
|
|
2651
|
+
)
|
|
2652
|
+
exportDialogVisible.value = true
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
const toggleExportAll = (checked: boolean) => {
|
|
2656
|
+
exportFieldKeys.value = checked ? exportableColumns.value.map((c) => c._key) : []
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
const toggleExportField = (key: string, checked: boolean) => {
|
|
2660
|
+
if (checked) {
|
|
2661
|
+
if (!exportFieldKeys.value.includes(key)) exportFieldKeys.value = [...exportFieldKeys.value, key]
|
|
2662
|
+
} else {
|
|
2663
|
+
exportFieldKeys.value = exportFieldKeys.value.filter((k) => k !== key)
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
const collectExportRows = (mode: 'current' | 'all' | 'selected' | 'range') => {
|
|
2668
|
+
if (mode === 'selected') return selectionRows.value.slice()
|
|
2669
|
+
if (mode === 'current') return pagedRows.value.map((f) => f.row)
|
|
2670
|
+
if (mode === 'range') {
|
|
2671
|
+
const start = Math.max(0, (exportStartPage.value - 1) * innerPageSize.value)
|
|
2672
|
+
const end = exportEndPage.value * innerPageSize.value
|
|
2673
|
+
return visibleRows.value.slice(start, end).map((r) => r.row)
|
|
2674
|
+
}
|
|
2675
|
+
const flat: any[] = []
|
|
2676
|
+
walkRows(props.data, (row) => flat.push(row))
|
|
2677
|
+
return flat
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
const confirmExport = async () => {
|
|
2681
|
+
const cfg = { ...(props.exportConfig || {}) }
|
|
2682
|
+
cfg.type = exportFormat.value
|
|
2683
|
+
if (cfg.beforeExportMethod) {
|
|
2684
|
+
const ok = await cfg.beforeExportMethod({ options: cfg })
|
|
2685
|
+
if (ok === false) return
|
|
2686
|
+
}
|
|
2687
|
+
const cols = exportableColumns.value.filter((c) => exportFieldKeys.value.includes(c._key))
|
|
2688
|
+
if (!cols.length) return
|
|
2689
|
+
cfg.mode = exportMode.value || 'all'
|
|
2690
|
+
cfg.startPage = exportStartPage.value
|
|
2691
|
+
cfg.endPage = exportEndPage.value
|
|
2692
|
+
const rows = collectExportRows(cfg.mode)
|
|
2693
|
+
const matrix = buildSheetMatrix(cols, rows, (row, col) => formatCell(row, col))
|
|
2694
|
+
const base = cfg.filename || 'export'
|
|
2695
|
+
await exportSheet({
|
|
2696
|
+
format: exportFormat.value,
|
|
2697
|
+
filename: base,
|
|
2698
|
+
matrix,
|
|
2699
|
+
})
|
|
2700
|
+
exportDialogVisible.value = false
|
|
2701
|
+
cfg.afterExportMethod?.({ options: cfg })
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
const exportData = async (opts?: {
|
|
2705
|
+
filename?: string
|
|
2706
|
+
format?: QiuTableExportFormat
|
|
2707
|
+
columns?: string[]
|
|
2708
|
+
mode?: 'current' | 'all' | 'selected' | 'range'
|
|
2709
|
+
startPage?: number
|
|
2710
|
+
endPage?: number
|
|
2711
|
+
}) => {
|
|
2712
|
+
const cfg = { ...(props.exportConfig || {}), ...opts }
|
|
2713
|
+
if (cfg.beforeExportMethod) {
|
|
2714
|
+
const ok = await cfg.beforeExportMethod({ options: cfg as QiuTableExportConfig })
|
|
2715
|
+
if (ok === false) return
|
|
2716
|
+
}
|
|
2717
|
+
const all = exportableColumns.value.filter((c) =>
|
|
2718
|
+
cfg.columnFilterMethod ? cfg.columnFilterMethod({ column: c }) !== false : true
|
|
2719
|
+
)
|
|
2720
|
+
const cols = opts?.columns?.length
|
|
2721
|
+
? all.filter((c) => opts.columns!.includes(c._key) || opts.columns!.includes(getProp(c) || ''))
|
|
2722
|
+
: all
|
|
2723
|
+
if (!cols.length) {
|
|
2724
|
+
openExportDialog()
|
|
2725
|
+
return
|
|
2726
|
+
}
|
|
2727
|
+
const exportModeValue = opts?.mode || cfg.mode || 'all'
|
|
2728
|
+
if (exportModeValue === 'range') {
|
|
2729
|
+
exportStartPage.value = Math.max(1, opts?.startPage ?? cfg.startPage ?? innerPage.value)
|
|
2730
|
+
exportEndPage.value = Math.max(
|
|
2731
|
+
exportStartPage.value,
|
|
2732
|
+
Math.min(pageCount.value, opts?.endPage ?? cfg.endPage ?? pageCount.value)
|
|
2733
|
+
)
|
|
2734
|
+
}
|
|
2735
|
+
const rows = collectExportRows(exportModeValue)
|
|
2736
|
+
const matrix = buildSheetMatrix(cols, rows, (row, col) => formatCell(row, col))
|
|
2737
|
+
await exportSheet({
|
|
2738
|
+
format: (opts?.format || cfg.type || 'xlsx') as QiuTableExportFormat,
|
|
2739
|
+
filename: opts?.filename || cfg.filename || 'export',
|
|
2740
|
+
matrix,
|
|
2741
|
+
})
|
|
2742
|
+
cfg.afterExportMethod?.({ options: cfg as QiuTableExportConfig })
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
const triggerImport = () => importInputRef.value?.click()
|
|
2746
|
+
|
|
2747
|
+
const onImportFile = async (e: Event) => {
|
|
2748
|
+
const file = (e.target as HTMLInputElement).files?.[0]
|
|
2749
|
+
;(e.target as HTMLInputElement).value = ''
|
|
2750
|
+
if (!file) return
|
|
2751
|
+
try {
|
|
2752
|
+
const rows = await parseImportFile(file)
|
|
2753
|
+
const objects = csvToObjects(rows, leafColumns.value)
|
|
2754
|
+
emit('import-data', { rows: objects })
|
|
2755
|
+
} catch (err) {
|
|
2756
|
+
console.error(err)
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
const escapePrintHtml = (s: any) =>
|
|
2761
|
+
String(s ?? '')
|
|
2762
|
+
.replace(/&/g, '&')
|
|
2763
|
+
.replace(/</g, '<')
|
|
2764
|
+
.replace(/>/g, '>')
|
|
2765
|
+
.replace(/"/g, '"')
|
|
2766
|
+
|
|
2767
|
+
/** 构建可打印表格:含完整表头;thead 在每页重复 */
|
|
2768
|
+
const printTable = () => {
|
|
2769
|
+
const cols = leafColumns.value
|
|
2770
|
+
const dataRows = visibleRows.value
|
|
2771
|
+
const built = buildHeaderRows(headerSourceColumns.value)
|
|
2772
|
+
const headRows = built.filter((r) => r.length)
|
|
2773
|
+
const depth = Math.max(headRows.length, 1)
|
|
2774
|
+
|
|
2775
|
+
const specialTh = (label = '') =>
|
|
2776
|
+
`<th class="qiu-print-special" rowspan="${depth}">${escapePrintHtml(label)}</th>`
|
|
2777
|
+
|
|
2778
|
+
let theadHtml = '<thead>'
|
|
2779
|
+
if (headRows.length) {
|
|
2780
|
+
headRows.forEach((row, ri) => {
|
|
2781
|
+
theadHtml += '<tr>'
|
|
2782
|
+
if (ri === 0) {
|
|
2783
|
+
if (resolvedShowExpand.value) theadHtml += specialTh('')
|
|
2784
|
+
if (resolvedShowRadio.value) theadHtml += specialTh('')
|
|
2785
|
+
if (resolvedShowSelection.value) theadHtml += specialTh('')
|
|
2786
|
+
if (resolvedShowIndex.value) theadHtml += specialTh(translate('序号'))
|
|
2787
|
+
}
|
|
2788
|
+
row.forEach((cell) => {
|
|
2789
|
+
theadHtml += `<th colspan="${cell.colspan || 1}" rowspan="${cell.rowspan || 1}">${escapePrintHtml(cell.label)}</th>`
|
|
2790
|
+
})
|
|
2791
|
+
theadHtml += '</tr>'
|
|
2792
|
+
})
|
|
2793
|
+
} else {
|
|
2794
|
+
theadHtml += '<tr>'
|
|
2795
|
+
if (resolvedShowExpand.value) theadHtml += specialTh('')
|
|
2796
|
+
if (resolvedShowRadio.value) theadHtml += specialTh('')
|
|
2797
|
+
if (resolvedShowSelection.value) theadHtml += specialTh('')
|
|
2798
|
+
if (resolvedShowIndex.value) theadHtml += specialTh(translate('序号'))
|
|
2799
|
+
cols.forEach((col) => {
|
|
2800
|
+
theadHtml += `<th>${escapePrintHtml(col.label || '')}</th>`
|
|
2801
|
+
})
|
|
2802
|
+
theadHtml += '</tr>'
|
|
2803
|
+
}
|
|
2804
|
+
theadHtml += '</thead>'
|
|
2805
|
+
|
|
2806
|
+
let tbodyHtml = '<tbody>'
|
|
2807
|
+
dataRows.forEach((item, i) => {
|
|
2808
|
+
tbodyHtml += '<tr>'
|
|
2809
|
+
if (resolvedShowExpand.value) tbodyHtml += '<td></td>'
|
|
2810
|
+
if (resolvedShowRadio.value) tbodyHtml += '<td></td>'
|
|
2811
|
+
if (resolvedShowSelection.value) tbodyHtml += '<td></td>'
|
|
2812
|
+
if (resolvedShowIndex.value) tbodyHtml += `<td>${item.flatIndex + 1}</td>`
|
|
2813
|
+
cols.forEach((col) => {
|
|
2814
|
+
const align = col.align || 'left'
|
|
2815
|
+
tbodyHtml += `<td style="text-align:${align}">${escapePrintHtml(formatCell(item.row, col))}</td>`
|
|
2816
|
+
})
|
|
2817
|
+
tbodyHtml += '</tr>'
|
|
2818
|
+
})
|
|
2819
|
+
tbodyHtml += '</tbody>'
|
|
2820
|
+
|
|
2821
|
+
let tfootHtml = ''
|
|
2822
|
+
if (props.showFooter && footerRows.value.length) {
|
|
2823
|
+
tfootHtml = '<tfoot>'
|
|
2824
|
+
footerRows.value.forEach((frow) => {
|
|
2825
|
+
tfootHtml += '<tr>'
|
|
2826
|
+
if (resolvedShowExpand.value) tfootHtml += '<td></td>'
|
|
2827
|
+
if (resolvedShowRadio.value) tfootHtml += '<td></td>'
|
|
2828
|
+
if (resolvedShowSelection.value) tfootHtml += '<td></td>'
|
|
2829
|
+
if (resolvedShowIndex.value) tfootHtml += '<td></td>'
|
|
2830
|
+
frow.forEach((text, ci) => {
|
|
2831
|
+
const align = cols[ci]?.align || 'left'
|
|
2832
|
+
tfootHtml += `<td style="text-align:${align}">${escapePrintHtml(text)}</td>`
|
|
2833
|
+
})
|
|
2834
|
+
tfootHtml += '</tr>'
|
|
2835
|
+
})
|
|
2836
|
+
tfootHtml += '</tfoot>'
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
const win = window.open('', '_blank')
|
|
2840
|
+
if (!win) return
|
|
2841
|
+
win.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8"/><title>${escapePrintHtml(translate('打印'))}</title>
|
|
2842
|
+
<style>
|
|
2843
|
+
@page { margin: 12mm; }
|
|
2844
|
+
* { box-sizing: border-box; }
|
|
2845
|
+
body { margin: 0; font-family: "Microsoft YaHei", "PingFang SC", sans-serif; color: #303133; }
|
|
2846
|
+
table.qiu-print-table {
|
|
2847
|
+
width: 100%;
|
|
2848
|
+
border-collapse: collapse;
|
|
2849
|
+
table-layout: auto;
|
|
2850
|
+
font-size: 12px;
|
|
2851
|
+
}
|
|
2852
|
+
.qiu-print-table th,
|
|
2853
|
+
.qiu-print-table td {
|
|
2854
|
+
border: 1px solid #dcdfe6;
|
|
2855
|
+
padding: 6px 8px;
|
|
2856
|
+
vertical-align: middle;
|
|
2857
|
+
word-break: break-word;
|
|
2858
|
+
}
|
|
2859
|
+
.qiu-print-table thead {
|
|
2860
|
+
display: table-header-group; /* 每一打印页重复表头 */
|
|
2861
|
+
}
|
|
2862
|
+
.qiu-print-table tfoot {
|
|
2863
|
+
display: table-footer-group;
|
|
2864
|
+
}
|
|
2865
|
+
.qiu-print-table tr {
|
|
2866
|
+
page-break-inside: avoid;
|
|
2867
|
+
}
|
|
2868
|
+
.qiu-print-table th {
|
|
2869
|
+
background: #f5f7fa !important;
|
|
2870
|
+
font-weight: 600;
|
|
2871
|
+
text-align: left;
|
|
2872
|
+
-webkit-print-color-adjust: exact;
|
|
2873
|
+
print-color-adjust: exact;
|
|
2874
|
+
}
|
|
2875
|
+
.qiu-print-table tfoot td {
|
|
2876
|
+
background: #fafafa !important;
|
|
2877
|
+
font-weight: 600;
|
|
2878
|
+
-webkit-print-color-adjust: exact;
|
|
2879
|
+
print-color-adjust: exact;
|
|
2880
|
+
}
|
|
2881
|
+
@media print {
|
|
2882
|
+
.qiu-print-table thead { display: table-header-group; }
|
|
2883
|
+
.qiu-print-table tfoot { display: table-footer-group; }
|
|
2884
|
+
}
|
|
2885
|
+
</style></head><body>
|
|
2886
|
+
<table class="qiu-print-table">${theadHtml}${tbodyHtml}${tfootHtml}</table>
|
|
2887
|
+
</body></html>`)
|
|
2888
|
+
win.document.close()
|
|
2889
|
+
win.focus()
|
|
2890
|
+
// 等样式/字体就绪再打
|
|
2891
|
+
setTimeout(() => {
|
|
2892
|
+
win.print()
|
|
2893
|
+
}, 50)
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
const toolButtonList = computed(() => [
|
|
2897
|
+
{
|
|
2898
|
+
name: 'refresh',
|
|
2899
|
+
show: refreshEnabled.value,
|
|
2900
|
+
title: translate('刷新'),
|
|
2901
|
+
icon: 'refresh-line',
|
|
2902
|
+
onClick: () => emit('refresh'),
|
|
2903
|
+
},
|
|
2904
|
+
{
|
|
2905
|
+
name: 'zoom',
|
|
2906
|
+
show: zoomEnabled.value,
|
|
2907
|
+
title: translate(isFullscreen.value ? '退出全屏' : '全屏'),
|
|
2908
|
+
icon: isFullscreen.value ? 'fullscreen-exit-line' : 'fullscreen-line',
|
|
2909
|
+
onClick: toggleFullscreen,
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
name: 'import',
|
|
2913
|
+
show: importEnabled.value,
|
|
2914
|
+
title: translate('导入'),
|
|
2915
|
+
icon: 'upload-2-line',
|
|
2916
|
+
onClick: triggerImport,
|
|
2917
|
+
},
|
|
2918
|
+
{
|
|
2919
|
+
name: 'print',
|
|
2920
|
+
show: printEnabled.value,
|
|
2921
|
+
title: translate('打印'),
|
|
2922
|
+
icon: 'printer-line',
|
|
2923
|
+
onClick: printTable,
|
|
2924
|
+
},
|
|
2925
|
+
{
|
|
2926
|
+
name: 'custom',
|
|
2927
|
+
show: customEnabled.value,
|
|
2928
|
+
title: translate('列设置'),
|
|
2929
|
+
icon: 'settings-3-line',
|
|
2930
|
+
onClick: () => (customPanelOpen.value = !customPanelOpen.value),
|
|
2931
|
+
},
|
|
2932
|
+
{
|
|
2933
|
+
name: 'export',
|
|
2934
|
+
show: exportEnabled.value || !!props.exportConfig,
|
|
2935
|
+
title: translate('导出'),
|
|
2936
|
+
icon: 'download-2-line',
|
|
2937
|
+
onClick: openExportDialog,
|
|
2938
|
+
},
|
|
2939
|
+
])
|
|
2940
|
+
|
|
2941
|
+
const isSwitchColumn = (col: LeafColumn) => {
|
|
2942
|
+
if (col.editor === 'switch' || col.type === 'switch') return true
|
|
2943
|
+
if ((col.editRender as any)?.name === 'switch' || (col.cellRender as any)?.name === 'switch') return true
|
|
2944
|
+
return false
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
const copyToClipboard = async (text: string) => {
|
|
2948
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
2949
|
+
await navigator.clipboard.writeText(text)
|
|
2950
|
+
return
|
|
2951
|
+
}
|
|
2952
|
+
const textarea = document.createElement('textarea')
|
|
2953
|
+
textarea.value = text
|
|
2954
|
+
textarea.style.position = 'fixed'
|
|
2955
|
+
textarea.style.opacity = '0'
|
|
2956
|
+
document.body.appendChild(textarea)
|
|
2957
|
+
textarea.select()
|
|
2958
|
+
try {
|
|
2959
|
+
document.execCommand('copy')
|
|
2960
|
+
} finally {
|
|
2961
|
+
textarea.remove()
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
const copyTableData = async (
|
|
2966
|
+
direction: 'vertical' | 'horizontal',
|
|
2967
|
+
scope: 'all' | 'selected' = 'selected'
|
|
2968
|
+
) => {
|
|
2969
|
+
let cols = leafColumns.value
|
|
2970
|
+
let rows = visibleRows.value.map((f) => f.row)
|
|
2971
|
+
const bounds = areaBounds.value
|
|
2972
|
+
if (scope === 'selected' && bounds) {
|
|
2973
|
+
cols = cols.slice(bounds.c0, bounds.c1 + 1)
|
|
2974
|
+
rows = rows.slice(bounds.r0, bounds.r1 + 1)
|
|
2975
|
+
}
|
|
2976
|
+
if (!cols.length || !rows.length) {
|
|
2977
|
+
qiuMsg.warning(translate('没有可复制的数据'))
|
|
2978
|
+
return
|
|
2979
|
+
}
|
|
2980
|
+
if (direction === 'horizontal' && cols.some(isSwitchColumn)) {
|
|
2981
|
+
qiuMsg.warning(translate('横向复制暂不支持包含开关/自定义渲染的列'))
|
|
2982
|
+
return
|
|
2983
|
+
}
|
|
2984
|
+
const escapeCell = (v: any) => String(v ?? '').replace(/\t/g, ' ').replace(/\r?\n/g, ' ')
|
|
2985
|
+
const cellText = (row: any, col: LeafColumn) => escapeCell(formatCell(row, col))
|
|
2986
|
+
const headerText = (col: LeafColumn) => escapeCell(col.label || col.title || col.field || col._key)
|
|
2987
|
+
let text = ''
|
|
2988
|
+
if (direction === 'vertical') {
|
|
2989
|
+
const lines: string[] = [cols.map(headerText).join('\t')]
|
|
2990
|
+
rows.forEach((row) => lines.push(cols.map((col) => cellText(row, col)).join('\t')))
|
|
2991
|
+
text = lines.join('\n')
|
|
2992
|
+
} else {
|
|
2993
|
+
const matrix: string[][] = []
|
|
2994
|
+
matrix.push([''].concat(rows.map((_, i) => String(i + 1))))
|
|
2995
|
+
cols.forEach((col) => {
|
|
2996
|
+
matrix.push([headerText(col)].concat(rows.map((row) => cellText(row, col))))
|
|
2997
|
+
})
|
|
2998
|
+
text = matrix.map((line) => line.join('\t')).join('\n')
|
|
2999
|
+
}
|
|
3000
|
+
try {
|
|
3001
|
+
await copyToClipboard(text)
|
|
3002
|
+
qiuMsg.success(translate('已复制到剪贴板'))
|
|
3003
|
+
} catch {
|
|
3004
|
+
qiuMsg.error(translate('复制失败,请检查浏览器剪贴板权限'))
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
const closeCtxMenu = () => {
|
|
3009
|
+
ctxMenu.visible = false
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
const onBodyContextMenu = (e: MouseEvent) => {
|
|
3013
|
+
if (props.menuConfig?.enabled === false) return
|
|
3014
|
+
const options = props.menuConfig?.body?.options
|
|
3015
|
+
if (!options?.length) return
|
|
3016
|
+
const tr = (e.target as HTMLElement).closest('tr') as HTMLElement | null
|
|
3017
|
+
const rowKey = tr?.getAttribute('data-row-key') || ''
|
|
3018
|
+
const row = rowKey ? findRowById(props.data, rowKey) : undefined
|
|
3019
|
+
if (
|
|
3020
|
+
props.menuConfig?.visibleMethod &&
|
|
3021
|
+
props.menuConfig.visibleMethod({
|
|
3022
|
+
type: 'body',
|
|
3023
|
+
row,
|
|
3024
|
+
options,
|
|
3025
|
+
}) === false
|
|
3026
|
+
) {
|
|
3027
|
+
return
|
|
3028
|
+
}
|
|
3029
|
+
ctxMenu.visible = true
|
|
3030
|
+
ctxMenu.x = e.clientX
|
|
3031
|
+
ctxMenu.y = e.clientY
|
|
3032
|
+
ctxMenu.options = options
|
|
3033
|
+
ctxMenu.row = row
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
const onMenuItemClick = (item: QiuTableMenuItem) => {
|
|
3037
|
+
if (item.code === 'copy' || item.code === 'COPY') {
|
|
3038
|
+
void copyArea()
|
|
3039
|
+
} else if (item.code === 'cut' || item.code === 'CUT') {
|
|
3040
|
+
void cutArea()
|
|
3041
|
+
} else if (item.code === 'paste' || item.code === 'PASTE') {
|
|
3042
|
+
void pasteArea()
|
|
3043
|
+
}
|
|
3044
|
+
emit('menu-click', { menu: item, row: ctxMenu.row, column: ctxMenu.column })
|
|
3045
|
+
closeCtxMenu()
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
const copyArea = async () => {
|
|
3049
|
+
if (clip().isCopy === false) return
|
|
3050
|
+
const text = buildTsv((row, prop) => {
|
|
3051
|
+
const col = leafColumns.value.find((c) => getProp(c) === prop)
|
|
3052
|
+
return col ? formatCell(row, col) : String(row?.[prop] ?? '')
|
|
3053
|
+
})
|
|
3054
|
+
if (!text) return
|
|
3055
|
+
try {
|
|
3056
|
+
await navigator.clipboard.writeText(text)
|
|
3057
|
+
} catch {
|
|
3058
|
+
// ignore
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
const applyClipboardValue = (row: any, prop: string, value: string) => {
|
|
3063
|
+
const col = leafColumns.value.find((c) => getProp(c) === prop)
|
|
3064
|
+
if (!col || !canEditCol(col)) {
|
|
3065
|
+
row[prop] = value
|
|
3066
|
+
return
|
|
3067
|
+
}
|
|
3068
|
+
void commitEdit(row, col, value)
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
const cutArea = async () => {
|
|
3072
|
+
if (clip().isCut === false) return
|
|
3073
|
+
await copyArea()
|
|
3074
|
+
clearAreaValues((row, prop, value) => applyClipboardValue(row, prop, value))
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
const pasteArea = async () => {
|
|
3078
|
+
if (clip().isPaste === false) return
|
|
3079
|
+
try {
|
|
3080
|
+
const text = await navigator.clipboard.readText()
|
|
3081
|
+
pasteTsv(text, (row, prop, value) => applyClipboardValue(row, prop, value))
|
|
3082
|
+
} catch {
|
|
3083
|
+
// ignore
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
const focusCurrentRow = (row: any) => {
|
|
3088
|
+
onRowClick(row)
|
|
3089
|
+
nextTick(() => scrollToRow(row))
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
const onTableKeydown = (e: KeyboardEvent) => {
|
|
3093
|
+
const allowCurrent = resolveRowCurrent(props.rowConfig)
|
|
3094
|
+
const allowArrow = kb().isArrow !== false
|
|
3095
|
+
const allowEnter = kb().isEnter !== false
|
|
3096
|
+
const allowEsc = kb().isEsc !== false
|
|
3097
|
+
const allowTab = kb().isTab === true
|
|
3098
|
+
const allowUndo = kb().isUndo !== false && historyEnabled()
|
|
3099
|
+
|
|
3100
|
+
if (!props.editable && !allowCurrent && !allowUndo) return
|
|
3101
|
+
|
|
3102
|
+
const el = e.target as HTMLElement | null
|
|
3103
|
+
const tag = el?.tagName
|
|
3104
|
+
const inEditor =
|
|
3105
|
+
tag === 'INPUT' ||
|
|
3106
|
+
tag === 'TEXTAREA' ||
|
|
3107
|
+
tag === 'SELECT' ||
|
|
3108
|
+
!!el?.isContentEditable ||
|
|
3109
|
+
!!el?.closest?.('.qiu-input, .qiu-select, .qiu-input-number, .qiu-time, .qiu-time-panel')
|
|
3110
|
+
|
|
3111
|
+
const mod = e.ctrlKey || e.metaKey
|
|
3112
|
+
|
|
3113
|
+
if (mod && (e.key === 'z' || e.key === 'Z') && allowUndo && !e.shiftKey) {
|
|
3114
|
+
e.preventDefault()
|
|
3115
|
+
undoEdit()
|
|
3116
|
+
return
|
|
3117
|
+
}
|
|
3118
|
+
if (mod && ((e.key === 'y' || e.key === 'Y') || ((e.key === 'z' || e.key === 'Z') && e.shiftKey)) && allowUndo) {
|
|
3119
|
+
e.preventDefault()
|
|
3120
|
+
redoEdit()
|
|
3121
|
+
return
|
|
3122
|
+
}
|
|
3123
|
+
if (mod && (e.key === 'c' || e.key === 'C') && clip().isCopy !== false) {
|
|
3124
|
+
if (!inEditor) {
|
|
3125
|
+
e.preventDefault()
|
|
3126
|
+
void copyArea()
|
|
3127
|
+
}
|
|
3128
|
+
return
|
|
3129
|
+
}
|
|
3130
|
+
if (mod && (e.key === 'x' || e.key === 'X') && clip().isCut !== false) {
|
|
3131
|
+
if (!inEditor) {
|
|
3132
|
+
e.preventDefault()
|
|
3133
|
+
void cutArea()
|
|
3134
|
+
}
|
|
3135
|
+
return
|
|
3136
|
+
}
|
|
3137
|
+
if (mod && (e.key === 'v' || e.key === 'V') && clip().isPaste !== false) {
|
|
3138
|
+
if (!inEditor) {
|
|
3139
|
+
e.preventDefault()
|
|
3140
|
+
void pasteArea()
|
|
3141
|
+
}
|
|
3142
|
+
return
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
if (inEditor && kb().isEdit !== true) return
|
|
3146
|
+
|
|
3147
|
+
const rows = pagedRows.value.map((f) => f.row)
|
|
3148
|
+
if (!rows.length) return
|
|
3149
|
+
const idx = currentRowId.value
|
|
3150
|
+
? rows.findIndex((r) => getRowId(r) === currentRowId.value)
|
|
3151
|
+
: -1
|
|
3152
|
+
const extend = e.shiftKey
|
|
3153
|
+
|
|
3154
|
+
if (allowArrow && e.key === 'ArrowDown') {
|
|
3155
|
+
e.preventDefault()
|
|
3156
|
+
const moved = moveActive(1, 0, extend)
|
|
3157
|
+
if (moved) focusCurrentRow(moved.row)
|
|
3158
|
+
else if (allowCurrent) {
|
|
3159
|
+
const next = rows[Math.min(rows.length - 1, Math.max(0, idx + 1))]
|
|
3160
|
+
if (next) focusCurrentRow(next)
|
|
3161
|
+
}
|
|
3162
|
+
} else if (allowArrow && e.key === 'ArrowUp') {
|
|
3163
|
+
e.preventDefault()
|
|
3164
|
+
const moved = moveActive(-1, 0, extend)
|
|
3165
|
+
if (moved) focusCurrentRow(moved.row)
|
|
3166
|
+
else if (allowCurrent) {
|
|
3167
|
+
const prev = rows[Math.max(0, idx <= 0 ? 0 : idx - 1)]
|
|
3168
|
+
if (prev) focusCurrentRow(prev)
|
|
3169
|
+
}
|
|
3170
|
+
} else if (allowArrow && e.key === 'ArrowLeft') {
|
|
3171
|
+
e.preventDefault()
|
|
3172
|
+
const moved = moveActive(0, -1, extend)
|
|
3173
|
+
if (moved) focusCurrentRow(moved.row)
|
|
3174
|
+
} else if (allowArrow && e.key === 'ArrowRight') {
|
|
3175
|
+
e.preventDefault()
|
|
3176
|
+
const moved = moveActive(0, 1, extend)
|
|
3177
|
+
if (moved) focusCurrentRow(moved.row)
|
|
3178
|
+
} else if (allowTab && e.key === 'Tab') {
|
|
3179
|
+
e.preventDefault()
|
|
3180
|
+
const moved = moveActive(0, e.shiftKey ? -1 : 1, false)
|
|
3181
|
+
if (moved) focusCurrentRow(moved.row)
|
|
3182
|
+
} else if (allowEnter && e.key === 'Enter' && idx >= 0 && props.editable) {
|
|
3183
|
+
if (editing.value) return
|
|
3184
|
+
e.preventDefault()
|
|
3185
|
+
const row = rows[idx]
|
|
3186
|
+
const prop = activeCell.value?.prop
|
|
3187
|
+
const col =
|
|
3188
|
+
(prop && leafColumns.value.find((c) => getProp(c) === prop && canEditCol(c))) ||
|
|
3189
|
+
leafColumns.value.find((c) => canEditCol(c) && getColEditor(c) !== 'switch')
|
|
3190
|
+
if (row && col) startEdit(row, col, bodyWrapRef.value)
|
|
3191
|
+
} else if (allowEsc && e.key === 'Escape') {
|
|
3192
|
+
clearEdit()
|
|
3193
|
+
clearArea()
|
|
3194
|
+
closeCtxMenu()
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
const getGrid = () => ({
|
|
3199
|
+
getTableData: () => ({ fullData: props.data }),
|
|
3200
|
+
getCheckboxRecords,
|
|
3201
|
+
clearEdit,
|
|
3202
|
+
exportData,
|
|
3203
|
+
setAllTreeExpand,
|
|
3204
|
+
importData: triggerImport,
|
|
3205
|
+
print: printTable,
|
|
3206
|
+
})
|
|
3207
|
+
|
|
3208
|
+
const findRowById = (rows: any[], id: string): any | null => {
|
|
3209
|
+
const childrenKey = resolvedTreeProps.value.children
|
|
3210
|
+
for (const row of rows || []) {
|
|
3211
|
+
if (getRowId(row) === id) return row
|
|
3212
|
+
if (row?.[childrenKey]?.length) {
|
|
3213
|
+
const found = findRowById(row[childrenKey], id)
|
|
3214
|
+
if (found) return found
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
return null
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
const initSortable = () => {
|
|
3221
|
+
sortableInst?.destroy()
|
|
3222
|
+
sortableInst = null
|
|
3223
|
+
if (!dragEnabled.value) return
|
|
3224
|
+
nextTick(() => {
|
|
3225
|
+
const el = tbodyRef.value
|
|
3226
|
+
if (!el) return
|
|
3227
|
+
sortableInst = Sortable.create(el, {
|
|
3228
|
+
handle: '.qiu-table__drag-handle',
|
|
3229
|
+
animation: 150,
|
|
3230
|
+
draggable: '.qiu-table__row:not(.qiu-table__row--empty)',
|
|
3231
|
+
onEnd: (evt) => {
|
|
3232
|
+
const dragId = (evt.item as HTMLElement)?.getAttribute('data-row-key') || ''
|
|
3233
|
+
emit('row-dragend', {
|
|
3234
|
+
dragRow: findRowById(props.data, dragId),
|
|
3235
|
+
oldIndex: evt.oldIndex,
|
|
3236
|
+
newIndex: evt.newIndex,
|
|
3237
|
+
})
|
|
3238
|
+
},
|
|
3239
|
+
})
|
|
3240
|
+
})
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
const registerContext: QiuTableRegisterContext = {
|
|
3244
|
+
register(col) {
|
|
3245
|
+
registeredColumns.value = [...registeredColumns.value.filter((c) => c.id !== col.id), col]
|
|
3246
|
+
},
|
|
3247
|
+
unregister(id) {
|
|
3248
|
+
registeredColumns.value = registeredColumns.value.filter((c) => c.id !== id)
|
|
3249
|
+
},
|
|
3250
|
+
update(col) {
|
|
3251
|
+
const idx = registeredColumns.value.findIndex((c) => c.id === col.id)
|
|
3252
|
+
if (idx >= 0) {
|
|
3253
|
+
const next = registeredColumns.value.slice()
|
|
3254
|
+
next[idx] = col
|
|
3255
|
+
registeredColumns.value = next
|
|
3256
|
+
} else registerContext.register(col)
|
|
3257
|
+
},
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
provide(QIU_TABLE_REGISTER_KEY, registerContext)
|
|
3261
|
+
|
|
3262
|
+
watch(
|
|
3263
|
+
() => [props.tree, props.defaultExpandAll, props.data],
|
|
3264
|
+
() => {
|
|
3265
|
+
if (!expandInitialized || props.defaultExpandAll) initExpandState()
|
|
3266
|
+
},
|
|
3267
|
+
{ deep: true, immediate: true }
|
|
3268
|
+
)
|
|
3269
|
+
|
|
3270
|
+
watch(
|
|
3271
|
+
() => [dragEnabled.value, props.data.length, pagedRows.value.length],
|
|
3272
|
+
() => initSortable()
|
|
3273
|
+
)
|
|
3274
|
+
|
|
3275
|
+
watch(
|
|
3276
|
+
() => [pagedRows.value.length, props.height, props.maxHeight, leafColumns.value.length],
|
|
3277
|
+
() => nextTick(scheduleLayout)
|
|
3278
|
+
)
|
|
3279
|
+
|
|
3280
|
+
onMounted(() => {
|
|
3281
|
+
initSortable()
|
|
3282
|
+
nextTick(() => {
|
|
3283
|
+
scheduleLayout()
|
|
3284
|
+
measureVirtual()
|
|
3285
|
+
updateHasScrollX()
|
|
3286
|
+
})
|
|
3287
|
+
})
|
|
3288
|
+
|
|
3289
|
+
onBeforeUnmount(() => {
|
|
3290
|
+
sortableInst?.destroy()
|
|
3291
|
+
sortableInst = null
|
|
3292
|
+
colSortableInst?.destroy()
|
|
3293
|
+
colSortableInst = null
|
|
3294
|
+
})
|
|
3295
|
+
|
|
3296
|
+
const CellContent = defineComponent({
|
|
3297
|
+
name: 'QiuTableCellContent',
|
|
3298
|
+
props: {
|
|
3299
|
+
row: { type: Object, required: true },
|
|
3300
|
+
col: { type: Object, required: true },
|
|
3301
|
+
index: { type: Number, required: true },
|
|
3302
|
+
},
|
|
3303
|
+
setup(cellProps) {
|
|
3304
|
+
return () => {
|
|
3305
|
+
const row = cellProps.row as any
|
|
3306
|
+
const col = cellProps.col as LeafColumn
|
|
3307
|
+
const index = cellProps.index as number
|
|
3308
|
+
const editor = getColEditor(col)
|
|
3309
|
+
const prop = getProp(col)
|
|
3310
|
+
const cellValue = prop != null ? row[prop] : undefined
|
|
3311
|
+
|
|
3312
|
+
if (hasAlwaysEditor(col) || isEditing(row, col)) {
|
|
3313
|
+
return renderEditor(row, col)
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
const renderName =
|
|
3317
|
+
typeof col.cellRender === 'string' ? col.cellRender : col.cellRender?.name
|
|
3318
|
+
if (renderName && QiuTableRenderer.has(renderName)) {
|
|
3319
|
+
const vnode = QiuTableRenderer.get(renderName)!({
|
|
3320
|
+
cellValue,
|
|
3321
|
+
row,
|
|
3322
|
+
column: col,
|
|
3323
|
+
$index: index,
|
|
3324
|
+
})
|
|
3325
|
+
if (vnode == null) return null
|
|
3326
|
+
if (typeof vnode === 'string') return h('span', { class: 'qiu-table__text' }, vnode)
|
|
3327
|
+
return vnode
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
let text = ''
|
|
3331
|
+
if (editor === 'select' && col.options?.length) {
|
|
3332
|
+
const hit = col.options.find((o) => o.value === cellValue)
|
|
3333
|
+
text = hit?.label ?? formatCell(row, col)
|
|
3334
|
+
} else {
|
|
3335
|
+
text = formatCell(row, col)
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
const tip = resolveTooltipContent(row, col)
|
|
3339
|
+
const useTip =
|
|
3340
|
+
shouldEllipsis(col) &&
|
|
3341
|
+
tip &&
|
|
3342
|
+
!isEditing(row, col) &&
|
|
3343
|
+
props.tooltipConfig?.showAll !== false
|
|
3344
|
+
|
|
3345
|
+
if (useTip) {
|
|
3346
|
+
return h(
|
|
3347
|
+
QiuTooltip,
|
|
3348
|
+
{
|
|
3349
|
+
content: tip,
|
|
3350
|
+
placement: 'top',
|
|
3351
|
+
showAfter: props.tooltipConfig?.enterDelay ?? 200,
|
|
3352
|
+
hideAfter: props.tooltipConfig?.leaveDelay ?? 100,
|
|
3353
|
+
},
|
|
3354
|
+
{
|
|
3355
|
+
default: () => h('span', { class: 'qiu-table__text' }, text),
|
|
3356
|
+
}
|
|
3357
|
+
)
|
|
3358
|
+
}
|
|
3359
|
+
return h('span', { class: 'qiu-table__text' }, text)
|
|
3360
|
+
}
|
|
3361
|
+
},
|
|
3362
|
+
})
|
|
3363
|
+
|
|
3364
|
+
const resolveRowClassName = (row: any, rowIndex: number) => {
|
|
3365
|
+
const fn = props.rowClassName
|
|
3366
|
+
if (!fn) return ''
|
|
3367
|
+
return typeof fn === 'function' ? fn({ row, rowIndex }) : fn
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
const resolveCellClassName = (
|
|
3371
|
+
row: any,
|
|
3372
|
+
column: QiuTableColumnConfig,
|
|
3373
|
+
rowIndex: number,
|
|
3374
|
+
columnIndex: number
|
|
3375
|
+
) => {
|
|
3376
|
+
const fn = props.cellClassName
|
|
3377
|
+
if (!fn) return ''
|
|
3378
|
+
return typeof fn === 'function' ? fn({ row, column, rowIndex, columnIndex }) : fn
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
const setCurrentRow = (row: any | null) => {
|
|
3382
|
+
if (row == null) {
|
|
3383
|
+
currentRowId.value = null
|
|
3384
|
+
emit('current-change', { row: null })
|
|
3385
|
+
return
|
|
3386
|
+
}
|
|
3387
|
+
if (!resolveRowCurrent(props.rowConfig)) return
|
|
3388
|
+
currentRowId.value = getRowId(row)
|
|
3389
|
+
emit('current-change', { row })
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
const clearCurrentRow = () => {
|
|
3393
|
+
currentRowId.value = null
|
|
3394
|
+
clearArea()
|
|
3395
|
+
emit('current-change', { row: null })
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
const clearSort = () => {
|
|
3399
|
+
sortState.value = { prop: '', order: null }
|
|
3400
|
+
sortList.value = []
|
|
3401
|
+
emit('sort-change', { prop: '', order: null, sorts: [] } as any)
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
const clearFilter = (field?: string) => {
|
|
3405
|
+
if (field) {
|
|
3406
|
+
const col = leafColumns.value.find((c) => getProp(c) === field || c._key === field)
|
|
3407
|
+
const key = col?._key
|
|
3408
|
+
if (key) {
|
|
3409
|
+
filterValues[key] = []
|
|
3410
|
+
filterDraft[key] = []
|
|
3411
|
+
}
|
|
3412
|
+
} else {
|
|
3413
|
+
Object.keys(filterValues).forEach((k) => {
|
|
3414
|
+
filterValues[k] = []
|
|
3415
|
+
filterDraft[k] = []
|
|
3416
|
+
})
|
|
3417
|
+
}
|
|
3418
|
+
emit('filter-change', { ...filterValues })
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
const scrollTo = (opts: { top?: number; left?: number }) => {
|
|
3422
|
+
const el = bodyWrapRef.value
|
|
3423
|
+
if (!el) return
|
|
3424
|
+
if (opts.top != null) el.scrollTop = opts.top
|
|
3425
|
+
if (opts.left != null) el.scrollLeft = opts.left
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
const scrollToRow = (row: any) => {
|
|
3429
|
+
const id = getRowId(row)
|
|
3430
|
+
const el = bodyWrapRef.value?.querySelector(
|
|
3431
|
+
`[data-row-key="${String(id).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]`
|
|
3432
|
+
)
|
|
3433
|
+
if (el instanceof HTMLElement) {
|
|
3434
|
+
el.scrollIntoView({ block: 'nearest' })
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
const getColumnWidth = (fieldOrKey: string) => {
|
|
3439
|
+
const col = leafColumns.value.find(
|
|
3440
|
+
(c) => c._key === fieldOrKey || getProp(c) === fieldOrKey
|
|
3441
|
+
)
|
|
3442
|
+
return col ? colWidthOf(col) : undefined
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
const setColumnWidthByField = (fieldOrKey: string, width: number) => {
|
|
3446
|
+
const col = leafColumns.value.find(
|
|
3447
|
+
(c) => c._key === fieldOrKey || getProp(c) === fieldOrKey
|
|
3448
|
+
)
|
|
3449
|
+
if (col) setColumnWidth(col._key, width)
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
const recalculate = () => {
|
|
3453
|
+
layoutTick.value++
|
|
3454
|
+
nextTick(scheduleLayout)
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
defineExpose({
|
|
3458
|
+
tableRef,
|
|
3459
|
+
gridRef: tableRef,
|
|
3460
|
+
getGrid,
|
|
3461
|
+
clearEdit,
|
|
3462
|
+
exportData,
|
|
3463
|
+
getCheckboxRecords,
|
|
3464
|
+
setAllTreeExpand,
|
|
3465
|
+
importData: triggerImport,
|
|
3466
|
+
print: printTable,
|
|
3467
|
+
getRadioRecord: () => {
|
|
3468
|
+
if (!radioRowId.value) return null
|
|
3469
|
+
return findRowById(props.data, radioRowId.value)
|
|
3470
|
+
},
|
|
3471
|
+
clearRadioRow,
|
|
3472
|
+
setRowExpand,
|
|
3473
|
+
clearRowExpand,
|
|
3474
|
+
getUpdateRecords,
|
|
3475
|
+
getInsertRecords,
|
|
3476
|
+
isUpdateByRow,
|
|
3477
|
+
reloadRow,
|
|
3478
|
+
reloadColumn: () => {
|
|
3479
|
+
layoutTick.value++
|
|
3480
|
+
},
|
|
3481
|
+
setCurrentRow,
|
|
3482
|
+
clearCurrentRow,
|
|
3483
|
+
clearSort,
|
|
3484
|
+
clearFilter,
|
|
3485
|
+
scrollTo,
|
|
3486
|
+
scrollToRow,
|
|
3487
|
+
getColumnWidth,
|
|
3488
|
+
setColumnWidth: setColumnWidthByField,
|
|
3489
|
+
recalculate,
|
|
3490
|
+
getColumns: () => leafColumns.value.slice(),
|
|
3491
|
+
undo: undoEdit,
|
|
3492
|
+
redo: redoEdit,
|
|
3493
|
+
clearHistory,
|
|
3494
|
+
canUndo,
|
|
3495
|
+
canRedo,
|
|
3496
|
+
copy: copyArea,
|
|
3497
|
+
cut: cutArea,
|
|
3498
|
+
paste: pasteArea,
|
|
3499
|
+
copyData: copyTableData,
|
|
3500
|
+
})
|
|
3501
|
+
</script>
|
|
3502
|
+
|
|
3503
|
+
<style lang="scss" src="./styles/table.scss"></style>
|