Browse Source

Merge branch 'main' of https://git.shalu.com/jiaxing.liao/sunmicro-lvgl-designer

Mickey Mike 2 weeks ago
parent
commit
88bf9c11b1

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

@@ -48,5 +48,9 @@
   "light": "Light",
   "link": "Link",
   "theme": "Theme",
-  "editProject": "Edit Project"
+  "editProject": "Edit Project",
+  "nameRequired": "Project Name Is Required",
+  "boardIsRequired": "Board Is Required",
+  "flashIsRequired": "Flash Is Required",
+  "ramIsRequired": "RAM Is Required"
 }

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

@@ -48,5 +48,9 @@
   "light": "亮色",
   "link": "连接",
   "download": "下载",
-  "editProject": "编辑项目"
+  "editProject": "编辑项目",
+  "nameRequired": "项目名称不能为空",
+  "boardIsRequired": "板卡不能为空",
+  "flashIsRequired": "闪存不能为空",
+  "ramIsRequired": "内存不能为空"
 }

+ 1 - 0
src/renderer/src/main.ts

@@ -1,6 +1,7 @@
 import { createApp } from 'vue'
 import App from './App.vue'
 import 'virtual:uno.css'
+import 'element-plus/dist/index.css'
 import 'element-plus/theme-chalk/dark/css-vars.css'
 import './theme/vars.css'
 import './style.less'

+ 10 - 10
src/renderer/src/theme/vars.css

@@ -63,19 +63,19 @@
   /* 浅色主题 (默认) */
   --bg-primary: #ffffff;
   --bg-secondary: #f3f3f3;
-  --bg-tertiary: #e4e4e4;
+  --bg-tertiary: #e8e8e8;
   --text-primary: #333333;
   --text-secondary: #666666;
-  --text-active: #000000; /* 激活文本 */
+  --text-active: #000000;
   --accent-blue: #0078d4;
-  --accent-green: #388a34;
-  --accent-yellow: #d7ba7d;
-  --accent-red: #f14c4c;
-  --border-color: #d1d1d1;
-  --scrollbar: #cccccc;
-  --sidebar-bg: #f0f0f0;
-  --input-bg: #ffffff;
-  --button-bg: #f0f0f0;
+  --accent-green: #388a3f;
+  --accent-yellow: #d0b000;
+  --accent-red: #e51400;
+  --border-color: #d4d4d4;
+  --scrollbar: #c5c5c5;
+  --sidebar-bg: #e5e5e5;
+  --input-bg: #f8f8f8;
+  --button-bg: #e3e3e3;
 }
 
 /* 平滑过渡效果 */

+ 7 - 0
src/renderer/src/views/designer/modals/projectModal/Recent.vue

@@ -0,0 +1,7 @@
+<template>
+  <div></div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped></style>

+ 340 - 315
src/renderer/src/views/designer/modals/projectModal/index.vue

@@ -2,157 +2,54 @@
   <el-dialog
     v-model="showModal"
     :title="mode === 'add' ? $t('createProject') : $t('editProject')"
-    width="1000px"
+    width="1200px"
     body-class="h-500px overflow-y-auto overflow-x-hidden modal-body-scroll px-12px"
     :modal="false"
     :close-on-click-modal="false"
     align-center
   >
