@melt-js/comps-vben 0.0.0-stage → 2026.11.8-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 (618) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +91 -2
  3. package/hel_dist/assets/index-DaBGAjzb.js +478 -0
  4. package/hel_dist/assets/style-CcRbEt6_.css +2 -0
  5. package/hel_dist/entry.js +20 -0
  6. package/hel_dist/hel-meta.json +116 -0
  7. package/hel_dist/index.html +37 -0
  8. package/hel_proxy/entry.js +20 -0
  9. package/hel_proxy/entry.js.map +1 -0
  10. package/hel_proxy_es/entry.js +11 -0
  11. package/hel_proxy_es/entry.js.map +1 -0
  12. package/package.json +101 -3
  13. package/scripts/check-tdz.mjs +20 -0
  14. package/scripts/check.mjs +18 -0
  15. package/scripts/meta.mjs +28 -0
  16. package/scripts/prepublishOnly.mjs +42 -0
  17. package/scripts/publish-stage.mjs +25 -0
  18. package/src/common-ui/components/api-component/api-component.vue +273 -0
  19. package/src/common-ui/components/api-component/index.ts +7 -0
  20. package/src/common-ui/components/api-component/types.ts +72 -0
  21. package/src/common-ui/components/captcha/hooks/useCaptchaPoints.ts +19 -0
  22. package/src/common-ui/components/captcha/index.ts +7 -0
  23. package/src/common-ui/components/captcha/point-selection-captcha/index.vue +176 -0
  24. package/src/common-ui/components/captcha/point-selection-captcha/point-selection-captcha-card.vue +86 -0
  25. package/src/common-ui/components/captcha/slider-captcha/index.vue +254 -0
  26. package/src/common-ui/components/captcha/slider-captcha/slider-captcha-action.vue +65 -0
  27. package/src/common-ui/components/captcha/slider-captcha/slider-captcha-bar.vue +40 -0
  28. package/src/common-ui/components/captcha/slider-captcha/slider-captcha-content.vue +53 -0
  29. package/src/common-ui/components/captcha/slider-rotate-captcha/index.vue +213 -0
  30. package/src/common-ui/components/captcha/slider-translate-captcha/index.vue +311 -0
  31. package/src/common-ui/components/captcha/types.ts +210 -0
  32. package/src/common-ui/components/col-page/col-page.vue +111 -0
  33. package/src/common-ui/components/col-page/index.ts +2 -0
  34. package/src/common-ui/components/col-page/types.ts +26 -0
  35. package/src/common-ui/components/count-to/count-to.vue +123 -0
  36. package/src/common-ui/components/count-to/index.ts +2 -0
  37. package/src/common-ui/components/count-to/types.ts +53 -0
  38. package/src/common-ui/components/cropper/cropper.vue +981 -0
  39. package/src/common-ui/components/cropper/index.ts +1 -0
  40. package/src/common-ui/components/ellipsis-text/ellipsis-text.vue +232 -0
  41. package/src/common-ui/components/ellipsis-text/index.ts +1 -0
  42. package/src/common-ui/components/icon-picker/icon-picker.vue +322 -0
  43. package/src/common-ui/components/icon-picker/icons.ts +56 -0
  44. package/src/common-ui/components/icon-picker/index.ts +2 -0
  45. package/src/common-ui/components/icon-picker/types.ts +26 -0
  46. package/src/common-ui/components/index.ts +51 -0
  47. package/src/common-ui/components/json-viewer/index.ts +3 -0
  48. package/src/common-ui/components/json-viewer/index.vue +128 -0
  49. package/src/common-ui/components/json-viewer/style.scss +91 -0
  50. package/src/common-ui/components/json-viewer/types.ts +44 -0
  51. package/src/common-ui/components/loading/directive.ts +132 -0
  52. package/src/common-ui/components/loading/index.ts +3 -0
  53. package/src/common-ui/components/loading/loading.vue +39 -0
  54. package/src/common-ui/components/loading/spinner.vue +28 -0
  55. package/src/common-ui/components/page/index.ts +2 -0
  56. package/src/common-ui/components/page/page.vue +107 -0
  57. package/src/common-ui/components/page/types.ts +23 -0
  58. package/src/common-ui/components/resize/index.ts +1 -0
  59. package/src/common-ui/components/resize/resize.vue +1269 -0
  60. package/src/common-ui/components/tippy/directive.ts +100 -0
  61. package/src/common-ui/components/tippy/index.ts +67 -0
  62. package/src/common-ui/components/tree/index.ts +1 -0
  63. package/src/common-ui/components/tree/tree.vue +25 -0
  64. package/src/common-ui/index.ts +2 -0
  65. package/src/common-ui/ui/about/about.ts +14 -0
  66. package/src/common-ui/ui/about/about.vue +183 -0
  67. package/src/common-ui/ui/about/index.ts +1 -0
  68. package/src/common-ui/ui/authentication/auth-title.vue +13 -0
  69. package/src/common-ui/ui/authentication/code-login.vue +127 -0
  70. package/src/common-ui/ui/authentication/dingding-login.vue +113 -0
  71. package/src/common-ui/ui/authentication/forget-password.vue +116 -0
  72. package/src/common-ui/ui/authentication/index.ts +7 -0
  73. package/src/common-ui/ui/authentication/login-expired-modal.vue +95 -0
  74. package/src/common-ui/ui/authentication/login.vue +186 -0
  75. package/src/common-ui/ui/authentication/qrcode-login.vue +105 -0
  76. package/src/common-ui/ui/authentication/register.vue +121 -0
  77. package/src/common-ui/ui/authentication/third-party-login.vue +71 -0
  78. package/src/common-ui/ui/authentication/types.ts +70 -0
  79. package/src/common-ui/ui/dashboard/analysis/analysis-chart-card.vue +24 -0
  80. package/src/common-ui/ui/dashboard/analysis/analysis-charts-tabs.vue +40 -0
  81. package/src/common-ui/ui/dashboard/analysis/analysis-overview.vue +55 -0
  82. package/src/common-ui/ui/dashboard/analysis/index.ts +3 -0
  83. package/src/common-ui/ui/dashboard/index.ts +3 -0
  84. package/src/common-ui/ui/dashboard/typing.ts +50 -0
  85. package/src/common-ui/ui/dashboard/workbench/index.ts +5 -0
  86. package/src/common-ui/ui/dashboard/workbench/workbench-header.vue +48 -0
  87. package/src/common-ui/ui/dashboard/workbench/workbench-project.vue +73 -0
  88. package/src/common-ui/ui/dashboard/workbench/workbench-quick-nav.vue +56 -0
  89. package/src/common-ui/ui/dashboard/workbench/workbench-todo.vue +63 -0
  90. package/src/common-ui/ui/dashboard/workbench/workbench-trends.vue +64 -0
  91. package/src/common-ui/ui/fallback/fallback.ts +25 -0
  92. package/src/common-ui/ui/fallback/fallback.vue +164 -0
  93. package/src/common-ui/ui/fallback/icons/icon-403.vue +151 -0
  94. package/src/common-ui/ui/fallback/icons/icon-404.vue +154 -0
  95. package/src/common-ui/ui/fallback/icons/icon-500.vue +215 -0
  96. package/src/common-ui/ui/fallback/icons/icon-coming-soon.vue +262 -0
  97. package/src/common-ui/ui/fallback/icons/icon-offline.vue +112 -0
  98. package/src/common-ui/ui/fallback/icons/warning.svg +1 -0
  99. package/src/common-ui/ui/fallback/index.ts +2 -0
  100. package/src/common-ui/ui/index.ts +5 -0
  101. package/src/common-ui/ui/profile/base-setting.vue +58 -0
  102. package/src/common-ui/ui/profile/index.ts +6 -0
  103. package/src/common-ui/ui/profile/notification-setting.vue +52 -0
  104. package/src/common-ui/ui/profile/password-setting.vue +59 -0
  105. package/src/common-ui/ui/profile/profile.vue +64 -0
  106. package/src/common-ui/ui/profile/security-setting.vue +52 -0
  107. package/src/common-ui/ui/profile/types.ts +21 -0
  108. package/src/configs/subApp.ts +1 -0
  109. package/src/entrance/libProperties.ts +34 -0
  110. package/src/entrance/libTypes.js +9 -0
  111. package/src/form-ui/components/form-actions.vue +178 -0
  112. package/src/form-ui/components/form-field-array.vue +234 -0
  113. package/src/form-ui/config.ts +117 -0
  114. package/src/form-ui/deprecation.ts +18 -0
  115. package/src/form-ui/field-name.ts +106 -0
  116. package/src/form-ui/form-api.ts +766 -0
  117. package/src/form-ui/form-codec.ts +40 -0
  118. package/src/form-ui/form-render/context.ts +25 -0
  119. package/src/form-ui/form-render/dependencies.ts +305 -0
  120. package/src/form-ui/form-render/expandable.ts +105 -0
  121. package/src/form-ui/form-render/form-field.vue +644 -0
  122. package/src/form-ui/form-render/form-group.vue +126 -0
  123. package/src/form-ui/form-render/form-label.vue +35 -0
  124. package/src/form-ui/form-render/form.vue +205 -0
  125. package/src/form-ui/form-render/helper.ts +65 -0
  126. package/src/form-ui/form-render/index.ts +3 -0
  127. package/src/form-ui/form-render/schema.ts +534 -0
  128. package/src/form-ui/form-render/utils.ts +170 -0
  129. package/src/form-ui/form-runtime-field.ts +82 -0
  130. package/src/form-ui/form-runtime.ts +325 -0
  131. package/src/form-ui/form-value-transform.ts +235 -0
  132. package/src/form-ui/index.ts +39 -0
  133. package/src/form-ui/rule-registry.ts +17 -0
  134. package/src/form-ui/types.ts +1054 -0
  135. package/src/form-ui/use-custom-field-value.ts +124 -0
  136. package/src/form-ui/use-form-context.ts +148 -0
  137. package/src/form-ui/use-vben-form.ts +81 -0
  138. package/src/form-ui/vben-form.vue +79 -0
  139. package/src/form-ui/vben-use-form.vue +187 -0
  140. package/src/layout-ui/components/index.ts +5 -0
  141. package/src/layout-ui/components/layout-content.vue +75 -0
  142. package/src/layout-ui/components/layout-footer.vue +46 -0
  143. package/src/layout-ui/components/layout-header.vue +89 -0
  144. package/src/layout-ui/components/layout-sidebar.vue +484 -0
  145. package/src/layout-ui/components/layout-tabbar.vue +30 -0
  146. package/src/layout-ui/components/widgets/index.ts +2 -0
  147. package/src/layout-ui/components/widgets/sidebar-collapse-button.vue +25 -0
  148. package/src/layout-ui/components/widgets/sidebar-fixed-button.vue +27 -0
  149. package/src/layout-ui/header-scroll-state.ts +28 -0
  150. package/src/layout-ui/hooks/use-layout.ts +53 -0
  151. package/src/layout-ui/hooks/use-sidebar-drag.ts +152 -0
  152. package/src/layout-ui/index.ts +2 -0
  153. package/src/layout-ui/vben-layout.ts +188 -0
  154. package/src/layout-ui/vben-layout.vue +768 -0
  155. package/src/layouts/authentication/authentication.vue +196 -0
  156. package/src/layouts/authentication/form.vue +38 -0
  157. package/src/layouts/authentication/icons/slogan.vue +4568 -0
  158. package/src/layouts/authentication/index.ts +2 -0
  159. package/src/layouts/authentication/toolbar.vue +49 -0
  160. package/src/layouts/authentication/types.ts +1 -0
  161. package/src/layouts/basic/README.md +7 -0
  162. package/src/layouts/basic/content/content-spinner.vue +12 -0
  163. package/src/layouts/basic/content/content.vue +112 -0
  164. package/src/layouts/basic/content/index.ts +2 -0
  165. package/src/layouts/basic/content/use-content-spinner.ts +98 -0
  166. package/src/layouts/basic/copyright/copyright.vue +49 -0
  167. package/src/layouts/basic/copyright/index.ts +1 -0
  168. package/src/layouts/basic/footer/footer.vue +11 -0
  169. package/src/layouts/basic/footer/index.ts +1 -0
  170. package/src/layouts/basic/header/header.vue +408 -0
  171. package/src/layouts/basic/header/index.ts +1 -0
  172. package/src/layouts/basic/index.ts +1 -0
  173. package/src/layouts/basic/layout.vue +478 -0
  174. package/src/layouts/basic/menu/extra-menu.vue +41 -0
  175. package/src/layouts/basic/menu/index.ts +5 -0
  176. package/src/layouts/basic/menu/menu.vue +45 -0
  177. package/src/layouts/basic/menu/mixed-menu.vue +46 -0
  178. package/src/layouts/basic/menu/use-extra-menu.ts +179 -0
  179. package/src/layouts/basic/menu/use-mixed-menu.ts +181 -0
  180. package/src/layouts/basic/menu/use-navigation.ts +74 -0
  181. package/src/layouts/basic/tabbar/index.ts +2 -0
  182. package/src/layouts/basic/tabbar/tabbar.vue +84 -0
  183. package/src/layouts/basic/tabbar/use-tabbar.ts +229 -0
  184. package/src/layouts/basic/use-layout-scroll.ts +85 -0
  185. package/src/layouts/hooks/index.ts +98 -0
  186. package/src/layouts/iframe/iframe-router-view.vue +86 -0
  187. package/src/layouts/iframe/iframe-view.vue +3 -0
  188. package/src/layouts/iframe/index.ts +2 -0
  189. package/src/layouts/index.ts +4 -0
  190. package/src/layouts/route-cached/cached-tab-keys.ts +13 -0
  191. package/src/layouts/route-cached/index.ts +2 -0
  192. package/src/layouts/route-cached/route-cached-page.vue +36 -0
  193. package/src/layouts/route-cached/route-cached-view.vue +97 -0
  194. package/src/layouts/widgets/breadcrumb-routes.ts +41 -0
  195. package/src/layouts/widgets/breadcrumb.vue +78 -0
  196. package/src/layouts/widgets/check-updates/check-updates.vue +136 -0
  197. package/src/layouts/widgets/check-updates/index.ts +1 -0
  198. package/src/layouts/widgets/color-toggle.vue +64 -0
  199. package/src/layouts/widgets/global-search/global-search.vue +173 -0
  200. package/src/layouts/widgets/global-search/index.ts +1 -0
  201. package/src/layouts/widgets/global-search/search-panel.vue +299 -0
  202. package/src/layouts/widgets/index.ts +12 -0
  203. package/src/layouts/widgets/language-toggle.vue +48 -0
  204. package/src/layouts/widgets/layout-toggle.vue +64 -0
  205. package/src/layouts/widgets/lock-screen/index.ts +2 -0
  206. package/src/layouts/widgets/lock-screen/lock-screen-modal.vue +110 -0
  207. package/src/layouts/widgets/lock-screen/lock-screen.vue +167 -0
  208. package/src/layouts/widgets/notification/index.ts +3 -0
  209. package/src/layouts/widgets/notification/notification.vue +210 -0
  210. package/src/layouts/widgets/notification/types.ts +19 -0
  211. package/src/layouts/widgets/preferences/blocks/block.vue +22 -0
  212. package/src/layouts/widgets/preferences/blocks/checkbox-item.vue +63 -0
  213. package/src/layouts/widgets/preferences/blocks/custom/custom.vue +121 -0
  214. package/src/layouts/widgets/preferences/blocks/draggable-list.vue +138 -0
  215. package/src/layouts/widgets/preferences/blocks/general/animation.vue +56 -0
  216. package/src/layouts/widgets/preferences/blocks/general/general.vue +94 -0
  217. package/src/layouts/widgets/preferences/blocks/index.ts +21 -0
  218. package/src/layouts/widgets/preferences/blocks/input-item.vue +71 -0
  219. package/src/layouts/widgets/preferences/blocks/layout/breadcrumb.vue +61 -0
  220. package/src/layouts/widgets/preferences/blocks/layout/content.vue +53 -0
  221. package/src/layouts/widgets/preferences/blocks/layout/copyright.vue +56 -0
  222. package/src/layouts/widgets/preferences/blocks/layout/footer.vue +17 -0
  223. package/src/layouts/widgets/preferences/blocks/layout/header.vue +78 -0
  224. package/src/layouts/widgets/preferences/blocks/layout/layout.vue +112 -0
  225. package/src/layouts/widgets/preferences/blocks/layout/navigation.vue +50 -0
  226. package/src/layouts/widgets/preferences/blocks/layout/sidebar.vue +108 -0
  227. package/src/layouts/widgets/preferences/blocks/layout/tabbar.vue +106 -0
  228. package/src/layouts/widgets/preferences/blocks/layout/widget.vue +188 -0
  229. package/src/layouts/widgets/preferences/blocks/number-field-item.vue +74 -0
  230. package/src/layouts/widgets/preferences/blocks/select-item.vue +76 -0
  231. package/src/layouts/widgets/preferences/blocks/shortcut-keys/global.vue +55 -0
  232. package/src/layouts/widgets/preferences/blocks/switch-item.vue +55 -0
  233. package/src/layouts/widgets/preferences/blocks/theme/builtin.vue +162 -0
  234. package/src/layouts/widgets/preferences/blocks/theme/color-mode.vue +26 -0
  235. package/src/layouts/widgets/preferences/blocks/theme/font-size.vue +62 -0
  236. package/src/layouts/widgets/preferences/blocks/theme/radius.vue +38 -0
  237. package/src/layouts/widgets/preferences/blocks/theme/theme.vue +114 -0
  238. package/src/layouts/widgets/preferences/blocks/toggle-item.vue +46 -0
  239. package/src/layouts/widgets/preferences/icons/content-compact.vue +119 -0
  240. package/src/layouts/widgets/preferences/icons/full-content.vue +50 -0
  241. package/src/layouts/widgets/preferences/icons/header-mixed-nav.vue +202 -0
  242. package/src/layouts/widgets/preferences/icons/header-nav.vue +119 -0
  243. package/src/layouts/widgets/preferences/icons/header-sidebar-nav.vue +177 -0
  244. package/src/layouts/widgets/preferences/icons/index.ts +12 -0
  245. package/src/layouts/widgets/preferences/icons/mixed-nav.vue +161 -0
  246. package/src/layouts/widgets/preferences/icons/setting.vue +12 -0
  247. package/src/layouts/widgets/preferences/icons/sidebar-mixed-nav.vue +173 -0
  248. package/src/layouts/widgets/preferences/icons/sidebar-nav.vue +153 -0
  249. package/src/layouts/widgets/preferences/index.ts +3 -0
  250. package/src/layouts/widgets/preferences/preferences-button.vue +31 -0
  251. package/src/layouts/widgets/preferences/preferences-drawer.vue +674 -0
  252. package/src/layouts/widgets/preferences/preferences.vue +93 -0
  253. package/src/layouts/widgets/preferences/use-open-preferences.ts +16 -0
  254. package/src/layouts/widgets/theme-toggle/index.ts +1 -0
  255. package/src/layouts/widgets/theme-toggle/theme-button.vue +164 -0
  256. package/src/layouts/widgets/theme-toggle/theme-toggle.vue +83 -0
  257. package/src/layouts/widgets/timezone/index.ts +1 -0
  258. package/src/layouts/widgets/timezone/timezone-button.vue +92 -0
  259. package/src/layouts/widgets/user-dropdown/index.ts +1 -0
  260. package/src/layouts/widgets/user-dropdown/user-dropdown.vue +578 -0
  261. package/src/main.ts +17 -0
  262. package/src/menu-ui/components/collapse-transition.vue +96 -0
  263. package/src/menu-ui/components/index.ts +4 -0
  264. package/src/menu-ui/components/menu-badge-dot.vue +28 -0
  265. package/src/menu-ui/components/menu-badge.vue +57 -0
  266. package/src/menu-ui/components/menu-item.vue +140 -0
  267. package/src/menu-ui/components/menu.vue +880 -0
  268. package/src/menu-ui/components/normal-menu/index.ts +2 -0
  269. package/src/menu-ui/components/normal-menu/normal-menu.ts +27 -0
  270. package/src/menu-ui/components/normal-menu/normal-menu.vue +162 -0
  271. package/src/menu-ui/components/sub-menu-content.vue +105 -0
  272. package/src/menu-ui/components/sub-menu.vue +276 -0
  273. package/src/menu-ui/hooks/index.ts +2 -0
  274. package/src/menu-ui/hooks/use-menu-context.ts +55 -0
  275. package/src/menu-ui/hooks/use-menu-scroll.ts +46 -0
  276. package/src/menu-ui/hooks/use-menu.ts +48 -0
  277. package/src/menu-ui/index.ts +4 -0
  278. package/src/menu-ui/menu.vue +32 -0
  279. package/src/menu-ui/sub-menu.vue +71 -0
  280. package/src/menu-ui/types.ts +154 -0
  281. package/src/menu-ui/utils/index.ts +52 -0
  282. package/src/plugins/echarts/README.md +41 -0
  283. package/src/plugins/echarts/echarts-ui.vue +15 -0
  284. package/src/plugins/echarts/echarts.ts +40 -0
  285. package/src/plugins/echarts/index.ts +4 -0
  286. package/src/plugins/echarts/types.ts +28 -0
  287. package/src/plugins/echarts/use-echarts.ts +213 -0
  288. package/src/plugins/index.ts +2 -0
  289. package/src/plugins/motion/README.md +26 -0
  290. package/src/plugins/motion/index.ts +8 -0
  291. package/src/plugins/motion/types.ts +26 -0
  292. package/src/plugins/plugins-context.ts +39 -0
  293. package/src/plugins/tiptap/extensions.ts +452 -0
  294. package/src/plugins/tiptap/index.ts +4 -0
  295. package/src/plugins/tiptap/preview.vue +34 -0
  296. package/src/plugins/tiptap/style.css +112 -0
  297. package/src/plugins/tiptap/tiptap.vue +324 -0
  298. package/src/plugins/tiptap/toolbar.ts +374 -0
  299. package/src/plugins/tiptap/types.ts +87 -0
  300. package/src/plugins/tiptap/use-tiptap-toolbar.ts +176 -0
  301. package/src/plugins/types.ts +24 -0
  302. package/src/plugins/vxe-table/README.md +50 -0
  303. package/src/plugins/vxe-table/api.ts +202 -0
  304. package/src/plugins/vxe-table/extends.ts +150 -0
  305. package/src/plugins/vxe-table/index.ts +10 -0
  306. package/src/plugins/vxe-table/init.ts +151 -0
  307. package/src/plugins/vxe-table/style.css +128 -0
  308. package/src/plugins/vxe-table/types.ts +116 -0
  309. package/src/plugins/vxe-table/use-vxe-grid.ts +86 -0
  310. package/src/plugins/vxe-table/use-vxe-grid.vue +564 -0
  311. package/src/plugins/vxe-table/viewed-row/index.ts +8 -0
  312. package/src/plugins/vxe-table/viewed-row/types.ts +84 -0
  313. package/src/plugins/vxe-table/viewed-row/use-viewed-row.ts +590 -0
  314. package/src/popup-ui/alert/AlertBuilder.ts +244 -0
  315. package/src/popup-ui/alert/alert.ts +101 -0
  316. package/src/popup-ui/alert/alert.vue +220 -0
  317. package/src/popup-ui/alert/index.ts +14 -0
  318. package/src/popup-ui/drawer/drawer-api.ts +178 -0
  319. package/src/popup-ui/drawer/drawer.ts +192 -0
  320. package/src/popup-ui/drawer/drawer.vue +359 -0
  321. package/src/popup-ui/drawer/index.ts +7 -0
  322. package/src/popup-ui/drawer/use-drawer.ts +210 -0
  323. package/src/popup-ui/index.ts +3 -0
  324. package/src/popup-ui/modal/index.ts +7 -0
  325. package/src/popup-ui/modal/modal-api.ts +191 -0
  326. package/src/popup-ui/modal/modal.ts +212 -0
  327. package/src/popup-ui/modal/modal.vue +395 -0
  328. package/src/popup-ui/modal/use-modal-draggable.ts +141 -0
  329. package/src/popup-ui/modal/use-modal.ts +222 -0
  330. package/src/shadcn-ui/assets/index.css +1 -0
  331. package/src/shadcn-ui/components/avatar/avatar.vue +76 -0
  332. package/src/shadcn-ui/components/avatar/index.ts +1 -0
  333. package/src/shadcn-ui/components/back-top/back-top.vue +43 -0
  334. package/src/shadcn-ui/components/back-top/backtop.ts +38 -0
  335. package/src/shadcn-ui/components/back-top/index.ts +1 -0
  336. package/src/shadcn-ui/components/back-top/use-backtop.ts +45 -0
  337. package/src/shadcn-ui/components/breadcrumb/breadcrumb-background.vue +111 -0
  338. package/src/shadcn-ui/components/breadcrumb/breadcrumb-view.vue +39 -0
  339. package/src/shadcn-ui/components/breadcrumb/breadcrumb.vue +98 -0
  340. package/src/shadcn-ui/components/breadcrumb/index.ts +3 -0
  341. package/src/shadcn-ui/components/breadcrumb/types.ts +17 -0
  342. package/src/shadcn-ui/components/button/button-group.vue +98 -0
  343. package/src/shadcn-ui/components/button/button.ts +55 -0
  344. package/src/shadcn-ui/components/button/button.vue +42 -0
  345. package/src/shadcn-ui/components/button/check-button-group.vue +196 -0
  346. package/src/shadcn-ui/components/button/icon-button.vue +78 -0
  347. package/src/shadcn-ui/components/button/index.ts +5 -0
  348. package/src/shadcn-ui/components/checkbox/checkbox.vue +26 -0
  349. package/src/shadcn-ui/components/checkbox/index.ts +1 -0
  350. package/src/shadcn-ui/components/collapsible/collapsible-params-item.vue +123 -0
  351. package/src/shadcn-ui/components/collapsible/collapsible-params.vue +268 -0
  352. package/src/shadcn-ui/components/collapsible/collapsible.vue +79 -0
  353. package/src/shadcn-ui/components/collapsible/index.ts +4 -0
  354. package/src/shadcn-ui/components/collapsible/type.ts +22 -0
  355. package/src/shadcn-ui/components/context-menu/context-menu.vue +128 -0
  356. package/src/shadcn-ui/components/context-menu/index.ts +3 -0
  357. package/src/shadcn-ui/components/context-menu/interface.ts +42 -0
  358. package/src/shadcn-ui/components/count-to-animator/count-to-animator.vue +128 -0
  359. package/src/shadcn-ui/components/count-to-animator/index.ts +1 -0
  360. package/src/shadcn-ui/components/descriptions/descriptions-cell.vue +129 -0
  361. package/src/shadcn-ui/components/descriptions/descriptions-item.vue +57 -0
  362. package/src/shadcn-ui/components/descriptions/descriptions-row.vue +112 -0
  363. package/src/shadcn-ui/components/descriptions/descriptions.vue +91 -0
  364. package/src/shadcn-ui/components/descriptions/index.ts +4 -0
  365. package/src/shadcn-ui/components/descriptions/types.ts +93 -0
  366. package/src/shadcn-ui/components/descriptions/use-descriptions.ts +216 -0
  367. package/src/shadcn-ui/components/dropdown-menu/dropdown-menu.vue +49 -0
  368. package/src/shadcn-ui/components/dropdown-menu/dropdown-radio-menu.vue +52 -0
  369. package/src/shadcn-ui/components/dropdown-menu/index.ts +4 -0
  370. package/src/shadcn-ui/components/dropdown-menu/interface.ts +32 -0
  371. package/src/shadcn-ui/components/expandable-arrow/expandable-arrow.vue +31 -0
  372. package/src/shadcn-ui/components/expandable-arrow/index.ts +1 -0
  373. package/src/shadcn-ui/components/full-screen/full-screen.vue +41 -0
  374. package/src/shadcn-ui/components/full-screen/index.ts +1 -0
  375. package/src/shadcn-ui/components/hover-card/hover-card.vue +55 -0
  376. package/src/shadcn-ui/components/hover-card/index.ts +2 -0
  377. package/src/shadcn-ui/components/icon/icon.vue +35 -0
  378. package/src/shadcn-ui/components/icon/index.ts +1 -0
  379. package/src/shadcn-ui/components/index.ts +26 -0
  380. package/src/shadcn-ui/components/input-password/index.ts +1 -0
  381. package/src/shadcn-ui/components/input-password/input-password.vue +57 -0
  382. package/src/shadcn-ui/components/input-password/password-strength.vue +66 -0
  383. package/src/shadcn-ui/components/logo/index.ts +1 -0
  384. package/src/shadcn-ui/components/logo/logo.vue +152 -0
  385. package/src/shadcn-ui/components/pin-input/index.ts +3 -0
  386. package/src/shadcn-ui/components/pin-input/input.vue +122 -0
  387. package/src/shadcn-ui/components/pin-input/types.ts +30 -0
  388. package/src/shadcn-ui/components/popover/index.ts +1 -0
  389. package/src/shadcn-ui/components/popover/popover.vue +60 -0
  390. package/src/shadcn-ui/components/render-content/index.ts +1 -0
  391. package/src/shadcn-ui/components/render-content/render-content.vue +59 -0
  392. package/src/shadcn-ui/components/scrollbar/index.ts +1 -0
  393. package/src/shadcn-ui/components/scrollbar/scrollbar.vue +165 -0
  394. package/src/shadcn-ui/components/segmented/index.ts +3 -0
  395. package/src/shadcn-ui/components/segmented/segmented.vue +75 -0
  396. package/src/shadcn-ui/components/segmented/tabs-indicator.vue +37 -0
  397. package/src/shadcn-ui/components/segmented/types.ts +6 -0
  398. package/src/shadcn-ui/components/select/index.ts +1 -0
  399. package/src/shadcn-ui/components/select/select.vue +57 -0
  400. package/src/shadcn-ui/components/spine-text/index.ts +1 -0
  401. package/src/shadcn-ui/components/spine-text/spine-text.vue +49 -0
  402. package/src/shadcn-ui/components/spinner/index.ts +2 -0
  403. package/src/shadcn-ui/components/spinner/loading.vue +140 -0
  404. package/src/shadcn-ui/components/spinner/spinner.vue +138 -0
  405. package/src/shadcn-ui/components/table-action/action-dropdown-item.vue +116 -0
  406. package/src/shadcn-ui/components/table-action/action-item.vue +98 -0
  407. package/src/shadcn-ui/components/table-action/index.ts +3 -0
  408. package/src/shadcn-ui/components/table-action/table-action.vue +218 -0
  409. package/src/shadcn-ui/components/table-action/types.ts +75 -0
  410. package/src/shadcn-ui/components/tooltip/help-tooltip.vue +31 -0
  411. package/src/shadcn-ui/components/tooltip/index.ts +2 -0
  412. package/src/shadcn-ui/components/tooltip/tooltip.vue +44 -0
  413. package/src/shadcn-ui/components.json +15 -0
  414. package/src/shadcn-ui/index.ts +39 -0
  415. package/src/shadcn-ui/ui/accordion/Accordion.vue +16 -0
  416. package/src/shadcn-ui/ui/accordion/AccordionContent.vue +28 -0
  417. package/src/shadcn-ui/ui/accordion/AccordionItem.vue +29 -0
  418. package/src/shadcn-ui/ui/accordion/AccordionTrigger.vue +39 -0
  419. package/src/shadcn-ui/ui/accordion/index.ts +4 -0
  420. package/src/shadcn-ui/ui/alert-dialog/AlertDialog.vue +20 -0
  421. package/src/shadcn-ui/ui/alert-dialog/AlertDialogAction.vue +13 -0
  422. package/src/shadcn-ui/ui/alert-dialog/AlertDialogCancel.vue +13 -0
  423. package/src/shadcn-ui/ui/alert-dialog/AlertDialogContent.vue +101 -0
  424. package/src/shadcn-ui/ui/alert-dialog/AlertDialogDescription.vue +28 -0
  425. package/src/shadcn-ui/ui/alert-dialog/AlertDialogFooter.vue +20 -0
  426. package/src/shadcn-ui/ui/alert-dialog/AlertDialogHeader.vue +18 -0
  427. package/src/shadcn-ui/ui/alert-dialog/AlertDialogOverlay.vue +31 -0
  428. package/src/shadcn-ui/ui/alert-dialog/AlertDialogTitle.vue +26 -0
  429. package/src/shadcn-ui/ui/alert-dialog/AlertDialogTrigger.vue +13 -0
  430. package/src/shadcn-ui/ui/alert-dialog/index.ts +9 -0
  431. package/src/shadcn-ui/ui/avatar/Avatar.vue +25 -0
  432. package/src/shadcn-ui/ui/avatar/AvatarFallback.vue +31 -0
  433. package/src/shadcn-ui/ui/avatar/AvatarImage.vue +17 -0
  434. package/src/shadcn-ui/ui/avatar/index.ts +3 -0
  435. package/src/shadcn-ui/ui/badge/Badge.vue +33 -0
  436. package/src/shadcn-ui/ui/badge/badge.ts +25 -0
  437. package/src/shadcn-ui/ui/badge/index.ts +3 -0
  438. package/src/shadcn-ui/ui/breadcrumb/Breadcrumb.vue +13 -0
  439. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbEllipsis.vue +25 -0
  440. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbItem.vue +18 -0
  441. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbLink.vue +27 -0
  442. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbList.vue +23 -0
  443. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbPage.vue +21 -0
  444. package/src/shadcn-ui/ui/breadcrumb/BreadcrumbSeparator.vue +24 -0
  445. package/src/shadcn-ui/ui/breadcrumb/index.ts +7 -0
  446. package/src/shadcn-ui/ui/button/Button.vue +36 -0
  447. package/src/shadcn-ui/ui/button/button.ts +35 -0
  448. package/src/shadcn-ui/ui/button/index.ts +3 -0
  449. package/src/shadcn-ui/ui/card/Card.vue +23 -0
  450. package/src/shadcn-ui/ui/card/CardAction.vue +23 -0
  451. package/src/shadcn-ui/ui/card/CardContent.vue +15 -0
  452. package/src/shadcn-ui/ui/card/CardDescription.vue +18 -0
  453. package/src/shadcn-ui/ui/card/CardFooter.vue +18 -0
  454. package/src/shadcn-ui/ui/card/CardHeader.vue +23 -0
  455. package/src/shadcn-ui/ui/card/CardTitle.vue +18 -0
  456. package/src/shadcn-ui/ui/card/index.ts +7 -0
  457. package/src/shadcn-ui/ui/checkbox/Checkbox.vue +49 -0
  458. package/src/shadcn-ui/ui/checkbox/index.ts +1 -0
  459. package/src/shadcn-ui/ui/context-menu/ContextMenu.vue +18 -0
  460. package/src/shadcn-ui/ui/context-menu/ContextMenuCheckboxItem.vue +51 -0
  461. package/src/shadcn-ui/ui/context-menu/ContextMenuContent.vue +44 -0
  462. package/src/shadcn-ui/ui/context-menu/ContextMenuGroup.vue +13 -0
  463. package/src/shadcn-ui/ui/context-menu/ContextMenuItem.vue +45 -0
  464. package/src/shadcn-ui/ui/context-menu/ContextMenuLabel.vue +32 -0
  465. package/src/shadcn-ui/ui/context-menu/ContextMenuPortal.vue +13 -0
  466. package/src/shadcn-ui/ui/context-menu/ContextMenuRadioGroup.vue +22 -0
  467. package/src/shadcn-ui/ui/context-menu/ContextMenuRadioItem.vue +51 -0
  468. package/src/shadcn-ui/ui/context-menu/ContextMenuSeparator.vue +24 -0
  469. package/src/shadcn-ui/ui/context-menu/ContextMenuShortcut.vue +20 -0
  470. package/src/shadcn-ui/ui/context-menu/ContextMenuSub.vue +16 -0
  471. package/src/shadcn-ui/ui/context-menu/ContextMenuSubContent.vue +37 -0
  472. package/src/shadcn-ui/ui/context-menu/ContextMenuSubTrigger.vue +39 -0
  473. package/src/shadcn-ui/ui/context-menu/ContextMenuTrigger.vue +15 -0
  474. package/src/shadcn-ui/ui/context-menu/index.ts +14 -0
  475. package/src/shadcn-ui/ui/dialog/Dialog.vue +16 -0
  476. package/src/shadcn-ui/ui/dialog/DialogClose.vue +13 -0
  477. package/src/shadcn-ui/ui/dialog/DialogContent.vue +149 -0
  478. package/src/shadcn-ui/ui/dialog/DialogDescription.vue +28 -0
  479. package/src/shadcn-ui/ui/dialog/DialogFooter.vue +33 -0
  480. package/src/shadcn-ui/ui/dialog/DialogHeader.vue +18 -0
  481. package/src/shadcn-ui/ui/dialog/DialogOverlay.vue +34 -0
  482. package/src/shadcn-ui/ui/dialog/DialogScrollContent.vue +69 -0
  483. package/src/shadcn-ui/ui/dialog/DialogTitle.vue +28 -0
  484. package/src/shadcn-ui/ui/dialog/DialogTrigger.vue +13 -0
  485. package/src/shadcn-ui/ui/dialog/index.ts +10 -0
  486. package/src/shadcn-ui/ui/dialog/use-dialog-state-events.ts +84 -0
  487. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenu.vue +20 -0
  488. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +51 -0
  489. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuContent.vue +50 -0
  490. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuGroup.vue +13 -0
  491. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuItem.vue +44 -0
  492. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuLabel.vue +30 -0
  493. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuRadioGroup.vue +22 -0
  494. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuRadioItem.vue +52 -0
  495. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuSeparator.vue +26 -0
  496. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuShortcut.vue +20 -0
  497. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuSub.vue +20 -0
  498. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuSubContent.vue +37 -0
  499. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuSubTrigger.vue +38 -0
  500. package/src/shadcn-ui/ui/dropdown-menu/DropdownMenuTrigger.vue +18 -0
  501. package/src/shadcn-ui/ui/dropdown-menu/index.ts +16 -0
  502. package/src/shadcn-ui/ui/form/FormControl.vue +20 -0
  503. package/src/shadcn-ui/ui/form/FormDescription.vue +23 -0
  504. package/src/shadcn-ui/ui/form/FormField.vue +35 -0
  505. package/src/shadcn-ui/ui/form/FormItem.vue +24 -0
  506. package/src/shadcn-ui/ui/form/FormLabel.vue +25 -0
  507. package/src/shadcn-ui/ui/form/FormMessage.vue +24 -0
  508. package/src/shadcn-ui/ui/form/index.ts +10 -0
  509. package/src/shadcn-ui/ui/form/injectionKeys.ts +16 -0
  510. package/src/shadcn-ui/ui/form/useFormField.ts +35 -0
  511. package/src/shadcn-ui/ui/hover-card/HoverCard.vue +16 -0
  512. package/src/shadcn-ui/ui/hover-card/HoverCardContent.vue +42 -0
  513. package/src/shadcn-ui/ui/hover-card/HoverCardTrigger.vue +13 -0
  514. package/src/shadcn-ui/ui/hover-card/index.ts +3 -0
  515. package/src/shadcn-ui/ui/index.ts +32 -0
  516. package/src/shadcn-ui/ui/input/Input.vue +54 -0
  517. package/src/shadcn-ui/ui/input/index.ts +1 -0
  518. package/src/shadcn-ui/ui/label/Label.vue +29 -0
  519. package/src/shadcn-ui/ui/label/index.ts +1 -0
  520. package/src/shadcn-ui/ui/number-field/NumberField.vue +29 -0
  521. package/src/shadcn-ui/ui/number-field/NumberFieldContent.vue +22 -0
  522. package/src/shadcn-ui/ui/number-field/NumberFieldDecrement.vue +36 -0
  523. package/src/shadcn-ui/ui/number-field/NumberFieldIncrement.vue +36 -0
  524. package/src/shadcn-ui/ui/number-field/NumberFieldInput.vue +23 -0
  525. package/src/shadcn-ui/ui/number-field/index.ts +5 -0
  526. package/src/shadcn-ui/ui/pagination/Pagination.vue +31 -0
  527. package/src/shadcn-ui/ui/pagination/PaginationContent.vue +27 -0
  528. package/src/shadcn-ui/ui/pagination/PaginationEllipsis.vue +29 -0
  529. package/src/shadcn-ui/ui/pagination/PaginationFirst.vue +50 -0
  530. package/src/shadcn-ui/ui/pagination/PaginationItem.vue +47 -0
  531. package/src/shadcn-ui/ui/pagination/PaginationLast.vue +50 -0
  532. package/src/shadcn-ui/ui/pagination/PaginationNext.vue +50 -0
  533. package/src/shadcn-ui/ui/pagination/PaginationPrevious.vue +50 -0
  534. package/src/shadcn-ui/ui/pagination/index.ts +8 -0
  535. package/src/shadcn-ui/ui/pin-input/PinInput.vue +38 -0
  536. package/src/shadcn-ui/ui/pin-input/PinInputGroup.vue +26 -0
  537. package/src/shadcn-ui/ui/pin-input/PinInputSeparator.vue +17 -0
  538. package/src/shadcn-ui/ui/pin-input/PinInputSlot.vue +31 -0
  539. package/src/shadcn-ui/ui/pin-input/index.ts +4 -0
  540. package/src/shadcn-ui/ui/popover/Popover.vue +16 -0
  541. package/src/shadcn-ui/ui/popover/PopoverAnchor.vue +13 -0
  542. package/src/shadcn-ui/ui/popover/PopoverContent.vue +44 -0
  543. package/src/shadcn-ui/ui/popover/PopoverTrigger.vue +13 -0
  544. package/src/shadcn-ui/ui/popover/index.ts +4 -0
  545. package/src/shadcn-ui/ui/progress/Progress.vue +58 -0
  546. package/src/shadcn-ui/ui/progress/index.ts +1 -0
  547. package/src/shadcn-ui/ui/radio-group/RadioGroup.vue +30 -0
  548. package/src/shadcn-ui/ui/radio-group/RadioGroupItem.vue +43 -0
  549. package/src/shadcn-ui/ui/radio-group/index.ts +2 -0
  550. package/src/shadcn-ui/ui/resizable/ResizableHandle.vue +48 -0
  551. package/src/shadcn-ui/ui/resizable/ResizablePanel.vue +32 -0
  552. package/src/shadcn-ui/ui/resizable/ResizablePanelGroup.vue +32 -0
  553. package/src/shadcn-ui/ui/resizable/index.ts +3 -0
  554. package/src/shadcn-ui/ui/scroll-area/ScrollArea.vue +40 -0
  555. package/src/shadcn-ui/ui/scroll-area/ScrollBar.vue +41 -0
  556. package/src/shadcn-ui/ui/scroll-area/index.ts +2 -0
  557. package/src/shadcn-ui/ui/select/Select.vue +16 -0
  558. package/src/shadcn-ui/ui/select/SelectContent.vue +65 -0
  559. package/src/shadcn-ui/ui/select/SelectGroup.vue +13 -0
  560. package/src/shadcn-ui/ui/select/SelectItem.vue +49 -0
  561. package/src/shadcn-ui/ui/select/SelectItemText.vue +13 -0
  562. package/src/shadcn-ui/ui/select/SelectLabel.vue +22 -0
  563. package/src/shadcn-ui/ui/select/SelectScrollDownButton.vue +33 -0
  564. package/src/shadcn-ui/ui/select/SelectScrollUpButton.vue +33 -0
  565. package/src/shadcn-ui/ui/select/SelectSeparator.vue +24 -0
  566. package/src/shadcn-ui/ui/select/SelectTrigger.vue +43 -0
  567. package/src/shadcn-ui/ui/select/SelectValue.vue +13 -0
  568. package/src/shadcn-ui/ui/select/index.ts +11 -0
  569. package/src/shadcn-ui/ui/separator/Separator.vue +33 -0
  570. package/src/shadcn-ui/ui/separator/index.ts +1 -0
  571. package/src/shadcn-ui/ui/sheet/Sheet.vue +16 -0
  572. package/src/shadcn-ui/ui/sheet/SheetClose.vue +13 -0
  573. package/src/shadcn-ui/ui/sheet/SheetContent.vue +110 -0
  574. package/src/shadcn-ui/ui/sheet/SheetDescription.vue +26 -0
  575. package/src/shadcn-ui/ui/sheet/SheetFooter.vue +16 -0
  576. package/src/shadcn-ui/ui/sheet/SheetHeader.vue +16 -0
  577. package/src/shadcn-ui/ui/sheet/SheetOverlay.vue +11 -0
  578. package/src/shadcn-ui/ui/sheet/SheetTitle.vue +26 -0
  579. package/src/shadcn-ui/ui/sheet/SheetTrigger.vue +13 -0
  580. package/src/shadcn-ui/ui/sheet/index.ts +10 -0
  581. package/src/shadcn-ui/ui/sheet/sheet.ts +24 -0
  582. package/src/shadcn-ui/ui/switch/Switch.vue +45 -0
  583. package/src/shadcn-ui/ui/switch/index.ts +1 -0
  584. package/src/shadcn-ui/ui/tabs/Tabs.vue +29 -0
  585. package/src/shadcn-ui/ui/tabs/TabsContent.vue +26 -0
  586. package/src/shadcn-ui/ui/tabs/TabsList.vue +31 -0
  587. package/src/shadcn-ui/ui/tabs/TabsTrigger.vue +33 -0
  588. package/src/shadcn-ui/ui/tabs/index.ts +5 -0
  589. package/src/shadcn-ui/ui/textarea/Textarea.vue +35 -0
  590. package/src/shadcn-ui/ui/textarea/index.ts +1 -0
  591. package/src/shadcn-ui/ui/toggle/Toggle.vue +45 -0
  592. package/src/shadcn-ui/ui/toggle/index.ts +2 -0
  593. package/src/shadcn-ui/ui/toggle/toggle.ts +27 -0
  594. package/src/shadcn-ui/ui/toggle-group/ToggleGroup.vue +64 -0
  595. package/src/shadcn-ui/ui/toggle-group/ToggleGroupItem.vue +56 -0
  596. package/src/shadcn-ui/ui/toggle-group/index.ts +2 -0
  597. package/src/shadcn-ui/ui/tooltip/Tooltip.vue +16 -0
  598. package/src/shadcn-ui/ui/tooltip/TooltipContent.vue +55 -0
  599. package/src/shadcn-ui/ui/tooltip/TooltipProvider.vue +13 -0
  600. package/src/shadcn-ui/ui/tooltip/TooltipTrigger.vue +13 -0
  601. package/src/shadcn-ui/ui/tooltip/index.ts +4 -0
  602. package/src/shadcn-ui/ui/tree/index.ts +4 -0
  603. package/src/shadcn-ui/ui/tree/tree.vue +689 -0
  604. package/src/shadcn-ui/ui/tree/types.ts +70 -0
  605. package/src/styles/index.css +9 -0
  606. package/src/tabs-ui/components/index.ts +2 -0
  607. package/src/tabs-ui/components/tabs/tabs.vue +150 -0
  608. package/src/tabs-ui/components/tabs-chrome/tabs.vue +211 -0
  609. package/src/tabs-ui/components/widgets/index.ts +3 -0
  610. package/src/tabs-ui/components/widgets/tool-more.vue +18 -0
  611. package/src/tabs-ui/components/widgets/tool-refresh.vue +18 -0
  612. package/src/tabs-ui/components/widgets/tool-screen.vue +19 -0
  613. package/src/tabs-ui/index.ts +3 -0
  614. package/src/tabs-ui/tabs-view.vue +106 -0
  615. package/src/tabs-ui/types.ts +73 -0
  616. package/src/tabs-ui/use-tabs-drag.ts +129 -0
  617. package/src/tabs-ui/use-tabs-view-scroll.ts +202 -0
  618. package/src/types/shims.d.ts +2 -0
