diff --git a/.gitignore b/.gitignore index 492c224a..658131b6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,12 @@ node_modules .DS_Store dist dist-ssr +dist.zip +dist.tar +dist.war +*-dist.zip +*-dist.tar +*-dist.war coverage *.local **/.vitepress/cache diff --git a/cspell.json b/cspell.json index a61ac275..714407ad 100644 --- a/cspell.json +++ b/cspell.json @@ -6,6 +6,7 @@ "words": [ "clsx", "esno", + "demi", "unref", "taze", "acmr", @@ -40,7 +41,8 @@ "vitepress", "ependencies", "vite", - "echarts" + "echarts", + "sortablejs" ], "ignorePaths": [ "**/node_modules/**", diff --git a/packages/@core/forward/stores/src/modules/tabbar.ts b/packages/@core/forward/stores/src/modules/tabbar.ts index 8b86c283..cb7570d9 100644 --- a/packages/@core/forward/stores/src/modules/tabbar.ts +++ b/packages/@core/forward/stores/src/modules/tabbar.ts @@ -12,6 +12,10 @@ interface TabsState { * @zh_CN 当前打开的标签页列表缓存 */ cachedTabs: Set; + /** + * @zh_CN 拖拽结束的索引 + */ + dragEndIndex: number; /** * @zh_CN 需要排除缓存的标签页 */ @@ -131,7 +135,6 @@ const useCoreTabbarStore = defineStore('core-tabbar', { } await this._bulkCloseByPaths(paths); }, - /** * @zh_CN 关闭其他标签页 * @param tab @@ -210,6 +213,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', { console.error('Failed to close the tab; only one tab remains open.'); } }, + /** * @zh_CN 通过key关闭标签页 * @param key @@ -222,7 +226,6 @@ const useCoreTabbarStore = defineStore('core-tabbar', { await this.closeTab(this.tabs[index], router); }, - /** * @zh_CN 固定标签页 * @param tab @@ -236,6 +239,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', { this.addTab(tab); } }, + /** * 刷新标签页 */ @@ -263,6 +267,17 @@ const useCoreTabbarStore = defineStore('core-tabbar', { this.addTab(routeToTab(tab)); } }, + /** + * @zh_CN 设置标签页顺序 + * @param oldIndex + * @param newIndex + */ + async sortTabs(oldIndex: number, newIndex: number) { + const currentTab = this.tabs[oldIndex]; + this.tabs.splice(oldIndex, 1); + this.tabs.splice(newIndex, 0, currentTab); + this.dragEndIndex = this.dragEndIndex + 1; + }, /** * @zh_CN 取消固定标签页 * @param tab @@ -315,7 +330,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', { getTabs(): TabItem[] { const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab)); - return [...affixTabs, ...normalTabs]; + return [...affixTabs, ...normalTabs].filter(Boolean); }, }, persist: [ @@ -327,6 +342,7 @@ const useCoreTabbarStore = defineStore('core-tabbar', { ], state: (): TabsState => ({ cachedTabs: new Set(), + dragEndIndex: 0, excludeCachedTabs: new Set(), renderRouteView: true, tabs: [], @@ -365,7 +381,7 @@ function cloneTab(route: TabItem): TabItem { * @param tab */ function isAffixTab(tab: TabItem) { - return tab.meta?.affixTab ?? false; + return tab?.meta?.affixTab ?? false; } /** diff --git a/packages/@core/locales/src/langs/en-US.json b/packages/@core/locales/src/langs/en-US.json index 32e9bdd2..48d61e10 100644 --- a/packages/@core/locales/src/langs/en-US.json +++ b/packages/@core/locales/src/langs/en-US.json @@ -178,9 +178,9 @@ "persist": "Persist Tabs", "contextMenu": { "reload": "Reload", - "close": "Close Tab", - "pin": "Pin Tab", - "unpin": "Unpin Tab", + "close": "Close", + "pin": "Pin", + "unpin": "Unpin", "closeLeft": "Close Left Tabs", "closeRight": "Close Right Tabs", "closeOther": "Close Other Tabs", diff --git a/packages/@core/locales/src/langs/zh-CN.json b/packages/@core/locales/src/langs/zh-CN.json index ba8750f3..c2659d75 100644 --- a/packages/@core/locales/src/langs/zh-CN.json +++ b/packages/@core/locales/src/langs/zh-CN.json @@ -178,9 +178,9 @@ "persist": "持久化标签页", "contextMenu": { "reload": "重新加载", - "close": "关闭标签页", - "pin": "固定标签页", - "unpin": "取消固定标签页", + "close": "关闭", + "pin": "固定", + "unpin": "取消固定", "closeLeft": "关闭左侧标签页", "closeRight": "关闭右侧标签页", "closeOther": "关闭其它标签页", diff --git a/packages/@core/shared/hooks/build.config.ts b/packages/@core/shared/hooks/build.config.ts new file mode 100644 index 00000000..97e572c5 --- /dev/null +++ b/packages/@core/shared/hooks/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/packages/@core/shared/hooks/package.json b/packages/@core/shared/hooks/package.json new file mode 100644 index 00000000..0dcbe88c --- /dev/null +++ b/packages/@core/shared/hooks/package.json @@ -0,0 +1,45 @@ +{ + "name": "@vben-core/hooks", + "version": "5.0.0", + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "packages/@vben-core/shared/hooks" + }, + "license": "MIT", + "type": "module", + "scripts": { + "build": "pnpm unbuild", + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "sideEffects": false, + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "exports": { + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts", + "default": "./dist/index.mjs" + } + }, + "publishConfig": { + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + } + } + }, + "dependencies": { + "sortablejs": "^1.15.2", + "vue": "^3.4.31" + }, + "devDependencies": { + "@types/sortablejs": "^1.15.8" + } +} diff --git a/packages/@core/shared/hooks/src/index.ts b/packages/@core/shared/hooks/src/index.ts new file mode 100644 index 00000000..8df44ce0 --- /dev/null +++ b/packages/@core/shared/hooks/src/index.ts @@ -0,0 +1 @@ +export * from './use-sortable'; diff --git a/packages/@core/shared/hooks/src/use-sortable.test.ts b/packages/@core/shared/hooks/src/use-sortable.test.ts new file mode 100644 index 00000000..af1ce1b5 --- /dev/null +++ b/packages/@core/shared/hooks/src/use-sortable.test.ts @@ -0,0 +1,46 @@ +import type { SortableOptions } from 'sortablejs'; + +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useSortable } from './use-sortable'; + +describe('useSortable', () => { + beforeEach(() => { + vi.mock('sortablejs', () => ({ + default: { + create: vi.fn(), + }, + })); + }); + it('should call Sortable.create with the correct options', async () => { + // Create a mock element + const mockElement = document.createElement('div') as HTMLDivElement; + + // Define custom options + const customOptions: SortableOptions = { + group: 'test-group', + sort: false, + }; + + // Use the useSortable function + const { initializeSortable } = useSortable(mockElement, customOptions); + + // Initialize sortable + await initializeSortable(); + + // Import sortablejs to access the mocked create function + const Sortable = await import('sortablejs'); + + // Verify that Sortable.create was called with the correct parameters + expect(Sortable.default.create).toHaveBeenCalledTimes(1); + expect(Sortable.default.create).toHaveBeenCalledWith( + mockElement, + expect.objectContaining({ + animation: 100, + delay: 400, + delayOnTouchOnly: true, + ...customOptions, + }), + ); + }); +}); diff --git a/packages/@core/shared/hooks/src/use-sortable.ts b/packages/@core/shared/hooks/src/use-sortable.ts new file mode 100644 index 00000000..563a2a79 --- /dev/null +++ b/packages/@core/shared/hooks/src/use-sortable.ts @@ -0,0 +1,33 @@ +import type { SortableOptions } from 'sortablejs'; + +function useSortable( + sortableContainer: T, + options: SortableOptions = {}, +) { + const initializeSortable = async () => { + const Sortable = await import( + // @ts-expect-error - This is a dynamic import + 'sortablejs/modular/sortable.complete.esm.js' + ); + // const { AutoScroll } = await import( + // // @ts-expect-error - This is a dynamic import + // 'sortablejs/modular/sortable.core.esm.js' + // ); + + // Sortable?.default?.mount?.(AutoScroll); + + const sortable = Sortable?.default?.create?.(sortableContainer, { + animation: 100, + delay: 400, + delayOnTouchOnly: true, + ...options, + }); + return sortable; + }; + + return { + initializeSortable, + }; +} + +export { useSortable }; diff --git a/packages/@core/shared/hooks/tsconfig.json b/packages/@core/shared/hooks/tsconfig.json new file mode 100644 index 00000000..f6860a32 --- /dev/null +++ b/packages/@core/shared/hooks/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/library.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue index 7322ec8a..d79d02c5 100644 --- a/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue +++ b/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue @@ -37,7 +37,7 @@ const style = computed((): CSSProperties => {