Feature(custom): support open and edit config file in software

This commit is contained in:
Kuingsmile
2026-01-25 18:25:52 +08:00
parent 9c78c54800
commit a10e701cc9
12 changed files with 387 additions and 3 deletions

View File

@@ -42,6 +42,24 @@ export default [
shell.openPath(abFilePath)
},
},
{
action: IRPCActionType.READ_FILE_CONTENT,
handler: async (_: IIPCEvent, args: [fileName: string]) => {
const abFilePath = path.join(STORE_PATH, args[0])
if (!fs.existsSync(abFilePath)) {
fs.writeFileSync(abFilePath, '')
}
return fs.readFileSync(abFilePath, 'utf-8')
},
type: IRPCType.INVOKE,
},
{
action: IRPCActionType.WRITE_FILE_CONTENT,
handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => {
const abFilePath = path.join(STORE_PATH, args[0])
fs.writeFileSync(abFilePath, args[1], 'utf-8')
},
},
{
action: IRPCActionType.PICLIST_OPEN_DIRECTORY,
handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => {

View File

@@ -138,4 +138,11 @@ export default [
})
},
},
{
action: IRPCActionType.RELOAD_WINDOW,
handler: async () => {
const window = BrowserWindow.getFocusedWindow()
window?.webContents.reload()
},
},
]

View File