-    <el-scrollbar wrap-class="pr-12px">
-      <el-form
-        :model="formData"
-        ref="form"
-        label-width="80px"
-        style="display: flex; flex-direction: column"
-        hide-required-asterisk
-      >
-        <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="$t('projectPath')" prop="path" required>
-          <el-input v-model="formData.path" readonly>
-            <template #append>
-              <el-button @click="selectPath"
-                ><LuFolder :size="16" :disabled="mode === 'edit'"
-              /></el-button>
-            </template>
-          </el-input>
-        </el-form-item>
-        <el-form-item :label="$t('projectType')" prop="type" required>
-          <el-select v-model="formData.type" @change="handlChangeType">
-            <el-option
-              v-for="item in typeOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            ></el-option>
-          </el-select>
-        </el-form-item>
-        <el-divider />
-        <!-- 芯片配置 -->
-        <template v-if="formData.type === 'chip'">
-          <el-row :gutter="12">
-            <el-col :span="8">
-              <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"
-                    :key="item"
-                    :label="item"
-                    :value="item"
-                  ></el-option>
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item :label="$t('flashSize')" prop="chip.flash_size.capacity" required>
-                <el-autocomplete
-                  size="small"
-                  style="width: 100%"
-                  :fetch-suggestions="querySearchFlash"
-                  v-model.number="formData.chip.flash_size.capacity"
-                >
-                  <template #append>
-                    <el-select v-model="formData.chip.flash_size.unit" style="width: 68px">
-                      <el-option
-                        v-for="item in selectedChipConfig?.flash_size?.unit_options || []"
-                        :key="item"
-                        :label="item"
-                        :value="item"
-                      ></el-option>
-                    </el-select>
-                  </template>
-                </el-autocomplete>
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item :label="$t('ramSize')" prop="chip.ram_size.capacity" required>
-                <el-autocomplete
-                  size="small"
-                  style="width: 100%"
-                  :fetch-suggestions="querySearchRam"
-                  v-model.number="formData.chip.ram_size.capacity"
-                >
-                  <template #append>
-                    <el-select v-model="formData.chip.ram_size.unit" style="width: 68px">
-                      <el-option
-                        v-for="item in selectedChipConfig?.ram_size?.unit_options || []"
-                        :key="item"
-                        :label="item"
-                        :value="item"
-                      ></el-option>
-                    </el-select>
-                  </template>
-                </el-autocomplete>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <div class="flex items-center justify-center gap-12px">
-            <el-radio-group
-              v-model="formData.screenType"
-              size="small"
-              fill="#6cf"
-              @change="handleChangeScreenTypeByChip"
-            >
-              <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">{{ $t('screenIndex', [index + 1]) }}</el-divider>
+    <div class="w-full h-full flex">
+      <el-scrollbar wrap-class="pr-12px">
+        <el-form
+          :model="formData"
+          :rules="rules"
+          ref="form"
+          label-width="120px"
+          style="display: flex; flex-direction: column"
+          hide-required-asterisk
+        >
+          <el-form-item :label="$t('projectName')" prop="name">
+            <el-input v-model="formData.name" :placeholder="$t('pleaseEnter')"></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"
+                  ><LuFolder :size="16" :disabled="mode === 'edit'"
+                /></el-button>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item :label="$t('projectType')" prop="type" required>
+            <el-select v-model="formData.type" @change="handlChangeType">
+              <el-option
+                v-for="item in typeOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-divider />
+          <!-- 芯片配置 -->
+          <template v-if="formData.type === 'chip'">
             <el-row :gutter="12">
               <el-col :span="8">
-                <el-form-item :label="$t('resolution')">
-                  <el-select
-                    :model-value="`${item.width}x${item.height}`"
-                    @change="(val) => handleSetResolution(val, index)"
-                  >
-                    <el-option :label="$t('custom')" value="custom" />
-                    <el-option
-                      v-for="item in getScreenOptions('resolutions')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item :label="$t('interfaceType')">
-                  <el-select v-model="item.interface" prop="interface" required>
-                    <el-option
-                      v-for="item in getScreenOptions('interface')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item :label="$t('colorFormat')">
-                  <el-select v-model="item.colorFormat">
-                    <el-option
-                      v-for="item in getScreenOptions('color_format')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item :label="$t('colorDepth')">
-                  <el-select v-model="item.colorDepth">
+                <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 getScreenOptions('color_depth')?.[index] || []"
+                      v-for="item in chipOptions"
+                      :key="item"
                       :label="item"
                       :value="item"
                     ></el-option>
@@ -160,187 +57,300 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="PCLK">
-                  <el-input 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-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="VFP">
-                  <el-input 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-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="HSYNC">
-                  <el-input 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-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="HsyncWidth">
-                  <el-input v-model="item.params.HsyncWidth" />
+                <el-form-item :label="$t('flashSize')" prop="chip.flash_size.capacity" required>
+                  <el-autocomplete
+                    size="small"
+                    style="width: 100%"
+                    :fetch-suggestions="querySearchFlash"
+                    v-model.number="formData.chip.flash_size.capacity"
+                  >
+                    <template #append>
+                      <el-select v-model="formData.chip.flash_size.unit" style="width: 68px">
+                        <el-option
+                          v-for="item in selectedChipConfig?.flash_size?.unit_options || []"
+                          :key="item"
+                          :label="item"
+                          :value="item"
+                        ></el-option>
+                      </el-select>
+                    </template>
+                  </el-autocomplete>
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="VsyncWidth">
-                  <el-input v-model="item.params.VsyncWidth" />
+                <el-form-item :label="$t('ramSize')" prop="chip.ram_size.capacity" required>
+                  <el-autocomplete
+                    size="small"
+                    style="width: 100%"
+                    :fetch-suggestions="querySearchRam"
+                    v-model.number="formData.chip.ram_size.capacity"
+                  >
+                    <template #append>
+                      <el-select v-model="formData.chip.ram_size.unit" style="width: 68px">
+                        <el-option
+                          v-for="item in selectedChipConfig?.ram_size?.unit_options || []"
+                          :key="item"
+                          :label="item"
+                          :value="item"
+                        ></el-option>
+                      </el-select>
+                    </template>
+                  </el-autocomplete>
                 </el-form-item>
               </el-col>
             </el-row>
