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,879 +0,0 @@
1
- <template>
2
- <div
3
- ref="rootRef"
4
- class="qiu-cascader"
5
- :class="[
6
- `qiu-cascader--${resolvedSize}`,
7
- {
8
- 'is-disabled': disabled,
9
- 'is-focused': visible,
10
- 'is-multiple': isMultiple,
11
- },
12
- ]"
13
- >
14
- <div
15
- class="qiu-cascader__wrapper"
16
- tabindex="0"
17
- @click="toggleDropdown"
18
- @keydown.enter.prevent="toggleDropdown"
19
- @keydown.space.prevent="toggleDropdown"
20
- @keydown.esc="closeDropdown"
21
- @keydown.down.prevent="openDropdown"
22
- >
23
- <div class="qiu-cascader__tags" v-if="isMultiple && selectedTags.length">
24
- <span
25
- v-for="tag in displayTags"
26
- :key="String(tag.value)"
27
- class="qiu-cascader__tag"
28
- >
29
- <span class="qiu-cascader__tag-text">{{ tag.label }}</span>
30
- <button
31
- type="button"
32
- class="qiu-cascader__tag-close"
33
- tabindex="-1"
34
- @mousedown.prevent
35
- @click.stop="removeTag(tag.value)"
36
- >
37
- ×
38
- </button>
39
- </span>
40
- <span v-if="hiddenTagCount > 0" class="qiu-cascader__tag is-collapse">
41
- +{{ hiddenTagCount }}
42
- </span>
43
- </div>
44
- <span
45
- v-else
46
- class="qiu-cascader__selected"
47
- :class="{ 'is-placeholder': !displayText }"
48
- >
49
- {{ displayText || placeholder }}
50
- </span>
51
- <span class="qiu-cascader__suffix">
52
- <button
53
- v-if="clearable && hasValue && !disabled"
54
- type="button"
55
- class="qiu-cascader__clear"
56
- tabindex="-1"
57
- @mousedown.prevent
58
- @click.stop="clear"
59
- >
60
- ×
61
- </button>
62
- <span class="qiu-cascader__caret" :class="{ 'is-open': visible }">▾</span>
63
- </span>
64
- </div>
65
-
66
- <Teleport to="body">
67
- <Transition name="qiu-cascader-fade">
68
- <div
69
- v-show="visible"
70
- ref="panelRef"
71
- class="qiu-cascader__dropdown"
72
- :style="dropdownStyle"
73
- @mousedown.prevent
74
- >
75
- <div v-if="filterable" class="qiu-cascader__filter">
76
- <input
77
- v-model="filterText"
78
- class="qiu-cascader__filter-input"
79
- :placeholder="filterPlaceholder"
80
- @click.stop
81
- />
82
- </div>
83
-
84
- <div v-if="filterText.trim()" class="qiu-cascader__suggestions">
85
- <div
86
- v-for="item in filteredSuggestions"
87
- :key="item.pathKey"
88
- class="qiu-cascader__suggestion"
89
- :class="{
90
- 'is-selected': isPathSelected(item.pathValues),
91
- 'is-disabled': item.disabled,
92
- }"
93
- @click="!item.disabled && pickSuggestion(item)"
94
- >
95
- {{ item.pathLabels.join(separator) }}
96
- </div>
97
- <div v-if="!filteredSuggestions.length" class="qiu-cascader__empty">无匹配数据</div>
98
- </div>
99
-
100
- <div v-else class="qiu-cascader__menus">
101
- <ul
102
- v-for="(menu, level) in menus"
103
- :key="level"
104
- class="qiu-cascader__menu"
105
- >
106
- <li
107
- v-for="opt in menu"
108
- :key="String(getOptValue(opt, cfg))"
109
- class="qiu-cascader__node"
110
- :class="{
111
- 'is-active': isActiveAt(level, opt),
112
- 'is-selected': isNodeSelected(opt, level),
113
- 'is-disabled': isOptDisabled(opt, cfg),
114
- }"
115
- @click="onNodeClick(opt, level)"
116
- @mouseenter="onNodeHover(opt, level)"
117
- >
118
- <label
119
- v-if="isMultiple || checkStrictly"
120
- class="qiu-cascader__check"
121
- @click.stop
122
- >
123
- <input
124
- type="checkbox"
125
- :checked="isNodeChecked(opt)"
126
- :disabled="isOptDisabled(opt, cfg) || (!checkStrictly && hasChildren(opt))"
127
- @change="onCheckChange(opt, level)"
128
- />
129
- </label>
130
- <span class="qiu-cascader__label">{{ getOptLabel(opt, cfg) }}</span>
131
- <span v-if="hasChildren(opt)" class="qiu-cascader__arrow">›</span>
132
- </li>
133
- <li v-if="!menu.length" class="qiu-cascader__empty-item">无数据</li>
134
- </ul>
135
- </div>
136
- </div>
137
- </Transition>
138
- </Teleport>
139
- </div>
140
- </template>
141
-
142
- <script lang="ts" setup>
143
- import {
144
- getOptChildren,
145
- getOptLabel,
146
- getOptValue,
147
- isOptDisabled,
148
- type QiuCascaderOption,
149
- type QiuCascaderPropsConfig,
150
- type QiuCascaderValue,
151
- } from './types'
152
-
153
- defineOptions({ name: 'QiuCascader' })
154
-
155
- const props = withDefaults(
156
- defineProps<{
157
- modelValue?: QiuCascaderValue
158
- options?: QiuCascaderOption[]
159
- props?: QiuCascaderPropsConfig
160
- placeholder?: string
161
- clearable?: boolean
162
- disabled?: boolean
163
- filterable?: boolean
164
- filterPlaceholder?: string
165
- showAllLevels?: boolean
166
- separator?: string
167
- collapseTags?: boolean
168
- maxCollapseTags?: number
169
- size?: 'large' | 'default' | 'small'
170
- }>(),
171
- {
172
- modelValue: null,
173
- options: () => [],
174
- props: () => ({}),
175
- placeholder: '请选择',
176
- clearable: false,
177
- disabled: false,
178
- filterable: false,
179
- filterPlaceholder: '输入关键字搜索',
180
- showAllLevels: true,
181
- separator: ' / ',
182
- collapseTags: false,
183
- maxCollapseTags: 1,
184
- size: 'default',
185
- }
186
- )
187
-
188
- const emit = defineEmits<{
189
- 'update:modelValue': [QiuCascaderValue]
190
- change: [QiuCascaderValue]
191
- clear: []
192
- visibleChange: [boolean]
193
- 'expand-change': [(string | number)[]]
194
- }>()
195
-
196
- const rootRef = ref<HTMLElement | null>(null)
197
- const panelRef = ref<HTMLElement | null>(null)
198
- const visible = ref(false)
199
- const filterText = ref('')
200
- const activePath = ref<QiuCascaderOption[]>([])
201
- const dropdownStyle = ref<Record<string, string>>({})
202
-
203
- const cfg = computed<QiuCascaderPropsConfig>(() => ({
204
- value: 'value',
205
- label: 'label',
206
- children: 'children',
207
- disabled: 'disabled',
208
- multiple: false,
209
- checkStrictly: false,
210
- emitPath: true,
211
- expandTrigger: 'click',
212
- ...props.props,
213
- }))
214
-
215
- const isMultiple = computed(() => Boolean(cfg.value.multiple))
216
- const checkStrictly = computed(() => Boolean(cfg.value.checkStrictly))
217
- const emitPath = computed(() => cfg.value.emitPath !== false)
218
- const expandTrigger = computed(() => cfg.value.expandTrigger || 'click')
219
- const resolvedSize = computed(() => props.size || 'default')
220
-
221
- const hasChildren = (opt: QiuCascaderOption) => {
222
- const children = getOptChildren(opt, cfg.value)
223
- return Boolean(children?.length)
224
- }
225
-
226
- const menus = computed(() => {
227
- const result: QiuCascaderOption[][] = [props.options || []]
228
- for (const node of activePath.value) {
229
- const children = getOptChildren(node, cfg.value)
230
- if (children?.length) result.push(children)
231
- else break
232
- }
233
- return result
234
- })
235
-
236
- type FlatItem = {
237
- option: QiuCascaderOption
238
- path: QiuCascaderOption[]
239
- pathValues: (string | number)[]
240
- pathLabels: string[]
241
- pathKey: string
242
- disabled: boolean
243
- }
244
-
245
- const flattenOptions = (
246
- nodes: QiuCascaderOption[],
247
- parent: QiuCascaderOption[] = []
248
- ): FlatItem[] => {
249
- const out: FlatItem[] = []
250
- for (const opt of nodes) {
251
- const path = [...parent, opt]
252
- const pathValues = path.map((n) => getOptValue(n, cfg.value))
253
- const pathLabels = path.map((n) => getOptLabel(n, cfg.value))
254
- const disabled = path.some((n) => isOptDisabled(n, cfg.value))
255
- const children = getOptChildren(opt, cfg.value)
256
- const leafOrStrict = !children?.length || checkStrictly.value
257
- if (leafOrStrict) {
258
- out.push({
259
- option: opt,
260
- path,
261
- pathValues,
262
- pathLabels,
263
- pathKey: pathValues.join('/'),
264
- disabled,
265
- })
266
- }
267
- if (children?.length) out.push(...flattenOptions(children, path))
268
- }
269
- return out
270
- }
271
-
272
- const flatList = computed(() => flattenOptions(props.options || []))
273
-
274
- const filteredSuggestions = computed(() => {
275
- const kw = filterText.value.trim().toLowerCase()
276
- if (!kw) return []
277
- return flatList.value.filter((item) =>
278
- item.pathLabels.some((l) => l.toLowerCase().includes(kw))
279
- )
280
- })
281
-
282
- const normalizeSingle = (val: QiuCascaderValue): (string | number)[] => {
283
- if (val == null || val === '') return []
284
- if (Array.isArray(val)) {
285
- if (!val.length) return []
286
- if (Array.isArray(val[0])) return []
287
- return val as (string | number)[]
288
- }
289
- return [val as string | number]
290
- }
291
-
292
- const normalizeMultiple = (val: QiuCascaderValue): (string | number)[][] => {
293
- if (val == null || val === '') return []
294
- if (!Array.isArray(val)) return [[val as string | number]]
295
- if (!val.length) return []
296
- if (Array.isArray(val[0])) return val as (string | number)[][]
297
- // 多选且 emitPath=false:一维数组表示多个末级值
298
- return (val as (string | number)[]).map((v) => [v])
299
- }
300
-
301
- const selectedPaths = computed<(string | number)[][]>(() => {
302
- if (isMultiple.value) return normalizeMultiple(props.modelValue ?? null)
303
- const single = normalizeSingle(props.modelValue ?? null)
304
- return single.length ? [single] : []
305
- })
306
-
307
- const findPathByValues = (values: (string | number)[]): QiuCascaderOption[] | null => {
308
- if (!values.length) return null
309
- // emitPath=false:只存末级,在树中查找完整路径
310
- if (!emitPath.value && values.length === 1) {
311
- const hit = flatList.value.find((f) => getOptValue(f.option, cfg.value) === values[0])
312
- return hit?.path || null
313
- }
314
- let nodes = props.options || []
315
- const path: QiuCascaderOption[] = []
316
- for (const v of values) {
317
- const found = nodes.find((n) => getOptValue(n, cfg.value) === v)
318
- if (!found) return null
319
- path.push(found)
320
- nodes = getOptChildren(found, cfg.value) || []
321
- }
322
- return path
323
- }
324
-
325
- const pathToValue = (path: QiuCascaderOption[]): string | number | (string | number)[] => {
326
- const values = path.map((n) => getOptValue(n, cfg.value))
327
- return emitPath.value ? values : values[values.length - 1]
328
- }
329
-
330
- const isPathSelected = (pathValues: (string | number)[]) => {
331
- const key = pathValues.join('/')
332
- const last = pathValues[pathValues.length - 1]
333
- return selectedPaths.value.some((p) => {
334
- if (emitPath.value) return p.join('/') === key
335
- return p[p.length - 1] === last
336
- })
337
- }
338
-
339
- const isNodeChecked = (opt: QiuCascaderOption) => {
340
- const v = getOptValue(opt, cfg.value)
341
- return selectedPaths.value.some((p) => {
342
- if (emitPath.value) return p[p.length - 1] === v
343
- return p.includes(v) || p[p.length - 1] === v
344
- })
345
- }
346
-
347
- const isActiveAt = (level: number, opt: QiuCascaderOption) => {
348
- const cur = activePath.value[level]
349
- return cur ? getOptValue(cur, cfg.value) === getOptValue(opt, cfg.value) : false
350
- }
351
-
352
- const isNodeSelected = (opt: QiuCascaderOption, level: number) => {
353
- const v = getOptValue(opt, cfg.value)
354
- return selectedPaths.value.some((p) => p[level] === v || (!emitPath.value && p[p.length - 1] === v))
355
- }
356
-
357
- const selectedTags = computed(() =>
358
- selectedPaths.value
359
- .map((values) => {
360
- const path = findPathByValues(values)
361
- if (!path?.length) {
362
- return {
363
- value: values[values.length - 1],
364
- label: String(values[values.length - 1] ?? ''),
365
- pathValues: values,
366
- }
367
- }
368
- const labels = path.map((n) => getOptLabel(n, cfg.value))
369
- return {
370
- value: getOptValue(path[path.length - 1], cfg.value),
371
- label: props.showAllLevels ? labels.join(props.separator) : labels[labels.length - 1],
372
- pathValues: values,
373
- }
374
- })
375
- .filter(Boolean)
376
- )
377
-
378
- const displayTags = computed(() => {
379
- if (!props.collapseTags) return selectedTags.value
380
- return selectedTags.value.slice(0, props.maxCollapseTags)
381
- })
382
-
383
- const hiddenTagCount = computed(() => {
384
- if (!props.collapseTags) return 0
385
- return Math.max(0, selectedTags.value.length - props.maxCollapseTags)
386
- })
387
-
388
- const displayText = computed(() => {
389
- if (isMultiple.value) return ''
390
- const paths = selectedPaths.value
391
- if (!paths.length) return ''
392
- const path = findPathByValues(paths[0])
393
- if (!path?.length) return String(paths[0][paths[0].length - 1] ?? '')
394
- const labels = path.map((n) => getOptLabel(n, cfg.value))
395
- return props.showAllLevels ? labels.join(props.separator) : labels[labels.length - 1]
396
- })
397
-
398
- const hasValue = computed(() => selectedPaths.value.length > 0)
399
-
400
- const syncActiveFromModel = () => {
401
- const paths = selectedPaths.value
402
- if (!paths.length) {
403
- activePath.value = []
404
- return
405
- }
406
- const path = findPathByValues(paths[0])
407
- if (path?.length) {
408
- // 展开到选中路径的父级
409
- activePath.value = path.slice(0, -1)
410
- }
411
- }
412
-
413
- const updateDropdownPosition = () => {
414
- const el = rootRef.value
415
- if (!el) return
416
- const rect = el.getBoundingClientRect()
417
- const spaceBelow = window.innerHeight - rect.bottom
418
- const preferUp = spaceBelow < 240 && rect.top > spaceBelow
419
- dropdownStyle.value = {
420
- position: 'fixed',
421
- left: `${rect.left}px`,
422
- top: preferUp ? 'auto' : `${rect.bottom + 4}px`,
423
- bottom: preferUp ? `${window.innerHeight - rect.top + 4}px` : 'auto',
424
- minWidth: `${Math.max(rect.width, 180)}px`,
425
- zIndex: '4000',
426
- }
427
- }
428
-
429
- const openDropdown = () => {
430
- if (props.disabled) return
431
- syncActiveFromModel()
432
- filterText.value = ''
433
- updateDropdownPosition()
434
- visible.value = true
435
- emit('visibleChange', true)
436
- }
437
-
438
- const closeDropdown = () => {
439
- if (!visible.value) return
440
- visible.value = false
441
- filterText.value = ''
442
- emit('visibleChange', false)
443
- }
444
-
445
- const toggleDropdown = () => {
446
- if (props.disabled) return
447
- visible.value ? closeDropdown() : openDropdown()
448
- }
449
-
450
- const emitValue = (next: QiuCascaderValue) => {
451
- emit('update:modelValue', next)
452
- emit('change', next)
453
- }
454
-
455
- const expandTo = (opt: QiuCascaderOption, level: number) => {
456
- const next = activePath.value.slice(0, level)
457
- next[level] = opt
458
- // 截断到当前层(展开子级由 menus 计算)
459
- activePath.value = next.slice(0, level + 1)
460
- emit('expand-change', activePath.value.map((n) => getOptValue(n, cfg.value)))
461
- }
462
-
463
- const selectPath = (path: QiuCascaderOption[]) => {
464
- if (!path.length) return
465
- if (isMultiple.value) {
466
- const values = path.map((n) => getOptValue(n, cfg.value))
467
- const key = values.join('/')
468
- const last = values[values.length - 1]
469
- const exists = selectedPaths.value.some((p) =>
470
- emitPath.value ? p.join('/') === key : p[p.length - 1] === last
471
- )
472
- let nextPaths = [...selectedPaths.value]
473
- if (exists) {
474
- nextPaths = nextPaths.filter((p) =>
475
- emitPath.value ? p.join('/') !== key : p[p.length - 1] !== last
476
- )
477
- } else {
478
- nextPaths.push(emitPath.value ? values : [last])
479
- }
480
- const payload: QiuCascaderValue = emitPath.value
481
- ? nextPaths
482
- : nextPaths.map((p) => p[p.length - 1])
483
- emitValue(payload)
484
- } else {
485
- emitValue(pathToValue(path) as QiuCascaderValue)
486
- closeDropdown()
487
- }
488
- }
489
-
490
- const onNodeClick = (opt: QiuCascaderOption, level: number) => {
491
- if (isOptDisabled(opt, cfg.value)) return
492
- const children = getOptChildren(opt, cfg.value)
493
- if (children?.length) {
494
- expandTo(opt, level)
495
- // checkStrictly 单选:点击任意级即可选中;多选仍走 checkbox
496
- if (checkStrictly.value && !isMultiple.value) {
497
- selectPath([...activePath.value.slice(0, level), opt])
498
- }
499
- return
500
- }
501
- // 叶子节点
502
- const path = [...activePath.value.slice(0, level), opt]
503
- selectPath(path)
504
- }
505
-
506
- const onNodeHover = (opt: QiuCascaderOption, level: number) => {
507
- if (expandTrigger.value !== 'hover') return
508
- if (isOptDisabled(opt, cfg.value)) return
509
- if (hasChildren(opt)) expandTo(opt, level)
510
- }
511
-
512
- const onCheckChange = (opt: QiuCascaderOption, level: number) => {
513
- if (isOptDisabled(opt, cfg.value)) return
514
- const path = [...activePath.value.slice(0, level), opt]
515
- if (!checkStrictly.value && hasChildren(opt)) return
516
- selectPath(path)
517
- if (!isMultiple.value) closeDropdown()
518
- }
519
-
520
- const pickSuggestion = (item: FlatItem) => {
521
- selectPath(item.path)
522
- }
523
-
524
- const removeTag = (value: string | number) => {
525
- const nextPaths = selectedPaths.value.filter((p) => p[p.length - 1] !== value)
526
- const payload: QiuCascaderValue = emitPath.value
527
- ? nextPaths
528
- : nextPaths.map((p) => p[p.length - 1])
529
- emitValue(nextPaths.length ? payload : null)
530
- }
531
-
532
- const clear = () => {
533
- emitValue(null)
534
- emit('clear')
535
- activePath.value = []
536
- }
537
-
538
- const onDocClick = (e: MouseEvent) => {
539
- const t = e.target as Node
540
- if (rootRef.value?.contains(t)) return
541
- if (panelRef.value?.contains(t)) return
542
- closeDropdown()
543
- }
544
-
545
- const onWinScroll = () => {
546
- if (visible.value) updateDropdownPosition()
547
- }
548
-
549
- onMounted(() => {
550
- document.addEventListener('mousedown', onDocClick)
551
- window.addEventListener('resize', onWinScroll)
552
- window.addEventListener('scroll', onWinScroll, true)
553
- })
554
-
555
- onUnmounted(() => {
556
- document.removeEventListener('mousedown', onDocClick)
557
- window.removeEventListener('resize', onWinScroll)
558
- window.removeEventListener('scroll', onWinScroll, true)
559
- })
560
-
561
- watch(
562
- () => props.modelValue,
563
- () => {
564
- if (!visible.value) syncActiveFromModel()
565
- }
566
- )
567
- </script>
568
-
569
- <style lang="scss" scoped>
570
- .qiu-cascader {
571
- position: relative;
572
- display: inline-block;
573
- width: 100%;
574
- vertical-align: middle;
575
- font-size: 14px;
576
-
577
- &.is-disabled {
578
- cursor: not-allowed;
579
- opacity: 0.65;
580
-
581
- .qiu-cascader__wrapper {
582
- pointer-events: none;
583
- background: var(--qiu-disabled-bg-color, #f5f7fa);
584
- }
585
- }
586
-
587
- &--small {
588
- font-size: 12px;
589
-
590
- .qiu-cascader__wrapper {
591
- min-height: 24px;
592
- padding: 0 8px;
593
- }
594
- }
595
-
596
- &--large .qiu-cascader__wrapper {
597
- min-height: 40px;
598
- padding: 0 15px;
599
- }
600
-
601
- &__wrapper {
602
- display: flex;
603
- gap: 6px;
604
- align-items: center;
605
- box-sizing: border-box;
606
- width: 100%;
607
- min-height: 32px;
608
- padding: 1px 11px;
609
- cursor: pointer;
610
- background: var(--qiu-fill-color-blank, #fff);
611
- border: 1px solid var(--qiu-border-color, #dcdfe6);
612
- border-radius: var(--qiu-border-radius-base, 4px);
613
- transition: border-color 0.2s;
614
-
615
- &:hover {
616
- border-color: var(--qiu-border-color-hover, #c0c4cc);
617
- }
618
- }
619
-
620
- &.is-focused .qiu-cascader__wrapper {
621
- border-color: var(--qiu-color-primary);
622
- outline: none;
623
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--qiu-color-primary) 20%, transparent);
624
- }
625
-
626
- &__selected {
627
- flex: 1;
628
- overflow: hidden;
629
- text-overflow: ellipsis;
630
- white-space: nowrap;
631
- color: var(--qiu-text-color-primary, #303133);
632
-
633
- &.is-placeholder {
634
- color: var(--qiu-text-color-placeholder, #a8abb2);
635
- }
636
- }
637
-
638
- &__tags {
639
- display: flex;
640
- flex: 1;
641
- flex-wrap: wrap;
642
- gap: 4px;
643
- align-items: center;
644
- min-width: 0;
645
- padding: 2px 0;
646
- }
647
-
648
- &__tag {
649
- display: inline-flex;
650
- gap: 2px;
651
- align-items: center;
652
- max-width: 100%;
653
- padding: 0 6px;
654
- font-size: 12px;
655
- line-height: 20px;
656
- color: var(--qiu-text-color-regular);
657
- background: var(--qiu-fill-color, #f0f2f5);
658
- border-radius: 4px;
659
-
660
- &.is-collapse {
661
- color: var(--qiu-color-primary);
662
- background: var(--qiu-color-primary-light-9);
663
- }
664
- }
665
-
666
- &__tag-text {
667
- overflow: hidden;
668
- text-overflow: ellipsis;
669
- white-space: nowrap;
670
- }
671
-
672
- &__tag-close {
673
- padding: 0;
674
- font-size: 14px;
675
- line-height: 1;
676
- color: var(--qiu-text-color-secondary);
677
- cursor: pointer;
678
- background: none;
679
- border: 0;
680
-
681
- &:hover {
682
- color: var(--qiu-color-primary);
683
- }
684
- }
685
-
686
- &__suffix {
687
- display: inline-flex;
688
- flex-shrink: 0;
689
- gap: 4px;
690
- align-items: center;
691
- color: var(--qiu-text-color-placeholder);
692
- }
693
-
694
- &__clear {
695
- display: inline-flex;
696
- align-items: center;
697
- justify-content: center;
698
- width: 16px;
699
- height: 16px;
700
- padding: 0;
701
- font-size: 14px;
702
- line-height: 1;
703
- color: var(--qiu-text-color-placeholder);
704
- cursor: pointer;
705
- background: var(--qiu-fill-color);
706
- border: 0;
707
- border-radius: 50%;
708
-
709
- &:hover {
710
- color: var(--qiu-text-color-secondary);
711
- }
712
- }
713
-
714
- &__caret {
715
- display: inline-block;
716
- transition: transform 0.2s;
717
-
718
- &.is-open {
719
- transform: rotate(180deg);
720
- }
721
- }
722
- }
723
- </style>
724
-
725
- <style lang="scss">
726
- .qiu-cascader__dropdown {
727
- box-sizing: border-box;
728
- overflow: hidden;
729
- background: var(--qiu-bg-color-overlay, #fff);
730
- border: 1px solid var(--qiu-border-color-light, #e4e7ed);
731
- border-radius: var(--qiu-border-radius-base, 4px);
732
- box-shadow: var(--qiu-box-shadow-light, 0 2px 12px 0 rgb(0 0 0 / 10%));
733
- }
734
-
735
- .qiu-cascader__filter {
736
- padding: 8px;
737
- border-bottom: 1px solid var(--qiu-border-color-lighter, #ebeef5);
738
- }
739
-
740
- .qiu-cascader__filter-input {
741
- box-sizing: border-box;
742
- width: 100%;
743
- height: 28px;
744
- padding: 0 8px;
745
- font-size: 13px;
746
- color: var(--qiu-text-color-primary);
747
- outline: none;
748
- background: var(--qiu-fill-color-blank, #fff);
749
- border: 1px solid var(--qiu-border-color, #dcdfe6);
750
- border-radius: 4px;
751
-
752
- &:focus {
753
- border-color: var(--qiu-color-primary);
754
- }
755
- }
756
-
757
- .qiu-cascader__menus {
758
- display: flex;
759
- max-height: 280px;
760
- }
761
-
762
- .qiu-cascader__menu {
763
- min-width: 160px;
764
- max-width: 220px;
765
- max-height: 280px;
766
- padding: 6px 0;
767
- margin: 0;
768
- overflow: auto;
769
- list-style: none;
770
- border-right: 1px solid var(--qiu-border-color-lighter, #ebeef5);
771
-
772
- &:last-child {
773
- border-right: 0;
774
- }
775
- }
776
-
777
- .qiu-cascader__node {
778
- display: flex;
779
- gap: 6px;
780
- align-items: center;
781
- padding: 0 12px;
782
- font-size: 14px;
783
- line-height: 34px;
784
- color: var(--qiu-text-color-regular, #606266);
785
- cursor: pointer;
786
- user-select: none;
787
-
788
- &:hover {
789
- background: var(--qiu-fill-color-light, #f5f7fa);
790
- }
791
-
792
- &.is-active,
793
- &.is-selected {
794
- color: var(--qiu-color-primary);
795
- font-weight: 500;
796
- background: var(--qiu-color-primary-light-9);
797
- }
798
-
799
- &.is-disabled {
800
- color: var(--qiu-text-color-disabled, #c0c4cc);
801
- cursor: not-allowed;
802
- background: transparent;
803
- }
804
- }
805
-
806
- .qiu-cascader__check {
807
- display: inline-flex;
808
- flex-shrink: 0;
809
- align-items: center;
810
- margin: 0;
811
- cursor: pointer;
812
-
813
- input {
814
- margin: 0;
815
- cursor: pointer;
816
- }
817
- }
818
-
819
- .qiu-cascader__label {
820
- flex: 1;
821
- overflow: hidden;
822
- text-overflow: ellipsis;
823
- white-space: nowrap;
824
- }
825
-
826
- .qiu-cascader__arrow {
827
- flex-shrink: 0;
828
- color: var(--qiu-text-color-placeholder);
829
- font-size: 16px;
830
- line-height: 1;
831
- }
832
-
833
- .qiu-cascader__suggestions {
834
- max-height: 280px;
835
- overflow: auto;
836
- }
837
-
838
- .qiu-cascader__suggestion {
839
- padding: 0 12px;
840
- font-size: 14px;
841
- line-height: 34px;
842
- color: var(--qiu-text-color-regular);
843
- cursor: pointer;
844
-
845
- &:hover {
846
- background: var(--qiu-fill-color-light, #f5f7fa);
847
- }
848
-
849
- &.is-selected {
850
- color: var(--qiu-color-primary);
851
- font-weight: 500;
852
- }
853
-
854
- &.is-disabled {
855
- color: var(--qiu-text-color-disabled);
856
- cursor: not-allowed;
857
- }
858
- }
859
-
860
- .qiu-cascader__empty,
861
- .qiu-cascader__empty-item {
862
- padding: 12px;
863
- font-size: 13px;
864
- color: var(--qiu-text-color-secondary);
865
- text-align: center;
866
- list-style: none;
867
- }
868
-
869
- .qiu-cascader-fade-enter-active,
870
- .qiu-cascader-fade-leave-active {
871
- transition: opacity 0.15s ease, transform 0.15s ease;
872
- }
873
-
874
- .qiu-cascader-fade-enter-from,
875
- .qiu-cascader-fade-leave-to {
876
- opacity: 0;
877
- transform: translateY(-4px);
878
- }
879
- </style>