feat: 更新设置,添加国际化源语言和Vue格式化器配置;优化创建模板界面,增强用户体验

This commit is contained in:
zhanggy committed 2025-10-27 23:12:56 +08:00
1 parent 5e478329bc
commit 2f85832b89
4 files changed
+299 -85

No files matched your search

+1
View File
@@ -0,0 +1 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
+5 -1
View File
@@ -24,5 +24,9 @@
}, },
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"src/renderer/locales" "src/renderer/locales"
] ],
"i18n-ally.sourceLanguage": "zh-hans",
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
} }
+1
View File
@@ -60,6 +60,7 @@ export default pluginTypeScriptESLint.config(
'vue/v-on-event-hyphenation': 'off', 'vue/v-on-event-hyphenation': 'off',
'vue/no-v-text-v-html-on-component': 'off', 'vue/no-v-text-v-html-on-component': 'off',
'vue/attribute-hyphenation': 'off', 'vue/attribute-hyphenation': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/max-attributes-per-line': [ 'vue/max-attributes-per-line': [
'error', 'error',
{ {
+292 -84
View File
@@ -1,77 +1,164 @@
<template> <template>
<v-container fluid class="pa-0 fill-height"> <v-container
fluid
class="pa-0 fill-height"
>
<v-row class="fill-height"> <v-row class="fill-height">
<!-- Left Panel - Template Info --> <!-- Left Panel - Template Info -->
<v-col cols="12" md="4" class="pa-4 border-e"> <v-col
cols="12"
md="4"
class="pa-4 border-e"
>
<div class="d-flex align-center mb-4"> <div class="d-flex align-center mb-4">
<h2 class="text-h5 mr-2"> <h2 class="text-h5 mr-2">
{{ isEditMode ? '编辑模板' : '创建模板' }} {{ isEditMode ? '编辑模板' : '创建模板' }}
</h2> </h2>
<v-spacer /> <v-spacer />
<v-btn icon variant="text" @click="goBack"> <v-btn
icon
variant="text"
@click="goBack"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
关闭 关闭
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</div> </div>
<v-form v-model="formValid" class="mb-4"> <v-form
<v-text-field v-model="templateName" label="模板名称 *" placeholder="输入模板名称" :rules="[requiredRule]" required v-model="formValid"
clearable density="comfortable" /> class="mb-4"
>
<v-text-field
v-model="templateName"
label="模板名称 *"
placeholder="输入模板名称"
:rules="[requiredRule]"
required
clearable
density="comfortable"
/>
<v-textarea v-model="templateDescription" label="模板描述" placeholder="描述这个模板的用途..." rows="2" auto-grow clearable <v-textarea
density="comfortable" /> v-model="templateDescription"
label="模板描述"
placeholder="描述这个模板的用途..."
rows="2"
auto-grow
clearable
density="comfortable"
/>
</v-form> </v-form>
<v-divider class="my-4" /> <v-divider class="my-4" />
<!-- Template Actions --> <!-- Template Actions -->
<div class="d-flex ga-2"> <div class="d-flex ga-2">
<v-btn color="primary" @click="saveTemplate" :disabled="!formValid || !hasChanges" <v-btn
prepend-icon="mdi-content-save" block> color="primary"
@click="saveTemplate"
:disabled="!formValid || !hasChanges"
prepend-icon="mdi-content-save"
block
>
保存模板 保存模板
</v-btn> </v-btn>
</div> </div>
</v-col> </v-col>
<!-- Right Panel - Tree Structure --> <!-- Right Panel - Tree Structure -->
<v-col cols="12" md="8" class="pa-4"> <v-col
cols="12"
md="8"
class="pa-4"
>
<div class="d-flex align-center mb-4"> <div class="d-flex align-center mb-4">
<h3 class="text-h6 mr-2">模板结构</h3> <h3 class="text-h6 mr-2">模板结构</h3>
<v-spacer /> <v-spacer />
<v-btn icon variant="text" @click="addRootDirectory" size="small"> <v-btn
icon
variant="text"
@click="addRootDirectory"
size="small"
>
<v-icon>mdi-folder-plus</v-icon> <v-icon>mdi-folder-plus</v-icon>
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
添加根目录 添加根目录
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn icon variant="text" @click="addRootFile" size="small"> <v-btn
icon
variant="text"
@click="addRootFile"
size="small"
>
<v-icon>mdi-file-plus</v-icon> <v-icon>mdi-file-plus</v-icon>
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
添加根文件 添加根文件
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn icon variant="text" @click="expandAll" size="small"> <v-btn
icon
variant="text"
@click="expandAll"
size="small"
>
<v-icon>mdi-arrow-expand-down</v-icon> <v-icon>mdi-arrow-expand-down</v-icon>
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
展开全部 展开全部
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn icon variant="text" @click="collapseAll" size="small"> <v-btn
icon
variant="text"
@click="collapseAll"
size="small"
>
<v-icon>mdi-arrow-collapse-up</v-icon> <v-icon>mdi-arrow-collapse-up</v-icon>
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
收起全部 收起全部
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</div> </div>
<v-card variant="outlined" class="pa-2" :height="treeHeight" <v-card
@contextmenu.prevent="showContextMenu($event, null)"> variant="outlined"
<v-treeview v-model:expanded="expandedNodes" :items="treeItems" item-title="name" item-key="id" class="pa-2"
item-children="children" activatable open-on-click :return-object="false"> :height="treeHeight"
<template v-slot:prepend="{ item }"> @contextmenu.prevent="showContextMenu($event, null)"
<v-icon :color="item.type === 'directory' ? 'warning' : 'info'" size="large"> >
<v-treeview
v-model:expanded="expandedNodes"
:items="treeItems"
item-title="name"
item-key="id"
item-children="children"
activatable
open-on-click
:return-object="false"
>
<template #prepend="{ item }">
<v-icon
:color="item.type === 'directory' ? 'warning' : 'info'"
size="large"
>
{{ {{
item.type === 'directory' item.type === 'directory'
? 'mdi-folder-outline' ? 'mdi-folder-outline'
@@ -80,33 +167,65 @@
</v-icon> </v-icon>
</template> </template>
<template v-slot:title="{ item }"> <template #title="{ item }">
<span @contextmenu.prevent="showContextMenu($event, item)" class="cursor-pointer d-flex align-center"> <span
@contextmenu.prevent="showContextMenu($event, item)"
class="cursor-pointer d-flex align-center"
>
{{ item.name }} {{ item.name }}
<v-btn v-if="item.type === 'directory'" icon variant="text" size="small" class="ml-2" <v-btn
@click.stop="addNewItemToCurrentDirectory(item)"> v-if="item.type === 'directory'"
icon
variant="text"
size="small"
class="ml-2"
@click.stop="addNewItemToCurrentDirectory(item)"
>
<v-icon size="small">mdi-plus</v-icon> <v-icon size="small">mdi-plus</v-icon>
<v-tooltip activator="parent" location="top"> <v-tooltip
activator="parent"
location="top"
>
添加到目录 添加到目录
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</span> </span>
</template> </template>
<template v-slot:append="{ item }"> <template #append="{ item }">
<div class="d-flex align-center ga-1"> <div class="d-flex align-center ga-1">
<v-btn v-if="item.type === 'file'" icon variant="text" size="small" @click.stop="editFile(item)"> <v-btn
v-if="item.type === 'file'"
icon
variant="text"
size="small"
@click.stop="editFile(item)"
>
<v-icon size="small"> mdi-pencil </v-icon> <v-icon size="small"> mdi-pencil </v-icon>
<v-tooltip activator="parent" location="top"> <v-tooltip
activator="parent"
location="top"
>
编辑内容 编辑内容
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn icon variant="text" size="small" @click.stop="deleteItem(item)"> <v-btn
<v-icon size="small" color="error"> icon
variant="text"
size="small"
@click.stop="deleteItem(item)"
>
<v-icon
size="small"
color="error"
>
mdi-delete mdi-delete
</v-icon> </v-icon>
<v-tooltip activator="parent" location="top"> <v-tooltip
activator="parent"
location="top"
>
删除 删除
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
@@ -114,18 +233,31 @@
</template> </template>
</v-treeview> </v-treeview>
<div v-if="treeItems.length === 0" class="text-center pa-4"> <div
<v-icon size="48" color="grey-lighten-1">mdi-folder-open-outline</v-icon> v-if="treeItems.length === 0"
class="text-center pa-4"
>
<v-icon
size="48"
color="grey-lighten-1"
>
mdi-folder-open-outline
</v-icon>
<p class="text-body-2 mt-2"> 暂无文件或目录 </p> <p class="text-body-2 mt-2"> 暂无文件或目录 </p>
<p class="text-caption"> 右键点击空白区域添加根目录或文件 </p> <p class="text-caption"> 右键点击空白区域添加根目录或文件 </p>
</div> </div>
</v-card> </v-card>
<!-- Context Menu --> <!-- Context Menu -->
<v-menu v-model="showContextMenuVar" :position-x="contextMenuX" :position-y="contextMenuY" absolute> <v-menu
v-model="showContextMenuVar"
:position-x="contextMenuX"
:position-y="contextMenuY"
absolute
>
<v-list density="compact"> <v-list density="compact">
<v-list-item @click="addDirectory(currentContextItem)"> <v-list-item @click="addDirectory(currentContextItem)">
<template v-slot:prepend> <template #prepend>
<v-icon>mdi-folder-plus</v-icon> <v-icon>mdi-folder-plus</v-icon>
</template> </template>
<v-list-item-title> <v-list-item-title>
@@ -134,7 +266,7 @@
</v-list-item> </v-list-item>
<v-list-item @click="addFile(currentContextItem)"> <v-list-item @click="addFile(currentContextItem)">
<template v-slot:prepend> <template #prepend>
<v-icon>mdi-file-plus</v-icon> <v-icon>mdi-file-plus</v-icon>
</template> </template>
<v-list-item-title> <v-list-item-title>
@@ -147,21 +279,33 @@
</v-row> </v-row>
<!-- Add Directory Dialog --> <!-- Add Directory Dialog -->
<v-dialog v-model="showDirectoryDialog" max-width="500"> <v-dialog
v-model="showDirectoryDialog"
max-width="500"
>
<v-card> <v-card>
<v-card-title>添加目录</v-card-title> <v-card-title>添加目录</v-card-title>
<v-card-text> <v-card-text>
<v-form v-model="directoryFormValid"> <v-form v-model="directoryFormValid">
<v-text-field v-model="newDirectoryPath" label="目录路径 *" placeholder="例如: src/components" <v-text-field
:rules="[requiredRule]" required density="comfortable" /> v-model="newDirectoryPath"
label="目录路径 *"
placeholder="例如: src/components"
:rules="[requiredRule]"
required
density="comfortable"
ref="directoryInputRef"
/>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
<v-btn @click="showDirectoryDialog = false"> <v-btn @click="showDirectoryDialog = false"> 取消 </v-btn>
取消 <v-btn
</v-btn> color="primary"
<v-btn color="primary" @click="confirmAddDirectory" :disabled="!directoryFormValid"> @click="confirmAddDirectory"
:disabled="!directoryFormValid"
>
添加 添加
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -169,21 +313,33 @@
</v-dialog> </v-dialog>
<!-- Add File Dialog --> <!-- Add File Dialog -->
<v-dialog v-model="showFileDialog" max-width="500"> <v-dialog
v-model="showFileDialog"
max-width="500"
>
<v-card> <v-card>
<v-card-title>添加文件</v-card-title> <v-card-title>添加文件</v-card-title>
<v-card-text> <v-card-text>
<v-form v-model="fileFormValid"> <v-form v-model="fileFormValid">
<v-text-field v-model="newFilePath" label="文件路径 *" placeholder="例如: src/index.js" :rules="[requiredRule]" <v-text-field
required density="comfortable" /> v-model="newFilePath"
label="文件路径 *"
placeholder="例如: src/index.js"
:rules="[requiredRule]"
required
density="comfortable"
ref="fileInputRef"
/>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
<v-btn @click="showFileDialog = false"> <v-btn @click="showFileDialog = false"> 取消 </v-btn>
取消 <v-btn
</v-btn> color="primary"
<v-btn color="primary" @click="confirmAddFile" :disabled="!fileFormValid"> @click="confirmAddFile"
:disabled="!fileFormValid"
>
添加并编辑 添加并编辑
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -191,24 +347,42 @@
</v-dialog> </v-dialog>
<!-- File Content Editor Dialog --> <!-- File Content Editor Dialog -->
<v-dialog v-model="showEditorDialog" fullscreen> <v-dialog
v-model="showEditorDialog"
fullscreen
>
<v-card> <v-card>
<v-toolbar dark color="primary"> <v-toolbar
<v-btn icon @click="showEditorDialog = false"> dark
color="primary"
>
<v-btn
icon
@click="showEditorDialog = false"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
<v-toolbar-title> <v-toolbar-title> 编辑文件: {{ editingFile?.path }} </v-toolbar-title>
编辑文件: {{ editingFile?.path }}
</v-toolbar-title>
<v-spacer /> <v-spacer />
<v-btn variant="text" @click="saveFileContent"> <v-btn
variant="text"
@click="saveFileContent"
>
保存 保存
</v-btn> </v-btn>
</v-toolbar> </v-toolbar>
<v-card-text class="pa-0 fill-height"> <v-card-text class="pa-0 fill-height">
<v-textarea v-model="editingFileContent" class="fill-height" style="height: calc(100vh - 120px);" no-resize <v-textarea
spellcheck="false" placeholder="在此输入文件内容..." variant="solo-filled" density="comfortable" /> v-model="editingFileContent"
class="fill-height"
style="height: calc(100vh - 120px)"
no-resize
spellcheck="false"
placeholder="在此输入文件内容..."
variant="solo-filled"
density="comfortable"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-dialog> </v-dialog>
@@ -241,11 +415,15 @@ const showEditorDialog = ref(false)
const directoryFormValid = ref(false) const directoryFormValid = ref(false)
const fileFormValid = ref(false) const fileFormValid = ref(false)
// Input refs for auto-focus
const directoryInputRef = ref<HTMLInputElement | null>(null)
const fileInputRef = ref<HTMLInputElement | null>(null)
// Context menu // Context menu
const showContextMenuVar = ref(false) const showContextMenuVar = ref(false)
const contextMenuX = ref(0) const contextMenuX = ref(0)
const contextMenuY = ref(0) const contextMenuY = ref(0)
const currentContextItem = ref<any>(null) const currentContextItem = ref<TreeItem | null>(null)
// New item state // New item state
const newDirectoryPath = ref('') const newDirectoryPath = ref('')
@@ -275,7 +453,7 @@ const treeItems = computed<TreeItem[]>(() => {
const items: TreeItem[] = [] const items: TreeItem[] = []
// Add directories // Add directories
directories.value.forEach(dir => { directories.value.forEach((dir) => {
items.push({ items.push({
id: `dir-${dir.path}`, id: `dir-${dir.path}`,
name: dir.path, name: dir.path,
@@ -285,7 +463,7 @@ const treeItems = computed<TreeItem[]>(() => {
}) })
// Add files // Add files
files.value.forEach(file => { files.value.forEach((file) => {
items.push({ items.push({
id: `file-${file.path}`, id: `file-${file.path}`,
name: file.path, name: file.path,
@@ -297,12 +475,16 @@ const treeItems = computed<TreeItem[]>(() => {
}) })
// Validation rules // Validation rules
const requiredRule = (v: any) => !!v || '此字段为必填项' const requiredRule = (v: string | null | undefined) => !!v || '此字段为必填项'
// Watch for changes to detect unsaved changes // Watch for changes to detect unsaved changes
watch([templateName, templateDescription, directories, files], () => { watch(
[templateName, templateDescription, directories, files],
() => {
hasChanges.value = true hasChanges.value = true
}, { deep: true }) },
{ deep: true }
)
// Lifecycle // Lifecycle
onMounted(async () => { onMounted(async () => {
@@ -343,7 +525,7 @@ const updateTreeHeight = () => {
treeHeight.value = window.innerHeight - 200 treeHeight.value = window.innerHeight - 200
} }
const showContextMenu = (event: MouseEvent, item: any) => { const showContextMenu = (event: MouseEvent, item: TreeItem | null) => {
event.preventDefault() event.preventDefault()
contextMenuX.value = event.clientX contextMenuX.value = event.clientX
contextMenuY.value = event.clientY contextMenuY.value = event.clientY
@@ -359,37 +541,44 @@ const showContextMenu = (event: MouseEvent, item: any) => {
const addRootDirectory = () => { const addRootDirectory = () => {
newDirectoryPath.value = '' newDirectoryPath.value = ''
showDirectoryDialog.value = true showDirectoryDialog.value = true
autoFocusDirectoryInput()
} }
const addRootFile = () => { const addRootFile = () => {
newFilePath.value = '' newFilePath.value = ''
showFileDialog.value = true showFileDialog.value = true
autoFocusFileInput()
} }
const addDirectory = (parentItem: any = null) => { const addDirectory = (parentItem: TreeItem | null = null) => {
showContextMenuVar.value = false showContextMenuVar.value = false
currentContextItem.value = parentItem currentContextItem.value = parentItem
newDirectoryPath.value = parentItem ? `${parentItem.name}/` : '' newDirectoryPath.value = parentItem ? `${parentItem.name}/` : ''
showDirectoryDialog.value = true showDirectoryDialog.value = true
autoFocusDirectoryInput()
} }
const addFile = (parentItem: any = null) => { const addFile = (parentItem: TreeItem | null = null) => {
showContextMenuVar.value = false showContextMenuVar.value = false
newFilePath.value = parentItem ? `${parentItem.name}/` : '' newFilePath.value = parentItem ? `${parentItem.name}/` : ''
showFileDialog.value = true showFileDialog.value = true
autoFocusFileInput()
} }
const addNewItemToCurrentDirectory = (directoryItem: any) => { const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
currentContextItem.value = directoryItem currentContextItem.value = directoryItem
newDirectoryPath.value = directoryItem ? `${directoryItem.name}/` : '' newDirectoryPath.value = directoryItem ? `${directoryItem.name}/` : ''
showDirectoryDialog.value = true showDirectoryDialog.value = true
autoFocusDirectoryInput()
} }
const confirmAddDirectory = () => { const confirmAddDirectory = () => {
if (!newDirectoryPath.value.trim()) return if (!newDirectoryPath.value.trim()) return
// Check for duplicates // Check for duplicates
const exists = directories.value.some(d => d.path === newDirectoryPath.value) const exists = directories.value.some(
(d) => d.path === newDirectoryPath.value
)
if (exists) { if (exists) {
alert('该目录路径已存在!') alert('该目录路径已存在!')
return return
@@ -408,7 +597,7 @@ const confirmAddFile = () => {
if (!newFilePath.value.trim()) return if (!newFilePath.value.trim()) return
// Check for duplicates // Check for duplicates
const exists = files.value.some(f => f.path === newFilePath.value) const exists = files.value.some((f) => f.path === newFilePath.value)
if (exists) { if (exists) {
alert('该文件路径已存在!') alert('该文件路径已存在!')
return return
@@ -440,15 +629,15 @@ const goBack = () => {
} }
const expandAll = () => { const expandAll = () => {
expandedNodes.value = treeItems.value.map(item => item.id) expandedNodes.value = treeItems.value.map((item) => item.id)
} }
const collapseAll = () => { const collapseAll = () => {
expandedNodes.value = [] expandedNodes.value = []
} }
const editFile = (item: any) => { const editFile = (item: TreeItem) => {
const file = files.value.find(f => `file-${f.path}` === item.id) const file = files.value.find((f) => `file-${f.path}` === item.id)
if (file) { if (file) {
editFileContent(file) editFileContent(file)
} }
@@ -462,7 +651,9 @@ const editFileContent = (file: { path: string; content: string }) => {
const saveFileContent = () => { const saveFileContent = () => {
if (editingFile.value) { if (editingFile.value) {
const index = files.value.findIndex(f => f.path === editingFile.value?.path) const index = files.value.findIndex(
(f) => f.path === editingFile.value?.path
)
if (index !== -1) { if (index !== -1) {
files.value[index].content = editingFileContent.value files.value[index].content = editingFileContent.value
} }
@@ -470,13 +661,30 @@ const saveFileContent = () => {
showEditorDialog.value = false showEditorDialog.value = false
} }
const deleteItem = (item: any) => { const deleteItem = (item: TreeItem) => {
if (!confirm('确定要删除此项吗?')) return if (!confirm('确定要删除此项吗?')) return
if (item.type === 'directory') { if (item.type === 'directory') {
directories.value = directories.value.filter(d => `dir-${d.path}` !== item.id) directories.value = directories.value.filter(
(d) => `dir-${d.path}` !== item.id
)
} else { } else {
files.value = files.value.filter(f => `file-${f.path}` !== item.id) files.value = files.value.filter((f) => `file-${f.path}` !== item.id)
}
}
// Auto focus methods
const autoFocusDirectoryInput = async () => {
await nextTick()
if (directoryInputRef.value) {
(directoryInputRef.value as HTMLInputElement).focus()
}
}
const autoFocusFileInput = async () => {
await nextTick()
if (fileInputRef.value) {
(fileInputRef.value as HTMLInputElement).focus()
} }
} }