-          </div>
-        </template>
-        <!-- 板卡配置 -->
-        <template v-if="formData.type === 'board'">
-          <div class="flex items-center justify-end gap-12px mb-12px">
-            <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">
-              <el-col v-for="item in boardOptions || []" :span="12">
-                <div
-                  class="w-full h-100px bg-#fff relative cursor-pointer border1 border-transparent border-solid"
-                  :class="formData.board.model === item.board_name ? 'border-#0ff!' : ''"
-                  @click="handleSetBoard(item)"
-                >
-                  <el-image class="w-full h-full" :src="item.pic_path">
-                    <template #error>
-                      <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">
-                    <div class="text-center">{{ item.board_name }}</div>
-                    <div class="text-center">{{ item.description }}</div>
+            <div class="flex items-center justify-center gap-12px">
+              <el-radio-group
+                v-model="formData.screenType"
+                size="small"
+                fill="#6cf"
+                @change="handleChangeScreenTypeByChip"
+              >
+                <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">{{ $t('screenIndex', [index + 1]) }}</el-divider>
+              <el-row :gutter="12">
+                <el-col :span="8">
+                  <el-form-item :label="$t('resolution')">
+                    <el-select
+                      :model-value="`${item.width}x${item.height}`"
+                      @change="(val) => handleSetResolution(val, index)"
+                    >
+                      <el-option :label="$t('custom')" value="custom" />
+                      <el-option
+                        v-for="item in getScreenOptions('resolutions')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item :label="$t('interfaceType')">
+                    <el-select v-model="item.interface" prop="interface" required>
+                      <el-option
+                        v-for="item in getScreenOptions('interface')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item :label="$t('colorFormat')">
+                    <el-select v-model="item.colorFormat">
+                      <el-option
+                        v-for="item in getScreenOptions('color_format')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item :label="$t('colorDepth')">
+                    <el-select v-model="item.colorDepth">
+                      <el-option
+                        v-for="item in getScreenOptions('color_depth')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="PCLK">
+                    <el-input 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-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="VFP">
+                    <el-input 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-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="HSYNC">
+                    <el-input 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-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="HsyncWidth">
+                    <el-input 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-form-item>
+                </el-col>
+              </el-row>
+            </div>
+          </template>
+          <!-- 板卡配置 -->
+          <template v-if="formData.type === 'board'">
+            <div class="flex items-center justify-end gap-12px mb-12px">
+              <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">
+                <el-col v-for="item in boardOptions || []" :span="12">
+                  <div
+                    class="w-full h-100px bg-#fff relative cursor-pointer border1 border-transparent border-solid"
+                    :class="formData.board.model === item.board_name ? 'border-#0ff!' : ''"
+                    @click="handleSetBoard(item)"
+                  >
+                    <el-image class="w-full h-full" :src="item.pic_path">
+                      <template #error>
+                        <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">
+                      <div class="text-center">{{ item.board_name }}</div>
+                      <div class="text-center">{{ item.description }}</div>
+                    </div>
                   </div>
-                </div>
-              </el-col>
-            </el-row>
-          </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="$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="$t('screenIndexColorDepth', [index + 1])" label-width="120px">
-                  <el-select v-model="screen.colorDepth">
-                    <el-option
-                      v-for="item in colorDepthOptions[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-            </template>
-          </el-row>
-        </template>
-        <!-- 虚拟显示 -->
-        <template v-if="formData.type === 'analog_display'">
-          <div class="flex items-center justify-center gap-12px">
-            <el-radio-group
-              v-model="formData.screenType"
-              size="small"
-              fill="#6cf"
-              @change="handleChangeScreenTypeByAnalog"
-            >
-              <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">{{ $t('screenIndex', [index + 1]) }}</el-divider>
+                </el-col>
+              </el-row>
+            </div>
+            <div class="my-12px text-text-secondary">
+              {{ $t('currentSelectModel', [formData.board.model]) }}
+            </div>
             <el-row :gutter="12">
