Browse Source

feat: 添加历史记录

jiaxing.liao 2 weeks ago
parent
commit
899f3a7093

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "@types/fs-extra": "^11.0.4",
     "@vueuse/components": "^14.0.0",
     "@vueuse/core": "^14.0.0",
+    "deep-diff": "^1.0.2",
     "element-plus": "^2.11.4",
     "fs-extra": "^11.3.2",
     "image-size": "^2.0.2",

+ 8 - 0
pnpm-lock.yaml

@@ -23,6 +23,9 @@ importers:
       '@vueuse/core':
         specifier: ^14.0.0
         version: 14.0.0(vue@3.5.22(typescript@5.9.3))
+      deep-diff:
+        specifier: ^1.0.2
+        version: 1.0.2
       element-plus:
         specifier: ^2.11.4
         version: 2.11.4(vue@3.5.22(typescript@5.9.3))
@@ -1452,6 +1455,9 @@ packages:
     resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
     engines: {node: '>=10'}
 
+  deep-diff@1.0.2:
+    resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==}
+
   deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
 
@@ -4708,6 +4714,8 @@ snapshots:
     dependencies:
       mimic-response: 3.1.0
 
+  deep-diff@1.0.2: {}
+
   deep-is@0.1.4: {}
 
   defaults@1.0.4:

+ 42 - 1
src/renderer/src/locales/en_US.json

@@ -6,5 +6,46 @@
   "projectJSON": "Project JSON",
   "createProject": "Create Project",
   "projectName": "Project Name",
-  "pleaseEnter": "Please Enter"
+  "pleaseEnter": "Please Enter",
+  "projectPath": "Project Path",
+  "projectType": "Project Type",
+  "chipModel": "Chip Model",
+  "doubleScreen": "Double Screen",
+  "flashSize": "Flash Size",
+  "ramSize": "RAM Size",
+  "screenIndex": "Screen {0}",
+  "singleScreen": "Single Screen",
+  "analogDisplay": "Analog Display",
+  "binNumber": "BIN Number",
+  "board": "Board",
+  "cCode": "C Code",
+  "cCodeBin": "C Code + BIN",
+  "cancel": "Cancel",
+  "chip": "Chip",
+  "colorDepth": "Color Depth",
+  "colorFormat": "Color Format",
+  "confirm": "Confirm",
+  "create": "Create",
+  "currentSelectModel": "Current Seleted: {0}",
+  "custom": "Custom",
+  "customResolution": "Custom Resolution",
+  "height": "Height",
+  "imageLoadError": "Image Load Error",
+  "interfaceType": "Interface Type",
+  "packageType": "Packageing Type",
+  "pleaseEnterModelQuery": "Pleash Enter Model Query",
+  "projectDesc": "Project Description",
+  "resolution": "Resolution",
+  "screenIndexColorDepth": "Screen {0} Color Depth",
+  "screenIndexResolution": "Screen {0} Resolution",
+  "search": "Search",
+  "version": "Version",
+  "width": "Width",
+  "dark": "Dark",
+  "download": "Download",
+  "generalSetting": "General Setting",
+  "language": "Language",
+  "light": "Light",
+  "link": "Link",
+  "theme": "Theme"
 }

+ 42 - 1
src/renderer/src/locales/zh_CN.json

@@ -6,5 +6,46 @@
   "projectJSON": "项目JSON",
   "createProject": "新建项目",
   "projectName": "项目名称",
