浏览代码

feat: 添加最近项目功能

jiaxing.liao 1 周之前
父节点
当前提交
786cf16a0e

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
     "deep-diff": "^1.0.2",
     "element-plus": "^2.11.4",
     "fs-extra": "^11.3.2",
+    "idb": "^8.0.3",
     "image-size": "^2.0.2",
     "klona": "^2.0.6",
     "monaco-editor": "^0.54.0",

+ 8 - 0
pnpm-lock.yaml

@@ -32,6 +32,9 @@ importers:
       fs-extra:
         specifier: ^11.3.2
         version: 11.3.2
+      idb:
+        specifier: ^8.0.3
+        version: 8.0.3
       image-size:
         specifier: ^2.0.2
         version: 2.0.2
@@ -1979,6 +1982,9 @@ packages:
     resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
     engines: {node: '>=0.10.0'}
 
+  idb@8.0.3:
+    resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==}
+
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
@@ -5382,6 +5388,8 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  idb@8.0.3: {}
+
   ieee754@1.2.1: {}
 
   ignore@5.3.2: {}

+ 2 - 0
project.json5

@@ -10,6 +10,8 @@
     "version": "1.0.0",
     // 项目描述
     "description": "智能HOME项目",
+    // 源码路径
+    "codePath": "C:\\Users\\Administrator\\Desktop\\project\\code",
     // 项目类型
     "type": "chip", // 'analog_display' | 'chip' | 'board' 1: 模拟显示 2: 芯片 3: 板卡
     // 屏幕类型

+ 1 - 0
src/renderer/components.d.ts

@@ -31,6 +31,7 @@ declare module 'vue' {
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElFlex: typeof import('element-plus/es')['ElFlex']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']

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

@@ -52,5 +52,13 @@
   "nameRequired": "Project Name Is Required",
   "flashIsRequired": "Flash Is Required",
   "ramIsRequired": "RAM Is Required",
-  "chipIsRequired": "Board Is Required"
+  "chipIsRequired": "Board Is Required",
+  "codePath": "Code Path",
+  "codePathRequired": "code path is required",
+  "recentProject": "Recent Project",
+  "nameRepeat": "project name repeat",
+  "selectBoard": "Please Select Board!",
+  "modifyTime": "Modify Time",
+  "noFoundProject": "This Project Not Found!",
+  "readProjectError": "Read Project File Error!"
 }

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

@@ -52,5 +52,13 @@
   "nameRequired": "项目名称不能为空",
   "flashIsRequired": "闪存不能为空",
   "ramIsRequired": "内存不能为空",
-  "chipIsRequired": "芯片不能为空"
+  "chipIsRequired": "芯片不能为空",
+  "codePath": "代码路径",
+  "codePathRequired": "代码路径不能为空",
+  "recentProject": "最近项目",
+  "nameRepeat": "项目名称重复",
+  "selectBoard": "请选择板卡",
+  "modifyTime": "修改时间:",
+  "noFoundProject": "该项目不存在, 无法打开!",
+  "readProjectError": "读取项目失败"
 }

+ 0 - 219
src/renderer/src/store/modules/history.ts

@@ -1,219 +0,0 @@
-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', () => {
-  // 状态
-  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
-  }
-})

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

@@ -14,26 +14,31 @@ import { defineStore } from 'pinia'
 import { klona } from 'klona'
 import { createBin, createScreen } from '@/model'
 import { useDebouncedRefHistory } from '@vueuse/core'
