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.
Files changed (234) hide show
  1. package/dist/components/QiuCanvasKit/QiuCanvasEditor.vue.d.ts +3 -1
  2. package/dist/components/QiuCanvasKit/components/DataModulesPanel.vue.d.ts +1 -0
  3. package/dist/components/QiuTable/index.vue.d.ts +12 -12
  4. package/dist/foucui.css +1 -1
  5. package/dist/foucui.js +7476 -7373
  6. package/dist/foucui.umd.cjs +19 -19
  7. package/package.json +23 -13
  8. package/components/QiuAffix/index.vue +0 -186
  9. package/components/QiuAlert/index.vue +0 -208
  10. package/components/QiuAnchor/context.ts +0 -18
  11. package/components/QiuAnchor/index.vue +0 -180
  12. package/components/QiuAnchorLink/index.vue +0 -67
  13. package/components/QiuApp/index.vue +0 -9
  14. package/components/QiuAutocomplete/index.vue +0 -408
  15. package/components/QiuAvatar/index.vue +0 -140
  16. package/components/QiuBacktop/index.vue +0 -121
  17. package/components/QiuBadge/api/index.ts +0 -389
  18. package/components/QiuBadge/components/BadgeImagePickerDialog.vue +0 -243
  19. package/components/QiuBadge/components/BadgeLibraryManagerDialog.vue +0 -30
  20. package/components/QiuBadge/components/BadgeLibraryPanel.vue +0 -182
  21. package/components/QiuBadge/composables/useTableHostHeight.ts +0 -53
  22. package/components/QiuBadge/index.ts +0 -18
  23. package/components/QiuBadge/shared/BadgeTemplateRenderer.vue +0 -32
  24. package/components/QiuBadge/shared/assetUrl.ts +0 -26
  25. package/components/QiuBadge/shared/badgeTemplate.ts +0 -309
  26. package/components/QiuBadge/utils/chinaAreaData.ts +0 -44
  27. package/components/QiuBadge/utils/hostRegion.ts +0 -22
  28. package/components/QiuBadge/utils/pca-code.json +0 -1
  29. package/components/QiuBadge/utils/pdfFromElement.ts +0 -348
  30. package/components/QiuBadge/views/BadgeLibraryView.vue +0 -32
  31. package/components/QiuBadge/views/BadgeManageView.vue +0 -1364
  32. package/components/QiuBadge/views/BadgeTemplateEditorView.vue +0 -10
  33. package/components/QiuBadge/views/BadgeTemplateListView.vue +0 -229
  34. package/components/QiuBadge/views/DeskLabelTemplateListView.vue +0 -227
  35. package/components/QiuBadge/views/DeskLabelView.vue +0 -683
  36. package/components/QiuBadgeDot/index.vue +0 -87
  37. package/components/QiuButton/index.vue +0 -247
  38. package/components/QiuButtonGroup/index.vue +0 -63
  39. package/components/QiuCanvasKit/QiuCanvasEditor.vue +0 -5503
  40. package/components/QiuCanvasKit/QiuCanvasRenderer.vue +0 -348
  41. package/components/QiuCanvasKit/assetUrl.ts +0 -11
  42. package/components/QiuCanvasKit/components/DataModulesPanel.vue +0 -352
  43. package/components/QiuCanvasKit/export/html.ts +0 -132
  44. package/components/QiuCanvasKit/export/pdf.ts +0 -48
  45. package/components/QiuCanvasKit/export/png.ts +0 -50
  46. package/components/QiuCanvasKit/index.ts +0 -20
  47. package/components/QiuCanvasKit/modules/badgeCompat.ts +0 -84
  48. package/components/QiuCanvasKit/modules/dataRegistry.ts +0 -207
  49. package/components/QiuCanvasKit/modules/merge.ts +0 -71
  50. package/components/QiuCanvasKit/presets/badge.ts +0 -67
  51. package/components/QiuCanvasKit/presets/banner.ts +0 -79
  52. package/components/QiuCanvasKit/types.ts +0 -138
  53. package/components/QiuCard/index.vue +0 -104
  54. package/components/QiuCarousel/context.ts +0 -16
  55. package/components/QiuCarousel/index.vue +0 -343
  56. package/components/QiuCarouselItem/index.vue +0 -47
  57. package/components/QiuCascader/index.vue +0 -879
  58. package/components/QiuCascader/types.ts +0 -48
  59. package/components/QiuCheckbox/QiuCheckboxGroup.vue +0 -78
  60. package/components/QiuCheckbox/index.vue +0 -231
  61. package/components/QiuCheckbox/types.ts +0 -8
  62. package/components/QiuCode/index.vue +0 -64
  63. package/components/QiuCol/col.scss +0 -55
  64. package/components/QiuCol/index.vue +0 -64
  65. package/components/QiuCollapse/context.ts +0 -10
  66. package/components/QiuCollapse/index.vue +0 -70
  67. package/components/QiuCollapseItem/index.vue +0 -167
  68. package/components/QiuColorField/index.vue +0 -296
  69. package/components/QiuColorPicker/README.md +0 -118
  70. package/components/QiuColorPicker/index.vue +0 -60
  71. package/components/QiuColorfulCard/index.vue +0 -101
  72. package/components/QiuCombobox/index.vue +0 -277
  73. package/components/QiuConfigProvider/index.vue +0 -31
  74. package/components/QiuContextMenu/index.vue +0 -209
  75. package/components/QiuDate/index.vue +0 -1134
  76. package/components/QiuDescriptions/context.ts +0 -20
  77. package/components/QiuDescriptions/index.vue +0 -115
  78. package/components/QiuDescriptionsItem/index.vue +0 -103
  79. package/components/QiuDialog/index.vue +0 -828
  80. package/components/QiuDigitalCard/index.vue +0 -159
  81. package/components/QiuDivider/index.vue +0 -185
  82. package/components/QiuDot/index.vue +0 -83
  83. package/components/QiuDrawer/index.vue +0 -350
  84. package/components/QiuDropdown/QiuDropdownItem.vue +0 -74
  85. package/components/QiuDropdown/QiuDropdownMenu.vue +0 -22
  86. package/components/QiuDropdown/context.ts +0 -9
  87. package/components/QiuDropdown/index.vue +0 -189
  88. package/components/QiuEmpty/index.vue +0 -73
  89. package/components/QiuForm/index.vue +0 -172
  90. package/components/QiuForm/types.ts +0 -39
  91. package/components/QiuFormItem/index.vue +0 -276
  92. package/components/QiuFullscreen/index.vue +0 -11
  93. package/components/QiuGrid/index.vue +0 -408
  94. package/components/QiuHoverCard/index.vue +0 -192
  95. package/components/QiuIcon/index.vue +0 -117
  96. package/components/QiuIconPicker/iconify.libraries.ts +0 -182
  97. package/components/QiuIconPicker/icons.catalog.ts +0 -3072
  98. package/components/QiuIconPicker/index.vue +0 -488
  99. package/components/QiuImage/index.vue +0 -177
  100. package/components/QiuInput/index.vue +0 -328
  101. package/components/QiuInputNumber/index.vue +0 -295
  102. package/components/QiuInputOtp/index.vue +0 -190
  103. package/components/QiuKbd/index.vue +0 -57
  104. package/components/QiuLayoutContainer/index.vue +0 -50
  105. package/components/QiuLinkPreview/index.vue +0 -224
  106. package/components/QiuLoading/index.ts +0 -176
  107. package/components/QiuLoading/spinner.vue +0 -820
  108. package/components/QiuLoading/types.ts +0 -44
  109. package/components/QiuMention/index.vue +0 -299
  110. package/components/QiuMenu/QiuMenuRoot.vue +0 -241
  111. package/components/QiuMenu/components/QiuMenuItem.vue +0 -340
  112. package/components/QiuMenu/components/QiuSubMenu.vue +0 -504
  113. package/components/QiuMenu/context.ts +0 -21
  114. package/components/QiuMenu/index.vue +0 -43
  115. package/components/QiuMenuNav/QiuMenuNavItem.vue +0 -100
  116. package/components/QiuMenuNav/QiuMenuNavSub.vue +0 -120
  117. package/components/QiuMenuNav/context.ts +0 -14
  118. package/components/QiuMenuNav/index.vue +0 -148
  119. package/components/QiuMsg/QiuMsgItem.vue +0 -180
  120. package/components/QiuMsg/index.ts +0 -158
  121. package/components/QiuMsg/types.ts +0 -23
  122. package/components/QiuMsgBox/index.ts +0 -147
  123. package/components/QiuNotification/QiuNotificationItem.vue +0 -168
  124. package/components/QiuNotification/index.ts +0 -155
  125. package/components/QiuNotification/types.ts +0 -14
  126. package/components/QiuPage/index.vue +0 -12
  127. package/components/QiuPageHeader/index.vue +0 -115
  128. package/components/QiuPageMain/index.vue +0 -78
  129. package/components/QiuPagination/index.vue +0 -421
  130. package/components/QiuPasswordStrength/index.vue +0 -101
  131. package/components/QiuPopconfirm/index.vue +0 -196
  132. package/components/QiuPopover/index.vue +0 -277
  133. package/components/QiuProgress/index.vue +0 -235
  134. package/components/QiuQrcode/index.vue +0 -302
  135. package/components/QiuQueryForm/components/QiuQueryFormBottomPanel.vue +0 -13
  136. package/components/QiuQueryForm/components/QiuQueryFormLeftPanel.vue +0 -37
  137. package/components/QiuQueryForm/components/QiuQueryFormRightPanel.vue +0 -37
  138. package/components/QiuQueryForm/components/QiuQueryFormTopPanel.vue +0 -13
  139. package/components/QiuQueryForm/index.vue +0 -117
  140. package/components/QiuRadio/QiuRadioGroup.vue +0 -75
  141. package/components/QiuRadio/index.vue +0 -191
  142. package/components/QiuRadio/types.ts +0 -9
  143. package/components/QiuRate/index.vue +0 -184
  144. package/components/QiuRefresh/index.vue +0 -32
  145. package/components/QiuResult/index.vue +0 -103
  146. package/components/QiuRow/index.vue +0 -60
  147. package/components/QiuRow/types.ts +0 -5
  148. package/components/QiuScrollbar/index.vue +0 -107
  149. package/components/QiuSearchBar/index.vue +0 -154
  150. package/components/QiuSegmented/index.vue +0 -120
  151. package/components/QiuSelect/QiuOption.vue +0 -93
  152. package/components/QiuSelect/index.vue +0 -452
  153. package/components/QiuSelect/types.ts +0 -15
  154. package/components/QiuSkeleton/index.vue +0 -78
  155. package/components/QiuSlider/index.vue +0 -266
  156. package/components/QiuSpace/index.vue +0 -95
  157. package/components/QiuSparkline/index.vue +0 -92
  158. package/components/QiuStatistic/index.vue +0 -4
  159. package/components/QiuStatistics/index.vue +0 -123
  160. package/components/QiuStep/index.vue +0 -220
  161. package/components/QiuSteps/context.ts +0 -25
  162. package/components/QiuSteps/index.vue +0 -84
  163. package/components/QiuSwitch/index.vue +0 -145
  164. package/components/QiuTabBar/index.vue +0 -218
  165. package/components/QiuTabBar/types.ts +0 -19
  166. package/components/QiuTabPane/index.vue +0 -82
  167. package/components/QiuTable/columns.ts +0 -16
  168. package/components/QiuTable/export.ts +0 -102
  169. package/components/QiuTable/index.ts +0 -24
  170. package/components/QiuTable/index.vue +0 -3503
  171. package/components/QiuTable/registry.ts +0 -75
  172. package/components/QiuTable/styles/table.scss +0 -1230
  173. package/components/QiuTable/types.ts +0 -413
  174. package/components/QiuTable/useArea.ts +0 -202
  175. package/components/QiuTable/useColumns.ts +0 -325
  176. package/components/QiuTable/useEdit.ts +0 -136
  177. package/components/QiuTable/useExtras.ts +0 -115
  178. package/components/QiuTable/useHistory.ts +0 -77
  179. package/components/QiuTable/useMerge.ts +0 -105
  180. package/components/QiuTable/useResize.ts +0 -86
  181. package/components/QiuTable/useScrollSync.ts +0 -152
  182. package/components/QiuTable/useVirtual.ts +0 -97
  183. package/components/QiuTable/utils.ts +0 -165
  184. package/components/QiuTableColumn/index.vue +0 -101
  185. package/components/QiuTag/index.vue +0 -143
  186. package/components/QiuText/index.vue +0 -99
  187. package/components/QiuTime/index.vue +0 -548
  188. package/components/QiuTimeAgo/index.vue +0 -103
  189. package/components/QiuTimeline/index.vue +0 -54
  190. package/components/QiuTimelineItem/index.vue +0 -140
  191. package/components/QiuTips/index.vue +0 -323
  192. package/components/QiuTooltip/index.vue +0 -237
  193. package/components/QiuTransfer/index.vue +0 -277
  194. package/components/QiuTree/QiuTreeNode.vue +0 -146
  195. package/components/QiuTree/index.vue +0 -143
  196. package/components/QiuTree/types.ts +0 -30
  197. package/components/QiuTreeSelect/index.vue +0 -259
  198. package/components/QiuTrend/index.vue +0 -92
  199. package/components/QiuUpload/index.vue +0 -628
  200. package/components/QiuUploadCrop/index.vue +0 -1267
  201. package/components/QiuWatermark/index.vue +0 -121
  202. package/components/useBodyScrollLock.ts +0 -28
  203. package/dist/components/QiuBadge/utils/pca-code.json.d.ts +0 -3
  204. package/plugins/qiu.ts +0 -83
  205. package/plugins/shortcuts.ts +0 -69
  206. package/src/bridge/@/api/media.ts +0 -8
  207. package/src/bridge/@/api/notice.ts +0 -5
  208. package/src/bridge/@/config/index.ts +0 -38
  209. package/src/bridge/@/config/media.config.ts +0 -37
  210. package/src/bridge/@/config/security.config.ts +0 -8
  211. package/src/bridge/@/i18n/index.ts +0 -6
  212. package/src/bridge/@/store/modules/bing.ts +0 -9
  213. package/src/bridge/@/store/modules/errorLog.ts +0 -9
  214. package/src/bridge/@/store/modules/media.ts +0 -10
  215. package/src/bridge/@/store/modules/routes.ts +0 -10
  216. package/src/bridge/@/store/modules/settings.ts +0 -9
  217. package/src/bridge/@/store/modules/tabs.ts +0 -9
  218. package/src/bridge/@/store/modules/user.ts +0 -9
  219. package/src/bridge/@/utils/convertToCamelCase.ts +0 -11
  220. package/src/bridge/@/utils/index.ts +0 -9
  221. package/src/bridge/@/utils/pageTitle.ts +0 -4
  222. package/src/bridge/@/utils/routes.ts +0 -14
  223. package/src/bridge/@/utils/validate.ts +0 -17
  224. package/src/bridge/@qiu/plugins/shortcuts.ts +0 -3
  225. package/src/i18n.ts +0 -8
  226. package/src/icons/qiu-logo.svg +0 -1
  227. package/styles/dark.scss +0 -194
  228. package/styles/normalize.css +0 -349
  229. package/styles/plain.scss +0 -423
  230. package/styles/qiu-tokens.scss +0 -40
  231. package/styles/qiu.scss +0 -1149
  232. package/styles/technology.scss +0 -452
  233. package/styles/var.scss +0 -382
  234. package/styles/variables.scss +0 -77