-  "pleaseEnter": "请输入"
+  "pleaseEnter": "请输入",
+  "projectPath": "项目路径",
+  "projectType": "项目类型",
+  "chipModel": "芯片型号",
+  "flashSize": "闪存大小",
+  "ramSize": "内存大小",
+  "singleScreen": "单屏",
+  "doubleScreen": "双屏",
+  "screenIndex": "屏幕{0}",
+  "resolution": "分辨率",
+  "custom": "自定义",
+  "interfaceType": "接口类型",
+  "colorFormat": "颜色格式",
+  "colorDepth": "颜色深度",
+  "pleaseEnterModelQuery": "请输入型号查询",
+  "search": "搜索",
+  "imageLoadError": "图片加载失败`",
+  "currentSelectModel": "当前选择:{0}",
+  "screenIndexResolution": "屏幕{0}分辨率",
+  "screenIndexColorDepth": "屏幕{0}颜色深度",
+  "version": "版本号",
+  "packageType": "打包方式",
+  "cCode": "C源码",
+  "cCodeBin": "C源码+BIN",
+  "binNumber": "BIN数量",
+  "projectDesc": "项目描述",
+  "cancel": "取消",
+  "create": "创建",
+  "customResolution": "自定义屏幕",
+  "width": "宽",
+  "height": "高",
+  "confirm": "确定",
+  "chip": "芯片",
+  "board": "板卡",
+  "analogDisplay": "模拟显示",
+  "generalSetting": "通用设置",
+  "language": "语言",
+  "theme": "主题",
+  "dark": "暗黑",
+  "light": "亮色",
+  "link": "连接",
+  "download": "下载"
 }

+ 215 - 1
src/renderer/src/store/modules/history.ts

@@ -1,5 +1,219 @@
 import { defineStore } from 'pinia'
