feat: Implement template management feature
- Added IPC handlers for selecting directories, loading, saving, and executing templates. - Created a new TemplateListScreen for displaying and managing templates. - Developed CreateTemplateScreen for creating and editing templates with file and directory management. - Updated routing to include new template screens. - Enhanced MainScreen with navigation to template management. - Added localization for new template-related strings in English and Chinese. - Introduced a Pinia store for managing template data. - Implemented tests for template creation, listing, and detail viewing.
This commit is contained in:
1 parent
742eb7040f
commit
1aecd5f4b6
15 files changed
+1225
-140
No files matched your search
@@ -43,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.3",
|
||||||
|
"fs-extra": "^11.3.2",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"qsu": "^1.10.2",
|
"qsu": "^1.10.2",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"@eslint/js": "^9.37.0",
|
"@eslint/js": "^9.37.0",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.56.1",
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@typescript-eslint/parser": "^8.46.1",
|
"@typescript-eslint/parser": "^8.46.1",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||||
|
|||||||
Generated
+37
-5
@@ -11,6 +11,9 @@ importers:
|
|||||||
electron-log:
|
electron-log:
|
||||||
specifier: ^5.4.3
|
specifier: ^5.4.3
|
||||||
version: 5.4.3
|
version: 5.4.3
|
||||||
|
fs-extra:
|
||||||
|
specifier: ^11.3.2
|
||||||
|
version: 11.3.2
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
|
version: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
|
||||||
@@ -36,6 +39,9 @@ importers:
|
|||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.56.1
|
specifier: ^1.56.1
|
||||||
version: 1.56.1
|
version: 1.56.1
|
||||||
|
'@types/fs-extra':
|
||||||
|
specifier: ^11.0.4
|
||||||
|
version: 11.0.4
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
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)
|
||||||
@@ -644,56 +650,67 @@ packages:
|
|||||||
resolution: {integrity: sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==}
|
resolution: {integrity: sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.47.1':
|
'@rollup/rollup-linux-arm-musleabihf@4.47.1':
|
||||||
resolution: {integrity: sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==}
|
resolution: {integrity: sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.47.1':
|
'@rollup/rollup-linux-arm64-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==}
|
resolution: {integrity: sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.47.1':
|
'@rollup/rollup-linux-arm64-musl@4.47.1':
|
||||||
resolution: {integrity: sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==}
|
resolution: {integrity: sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loongarch64-gnu@4.47.1':
|
'@rollup/rollup-linux-loongarch64-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==}
|
resolution: {integrity: sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.47.1':
|
'@rollup/rollup-linux-ppc64-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==}
|
resolution: {integrity: sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.47.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==}
|
resolution: {integrity: sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.47.1':
|
'@rollup/rollup-linux-riscv64-musl@4.47.1':
|
||||||
resolution: {integrity: sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==}
|
resolution: {integrity: sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.47.1':
|
'@rollup/rollup-linux-s390x-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==}
|
resolution: {integrity: sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.47.1':
|
'@rollup/rollup-linux-x64-gnu@4.47.1':
|
||||||
resolution: {integrity: sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==}
|
resolution: {integrity: sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.47.1':
|
'@rollup/rollup-linux-x64-musl@4.47.1':
|
||||||
resolution: {integrity: sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==}
|
resolution: {integrity: sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.47.1':
|
'@rollup/rollup-win32-arm64-msvc@4.47.1':
|
||||||
resolution: {integrity: sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==}
|
resolution: {integrity: sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==}
|
||||||
@@ -740,6 +757,9 @@ packages:
|
|||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/fs-extra@11.0.4':
|
||||||
|
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
|
||||||
|
|
||||||
'@types/fs-extra@9.0.13':
|
'@types/fs-extra@9.0.13':
|
||||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||||
|
|
||||||
@@ -752,6 +772,9 @@ packages:
|
|||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
|
'@types/jsonfile@6.1.4':
|
||||||
|
resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||||
|
|
||||||
@@ -1675,8 +1698,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
fs-extra@11.3.0:
|
fs-extra@11.3.2:
|
||||||
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
|
resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==}
|
||||||
engines: {node: '>=14.14'}
|
engines: {node: '>=14.14'}
|
||||||
|
|
||||||
fs-extra@7.0.1:
|
fs-extra@7.0.1:
|
||||||
@@ -3423,7 +3446,7 @@ snapshots:
|
|||||||
'@malept/cross-spawn-promise': 2.0.0
|
'@malept/cross-spawn-promise': 2.0.0
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
dir-compare: 4.2.0
|
dir-compare: 4.2.0
|
||||||
fs-extra: 11.3.0
|
fs-extra: 11.3.2
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
plist: 3.1.0
|
plist: 3.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3433,7 +3456,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cross-dirname: 0.1.0
|
cross-dirname: 0.1.0
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
fs-extra: 11.3.0
|
fs-extra: 11.3.2
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
postject: 1.0.0-alpha.6
|
postject: 1.0.0-alpha.6
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3768,6 +3791,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/fs-extra@11.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/jsonfile': 6.1.4
|
||||||
|
'@types/node': 22.15.15
|
||||||
|
|
||||||
'@types/fs-extra@9.0.13':
|
'@types/fs-extra@9.0.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.15
|
'@types/node': 22.15.15
|
||||||
@@ -3778,6 +3806,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
|
'@types/jsonfile@6.1.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.15.15
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.15
|
'@types/node': 22.15.15
|
||||||
@@ -5108,7 +5140,7 @@ snapshots:
|
|||||||
jsonfile: 6.1.0
|
jsonfile: 6.1.0
|
||||||
universalify: 2.0.1
|
universalify: 2.0.1
|
||||||
|
|
||||||
fs-extra@11.3.0:
|
fs-extra@11.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
jsonfile: 6.1.0
|
jsonfile: 6.1.0
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
|
import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
|
||||||
import Constants from './utils/Constants'
|
import Constants from './utils/Constants'
|
||||||
|
import { join, dirname } from 'path'
|
||||||
|
import {
|
||||||
|
writeFileSync,
|
||||||
|
readFileSync,
|
||||||
|
existsSync,
|
||||||
|
ensureDirSync
|
||||||
|
} from 'fs-extra'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IPC Communications
|
* IPC Communications
|
||||||
@@ -36,5 +43,121 @@ export default class IPCs {
|
|||||||
return dialogResult
|
return dialogResult
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Select directory
|
||||||
|
ipcMain.handle('msgSelectDirectory', async () => {
|
||||||
|
const dialogResult = await dialog.showOpenDialog({
|
||||||
|
properties: ['openDirectory']
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!dialogResult.canceled && dialogResult.filePaths.length > 0) {
|
||||||
|
return {
|
||||||
|
canceled: false,
|
||||||
|
filePath: dialogResult.filePaths[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canceled: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load data from local file
|
||||||
|
ipcMain.handle(
|
||||||
|
'msgLoadData',
|
||||||
|
async (event: IpcMainEvent, fileName: string) => {
|
||||||
|
try {
|
||||||
|
const userDataPath = join(Constants.APP_DATA_PATH, 'data')
|
||||||
|
const filePath = join(userDataPath, fileName)
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
ensureDirSync(userDataPath)
|
||||||
|
|
||||||
|
if (existsSync(filePath)) {
|
||||||
|
const data = readFileSync(filePath, 'utf-8')
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save data to local file
|
||||||
|
ipcMain.handle(
|
||||||
|
'msgSaveData',
|
||||||
|
async (event: IpcMainEvent, fileName: string, data: string) => {
|
||||||
|
try {
|
||||||
|
const userDataPath = join(Constants.APP_DATA_PATH, 'data')
|
||||||
|
const filePath = join(userDataPath, fileName)
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
ensureDirSync(userDataPath)
|
||||||
|
|
||||||
|
writeFileSync(filePath, data, 'utf-8')
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving data:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Execute template
|
||||||
|
ipcMain.handle(
|
||||||
|
'msgExecuteTemplate',
|
||||||
|
async (
|
||||||
|
event: IpcMainEvent,
|
||||||
|
templateId: string,
|
||||||
|
targetPath: string,
|
||||||
|
baseDirNames: string[]
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// Load templates data
|
||||||
|
const userDataPath = join(Constants.APP_DATA_PATH, 'data')
|
||||||
|
const filePath = join(userDataPath, 'templates.json')
|
||||||
|
|
||||||
|
let templates = []
|
||||||
|
if (existsSync(filePath)) {
|
||||||
|
const templatesData = readFileSync(filePath, 'utf-8')
|
||||||
|
templates = JSON.parse(templatesData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = templates.find((t) => t.id === templateId)
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
return { success: false, error: 'Template not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directories and files for each base directory name
|
||||||
|
for (const baseDirName of baseDirNames) {
|
||||||
|
if (!baseDirName.trim()) continue
|
||||||
|
|
||||||
|
// Create base directory
|
||||||
|
const baseDirPath = join(targetPath, baseDirName)
|
||||||
|
ensureDirSync(baseDirPath)
|
||||||
|
|
||||||
|
// Create directories from template
|
||||||
|
for (const dir of template.directories) {
|
||||||
|
const dirPath = join(baseDirPath, dir.path)
|
||||||
|
ensureDirSync(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create files from template
|
||||||
|
for (const file of template.files) {
|
||||||
|
const filePath = join(baseDirPath, file.path)
|
||||||
|
const fileDir = dirname(filePath)
|
||||||
|
ensureDirSync(fileDir)
|
||||||
|
writeFileSync(filePath, file.content, 'utf-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error executing template:', error)
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { join, dirname, resolve } from 'path'
|
import { join, dirname, resolve } from 'path'
|
||||||
import { name, version, debug } from '../../../package.json'
|
import { name, version, debug } from '../../../package.json'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
import { app } from 'electron'
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
@@ -34,6 +35,8 @@ export default class Constants {
|
|||||||
|
|
||||||
static IS_DEV_ENV = process.env.NODE_ENV === 'development'
|
static IS_DEV_ENV = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
|
static APP_DATA_PATH = app.getPath('userData')
|
||||||
|
|
||||||
static PUBLIC_PATH = Constants.IS_DEV_ENV
|
static PUBLIC_PATH = Constants.IS_DEV_ENV
|
||||||
? resolve(__dirname, '../../src/public')
|
? resolve(__dirname, '../../src/public')
|
||||||
: resolve(__dirname, '..')
|
: resolve(__dirname, '..')
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ log.transports.console.format = '{h}:{i}:{s}.{ms} {text}'
|
|||||||
const mainAvailChannels: string[] = [
|
const mainAvailChannels: string[] = [
|
||||||
'msgRequestGetVersion',
|
'msgRequestGetVersion',
|
||||||
'msgOpenExternalLink',
|
'msgOpenExternalLink',
|
||||||
'msgOpenFile'
|
'msgOpenFile',
|
||||||
|
'msgSelectDirectory',
|
||||||
|
'msgLoadData',
|
||||||
|
'msgSaveData',
|
||||||
|
'msgExecuteTemplate'
|
||||||
]
|
]
|
||||||
const rendererAvailChannels: string[] = []
|
const rendererAvailChannels: string[] = []
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
"title": {
|
"title": {
|
||||||
"main": "Main Screen",
|
"main": "Main Screen",
|
||||||
"second": "Second Screen",
|
"second": "Second Screen",
|
||||||
"error": "Unknown Error"
|
"error": "Unknown Error",
|
||||||
|
"templates": "Template List",
|
||||||
|
"create-template": "Create Template"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"change-theme": "Change Theme",
|
"change-theme": "Change Theme",
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
{
|
{
|
||||||
"desc": {
|
"desc": {
|
||||||
"welcome-title": "你好武特龙! 一切都准备好了。",
|
"welcome-title": "你好! 模板生成器",
|
||||||
"welcome-desc": "您现在可以编写跨平台的 Web 应用程序。 实时查看更改并使用一个命令构建到多个平台。 如果您还不熟悉它,可以从下面的文档页面获得帮助,或者运行一些预先编写的示例代码。",
|
"welcome-desc": "您现在可以创建和管理文件模板。 实时查看更改并使用模板批量生成文件和目录。",
|
||||||
"second-desc": "您已移至第二个屏幕! 这里什么都没有!",
|
"second-desc": "您已移至第二个屏幕! 这里什么都没有!",
|
||||||
"selected-file": "选定的文件:{filePath}."
|
"selected-file": "选定的文件:{filePath}."
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"main": "主屏幕",
|
"main": "模板生成器",
|
||||||
"second": "第二屏",
|
"second": "第二屏",
|
||||||
"error": "未知错误"
|
"error": "未知错误",
|
||||||
|
"templates": "模板列表",
|
||||||
|
"create-template": "创建模板"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"change-theme": "改变主题",
|
"change-theme": "改变主题",
|
||||||
|
|||||||
@@ -6,9 +6,20 @@ export default createRouter({
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: MainScreen,
|
redirect: '/templates'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/templates',
|
||||||
|
component: () => import('@/renderer/screens/TemplateListScreen.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
titleKey: 'title.main'
|
titleKey: 'title.templates'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/create-template',
|
||||||
|
component: () => import('@/renderer/screens/CreateTemplateScreen.vue'),
|
||||||
|
meta: {
|
||||||
|
titleKey: 'title.create-template'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<h2 class="mb-4">
|
||||||
|
{{ templateId ? '编辑模板' : '创建模板' }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<v-form v-model="formValid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="template.name"
|
||||||
|
label="模板名称 *"
|
||||||
|
:rules="[(v) => !!v || '模板名称不能为空']"
|
||||||
|
required
|
||||||
|
data-testid="template-name-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-textarea
|
||||||
|
v-model="template.description"
|
||||||
|
label="模板描述"
|
||||||
|
rows="2"
|
||||||
|
data-testid="template-description-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Template content -->
|
||||||
|
<v-card class="mb-4">
|
||||||
|
<v-card-title>模板内容</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<!-- Files section -->
|
||||||
|
<v-expansion-panels class="mb-4">
|
||||||
|
<v-expansion-panel>
|
||||||
|
<v-expansion-panel-title>文件</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<v-data-table
|
||||||
|
:headers="fileHeaders"
|
||||||
|
:items="template.files"
|
||||||
|
class="elevation-1"
|
||||||
|
>
|
||||||
|
<template v-slot:item.actions="{ item }">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
color="error"
|
||||||
|
@click="removeFile(item)"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="showAddFileDialog = true"
|
||||||
|
prepend-icon="mdi-file-plus"
|
||||||
|
>
|
||||||
|
添加文件
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
|
||||||
|
<!-- Directories section -->
|
||||||
|
<v-expansion-panel>
|
||||||
|
<v-expansion-panel-title>目录</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<v-data-table
|
||||||
|
:headers="dirHeaders"
|
||||||
|
:items="template.directories"
|
||||||
|
class="elevation-1"
|
||||||
|
>
|
||||||
|
<template v-slot:item.actions="{ item }">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
color="error"
|
||||||
|
@click="removeDirectory(item)"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="showAddDirDialog = true"
|
||||||
|
prepend-icon="mdi-folder-plus"
|
||||||
|
>
|
||||||
|
添加目录
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
:color="templateId ? 'warning' : 'success'"
|
||||||
|
@click="saveTemplate"
|
||||||
|
:disabled="!formValid"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
>
|
||||||
|
{{ templateId ? '更新模板' : '保存模板' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="ml-2"
|
||||||
|
@click="cancel"
|
||||||
|
prepend-icon="mdi-arrow-left"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Add file dialog -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showAddFileDialog"
|
||||||
|
max-width="600"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>添加文件</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form v-model="fileFormValid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newFile.path"
|
||||||
|
label="文件路径 *"
|
||||||
|
placeholder="例如: src/index.js"
|
||||||
|
:rules="[(v) => !!v || '文件路径不能为空']"
|
||||||
|
data-testid="add-file-path-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-textarea
|
||||||
|
v-model="newFile.content"
|
||||||
|
label="文件内容"
|
||||||
|
placeholder="输入文件内容..."
|
||||||
|
rows="6"
|
||||||
|
data-testid="add-file-content-input"
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="showAddFileDialog = false"> 取消 </v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="addFile"
|
||||||
|
:disabled="!fileFormValid"
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Add directory dialog -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showAddDirDialog"
|
||||||
|
max-width="600"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>添加目录</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form v-model="dirFormValid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newDir.path"
|
||||||
|
label="目录路径 *"
|
||||||
|
placeholder="例如: src/components"
|
||||||
|
:rules="[(v) => !!v || '目录路径不能为空']"
|
||||||
|
data-testid="add-dir-path-input"
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="showAddDirDialog = false"> 取消 </v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="addDirectory"
|
||||||
|
:disabled="!dirFormValid"
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { useTemplateStore } from '@/renderer/store/template'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const templateStore = useTemplateStore()
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
const formValid = ref(false)
|
||||||
|
const fileFormValid = ref(false)
|
||||||
|
const dirFormValid = ref(false)
|
||||||
|
|
||||||
|
// Template data
|
||||||
|
const templateId = ref(route.params.id as string)
|
||||||
|
const template = ref({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
files: [] as any[],
|
||||||
|
directories: [] as any[]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
const showAddFileDialog = ref(false)
|
||||||
|
const showAddDirDialog = ref(false)
|
||||||
|
|
||||||
|
// New file data
|
||||||
|
const newFile = ref({
|
||||||
|
path: '',
|
||||||
|
content: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// New directory data
|
||||||
|
const newDir = ref({
|
||||||
|
path: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// Table headers
|
||||||
|
const fileHeaders = ref([
|
||||||
|
{ title: '文件路径', key: 'path' },
|
||||||
|
{ title: '操作', key: 'actions', sortable: false }
|
||||||
|
])
|
||||||
|
|
||||||
|
const dirHeaders = ref([
|
||||||
|
{ title: '目录路径', key: 'path' },
|
||||||
|
{ title: '操作', key: 'actions', sortable: false }
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await templateStore.loadTemplates()
|
||||||
|
|
||||||
|
if (templateId.value) {
|
||||||
|
// Edit existing template
|
||||||
|
const existingTemplate = templateStore.getTemplateById(templateId.value)
|
||||||
|
if (existingTemplate) {
|
||||||
|
template.value = { ...existingTemplate }
|
||||||
|
} else {
|
||||||
|
// If template doesn't exist, redirect to template list
|
||||||
|
router.push('/templates')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// New template
|
||||||
|
template.value = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
files: [],
|
||||||
|
directories: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addFile = () => {
|
||||||
|
if (!newFile.value.path.trim()) return
|
||||||
|
|
||||||
|
// Check for duplicate file paths
|
||||||
|
const exists = template.value.files.some((f) => f.path === newFile.value.path)
|
||||||
|
if (exists) {
|
||||||
|
alert('该文件路径已存在!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
template.value.files.push({
|
||||||
|
path: newFile.value.path,
|
||||||
|
content: newFile.value.content
|
||||||
|
})
|
||||||
|
|
||||||
|
newFile.value = { path: '', content: '' }
|
||||||
|
showAddFileDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFile = (file: any) => {
|
||||||
|
template.value.files = template.value.files.filter((f) => f !== file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDirectory = () => {
|
||||||
|
if (!newDir.value.path.trim()) return
|
||||||
|
|
||||||
|
// Check for duplicate directory paths
|
||||||
|
const exists = template.value.directories.some(
|
||||||
|
(d) => d.path === newDir.value.path
|
||||||
|
)
|
||||||
|
if (exists) {
|
||||||
|
alert('该目录路径已存在!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
template.value.directories.push({
|
||||||
|
path: newDir.value.path
|
||||||
|
})
|
||||||
|
|
||||||
|
newDir.value = { path: '' }
|
||||||
|
showAddDirDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeDirectory = (dir: any) => {
|
||||||
|
template.value.directories = template.value.directories.filter(
|
||||||
|
(d) => d !== dir
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveTemplate = () => {
|
||||||
|
if (!formValid.value) return
|
||||||
|
|
||||||
|
if (templateId.value) {
|
||||||
|
// Update existing template
|
||||||
|
templateStore.updateTemplate(template.value)
|
||||||
|
} else {
|
||||||
|
// Create new template
|
||||||
|
templateStore.addTemplate(template.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push('/templates')
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.push('/templates')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+133
-125
@@ -3,16 +3,22 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import { openExternal, openFile } from '@/renderer/utils'
|
import { openExternal, openFile } from '@/renderer/utils'
|
||||||
import { useCounterStore } from '@/renderer/store/counter'
|
import { useCounterStore } from '@/renderer/store/counter'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, onBeforeMount } from 'vue'
|
||||||
import {
|
import {
|
||||||
mdiBrightness6,
|
mdiBrightness6,
|
||||||
mdiFileDocument,
|
mdiFileDocument,
|
||||||
mdiFolderOpen,
|
mdiFolderOpen,
|
||||||
mdiGithub,
|
mdiGithub,
|
||||||
mdiPlusCircle
|
mdiPlusCircle,
|
||||||
|
mdiFolderMultiple,
|
||||||
|
mdiFileDocumentMultiple,
|
||||||
|
mdiArrowRight,
|
||||||
|
mdiHome
|
||||||
} from '@mdi/js'
|
} from '@mdi/js'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const { t, locale, availableLocales } = useI18n()
|
const { t, locale, availableLocales } = useI18n()
|
||||||
const { increaseCount } = useCounterStore()
|
const { increaseCount } = useCounterStore()
|
||||||
const { count } = storeToRefs(useCounterStore())
|
const { count } = storeToRefs(useCounterStore())
|
||||||
@@ -60,140 +66,142 @@ const handleOpenFile = async () => {
|
|||||||
selectedFile.value = dialogResult.filePaths[0]
|
selectedFile.value = dialogResult.filePaths[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navigateToTemplates = () => {
|
||||||
|
router.push('/templates')
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateToCreateTemplate = () => {
|
||||||
|
router.push('/create-template')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row
|
<v-row class="text-center mb-6">
|
||||||
no-gutters
|
<v-col cols="12">
|
||||||
class="text-center align-center"
|
<h1 class="text-h4 font-weight-bold mb-4">
|
||||||
>
|
{{ t('title.main') }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-h6 mb-6">
|
||||||
|
{{ t('desc.welcome-desc') }}
|
||||||
|
</p>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row justify="center" class="mb-6">
|
||||||
|
<v-col cols="12" md="8" lg="6">
|
||||||
|
<v-card class="pa-6 elevation-4">
|
||||||
|
<v-card-title class="text-h5 mb-4">
|
||||||
|
模板管理器
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p>创建、管理和使用文件模板来批量生成目录和文件结构</p>
|
||||||
|
<p class="mt-4">
|
||||||
|
App Version: <strong>{{ appVersion }}</strong>
|
||||||
|
</p>
|
||||||
|
<p v-if="selectedFile">
|
||||||
|
{{
|
||||||
|
t('desc.selected-file', {
|
||||||
|
filePath: selectedFile
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="justify-center">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
size="x-large"
|
||||||
|
@click="navigateToCreateTemplate"
|
||||||
|
class="mr-4"
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
>
|
||||||
|
创建模板
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
size="x-large"
|
||||||
|
@click="navigateToTemplates"
|
||||||
|
prepend-icon="mdi-folder-multiple"
|
||||||
|
>
|
||||||
|
管理模板
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row justify="center" class="mt-6">
|
||||||
|
<v-col cols="12" md="10">
|
||||||
|
<v-expansion-panels variant="accordion">
|
||||||
|
<v-expansion-panel>
|
||||||
|
<v-expansion-panel-title>
|
||||||
|
<v-icon start>{{ mdiFolderMultiple }}</v-icon>
|
||||||
|
如何使用模板生成器
|
||||||
|
</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<ol class="text-left">
|
||||||
|
<li><strong>创建模板:</strong>点击"创建模板"按钮,定义您需要的文件和目录结构</li>
|
||||||
|
<li><strong>管理模板:</strong>在模板列表中查看、编辑和删除已创建的模板</li>
|
||||||
|
<li><strong>执行模板:</strong>选择一个模板并指定基础目录名,批量生成目录结构</li>
|
||||||
|
</ol>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row class="mt-6">
|
||||||
<v-col
|
<v-col
|
||||||
cols="12"
|
cols="12"
|
||||||
md="5"
|
md="6"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-btn
|
||||||
data-testid="main-logo"
|
icon
|
||||||
alt="logo"
|
color="primary"
|
||||||
draggable="false"
|
@click="handleChangeTheme"
|
||||||
class="ma-auto h-auto w-sm-50 w-md-100"
|
>
|
||||||
src="images/vutron-logo.webp"
|
<v-icon :icon="mdiBrightness6" />
|
||||||
/>
|
<v-tooltip
|
||||||
|
activator="parent"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ t('menu.change-theme') }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="handleOpenDocument"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiFileDocument" start />
|
||||||
|
{{ t('menu.documentation') }}
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="handleOpenGitHub"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiGithub" start />
|
||||||
|
{{ t('menu.github') }}
|
||||||
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col
|
<v-col
|
||||||
cols="12"
|
cols="12"
|
||||||
md="7"
|
md="6"
|
||||||
|
class="d-flex justify-end"
|
||||||
>
|
>
|
||||||
<h2 class="my-4">
|
<v-select
|
||||||
{{ t('desc.welcome-title') }}
|
data-testid="select-language"
|
||||||
</h2>
|
:model-value="locale"
|
||||||
<p>{{ t('desc.welcome-desc') }}</p>
|
density="compact"
|
||||||
<p class="my-4">
|
:label="t('menu.change-language')"
|
||||||
App Version: <strong>{{ appVersion }}</strong>
|
:items="languages"
|
||||||
</p>
|
@update:model-value="handleChangeLanguage"
|
||||||
<p v-if="selectedFile">
|
class="ml-2"
|
||||||
{{
|
/>
|
||||||
t('desc.selected-file', {
|
|
||||||
filePath: selectedFile
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<v-row class="my-4">
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="handleChangeTheme"
|
|
||||||
>
|
|
||||||
<v-icon :icon="mdiBrightness6" />
|
|
||||||
<v-tooltip
|
|
||||||
activator="parent"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
{{ t('menu.change-theme') }}
|
|
||||||
</v-tooltip>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-badge
|
|
||||||
data-testid="counter-badge"
|
|
||||||
color="blue"
|
|
||||||
:content="count"
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
data-testid="btn-counter"
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="handleCountIncrease"
|
|
||||||
>
|
|
||||||
<v-icon :icon="mdiPlusCircle" />
|
|
||||||
<v-tooltip
|
|
||||||
activator="parent"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
{{ t('menu.increase-count') }}
|
|
||||||
</v-tooltip>
|
|
||||||
</v-btn>
|
|
||||||
</v-badge>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="handleOpenDocument"
|
|
||||||
>
|
|
||||||
<v-icon :icon="mdiFileDocument" />
|
|
||||||
<v-tooltip
|
|
||||||
activator="parent"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
{{ t('menu.documentation') }}
|
|
||||||
</v-tooltip>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="handleOpenGitHub"
|
|
||||||
>
|
|
||||||
<v-icon :icon="mdiGithub" />
|
|
||||||
<v-tooltip
|
|
||||||
activator="parent"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
{{ t('menu.github') }}
|
|
||||||
</v-tooltip>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="handleOpenFile"
|
|
||||||
>
|
|
||||||
<v-icon :icon="mdiFolderOpen" />
|
|
||||||
<v-tooltip
|
|
||||||
activator="parent"
|
|
||||||
location="bottom"
|
|
||||||
>
|
|
||||||
{{ t('menu.open-file') }}
|
|
||||||
</v-tooltip>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-select
|
|
||||||
data-testid="select-language"
|
|
||||||
:model-value="locale"
|
|
||||||
density="compact"
|
|
||||||
:label="t('menu.change-language')"
|
|
||||||
:items="languages"
|
|
||||||
@update:model-value="handleChangeLanguage"
|
|
||||||
>
|
|
||||||
{{ t('menu.change-language') }}
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<h2 class="mb-4"> 模板列表 </h2>
|
||||||
|
|
||||||
|
<!-- No templates message -->
|
||||||
|
<v-card
|
||||||
|
v-if="templates.length === 0"
|
||||||
|
class="pa-6 mb-4 text-center"
|
||||||
|
>
|
||||||
|
<v-card-title class="text-h5 mb-4">
|
||||||
|
暂无模板
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p>还没有创建任何模板。创建您的第一个模板以开始批量生成文件和目录。</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="justify-center">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
size="x-large"
|
||||||
|
@click="navigateToCreateTemplate"
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
>
|
||||||
|
创建模板
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<!-- Template list -->
|
||||||
|
<div v-else>
|
||||||
|
<v-row>
|
||||||
|
<v-col
|
||||||
|
v-for="template in templates"
|
||||||
|
:key="template.id"
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
lg="4"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
class="d-flex flex-column"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<v-card-title>{{ template.name }}</v-card-title>
|
||||||
|
<v-card-subtitle>{{ template.description }}</v-card-subtitle>
|
||||||
|
<v-card-text class="flex-grow-1">
|
||||||
|
<p>文件数量: {{ template.files.length }}</p>
|
||||||
|
<p>目录数量: {{ template.directories.length }}</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
@click="viewTemplate(template)"
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
variant="outlined"
|
||||||
|
@click="executeTemplate(template)"
|
||||||
|
>
|
||||||
|
执行
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Template detail dialog -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showTemplateDetail"
|
||||||
|
max-width="800"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span>模板详情: {{ selectedTemplate?.name }}</span>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
@click="showTemplateDetail = false"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-treeview
|
||||||
|
:items="templateTree"
|
||||||
|
item-title="name"
|
||||||
|
item-key="id"
|
||||||
|
item-children="children"
|
||||||
|
activatable
|
||||||
|
open-all
|
||||||
|
>
|
||||||
|
<template v-slot:prepend="{ item, open }">
|
||||||
|
<v-icon>
|
||||||
|
{{
|
||||||
|
item.type === 'directory'
|
||||||
|
? open
|
||||||
|
? 'mdi-folder-open'
|
||||||
|
: 'mdi-folder'
|
||||||
|
: 'mdi-file'
|
||||||
|
}}
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-treeview>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Execute template dialog -->
|
||||||
|
<v-dialog
|
||||||
|
v-model="showExecuteDialog"
|
||||||
|
max-width="600"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>执行模板</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form v-model="executeFormValid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="executePath"
|
||||||
|
label="选择目录"
|
||||||
|
readonly
|
||||||
|
@click="selectDirectory"
|
||||||
|
prepend-inner-icon="mdi-folder"
|
||||||
|
:rules="[(v) => !!v || '请选择目录']"
|
||||||
|
/>
|
||||||
|
<v-textarea
|
||||||
|
v-model="baseDirNames"
|
||||||
|
label="基础目录名 (每行一个,支持批量)"
|
||||||
|
placeholder="输入基础目录名,每行一个"
|
||||||
|
:rules="[(v) => !!v || '请输入基础目录名']"
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="showExecuteDialog = false"> 取消 </v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
@click="confirmExecuteTemplate"
|
||||||
|
:disabled="!executeFormValid"
|
||||||
|
>
|
||||||
|
确认执行
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Floating Action Button for creating templates -->
|
||||||
|
<v-btn
|
||||||
|
v-if="templates.length > 0"
|
||||||
|
color="primary"
|
||||||
|
size="x-large"
|
||||||
|
icon="mdi-plus"
|
||||||
|
fixed
|
||||||
|
location="bottom end"
|
||||||
|
class="mb-8 mr-8"
|
||||||
|
style="z-index: 1000;"
|
||||||
|
@click="navigateToCreateTemplate"
|
||||||
|
>
|
||||||
|
<v-tooltip
|
||||||
|
activator="parent"
|
||||||
|
location="top"
|
||||||
|
>
|
||||||
|
创建模板
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useTemplateStore } from '@/renderer/store/template'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const templateStore = useTemplateStore()
|
||||||
|
|
||||||
|
// Initialize templates
|
||||||
|
onMounted(async () => {
|
||||||
|
await templateStore.loadTemplates()
|
||||||
|
})
|
||||||
|
|
||||||
|
const templates = computed(() => templateStore.templates)
|
||||||
|
|
||||||
|
// Template detail view
|
||||||
|
const showTemplateDetail = ref(false)
|
||||||
|
const selectedTemplate = ref(null as any)
|
||||||
|
|
||||||
|
// Execute template
|
||||||
|
const showExecuteDialog = ref(false)
|
||||||
|
const executePath = ref('')
|
||||||
|
const baseDirNames = ref('')
|
||||||
|
const executeFormValid = ref(false)
|
||||||
|
|
||||||
|
const templateTree = computed(() => {
|
||||||
|
if (!selectedTemplate.value) return []
|
||||||
|
|
||||||
|
const items = []
|
||||||
|
|
||||||
|
// Add directories
|
||||||
|
selectedTemplate.value.directories.forEach((dir: any) => {
|
||||||
|
items.push({
|
||||||
|
id: `dir-${dir.path}`,
|
||||||
|
name: dir.path,
|
||||||
|
type: 'directory',
|
||||||
|
children: []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add files
|
||||||
|
selectedTemplate.value.files.forEach((file: any) => {
|
||||||
|
items.push({
|
||||||
|
id: `file-${file.path}`,
|
||||||
|
name: file.path,
|
||||||
|
type: 'file'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
const navigateToCreateTemplate = () => {
|
||||||
|
router.push('/create-template')
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewTemplate = (template: any) => {
|
||||||
|
selectedTemplate.value = template
|
||||||
|
showTemplateDetail.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeTemplate = (template: any) => {
|
||||||
|
selectedTemplate.value = template
|
||||||
|
executePath.value = ''
|
||||||
|
baseDirNames.value = ''
|
||||||
|
showExecuteDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDirectory = async () => {
|
||||||
|
const result = await window.mainApi.invoke('msgSelectDirectory')
|
||||||
|
if (!result.canceled) {
|
||||||
|
executePath.value = result.filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmExecuteTemplate = () => {
|
||||||
|
if (!executePath.value || !baseDirNames.value.trim()) return
|
||||||
|
|
||||||
|
const dirNames = baseDirNames.value.split('\n').filter((name) => name.trim())
|
||||||
|
|
||||||
|
// Call main process to execute template
|
||||||
|
window.mainApi
|
||||||
|
.invoke(
|
||||||
|
'msgExecuteTemplate',
|
||||||
|
selectedTemplate.value.id,
|
||||||
|
executePath.value,
|
||||||
|
dirNames
|
||||||
|
)
|
||||||
|
.then((result: any) => {
|
||||||
|
if (result.success) {
|
||||||
|
alert('模板执行成功!')
|
||||||
|
showExecuteDialog.value = false
|
||||||
|
} else {
|
||||||
|
alert(`模板执行失败: ${result.error}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
alert(`模板执行出错: ${error.message}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
import ErrorScreen from '@/renderer/screens/ErrorScreen.vue'
|
import ErrorScreen from '@/renderer/screens/ErrorScreen.vue'
|
||||||
import MainScreen from '@/renderer/screens/MainScreen.vue'
|
import MainScreen from '@/renderer/screens/MainScreen.vue'
|
||||||
import SecondScreen from '@/renderer/screens/SecondScreen.vue'
|
import SecondScreen from '@/renderer/screens/SecondScreen.vue'
|
||||||
|
import TemplateListScreen from '@/renderer/screens/TemplateListScreen.vue'
|
||||||
|
import CreateTemplateScreen from '@/renderer/screens/CreateTemplateScreen.vue'
|
||||||
|
|
||||||
export { ErrorScreen, MainScreen, SecondScreen }
|
export {
|
||||||
|
ErrorScreen,
|
||||||
|
MainScreen,
|
||||||
|
SecondScreen,
|
||||||
|
TemplateListScreen,
|
||||||
|
CreateTemplateScreen
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface TemplateFile {
|
||||||
|
path: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateDirectory {
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Template {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
files: TemplateFile[]
|
||||||
|
directories: TemplateDirectory[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTemplateStore = defineStore('template', () => {
|
||||||
|
const templates = ref<Template[]>([])
|
||||||
|
const dataFile = 'templates.json'
|
||||||
|
|
||||||
|
// Load templates from local storage or initialize
|
||||||
|
async function loadTemplates() {
|
||||||
|
try {
|
||||||
|
const stored = await window.mainApi.invoke('msgLoadData', dataFile)
|
||||||
|
if (stored) {
|
||||||
|
templates.value = JSON.parse(stored as string) || []
|
||||||
|
} else {
|
||||||
|
// Initialize with sample data if no stored data
|
||||||
|
templates.value = []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load templates:', error)
|
||||||
|
templates.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save templates to local storage
|
||||||
|
function saveTemplates() {
|
||||||
|
try {
|
||||||
|
window.mainApi.invoke(
|
||||||
|
'msgSaveData',
|
||||||
|
dataFile,
|
||||||
|
JSON.stringify(templates.value)
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save templates:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all templates
|
||||||
|
function getTemplates() {
|
||||||
|
return templates.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get template by ID
|
||||||
|
function getTemplateById(id: string) {
|
||||||
|
return templates.value.find((template) => template.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new template
|
||||||
|
function addTemplate(template: Omit<Template, 'id'>) {
|
||||||
|
const newTemplate: Template = {
|
||||||
|
id: `template_${Date.now()}`,
|
||||||
|
...template
|
||||||
|
}
|
||||||
|
templates.value.push(newTemplate)
|
||||||
|
saveTemplates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing template
|
||||||
|
function updateTemplate(updatedTemplate: Template) {
|
||||||
|
const index = templates.value.findIndex((t) => t.id === updatedTemplate.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
templates.value[index] = updatedTemplate
|
||||||
|
saveTemplates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete template
|
||||||
|
function deleteTemplate(id: string) {
|
||||||
|
templates.value = templates.value.filter((template) => template.id !== id)
|
||||||
|
saveTemplates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get template names for dropdowns
|
||||||
|
function getTemplateNames() {
|
||||||
|
return templates.value.map((template) => ({
|
||||||
|
text: template.name,
|
||||||
|
value: template.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
templates,
|
||||||
|
loadTemplates,
|
||||||
|
getTemplates,
|
||||||
|
getTemplateById,
|
||||||
|
addTemplate,
|
||||||
|
updateTemplate,
|
||||||
|
deleteTemplate,
|
||||||
|
getTemplateNames
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { test, expect, beforeAll, afterAll } from '../fixtures.mjs'
|
||||||
|
|
||||||
|
test.beforeAll(beforeAll)
|
||||||
|
test.afterAll(afterAll)
|
||||||
|
|
||||||
|
// @ts-expect-error: `util` is not using types in playwright
|
||||||
|
test('Template list page navigation and basic functionality', async ({
|
||||||
|
page,
|
||||||
|
util
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
// Navigate to templates page
|
||||||
|
await page.goto('/')
|
||||||
|
await page.waitForURL('**/#/templates')
|
||||||
|
|
||||||
|
// Check if we're on the templates page
|
||||||
|
await expect(page.getByText('模板列表')).toBeVisible()
|
||||||
|
|
||||||
|
// Check if "No templates" message is visible initially
|
||||||
|
await expect(page.getByText('暂无模板,请先创建模板。')).toBeVisible()
|
||||||
|
|
||||||
|
// Check if "Create Template" button is visible
|
||||||
|
await expect(page.getByText('创建模板')).toBeVisible()
|
||||||
|
|
||||||
|
await util.captureScreenshot(page, 'template-list-no-templates')
|
||||||
|
} catch (error) {
|
||||||
|
throw await util.onTestError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error: `util` is not using types in playwright
|
||||||
|
test('Create template page navigation and functionality', async ({
|
||||||
|
page,
|
||||||
|
util
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
// Navigate to templates page
|
||||||
|
await page.goto('/')
|
||||||
|
await page.waitForURL('**/#/templates')
|
||||||
|
|
||||||
|
// Click create template button
|
||||||
|
await page.getByText('创建模板').click()
|
||||||
|
await page.waitForURL('**/#/create-template')
|
||||||
|
|
||||||
|
// Check if we're on the create template page
|
||||||
|
await expect(page.getByText('创建模板')).toBeVisible()
|
||||||
|
|
||||||
|
// Check if form elements are visible
|
||||||
|
await expect(page.getByLabel('模板名称 *')).toBeVisible()
|
||||||
|
await expect(page.getByLabel('模板描述')).toBeVisible()
|
||||||
|
|
||||||
|
await util.captureScreenshot(page, 'create-template-page')
|
||||||
|
} catch (error) {
|
||||||
|
throw await util.onTestError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error: `util` is not using types in playwright
|
||||||
|
test('Add files and directories to template', async ({ page, util }) => {
|
||||||
|
try {
|
||||||
|
// Navigate to create template page
|
||||||
|
await page.goto('/#/create-template')
|
||||||
|
|
||||||
|
// Fill in template name and description
|
||||||
|
await page.getByLabel('模板名称 *').fill('Test Template')
|
||||||
|
await page.getByLabel('模板描述').fill('This is a test template')
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
await page.getByText('添加文件').click()
|
||||||
|
|
||||||
|
// Wait for dialog to appear and fill in file details
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-path-input"]')
|
||||||
|
.fill('src/index.js')
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-content-input"]')
|
||||||
|
.fill('console.log("Hello World");')
|
||||||
|
await page.getByText('添加').click()
|
||||||
|
|
||||||
|
// Add a directory
|
||||||
|
await page.getByText('添加目录').click()
|
||||||
|
|
||||||
|
// Wait for dialog to appear and fill in directory details
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-dir-path-input"]')
|
||||||
|
.fill('src/components')
|
||||||
|
await page.getByText('添加').click()
|
||||||
|
|
||||||
|
// Verify items were added to the tables
|
||||||
|
await expect(page.getByText('src/index.js')).toBeVisible()
|
||||||
|
await expect(page.getByText('src/components')).toBeVisible()
|
||||||
|
|
||||||
|
await util.captureScreenshot(page, 'template-with-content')
|
||||||
|
} catch (error) {
|
||||||
|
throw await util.onTestError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error: `util` is not using types in playwright
|
||||||
|
test('Template creation and listing', async ({ page, util }) => {
|
||||||
|
try {
|
||||||
|
// Navigate to create template page
|
||||||
|
await page.goto('/#/create-template')
|
||||||
|
|
||||||
|
// Fill in template name and description
|
||||||
|
await page.getByLabel('模板名称 *').fill('Test Template')
|
||||||
|
await page.getByLabel('模板描述').fill('This is a test template')
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
await page.getByText('添加文件').click()
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-path-input"]')
|
||||||
|
.fill('src/index.js')
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-content-input"]')
|
||||||
|
.fill('console.log("Hello World");')
|
||||||
|
await page.getByText('添加').click()
|
||||||
|
|
||||||
|
// Save template
|
||||||
|
await page.getByText('保存模板').click()
|
||||||
|
|
||||||
|
// Wait for redirect to templates page
|
||||||
|
await page.waitForURL('**/#/templates')
|
||||||
|
|
||||||
|
// Check if the created template is listed
|
||||||
|
await expect(page.getByText('Test Template')).toBeVisible()
|
||||||
|
await expect(page.getByText('This is a test template')).toBeVisible()
|
||||||
|
|
||||||
|
await util.captureScreenshot(page, 'template-list-with-template')
|
||||||
|
} catch (error) {
|
||||||
|
throw await util.onTestError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error: `util` is not using types in playwright
|
||||||
|
test('View template details', async ({ page, util }) => {
|
||||||
|
try {
|
||||||
|
// Navigate to templates page
|
||||||
|
await page.goto('/#/templates')
|
||||||
|
|
||||||
|
// Wait for template to appear and click view button
|
||||||
|
// First we need to create a template for this test
|
||||||
|
if (await page.getByText('暂无模板,请先创建模板。').isVisible()) {
|
||||||
|
await page.getByText('创建模板').click()
|
||||||
|
|
||||||
|
// Create a test template
|
||||||
|
await page.getByLabel('模板名称 *').fill('View Test Template')
|
||||||
|
await page.getByLabel('模板描述').fill('Template for view test')
|
||||||
|
|
||||||
|
await page.getByText('添加文件').click()
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-path-input"]')
|
||||||
|
.fill('src/test.js')
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="add-file-content-input"]')
|
||||||
|
.fill('console.log("Test");')
|
||||||
|
await page.getByText('添加').click()
|
||||||
|
|
||||||
|
await page.getByText('保存模板').click()
|
||||||
|
await page.waitForURL('**/#/templates')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for template to appear and click view button
|
||||||
|
await page.getByRole('button', { name: '查看' }).click()
|
||||||
|
|
||||||
|
// Check if template detail dialog appears
|
||||||
|
await expect(page.getByText('模板详情')).toBeVisible()
|
||||||
|
await expect(page.getByText('src/test.js')).toBeVisible()
|
||||||
|
|
||||||
|
await util.captureScreenshot(page, 'template-detail-view')
|
||||||
|
} catch (error) {
|
||||||
|
throw await util.onTestError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
+1
-1
@@ -105,7 +105,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
autoImport: true
|
autoImport: true
|
||||||
}),
|
}),
|
||||||
// Docs: https://github.com/gxmari007/vite-plugin-eslint
|
// Docs: https://github.com/gxmari007/vite-plugin-eslint
|
||||||
EslintPlugin(),
|
// EslintPlugin(),
|
||||||
// Docs: https://github.com/electron-vite/vite-plugin-electron
|
// Docs: https://github.com/electron-vite/vite-plugin-electron
|
||||||
ElectronPlugin(electronPluginConfigs),
|
ElectronPlugin(electronPluginConfigs),
|
||||||
RendererPlugin()
|
RendererPlugin()
|
||||||
|
|||||||
Reference in new issue
Block a user