@@ -1,190 +0,0 @@
1
- <template>
2
- <div
3
- ref="rootRef"
4
- class="qiu-otp"
5
- :class="[`qiu-otp--${size}`, { 'is-disabled': disabled }]"
6
- @paste="onPaste"
7
- >
8
- <input
9
- v-for="(cell, i) in cells"
10
- :key="i"
11
- :ref="(el) => setInputRef(el, i)"
12
- class="qiu-otp__input"
13
- :class="{ 'is-filled': !!cell }"
14
- :value="cell"
15
- :disabled="disabled"
16
- :type="mask ? 'password' : 'text'"
17
- inputmode="numeric"
18
- maxlength="1"
19
- :aria-label="`验证码第 ${i + 1} 位`"
20
- @input="onInput($event, i)"
21
- @keydown="onKeydown($event, i)"
22
- @focus="onFocus($event, i)"
23
- />
24
- </div>
25
- </template>
26
-
27
- <script lang="ts" setup>
28
- import { computed, nextTick, ref, watch } from 'vue'
29
-
30
- defineOptions({ name: 'QiuOtp' })
31
-
32
- const props = withDefaults(
33
- defineProps<{
34
- modelValue?: string
35
- length?: number
36
- size?: 'small' | 'default' | 'large'
37
- disabled?: boolean
38
- mask?: boolean
39
- /** 仅允许数字 */
40
- numeric?: boolean
41
- }>(),
42
- {
43
- modelValue: '',
44
- length: 6,
45
- size: 'default',
46
- disabled: false,
47
- mask: false,
48
- numeric: true,
49
- }
50
- )
51
-
52
- const emit = defineEmits<{
53
- 'update:modelValue': [string]
54
- change: [string]
55
- complete: [string]
56
- }>()
57
-
58
- const rootRef = ref<HTMLElement | null>(null)
59
- const inputRefs = ref<(HTMLInputElement | null)[]>([])
60
-
61
- const cells = computed(() => {
62
- const arr = String(props.modelValue || '')
63
- .slice(0, props.length)
64
- .split('')
65
- const out: string[] = []
66
- for (let i = 0; i < props.length; i++) out.push(arr[i] ?? '')
67
- return out
68
- })
69
-
70
- const setInputRef = (el: any, i: number) => {
71
- inputRefs.value[i] = el as HTMLInputElement | null
72
- }
73
-
74
- const emitValue = (next: string[]) => {
75
- const str = next.join('')
76
- emit('update:modelValue', str)
77
- emit('change', str)
78
- if (str.length === props.length && !next.includes('')) emit('complete', str)
79
- }
80
-
81
- const focusAt = (i: number) => {
82
- nextTick(() => inputRefs.value[i]?.focus())
83
- }
84
-
85
- const onInput = (e: Event, i: number) => {
86
- const target = e.target as HTMLInputElement
87
- let v = target.value
88
- if (props.numeric) v = v.replace(/\D/g, '')
89
- v = v.slice(-1)
90
- const next = [...cells.value]
91
- next[i] = v
92
- // 自动跳到下一个
93
- if (v && i < props.length - 1) focusAt(i + 1)
94
- emitValue(next)
95
- }
96
-
97
- const onKeydown = (e: KeyboardEvent, i: number) => {
98
- if (e.key === 'Backspace') {
99
- const next = [...cells.value]
100
- if (next[i]) {
101
- next[i] = ''
102
- emitValue(next)
103
- } else if (i > 0) {
104
- next[i - 1] = ''
105
- emitValue(next)
106
- focusAt(i - 1)
107
- }
108
- e.preventDefault()
109
- } else if (e.key === 'ArrowLeft' && i > 0) {
110
- focusAt(i - 1)
111
- } else if (e.key === 'ArrowRight' && i < props.length - 1) {
112
- focusAt(i + 1)
113
- }
114
- }
115
-
116
- const onFocus = (e: FocusEvent, i: number) => {
117
- const target = e.target as HTMLInputElement
118
- target.select()
119
- }
120
-
121
- const onPaste = (e: ClipboardEvent) => {
122
- e.preventDefault()
123
- let text = e.clipboardData?.getData('text') ?? ''
124
- if (props.numeric) text = text.replace(/\D/g, '')
125
- const chars = text.slice(0, props.length).split('')
126
- const next = [...cells.value]
127
- chars.forEach((c, i) => (next[i] = c))
128
- emitValue(next)
129
- focusAt(Math.min(chars.length, props.length - 1))
130
- }
131
-
132
- watch(
133
- () => props.length,
134
- () => (inputRefs.value = [])
135
- )
136
- </script>
137
-
138
- <style lang="scss" scoped>
139
- .qiu-otp {
140
- display: inline-flex;
141
- gap: 8px;
142
- vertical-align: middle;
143
-
144
- &.is-disabled {
145
- opacity: 0.6;
146
- pointer-events: none;
147
- }
148
- }
149
-
150
- .qiu-otp__input {
151
- box-sizing: border-box;
152
- width: 40px;
153
- height: 40px;
154
- padding: 0;
155
- font-size: 18px;
156
- font-weight: 600;
157
- text-align: center;
158
- color: var(--qiu-text-color-primary, #303133);
159
- background: var(--qiu-bg-color, #fff);
160
- border: 1px solid var(--qiu-border-color, #dcdfe6);
161
- border-radius: var(--qiu-border-radius-base, 4px);
162
- outline: none;
163
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
164
-
165
- &:hover {
166
- border-color: var(--qiu-border-color-hover, #c0c4cc);
167
- }
168
-
169
- &:focus {
170
- border-color: var(--qiu-color-primary, #409eff);
171
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--qiu-color-primary, #409eff) 25%, transparent);
172
- }
173
-
174
- &.is-filled {
175
- border-color: var(--qiu-color-primary, #409eff);
176
- }
177
- }
178
-
179
- .qiu-otp--small .qiu-otp__input {
180
- width: 32px;
181
- height: 32px;
182
- font-size: 15px;
183
- }
184
-
185
- .qiu-otp--large .qiu-otp__input {
186
- width: 48px;
187
- height: 48px;
188
- font-size: 20px;
189
- }
190
- </style>
@@ -1,57 +0,0 @@
1
- <template>
2
- <kbd class="qiu-kbd" :class="[`qiu-kbd--${size}`]">
3
- <slot />
4
- </kbd>
5
- </template>
6
-
7
- <script lang="ts" setup>
8
- defineOptions({ name: 'QiuKbd' })
9
-
10
- withDefaults(
11
- defineProps<{
12
- size?: 'small' | 'default' | 'large'
13
- }>(),
14
- {
15
- size: 'default',
16
- }
17
- )
18
- </script>
19
-
20
- <style lang="scss" scoped>
21
- .qiu-kbd {
22
- display: inline-flex;
23
- align-items: center;
24
- justify-content: center;
25
- box-sizing: border-box;
26
- font-family: var(--qiu-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
27
- line-height: 1;
28
- color: var(--qiu-text-color-regular, #606266);
29
- white-space: nowrap;
30
- background: var(--qiu-fill-color-blank, #fff);
31
- border: 1px solid var(--qiu-border-color, #dcdfe6);
32
- border-bottom-width: 2px;
33
- border-radius: 6px;
34
- box-shadow: 0 1px 0 var(--qiu-border-color-lighter, #ebeef5);
35
- }
36
-
37
- .qiu-kbd--small {
38
- min-width: 18px;
39
- height: 18px;
40
- padding: 0 5px;
41
- font-size: 11px;
42
- }
43
-
44
- .qiu-kbd--default {
45
- min-width: 22px;
46
- height: 22px;
47
- padding: 0 6px;
48
- font-size: 12px;
49
- }
50
-
51
- .qiu-kbd--large {
52
- min-width: 26px;
53
- height: 26px;
54
- padding: 0 8px;
55
- font-size: 13px;
56
- }
57
- </style>
@@ -1,50 +0,0 @@
1
- <template>
2
- <div class="qiu-layout" :class="[`qiu-layout--${direction}`]">
3
- <slot />
4
- </div>
5
- </template>
6
-
7
- <script lang="ts" setup>
8
- defineOptions({ name: 'QiuLayoutContainer' })
9
-
10
- withDefaults(
11
- defineProps<{
12
- /** 主轴方向 */
13
- direction?: 'horizontal' | 'vertical'
14
- /** 子项是否均分 */
15
- fit?: boolean
16
- /** 间距(px) */
17
- gap?: number
18
- }>(),
19
- {
20
- direction: 'horizontal',
21
- fit: false,
22
- gap: 12,
23
- }
24
- )
25
- </script>
26
-
27
- <style lang="scss" scoped>
28
- .qiu-layout {
29
- display: flex;
30
- width: 100%;
31
- box-sizing: border-box;
32
-
33
- &--horizontal {
34
- flex-direction: row;
35
- }
36
-
37
- &--vertical {
38
- flex-direction: column;
39
- }
40
-
41
- &--vertical > :deep(*) {
42
- width: 100%;
43
- }
44
-
45
- &--horizontal.qiu-layout--fit > :deep(*) {
46
- flex: 1 1 0;
47
- min-width: 0;
48
- }
49
- }
50
- </style>
@@ -1,224 +0,0 @@
1
- <template>
2
- <a
3
- ref="triggerRef"
4
- class="qiu-link-preview"
5
- :href="href"
6
- :target="target"
7
- rel="noopener noreferrer"
8
- @mouseenter="onEnter"
9
- @mouseleave="onLeave"
10
- >
11
- <slot>{{ href }}</slot>
12
- </a>
13
-
14
- <Teleport to="body">
15
- <Transition name="qiu-link-preview-fade">
16
- <div
17
- v-if="visible"
18
- ref="cardRef"
19
- class="qiu-link-preview__panel"
20
- :style="panelStyle"
21
- @mouseenter="onCardEnter"
22
- @mouseleave="onCardLeave"
23
- >
24
- <div v-if="image" class="qiu-link-preview__image" :style="{ backgroundImage: `url(${image})` }" />
25
- <div class="qiu-link-preview__content">
26
- <div class="qiu-link-preview__host">
27
- <qiu-icon icon="links-line" />
28
- <span>{{ host }}</span>
29
- </div>
30
- <div v-if="title" class="qiu-link-preview__title">{{ title }}</div>
31
- <div v-if="description" class="qiu-link-preview__desc">{{ description }}</div>
32
- </div>
33
- </div>
34
- </Transition>
35
- </Teleport>
36
- </template>
37
-
38
- <script lang="ts" setup>
39
- import { computed, nextTick, ref } from 'vue'
40
-
41
- defineOptions({ name: 'QiuLinkPreview' })
42
-
43
- const props = withDefaults(
44
- defineProps<{
45
- href: string
46
- title?: string
47
- description?: string
48
- image?: string
49
- target?: string
50
- openDelay?: number
51
- closeDelay?: number
52
- }>(),
53
- {
54
- title: '',
55
- description: '',
56
- image: '',
57
- target: '_blank',
58
- openDelay: 200,
59
- closeDelay: 150,
60
- }
61
- )
62
-
63
- const emit = defineEmits<{ visibleChange: [boolean] }>()
64
-
65
- const triggerRef = ref<HTMLElement | null>(null)
66
- const cardRef = ref<HTMLElement | null>(null)
67
- const visible = ref(false)
68
- const panelStyle = ref<Record<string, string>>({})
69
- let openTimer: ReturnType<typeof setTimeout> | undefined
70
- let closeTimer: ReturnType<typeof setTimeout> | undefined
71
- let inCard = false
72
-
73
- const host = computed(() => {
74
- try {
75
- return new URL(props.href).hostname
76
- } catch {
77
- return props.href
78
- }
79
- })
80
-
81
- const clearTimers = () => {
82
- if (openTimer) clearTimeout(openTimer)
83
- if (closeTimer) clearTimeout(closeTimer)
84
- }
85
-
86
- const open = async () => {
87
- clearTimers()
88
- openTimer = setTimeout(async () => {
89
- visible.value = true
90
- emit('visibleChange', true)
91
- await nextTick()
92
- updatePosition()
93
- }, props.openDelay)
94
- }
95
-
96
- const scheduleClose = () => {
97
- clearTimers()
98
- closeTimer = setTimeout(() => {
99
- if (!inCard) {
100
- visible.value = false
101
- emit('visibleChange', false)
102
- }
103
- }, props.closeDelay)
104
- }
105
-
106
- const onEnter = () => open()
107
- const onLeave = () => scheduleClose()
108
- const onCardEnter = () => {
109
- inCard = true
110
- clearTimers()
111
- }
112
- const onCardLeave = () => {
113
- inCard = false
114
- scheduleClose()
115
- }
116
-
117
- const updatePosition = () => {
118
- const trigger = triggerRef.value
119
- const card = cardRef.value
120
- if (!trigger || !card) return
121
- const rect = trigger.getBoundingClientRect()
122
- const cardRect = card.getBoundingClientRect()
123
- const gap = 10
124
- let top = rect.bottom + gap
125
- if (top + cardRect.height > window.innerHeight - 4) top = rect.top - cardRect.height - gap
126
- let left = rect.left
127
- left = Math.min(Math.max(4, left), window.innerWidth - cardRect.width - 4)
128
- top = Math.max(4, top)
129
- panelStyle.value = {
130
- position: 'fixed',
131
- top: `${top}px`,
132
- left: `${left}px`,
133
- width: `${Math.min(320, window.innerWidth - 8)}px`,
134
- zIndex: '2100',
135
- }
136
- }
137
-
138
- const onScrollOrResize = () => visible.value && updatePosition()
139
-
140
- watch(visible, (v) => {
141
- if (v) {
142
- window.addEventListener('scroll', onScrollOrResize, true)
143
- window.addEventListener('resize', onScrollOrResize)
144
- } else {
145
- window.removeEventListener('scroll', onScrollOrResize, true)
146
- window.removeEventListener('resize', onScrollOrResize)
147
- }
148
- })
149
-
150
- onBeforeUnmount(() => {
151
- clearTimers()
152
- window.removeEventListener('scroll', onScrollOrResize, true)
153
- window.removeEventListener('resize', onScrollOrResize)
154
- })
155
- </script>
156
-
157
- <style lang="scss" scoped>
158
- .qiu-link-preview {
159
- color: var(--qiu-color-primary, #409eff);
160
- text-decoration: none;
161
- cursor: pointer;
162
-
163
- &:hover {
164
- text-decoration: underline;
165
- }
166
- }
167
-
168
- .qiu-link-preview__panel {
169
- box-sizing: border-box;
170
- overflow: hidden;
171
- background: var(--qiu-bg-color-overlay, #fff);
172
- border: 1px solid var(--qiu-border-color-light, #e4e7ed);
173
- border-radius: 10px;
174
- box-shadow: var(--qiu-box-shadow-light, 0 6px 16px rgba(0, 0, 0, 0.12));
175
- }
176
-
177
- .qiu-link-preview__image {
178
- height: 120px;
179
- background-color: var(--qiu-fill-color-light, #f5f7fa);
180
- background-repeat: no-repeat;
181
- background-position: center;
182
- background-size: cover;
183
- }
184
-
185
- .qiu-link-preview__content {
186
- padding: 12px 14px;
187
- }
188
-
189
- .qiu-link-preview__host {
190
- display: flex;
191
- align-items: center;
192
- gap: 4px;
193
- margin-bottom: 6px;
194
- font-size: 12px;
195
- color: var(--qiu-text-color-secondary, #909399);
196
- }
197
-
198
- .qiu-link-preview__title {
199
- font-size: 14px;
200
- font-weight: 600;
201
- color: var(--qiu-text-color-primary, #303133);
202
- }
203
-
204
- .qiu-link-preview__desc {
205
- margin-top: 4px;
206
- font-size: 13px;
207
- line-height: 1.5;
208
- color: var(--qiu-text-color-regular, #606266);
209
- display: -webkit-box;
210
- -webkit-line-clamp: 2;
211
- -webkit-box-orient: vertical;
212
- overflow: hidden;
213
- }
214
-
215
- .qiu-link-preview-fade-enter-active,
216
- .qiu-link-preview-fade-leave-active {
217
- transition: opacity 0.15s ease;
218
- }
219
-
220
- .qiu-link-preview-fade-enter-from,
221
- .qiu-link-preview-fade-leave-to {
222
- opacity: 0;
223
- }
224
- </style>
@@ -1,176 +0,0 @@
1
- /**
2
- * QiuLoading — 指令 vQiuLoading + 服务 qiuLoading.service
3
- *
4
- * 使用:
5
- * import { vQiuLoading, qiuLoading } from '.../QiuLoading'
6
- * app.directive('qiu-loading', vQiuLoading)
7
- *
8
- * v-qiu-loading="true"
9
- * v-qiu-loading="{ loading: true, text: '加载中', effect: 'dots' }"
10
- * qiuLoading.service({ target: el, text: '...', effect: 'bars' })
11
- */
12
- import { createApp, h, type App, type Directive, type DirectiveBinding } from 'vue'
13
- import QiuLoadingSpinner from './spinner.vue'
14
- import { QIU_LOADING_EFFECTS, type QiuLoadingEffect } from './types'
15
-
16
- export type { QiuLoadingEffect }
17
- export { QIU_LOADING_EFFECTS }
18
-
19
- export interface QiuLoadingOptions {
20
- target?: HTMLElement | string
21
- text?: string
22
- fullscreen?: boolean
23
- transparent?: boolean
24
- /** 动画效果,见 QIU_LOADING_EFFECTS */
25
- effect?: QiuLoadingEffect
26
- /** directive 绑定值 */
27
- loading?: boolean
28
- }
29
-
30
- interface LoadingInstance {
31
- app: App
32
- el: HTMLElement
33
- host: HTMLElement
34
- text: string
35
- effect: QiuLoadingEffect
36
- transparent: boolean
37
- }
38
-
39
- const instanceMap = new WeakMap<HTMLElement, LoadingInstance>()
40
-
41
- const resolveTarget = (target?: HTMLElement | string): HTMLElement => {
42
- if (target instanceof HTMLElement) return target
43
- if (typeof target === 'string') {
44
- const el = document.querySelector(target)
45
- if (el instanceof HTMLElement) return el
46
- }
47
- return document.body
48
- }
49
-
50
- const ensureRelative = (el: HTMLElement) => {
51
- const pos = getComputedStyle(el).position
52
- if (pos === 'static' || !pos) {
53
- el.dataset.qiuLoadingPrevPosition = el.style.position || ''
54
- el.style.position = 'relative'
55
- }
56
- }
57
-
58
- const restorePosition = (el: HTMLElement) => {
59
- if ('qiuLoadingPrevPosition' in el.dataset) {
60
- el.style.position = el.dataset.qiuLoadingPrevPosition || ''
61
- delete el.dataset.qiuLoadingPrevPosition
62
- }
63
- }
64
-
65
- const parseBinding = (binding: DirectiveBinding<boolean | QiuLoadingOptions>): QiuLoadingOptions => {
66
- if (typeof binding.value === 'object' && binding.value !== null) {
67
- return { ...binding.value }
68
- }
69
- return { loading: !!binding.value }
70
- }
71
-
72
- const getLoadingState = (binding: DirectiveBinding<boolean | QiuLoadingOptions>): boolean => {
73
- if (typeof binding.value === 'object' && binding.value !== null) {
74
- if ((binding.value as QiuLoadingOptions).loading === undefined) return true
75
- return !!(binding.value as QiuLoadingOptions).loading
76
- }
77
- return !!binding.value
78
- }
79
-
80
- const mountLoading = (host: HTMLElement, options: QiuLoadingOptions) => {
81
- const text = options.text || ''
82
- const effect: QiuLoadingEffect = options.effect || 'spinner'
83
- const transparent = !!options.transparent
84
- const existing = instanceMap.get(host)
85
- // 已在 loading 且参数未变:勿重复挂载
86
- if (
87
- existing &&
88
- existing.text === text &&
89
- existing.effect === effect &&
90
- existing.transparent === transparent
91
- ) {
92
- return existing
93
- }
94
- if (existing) {
95
- existing.app.unmount()
96
- existing.el.remove()
97
- instanceMap.delete(host)
98
- }
99
-
100
- const isFullscreen = options.fullscreen ?? host === document.body
101
- if (!isFullscreen) ensureRelative(host)
102
-
103
- const el = document.createElement('div')
104
- el.className = 'qiu-loading-host'
105
- host.appendChild(el)
106
-
107
- const app = createApp({
108
- setup() {
109
- return () =>
110
- h(QiuLoadingSpinner, {
111
- text,
112
- fullscreen: isFullscreen,
113
- transparent,
114
- effect,
115
- })
116
- },
117
- })
118
-
119
- app.mount(el)
120
- const inst: LoadingInstance = { app, el, host, text, effect, transparent }
121
- instanceMap.set(host, inst)
122
- return inst
123
- }
124
-
125
- const unmountLoading = (host: HTMLElement) => {
126
- const inst = instanceMap.get(host)
127
- if (!inst) return
128
- inst.app.unmount()
129
- inst.el.remove()
130
- instanceMap.delete(host)
131
- if (host !== document.body) restorePosition(host)
132
- }
133
-
134
- export const vQiuLoading: Directive<HTMLElement, boolean | QiuLoadingOptions> = {
135
- mounted(el, binding) {
136
- if (getLoadingState(binding)) {
137
- const opts = parseBinding(binding)
138
- mountLoading(el, { ...opts, loading: true })
139
- }
140
- },
141
- updated(el, binding) {
142
- const opts = parseBinding(binding)
143
- if (getLoadingState(binding)) {
144
- mountLoading(el, { ...opts, loading: true })
145
- } else {
146
- unmountLoading(el)
147
- }
148
- },
149
- unmounted(el) {
150
- unmountLoading(el)
151
- },
152
- }
153
-
154
- export const qiuLoading = {
155
- service(options: QiuLoadingOptions = {}) {
156
- const host = resolveTarget(options.target)
157
- const isBody = host === document.body
158
- mountLoading(host, {
159
- ...options,
160
- loading: true,
161
- fullscreen: options.fullscreen ?? isBody,
162
- })
163
-
164
- return {
165
- close: () => unmountLoading(host),
166
- }
167
- },
168
- }
169
-
170
- export default {
171
- install(app: App) {
172
- app.directive('qiu-loading', vQiuLoading)
173
- },
174
- }
175
-
176
- export { QiuLoadingSpinner }