foucui 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/QiuCanvasKit/QiuCanvasEditor.vue.d.ts +3 -1
- package/dist/components/QiuCanvasKit/components/DataModulesPanel.vue.d.ts +1 -0
- package/dist/components/QiuTable/index.vue.d.ts +12 -12
- package/dist/foucui.css +1 -1
- package/dist/foucui.js +7476 -7373
- package/dist/foucui.umd.cjs +19 -19
- package/package.json +23 -13
- package/components/QiuAffix/index.vue +0 -186
- package/components/QiuAlert/index.vue +0 -208
- package/components/QiuAnchor/context.ts +0 -18
- package/components/QiuAnchor/index.vue +0 -180
- package/components/QiuAnchorLink/index.vue +0 -67
- package/components/QiuApp/index.vue +0 -9
- package/components/QiuAutocomplete/index.vue +0 -408
- package/components/QiuAvatar/index.vue +0 -140
- package/components/QiuBacktop/index.vue +0 -121
- package/components/QiuBadge/api/index.ts +0 -389
- package/components/QiuBadge/components/BadgeImagePickerDialog.vue +0 -243
- package/components/QiuBadge/components/BadgeLibraryManagerDialog.vue +0 -30
- package/components/QiuBadge/components/BadgeLibraryPanel.vue +0 -182
- package/components/QiuBadge/composables/useTableHostHeight.ts +0 -53
- package/components/QiuBadge/index.ts +0 -18
- package/components/QiuBadge/shared/BadgeTemplateRenderer.vue +0 -32
- package/components/QiuBadge/shared/assetUrl.ts +0 -26
- package/components/QiuBadge/shared/badgeTemplate.ts +0 -309
- package/components/QiuBadge/utils/chinaAreaData.ts +0 -44
- package/components/QiuBadge/utils/hostRegion.ts +0 -22
- package/components/QiuBadge/utils/pca-code.json +0 -1
- package/components/QiuBadge/utils/pdfFromElement.ts +0 -348
- package/components/QiuBadge/views/BadgeLibraryView.vue +0 -32
- package/components/QiuBadge/views/BadgeManageView.vue +0 -1364
- package/components/QiuBadge/views/BadgeTemplateEditorView.vue +0 -10
- package/components/QiuBadge/views/BadgeTemplateListView.vue +0 -229
- package/components/QiuBadge/views/DeskLabelTemplateListView.vue +0 -227
- package/components/QiuBadge/views/DeskLabelView.vue +0 -683
- package/components/QiuBadgeDot/index.vue +0 -87
- package/components/QiuButton/index.vue +0 -247
- package/components/QiuButtonGroup/index.vue +0 -63
- package/components/QiuCanvasKit/QiuCanvasEditor.vue +0 -5503
- package/components/QiuCanvasKit/QiuCanvasRenderer.vue +0 -348
- package/components/QiuCanvasKit/assetUrl.ts +0 -11
- package/components/QiuCanvasKit/components/DataModulesPanel.vue +0 -352
- package/components/QiuCanvasKit/export/html.ts +0 -132
- package/components/QiuCanvasKit/export/pdf.ts +0 -48
- package/components/QiuCanvasKit/export/png.ts +0 -50
- package/components/QiuCanvasKit/index.ts +0 -20
- package/components/QiuCanvasKit/modules/badgeCompat.ts +0 -84
- package/components/QiuCanvasKit/modules/dataRegistry.ts +0 -207
- package/components/QiuCanvasKit/modules/merge.ts +0 -71
- package/components/QiuCanvasKit/presets/badge.ts +0 -67
- package/components/QiuCanvasKit/presets/banner.ts +0 -79
- package/components/QiuCanvasKit/types.ts +0 -138
- package/components/QiuCard/index.vue +0 -104
- package/components/QiuCarousel/context.ts +0 -16
- package/components/QiuCarousel/index.vue +0 -343
- package/components/QiuCarouselItem/index.vue +0 -47
- package/components/QiuCascader/index.vue +0 -879
- package/components/QiuCascader/types.ts +0 -48
- package/components/QiuCheckbox/QiuCheckboxGroup.vue +0 -78
- package/components/QiuCheckbox/index.vue +0 -231
- package/components/QiuCheckbox/types.ts +0 -8
- package/components/QiuCode/index.vue +0 -64
- package/components/QiuCol/col.scss +0 -55
- package/components/QiuCol/index.vue +0 -64
- package/components/QiuCollapse/context.ts +0 -10
- package/components/QiuCollapse/index.vue +0 -70
- package/components/QiuCollapseItem/index.vue +0 -167
- package/components/QiuColorField/index.vue +0 -296
- package/components/QiuColorPicker/README.md +0 -118
- package/components/QiuColorPicker/index.vue +0 -60
- package/components/QiuColorfulCard/index.vue +0 -101
- package/components/QiuCombobox/index.vue +0 -277
- package/components/QiuConfigProvider/index.vue +0 -31
- package/components/QiuContextMenu/index.vue +0 -209
- package/components/QiuDate/index.vue +0 -1134
- package/components/QiuDescriptions/context.ts +0 -20
- package/components/QiuDescriptions/index.vue +0 -115
- package/components/QiuDescriptionsItem/index.vue +0 -103
- package/components/QiuDialog/index.vue +0 -828
- package/components/QiuDigitalCard/index.vue +0 -159
- package/components/QiuDivider/index.vue +0 -185
- package/components/QiuDot/index.vue +0 -83
- package/components/QiuDrawer/index.vue +0 -350
- package/components/QiuDropdown/QiuDropdownItem.vue +0 -74
- package/components/QiuDropdown/QiuDropdownMenu.vue +0 -22
- package/components/QiuDropdown/context.ts +0 -9
- package/components/QiuDropdown/index.vue +0 -189
- package/components/QiuEmpty/index.vue +0 -73
- package/components/QiuForm/index.vue +0 -172
- package/components/QiuForm/types.ts +0 -39
- package/components/QiuFormItem/index.vue +0 -276
- package/components/QiuFullscreen/index.vue +0 -11
- package/components/QiuGrid/index.vue +0 -408
- package/components/QiuHoverCard/index.vue +0 -192
- package/components/QiuIcon/index.vue +0 -117
- package/components/QiuIconPicker/iconify.libraries.ts +0 -182
- package/components/QiuIconPicker/icons.catalog.ts +0 -3072
- package/components/QiuIconPicker/index.vue +0 -488
- package/components/QiuImage/index.vue +0 -177
- package/components/QiuInput/index.vue +0 -328
- package/components/QiuInputNumber/index.vue +0 -295
- package/components/QiuInputOtp/index.vue +0 -190
- package/components/QiuKbd/index.vue +0 -57
- package/components/QiuLayoutContainer/index.vue +0 -50
- package/components/QiuLinkPreview/index.vue +0 -224
- package/components/QiuLoading/index.ts +0 -176
- package/components/QiuLoading/spinner.vue +0 -820
- package/components/QiuLoading/types.ts +0 -44
- package/components/QiuMention/index.vue +0 -299
- package/components/QiuMenu/QiuMenuRoot.vue +0 -241
- package/components/QiuMenu/components/QiuMenuItem.vue +0 -340
- package/components/QiuMenu/components/QiuSubMenu.vue +0 -504
- package/components/QiuMenu/context.ts +0 -21
- package/components/QiuMenu/index.vue +0 -43
- package/components/QiuMenuNav/QiuMenuNavItem.vue +0 -100
- package/components/QiuMenuNav/QiuMenuNavSub.vue +0 -120
- package/components/QiuMenuNav/context.ts +0 -14
- package/components/QiuMenuNav/index.vue +0 -148
- package/components/QiuMsg/QiuMsgItem.vue +0 -180
- package/components/QiuMsg/index.ts +0 -158
- package/components/QiuMsg/types.ts +0 -23
- package/components/QiuMsgBox/index.ts +0 -147
- package/components/QiuNotification/QiuNotificationItem.vue +0 -168
- package/components/QiuNotification/index.ts +0 -155
- package/components/QiuNotification/types.ts +0 -14
- package/components/QiuPage/index.vue +0 -12
- package/components/QiuPageHeader/index.vue +0 -115
- package/components/QiuPageMain/index.vue +0 -78
- package/components/QiuPagination/index.vue +0 -421
- package/components/QiuPasswordStrength/index.vue +0 -101
- package/components/QiuPopconfirm/index.vue +0 -196
- package/components/QiuPopover/index.vue +0 -277
- package/components/QiuProgress/index.vue +0 -235
- package/components/QiuQrcode/index.vue +0 -302
- package/components/QiuQueryForm/components/QiuQueryFormBottomPanel.vue +0 -13
- package/components/QiuQueryForm/components/QiuQueryFormLeftPanel.vue +0 -37
- package/components/QiuQueryForm/components/QiuQueryFormRightPanel.vue +0 -37
- package/components/QiuQueryForm/components/QiuQueryFormTopPanel.vue +0 -13
- package/components/QiuQueryForm/index.vue +0 -117
- package/components/QiuRadio/QiuRadioGroup.vue +0 -75
- package/components/QiuRadio/index.vue +0 -191
- package/components/QiuRadio/types.ts +0 -9
- package/components/QiuRate/index.vue +0 -184
- package/components/QiuRefresh/index.vue +0 -32
- package/components/QiuResult/index.vue +0 -103
- package/components/QiuRow/index.vue +0 -60
- package/components/QiuRow/types.ts +0 -5
- package/components/QiuScrollbar/index.vue +0 -107
- package/components/QiuSearchBar/index.vue +0 -154
- package/components/QiuSegmented/index.vue +0 -120
- package/components/QiuSelect/QiuOption.vue +0 -93
- package/components/QiuSelect/index.vue +0 -452
- package/components/QiuSelect/types.ts +0 -15
- package/components/QiuSkeleton/index.vue +0 -78
- package/components/QiuSlider/index.vue +0 -266
- package/components/QiuSpace/index.vue +0 -95
- package/components/QiuSparkline/index.vue +0 -92
- package/components/QiuStatistic/index.vue +0 -4
- package/components/QiuStatistics/index.vue +0 -123
- package/components/QiuStep/index.vue +0 -220
- package/components/QiuSteps/context.ts +0 -25
- package/components/QiuSteps/index.vue +0 -84
- package/components/QiuSwitch/index.vue +0 -145
- package/components/QiuTabBar/index.vue +0 -218
- package/components/QiuTabBar/types.ts +0 -19
- package/components/QiuTabPane/index.vue +0 -82
- package/components/QiuTable/columns.ts +0 -16
- package/components/QiuTable/export.ts +0 -102
- package/components/QiuTable/index.ts +0 -24
- package/components/QiuTable/index.vue +0 -3503
- package/components/QiuTable/registry.ts +0 -75
- package/components/QiuTable/styles/table.scss +0 -1230
- package/components/QiuTable/types.ts +0 -413
- package/components/QiuTable/useArea.ts +0 -202
- package/components/QiuTable/useColumns.ts +0 -325
- package/components/QiuTable/useEdit.ts +0 -136
- package/components/QiuTable/useExtras.ts +0 -115
- package/components/QiuTable/useHistory.ts +0 -77
- package/components/QiuTable/useMerge.ts +0 -105
- package/components/QiuTable/useResize.ts +0 -86
- package/components/QiuTable/useScrollSync.ts +0 -152
- package/components/QiuTable/useVirtual.ts +0 -97
- package/components/QiuTable/utils.ts +0 -165
- package/components/QiuTableColumn/index.vue +0 -101
- package/components/QiuTag/index.vue +0 -143
- package/components/QiuText/index.vue +0 -99
- package/components/QiuTime/index.vue +0 -548
- package/components/QiuTimeAgo/index.vue +0 -103
- package/components/QiuTimeline/index.vue +0 -54
- package/components/QiuTimelineItem/index.vue +0 -140
- package/components/QiuTips/index.vue +0 -323
- package/components/QiuTooltip/index.vue +0 -237
- package/components/QiuTransfer/index.vue +0 -277
- package/components/QiuTree/QiuTreeNode.vue +0 -146
- package/components/QiuTree/index.vue +0 -143
- package/components/QiuTree/types.ts +0 -30
- package/components/QiuTreeSelect/index.vue +0 -259
- package/components/QiuTrend/index.vue +0 -92
- package/components/QiuUpload/index.vue +0 -628
- package/components/QiuUploadCrop/index.vue +0 -1267
- package/components/QiuWatermark/index.vue +0 -121
- package/components/useBodyScrollLock.ts +0 -28
- package/dist/components/QiuBadge/utils/pca-code.json.d.ts +0 -3
- package/plugins/qiu.ts +0 -83
- package/plugins/shortcuts.ts +0 -69
- package/src/bridge/@/api/media.ts +0 -8
- package/src/bridge/@/api/notice.ts +0 -5
- package/src/bridge/@/config/index.ts +0 -38
- package/src/bridge/@/config/media.config.ts +0 -37
- package/src/bridge/@/config/security.config.ts +0 -8
- package/src/bridge/@/i18n/index.ts +0 -6
- package/src/bridge/@/store/modules/bing.ts +0 -9
- package/src/bridge/@/store/modules/errorLog.ts +0 -9
- package/src/bridge/@/store/modules/media.ts +0 -10
- package/src/bridge/@/store/modules/routes.ts +0 -10
- package/src/bridge/@/store/modules/settings.ts +0 -9
- package/src/bridge/@/store/modules/tabs.ts +0 -9
- package/src/bridge/@/store/modules/user.ts +0 -9
- package/src/bridge/@/utils/convertToCamelCase.ts +0 -11
- package/src/bridge/@/utils/index.ts +0 -9
- package/src/bridge/@/utils/pageTitle.ts +0 -4
- package/src/bridge/@/utils/routes.ts +0 -14
- package/src/bridge/@/utils/validate.ts +0 -17
- package/src/bridge/@qiu/plugins/shortcuts.ts +0 -3
- package/src/i18n.ts +0 -8
- package/src/icons/qiu-logo.svg +0 -1
- package/styles/dark.scss +0 -194
- package/styles/normalize.css +0 -349
- package/styles/plain.scss +0 -423
- package/styles/qiu-tokens.scss +0 -40
- package/styles/qiu.scss +0 -1149
- package/styles/technology.scss +0 -452
- package/styles/var.scss +0 -382
- package/styles/variables.scss +0 -77
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import type { QiuTableColumnConfig, QiuTableHeaderCell } from './types'
|
|
2
|
-
import { QIU_SPECIAL_COL_TYPES } from './types'
|
|
3
|
-
|
|
4
|
-
export type LeafColumn = QiuTableColumnConfig & {
|
|
5
|
-
_key: string
|
|
6
|
-
_defaultSlot?: (scope: any) => any
|
|
7
|
-
_headerSlot?: (scope: any) => any
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const normalizeSize = (v: string | number) => (typeof v === 'number' ? `${v}px` : String(v))
|
|
11
|
-
|
|
12
|
-
/** 解析列宽为像素;百分比需传入 containerWidth */
|
|
13
|
-
export function parseWidth(w?: string | number, containerWidth = 0) {
|
|
14
|
-
if (w == null || w === '') return 0
|
|
15
|
-
if (typeof w === 'number') return Number.isFinite(w) ? w : 0
|
|
16
|
-
const s = String(w).trim()
|
|
17
|
-
if (!s || s === 'auto') return 0
|
|
18
|
-
if (s.endsWith('%')) {
|
|
19
|
-
const pct = parseFloat(s)
|
|
20
|
-
if (!Number.isFinite(pct) || containerWidth <= 0) return 0
|
|
21
|
-
return (containerWidth * pct) / 100
|
|
22
|
-
}
|
|
23
|
-
return parseFloat(s) || 0
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function colStyle(col: LeafColumn) {
|
|
27
|
-
const style: Record<string, string> = {}
|
|
28
|
-
if (col.width != null) style.width = normalizeSize(col.width)
|
|
29
|
-
if (col.minWidth != null) style.minWidth = normalizeSize(col.minWidth)
|
|
30
|
-
if (col.maxWidth != null) style.maxWidth = normalizeSize(col.maxWidth)
|
|
31
|
-
return style
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type ColumnSizeHints = {
|
|
35
|
-
/** 显式 width(固定列宽) */
|
|
36
|
-
fixedWidth: number
|
|
37
|
-
minWidth: number
|
|
38
|
-
maxWidth: number
|
|
39
|
-
/** 无 width,仅靠 minWidth 参与剩余空间分配 */
|
|
40
|
-
isFlex: boolean
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getColumnSizeHints(
|
|
44
|
-
col: LeafColumn,
|
|
45
|
-
containerWidth = 0,
|
|
46
|
-
defaults?: { minWidth?: number; maxWidth?: number }
|
|
47
|
-
): ColumnSizeHints {
|
|
48
|
-
const defaultMin = defaults?.minWidth ?? 80
|
|
49
|
-
const defaultMax = defaults?.maxWidth ?? Number.POSITIVE_INFINITY
|
|
50
|
-
const fixedWidth = parseWidth(col.width, containerWidth)
|
|
51
|
-
const minWidth = Math.max(
|
|
52
|
-
parseWidth(col.minWidth, containerWidth) || (fixedWidth ? 0 : defaultMin),
|
|
53
|
-
48
|
|
54
|
-
)
|
|
55
|
-
const maxRaw = parseWidth(col.maxWidth, containerWidth)
|
|
56
|
-
const maxWidth = maxRaw > 0 ? maxRaw : defaultMax
|
|
57
|
-
return {
|
|
58
|
-
fixedWidth,
|
|
59
|
-
minWidth: fixedWidth ? Math.min(minWidth || fixedWidth, fixedWidth) || 48 : minWidth,
|
|
60
|
-
maxWidth: Math.max(maxWidth, minWidth || 48),
|
|
61
|
-
isFlex: fixedWidth <= 0,
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 计算每列最终渲染宽度(对齐 vxe:width 固定;仅 minWidth 的列吃剩余空间)
|
|
67
|
-
* resized 优先
|
|
68
|
-
*/
|
|
69
|
-
export function computeRenderWidths(
|
|
70
|
-
leafColumns: LeafColumn[],
|
|
71
|
-
opts: {
|
|
72
|
-
containerWidth: number
|
|
73
|
-
showSelection: boolean
|
|
74
|
-
showIndex: boolean
|
|
75
|
-
showRadio?: boolean
|
|
76
|
-
showExpand?: boolean
|
|
77
|
-
fit?: boolean
|
|
78
|
-
resized?: Record<string, number>
|
|
79
|
-
defaults?: { minWidth?: number; maxWidth?: number }
|
|
80
|
-
}
|
|
81
|
-
): Record<string, number> {
|
|
82
|
-
const {
|
|
83
|
-
containerWidth,
|
|
84
|
-
showSelection,
|
|
85
|
-
showIndex,
|
|
86
|
-
showRadio,
|
|
87
|
-
showExpand,
|
|
88
|
-
fit = true,
|
|
89
|
-
resized = {},
|
|
90
|
-
defaults,
|
|
91
|
-
} = opts
|
|
92
|
-
|
|
93
|
-
let special = 0
|
|
94
|
-
if (showExpand) special += 48
|
|
95
|
-
if (showRadio) special += 48
|
|
96
|
-
if (showSelection) special += 48
|
|
97
|
-
if (showIndex) special += 55
|
|
98
|
-
|
|
99
|
-
const result: Record<string, number> = {}
|
|
100
|
-
const flexKeys: string[] = []
|
|
101
|
-
let fixedSum = 0
|
|
102
|
-
|
|
103
|
-
for (const col of leafColumns) {
|
|
104
|
-
const resizedW = resized[col._key]
|
|
105
|
-
if (resizedW != null && resizedW > 0) {
|
|
106
|
-
result[col._key] = resizedW
|
|
107
|
-
fixedSum += resizedW
|
|
108
|
-
continue
|
|
109
|
-
}
|
|
110
|
-
const hints = getColumnSizeHints(col, containerWidth, defaults)
|
|
111
|
-
if (!hints.isFlex) {
|
|
112
|
-
const w = Math.min(Math.max(hints.fixedWidth, hints.minWidth), hints.maxWidth)
|
|
113
|
-
result[col._key] = w
|
|
114
|
-
fixedSum += w
|
|
115
|
-
} else {
|
|
116
|
-
flexKeys.push(col._key)
|
|
117
|
-
// 先占位 min,后面再分配剩余
|
|
118
|
-
result[col._key] = hints.minWidth
|
|
119
|
-
fixedSum += hints.minWidth
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (fit && flexKeys.length && containerWidth > 0) {
|
|
124
|
-
let remain = containerWidth - special - fixedSum
|
|
125
|
-
if (remain > 0) {
|
|
126
|
-
// 按 minWidth 比例分配剩余
|
|
127
|
-
const mins = flexKeys.map((k) => {
|
|
128
|
-
const col = leafColumns.find((c) => c._key === k)!
|
|
129
|
-
return getColumnSizeHints(col, containerWidth, defaults).minWidth
|
|
130
|
-
})
|
|
131
|
-
const minSum = mins.reduce((a, b) => a + b, 0) || flexKeys.length
|
|
132
|
-
let used = 0
|
|
133
|
-
flexKeys.forEach((k, i) => {
|
|
134
|
-
const col = leafColumns.find((c) => c._key === k)!
|
|
135
|
-
const hints = getColumnSizeHints(col, containerWidth, defaults)
|
|
136
|
-
const share =
|
|
137
|
-
i === flexKeys.length - 1
|
|
138
|
-
? remain - used
|
|
139
|
-
: Math.floor((remain * mins[i]) / minSum)
|
|
140
|
-
used += share
|
|
141
|
-
const next = Math.min(Math.max(hints.minWidth + share, hints.minWidth), hints.maxWidth)
|
|
142
|
-
// 之前已计入 minWidth,这里补差值
|
|
143
|
-
const delta = next - hints.minWidth
|
|
144
|
-
result[k] = next
|
|
145
|
-
// remain 已按 share 消费;fixedSum 不需再改
|
|
146
|
-
void delta
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return result
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function computeTableMinWidth(
|
|
155
|
-
leafColumns: LeafColumn[],
|
|
156
|
-
opts: {
|
|
157
|
-
showSelection: boolean
|
|
158
|
-
showIndex: boolean
|
|
159
|
-
showRadio?: boolean
|
|
160
|
-
showExpand?: boolean
|
|
161
|
-
},
|
|
162
|
-
/** 优先用最终渲染宽 / 拖拽宽 */
|
|
163
|
-
resolveWidth?: (col: LeafColumn) => number | undefined,
|
|
164
|
-
defaults?: { minWidth?: number; maxWidth?: number }
|
|
165
|
-
) {
|
|
166
|
-
let min = 0
|
|
167
|
-
if (opts.showExpand) min += 48
|
|
168
|
-
if (opts.showRadio) min += 48
|
|
169
|
-
if (opts.showSelection) min += 48
|
|
170
|
-
if (opts.showIndex) min += 55
|
|
171
|
-
for (const col of leafColumns) {
|
|
172
|
-
const resolved = resolveWidth?.(col)
|
|
173
|
-
if (resolved != null && resolved > 0) {
|
|
174
|
-
min += resolved
|
|
175
|
-
continue
|
|
176
|
-
}
|
|
177
|
-
const hints = getColumnSizeHints(col, 0, defaults)
|
|
178
|
-
min += hints.isFlex ? hints.minWidth : hints.fixedWidth || hints.minWidth || 100
|
|
179
|
-
}
|
|
180
|
-
return min
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function leftOffsetForColumn(
|
|
184
|
-
leafColumns: LeafColumn[],
|
|
185
|
-
colKey: string,
|
|
186
|
-
opts: {
|
|
187
|
-
showSelection: boolean
|
|
188
|
-
showIndex: boolean
|
|
189
|
-
showRadio?: boolean
|
|
190
|
-
showExpand?: boolean
|
|
191
|
-
},
|
|
192
|
-
resolveWidth?: (col: LeafColumn) => number
|
|
193
|
-
) {
|
|
194
|
-
let left = 0
|
|
195
|
-
if (opts.showExpand) left += 48
|
|
196
|
-
if (opts.showRadio) left += 48
|
|
197
|
-
if (opts.showSelection) left += 48
|
|
198
|
-
if (opts.showIndex) left += 55
|
|
199
|
-
for (const col of leafColumns) {
|
|
200
|
-
if (col._key === colKey) break
|
|
201
|
-
if (col.fixed === 'left' || col.fixed === true) {
|
|
202
|
-
left +=
|
|
203
|
-
resolveWidth?.(col) ||
|
|
204
|
-
parseWidth(col.width) ||
|
|
205
|
-
parseWidth(col.minWidth) ||
|
|
206
|
-
120
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return left
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function rightOffsetForColumn(
|
|
213
|
-
leafColumns: LeafColumn[],
|
|
214
|
-
colKey: string,
|
|
215
|
-
resolveWidth?: (col: LeafColumn) => number
|
|
216
|
-
) {
|
|
217
|
-
let right = 0
|
|
218
|
-
for (let i = leafColumns.length - 1; i >= 0; i--) {
|
|
219
|
-
const col = leafColumns[i]
|
|
220
|
-
if (col._key === colKey) break
|
|
221
|
-
if (col.fixed === 'right') {
|
|
222
|
-
right +=
|
|
223
|
-
resolveWidth?.(col) ||
|
|
224
|
-
parseWidth(col.width) ||
|
|
225
|
-
parseWidth(col.minWidth) ||
|
|
226
|
-
120
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return right
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** 是否左侧固定列中的最后一列(用于阴影) */
|
|
233
|
-
export function isLastFixedLeft(leafColumns: LeafColumn[], colKey: string) {
|
|
234
|
-
const lefts = leafColumns.filter((c) => c.fixed === 'left' || c.fixed === true)
|
|
235
|
-
return lefts.length > 0 && lefts[lefts.length - 1]._key === colKey
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/** 是否右侧固定列中的第一列(从右往左的最内侧,用于阴影) */
|
|
239
|
-
export function isFirstFixedRight(leafColumns: LeafColumn[], colKey: string) {
|
|
240
|
-
const rights = leafColumns.filter((c) => c.fixed === 'right')
|
|
241
|
-
return rights.length > 0 && rights[0]._key === colKey
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** 生成稳定且唯一的列 key(同 prop 多列也不会冲突) */
|
|
245
|
-
export function makeColumnKey(
|
|
246
|
-
col: QiuTableColumnConfig,
|
|
247
|
-
index: number,
|
|
248
|
-
prefix = ''
|
|
249
|
-
): string {
|
|
250
|
-
const keyBase = col.prop || col.field || col.slot || col.type || 'col'
|
|
251
|
-
return prefix ? `${prefix}.${keyBase}#${index}` : `${keyBase}#${index}`
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export { makeColumnKey as createColumnKey }
|
|
255
|
-
|
|
256
|
-
/** 扁平化叶子列(跳过仅分组的父列) */
|
|
257
|
-
export function flattenLeafColumns(
|
|
258
|
-
columns: QiuTableColumnConfig[],
|
|
259
|
-
prefix = ''
|
|
260
|
-
): LeafColumn[] {
|
|
261
|
-
const out: LeafColumn[] = []
|
|
262
|
-
columns.forEach((col, i) => {
|
|
263
|
-
const key = makeColumnKey(col, i, prefix)
|
|
264
|
-
if (col.children?.length) {
|
|
265
|
-
out.push(...flattenLeafColumns(col.children, key))
|
|
266
|
-
return
|
|
267
|
-
}
|
|
268
|
-
if (col.type && QIU_SPECIAL_COL_TYPES.has(col.type) && !col.prop && !col.field && !col.editor) {
|
|
269
|
-
// 特殊列仍作为叶子
|
|
270
|
-
}
|
|
271
|
-
out.push({ ...col, _key: key })
|
|
272
|
-
})
|
|
273
|
-
return out
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function depthOf(col: QiuTableColumnConfig): number {
|
|
277
|
-
if (!col.children?.length) return 1
|
|
278
|
-
return 1 + Math.max(...col.children.map(depthOf))
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function leafCount(col: QiuTableColumnConfig): number {
|
|
282
|
-
if (!col.children?.length) return 1
|
|
283
|
-
return col.children.reduce((n, c) => n + leafCount(c), 0)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/** 生成多级表头矩阵(每行一组 th) */
|
|
287
|
-
export function buildHeaderRows(
|
|
288
|
-
columns: QiuTableColumnConfig[],
|
|
289
|
-
keyPrefix = ''
|
|
290
|
-
): QiuTableHeaderCell[][] {
|
|
291
|
-
if (!columns.length) return [[]]
|
|
292
|
-
const maxDepth = Math.max(...columns.map(depthOf), 1)
|
|
293
|
-
const rows: QiuTableHeaderCell[][] = Array.from({ length: maxDepth }, () => [])
|
|
294
|
-
|
|
295
|
-
const walk = (
|
|
296
|
-
cols: QiuTableColumnConfig[],
|
|
297
|
-
depth: number,
|
|
298
|
-
startCol: number,
|
|
299
|
-
prefix: string
|
|
300
|
-
): number => {
|
|
301
|
-
let cursor = startCol
|
|
302
|
-
cols.forEach((col, i) => {
|
|
303
|
-
const key = makeColumnKey(col, i, prefix)
|
|
304
|
-
const leaves = leafCount(col)
|
|
305
|
-
const isLeaf = !col.children?.length
|
|
306
|
-
const rowspan = isLeaf ? maxDepth - depth : 1
|
|
307
|
-
rows[depth].push({
|
|
308
|
-
column: { ...col, _key: key },
|
|
309
|
-
label: col.label || col.title || '',
|
|
310
|
-
colspan: leaves,
|
|
311
|
-
rowspan,
|
|
312
|
-
isLeaf,
|
|
313
|
-
colIndex: cursor,
|
|
314
|
-
})
|
|
315
|
-
if (!isLeaf && col.children) {
|
|
316
|
-
walk(col.children, depth + 1, cursor, key)
|
|
317
|
-
}
|
|
318
|
-
cursor += leaves
|
|
319
|
-
})
|
|
320
|
-
return cursor
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
walk(columns, 0, 0, keyPrefix)
|
|
324
|
-
return rows
|
|
325
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { nextTick, ref, type Ref } from 'vue'
|
|
2
|
-
import type { QiuTableColumnConfig, QiuTableEditConfig } from './types'
|
|
3
|
-
import { resolveColumnEditor, resolveColumnProp, resolveEditRender } from './types'
|
|
4
|
-
|
|
5
|
-
export function useTableEdit(opts: {
|
|
6
|
-
editable: Ref<boolean> | (() => boolean)
|
|
7
|
-
editTrigger: Ref<string> | (() => string)
|
|
8
|
-
editConfig: Ref<QiuTableEditConfig | undefined> | (() => QiuTableEditConfig | undefined)
|
|
9
|
-
getRowId: (row: any) => string
|
|
10
|
-
emit: (event: string, payload: any) => void
|
|
11
|
-
}) {
|
|
12
|
-
const editing = ref<{ rowId: string; prop: string } | null>(null)
|
|
13
|
-
/** 刚进入编辑时忽略 blur,避免挂载/聚焦过程中立刻提交造成闪一下 */
|
|
14
|
-
let editGuardUntil = 0
|
|
15
|
-
const inEditGuard = () => Date.now() < editGuardUntil
|
|
16
|
-
|
|
17
|
-
const getEditable = () =>
|
|
18
|
-
typeof opts.editable === 'function' ? opts.editable() : opts.editable.value
|
|
19
|
-
|
|
20
|
-
const getCfg = () =>
|
|
21
|
-
typeof opts.editConfig === 'function' ? opts.editConfig() : opts.editConfig.value
|
|
22
|
-
|
|
23
|
-
const getTrigger = () => {
|
|
24
|
-
const cfg = getCfg()
|
|
25
|
-
if (cfg?.trigger) return cfg.trigger
|
|
26
|
-
return typeof opts.editTrigger === 'function' ? opts.editTrigger() : opts.editTrigger.value
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const getMode = () => getCfg()?.mode || 'cell'
|
|
30
|
-
const getAutoClear = () => getCfg()?.autoClear !== false
|
|
31
|
-
const getShowIcon = () => {
|
|
32
|
-
const cfg = getCfg()
|
|
33
|
-
// 可编辑表默认显示表头编辑图标(文字前);显式 false 才关闭
|
|
34
|
-
if (cfg?.showIcon === false) return false
|
|
35
|
-
if (cfg?.showIcon === true) return true
|
|
36
|
-
return getEditable()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const getColEditor = (col: QiuTableColumnConfig) => resolveColumnEditor(col)
|
|
40
|
-
const getEditRender = (col: QiuTableColumnConfig) => resolveEditRender(col)
|
|
41
|
-
const getProp = (col: QiuTableColumnConfig) => resolveColumnProp(col)
|
|
42
|
-
|
|
43
|
-
const canEditCol = (col: QiuTableColumnConfig) => {
|
|
44
|
-
if (!getEditable()) return false
|
|
45
|
-
const er = getEditRender(col)
|
|
46
|
-
if (er?.enabled === false) return false
|
|
47
|
-
return !!getColEditor(col) && col.editable !== false && !!getProp(col)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const hasAlwaysEditor = (col: QiuTableColumnConfig) => {
|
|
51
|
-
if (!canEditCol(col)) return false
|
|
52
|
-
if (getTrigger() === 'always') return true
|
|
53
|
-
return getColEditor(col) === 'switch'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const isInputEditor = (col: QiuTableColumnConfig) => getColEditor(col) === 'input'
|
|
57
|
-
|
|
58
|
-
const isEditing = (row: any, col: QiuTableColumnConfig) => {
|
|
59
|
-
if (!canEditCol(col)) return false
|
|
60
|
-
if (hasAlwaysEditor(col)) return true
|
|
61
|
-
if (!editing.value) return false
|
|
62
|
-
const sameRow = editing.value.rowId === opts.getRowId(row)
|
|
63
|
-
if (!sameRow) return false
|
|
64
|
-
if (getMode() === 'row') return true
|
|
65
|
-
return editing.value.prop === getProp(col)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const isInputFill = (row: any, col: QiuTableColumnConfig) =>
|
|
69
|
-
isEditing(row, col) && isInputEditor(col)
|
|
70
|
-
|
|
71
|
-
const startEdit = (row: any, col: QiuTableColumnConfig, focusRoot?: HTMLElement | null) => {
|
|
72
|
-
if (!canEditCol(col) || hasAlwaysEditor(col)) return
|
|
73
|
-
const prop = getProp(col)!
|
|
74
|
-
const rowId = opts.getRowId(row)
|
|
75
|
-
// 同一单元格重复进入时不要重挂载,避免闪烁
|
|
76
|
-
if (editing.value?.rowId === rowId && editing.value?.prop === prop) return
|
|
77
|
-
editGuardUntil = Date.now() + 120
|
|
78
|
-
editing.value = { rowId, prop }
|
|
79
|
-
opts.emit('cell-edit-start', { row, column: col })
|
|
80
|
-
nextTick(() => {
|
|
81
|
-
const root = focusRoot
|
|
82
|
-
const active =
|
|
83
|
-
(root?.querySelector(
|
|
84
|
-
`.qiu-table__cell.is-editing .qiu-input__inner`
|
|
85
|
-
) as HTMLInputElement | null) ||
|
|
86
|
-
(root?.querySelector(`.qiu-table__cell.is-editing input`) as HTMLInputElement | null) ||
|
|
87
|
-
(root?.querySelector(
|
|
88
|
-
`.qiu-table__cell.is-editing .qiu-table__date-input`
|
|
89
|
-
) as HTMLInputElement | null)
|
|
90
|
-
active?.focus?.({ preventScroll: true })
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const clearEdit = () => {
|
|
95
|
-
editing.value = null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const commitEdit = (row: any, col: QiuTableColumnConfig, value: any) => {
|
|
99
|
-
if (inEditGuard()) return
|
|
100
|
-
const prop = getProp(col)
|
|
101
|
-
if (prop != null && row[prop] !== value) row[prop] = value
|
|
102
|
-
// row 模式:blur 不立刻清整行,交给 autoClear / 切行
|
|
103
|
-
if (getMode() === 'cell') {
|
|
104
|
-
if (
|
|
105
|
-
!hasAlwaysEditor(col) &&
|
|
106
|
-
editing.value?.rowId === opts.getRowId(row) &&
|
|
107
|
-
editing.value?.prop === prop
|
|
108
|
-
) {
|
|
109
|
-
editing.value = null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
opts.emit('cell-edit-end', { row, column: col, value })
|
|
113
|
-
opts.emit('edit', { row, column: col, value })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
editing,
|
|
118
|
-
getColEditor,
|
|
119
|
-
getEditRender,
|
|
120
|
-
getProp,
|
|
121
|
-
canEditCol,
|
|
122
|
-
hasAlwaysEditor,
|
|
123
|
-
isInputEditor,
|
|
124
|
-
isEditing,
|
|
125
|
-
isInputFill,
|
|
126
|
-
startEdit,
|
|
127
|
-
clearEdit,
|
|
128
|
-
commitEdit,
|
|
129
|
-
inEditGuard,
|
|
130
|
-
getTrigger,
|
|
131
|
-
getMode,
|
|
132
|
-
getAutoClear,
|
|
133
|
-
getShowIcon,
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { reactive, ref, type Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/** 行展开(非树) */
|
|
4
|
-
export function useExpandRows(opts: { getRowId: (row: any) => string }) {
|
|
5
|
-
const expanded = ref<Set<string>>(new Set())
|
|
6
|
-
|
|
7
|
-
const isRowExpand = (row: any) => expanded.value.has(opts.getRowId(row))
|
|
8
|
-
|
|
9
|
-
const toggleRowExpand = (row: any) => {
|
|
10
|
-
const id = opts.getRowId(row)
|
|
11
|
-
const next = new Set(expanded.value)
|
|
12
|
-
if (next.has(id)) next.delete(id)
|
|
13
|
-
else next.add(id)
|
|
14
|
-
expanded.value = next
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const setRowExpand = (rows: any[], checked: boolean) => {
|
|
18
|
-
const next = new Set(expanded.value)
|
|
19
|
-
rows.forEach((r) => {
|
|
20
|
-
const id = opts.getRowId(r)
|
|
21
|
-
if (checked) next.add(id)
|
|
22
|
-
else next.delete(id)
|
|
23
|
-
})
|
|
24
|
-
expanded.value = next
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const clearRowExpand = () => {
|
|
28
|
-
expanded.value = new Set()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return { expanded, isRowExpand, toggleRowExpand, setRowExpand, clearRowExpand }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** 单选 radio */
|
|
35
|
-
export function useRadioRow(opts: { getRowId: (row: any) => string }) {
|
|
36
|
-
const radioRowId = ref<string | null>(null)
|
|
37
|
-
|
|
38
|
-
const isRadioChecked = (row: any) => radioRowId.value === opts.getRowId(row)
|
|
39
|
-
|
|
40
|
-
const setRadioRow = (row: any | null) => {
|
|
41
|
-
radioRowId.value = row ? opts.getRowId(row) : null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const clearRadioRow = () => {
|
|
45
|
-
radioRowId.value = null
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return { radioRowId, isRadioChecked, setRadioRow, clearRadioRow }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** keepSource 脏数据追踪 */
|
|
52
|
-
export function useKeepSource(opts: {
|
|
53
|
-
enabled: () => boolean
|
|
54
|
-
getRowId: (row: any) => string
|
|
55
|
-
data: Ref<any[]> | (() => any[])
|
|
56
|
-
}) {
|
|
57
|
-
const sourceMap = reactive<Record<string, string>>({})
|
|
58
|
-
|
|
59
|
-
const getData = () => (typeof opts.data === 'function' ? opts.data() : opts.data.value)
|
|
60
|
-
|
|
61
|
-
const snapshot = () => {
|
|
62
|
-
if (!opts.enabled()) return
|
|
63
|
-
Object.keys(sourceMap).forEach((k) => delete sourceMap[k])
|
|
64
|
-
for (const row of getData() || []) {
|
|
65
|
-
const id = opts.getRowId(row)
|
|
66
|
-
if (id) sourceMap[id] = JSON.stringify(row)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const isUpdateByRow = (row: any) => {
|
|
71
|
-
if (!opts.enabled()) return false
|
|
72
|
-
const id = opts.getRowId(row)
|
|
73
|
-
const src = sourceMap[id]
|
|
74
|
-
if (src == null) return false
|
|
75
|
-
return src !== JSON.stringify(row)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const getUpdateRecords = () => {
|
|
79
|
-
if (!opts.enabled()) return []
|
|
80
|
-
return (getData() || []).filter((r) => isUpdateByRow(r))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const getInsertRecords = () => {
|
|
84
|
-
if (!opts.enabled()) return []
|
|
85
|
-
return (getData() || []).filter((r) => {
|
|
86
|
-
const id = opts.getRowId(r)
|
|
87
|
-
return id && sourceMap[id] == null
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const reloadRow = (row: any) => {
|
|
92
|
-
const id = opts.getRowId(row)
|
|
93
|
-
const src = sourceMap[id]
|
|
94
|
-
if (!src) return
|
|
95
|
-
Object.assign(row, JSON.parse(src))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return { snapshot, isUpdateByRow, getUpdateRecords, getInsertRecords, reloadRow, sourceMap }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export type SortItem = { prop: string; order: 'asc' | 'desc' }
|
|
102
|
-
|
|
103
|
-
export function compareRows(a: any, b: any, sorts: SortItem[]) {
|
|
104
|
-
for (const s of sorts) {
|
|
105
|
-
if (!s.order) continue
|
|
106
|
-
const av = a[s.prop]
|
|
107
|
-
const bv = b[s.prop]
|
|
108
|
-
if (av === bv) continue
|
|
109
|
-
if (av == null) return 1
|
|
110
|
-
if (bv == null) return -1
|
|
111
|
-
const cmp = av > bv ? 1 : -1
|
|
112
|
-
return s.order === 'asc' ? cmp : -cmp
|
|
113
|
-
}
|
|
114
|
-
return 0
|
|
115
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
export type QiuTableHistoryPatch = {
|
|
4
|
-
rowId: string
|
|
5
|
-
prop: string
|
|
6
|
-
oldValue: any
|
|
7
|
-
newValue: any
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 单元格编辑 undo/redo 栈
|
|
12
|
-
*/
|
|
13
|
-
export function useTableHistory(opts: {
|
|
14
|
-
enabled: () => boolean
|
|
15
|
-
size?: () => number
|
|
16
|
-
findRow: (rowId: string) => any | null
|
|
17
|
-
onApply?: (patch: QiuTableHistoryPatch, direction: 'undo' | 'redo') => void
|
|
18
|
-
}) {
|
|
19
|
-
const undoStack = ref<QiuTableHistoryPatch[]>([])
|
|
20
|
-
const redoStack = ref<QiuTableHistoryPatch[]>([])
|
|
21
|
-
|
|
22
|
-
const maxSize = () => Math.max(1, opts.size?.() ?? 50)
|
|
23
|
-
|
|
24
|
-
const push = (patch: QiuTableHistoryPatch) => {
|
|
25
|
-
if (!opts.enabled()) return
|
|
26
|
-
if (patch.oldValue === patch.newValue) return
|
|
27
|
-
undoStack.value.push(patch)
|
|
28
|
-
if (undoStack.value.length > maxSize()) undoStack.value.shift()
|
|
29
|
-
redoStack.value = []
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const apply = (patch: QiuTableHistoryPatch, value: any, direction: 'undo' | 'redo') => {
|
|
33
|
-
const row = opts.findRow(patch.rowId)
|
|
34
|
-
if (!row) return false
|
|
35
|
-
row[patch.prop] = value
|
|
36
|
-
opts.onApply?.(patch, direction)
|
|
37
|
-
return true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const undo = () => {
|
|
41
|
-
if (!opts.enabled()) return false
|
|
42
|
-
const patch = undoStack.value.pop()
|
|
43
|
-
if (!patch) return false
|
|
44
|
-
if (apply(patch, patch.oldValue, 'undo')) {
|
|
45
|
-
redoStack.value.push(patch)
|
|
46
|
-
return true
|
|
47
|
-
}
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const redo = () => {
|
|
52
|
-
if (!opts.enabled()) return false
|
|
53
|
-
const patch = redoStack.value.pop()
|
|
54
|
-
if (!patch) return false
|
|
55
|
-
if (apply(patch, patch.newValue, 'redo')) {
|
|
56
|
-
undoStack.value.push(patch)
|
|
57
|
-
return true
|
|
58
|
-
}
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const clear = () => {
|
|
63
|
-
undoStack.value = []
|
|
64
|
-
redoStack.value = []
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
undoStack,
|
|
69
|
-
redoStack,
|
|
70
|
-
push,
|
|
71
|
-
undo,
|
|
72
|
-
redo,
|
|
73
|
-
clear,
|
|
74
|
-
canUndo: () => undoStack.value.length > 0,
|
|
75
|
-
canRedo: () => redoStack.value.length > 0,
|
|
76
|
-
}
|
|
77
|
-
}
|