-              <el-col :span="8">
-                <el-form-item :label="$t('resolution')">
-                  <el-select
-                    :model-value="`${item.width}x${item.height}`"
-                    @change="(val) => handleSetResolution(val, index)"
+              <template v-for="(screen, index) in formData.screens">
+                <el-col :span="12">
+                  <el-form-item
+                    :label="$t('screenIndexResolution', [index + 1])"
+                    label-width="120px"
                   >
-                    <el-option :label="$t('custom')" value="custom" />
-                    <el-option
-                      v-for="item in getAnalogDisplayOptions('resolutions')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item :label="$t('colorFormat')">
-                  <el-select v-model="item.colorFormat">
-                    <el-option
-                      v-for="item in getAnalogDisplayOptions('color_format')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item :label="$t('colorDepth')">
-                  <el-select v-model="item.colorDepth">
-                    <el-option
-                      v-for="item in getAnalogDisplayOptions('color_depth')?.[index] || []"
-                      :label="item"
-                      :value="item"
-                    ></el-option>
-                  </el-select>
-                </el-form-item>
-              </el-col>
+                    <el-input :model-value="`${screen.width}x${screen.height}`" disabled />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <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] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </template>
             </el-row>
-          </div>
-        </template>
-        <el-divider />
-        <el-row :gutter="12">
-          <el-col :span="8">
-            <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="$t('packageType')">
-              <el-select v-model="formData.resourcePackaging">
-                <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="$t('binNumber')">
-              <el-input v-model.number="formData.binNum" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item :label="$t('projectDesc')">
-              <el-input type="textarea" v-model="formData.description" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-    </el-scrollbar>
+          </template>
+          <!-- 虚拟显示 -->
+          <template v-if="formData.type === 'analog_display'">
+            <div class="flex items-center justify-center gap-12px">
+              <el-radio-group
+                v-model="formData.screenType"
+                size="small"
+                fill="#6cf"
+                @change="handleChangeScreenTypeByAnalog"
+              >
+                <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">{{ $t('screenIndex', [index + 1]) }}</el-divider>
+              <el-row :gutter="12">
+                <el-col :span="8">
+                  <el-form-item :label="$t('resolution')">
+                    <el-select
+                      :model-value="`${item.width}x${item.height}`"
+                      @change="(val) => handleSetResolution(val, index)"
+                    >
+                      <el-option :label="$t('custom')" value="custom" />
+                      <el-option
+                        v-for="item in getAnalogDisplayOptions('resolutions')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item :label="$t('colorFormat')">
+                    <el-select v-model="item.colorFormat">
+                      <el-option
+                        v-for="item in getAnalogDisplayOptions('color_format')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item :label="$t('colorDepth')">
+                    <el-select v-model="item.colorDepth">
+                      <el-option
+                        v-for="item in getAnalogDisplayOptions('color_depth')?.[index] || []"
+                        :label="item"
+                        :value="item"
+                      ></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+            </div>
+          </template>
+          <el-divider />
+          <el-row :gutter="12">
+            <el-col :span="8">
+              <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="$t('packageType')">
+                <el-select v-model="formData.resourcePackaging">
+                  <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="$t('binNumber')">
+                <el-input v-model.number="formData.binNum" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item :label="$t('projectDesc')">
+                <el-input type="textarea" v-model="formData.description" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </el-scrollbar>
+      <div class="w-200px shrink-0">最近项目</div>
+    </div>
 
     <template #footer>
       <el-button @click="showModal = false">{{ $t('cancel') }}</el-button>
@@ -373,7 +383,7 @@
 
 <script setup lang="ts">
 import type { AppMeta } from '@/types/appMeta'
-import type { FormInstance } from 'element-plus'
+import { ElMessage, type FormInstance } from 'element-plus'
 import { computed, reactive, ref, defineExpose, nextTick } from 'vue'
 import { LuFolder } from 'vue-icons-plus/lu'
 import { useProjectStore } from '@/store/modules/project'
@@ -466,6 +476,17 @@ const typeOptions = computed(() => {
   ]
 })
 