@@ -0,0 +1,981 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted, ref, watch } from 'vue';
3
+
4
+ // 定义组件参数
5
+ const props = defineProps<{
6
+ /** 裁剪比例 格式如 '1:1', '16:9', '3:4' 等(非必填) */
7
+ aspectRatio?: string;
8
+ /** 容器高度(默认400) */
9
+ height?: number;
10
+ /** 图片地址 */
11
+ img: string;
12
+ /** 容器宽度(默认500) */
13
+ width?: number;
14
+ }>();
15
+
16
+ const CROPPER_CONSTANTS = {
17
+ MIN_WIDTH: 60 as const,
18
+ MIN_HEIGHT: 60 as const,
19
+ DEFAULT_WIDTH: 500 as const,
20
+ DEFAULT_HEIGHT: 400 as const,
21
+ PADDING_RATIO: 0.1 as const,
22
+ MAX_PADDING: 50 as const,
23
+ } as const;
24
+
25
+ type Point = [number, number]; // [clientX, clientY]
26
+ type Dimension = [number, number, number, number]; // [top, right, bottom, left]
27
+
28
+ // 拖拽点类型
29
+ type DragAction =
30
+ | 'bottom'
31
+ | 'bottom-left'
32
+ | 'bottom-right'
33
+ | 'left'
34
+ | 'move'
35
+ | 'right'
36
+ | 'top'
37
+ | 'top-left'
38
+ | 'top-right';
39
+
40
+ // DOM 引用
41
+ const containerRef = ref<HTMLDivElement | null>(null);
42
+ const bgImageRef = ref<HTMLImageElement | null>(null);
43
+ // const maskRef = ref<HTMLDivElement | null>(null);
44
+ const maskViewRef = ref<HTMLDivElement | null>(null);
45
+ const cropperRef = ref<HTMLDivElement | null>(null);
46
+ // const cropperViewRef = ref<HTMLDivElement | null>(null);
47
+
48
+ // 响应式数据
49
+ const isCropperVisible = ref<boolean>(false);
50
+ const validAspectRatio = ref<null | number>(null); // 有效比例值(null表示无固定比例)
51
+ const containerWidth = ref<number>(
52
+ props.width ?? CROPPER_CONSTANTS.DEFAULT_WIDTH,
53
+ );
54
+ const containerHeight = ref<number>(
55
+ props.height ?? CROPPER_CONSTANTS.DEFAULT_HEIGHT,
56
+ );
57
+
58
+ // 裁剪区域尺寸(top, right, bottom, left)
59
+ const currentDimension = ref<Dimension>([50, 50, 50, 50]);
60
+ const initDimension = ref<Dimension>([50, 50, 50, 50]);
61
+
62
+ // 拖拽状态
63
+ const dragging = ref<boolean>(false);
64
+ const startPoint = ref<Point>([0, 0]);
65
+ const startDimension = ref<Dimension>([0, 0, 0, 0]);
66
+ const direction = ref<Dimension>([0, 0, 0, 0]);
67
+ const moving = ref<boolean>(false);
68
+
69
+ /**
70
+ * 计算图片的适配尺寸,保证完整显示且不超过最大宽高限制
71
+ */
72
+ const calculateImageFitSize = () => {
73
+ if (!bgImageRef.value) return;
74
+
75
+ // 获取图片原始尺寸
76
+ const imgWidth = bgImageRef.value.naturalWidth;
77
+ const imgHeight = bgImageRef.value.naturalHeight;
78
+
79
+ if (imgWidth === 0 || imgHeight === 0) return;
80
+
81
+ // 计算缩放比例(使用传入的width/height,默认500/400)
82
+ const widthRatio =
83
+ (props.width ?? CROPPER_CONSTANTS.DEFAULT_WIDTH) / imgWidth;
84
+ const heightRatio =
85
+ (props.height ?? CROPPER_CONSTANTS.DEFAULT_HEIGHT) / imgHeight;
86
+ const scaleRatio = Math.min(widthRatio, heightRatio, 1); // 不放大图片,只缩小
87
+
88
+ // 计算适配后的容器尺寸
89
+ const fitWidth = Math.floor(imgWidth * scaleRatio);
90
+ const fitHeight = Math.floor(imgHeight * scaleRatio);
91
+
92
+ containerWidth.value = fitWidth;
93
+ containerHeight.value = fitHeight;
94
+
95
+ // 重置裁剪框初始尺寸(基于新的容器尺寸)
96
+ const padding = Math.min(
97
+ CROPPER_CONSTANTS.MAX_PADDING,
98
+ Math.floor(fitWidth * CROPPER_CONSTANTS.PADDING_RATIO),
99
+ Math.floor(fitHeight * CROPPER_CONSTANTS.PADDING_RATIO),
100
+ );
101
+
102
+ initDimension.value = [padding, padding, padding, padding];
103
+ currentDimension.value = [padding, padding, padding, padding];
104
+ };
105
+
106
+ /**
107
+ * 验证并解析比例字符串
108
+ * @returns {number|null} 比例值 (width/height),解析失败返回null
109
+ */
110
+ const parseAndValidateAspectRatio = (): null | number => {
111
+ // 如果未传入比例参数,直接返回null
112
+ if (!props.aspectRatio) {
113
+ return null;
114
+ }
115
+
116
+ // 验证比例格式
117
+ const ratioRegex = /^[1-9]\d*:[1-9]\d*$/;
118
+ if (!ratioRegex.test(props.aspectRatio)) {
119
+ console.warn('裁剪比例格式错误,应为 "数字:数字" 格式,如 "16:9"');
120
+ return null;
121
+ }
122
+
123
+ // 解析比例
124
+ const [width, height] = props.aspectRatio.split(':').map(Number);
125
+
126
+ // 验证解析结果有效性
127
+ if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {
128
+ console.warn('裁剪比例解析失败,宽高必须为正整数');
129
+ return null;
130
+ }
131
+
132
+ return width / height;
133
+ };
134
+
135
+ /**
136
+ * 设置裁剪区域尺寸
137
+ * @param {Dimension} dimension - [top, right, bottom, left]
138
+ */
139
+ const setDimension = (dimension: Dimension) => {
140
+ currentDimension.value = [...dimension];
141
+ if (maskViewRef.value) {
142
+ maskViewRef.value.style.clipPath = `inset(${dimension[0]}px ${dimension[1]}px ${dimension[2]}px ${dimension[3]}px)`;
143
+ }
144
+ };
145
+
146
+ /**
147
+ * 调整裁剪区域至指定比例
148
+ */
149
+ const adjustCropperToAspectRatio = () => {
150
+ if (!cropperRef.value) return;
151
+
152
+ // 验证并解析比例
153
+ validAspectRatio.value = parseAndValidateAspectRatio();
154
+
155
+ // 如果无有效比例,使用初始尺寸,不强制固定比例
156
+ if (validAspectRatio.value === null) {
157
+ setDimension(initDimension.value);
158
+ return;
159
+ }
160
+
161
+ // 有有效比例,按比例调整裁剪框
162
+ const ratio = validAspectRatio.value;
163
+ const containerWidthVal = containerWidth.value;
164
+ const containerHeightVal = containerHeight.value;
165
+
166
+ // 根据比例计算裁剪框尺寸
167
+ let newHeight: number, newWidth: number;
168
+
169
+ // 先按宽度优先计算
170
+ newWidth = containerWidthVal;
171
+ newHeight = newWidth / ratio;
172
+
173
+ // 如果高度超出容器,按高度优先计算
174
+ if (newHeight > containerHeightVal) {
175
+ newHeight = containerHeightVal;
176
+ newWidth = newHeight * ratio;
177
+ }
178
+
179
+ // 居中显示
180
+ const leftRight = (containerWidthVal - newWidth) / 2;
181
+ const topBottom = (containerHeightVal - newHeight) / 2;
182
+
183
+ const newDimension: Dimension = [topBottom, leftRight, topBottom, leftRight];
184
+
185
+ setDimension(newDimension);
186
+ };
187
+
188
+ /**
189
+ * 创建裁剪器
190
+ */
191
+ const createCropper = () => {
192
+ // 计算图片适配尺寸
193
+ calculateImageFitSize();
194
+
195
+ isCropperVisible.value = true;
196
+ adjustCropperToAspectRatio();
197
+ };
198
+
199
+ /**
200
+ * 处理鼠标按下事件
201
+ * @param {MouseEvent} e - 鼠标事件
202
+ * @param {DragAction} action - 操作类型
203
+ */
204
+ const handleMouseDown = (e: MouseEvent, action: DragAction) => {
205
+ dragging.value = true;
206
+ startPoint.value = [e.clientX, e.clientY];
207
+ startDimension.value = [...currentDimension.value];
208
+ direction.value = [0, 0, 0, 0];
209
+ moving.value = false;
210
+
211
+ // 处理移动
212
+ if (action === 'move') {
213
+ direction.value[0] = 1;
214
+ direction.value[2] = -1;
215
+ direction.value[3] = 1;
216
+ direction.value[1] = -1;
217
+ moving.value = true;
218
+ return;
219
+ }
220
+
221
+ // 处理拖拽方向
222
+ switch (action) {
223
+ case 'bottom': {
224
+ direction.value[2] = -1;
225
+ break;
226
+ }
227
+ case 'bottom-left': {
228
+ direction.value[2] = -1;
229
+ direction.value[3] = 1;
230
+ break;
231
+ }
232
+ case 'bottom-right': {
233
+ direction.value[2] = -1;
234
+ direction.value[1] = -1;
235
+ break;
236
+ }
237
+ case 'left': {
238
+ direction.value[3] = 1;
239
+ break;
240
+ }
241
+ case 'right': {
242
+ direction.value[1] = -1;
243
+ break;
244
+ }
245
+ case 'top': {
246
+ direction.value[0] = 1;
247
+ break;
248
+ }
249
+ case 'top-left': {
250
+ direction.value[0] = 1;
251
+ direction.value[3] = 1;
252
+ break;
253
+ }
254
+ case 'top-right': {
255
+ direction.value[0] = 1;
256
+ direction.value[1] = -1;
257
+ break;
258
+ }
259
+ }
260
+ };
261
+
262
+ /**
263
+ * 处理鼠标移动事件
264
+ * @param {MouseEvent} e - 鼠标事件
265
+ */
266
+ const handleMouseMove = (e: MouseEvent) => {
267
+ if (!dragging.value || !cropperRef.value) return;
268
+
269
+ const { clientX, clientY } = e;
270
+ const diffX = clientX - startPoint.value[0];
271
+ const diffY = clientY - startPoint.value[1];
272
+
273
+ // 处理移动裁剪框
274
+ if (moving.value) {
275
+ handleMoveCropBox(diffX, diffY);
276
+ return;
277
+ }
278
+
279
+ // 无有效比例
280
+ if (validAspectRatio.value === null) {
281
+ handleFreeAspectResize(diffX, diffY);
282
+ } else {
283
+ handleFixedAspectResize(diffX, diffY);
284
+ }
285
+ };
286
+
287
+ const handleMoveCropBox = (diffX: number, diffY: number) => {
288
+ const newDimension = [...startDimension.value] as Dimension;
289
+
290
+ // 计算临时偏移后的位置
291
+ const tempTop = startDimension.value[0] + diffY;
292
+ const tempLeft = startDimension.value[3] + diffX;
293
+
294
+ // 计算裁剪框的固定尺寸
295
+ const cropWidth =
296
+ containerWidth.value - startDimension.value[3] - startDimension.value[1];
297
+ const cropHeight =
298
+ containerHeight.value - startDimension.value[0] - startDimension.value[2];
299
+
300
+ // 边界限制:确保裁剪框完全在容器内,且尺寸不变
301
+ // 顶部边界:top >= 0,且 bottom = 容器高度 - top - 裁剪高度 >= 0
302
+ newDimension[0] = Math.max(
303
+ 0,
304
+ Math.min(tempTop, containerHeight.value - cropHeight),
305
+ );
306
+ // 底部边界:bottom = 容器高度 - top - 裁剪高度(由top推导,无需额外计算)
307
+ newDimension[2] = containerHeight.value - newDimension[0] - cropHeight;
308
+ // 左侧边界:left >= 0,且 right = 容器宽度 - left - 裁剪宽度 >= 0
309
+ newDimension[3] = Math.max(
310
+ 0,
311
+ Math.min(tempLeft, containerWidth.value - cropWidth),
312
+ );
313
+ // 右侧边界:right = 容器宽度 - left - 裁剪宽度(由left推导,无需额外计算)
314
+ newDimension[1] = containerWidth.value - newDimension[3] - cropWidth;
315
+
316
+ // 强制保证尺寸不变(兜底)
317
+ const finalWidth = containerWidth.value - newDimension[3] - newDimension[1];
318
+ const finalHeight = containerHeight.value - newDimension[0] - newDimension[2];
319
+
320
+ if (finalWidth !== cropWidth) {
321
+ newDimension[1] = containerWidth.value - newDimension[3] - cropWidth;
322
+ }
323
+
324
+ if (finalHeight !== cropHeight) {
325
+ newDimension[2] = containerHeight.value - newDimension[0] - cropHeight;
326
+ }
327
+
328
+ // 更新裁剪区域(仅位置变化,尺寸/比例完全不变)
329
+ setDimension(newDimension);
330
+ };
331
+
332
+ const handleFreeAspectResize = (diffX: number, diffY: number) => {
333
+ const cropperWidth = containerWidth.value;
334
+ const cropperHeight = containerHeight.value;
335
+ const currentDimensionNew: Dimension = [0, 0, 0, 0];
336
+
337
+ // 计算新的尺寸,确保不小于最小值
338
+ currentDimensionNew[0] = Math.min(
339
+ Math.max(startDimension.value[0] + direction.value[0] * diffY, 0),
340
+ cropperHeight - CROPPER_CONSTANTS.MIN_HEIGHT,
341
+ );
342
+
343
+ currentDimensionNew[1] = Math.min(
344
+ Math.max(startDimension.value[1] + direction.value[1] * diffX, 0),
345
+ cropperWidth - CROPPER_CONSTANTS.MIN_WIDTH,
346
+ );
347
+
348
+ currentDimensionNew[2] = Math.min(
349
+ Math.max(startDimension.value[2] + direction.value[2] * diffY, 0),
350
+ cropperHeight - CROPPER_CONSTANTS.MIN_HEIGHT,
351
+ );
352
+
353
+ currentDimensionNew[3] = Math.min(
354
+ Math.max(startDimension.value[3] + direction.value[3] * diffX, 0),
355
+ cropperWidth - CROPPER_CONSTANTS.MIN_WIDTH,
356
+ );
357
+
358
+ // 确保裁剪区域宽度和高度不小于最小值
359
+ const newWidth =
360
+ cropperWidth - currentDimensionNew[3] - currentDimensionNew[1];
361
+ const newHeight =
362
+ cropperHeight - currentDimensionNew[0] - currentDimensionNew[2];
363
+
364
+ if (newWidth < CROPPER_CONSTANTS.MIN_WIDTH) {
365
+ if (direction.value[3] === 1) {
366
+ currentDimensionNew[3] =
367
+ cropperWidth - currentDimensionNew[1] - CROPPER_CONSTANTS.MIN_WIDTH;
368
+ } else {
369
+ currentDimensionNew[1] =
370
+ cropperWidth - currentDimensionNew[3] - CROPPER_CONSTANTS.MIN_WIDTH;
371
+ }
372
+ }
373
+
374
+ if (newHeight < CROPPER_CONSTANTS.MIN_HEIGHT) {
375
+ if (direction.value[0] === 1) {
376
+ currentDimensionNew[0] =
377
+ cropperHeight - currentDimensionNew[2] - CROPPER_CONSTANTS.MIN_HEIGHT;
378
+ } else {
379
+ currentDimensionNew[2] =
380
+ cropperHeight - currentDimensionNew[0] - CROPPER_CONSTANTS.MIN_HEIGHT;
381
+ }
382
+ }
383
+
384
+ setDimension(currentDimensionNew);
385
+ };
386
+
387
+ const handleFixedAspectResize = (diffX: number, diffY: number) => {
388
+ if (validAspectRatio.value === null) return;
389
+ const cropperWidth = containerWidth.value;
390
+ const cropperHeight = containerHeight.value;
391
+ // 有有效比例 - 固定比例裁剪
392
+ const ratio = validAspectRatio.value;
393
+ const currentWidth =
394
+ cropperWidth - startDimension.value[3] - startDimension.value[1];
395
+ const currentHeight =
396
+ cropperHeight - startDimension.value[0] - startDimension.value[2];
397
+
398
+ let newHeight: number, newWidth: number;
399
+ let widthChange = 0;
400
+ let heightChange = 0;
401
+
402
+ // 计算宽度/高度变化量
403
+ if (direction.value[3] === 1) widthChange = -diffX;
404
+ else if (direction.value[1] === -1) widthChange = diffX;
405
+
406
+ if (direction.value[0] === 1) heightChange = -diffY;
407
+ else if (direction.value[2] === -1) heightChange = diffY;
408
+
409
+ const isCornerDrag =
410
+ (direction.value[3] === 1 || direction.value[1] === -1) &&
411
+ (direction.value[0] === 1 || direction.value[2] === -1);
412
+
413
+ // 计算新尺寸
414
+ if (isCornerDrag) {
415
+ if (Math.abs(widthChange) > Math.abs(heightChange)) {
416
+ newWidth = Math.max(
417
+ CROPPER_CONSTANTS.MIN_WIDTH,
418
+ currentWidth + widthChange,
419
+ );
420
+ newHeight = newWidth / ratio;
421
+ } else {
422
+ newHeight = Math.max(
423
+ CROPPER_CONSTANTS.MIN_HEIGHT,
424
+ currentHeight + heightChange,
425
+ );
426
+ newWidth = newHeight * ratio;
427
+ }
428
+ } else {
429
+ if (direction.value[3] === 1 || direction.value[1] === -1) {
430
+ newWidth = Math.max(
431
+ CROPPER_CONSTANTS.MIN_WIDTH,
432
+ currentWidth + widthChange,
433
+ );
434
+ newHeight = newWidth / ratio;
435
+ } else {
436
+ newHeight = Math.max(
437
+ CROPPER_CONSTANTS.MIN_HEIGHT,
438
+ currentHeight + heightChange,
439
+ );
440
+ newWidth = newHeight * ratio;
441
+ }
442
+ }
443
+
444
+ // 限制最大尺寸
445
+ const maxWidth = cropperWidth;
446
+ const maxHeight = cropperHeight;
447
+
448
+ if (newWidth > maxWidth) {
449
+ newWidth = maxWidth;
450
+ newHeight = newWidth / ratio;
451
+ }
452
+
453
+ if (newHeight > maxHeight) {
454
+ newHeight = maxHeight;
455
+ newWidth = newHeight * ratio;
456
+ }
457
+
458
+ // 计算新的位置
459
+ let newLeft = startDimension.value[3];
460
+ let newTop = startDimension.value[0];
461
+ let newRight = startDimension.value[1];
462
+ let newBottom = startDimension.value[2];
463
+
464
+ // 根据拖拽方向调整位置
465
+ if (direction.value[3] === 1) {
466
+ newLeft = cropperWidth - newWidth - startDimension.value[1];
467
+ } else if (direction.value[1] === -1) {
468
+ newRight = cropperWidth - newWidth - startDimension.value[3];
469
+ } else if (!isCornerDrag) {
470
+ // 居中调整
471
+ const currentHorizontalCenter = startDimension.value[3] + currentWidth / 2;
472
+ newLeft = Math.max(
473
+ 0,
474
+ Math.min(cropperWidth - newWidth, currentHorizontalCenter - newWidth / 2),
475
+ );
476
+ newRight = cropperWidth - newWidth - newLeft;
477
+ }
478
+
479
+ if (direction.value[0] === 1) {
480
+ newTop = cropperHeight - newHeight - startDimension.value[2];
481
+ } else if (direction.value[2] === -1) {
482
+ newBottom = cropperHeight - newHeight - startDimension.value[0];
483
+ } else if (!isCornerDrag) {
484
+ // 居中调整
485
+ const currentVerticalCenter = startDimension.value[0] + currentHeight / 2;
486
+ newTop = Math.max(
487
+ 0,
488
+ Math.min(
489
+ cropperHeight - newHeight,
490
+ currentVerticalCenter - newHeight / 2,
491
+ ),
492
+ );
493
+ newBottom = cropperHeight - newHeight - newTop;
494
+ }
495
+
496
+ // 边界检查
497
+ newLeft = Math.max(0, newLeft);
498
+ newTop = Math.max(0, newTop);
499
+ newRight = Math.max(0, newRight);
500
+ newBottom = Math.max(0, newBottom);
501
+
502
+ const newDimension: Dimension = [newTop, newRight, newBottom, newLeft];
503
+ setDimension(newDimension);
504
+ };
505
+
506
+ /**
507
+ * 处理鼠标抬起事件
508
+ */
509
+ const handleMouseUp = () => {
510
+ dragging.value = false;
511
+ moving.value = false;
512
+ direction.value = [0, 0, 0, 0];
513
+ };
514
+
515
+ /**
516
+ * 处理图片加载完成
517
+ */
518
+ const handleImageLoad = () => {
519
+ createCropper();
520
+ };
521
+
522
+ /**
523
+ * 裁剪图片
524
+ * @param {'image/jpeg' | 'image/png'} format - 输出图片格式
525
+ * @param {number} quality - 压缩质量(0-1)
526
+ * @param {'blob' | 'base64'} outputType - 输出类型
527
+ * @param {number} targetWidth - 目标宽度(可选,不传则为原始裁剪宽度)
528
+ * @param {number} targetHeight - 目标高度(可选,不传则为原始裁剪高度)
529
+ */
530
+ const getCropImage = async (
531
+ format: 'image/jpeg' | 'image/png' = 'image/png',
532
+ quality: number = 0.92,
533
+ outputType: 'base64' | 'blob' = 'blob',
534
+ targetWidth?: number,
535
+ targetHeight?: number,
536
+ ): Promise<Blob | string | undefined> => {
537
+ if (!props.img || !bgImageRef.value || !containerRef.value) return;
538
+
539
+ // 质量参数边界修正:强制限制在 0-1 区间,防止传入非法值报错
540
+ const validQuality = Math.max(0, Math.min(1, quality));
541
+
542
+ // 创建临时图片对象获取原始尺寸
543
+ const tempImg = new Image();
544
+ // 跨域图片处理:仅对非同源的网络图片设置跨域匿名
545
+ if (props.img.startsWith('http://') || props.img.startsWith('https://')) {
546
+ try {
547
+ const url = new URL(props.img);
548
+ if (url.origin !== location.origin) {
549
+ tempImg.crossOrigin = 'anonymous';
550
+ }
551
+ } catch {
552
+ // Invalid URL,跳过跨域配置,不中断执行
553
+ }
554
+ }
555
+
556
+ // 等待临时图片加载完成
557
+ await new Promise<void>((resolve, reject) => {
558
+ const timeout = setTimeout(() => {
559
+ tempImg.removeEventListener('load', handleLoad);
560
+ tempImg.removeEventListener('error', handleError);
561
+ reject(new Error('图片加载超时,超时时间10秒'));
562
+ }, 10_000);
563
+ const handleLoad = () => {
564
+ clearTimeout(timeout);
565
+ tempImg.removeEventListener('load', handleLoad);
566
+ tempImg.removeEventListener('error', handleError);
567
+ resolve();
568
+ };
569
+
570
+ const handleError = (err: ErrorEvent) => {
571
+ clearTimeout(timeout);
572
+ tempImg.removeEventListener('load', handleLoad);
573
+ tempImg.removeEventListener('error', handleError);
574
+ reject(new Error(`图片加载失败: ${err.message}`));
575
+ };
576
+
577
+ tempImg.addEventListener('load', handleLoad);
578
+ tempImg.addEventListener('error', handleError);
579
+ tempImg.src = props.img;
580
+ });
581
+
582
+ const containerRect = containerRef.value.getBoundingClientRect();
583
+ const imgRect = bgImageRef.value.getBoundingClientRect();
584
+
585
+ // 1. 计算图片在容器内的渲染参数
586
+ const containerWidth = containerRect.width;
587
+ const containerHeight = containerRect.height;
588
+ const renderedImgWidth = imgRect.width;
589
+ const renderedImgHeight = imgRect.height;
590
+ const imgOffsetX = (containerWidth - renderedImgWidth) / 2;
591
+ const imgOffsetY = (containerHeight - renderedImgHeight) / 2;
592
+
593
+ // 2. 计算裁剪框在容器内的实际坐标
594
+ const [cropTop, cropRight, cropBottom, cropLeft] = currentDimension.value;
595
+ const cropBoxWidth = containerWidth - cropLeft - cropRight;
596
+ const cropBoxHeight = containerHeight - cropTop - cropBottom;
597
+
598
+ // 3. 将裁剪框坐标转换为图片上的坐标(考虑图片偏移)
599
+ const cropOnImgX = cropLeft - imgOffsetX;
600
+ const cropOnImgY = cropTop - imgOffsetY;
601
+
602
+ // 4. 计算渲染图片到原始图片的缩放比例(保留原始像素)
603
+ const scaleX = tempImg.width / renderedImgWidth;
604
+ const scaleY = tempImg.height / renderedImgHeight;
605
+
606
+ // 5. 映射到原始图片的裁剪区域(精确到原始像素,防止越界)
607
+ const originalCropX = Math.max(0, Math.floor(cropOnImgX * scaleX));
608
+ const originalCropY = Math.max(0, Math.floor(cropOnImgY * scaleY));
609
+ const originalCropWidth = Math.min(
610
+ Math.floor(cropBoxWidth * scaleX),
611
+ tempImg.width - originalCropX,
612
+ );
613
+ const originalCropHeight = Math.min(
614
+ Math.floor(cropBoxHeight * scaleY),
615
+ tempImg.height - originalCropY,
616
+ );
617
+
618
+ // 边界校验:裁剪尺寸非法则返回
619
+ if (originalCropWidth <= 0 || originalCropHeight <= 0) return;
620
+
621
+ // 6. 处理高清屏适配(解决Retina屏模糊)
622
+ const dpr = window.devicePixelRatio || 1;
623
+
624
+ // 最终画布尺寸(优先使用传入的目标尺寸,无则用原始裁剪尺寸)
625
+ const finalWidth = targetWidth ? Math.max(1, targetWidth) : originalCropWidth;
626
+ const finalHeight = targetHeight
627
+ ? Math.max(1, targetHeight)
628
+ : originalCropHeight;
629
+
630
+ // 创建画布并获取绘制上下文
631
+ const canvas = document.createElement('canvas');
632
+ const ctx = canvas.getContext('2d');
633
+ if (!ctx) return;
634
+
635
+ // 画布物理尺寸(乘以设备像素比,保证高清无模糊)
636
+ canvas.width = finalWidth * dpr;
637
+ canvas.height = finalHeight * dpr;
638
+
639
+ // 画布显示尺寸(视觉尺寸,和最终展示一致)
640
+ canvas.style.width = `${finalWidth}px`;
641
+ canvas.style.height = `${finalHeight}px`;
642
+
643
+ // 缩放画布上下文,适配高清屏DPR
644
+ ctx.scale(dpr, dpr);
645
+
646
+ // 7. 绘制裁剪后的图片(使用原始像素绘制,保证清晰度)
647
+ ctx.drawImage(
648
+ tempImg,
649
+ originalCropX, // 原始图片裁剪起始X(精确像素)
650
+ originalCropY, // 原始图片裁剪起始Y(精确像素)
651
+ originalCropWidth, // 原始图片裁剪宽度(精确像素)
652
+ originalCropHeight, // 原始图片裁剪高度(精确像素)
653
+ 0, // 画布绘制起始X
654
+ 0, // 画布绘制起始Y
655
+ finalWidth, // 画布绘制宽度(目标尺寸)
656
+ finalHeight, // 画布绘制高度(目标尺寸)
657
+ );
658
+
659
+ try {
660
+ return outputType === 'base64'
661
+ ? canvas.toDataURL(format, validQuality)
662
+ : new Promise<Blob>((resolve) => {
663
+ canvas.toBlob(
664
+ (blob) => {
665
+ // 兜底:如果blob生成失败,返回空Blob(防止null)
666
+ resolve(blob || new Blob([], { type: format }));
667
+ },
668
+ format,
669
+ validQuality,
670
+ );
671
+ });
672
+ } catch (error) {
673
+ console.error('图片导出失败:', error);
674
+ }
675
+ };
676
+
677
+ // 监听比例变化,重新调整裁剪框
678
+ watch(() => props.aspectRatio, adjustCropperToAspectRatio);
679
+
680
+ // 监听width/height变化,重新计算尺寸
681
+ watch([() => props.width, () => props.height], () => {
682
+ calculateImageFitSize();
683
+ adjustCropperToAspectRatio();
684
+ });
685
+
686
+ // 组件挂载时注册全局事件
687
+ onMounted(() => {
688
+ document.addEventListener('mousemove', handleMouseMove);
689
+ document.addEventListener('mouseup', handleMouseUp);
690
+
691
+ // 如果图片已经加载完成,手动触发创建裁剪器
692
+ if (
693
+ bgImageRef.value &&
694
+ bgImageRef.value.complete &&
695
+ bgImageRef.value.naturalWidth > 0
696
+ ) {
697
+ createCropper();
698
+ }
699
+ });
700
+
701
+ // 组件卸载时清理
702
+ onUnmounted(() => {
703
+ document.removeEventListener('mousemove', handleMouseMove);
704
+ document.removeEventListener('mouseup', handleMouseUp);
705
+ });
706
+
707
+ defineExpose({ getCropImage });
708
+ </script>
709
+
710
+ <template>
711
+ <div
712
+ :style="{
713
+ width: `${width || CROPPER_CONSTANTS.DEFAULT_WIDTH}px`,
714
+ height: `${height || CROPPER_CONSTANTS.DEFAULT_HEIGHT}px`,
715
+ }"
716
+ class="cropper-action-wrapper"
717
+ >
718
+ <div
719
+ ref="containerRef"
720
+ class="cropper-container"
721
+ :style="{
722
+ width: `${containerWidth}px`,
723
+ height: `${containerHeight}px`,
724
+ }"
725
+ >
726
+ <!-- 原图展示 - 自适应尺寸 -->
727
+ <img
728
+ ref="bgImageRef"
729
+ class="cropper-image"
730
+ :src="img"
731
+ @load="handleImageLoad"
732
+ :style="{
733
+ maxWidth: '100%',
734
+ maxHeight: '100%',
735
+ objectFit: 'contain',
736
+ }"
737
+ alt="裁剪原图"
738
+ />
739
+
740
+ <!-- 遮罩层 -->
741
+ <div
742
+ class="cropper-mask"
743
+ :style="{
744
+ display: isCropperVisible ? 'block' : 'none',
745
+ width: '100%',
746
+ height: '100%',
747
+ }"
748
+ >
749
+ <div
750
+ ref="maskViewRef"
751
+ class="cropper-mask-view"
752
+ :style="{
753
+ backgroundImage: `url(${img})`,
754
+ backgroundSize: 'contain',
755
+ backgroundPosition: 'center',
756
+ backgroundRepeat: 'no-repeat',
757
+ clipPath: `inset(${currentDimension[0]}px ${currentDimension[1]}px ${currentDimension[2]}px ${currentDimension[3]}px)`,
758
+ width: '100%',
759
+ height: '100%',
760
+ }"
761
+ ></div>
762
+ </div>
763
+
764
+ <!-- 裁剪框 -->
765
+ <div
766
+ ref="cropperRef"
767
+ class="cropper-box"
768
+ :style="{
769
+ display: isCropperVisible ? 'block' : 'none',
770
+ width: '100%',
771
+ height: '100%',
772
+ }"
773
+ >
774
+ <div
775
+ class="cropper-view"
776
+ :style="{
777
+ inset: `${currentDimension[0]}px ${currentDimension[1]}px ${currentDimension[2]}px ${currentDimension[3]}px`,
778
+ }"
779
+ >
780
+ <!-- 裁剪框辅助线-->
781
+ <span class="cropper-dashed-h"></span>
782
+ <span class="cropper-dashed-v"></span>
783
+
784
+ <!-- 裁剪框拖拽区域 -->
785
+ <span
786
+ class="cropper-move-area"
787
+ @mousedown="handleMouseDown($event, 'move')"
788
+ ></span>
789
+
790
+ <!-- 边框线 -->
791
+ <span class="cropper-line-e"></span>
792
+ <span class="cropper-line-n"></span>
793
+ <span class="cropper-line-w"></span>
794
+ <span class="cropper-line-s"></span>
795
+
796
+ <!-- 边角拖拽点 -->
797
+ <span
798
+ class="cropper-point cropper-point-ne"
799
+ @mousedown="handleMouseDown($event, 'top-right')"
800
+ >
801
+ <span class="cropper-point-inner"></span>
802
+ </span>
803
+ <span
804
+ class="cropper-point cropper-point-nw"
805
+ @mousedown="handleMouseDown($event, 'top-left')"
806
+ >
807
+ <span class="cropper-point-inner"></span>
808
+ </span>
809
+ <span
810
+ class="cropper-point cropper-point-sw"
811
+ @mousedown="handleMouseDown($event, 'bottom-left')"
812
+ >
813
+ <span class="cropper-point-inner"></span>
814
+ </span>
815
+ <span
816
+ class="cropper-point cropper-point-se"
817
+ @mousedown="handleMouseDown($event, 'bottom-right')"
818
+ >
819
+ <span class="cropper-point-inner"></span>
820
+ </span>
821
+
822
+ <!-- 边中点拖拽点 -->
823
+ <span
824
+ class="cropper-point cropper-point-e"
825
+ @mousedown="handleMouseDown($event, 'right')"
826
+ >
827
+ <span class="cropper-point-inner"></span>
828
+ </span>
829
+ <span
830
+ class="cropper-point cropper-point-n"
831
+ @mousedown="handleMouseDown($event, 'top')"
832
+ >
833
+ <span class="cropper-point-inner"></span>
834
+ </span>
835
+ <span
836
+ class="cropper-point cropper-point-w"
837
+ @mousedown="handleMouseDown($event, 'left')"
838
+ >
839
+ <span class="cropper-point-inner"></span>
840
+ </span>
841
+ <span
842
+ class="cropper-point cropper-point-s"
843
+ @mousedown="handleMouseDown($event, 'bottom')"
844
+ >
845
+ <span class="cropper-point-inner"></span>
846
+ </span>
847
+ </div>
848
+ </div>
849
+ </div>
850
+ </div>
851
+ </template>
852
+
853
+ <style scoped>
854
+ @reference "@melt-js/libs-vben/theme";
855
+
856
+ .cropper-action-wrapper {
857
+ @apply box-border flex items-center justify-center;
858
+
859
+ background-color: transparent;
860
+
861
+ /* 马赛克背景 */
862
+ background-image:
863
+ linear-gradient(45deg, #ccc 25%, transparent 25%),
864
+ linear-gradient(-45deg, #ccc 25%, transparent 25%),
865
+ linear-gradient(45deg, transparent 75%, #ccc 75%),
866
+ linear-gradient(-45deg, transparent 75%, #ccc 75%);
867
+ background-position:
868
+ 0 0,
869
+ 0 10px,
870
+ 10px -10px,
871
+ -10px 0;
872
+ background-size: 20px 20px;
873
+ }
874
+
875
+ .cropper-container {
876
+ @apply relative;
877
+ }
878
+
879
+ .cropper-image {
880
+ @apply block;
881
+ }
882
+
883
+ /* 遮罩层 */
884
+ .cropper-mask {
885
+ @apply absolute top-0 left-0 bg-black/50;
886
+ }
887
+
888
+ .cropper-mask-view {
889
+ @apply absolute top-0 left-0;
890
+ }
891
+
892
+ /* 裁剪框 */
893
+ .cropper-box {
894
+ @apply absolute top-0 left-0 z-10;
895
+ }
896
+
897
+ .cropper-view {
898
+ @apply absolute top-0 right-0 bottom-0 left-0 outline-1 outline-blue-500 select-none;
899
+ }
900
+
901
+ /* 裁剪框辅助线 */
902
+ .cropper-dashed-h {
903
+ @apply absolute top-1/3 left-0 block h-1/3 w-full border-t border-b border-dashed border-gray-200/50;
904
+ }
905
+
906
+ .cropper-dashed-v {
907
+ @apply absolute top-0 left-1/3 block h-full w-1/3 border-r border-l border-dashed border-gray-200/50;
908
+ }
909
+
910
+ /* 裁剪框拖拽区域 */
911
+ .cropper-move-area {
912
+ @apply absolute top-0 left-0 block h-full w-full cursor-move bg-white/10;
913
+ }
914
+
915
+ /* 边框拖拽线 */
916
+ .cropper-line-e,
917
+ .cropper-line-n,
918
+ .cropper-line-w,
919
+ .cropper-line-s {
920
+ @apply absolute block bg-blue-500/10;
921
+ }
922
+
923
+ .cropper-line-e {
924
+ @apply top-0 -right-0.75 h-full w-1;
925
+ }
926
+
927
+ .cropper-line-n {
928
+ @apply -top-0.75 left-0 h-1 w-full;
929
+ }
930
+
931
+ .cropper-line-w {
932
+ @apply top-0 -left-0.75 h-full w-1;
933
+ }
934
+
935
+ .cropper-line-s {
936
+ @apply -bottom-0.75 left-0 h-1 w-full;
937
+ }
938
+
939
+ /* 拖拽点 */
940
+ .cropper-point {
941
+ @apply absolute flex h-2 w-2 items-center justify-center bg-blue-500;
942
+ }
943
+
944
+ .cropper-point-inner {
945
+ @apply block h-1.5 w-1.5 bg-white;
946
+ }
947
+
948
+ /* 边角拖拽点位置和光标 */
949
+ .cropper-point-ne {
950
+ @apply -top-1.25 -right-1.25 cursor-ne-resize;
951
+ }
952
+
953
+ .cropper-point-nw {
954
+ @apply -top-1.25 -left-1.25 cursor-nw-resize;
955
+ }
956
+
957
+ .cropper-point-sw {
958
+ @apply -bottom-1.25 -left-1.25 cursor-sw-resize;
959
+ }
960
+
961
+ .cropper-point-se {
962
+ @apply -right-1.25 -bottom-1.25 cursor-se-resize;
963
+ }
964
+
965
+ /* 边中点拖拽点位置和光标 */
966
+ .cropper-point-e {
967
+ @apply top-1/2 -right-1.25 -mt-1 cursor-e-resize;
968
+ }
969
+
970
+ .cropper-point-n {
971
+ @apply -top-1.25 left-1/2 -ml-1 cursor-n-resize;
972
+ }
973
+
974
+ .cropper-point-w {
975
+ @apply top-1/2 -left-1.25 -mt-1 cursor-w-resize;
976
+ }
977
+
978
+ .cropper-point-s {
979
+ @apply -bottom-1.25 left-1/2 -ml-1 cursor-s-resize;
980
+ }
981
+ </style>