@@ -123,6 +123,9 @@ export const IRPCActionType = {
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
RELOAD_WINDOW: 'RELOAD_WINDOW',
// shortkey setting rpc
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',

View File

@@ -0,0 +1,99 @@
<template>
<div ref="editorRef" class="h-full w-full overflow-hidden rounded-[4px]"></div>
</template>
<script setup>
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
import { javascript } from '@codemirror/lang-javascript'
import { json } from '@codemirror/lang-json'
import { openSearchPanel, search, searchKeymap } from '@codemirror/search'
import { EditorState } from '@codemirror/state'
import { oneDark } from '@codemirror/theme-one-dark'
import { EditorView, keymap, lineNumbers } from '@codemirror/view'
import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
const props = defineProps({
modelValue: { type: String, default: '' },
language: { type: String, default: 'javascript' },
})
const emit = defineEmits(['update:modelValue'])
const editorRef = ref(null)
const view = shallowRef(null)
onMounted(() => {
const startState = EditorState.create({
doc: props.modelValue,
extensions: [
lineNumbers(),
history(),
keymap.of([...defaultKeymap, ...historyKeymap]),
json(),
javascript(),
oneDark,
search({ top: true }),
keymap.of([...searchKeymap]),
EditorView.lineWrapping,
EditorView.updateListener.of(update => {
if (update.docChanged) {
emit('update:modelValue', update.state.doc.toString())
}
}),
],
})
view.value = new EditorView({
state: startState,
parent: editorRef.value,
})
})
watch(
() => props.modelValue,
newVal => {
const currVal = view.value?.state.doc.toString()
if (view.value && newVal !== currVal) {
view.value.dispatch({
changes: { from: 0, to: currVal.length, insert: newVal },
})
}
},
)
onMounted(() => {
openSearchPanel(view.value)
})
onBeforeUnmount(() => {
view.value?.destroy()
})
</script>
<style scoped>
@import 'tailwindcss' reference;
@import '../assets/css/theme.css' reference;
@import '../assets/css/utilities.css' reference;
:deep(.cm-editor) {
@apply h-full;
}
:deep(.cm-scroller) {
@apply font-['Fira_Code','SF_Mono',Monaco,Menlo,'Ubuntu_Mono',monospace];
}
:deep(.cm-search) {
@apply border-b border-b-[#181a1f] bg-[#282c34] p-2! text-[#abb2bf];
}
:deep(.cm-search input) {
@apply mr-2 rounded-md border border-[#181a1f] bg-[#21252b] px-2 py-1 text-white;
}
:deep(.cm-search button) {
@apply cursor-pointer rounded-md border-none bg-[#3e4451] font-medium text-[#ecefe4];
}
:deep(.cm-search button:hover) {
@apply bg-[#4b5263];
}
</style>

View File

@@ -6,8 +6,10 @@
"cancel": "Cancel",
"close": "Close",
"confirm": "Confirm",
"edit": "Edit",
"import": "Import",
"reset": "Reset",
"save": "Save",
"submit": "Submit",
"version": "Version"
},
@@ -718,6 +720,7 @@
"enableServer": "Enable Upload API Service",
"enableWebServer": "Enable Web Server",
"guiLogFile": "GUI Log File",
"invalidJson": "Invalid JSON format",
"logDialogDesc": "View log files and configure log settings",
"logFile": "General Log File",
"logFilePath": "Log File Path",
@@ -739,6 +742,8 @@
"pluginInstallMirror": "Plugin Install Mirror",
"pluginInstallProxy": "Plugin Install Proxy",
"proxyDialogDesc": "Configure network proxy and plugin mirrors",
"saveFileFailed": "failed to save file",
"saveFileSuccess": "file saved successfully",
"serverConfig": "Server Configuration",
"serverDialogDesc": "Configure upload API service connection parameters",
"serverEncryptionKey": "API Data Encryption Key",
@@ -777,6 +782,7 @@
"commonConfig": "Common Configuration",
"configureSync": "Platform Config",
"downloadSettings": "Download Settings",
"editConfigFile": "Edit Configuration File",
"fileManagement": "File Management",
"galleryDB": "Gallery Database Sync",
"gitea": {

View File

@@ -6,8 +6,10 @@
"cancel": "取消",
"close": "关闭",
"confirm": "确认",
"edit": "编辑",
"import": "导入",
"reset": "重置",
"save": "保存",
"submit": "提交",
"version": "版本"
},
@@ -718,6 +720,7 @@
"enableServer": "是否开启上传API服务",
"enableWebServer": "是否开启 Web 服务",
"guiLogFile": "GUI 日志文件",
"invalidJson": "无效的JSON格式",
"logDialogDesc": "查看日志文件和配置日志设置",
"logFile": "常规日志文件",
"logFilePath": "日志文件路径",
@@ -739,6 +742,8 @@
"pluginInstallMirror": "插件安装镜像",
"pluginInstallProxy": "插件安装代理",
"proxyDialogDesc": "配置网络代理和插件安装镜像",
"saveFileFailed": "文件保存失败",
"saveFileSuccess": "文件保存成功",
"serverConfig": "服务器配置",
"serverDialogDesc": "配置上传 API 服务的连接参数",
"serverEncryptionKey": "接口数据加密密钥",
@@ -777,6 +782,7 @@
"commonConfig": "通用配置",
"configureSync": "平台设置",
"downloadSettings": "下载配置",
"editConfigFile": "编辑配置文件",
"fileManagement": "文件管理",
"galleryDB": "相册数据库同步",
"gitea": {

View File

@@ -6,8 +6,10 @@
"cancel": "取消",
"close": "關閉",
"confirm": "確認",
"edit": "編輯",
"import": "匯入",
"reset": "重置",
"save": "保存",
"submit": "提交",
"version": "版本"
},
@@ -718,6 +720,7 @@
"enableServer": "是否開啟上傳API服務",
"enableWebServer": "是否開啟 Web 服務",
"guiLogFile": "GUI 日誌文件",
"invalidJson": "無效的 JSON 格式",
"logDialogDesc": "查看日誌文件和配置日誌設置",
"logFile": "常規日誌文件",
"logFilePath": "日誌文件路徑",
@@ -739,6 +742,8 @@
"pluginInstallMirror": "插件安裝鏡像",
"pluginInstallProxy": "插件安裝代理",
"proxyDialogDesc": "配置網絡代理和插件安裝鏡像",
"saveFileFailed": "文件保存失敗",
"saveFileSuccess": "文件保存成功",
"serverConfig": "伺服器配置",
"serverDialogDesc": "配置上傳 API 服務的連接參數",
"serverEncryptionKey": "接口數據加密密鑰",
@@ -777,6 +782,7 @@
"commonConfig": "通用配置",
"configureSync": "平台配置",
"downloadSettings": "下載配置",
"editConfigFile": "編輯配置文件",
"fileManagement": "文件管理",
"galleryDB": "相冊數據庫同步",
"gitea": {

View File

@@ -274,6 +274,11 @@
:icon="FileText"
@click="openFile('data.json')"
/>
<CustomNavCard
:title="t('pages.settings.sync.editConfigFile')"
:icon="Edit"
@click="editFile('data.json')"
/>
<CustomNavCard
:title="t('pages.settings.sync.openConfigFileDir')"
:icon="FolderOpen"
@@ -1213,6 +1218,14 @@
>
<ImageProcessSetting :config-id="''" :current-picbed-name="''" />
</CustomModal>
<CustomModal v-if="editorVisible" v-model:visible="editorVisible" :title="t('common.edit')">
<Editor v-model="editorContent" language="json" />
<template #footer>
<CustomButton type="secondary" :text="t('common.cancel')" @click="editorVisible = false" />
<CustomButton type="primary" :text="t('common.save')" @click="saveEditorContent" />
</template>
</CustomModal>
</div>
</template>
@@ -1257,6 +1270,7 @@ import MultiSelect from '@/components/common/MultiSelect.vue'
import placeholderTable from '@/components/common/PlaceholderTable.vue'
import SettingCard from '@/components/common/SettingCard.vue'
import SettingSection from '@/components/common/SettingSection.vue'
import Editor from '@/components/Editor.vue'
import ImageProcessSetting from '@/components/ImageProcessSetting.vue'
import useConfirm from '@/hooks/useConfirm'
import { osGlobal, usePicBed } from '@/hooks/useGlobal'
@@ -1296,6 +1310,8 @@ const webServerVisible = ref(false)
const syncVisible = ref(false)
const upDownConfigVisible = ref(false)
const proxyVisible = ref(false)
const editorVisible = ref(false)
const editorContent = ref('// 在这里开始编写代码...\nfunction hello() {\n console.log("Hello Electron!");\n}')
const latestVersion = ref('')
const releaseNotes = ref('')
@@ -1829,10 +1845,53 @@ async function handleChangeSecondPicBed() {
window.electron.sendRPC(IRPCActionType.SHOW_SECOND_UPLOADER_MENU)
}
function openFile(file: string) {
async function saveEditorContent() {
const content = editorContent.value.trim()
await saveFile('data.json', content, 'json')
editorVisible.value = false
}
async function openFile(file: string) {
window.electron.sendRPC(IRPCActionType.PICLIST_OPEN_FILE, file)
}
async function editFile(file: string, mode: 'text' | 'json' = 'text') {
const content = (await window.electron.triggerRPC<string>(IRPCActionType.READ_FILE_CONTENT, file)) || ''
if (mode === 'json') {
try {
editorContent.value = JSON.stringify(JSON.parse(content), null, 2)
} catch (error) {
editorContent.value = content
}
} else {
editorContent.value = content
}
editorVisible.value = true
}
async function saveFile(file: string, content: string, mode: 'text' | 'json' = 'text') {
let dataToSave = content
if (mode === 'json') {
try {
dataToSave = JSON.stringify(JSON.parse(content), null, 2)
} catch (error) {
console.error('Invalid JSON content:', error)
message.error(t('pages.settings.advanced.invalidJson'))
return
}
}
try {
window.electron.sendRPC(IRPCActionType.WRITE_FILE_CONTENT, file, dataToSave)
message.success(t('pages.settings.advanced.saveFileSuccess'))
setTimeout(() => {
window.electron.sendRPC(IRPCActionType.RELOAD_WINDOW)
}, 1000)
} catch (error) {
console.error('Failed to save file:', error)
message.error(t('pages.settings.advanced.saveFileFailed'))
}
}
function openDirectory(directory?: string, inStorePath = true) {
window.electron.sendRPC(IRPCActionType.PICLIST_OPEN_DIRECTORY, directory, inStorePath)
}

View File

@@ -65,6 +65,9 @@ export const IRPCActionType = {
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
RELOAD_WINDOW: 'RELOAD_WINDOW',
// shortkey setting rpc
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',