+import { useRecentProject } from './recentProject'
+import { v4 } from 'uuid'
+import dayjs from 'dayjs'
+
+export interface IProject {
+  version: string
+  meta: AppMeta
+  bins: Bin[]
+  resources: {
+    images: ImageResource[]
+    fonts: FontResource[]
+    others: OtherResource[]
+  }
+  widgets: BaseWidget[]
+  variables: Variable[]
+  themes: Theme[]
+  animations: Animation[]
+  languages: Language[]
+  methods: Method[]
+  screens: Screen[]
+}
 
 export const useProjectStore = defineStore('project', () => {
   // 项目信息
-  const project = ref<{
-    version: string
-    meta: AppMeta
-    bins: Bin[]
-    resources: {
-      images: ImageResource[]
-      fonts: FontResource[]
-      others: OtherResource[]
-    }
-    widgets: BaseWidget[]
-    variables: Variable[]
-    themes: Theme[]
-    animations: Animation[]
-    languages: Language[]
-    methods: Method[]
-    screens: Screen[]
-  }>()
+  const project = ref<IProject>()
 
   const { history, undo, redo, canRedo, canUndo, clear } = useDebouncedRefHistory(project, {
     debounce: 300,
@@ -42,6 +47,8 @@ export const useProjectStore = defineStore('project', () => {
     clone: klona
   })
 
+  const recentProjectStore = useRecentProject()
+
   // 项目路径
   const projectPath = ref<string>()
   // 活动页面key
@@ -59,7 +66,8 @@ export const useProjectStore = defineStore('project', () => {
    * 创建应用
    * @param meta 应用元信息
    */
-  const createApp = async (meta: AppMeta & { path: string }) => {
+  const createApp = async (meta: AppMeta) => {
+    meta = klona(meta)
     // 1、应用元信息
     project.value = {
       version: '1.0.0',
@@ -125,6 +133,28 @@ export const useProjectStore = defineStore('project', () => {
     )
 
     imageCompressFormat.value = meta.imageCompress
+    // 清除记录
+    clear()
+    recentProjectStore.addProject({
+      id: v4(),
+      projectName: meta.name,
+      projectPath: `${meta.path}\\${meta.name}`,
+      createTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+      modifyTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
+    })
+  }
+
+  /**
+   * 加载项目
+   * @param newProject 项目字符串
+   */
+  const loadProject = (newProject: IProject) => {
+    project.value = newProject
+    // 初始化处理
+    clear()
+    projectPath.value = `${newProject.meta.path}/${newProject.meta.name}`
+    activePageId.value = newProject.screens[0].pages?.[0]?.id
+    imageCompressFormat.value = newProject.meta.imageCompress
   }
 
   // 删除页面
@@ -153,6 +183,7 @@ export const useProjectStore = defineStore('project', () => {
     deleteWidget,
     projectPath,
     imageCompressFormat,
+    loadProject,
 
     // 历史记录
     history,

+ 68 - 0
src/renderer/src/store/modules/recentProject.ts

@@ -0,0 +1,68 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { openDB } from 'idb'
+
+type ProjectRecord = {
+  id: string
+  projectName: string
+  projectPath: string
+  createTime: string
+  modifyTime: string
+}
+
+export const useRecentProject = defineStore('recentProject', () => {
+  const recentProjects = ref<ProjectRecord[]>()
+  const currentRecord = ref<ProjectRecord>()
+
+  // 获取数据库
+  const getDB = async () => {
+    const db = await openDB('projects', 1, {
+      upgrade(db) {
+        db.createObjectStore('projects', { keyPath: 'id' })
+      }
+    })
+    return db
+  }
+
+  // 获取所有项目
+  const getAllProjects = async () => {
+    const db = await getDB()
+    const projects = await db.getAll('projects')
+    console.log('projects', projects)
+    recentProjects.value = projects
+  }
+
+  // 添加项目
+  const addProject = async (project: ProjectRecord) => {
+    currentRecord.value = project
+    const db = await getDB()
+    const result = await db.add('projects', project)
+    getAllProjects()
+    return result
+  }
+
+  // 删除项目
+  const deleteProject = async (id: string) => {
+    const db = await getDB()
+    const result = await db.delete('projects', id)
+    getAllProjects()
+    return result
+  }
+
+  // 修改项目
+  const updateProject = async (project: ProjectRecord) => {
+    const db = await getDB()
+    const result = await db.put('projects', project)
+    return result
+  }
+
+  getAllProjects()
+
+  return {
+    recentProjects,
+    currentRecord,
+    addProject,
+    deleteProject,
+    updateProject
+  }
+})

+ 33 - 0
src/renderer/src/style.less

@@ -1,3 +1,36 @@
 .el-dialog {
   border: solid 1px #666666;
+}
+
+/* 平滑过渡效果 */
+html,
+body {
+  transition:
+    background-color 0.3s ease,
+    color 0.3s ease,
+    border-color 0.3s ease;
+}
+
+
+.el-tabs {
+  --el-tabs-header-height: 28px !important;
+  .el-tabs__active-bar {
+    background-color: var(--text-active);
+  }
+  .el-tabs__item.is-active, .el-tabs__item:hover {
+    color: var(--text-active);
+  }
+}
+
+.el-collapse {
+  --el-collapse-border-color: var(--el-border-color-lighter);
+  --el-collapse-header-height: 28px !important;
+  --el-collapse-header-bg-color: var(--el-fill-color-blank);
+  --el-collapse-header-text-color: var(--el-text-color-primary);
+  --el-collapse-header-font-size: 11px !important;
+  --el-collapse-content-bg-color: var(--el-fill-color-blank);
+  --el-collapse-content-font-size: 12px;
+  --el-collapse-content-text-color: var(--el-text-color-primary);
+  border-bottom: 1px solid var(--el-collapse-border-color);
+  border-top: 1px solid var(--el-collapse-border-color);
 }

+ 0 - 28
src/renderer/src/theme/vars.css

@@ -79,32 +79,4 @@
   --sidebar-bg: #e5e5e5;
   --input-bg: #f8f8f8;
   --button-bg: #e3e3e3;
-}
-
-/* 平滑过渡效果 */
-html,
-body {
-  transition:
-    background-color 0.3s ease,
-    color 0.3s ease,
-    border-color 0.3s ease;
-}
-
-
-.el-tabs {
-  --el-tabs-header-height: 28px !important;
-  --el-color-primary: var(--text-active);
-}
-
-.el-collapse {
-  --el-collapse-border-color: var(--el-border-color-lighter);
-  --el-collapse-header-height: 28px !important;
-  --el-collapse-header-bg-color: var(--el-fill-color-blank);
-  --el-collapse-header-text-color: var(--el-text-color-primary);
-  --el-collapse-header-font-size: 11px !important;
-  --el-collapse-content-bg-color: var(--el-fill-color-blank);
-  --el-collapse-content-font-size: 12px;
-  --el-collapse-content-text-color: var(--el-text-color-primary);
-  border-bottom: 1px solid var(--el-collapse-border-color);
-  border-top: 1px solid var(--el-collapse-border-color);
 }

+ 2 - 0
src/renderer/src/types/appMeta.d.ts

@@ -58,6 +58,8 @@ export interface AppMeta {
   language: string // 语言
   resourcePackaging: ResourcePackaging // 资源打包方式1: C源码 2: C源码+BIN
   binNum: number // // BIN数量 1~32
+  path: string // 项目路径
+  codePath: string // 项目代码路径
   // 芯片配置
   chip: ChipConfig
   // 板卡配置

+ 63 - 2
src/renderer/src/views/designer/modals/projectModal/Recent.vue

@@ -1,7 +1,68 @@
 <template>
-  <div></div>
+  <div>{{ $t('recentProject') }}</div>
+  <el-scrollbar>
+    <ul class="p0">
+      <li
+        class="flex gap-12px items-center hover:bg-bg-secondary cursor-pointer px-4px py-6px"
+        v-for="item in recentStore.recentProjects || []"
+        :key="item.id"
+        @dblclick="openProject(item)"
+      >
+        <div>
+          <FcOpenedFolder size="32px" />
+        </div>
+        <div :title="item.projectPath">
+          <div class="text-12px text-text-primary mb-4px">{{ item.projectName }}</div>
+          <div class="text-10px">
+            <p class="m0 p0 flex gap-8px">
+              <span>{{ $t('projectPath') }}</span>
+              <span>{{ item.createTime }}</span>
+            </p>
+            <p class="m0 p0 flex gap-8px">
+              <span>{{ $t('modifyTime') }}</span>
+              <span>{{ item.modifyTime }}</span>
+            </p>
+          </div>
+        </div>
+      </li>
+    </ul>
+    <el-empty v-if="!recentStore.recentProjects?.length" :image-size="120"> </el-empty>
+  </el-scrollbar>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { FcOpenedFolder } from 'vue-icons-plus/fc'
+import { useRecentProject } from '@/store/modules/recentProject'
+import { ElMessage } from 'element-plus'
+import { useI18n } from 'vue-i18n'
+import { useProjectStore } from '@/store/modules/project'
+
+const recentStore = useRecentProject()
+const projectStore = useProjectStore()
+const { t } = useI18n()
+
+const emit = defineEmits(['opened'])
+
+const openProject = async (project) => {
+  const projectPath = project.projectPath + '//project.ui'
+  // 检查项目是否存在
+  const res = await window.electron.ipcRenderer.invoke('check-file-path', projectPath)
+  if (res) {
+    // 存在 打开项目
+    const result = await window.electron.ipcRenderer.invoke('read-file', projectPath, 'utf-8')
+    if (result) {
+      projectStore.loadProject(JSON.parse(result))
+      emit('opened')
+    } else {
+      // 读取失败 删除项目
+      ElMessage.error(t('readProjectError'))
+    }
+  } else {
+    // 不存在 删除项目
+    ElMessage.error(t('noFoundProject'))
+    recentStore.deleteProject(project.id)
+  }
+}
+</script>
 
 <style scoped></style>

+ 108 - 27
src/renderer/src/views/designer/modals/projectModal/index.vue

@@ -19,12 +19,25 @@
           hide-required-asterisk
         >
           <el-form-item :label="$t('projectName')" prop="name">
-            <el-input v-model="formData.name" :placeholder="$t('pleaseEnter')"></el-input>
+            <el-input
+              v-model="formData.name"
+              :placeholder="$t('pleaseEnter')"
+              spellcheck="false"
+            ></el-input>
           </el-form-item>
           <el-form-item :label="$t('projectPath')" prop="path" required>
             <el-input v-model="formData.path" readonly>
               <template #append>
-                <el-button @click="selectPath"
+                <el-button @click="selectPath('path')"
+                  ><LuFolder :size="16" :disabled="mode === 'edit'"
+                /></el-button>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item :label="$t('codePath')" prop="codePath" required>
+            <el-input v-model="formData.codePath" readonly>
+              <template #append>
+                <el-button @click="selectPath('codePath')"
                   ><LuFolder :size="16" :disabled="mode === 'edit'"
                 /></el-button>
               </template>
@@ -163,42 +176,74 @@
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="PCLK">
-                    <el-input v-model="item.params.PCLK" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.PCLK"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="VBP">
-                    <el-input v-model="item.params.VBP" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.VBP"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="VFP">
-                    <el-input v-model="item.params.VFP" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.VFP"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="HFP">
-                    <el-input v-model="item.params.HFP" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.HFP"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="HSYNC">
-                    <el-input v-model="item.params.HSYNC" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.HSYNC"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="VSYNC">
-                    <el-input v-model="item.params.VSYNC" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.VSYNC"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="HsyncWidth">
-                    <el-input v-model="item.params.HsyncWidth" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.HsyncWidth"
+                    />
                   </el-form-item>
                 </el-col>
                 <el-col :span="8">
                   <el-form-item label="VsyncWidth">
-                    <el-input v-model="item.params.VsyncWidth" />
+                    <el-input-number
+                      controls-position="right"
+                      style="width: 100%"
+                      v-model="item.params.VsyncWidth"
+                    />
                   </el-form-item>
                 </el-col>
               </el-row>
@@ -349,11 +394,13 @@
           </el-row>
         </el-form>
       </el-scrollbar>
-      <div class="w-200px shrink-0">最近项目</div>
+      <div class="w-200px shrink-0 flex flex-col">
+        <Recent @opened="close" />
+      </div>
     </div>
 
     <template #footer>
-      <el-button @click="showModal = false">{{ $t('cancel') }}</el-button>
+      <el-button @click="close">{{ $t('cancel') }}</el-button>
       <el-button type="primary" @click="mode === 'add' ? handleSubmit() : handleEdit()">{{
         $t('create')
       }}</el-button>
@@ -384,11 +431,13 @@
 <script setup lang="ts">
 import type { AppMeta } from '@/types/appMeta'
 import { ElMessage, type FormInstance } from 'element-plus'
-import { computed, reactive, ref, defineExpose, nextTick } from 'vue'
+import { computed, reactive, ref, nextTick, watch } from 'vue'
 import { LuFolder } from 'vue-icons-plus/lu'
 import { useProjectStore } from '@/store/modules/project'
+import { useRecentProject } from '@/store/modules/recentProject'
 import { useAppStore } from '@/store/modules/app'
 import { useI18n } from 'vue-i18n'
+import Recent from './Recent.vue'
 
 import chipConfig from '@/config/multi_chip_config.json'
 import boardConfig from '@/config/board_card_config.json'
@@ -397,6 +446,10 @@ import { klona } from 'klona'
 
 const { t } = useI18n()
 const mode = ref<'add' | 'edit'>('add')
+const projectStore = useProjectStore()
+const appStore = useAppStore()
+const recentProject = useRecentProject()
+
 const formData = reactive<
   AppMeta & {
     path: string
@@ -404,8 +457,9 @@ const formData = reactive<
 >({
   version: '1.0.0',
   description: '',
-  name: 'ProjectName',
+  name: '',
   path: 'D:\\sunmicroDesignerProjects',
+  codePath: 'D:\\sunmicroDesignerProjects',
   type: 'chip',
   chip: {
     model: '',
@@ -452,8 +506,12 @@ const formData = reactive<
   imageCompress: []
 })
 
-const projectStore = useProjectStore()
-const appStore = useAppStore()
+watch(
+  () => recentProject.recentProjects,
+  () => {
+    formData.name = `projectName${(recentProject.recentProjects?.length || 0) + 1}`
+  }
+)
 
 const form = ref<FormInstance>()
 // 显示模态框
@@ -468,17 +526,32 @@ const customScreen = ref({
 })
 // 项目类型选项
 const typeOptions = computed(() => {
-  console.log(appStore.lang)
-  return [
-    { label: t('chip'), value: 'chip' },
-    { label: t('board'), value: 'board' },
-    { label: t('analogDisplay'), value: 'analog_display' }
-  ]
+  return (
+    appStore.lang && [
+      { label: t('chip'), value: 'chip' },
+      { label: t('board'), value: 'board' },
+      { label: t('analogDisplay'), value: 'analog_display' }
+    ]
+  )
 })
 
 const rules = computed(() => {
   return {
-    name: [{ required: true, message: t('nameRequired'), trigger: 'blur' }],
+    name: [
+      { required: true, message: t('nameRequired'), trigger: 'blur' },
+      {
+        trigger: 'blur',
+        validator: (_rule, val, callback) => {
+          const names = recentProject.recentProjects?.map((item) => item.projectName) || []
+          if (names.includes(val)) {
+            callback(new Error(t('nameRepeat')))
+          } else {
+            callback()
+          }
+        }
+      }
+    ],
+    codePath: [{ required: true, message: t('codePathRequired'), trigger: 'blur' }],
     'chip.model': [{ required: true, message: t('chipIsRequired'), trigger: 'blur' }],
     'chip.flash_size.capacity': [
       { required: true, message: t('flashIsRequired'), trigger: 'blur' }
@@ -498,12 +571,12 @@ const handlChangeType = (type: string) => {
   handleChangeScreenTypeByAnalog(formData.screenType)
 }
 
-// 选择项目路径
-const selectPath = async () => {
+// 选择文件夹路径
+const selectPath = async (key: string) => {
   const path = await window.electron.ipcRenderer.invoke('get-directory')
 
   if (path) {
-    formData.path = path
+    formData[key] = path
   }
 }
 
@@ -757,9 +830,10 @@ const handleChangeScreenTypeByAnalog = (type: any) => {
 const handleSubmit = async () => {
   await form.value?.validate()
   if (formData.type === 'board' && !formData.board.model) {
-    ElMessage.warning('请选择板卡')
+    ElMessage.warning(t('selectBoard'))
     return
   }
+
   projectStore.createApp(formData)
   form.value?.resetFields()
   showModal.value = false
@@ -770,6 +844,13 @@ const handleEdit = async () => {
   await form.value?.validate()
 }
 
+// 关闭弹窗
+const close = () => {
+  console.log('close')
+  form.value?.resetFields()
+  showModal.value = false
+}
+
 // expose
 defineExpose({
   create: async () => {

+ 0 - 0
src/renderer/src/views/designer/modals/settingModal/ProjectSettingModal.vue


+ 24 - 23
src/renderer/src/views/designer/sidebar/index.vue

@@ -91,29 +91,30 @@ import GeneralSettingModal from '../modals/settingModal/GeneralSettingModal.vue'
 const { t } = useI18n()
 
 const sidebarMenu = computed(() => {
-  console.log(appStore.lang)
-  return [
-    {
-      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')
-    }
-  ]
+  return (
+    appStore.lang && [
+      {
+        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('widget')

+ 0 - 1
src/renderer/src/views/designer/tools/Operate.vue

@@ -32,7 +32,6 @@ const onMenuClick = inject<(menuKey: string) => void>('onMenuClick', () => {})
 const projectStore = useProjectStore()
 
 const projectMenu = computed((): MenuItemType[] => {
-  console.log(projectStore.history)
   return [
     {
       key: 'undo',