+const rules = computed(() => {
+  return {
+    name: [{ required: true, message: t('nameRequired'), trigger: 'blur' }],
+    'chip.model': [{ required: true, message: t('boardIsRequired'), trigger: 'blur' }],
+    'chip.flash_size.capacity': [
+      { required: true, message: t('flashIsRequired'), trigger: 'blur' }
+    ],
+    'chip.ram_size.capacity': [{ required: true, message: t('ramIsRequired'), trigger: 'blur' }]
+  }
+})
+
 // 切换项目类型
 const handlChangeType = (type: string) => {
   if (type === 'analog_display') {
@@ -735,6 +756,10 @@ const handleChangeScreenTypeByAnalog = (type: any) => {
 // 创建项目
 const handleSubmit = async () => {
   await form.value?.validate()
+  if (formData.type === 'board' && !formData.board.model) {
+    ElMessage.warning('请选择板卡')
+    return
+  }
   projectStore.createApp(formData)
   form.value?.resetFields()
   showModal.value = false

+ 5 - 5
src/renderer/src/views/designer/tools/Help.vue

@@ -19,12 +19,12 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, inject } from 'vue'
-import { LuHelpCircle, LuFileKey, LuBadgeInfo } from 'vue-icons-plus/lu'
+import { inject } from 'vue'
+import { LuHelpCircle, LuFileKey, LuInfo } from 'vue-icons-plus/lu'
 
 const onMenuClick = inject<(menuKey: string) => void>('onMenuClick', () => {})
 
-const projectMenu = reactive([
+const projectMenu = [
   {
     key: 'lincense',
     label: '授权信息',
@@ -38,9 +38,9 @@ const projectMenu = reactive([
   {
     key: 'about',
     label: '关于',
-    img: LuBadgeInfo
+    img: LuInfo
   }
-])
+]
 
 const handleClick = (menuKey: string) => {
   onMenuClick?.(menuKey)

+ 2 - 16
src/renderer/src/views/designer/tools/Operate.vue

@@ -1,22 +1,7 @@
 <template>
   <template v-for="item in projectMenu" :key="item.label">
     <el-divider direction="vertical" v-if="item.type" />
-    <div
-      v-else
-      class="flex flex-col justify-center items-center w-50px h-50px cursor-pointer mr-10px hover:bg-bg-tertiary rounded-4px"
-      @click="handleClick(item.key)"
-    >
-      <div class="w-30px h-30px flex items-center justify-center">
-        <img
-          v-if="typeof item.img === 'string'"
-          :src="item.img"
-          class="w-24px h-24px"
-          :alt="item.label"
-        />
-        <component v-else :is="item.img" size="20px"></component>
-      </div>
-      <div class="text-12px">{{ item.label }}</div>
-    </div>
+    <MenuItem v-else :item="item" @click="handleClick(item.key)" />
   </template>
 </template>
 
@@ -38,6 +23,7 @@ import {
   LuArrowUpToLine,
   LuArrowDownToLine
 } from 'vue-icons-plus/lu'
+import MenuItem from './components/MenuItem.vue'
 
 const onMenuClick = inject<(menuKey: string) => void>('onMenuClick', () => {})
 

+ 44 - 0
src/renderer/src/views/designer/tools/components/MenuItem.vue

@@ -0,0 +1,44 @@
+<template>
+  <div
+    class="flex flex-col justify-center items-center w-50px h-50px cursor-pointer mr-10px hover:bg-bg-tertiary rounded-4px"
+    :class="disabled ? 'cursor-not-allowed text-text-secondary' : 'pointer-cursor'"
+    @click="handleClick(item)"
+  >
+    <div class="w-30px h-30px flex items-center justify-center">
+      <img
+        v-if="typeof item.img === 'string'"
+        :src="item.img"
+        class="w-24px h-24px"
+        :alt="item.label"
+      />
+      <component v-else :is="item.img" size="20px"></component>
+    </div>
+    <div class="text-12px">{{ item.label }}</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { VNode } from 'vue'
+import { defineEmits, defineProps } from 'vue'
+
+const props = defineProps<{
+  item: {
+    key: string
+    label: string
+    img: string | VNode
+  }
+  disabled?: boolean
+  onClick?: (item: { key: string; label: string; img: string | VNode }) => void
+}>()
+
+const emit = defineEmits<{
+  (e: 'click', item: { key: string; label: string; img: string | VNode }): void
+}>()
+
+function handleClick(item: { key: string; label: string; img: string | VNode }) {
+  if (props.disabled) return
+  emit('click', item)
+}
+</script>
+
+<style scoped></style>