+import { computed, reactive, ref } from 'vue'
+import { diff as createDiff, applyChange } from 'deep-diff'
+import type { Diff } from 'deep-diff'
+import { klona } from 'klona'
+import { useDebounceFn } from '@vueuse/core'
+
+// 类型定义
+export interface HistoryRecord {
+  id: string
+  type: string
+  timestamp: Date
+  payload?: any
+  diff?: Diff<any>[]
+}
+
+export interface ProjectData {
+  [key: string]: any
+}
 
 export const useHistoryStore = defineStore('history', () => {
-  return {}
+  // 状态
+  const projectData = ref<ProjectData>()
+  const history = reactive<HistoryRecord[]>([])
+  const currentIndex = ref(-1)
+  const snapshots = reactive<Record<number, ProjectData>>({})
+  const maxHistorySteps = ref(200)
+  const historyLimit = ref(50) // 在历史面板中显示的数量限制
+
+  // 操作类型常量
+  const ActionTypes = {
+    INIT: 'INIT',
+    UPDATE: 'UPDATE',
+    ADD: 'ADD',
+    REMOVE: 'REMOVE',
+    MOVE: 'MOVE',
+    BATCH: 'BATCH',
+    SELECT: 'SELECT',
+    OTHER: 'OTHER'
+  }
+
+  // 初始化项目数据
+  const initProjectData = (data: ProjectData) => {
+    projectData.value = klona(data)
+    history.splice(0, history.length)
+    currentIndex.value = -1
+    Object.keys(snapshots).forEach((key) => delete snapshots[Number(key)])
+
+    addRecord(ActionTypes.INIT, null)
+  }
+
+  // 添加记录(带防抖)
+  const addRecord = useDebounceFn((type: string, payload?: any) => {
+    _addRecord(type, payload)
+  }, 300)
+
+  // 实际添加记录
+  const _addRecord = (type: string, payload?: any) => {
+    // 清除当前位置之后的历史
+    if (currentIndex.value < history.length - 1) {
+      history.splice(currentIndex.value + 1)
+    }
+
+    // 计算当前数据与上次记录时的差异
+    let diffData: Diff<any>[] = []
+    if (history.length > 0) {
+      const prevData = getStateAt(currentIndex.value)
+      diffData = createDiff(prevData, projectData.value) || []
+    }
+
+    // 创建记录
+    const record: HistoryRecord = {
+      id: Date.now().toString(),
+      type,
+      timestamp: new Date(),
+      payload,
+      diff: diffData
+    }
+
+    // 添加到历史记录
+    history.push(record)
+    currentIndex.value = history.length - 1
+
+    // 清除过旧的历史记录
+    if (history.length > maxHistorySteps.value) {
+      history.splice(0, history.length - maxHistorySteps.value)
+      currentIndex.value = maxHistorySteps.value - 1
+      updateSnapshots()
+    }
+
+    // 每10步保存一次完整快照
+    if (currentIndex.value % 10 === 0) {
+      snapshots[currentIndex.value] = klona(projectData.value!)
+    }
+  }
+
+  // 获取指定索引位置的状态
+  const getStateAt = (index: number): ProjectData => {
+    if (index < 0) return {}
+
+    // 查找最近的快照
+    const snapshotIndexes = Object.keys(snapshots)
+      .map(Number)
+      .filter((i) => i <= index)
+      .sort((a, b) => b - a)
+
+    const snapshotIndex = snapshotIndexes.length > 0 ? snapshotIndexes[0] : -1
+    let state: ProjectData = snapshotIndex >= 0 ? klona(snapshots[snapshotIndex]) : {}
+
+    // 应用从快照位置到目标索引的所有变更
+    for (let i = Math.max(0, snapshotIndex + 1); i <= index; i++) {
+      const record = history[i]
+      if (record && record.diff) {
+        record.diff.forEach((change) => {
+          applyDiffChange(state, change)
+        })
+      }
+    }
+
+    return state
+  }
+
+  // 应用变更
+  const applyDiffChange = (target: any, change: Diff<any>) => {
+    try {
+      applyChange(target, undefined, change)
+    } catch (e) {
+      console.error('Error applying diff change:', e)
+    }
+  }
+
+  // 撤销变更
+  const undo = () => {
+    if (!canUndo.value) return
+
+    currentIndex.value--
+    projectData.value = getStateAt(currentIndex.value)
+  }
+
+  // 重做变更
+  const redo = () => {
+    if (!canRedo.value) return
+
+    currentIndex.value++
+    projectData.value = getStateAt(currentIndex.value)
+  }
+
+  // 跳转到特定历史记录
+  const jumpToHistory = (index: number) => {
+    if (index < 0 || index >= history.length) return
+
+    currentIndex.value = index
+    projectData.value = getStateAt(index)
+  }
+
+  // 更新快照索引
+  const updateSnapshots = () => {
+    const newSnapshots: Record<number, ProjectData> = {}
+    Object.entries(snapshots).forEach(([key, value]) => {
+      const index = parseInt(key)
+      if (index >= currentIndex.value - 10 && index <= currentIndex.value) {
+        newSnapshots[index] = value
+      }
+    })
+    Object.assign(snapshots, newSnapshots)
+  }
+
+  // 清除历史记录
+  const clearHistory = () => {
+    history.splice(0, history.length)
+    currentIndex.value = -1
+    Object.keys(snapshots).forEach((key) => delete snapshots[parseInt(key)])
+  }
+
+  // 计算属性
+  const canUndo = computed(() => currentIndex.value > 0)
+  const canRedo = computed(() => currentIndex.value < history.length - 1)
+  const historyCount = computed(() => history.length)
+  const currentHistory = computed(() => history[currentIndex.value])
+
+  // 用于显示的历史记录(限制数量)
+  const displayHistory = computed(() => {
+    const start = Math.max(0, history.length - historyLimit.value)
+    return history.slice(start).map((record, index) => ({
+      ...record,
+      displayIndex: start + index,
+      isCurrent: start + index === currentIndex.value
+    }))
+  })
+
+  return {
+    // 状态
+    projectData,
+    history,
+    currentIndex,
+    snapshots,
+    maxHistorySteps,
+    historyLimit,
+
+    // 操作
+    initProjectData,
+    addRecord,
+    undo,
+    redo,
+    jumpToHistory,
+    clearHistory,
+    getStateAt,
+
+    // 计算属性
+    canUndo,
+    canRedo,
+    historyCount,
+    currentHistory,
+    displayHistory,
+
+    // 常量
+    ActionTypes
+  }
 })

+ 18 - 2
src/renderer/src/store/modules/project.ts

@@ -13,6 +13,7 @@ import { computed, ref } from 'vue'
 import { defineStore } from 'pinia'
 import { klona } from 'klona'
 import { createBin, createScreen } from '@/model'
