Merge pull request #2 from Jasery/develop

Develop
This commit is contained in:
zhanggy authored and GitHub committed 2025-11-02 19:56:52 +08:00
commit 5e3fba3622
22 files changed
+1720 -892

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
# IFLOW 上下文文档
## 项目概述
这是一个基于 **Vite + Vue 3 + Electron** 的桌面应用程序开发模板,名为 **Vutron**。它提供了一个快速搭建跨平台桌面应用的环境,内置了热重载、TypeScript 支持、多语言支持、主题切换(暗黑/明亮)、全局状态管理(Pinia)等功能。
项目使用 `pnpm` 作为包管理器,主要技术栈包括:
- 前端框架:Vue 3 (Composition API)
- 构建工具:Vite
- 桌面应用框架:Electron
- UI 库:Vuetify
- 状态管理:Pinia
- 国际化:vue-i18n
- 自动更新:electron-updater
## 项目结构
```
src/
├── main/ # Electron 主进程代码
│ ├── AutoUpdater.ts # 自动更新功能
│ ├── IPCs.ts # IPC 通信处理
│ ├── MainRunner.ts # 主窗口和错误窗口创建
│ ├── index.dev.ts # 开发环境入口
│ ├── index.ts # 生产环境入口
│ ├── tray.ts # 系统托盘功能
│ └── utils/
│ └── Constants.ts # 常量定义
├── preload/ # Electron 预加载脚本
├── public/ # 静态资源
└── renderer/ # 渲染进程(Vue 应用)
├── App.vue # 根组件
├── main.ts # Vue 应用入口
├── components/ # 公共组件
├── screens/ # 页面组件
├── plugins/ # 插件配置(vuetify, i18n, pinia)
├── router/ # 路由配置
├── store/ # Pinia 状态管理
├── locales/ # 语言包
└── utils/ # 工具函数
buildAssets/ # 构建相关资源
docs/ # 项目文档
tests/ # 测试文件
```
## 构建和运行
### 开发环境
1. 安装依赖:
```bash
pnpm install
```
2. 启动开发服务器:
```bash
pnpm dev
```
这将启动 Vite 开发服务器,并在 Electron 中打开应用。
3. 构建项目:
```bash
pnpm build
```
构建应用到 `dist/` 目录,并使用 `electron-builder` 打包成可执行文件。
4. 运行测试:
```bash
pnpm test
```
运行 Playwright 自动化测试。
### 生产环境
构建后的应用位于 `release/` 目录下,根据不同平台生成对应的安装包。
## 开发约定
- 使用 TypeScript 进行开发,类型检查通过 `vue-tsc` 完成。
- 代码风格遵循 ESLint 和 Prettier 规范。
- 使用 Pinia 进行全局状态管理。
- 国际化支持通过 vue-i18n 实现。
- 主进程与渲染进程通过 IPC 进行通信。
- 日志记录使用 `electron-log` 模块。
- 自动更新功能通过 `electron-updater` 实现。
如果有修改代码,任务结束前需要先运行 `pnpm typecheck` 检查类型错误。不要直接运行 `pnpm dev` 或 `pnpm build`。
+1 -1
View File
@@ -229,6 +229,6 @@ Building creates different distribution formats based on the platform:
Artifacts are named with the format `${productName} ${version}_${arch}.${ext}` and stored in the `release/${version}` directory. Artifacts are named with the format `${productName} ${version}_${arch}.${ext}` and stored in the `release/${version}` directory.
run `pnpm typecheck` before finish the task if need run `pnpm typecheck` before finish the task if need,don't run `pnpm dev` or `pnpm build`.
Always respond in 中文 Always respond in 中文
+1
View File
@@ -112,6 +112,7 @@
"standard-version": "^9.5.0", "standard-version": "^9.5.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.46.1", "typescript-eslint": "^8.46.1",
"unplugin-auto-import": "^20.2.0",
"vite": "^7.1.10", "vite": "^7.1.10",
"vite-plugin-electron": "^0.29.0", "vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6", "vite-plugin-electron-renderer": "^0.14.6",
+170
View File
@@ -117,6 +117,9 @@ importers:
typescript-eslint: typescript-eslint:
specifier: ^8.46.1 specifier: ^8.46.1
version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)
unplugin-auto-import:
specifier: ^20.2.0
version: 20.2.0(@vueuse/core@14.0.0(vue@3.5.22(typescript@5.9.3)))
vite: vite:
specifier: ^7.1.10 specifier: ^7.1.10
version: 7.1.10(@types/node@22.15.15)(jiti@2.6.1) version: 7.1.10(@types/node@22.15.15)(jiti@2.6.1)
@@ -1340,6 +1343,12 @@ packages:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0} engines: {'0': node >= 6.0}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
config-file-ts@0.2.8-rc1: config-file-ts@0.2.8-rc1:
resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==}
@@ -1697,6 +1706,10 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
eslint-compat-utils@0.5.1: eslint-compat-utils@0.5.1:
resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1824,6 +1837,9 @@ packages:
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
esutils@2.0.3: esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -1831,6 +1847,9 @@ packages:
exponential-backoff@3.1.2: exponential-backoff@3.1.2:
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
exsolve@1.0.7:
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
extract-zip@2.0.1: extract-zip@2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'} engines: {node: '>= 10.17.0'}
@@ -2377,6 +2396,9 @@ packages:
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@4.1.0: js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true hasBin: true
@@ -2453,6 +2475,10 @@ packages:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'} engines: {node: '>=4'}
local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
locate-path@2.0.0: locate-path@2.0.0:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -2641,6 +2667,9 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
modify-values@1.0.1: modify-values@1.0.1:
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2833,6 +2862,9 @@ packages:
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
engines: {node: '>=4'} engines: {node: '>=4'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pe-library@0.4.1: pe-library@0.4.1:
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
@@ -2871,6 +2903,12 @@ packages:
typescript: typescript:
optional: true optional: true
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
playwright-core@1.56.1: playwright-core@1.56.1:
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2957,6 +2995,9 @@ packages:
resolution: {integrity: sha512-+37VW4m47xbKfh65Wn6Xr9fnYF3QvtRSgRCSh2gtEyXlUI1SzFZtUFdRKA5+7sJkZbMEVVwhy5SOYkWA3N7gLw==} resolution: {integrity: sha512-+37VW4m47xbKfh65Wn6Xr9fnYF3QvtRSgRCSh2gtEyXlUI1SzFZtUFdRKA5+7sJkZbMEVVwhy5SOYkWA3N7gLw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -3102,6 +3143,9 @@ packages:
sax@1.4.1: sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
semver-compare@1.0.0: semver-compare@1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
@@ -3291,6 +3335,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
sumchecker@3.0.1: sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'} engines: {node: '>= 8.0'}
@@ -3436,6 +3483,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
uglify-js@3.19.3: uglify-js@3.19.3:
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@@ -3448,6 +3498,10 @@ packages:
undici-types@6.21.0: undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
unimport@5.5.0:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
engines: {node: '>=18.12.0'}
unique-filename@2.0.1: unique-filename@2.0.1:
resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -3464,6 +3518,26 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
unplugin-auto-import@20.2.0:
resolution: {integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==}
engines: {node: '>=14'}
peerDependencies:
'@nuxt/kit': ^4.0.0
'@vueuse/core': '*'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
'@vueuse/core':
optional: true
unplugin-utils@0.3.1:
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
engines: {node: '>=20.19.0'}
unplugin@2.3.10:
resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
engines: {node: '>=18.12.0'}
unzip-crx-3@0.2.0: unzip-crx-3@0.2.0:
resolution: {integrity: sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==} resolution: {integrity: sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==}
@@ -3610,6 +3684,9 @@ packages:
wcwidth@1.0.1: wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
which-boxed-primitive@1.1.1: which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5094,6 +5171,10 @@ snapshots:
readable-stream: 3.6.2 readable-stream: 3.6.2
typedarray: 0.0.6 typedarray: 0.0.6
confbox@0.1.8: {}
confbox@0.2.2: {}
config-file-ts@0.2.8-rc1: config-file-ts@0.2.8-rc1:
dependencies: dependencies:
glob: 10.4.5 glob: 10.4.5
@@ -5664,6 +5745,8 @@ snapshots:
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {}
eslint-compat-utils@0.5.1(eslint@9.37.0(jiti@2.6.1)): eslint-compat-utils@0.5.1(eslint@9.37.0(jiti@2.6.1)):
dependencies: dependencies:
eslint: 9.37.0(jiti@2.6.1) eslint: 9.37.0(jiti@2.6.1)
@@ -5834,10 +5917,16 @@ snapshots:
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
esutils@2.0.3: {} esutils@2.0.3: {}
exponential-backoff@3.1.2: {} exponential-backoff@3.1.2: {}
exsolve@1.0.7: {}
extract-zip@2.0.1: extract-zip@2.0.1:
dependencies: dependencies:
debug: 4.4.0 debug: 4.4.0
@@ -6424,6 +6513,8 @@ snapshots:
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
js-yaml@4.1.0: js-yaml@4.1.0:
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
@@ -6495,6 +6586,12 @@ snapshots:
pify: 3.0.0 pify: 3.0.0
strip-bom: 3.0.0 strip-bom: 3.0.0
local-pkg@1.1.2:
dependencies:
mlly: 1.8.0
pkg-types: 2.3.0
quansync: 0.2.11
locate-path@2.0.0: locate-path@2.0.0:
dependencies: dependencies:
p-locate: 2.0.0 p-locate: 2.0.0
@@ -6685,6 +6782,13 @@ snapshots:
mkdirp@1.0.4: {} mkdirp@1.0.4: {}
mlly@1.8.0:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
modify-values@1.0.1: {} modify-values@1.0.1: {}
ms@2.1.3: {} ms@2.1.3: {}
@@ -6883,6 +6987,8 @@ snapshots:
dependencies: dependencies:
pify: 3.0.0 pify: 3.0.0
pathe@2.0.3: {}
pe-library@0.4.1: {} pe-library@0.4.1: {}
pend@1.2.0: {} pend@1.2.0: {}
@@ -6906,6 +7012,18 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 5.9.3
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.0
pathe: 2.0.3
pkg-types@2.3.0:
dependencies:
confbox: 0.2.2
exsolve: 1.0.7
pathe: 2.0.3
playwright-core@1.56.1: {} playwright-core@1.56.1: {}
playwright@1.56.1: playwright@1.56.1:
@@ -6970,6 +7088,8 @@ snapshots:
qsu@1.10.2: {} qsu@1.10.2: {}
quansync@0.2.11: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
quick-lru@4.0.1: {} quick-lru@4.0.1: {}
@@ -7163,6 +7283,8 @@ snapshots:
sax@1.4.1: {} sax@1.4.1: {}
scule@1.3.0: {}
semver-compare@1.0.0: semver-compare@1.0.0:
optional: true optional: true
@@ -7389,6 +7511,10 @@ snapshots:
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
sumchecker@3.0.1: sumchecker@3.0.1:
dependencies: dependencies:
debug: 4.4.0 debug: 4.4.0
@@ -7551,6 +7677,8 @@ snapshots:
typescript@5.9.3: {} typescript@5.9.3: {}
ufo@1.6.1: {}
uglify-js@3.19.3: uglify-js@3.19.3:
optional: true optional: true
@@ -7563,6 +7691,23 @@ snapshots:
undici-types@6.21.0: {} undici-types@6.21.0: {}
unimport@5.5.0:
dependencies:
acorn: 8.15.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
local-pkg: 1.1.2
magic-string: 0.30.19
mlly: 1.8.0
pathe: 2.0.3
picomatch: 4.0.3
pkg-types: 2.3.0
scule: 1.3.0
strip-literal: 3.1.0
tinyglobby: 0.2.15
unplugin: 2.3.10
unplugin-utils: 0.3.1
unique-filename@2.0.1: unique-filename@2.0.1:
dependencies: dependencies:
unique-slug: 3.0.0 unique-slug: 3.0.0
@@ -7575,6 +7720,29 @@ snapshots:
universalify@2.0.1: {} universalify@2.0.1: {}
unplugin-auto-import@20.2.0(@vueuse/core@14.0.0(vue@3.5.22(typescript@5.9.3))):
dependencies:
local-pkg: 1.1.2
magic-string: 0.30.19
picomatch: 4.0.3
unimport: 5.5.0
unplugin: 2.3.10
unplugin-utils: 0.3.1
optionalDependencies:
'@vueuse/core': 14.0.0(vue@3.5.22(typescript@5.9.3))
unplugin-utils@0.3.1:
dependencies:
pathe: 2.0.3
picomatch: 4.0.3
unplugin@2.3.10:
dependencies:
'@jridgewell/remapping': 2.3.5
acorn: 8.15.0
picomatch: 4.0.3
webpack-virtual-modules: 0.6.2
unzip-crx-3@0.2.0: unzip-crx-3@0.2.0:
dependencies: dependencies:
jszip: 3.10.1 jszip: 3.10.1
@@ -7700,6 +7868,8 @@ snapshots:
dependencies: dependencies:
defaults: 1.0.4 defaults: 1.0.4
webpack-virtual-modules@0.6.2: {}
which-boxed-primitive@1.1.1: which-boxed-primitive@1.1.1:
dependencies: dependencies:
is-bigint: 1.1.0 is-bigint: 1.1.0
+40 -32
View File
@@ -2,13 +2,16 @@ import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
import Constants from './utils/Constants' import Constants from './utils/Constants'
import { join, dirname } from 'path' import { join, dirname } from 'path'
import { import {
writeFileSync, writeFile,
readFileSync, readFile,
existsSync, pathExists,
ensureDirSync, ensureDir,
copyFileSync copy,
readdir,
stat,
} from 'fs-extra' } from 'fs-extra'
import { autoUpdater } from 'electron-updater' import { autoUpdater } from 'electron-updater'
import { Template, TreeNode } from '../types'
/* /*
* IPC Communications * IPC Communications
@@ -93,21 +96,19 @@ export default class IPCs {
'msgFindFilesInFolder', 'msgFindFilesInFolder',
async (event: IpcMainEvent, folderPath: string) => { async (event: IpcMainEvent, folderPath: string) => {
try { try {
const { readdirSync, statSync } = require('fs')
const { join } = require('path')
const foundFiles: string[] = [] const foundFiles: string[] = []
const searchFiles = (dir: string) => { const searchFiles = async (dir: string) => {
const items = readdirSync(dir) const items = await readdir(dir)
for (const item of items) { for (const item of items) {
const fullPath = join(dir, item) const fullPath = join(dir, item)
const stats = statSync(fullPath) const stats = await stat(fullPath)
if (stats.isDirectory()) { if (stats.isDirectory()) {
// Recursively search in subdirectories // Recursively search in subdirectories
searchFiles(fullPath) await searchFiles(fullPath)
} else if (stats.isFile()) { } else if (stats.isFile()) {
// Add file to the list // Add file to the list
foundFiles.push(fullPath) foundFiles.push(fullPath)
@@ -115,7 +116,7 @@ export default class IPCs {
} }
} }
searchFiles(folderPath) await searchFiles(folderPath)
return foundFiles return foundFiles
} catch (error) { } catch (error) {
@@ -134,10 +135,10 @@ export default class IPCs {
const filePath = join(userDataPath, fileName) const filePath = join(userDataPath, fileName)
// Ensure directory exists // Ensure directory exists
ensureDirSync(userDataPath) await ensureDir(userDataPath)
if (existsSync(filePath)) { if (await pathExists(filePath)) {
const data = readFileSync(filePath, 'utf-8') const data = await readFile(filePath, 'utf-8')
return data return data
} }
return null return null
@@ -157,9 +158,9 @@ export default class IPCs {
const filePath = join(userDataPath, fileName) const filePath = join(userDataPath, fileName)
// Ensure directory exists // Ensure directory exists
ensureDirSync(userDataPath) await ensureDir(userDataPath)
writeFileSync(filePath, data, 'utf-8') await writeFile(filePath, data, 'utf-8')
return true return true
} catch (error) { } catch (error) {
console.error('Error saving data:', error) console.error('Error saving data:', error)
@@ -182,9 +183,9 @@ export default class IPCs {
const userDataPath = join(Constants.APP_DATA_PATH, 'data') const userDataPath = join(Constants.APP_DATA_PATH, 'data')
const filePath = join(userDataPath, 'templates.json') const filePath = join(userDataPath, 'templates.json')
let templates = [] let templates: Template[] = []
if (existsSync(filePath)) { if (await pathExists(filePath)) {
const templatesData = readFileSync(filePath, 'utf-8') const templatesData = await readFile(filePath, 'utf-8')
templates = JSON.parse(templatesData) templates = JSON.parse(templatesData)
} }
@@ -200,14 +201,17 @@ export default class IPCs {
// Create base directory // Create base directory
const baseDirPath = join(targetPath, baseDirName) const baseDirPath = join(targetPath, baseDirName)
ensureDirSync(baseDirPath) await ensureDir(baseDirPath)
// Helper function to traverse the tree and collect all nodes // Helper function to traverse the tree and collect all nodes
const collectAllNodes = (nodes, allNodes = []) => { const collectAllNodes = (nodes: TreeNode[], allNodes: TreeNode[] = [], parent?: TreeNode) => {
for (const node of nodes) { for (const node of nodes) {
allNodes.push(node); allNodes.push({
...node,
path: join(parent?.path || '', node.path)
});
if (node.children) { if (node.children) {
collectAllNodes(node.children, allNodes); collectAllNodes(node.children, allNodes, node);
} }
} }
return allNodes; return allNodes;
@@ -222,18 +226,22 @@ export default class IPCs {
const nodePath = join(baseDirPath, node.path); const nodePath = join(baseDirPath, node.path);
if (node.type === 'directory') { if (node.type === 'directory') {
ensureDirSync(nodePath); await ensureDir(nodePath);
} else if (node.type === 'file') { } else if (node.type === 'file') {
const fileDir = dirname(nodePath); const fileDir = dirname(nodePath);
ensureDirSync(fileDir); await ensureDir(fileDir);
if (node.sourceFilePath) { if (node.sourceType === 'file') {
// Copy from source file await copy(node.sourcePath, nodePath);
const sourceContent = readFileSync(node.sourceFilePath); } else {
writeFileSync(nodePath, sourceContent);
} else if (node.content) {
// Write content directly // Write content directly
writeFileSync(nodePath, node.content, 'utf-8'); const files = await readdir(node.sourcePath, { withFileTypes: true })
for (const file of files) {
if (file.isFile() && file.name.startsWith(baseDirName)) {
const filePath = join(node.sourcePath, file.name);
await copy(filePath, nodePath);
}
}
} }
} }
} }
+2
View File
@@ -2,6 +2,7 @@
import { DefaultLayout } from '@/renderer/components/layout' import { DefaultLayout } from '@/renderer/components/layout'
import GlobalDialog from '@/renderer/components/GlobalDialog.vue' import GlobalDialog from '@/renderer/components/GlobalDialog.vue'
import UpdateDialog from '@/renderer/components/UpdateDialog.vue' import UpdateDialog from '@/renderer/components/UpdateDialog.vue'
import GlobalSnackbar from '@/renderer/components/GlobalSnackbar.vue'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useAppStore } from '@/renderer/store/app' import { useAppStore } from '@/renderer/store/app'
@@ -75,6 +76,7 @@ const checkForUpdates = async () => {
</DefaultLayout> </DefaultLayout>
<GlobalDialog /> <GlobalDialog />
<UpdateDialog /> <UpdateDialog />
<GlobalSnackbar />
</template> </template>
<style> <style>
+631
View File
@@ -0,0 +1,631 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
const computedInject: typeof import('@vueuse/core').computedInject
const computedWithControl: typeof import('@vueuse/core').computedWithControl
const controlledComputed: typeof import('@vueuse/core').controlledComputed
const controlledRef: typeof import('@vueuse/core').controlledRef
const createApp: typeof import('vue').createApp
const createEventHook: typeof import('@vueuse/core').createEventHook
const createGlobalState: typeof import('@vueuse/core').createGlobalState
const createInjectionState: typeof import('@vueuse/core').createInjectionState
const createPinia: typeof import('pinia').createPinia
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
const createRef: typeof import('@vueuse/core').createRef
const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate
const createSharedComposable: typeof import('@vueuse/core').createSharedComposable
const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise
const createUnrefFn: typeof import('@vueuse/core').createUnrefFn
const customRef: typeof import('vue').customRef
const debouncedRef: typeof import('@vueuse/core').debouncedRef
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineStore: typeof import('pinia').defineStore
const eagerComputed: typeof import('@vueuse/core').eagerComputed
const effectScope: typeof import('vue').effectScope
const extendRef: typeof import('@vueuse/core').extendRef
const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const inject: typeof import('vue').inject
const injectLocal: typeof import('@vueuse/core').injectLocal
const isDefined: typeof import('@vueuse/core').isDefined
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
const manualResetRef: typeof import('@vueuse/core').manualResetRef
const mapActions: typeof import('pinia').mapActions
const mapGetters: typeof import('pinia').mapGetters
const mapState: typeof import('pinia').mapState
const mapStores: typeof import('pinia').mapStores
const mapWritableState: typeof import('pinia').mapWritableState
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onClickOutside: typeof import('@vueuse/core').onClickOutside
const onDeactivated: typeof import('vue').onDeactivated
const onElementRemoval: typeof import('@vueuse/core').onElementRemoval
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onKeyStroke: typeof import('@vueuse/core').onKeyStroke
const onLongPress: typeof import('@vueuse/core').onLongPress
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onStartTyping: typeof import('@vueuse/core').onStartTyping
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const pausableWatch: typeof import('@vueuse/core').pausableWatch
const provide: typeof import('vue').provide
const provideLocal: typeof import('@vueuse/core').provideLocal
const reactify: typeof import('@vueuse/core').reactify
const reactifyObject: typeof import('@vueuse/core').reactifyObject
const reactive: typeof import('vue').reactive
const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
const reactivePick: typeof import('@vueuse/core').reactivePick
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const refAutoReset: typeof import('@vueuse/core').refAutoReset
const refDebounced: typeof import('@vueuse/core').refDebounced
const refDefault: typeof import('@vueuse/core').refDefault
const refManualReset: typeof import('@vueuse/core').refManualReset
const refThrottled: typeof import('@vueuse/core').refThrottled
const refWithControl: typeof import('@vueuse/core').refWithControl
const resolveComponent: typeof import('vue').resolveComponent
const resolveRef: typeof import('@vueuse/core').resolveRef
const setActivePinia: typeof import('pinia').setActivePinia
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const storeToRefs: typeof import('pinia').storeToRefs
const syncRef: typeof import('@vueuse/core').syncRef
const syncRefs: typeof import('@vueuse/core').syncRefs
const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core').throttledWatch
const toRaw: typeof import('vue').toRaw
const toReactive: typeof import('@vueuse/core').toReactive
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
const unref: typeof import('vue').unref
const unrefElement: typeof import('@vueuse/core').unrefElement
const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core').useAnimate
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
const useArrayFind: typeof import('@vueuse/core').useArrayFind
const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
const useArrayMap: typeof import('@vueuse/core').useArrayMap
const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
const useArraySome: typeof import('@vueuse/core').useArraySome
const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue').useAttrs
const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core').useBluetooth
const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
const useCached: typeof import('@vueuse/core').useCached
const useClipboard: typeof import('@vueuse/core').useClipboard
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core').useColorMode
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useCountdown: typeof import('@vueuse/core').useCountdown
const useCounter: typeof import('@vueuse/core').useCounter
const useCssModule: typeof import('vue').useCssModule
const useCssVar: typeof import('@vueuse/core').useCssVar
const useCssVars: typeof import('vue').useCssVars
const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
const useCycleList: typeof import('@vueuse/core').useCycleList
const useDark: typeof import('@vueuse/core').useDark
const useDateFormat: typeof import('@vueuse/core').useDateFormat
const useDebounce: typeof import('@vueuse/core').useDebounce
const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
const useDevicesList: typeof import('@vueuse/core').useDevicesList
const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
const useDraggable: typeof import('@vueuse/core').useDraggable
const useDropZone: typeof import('@vueuse/core').useDropZone
const useElementBounding: typeof import('@vueuse/core').useElementBounding
const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
const useElementHover: typeof import('@vueuse/core').useElementHover
const useElementSize: typeof import('@vueuse/core').useElementSize
const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
const useEventBus: typeof import('@vueuse/core').useEventBus
const useEventListener: typeof import('@vueuse/core').useEventListener
const useEventSource: typeof import('@vueuse/core').useEventSource
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFavicon: typeof import('@vueuse/core').useFavicon
const useFetch: typeof import('@vueuse/core').useFetch
const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFocus: typeof import('@vueuse/core').useFocus
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useFps: typeof import('@vueuse/core').useFps
const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useGamepad: typeof import('@vueuse/core').useGamepad
const useGeolocation: typeof import('@vueuse/core').useGeolocation
const useI18n: typeof import('vue-i18n').useI18n
const useId: typeof import('vue').useId
const useIdle: typeof import('@vueuse/core').useIdle
const useImage: typeof import('@vueuse/core').useImage
const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useInterval: typeof import('@vueuse/core').useInterval
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
const useLastChanged: typeof import('@vueuse/core').useLastChanged
const useLink: typeof import('vue-router').useLink
const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMediaControls: typeof import('@vueuse/core').useMediaControls
const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
const useMemoize: typeof import('@vueuse/core').useMemoize
const useMemory: typeof import('@vueuse/core').useMemory
const useModel: typeof import('vue').useModel
const useMounted: typeof import('@vueuse/core').useMounted
const useMouse: typeof import('@vueuse/core').useMouse
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
const useMousePressed: typeof import('@vueuse/core').useMousePressed
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
const useNetwork: typeof import('@vueuse/core').useNetwork
const useNow: typeof import('@vueuse/core').useNow
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const useOnline: typeof import('@vueuse/core').useOnline
const usePageLeave: typeof import('@vueuse/core').usePageLeave
const useParallax: typeof import('@vueuse/core').useParallax
const useParentElement: typeof import('@vueuse/core').useParentElement
const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
const usePermission: typeof import('@vueuse/core').usePermission
const usePointer: typeof import('@vueuse/core').usePointer
const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency
const usePrevious: typeof import('@vueuse/core').usePrevious
const useRafFn: typeof import('@vueuse/core').useRafFn
const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSSRWidth: typeof import('@vueuse/core').useSSRWidth
const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation
const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea
const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useScroll: typeof import('@vueuse/core').useScroll
const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useShare: typeof import('@vueuse/core').useShare
const useSlots: typeof import('vue').useSlots
const useSorted: typeof import('@vueuse/core').useSorted
const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
const useStepper: typeof import('@vueuse/core').useStepper
const useStorage: typeof import('@vueuse/core').useStorage
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useSupported: typeof import('@vueuse/core').useSupported
const useSwipe: typeof import('@vueuse/core').useSwipe
const useTemplateRef: typeof import('vue').useTemplateRef
const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
const useTextDirection: typeof import('@vueuse/core').useTextDirection
const useTextSelection: typeof import('@vueuse/core').useTextSelection
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
const useThrottle: typeof import('@vueuse/core').useThrottle
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl
const useTimeout: typeof import('@vueuse/core').useTimeout
const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn
const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll
const useTimestamp: typeof import('@vueuse/core').useTimestamp
const useTitle: typeof import('@vueuse/core').useTitle
const useToNumber: typeof import('@vueuse/core').useToNumber
const useToString: typeof import('@vueuse/core').useToString
const useToggle: typeof import('@vueuse/core').useToggle
const useTransition: typeof import('@vueuse/core').useTransition
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useUserMedia: typeof import('@vueuse/core').useUserMedia
const useVModel: typeof import('@vueuse/core').useVModel
const useVModels: typeof import('@vueuse/core').useVModels
const useVibrate: typeof import('@vueuse/core').useVibrate
const useVirtualList: typeof import('@vueuse/core').useVirtualList
const useWakeLock: typeof import('@vueuse/core').useWakeLock
const useWebNotification: typeof import('@vueuse/core').useWebNotification
const useWebSocket: typeof import('@vueuse/core').useWebSocket
const useWebWorker: typeof import('@vueuse/core').useWebWorker
const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const useWindowSize: typeof import('@vueuse/core').useWindowSize
const watch: typeof import('vue').watch
const watchArray: typeof import('@vueuse/core').watchArray
const watchAtMost: typeof import('@vueuse/core').watchAtMost
const watchDebounced: typeof import('@vueuse/core').watchDebounced
const watchDeep: typeof import('@vueuse/core').watchDeep
const watchEffect: typeof import('vue').watchEffect
const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
const watchImmediate: typeof import('@vueuse/core').watchImmediate
const watchOnce: typeof import('@vueuse/core').watchOnce
const watchPausable: typeof import('@vueuse/core').watchPausable
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
const watchThrottled: typeof import('@vueuse/core').watchThrottled
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
const whenever: typeof import('@vueuse/core').whenever
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly manualResetRef: UnwrapRef<typeof import('@vueuse/core')['manualResetRef']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refManualReset: UnwrapRef<typeof import('@vueuse/core')['refManualReset']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useId: UnwrapRef<typeof import('vue')['useId']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}
+119
View File
@@ -0,0 +1,119 @@
<template>
<v-dialog
v-model="isVisible"
max-width="500"
@after-leave="reset"
>
<v-card>
<v-card-title>
{{ isEditMode ? '编辑目录' : '添加目录' }}
</v-card-title>
<v-card-text>
<v-form v-model="formValid">
<v-text-field
v-model="directoryPath"
label="目录名称"
placeholder="请输入目录名称"
:rules="[requiredRule('目录名称')]"
required
density="comfortable"
ref="inputRef"
@keydown.enter="confirm"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="close">取消</v-btn>
<v-btn
color="primary"
@click="confirm"
:disabled="!formValid"
>
{{ isEditMode ? '保存' : '添加' }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { TreeNode } from '@/types'
interface Props {
modelValue: boolean
isEditMode?: boolean
parentDirectory?: TreeNode | null
editingItem?: TreeNode | null
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { path: string; }): void
}
const props = withDefaults(defineProps<Props>(), {
isEditMode: false,
parentDirectory: null,
editingItem: null
})
const emit = defineEmits<Emits>()
// Internal state
const isVisible = ref(false)
const formValid = ref(false)
const directoryPath = ref('')
const inputRef = ref<HTMLInputElement | null>(null)
// Computed
const isEditMode = computed(() => props.isEditMode)
// Watch for visibility changes
watch(() => props.modelValue, (newValue) => {
isVisible.value = newValue
if (newValue) {
// Reset form when dialog opens
directoryPath.value = props.editingItem?.path || ''
autoFocusInput()
}
})
// Watch for internal visibility changes and emit update
watch(isVisible, (newValue) => {
emit('update:modelValue', newValue)
})
// Validation rules
const requiredRule = (fieldName: string) => (v: string | null | undefined) =>
!!v || `${fieldName}为必填项`
// Methods
const autoFocusInput = async () => {
await nextTick()
if (inputRef.value) {
;(inputRef.value as HTMLInputElement).focus()
}
}
const confirm = () => {
if (!directoryPath.value.trim()) return
// Construct the full path if there's a parent directory
emit('confirm', {
path: directoryPath.value,
})
close()
}
const reset = () => {
directoryPath.value = ''
formValid.value = false
}
const close = () => {
isVisible.value = false
}
</script>
+200
View File
@@ -0,0 +1,200 @@
<template>
<v-dialog
v-model="isVisible"
max-width="600"
@after-leave="reset"
>
<v-card>
<v-card-title>
{{ isEditMode ? '编辑文件' : '添加文件' }}
</v-card-title>
<v-card-text>
<v-form v-model="formValid">
<v-text-field
v-model="filePath"
label="文件名"
placeholder="文件名需要带上后缀,如:style skech.doc"
:rules="[requiredRule('文件名')]"
required
density="comfortable"
ref="inputRef"
/>
<!-- 文件选择输入框 -->
<v-text-field
v-model="selectedFilePath"
label="选择文件"
placeholder="点击输入框选择文件"
readonly
:append-inner-icon="'mdi-file'"
density="comfortable"
class="mt-4"
@click="selectSourceFile"
/>
<!-- 文件夹选择输入框 -->
<v-text-field
v-model="selectedFolderPath"
label="选择文件夹"
placeholder="点击输入框选择文件夹"
readonly
:append-inner-icon="'mdi-folder'"
density="comfortable"
class="mt-4"
@click="selectSourceFolder"
/>
<!-- 选择提示 -->
<v-alert
type="info"
variant="tonal"
class="mt-2"
>
<div class="d-flex align-start flex-column">
<span>选择文件会直接复制到目标位置</span>
<span>
选择文件夹则会自动匹配该文件夹下的文件进行复制并且重命名
</span>
<span class="mt-1 font-weight-bold">
文件和文件夹选择其中一个即可
</span>
</div>
</v-alert>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="close">取消</v-btn>
<v-btn
color="primary"
@click="confirm"
:disabled="!formValid || (!selectedFilePath && !selectedFolderPath && !isEditMode)"
>
{{ isEditMode ? '保存' : '添加' }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { TreeNode } from '@/types'
interface Props {
modelValue: boolean
isEditMode?: boolean
parentDirectory?: TreeNode | null
editingItem?: TreeNode | null
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { path: string; sourcePath?: string; sourceType?: 'file' | 'folder' }): void
}
const props = withDefaults(defineProps<Props>(), {
isEditMode: false,
parentDirectory: null,
editingItem: null
})
const emit = defineEmits<Emits>()
// Internal state
const isVisible = ref(false)
const formValid = ref(false)
const filePath = ref('')
const selectedFilePath = ref<string>('')
const selectedFolderPath = ref<string>('')
const inputRef = ref<HTMLInputElement | null>(null)
// Computed
const isEditMode = computed(() => props.isEditMode)
// Watch for visibility changes
watch(() => props.modelValue, (newValue) => {
isVisible.value = newValue
if (newValue) {
// Reset form when dialog opens
filePath.value = props.editingItem?.path || ''
selectedFilePath.value = props.editingItem?.sourceType === 'file' ? props.editingItem?.sourcePath || '' : ''
selectedFolderPath.value = props.editingItem?.sourceType === 'folder' ? props.editingItem?.sourcePath || '' : ''
autoFocusInput()
}
})
// Watch for internal visibility changes and emit update
watch(isVisible, (newValue) => {
emit('update:modelValue', newValue)
})
// Validation rules
const requiredRule = (fieldName: string) => (v: string | null | undefined) =>
!!v || `${fieldName}为必填项`
// Methods
const autoFocusInput = async () => {
await nextTick()
if (inputRef.value) {
;(inputRef.value as HTMLInputElement).focus()
}
}
const selectSourceFile = async () => {
// 使用主进程API来选择文件
const result = await window.mainApi.invoke('msgOpenFile', 'any')
if (result && !result.canceled && result.filePaths.length > 0) {
selectedFilePath.value = result.filePaths[0]
selectedFolderPath.value = '' // 清空文件夹选择
}
}
const selectSourceFolder = async () => {
// 使用主进程API来选择文件夹
const result = await window.mainApi.invoke('msgSelectDirectory')
if (result && !result.canceled && result.filePath) {
selectedFolderPath.value = result.filePath
selectedFilePath.value = '' // 清空文件选择
}
}
const confirm = () => {
if (!filePath.value.trim()) return
// 确定选择的源类型和路径
let sourcePathValue: string | undefined
let sourceType: 'file' | 'folder' | undefined
if (selectedFilePath.value) {
sourcePathValue = selectedFilePath.value
sourceType = 'file'
} else if (selectedFolderPath.value) {
sourcePathValue = selectedFolderPath.value
sourceType = 'folder'
}
emit('confirm', {
path: filePath.value,
sourcePath: sourcePathValue,
sourceType
})
close()
}
const reset = () => {
filePath.value = ''
selectedFilePath.value = ''
selectedFolderPath.value = ''
formValid.value = false
}
const close = () => {
isVisible.value = false
}
</script>
@@ -0,0 +1,34 @@
<template>
<v-snackbar
v-model="snackbar.isVisible"
:color="snackbar.color"
:timeout="snackbar.timeout"
location="top"
>
{{ snackbar.message }}
<template #actions>
<v-btn
v-for="(action, index) in snackbar.actions"
:key="index"
:color="action.color || 'white'"
variant="text"
@click="action.handler && action.handler()"
>
{{ action.text }}
</v-btn>
<v-btn
icon
variant="text"
@click="snackbar.hide"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script setup lang="ts">
import { useSnackbarStore } from '@/renderer/store/snackbar'
const snackbar = useSnackbarStore()
</script>
+108
View File
@@ -0,0 +1,108 @@
<template>
<v-card class="d-flex flex-column" height="100%" variant="outlined">
<v-card-item>
<v-card-title>{{ template.name }}</v-card-title>
<v-card-subtitle>{{ template.description }}</v-card-subtitle>
</v-card-item>
<v-card-text class="flex-grow-1 py-2">
<v-chip-group column>
<v-chip size="small" color="primary" variant="tonal">
文件: {{ nodeCount.files }}
</v-chip>
<v-chip size="small" color="success" variant="tonal">
目录: {{ nodeCount.directories }}
</v-chip>
</v-chip-group>
</v-card-text>
<v-divider class="mt-auto"></v-divider>
<v-card-actions class="pa-2 justify-space-between">
<div>
<v-btn color="primary" variant="text" size="small" @click="$emit('view', template)">
查看
</v-btn>
<v-btn color="success" variant="text" size="small" @click="$emit('execute', template)">
执行
</v-btn>
</div>
<v-menu location="bottom end">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon variant="text" size="small" density="comfortable">
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item @click="$emit('edit', template)">
<template v-slot:prepend>
<v-icon>mdi-pencil</v-icon>
</template>
<v-list-item-title>修改</v-list-item-title>
</v-list-item>
<v-list-item @click="$emit('copy', template)">
<template v-slot:prepend>
<v-icon color="primary">mdi-content-copy</v-icon>
</template>
<v-list-item-title>复制</v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-item @click="$emit('delete', template)">
<template v-slot:prepend>
<v-icon color="error">mdi-delete</v-icon>
</template>
<v-list-item-title class="text-error">删除</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-card-actions>
</v-card>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Template, TreeNode } from '@/types'
interface Props {
template: Template
}
interface Emits {
(e: 'view', template: Template): void
(e: 'execute', template: Template): void
(e: 'edit', template: Template): void
(e: 'delete', template: Template): void
(e: 'copy', template: Template): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// Count files and directories in tree structure
const nodeCount = computed(() => {
let files = 0
let directories = 0
const count = (nodeList: TreeNode[]) => {
nodeList.forEach(node => {
if (node.type === 'file') {
files++
} else if (node.type === 'directory') {
directories++
if (node.children) {
count(node.children)
}
}
})
}
count(props.template.treeStructure || [])
return { files, directories }
})
</script>
+5 -2
View File
@@ -3,15 +3,18 @@
variant="outlined" variant="outlined"
class="pa-2" class="pa-2"
:height="height" :height="height"
style="overflow: auto;"
> >
<v-treeview <v-treeview
open-all open-all
:items="items" :items="items"
item-title="name" item-title="name"
item-key="id" item-value="id"
item-children="children"
open-on-click open-on-click
:hoverable="true" :hoverable="true"
:indent-lines="'default'"
separate-roots
density="compact"
> >
<template #prepend="{ item }"> <template #prepend="{ item }">
<v-icon <v-icon
+32 -22
View File
@@ -22,35 +22,45 @@ const headerMenus: {
text: string text: string
path: string path: string
}[] = [ }[] = [
{ {
icon: mdiHome, icon: mdiHome,
text: 'title.main', text: 'title.main',
path: '/' path: '/'
}, },
{ {
icon: mdiFitToScreenOutline, icon: mdiFitToScreenOutline,
text: 'title.second', text: 'title.second',
path: '/second' path: '/second'
} }
] ]
// Get app version
const appVersion = ref('')
onMounted(async () => {
try {
appVersion.value = await window.mainApi.invoke('msgRequestGetVersion')
} catch (error) {
console.error('Failed to get app version:', error)
appVersion.value = 'unknown'
}
})
</script> </script>
<template> <template>
<v-app-bar color="primary" density="compact"> <v-app-bar color="primary" density="compact">
<v-app-bar-title @click="handleRoute('/')"> <v-app-bar-title @click="handleRoute('/')">
GDT Tool GDT Tool
</v-app-bar-title> </v-app-bar-title>
<!-- <template #append> <template #append>
<v-btn <v-chip
v-for="menu in headerMenus" variant="tonal"
:key="menu.path" color="white"
:prepend-icon="menu.icon" size="small"
variant="text" class="mr-2"
:class="{ active: isCurrentRoute(menu.path) }"
@click="handleRoute(menu.path)"
> >
{{ t(menu.text) }} v{{ appVersion }}
</v-btn> </v-chip>
</template> --> </template>
</v-app-bar> </v-app-bar>
</template> </template>
<style scoped> <style scoped>
File diff suppressed because it is too large. Load diff
+17 -80
View File
@@ -28,62 +28,14 @@
<div v-else> <div v-else>
<v-row> <v-row>
<v-col v-for="template in templates" :key="template.id" cols="12" sm="6" md="4" lg="3"> <v-col v-for="template in templates" :key="template.id" cols="12" sm="6" md="4" lg="3">
<v-card class="d-flex flex-column" height="100%"> <TemplateCard
<v-card-item> :template="template"
<v-card-title>{{ template.name }}</v-card-title> @view="viewTemplate"
<v-card-subtitle>{{ template.description }}</v-card-subtitle> @execute="executeTemplate"
</v-card-item> @edit="editTemplate"
@copy="copyTemplate"
<v-card-text class="flex-grow-1 py-2"> @delete="deleteTemplate"
<v-chip-group column> />
<v-chip size="small" color="primary" variant="tonal">
节点: {{ countTreeNodes(template.treeStructure).files }}
</v-chip>
<v-chip size="small" color="success" variant="tonal">
目录: {{ countTreeNodes(template.treeStructure).directories }}
</v-chip>
</v-chip-group>
</v-card-text>
<v-divider class="mt-auto"></v-divider>
<v-card-actions class="pa-2 justify-space-between">
<div>
<v-btn color="primary" variant="text" size="small" @click="viewTemplate(template)">
查看
</v-btn>
<v-btn color="success" variant="text" size="small" @click="executeTemplate(template)">
执行
</v-btn>
</div>
<v-menu location="bottom end">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon variant="text" size="small" density="comfortable">
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item @click="editTemplate(template)">
<template v-slot:prepend>
<v-icon>mdi-pencil</v-icon>
</template>
<v-list-item-title>修改</v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-item @click="deleteTemplate(template)">
<template v-slot:prepend>
<v-icon color="error">mdi-delete</v-icon>
</template>
<v-list-item-title class="text-error">删除</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-card-actions>
</v-card>
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
@@ -113,7 +65,7 @@
<!-- Execute template dialog --> <!-- Execute template dialog -->
<v-dialog v-model="showExecuteDialog" max-width="600"> <v-dialog v-model="showExecuteDialog" max-width="600">
<v-card> <v-card>
<v-card-title>执行模板</v-card-title> <v-card-title>执行模板: {{ selectedTemplate?.name }}</v-card-title>
<v-card-text> <v-card-text>
<v-form v-model="executeFormValid"> <v-form v-model="executeFormValid">
<v-text-field v-model="executePath" label="选择目录" readonly @click="selectDirectory" <v-text-field v-model="executePath" label="选择目录" readonly @click="selectDirectory"
@@ -138,9 +90,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useTemplateStore, TreeNode } from '@/renderer/store/template' import { useTemplateStore } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template' import { TreeNode, Template } from '@/types'
import TemplateTree from '@/renderer/components/TemplateTree.vue' import TemplateTree from '@/renderer/components/TemplateTree.vue'
import TemplateCard from '@/renderer/components/TemplateCard.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service' import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
const router = useRouter() const router = useRouter()
@@ -166,28 +119,7 @@ const executePath = ref('')
const baseDirNames = ref('') const baseDirNames = ref('')
const executeFormValid = ref(false) const executeFormValid = ref(false)
// Count files and directories in tree structure
const countTreeNodes = (nodes: TreeNode[]) => {
let files = 0;
let directories = 0;
const count = (nodeList: TreeNode[]) => {
nodeList.forEach(node => {
if (node.type === 'file') {
files++;
} else if (node.type === 'directory') {
directories++;
if (node.children) {
count(node.children);
}
}
});
};
count(nodes || []);
return { files, directories };
}
const templateTree = computed<TreeNode[]>(() => { const templateTree = computed<TreeNode[]>(() => {
if (!selectedTemplate.value) return [] if (!selectedTemplate.value) return []
@@ -209,6 +141,11 @@ const editTemplate = (template: Template) => {
router.push(`/create-template/${template.id}`) router.push(`/create-template/${template.id}`)
} }
const copyTemplate = (template: Template) => {
router.push(`/create-template?copyId=${template.id}`)
}
const deleteTemplate = async (template: Template) => { const deleteTemplate = async (template: Template) => {
const confirmed = await ConfirmDialogService.confirm({ const confirmed = await ConfirmDialogService.confirm({
title: '删除模板', title: '删除模板',
+48
View File
@@ -0,0 +1,48 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface SnackbarAction {
text: string
color?: string
handler?: () => void
}
export const useSnackbarStore = defineStore('snackbar', () => {
const isVisible = ref(false)
const message = ref('')
const color = ref('info')
const timeout = ref(3000)
const actions = ref<SnackbarAction[]>([])
const show = (msg: string, clr = 'info', tout = 3000, actionList: SnackbarAction[] = []) => {
message.value = msg
color.value = clr
timeout.value = tout
actions.value = actionList
isVisible.value = true
}
const success = (msg: string, tout = 3000) => show(msg, 'success', tout)
const error = (msg: string, tout = 3000) => show(msg, 'error', tout)
const warning = (msg: string, tout = 3000) => show(msg, 'warning', tout)
const info = (msg: string, tout = 3000) => show(msg, 'info', tout)
const hide = () => {
isVisible.value = false
actions.value = []
}
return {
isVisible,
message,
color,
timeout,
actions,
show,
success,
error,
warning,
info,
hide
}
})
+1 -20
View File
@@ -1,25 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
import { TreeNode, Template } from '@/types'
// 定义树状结构的节点
export interface TreeNode {
id: string
name: string
path: string // 完整路径
type: 'directory' | 'file'
children?: TreeNode[]
// 文件特定属性
content?: string
sourceFilePath?: string
}
export interface Template {
id: string
name: string
description: string
// 使用树状结构存储
treeStructure: TreeNode[]
}
export const useTemplateStore = defineStore('template', () => { export const useTemplateStore = defineStore('template', () => {
const templates = ref<Template[]>([]) const templates = ref<Template[]>([])
+11
View File
@@ -9,3 +9,14 @@ export async function openExternal(url: string): Promise<void> {
export async function openFile(type: string): Promise<any> { export async function openFile(type: string): Promise<any> {
return window.mainApi.invoke('msgOpenFile', type) return window.mainApi.invoke('msgOpenFile', type)
} }
/**
* 生成唯一ID
* @param prefix ID前缀,默认为空
* @returns 唯一ID字符串
*/
export function generateUniqueId(prefix: string = ''): string {
const timestamp = Date.now().toString(36) // 转换为36进制,更短
const randomStr = Math.random().toString(36).substring(2, 9) // 7位随机字符
return prefix ? `${prefix}-${timestamp}-${randomStr}` : `${timestamp}-${randomStr}`
}
+18
View File
@@ -0,0 +1,18 @@
// 定义树状结构的节点
export interface TreeNode {
id: string
name: string
path: string // 完整路径
type: 'directory' | 'file'
children?: TreeNode[]
sourcePath?: string
sourceType?: 'file' | 'folder' // 记录选择的是文件还是文件夹
}
export interface Template {
id: string
name: string
description: string
// 使用树状结构存储
treeStructure: TreeNode[]
}
+1 -1
View File
@@ -19,7 +19,7 @@
}, },
"lib": ["esnext", "dom"] "lib": ["esnext", "dom"]
}, },
"include": ["src/*.ts", "src/*.d.ts", "src/renderer"], "include": ["src/*.ts", "src/*.d.ts", "src/renderer", "src/types"],
"references": [ "references": [
{ {
"path": "./tsconfig.node.json" "path": "./tsconfig.node.json"
+1
View File
@@ -12,6 +12,7 @@
"include": [ "include": [
"src/main", "src/main",
"src/preload", "src/preload",
"src/types",
"package.json", "package.json",
"eslint.config.ts", "eslint.config.ts",
"vite.config.mts", "vite.config.mts",
+18
View File
@@ -6,6 +6,7 @@ import EslintPlugin from 'vite-plugin-eslint'
import VuetifyPlugin from 'vite-plugin-vuetify' import VuetifyPlugin from 'vite-plugin-vuetify'
import VueJsx from '@vitejs/plugin-vue-jsx' import VueJsx from '@vitejs/plugin-vue-jsx'
import Vue from '@vitejs/plugin-vue' import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import { rmSync } from 'fs' import { rmSync } from 'fs'
import { resolve, dirname } from 'path' import { resolve, dirname } from 'path'
import { builtinModules } from 'module' import { builtinModules } from 'module'
@@ -100,6 +101,23 @@ export default defineConfig(({ mode }) => {
plugins: [ plugins: [
Vue(), Vue(),
VueJsx(), VueJsx(),
// Auto import commonly used APIs
AutoImport({
imports: [
'vue',
'vue-router',
'vue-i18n',
'@vueuse/core',
'pinia'
],
dirs: [
'./src/renderer/composables',
'./src/renderer/utils',
'./src/renderer/store',
'./src/renderer/components'
],
vueTemplate: true
}),
// Docs: https://github.com/vuetifyjs/vuetify-loader // Docs: https://github.com/vuetifyjs/vuetify-loader
VuetifyPlugin({ VuetifyPlugin({
autoImport: true autoImport: true