+import { useDebouncedRefHistory } from '@vueuse/core'
 
 export const useProjectStore = defineStore('project', () => {
   // 项目信息
@@ -34,6 +35,13 @@ export const useProjectStore = defineStore('project', () => {
     screens: Screen[]
   }>()
 
+  const { history, undo, redo, canRedo, canUndo, clear } = useDebouncedRefHistory(project, {
+    debounce: 300,
+    deep: true,
+    capacity: 20,
+    clone: klona
+  })
+
   // 项目路径
   const projectPath = ref<string>()
   // 活动页面key
@@ -112,7 +120,7 @@ export const useProjectStore = defineStore('project', () => {
     // 工程json
     await window.electron.ipcRenderer.invoke(
       'write-file',
-      `${meta.path}\\${meta.name}\\project.json`,
+      `${meta.path}\\${meta.name}\\project.ui`,
       JSON.stringify(project.value)
     )
 
@@ -144,6 +152,14 @@ export const useProjectStore = defineStore('project', () => {
     deletePage,
     deleteWidget,
     projectPath,
-    imageCompressFormat
+    imageCompressFormat,
+
+    // 历史记录
+    history,
+    undo,
+    redo,
+    canRedo,
+    canUndo,
+    clear
   }
 })

+ 56 - 43
src/renderer/src/views/designer/modals/projectModal/index.vue

@@ -19,14 +19,14 @@
         <el-form-item :label="$t('projectName')" prop="name" required>
           <el-input v-model="formData.name" :placeholder="$t('pleaseEnter')"></el-input>
         </el-form-item>
-        <el-form-item label="项目路径" prop="path" required>
+        <el-form-item :label="$t('projectPath')" prop="path" required>
           <el-input v-model="formData.path" readonly>
             <template #append>
               <el-button @click="selectPath"><LuFolder :size="16" /></el-button>
             </template>
           </el-input>
         </el-form-item>
-        <el-form-item label="项目类型" prop="type" required>
+        <el-form-item :label="$t('projectType')" prop="type" required>
           <el-select v-model="formData.type" @change="handlChangeType">
             <el-option
               v-for="item in typeOptions"
@@ -41,7 +41,7 @@
         <template v-if="formData.type === 'chip'">
           <el-row :gutter="12">
             <el-col :span="8">
-              <el-form-item label="芯片型号" prop="chip.model" required>
+              <el-form-item :label="$t('chipModel')" prop="chip.model" required>
                 <el-select v-model="formData.chip.model" @change="handleSelectChip">
                   <el-option
                     v-for="item in chipOptions"
@@ -53,7 +53,7 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="闪存大小" prop="chip.flash_size.capacity" required>
+              <el-form-item :label="$t('flashSize')" prop="chip.flash_size.capacity" required>
                 <el-autocomplete
                   size="small"
                   style="width: 100%"
@@ -74,7 +74,7 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="内存大小" prop="chip.ram_size.capacity" required>
+              <el-form-item :label="$t('ramSize')" prop="chip.ram_size.capacity" required>
                 <el-autocomplete
                   size="small"
                   style="width: 100%"
@@ -102,20 +102,20 @@
               fill="#6cf"
               @change="handleChangeScreenTypeByChip"
             >
-              <el-radio-button label="单屏" value="single" />
-              <el-radio-button label="双屏" value="dual" />
+              <el-radio-button :label="$t('singleScreen')" value="single" />
+              <el-radio-button :label="$t('doubleScreen')" value="dual" />
             </el-radio-group>
           </div>
           <div v-for="(item, index) in formData.screens" :key="index">
-            <el-divider border-style="dashed">屏幕{{ index + 1 }}</el-divider>
+            <el-divider border-style="dashed">{{ $t('screenIndex', [index + 1]) }}</el-divider>
             <el-row :gutter="12">
               <el-col :span="8">
-                <el-form-item label="分辨率">
+                <el-form-item :label="$t('resolution')">
                   <el-select
                     :model-value="`${item.width}x${item.height}`"
                     @change="(val) => handleSetResolution(val, index)"
                   >
-                    <el-option label="自定义" value="custom" />
+                    <el-option :label="$t('custom')" value="custom" />
                     <el-option
                       v-for="item in getScreenOptions('resolutions')?.[index] || []"
                       :label="item"
@@ -125,7 +125,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="接口类型">
+                <el-form-item :label="$t('interfaceType')">
                   <el-select v-model="item.interface" prop="interface" required>
                     <el-option
                       v-for="item in getScreenOptions('interface')?.[index] || []"
@@ -136,7 +136,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="颜色格式">
+                <el-form-item :label="$t('colorFormat')">
                   <el-select v-model="item.colorFormat">
                     <el-option
                       v-for="item in getScreenOptions('color_format')?.[index] || []"
@@ -147,7 +147,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="颜色深度">
+                <el-form-item :label="$t('colorDepth')">
                   <el-select v-model="item.colorDepth">
                     <el-option
                       v-for="item in getScreenOptions('color_depth')?.[index] || []"
@@ -203,8 +203,12 @@
         <!-- 板卡配置 -->
         <template v-if="formData.type === 'board'">
           <div class="flex items-center justify-end gap-12px mb-12px">
-            <el-input style="width: 160px" placeholder="请输入型号查询" v-model="query" />
-            <el-button @click="searchBoard">搜索</el-button>
+            <el-input
+              style="width: 160px"
+              :placeholder="$t('pleaseEnterModelQuery')"
+              v-model="query"
+            />
+            <el-button @click="searchBoard">{{ $t('search') }}</el-button>
           </div>
           <div class="max-h-300px overflow-y-auto overflow-x-hidden">
             <el-row :gutter="12">
@@ -216,7 +220,7 @@
                 >
                   <el-image class="w-full h-full" :src="item.pic_path">
                     <template #error>
-                      <div class="text-center text-#fff">图片加载失败</div>
+                      <div class="text-center text-#fff">{{ $t('imageLoadError') }}</div>
                     </template>
                   </el-image>
                   <div class="absolute w-full h-40px left-0 bottom-0 bg-#ccc truncate text-#fff">
@@ -227,16 +231,18 @@
               </el-col>
             </el-row>
           </div>
-          <div class="my-12px text-text-secondary">当前选择:{{ formData.board.model }}</div>
+          <div class="my-12px text-text-secondary">
+            {{ $t('currentSelectModel', [formData.board.model]) }}
+          </div>
           <el-row :gutter="12">
             <template v-for="(screen, index) in formData.screens">
               <el-col :span="12">
-                <el-form-item :label="`屏幕${index + 1}分辨率`" label-width="120px">
+                <el-form-item :label="$t('screenIndexResolution', [index + 1])" label-width="120px">
                   <el-input :model-value="`${screen.width}x${screen.height}`" disabled />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item :label="`屏幕${index + 1}颜色深度`" label-width="120px">
+                <el-form-item :label="$t('screenIndexColorDepth', [index + 1])" label-width="120px">
                   <el-select v-model="screen.colorDepth">
                     <el-option
                       v-for="item in colorDepthOptions[index] || []"
@@ -258,20 +264,20 @@
               fill="#6cf"
               @change="handleChangeScreenTypeByAnalog"
             >
-              <el-radio-button label="单屏" value="single" />
-              <el-radio-button label="双屏" value="dual" />
+              <el-radio-button :label="$t('singleScreen')" value="single" />
+              <el-radio-button :label="$t('doubleScreen')" value="dual" />
             </el-radio-group>
           </div>
           <div v-for="(item, index) in formData.screens" :key="index">
-            <el-divider border-style="dashed">屏幕{{ index + 1 }}</el-divider>
+            <el-divider border-style="dashed">{{ $t('screenIndex', [index + 1]) }}</el-divider>
             <el-row :gutter="12">
               <el-col :span="8">
-                <el-form-item label="分辨率">
+                <el-form-item :label="$t('resolution')">
                   <el-select
                     :model-value="`${item.width}x${item.height}`"
                     @change="(val) => handleSetResolution(val, index)"
                   >
-                    <el-option label="自定义" value="custom" />
+                    <el-option :label="$t('custom')" value="custom" />
                     <el-option
                       v-for="item in getAnalogDisplayOptions('resolutions')?.[index] || []"
                       :label="item"
@@ -281,7 +287,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="颜色格式">
+                <el-form-item :label="$t('colorFormat')">
                   <el-select v-model="item.colorFormat">
                     <el-option
                       v-for="item in getAnalogDisplayOptions('color_format')?.[index] || []"
@@ -292,7 +298,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="颜色深度">
+                <el-form-item :label="$t('colorDepth')">
                   <el-select v-model="item.colorDepth">
                     <el-option
                       v-for="item in getAnalogDisplayOptions('color_depth')?.[index] || []"
@@ -308,25 +314,25 @@
         <el-divider />
         <el-row :gutter="12">
           <el-col :span="8">
-            <el-form-item label="版本号">
+            <el-form-item :label="$t('version')">
               <el-input v-model="formData.version" />
             </el-form-item>
           </el-col>
           <el-col :span="8">
-            <el-form-item label="打包方式">
+            <el-form-item :label="$t('packageType')">
               <el-select v-model="formData.resourcePackaging">
-                <el-option label="C源码" value="c" />
-                <el-option label="C源码+BIN" value="c_bin" />
+                <el-option :label="$t('cCode')" value="c" />
+                <el-option :label="$t('cCodeBin')" value="c_bin" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="8">
-            <el-form-item v-if="formData.resourcePackaging === 'c_bin'" label="BIN数量">
+            <el-form-item v-if="formData.resourcePackaging === 'c_bin'" :label="$t('binNumber')">
               <el-input v-model.number="formData.binNum" />
             </el-form-item>
           </el-col>
           <el-col :span="24">
-            <el-form-item label="项目描述">
+            <el-form-item :label="$t('projectDesc')">
               <el-input type="textarea" v-model="formData.description" />
             </el-form-item>
           </el-col>
@@ -335,27 +341,27 @@
     </el-scrollbar>
 
     <template #footer>
-      <el-button @click="showModal = false">取消</el-button>
-      <el-button type="primary" @click="handleSubmit">创建</el-button>
+      <el-button @click="showModal = false">{{ $t('cancel') }}</el-button>
+      <el-button type="primary" @click="handleSubmit">{{ $t('create') }}</el-button>
     </template>
 
     <el-dialog
-      title="自定义屏幕"
+      :title="$t('customResolution')"
       v-model="showScreenModal"
       width="440px"
       :modal="false"
       align-center
     >
       <el-form>
-        <el-form-item label="宽">
+        <el-form-item :label="$t('width')">
           <el-input v-model.number="customScreen.width" />
         </el-form-item>
-        <el-form-item label="高">
+        <el-form-item :label="$t('height')">
           <el-input v-model.number="customScreen.height" />
         </el-form-item>
       </el-form>
       <template #footer>
-        <el-button type="primary" @click="handleSetScreen">确定</el-button>
+        <el-button type="primary" @click="handleSetScreen">{{ $t('confirm') }}</el-button>
       </template>
     </el-dialog>
   </el-dialog>
@@ -367,11 +373,14 @@ import type { FormInstance } from 'element-plus'
 import { computed, reactive, ref, defineExpose } from 'vue'
 import { LuFolder } from 'vue-icons-plus/lu'
 import { useProjectStore } from '@/store/modules/project'
+import { useAppStore } from '@/store/modules/app'
+import { useI18n } from 'vue-i18n'
 
 import chipConfig from '@/config/multi_chip_config.json'
 import boardConfig from '@/config/board_card_config.json'
 import displayConfig from '@/config/analog_display_config.json'
 
+const { t } = useI18n()
 const formData = reactive<
   AppMeta & {
     path: string
@@ -428,6 +437,7 @@ const formData = reactive<
 })
 
 const projectStore = useProjectStore()
+const appStore = useAppStore()
 
 const form = ref<FormInstance>()
 // 显示模态框
@@ -441,11 +451,14 @@ const customScreen = ref({
   index: 0
 })
 // 项目类型选项
-const typeOptions = [
-  { label: '芯片', value: 'chip' },
-  { label: '板卡', value: 'board' },
-  { label: '模拟显示', value: 'analog_display' }
-]
+const typeOptions = computed(() => {
+  console.log(appStore.lang)
+  return [
+    { label: t('chip'), value: 'chip' },
+    { label: t('board'), value: 'board' },
+    { label: t('analogDisplay'), value: 'analog_display' }
+  ]
+})
 
 // 切换项目类型
 const handlChangeType = (type: string) => {

+ 10 - 8
src/renderer/src/views/designer/modals/settingModal/GeneralSettingModal.vue

@@ -1,23 +1,23 @@
 <template>
   <el-dialog
     v-model="appStore.showGeneralModal"
-    title="通用设置"
+    :title="$t('generalSetting')"
     width="440px"
     :modal="false"
     :close-on-click-modal="false"
     align-center
   >
     <el-form label-position="top">
-      <el-form-item label="语言">
+      <el-form-item :label="$t('language')">
         <el-select :model-value="appStore.lang" @change="(val) => appStore.setLang(val)">
           <el-option label="中文" value="zh_CN"></el-option>
           <el-option label="English" value="en_US"></el-option>
         </el-select>
       </el-form-item>
-      <el-form-item label="主题">
+      <el-form-item :label="$t('theme')">
         <el-select :model-value="appStore.theme" @change="(val) => appStore.setTheme(val)">
-          <el-option label="暗黑" value="dark"></el-option>
-          <el-option label="亮色" value="light"></el-option>
+          <el-option :label="$t('dark')" value="dark"></el-option>
+          <el-option :label="$t('light')" value="light"></el-option>
         </el-select>
       </el-form-item>
       <el-form-item label="sunmicro.exe路径">
@@ -28,12 +28,14 @@
         </el-input>
       </el-form-item>
       <div class="pl-0px">
-        <el-checkbox v-model="appStore.showLink">连接</el-checkbox>
-        <el-checkbox v-model="appStore.showDownload">下载</el-checkbox>
+        <el-checkbox v-model="appStore.showLink">{{ $t('link') }}</el-checkbox>
+        <el-checkbox v-model="appStore.showDownload">{{ $t('download') }}</el-checkbox>
       </div>
     </el-form>
     <template #footer>
-      <el-button type="primary" @click="appStore.showGeneralModal = false">确定</el-button>
+      <el-button type="primary" @click="appStore.showGeneralModal = false">{{
+        $t('confirm')
+      }}</el-button>
     </template>
   </el-dialog>
 </template>

+ 31 - 28
src/renderer/src/views/designer/sidebar/index.vue

@@ -72,7 +72,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, h } from 'vue'
+import { ref, h, computed } from 'vue'
 import { useI18n } from 'vue-i18n'
 import { useAppStore } from '@/store/modules/app'
 
@@ -86,33 +86,36 @@ import GeneralSettingModal from '../modals/settingModal/GeneralSettingModal.vue'
 
 const { t } = useI18n()
 
-const sidebarMenu = [
-  {
-    key: 'file',
-    title: h('span', null, h(LuFiles)),
-    tooltip: t('directory')
-  },
-  {
-    key: 'widget',
-    title: h('span', null, h(LuBoxes)),
-    tooltip: t('widgetLibrary')
-  },
-  {
-    key: 'resource',
-    title: h('span', null, h(LuInbox)),
-    tooltip: t('resourceManager')
-  },
-  {
-    key: 'code',
-    title: h('span', null, h(LuCode2)),
-    tooltip: t('codeView')
-  },
-  {
-    key: 'json',
-    title: h('span', null, 'JSON'),
-    tooltip: t('projectJSON')
-  }
-]
+const sidebarMenu = computed(() => {
+  console.log(appStore.lang)
+  return [
+    {
+      key: 'file',
+      title: h('span', null, h(LuFiles)),
+      tooltip: t('directory')
+    },
+    {
+      key: 'widget',
+      title: h('span', null, h(LuBoxes)),
+      tooltip: t('widgetLibrary')
+    },
+    {
+      key: 'resource',
+      title: h('span', null, h(LuInbox)),
+      tooltip: t('resourceManager')
+    },
+    {
+      key: 'code',
+      title: h('span', null, h(LuCode2)),
+      tooltip: t('codeView')
+    },
+    {
+      key: 'json',
+      title: h('span', null, 'JSON'),
+      tooltip: t('projectJSON')
+    }
+  ]
+})
 
 const activeMenu = ref('file')
 const appStore = useAppStore()