Explorar el Código

i18n(refactor): 国际化重构及代码清理

- 将硬编码的中文文本替换为国际化键值
- 添加 vue-i18n 依赖并在相关组件中使用 t 函数
- 移除注释代码和冗余的空行以优化代码结构
- 在 blue-header.vue 中将标题、用户手册、昵称等文本国际化
- 在 CameraConfig.vue 中将点位标签、刷新、相机绑定等文本国际化
- 在 check.vue 中将拍摄检查页面的提示文本和按钮文本国际化
- 清理未使用的变量声明和注释掉的代码块
- 优化样式代码的缩进和格式
panqiuyao hace 3 días
padre
commit
bf8108807c
Se han modificado 28 ficheros con 1161 adiciones y 3656 borrados
  1. 1 0
      frontend/package.json
  2. 31 28
      frontend/src/components/check/index.vue
  3. 8 6
      frontend/src/components/header-bar/blue-header.vue
  4. 80 34
      frontend/src/components/header-bar/index.vue
  5. 26 59
      frontend/src/components/login/index.vue
  6. 2 0
      frontend/src/main.ts
  7. 22 17
      frontend/src/router/index.ts
  8. 7 6
      frontend/src/utils/menus/generate.ts
  9. 5 32
      frontend/src/views/Developer/cmd.vue
  10. 9 30
      frontend/src/views/Developer/index.vue
  11. 6 35
      frontend/src/views/Developer/mcu.vue
  12. 58 100
      frontend/src/views/Developer/normal.vue
  13. 59 187
      frontend/src/views/Home/index.vue
  14. 72 191
      frontend/src/views/OTA/index.vue
  15. 97 393
      frontend/src/views/Photography/check.vue
  16. 8 19
      frontend/src/views/Photography/components/LoadingDialog.vue
  17. 38 37
      frontend/src/views/Photography/components/editRow.vue
  18. 74 83
      frontend/src/views/Photography/detail.vue
  19. 13 244
      frontend/src/views/Photography/expired.vue
  20. 87 81
      frontend/src/views/Photography/mixin/usePhotography.ts
  21. 28 27
      frontend/src/views/Photography/processImage.vue
  22. 30 61
      frontend/src/views/Photography/seniorDetail.vue
  23. 90 779
      frontend/src/views/Photography/shot.vue
  24. 70 252
      frontend/src/views/RemoteControl/index.vue
  25. 45 138
      frontend/src/views/Setting/components/CameraConfig.vue
  26. 7 27
      frontend/src/views/Setting/components/otherConfig.vue
  27. 172 775
      frontend/src/views/Setting/index.vue
  28. 16 15
      public/dist/index.html

+ 1 - 0
frontend/package.json

@@ -19,6 +19,7 @@
     "pinia": "3.0.1",
     "pinia-plugin-persistedstate": "4.2.0",
     "vue": "3.5.13",
+    "vue-i18n": "^9.14.4",
     "vue-router": "4.5.0"
   },
   "devDependencies": {

+ 31 - 28
frontend/src/components/check/index.vue

@@ -18,9 +18,9 @@
           </template>
           <div class="step-content">
             <div class="step-title">
-              <template v-if="checkLoading">正在初始化检测硬件......</template>
-              <template v-else-if="checkSuccess">硬件检测完成</template>
-              <template v-else>初始化检测硬件失败</template>
+              <template v-if="checkLoading">{{ $t('hardwareCheck.checkingHardware') }}</template>
+              <template v-else-if="checkSuccess">{{ $t('hardwareCheck.hardwareCheckComplete') }}</template>
+              <template v-else>{{ $t('hardwareCheck.hardwareCheckFailed') }}</template>
             </div>
             <el-progress
               :percentage="checkInfoStore.getProgress"
@@ -40,10 +40,10 @@
             <img src="@/assets/images/check/icon2.png" class="custom-timeline-icon" />
           </template>
           <div class="step-content">
-            <div class="step-title">自我检查</div>
+            <div class="step-title">{{ $t('hardwareCheck.selfCheck') }}</div>
             <div class="check-result">
-              <p>补光灯电源和连接器是否正常连上。</p>
-              <p>请检查红外线器是否正常显示。</p>
+              <p>{{ $t('hardwareCheck.checkTip1') }}</p>
+              <p>{{ $t('hardwareCheck.checkTip2') }}</p>
             </div>
           </div>
         </el-timeline-item>
@@ -51,11 +51,11 @@
     </div>
     <template #footer v-if="!checkLoading">
         <div class="flex" v-if="!checkSuccess">
-            <div class="check-btn cu-p" style="width: 160px; margin-right: 10px;" @click="reCheck">重新监测一次</div>
-            <div class="check-btn check-btn-secondary cu-p" style="width: 160px;" @click="openStatusDialog">查看设备状态</div>
+            <div class="check-btn cu-p" style="width: 160px; margin-right: 10px;" @click="reCheck">{{ $t('hardwareCheck.reCheck') }}</div>
+            <div class="check-btn check-btn-secondary cu-p" style="width: 160px;" @click="openStatusDialog">{{ $t('hardwareCheck.viewDeviceStatus') }}</div>
         </div>
         <div class="flex" v-else>
-            <div class="check-btn cu-p" style="width: 180px" @click="confirm()">检测成功,继续操作!</div>
+            <div class="check-btn cu-p" style="width: 180px" @click="confirm()">{{ $t('hardwareCheck.checkSuccessContinue') }}</div>
         </div>
     </template>
   </el-dialog>
@@ -63,7 +63,7 @@
   <!-- 设备状态对话框 -->
   <el-dialog
     v-model="showStatusDialog"
-    title="设备运行状态"
+    :title="$t('hardwareCheck.deviceStatus')"
     width="600px"
     :close-on-click-modal="false"
   >
@@ -75,8 +75,8 @@
     </div>
     <template #footer>
       <span class="dialog-footer">
-        <el-button @click="showStatusDialog = false">取消</el-button>
-        <el-button type="primary" :loading="statusLoading" @click="getDeviceStatus">刷新</el-button>
+        <el-button @click="showStatusDialog = false">{{ $t('hardwareCheck.cancel') }}</el-button>
+        <el-button type="primary" :loading="statusLoading" @click="getDeviceStatus">{{ $t('hardwareCheck.refresh') }}</el-button>
       </span>
     </template>
   </el-dialog>
@@ -84,6 +84,7 @@
 
 <script setup>
 import { ref, computed, watch, onBeforeUnmount, nextTick, watchEffect } from 'vue';
+import i18n from '@/locales';
 import { ElMessage, ElDialog, ElButton } from 'element-plus';
 import useUserInfo from "@/stores/modules/user";
 import checkInfo from "@/stores/modules/check";
@@ -105,7 +106,7 @@ const props = defineProps({
   },
   title: {
     type: String,
-    default: '检测硬件'
+    default: 'hardwareCheck.title'
   }
 });
 
@@ -235,30 +236,32 @@ onBeforeUnmount(() => {
   clientStore.ipc.removeAllListeners(icpList.socket.message + '_get_mcu_info')
 })
 
-// 状态文本映射
-const stateTextMap = {
-  0: "未初始化",
-  1: "运动中",
-  2: "已停止",
-  3: "未在线",
-  4: "堵转"
+// 获取状态文本映射
+function getStateTextMap() {
+  return {
+    0: i18n.global.t('hardwareCheck.statusNotInit'),
+    1: i18n.global.t('hardwareCheck.statusMoving'),
+    2: i18n.global.t('hardwareCheck.statusStopped'),
+    3: i18n.global.t('hardwareCheck.statusOffline'),
+    4: i18n.global.t('hardwareCheck.statusBlocked')
+  }
 }
 
 // 获取状态标签
 function getStatusLabel(key) {
   const labelMap = {
-    state_camera_motor: '相机高度状态',
-    state_camera_steering: '相机角度状态',
-    state_turntable_steering: '转盘状态',
-    state_move_turntable_steering: '转盘前后移动状态',
-    state_overturn_steering: '翻板状态'
+    state_camera_motor: i18n.global.t('hardwareCheck.cameraHeightStatus'),
+    state_camera_steering: i18n.global.t('hardwareCheck.cameraAngleStatus'),
+    state_turntable_steering: i18n.global.t('hardwareCheck.turntableStatus'),
+    state_move_turntable_steering: i18n.global.t('hardwareCheck.turntableMoveStatus'),
+    state_overturn_steering: i18n.global.t('hardwareCheck.flipBoardStatus')
   }
   return labelMap[key] || key
 }
 
 // 获取状态文本
 function getStatusText(status) {
-  return stateTextMap[status] || '未知状态'
+  return getStateTextMap()[status] || i18n.global.t('hardwareCheck.statusUnknown')
 }
 
 // 获取状态样式类
@@ -307,7 +310,7 @@ function getDeviceStatus() {
         state_overturn_steering: result.data.data_state.state_overturn_steering || 0
       }
     } else {
-      ElMessage.error(result.msg || '获取设备状态失败')
+      ElMessage.error(result.msg || i18n.global.t('hardwareCheck.getStatusFailed'))
     }
 
     clientStore.ipc.removeAllListeners(icpList.socket.message + '_get_mcu_info')
@@ -317,7 +320,7 @@ function getDeviceStatus() {
   setTimeout(() => {
     if (statusLoading.value) {
       statusLoading.value = false
-      ElMessage.error('获取设备状态超时')
+      ElMessage.error(i18n.global.t('hardwareCheck.getStatusTimeout'))
       clientStore.ipc.removeAllListeners(icpList.socket.message + '_get_mcu_info')
     }
   }, 10000)

+ 8 - 6
frontend/src/components/header-bar/blue-header.vue

@@ -2,19 +2,19 @@
   <div class="blue-header-bar">
     <div class="blue-header-bar__left">
       <img src="@/assets/images/detail/logo.png" class="blue-header-bar__logo" alt="logo" />
-      <span class="blue-header-bar__title">智惠映AI自动拍照机 <span class="blue-header-bar__version"  @click="openOTA">{{ currentVersion }}</span></span>
+      <span class="blue-header-bar__title">{{ $t('home.appTitle') }} <span class="blue-header-bar__version"  @click="openOTA">{{ currentVersion }}</span></span>
     </div>
     <div class="blue-header-bar__right">
-      <div class="blue-header-bar__manual" @click="openManual" title="点击查看使用手册">
-        使用手册
+      <div class="blue-header-bar__manual" @click="openManual" :title="$t('header.userManual')">
+        {{ $t('header.userManual') }}
       </div>
       <div class="blue-header-bar__user">
-        <span class="blue-header-bar__user-label">昵称:</span>
+        <span class="blue-header-bar__user-label">{{ $t('common.nickname') }}:</span>
         <span class="blue-header-bar__user-name">
           {{  (useUserInfoStore.userInfo as any).account_name
             || (useUserInfoStore.userInfo as any).real_name
             || (useUserInfoStore.userInfo as any).login_name
-            || '未登录' }}
+            || $t('common.notLoggedIn') }}
         </span>
       </div>
     </div>
@@ -24,6 +24,7 @@
 
 <script setup lang="ts">
 import {defineProps, reactive, onMounted, onUnmounted, ref} from 'vue'
+import { useI18n } from 'vue-i18n'
 import useUserInfo from '@/stores/modules/user'
 import tokenInfo from '@/stores/modules/token';
 import packageJson from '@/../../package.json'
@@ -37,6 +38,7 @@ const useUserInfoStore = useUserInfo()
 const tokenInfoStore = tokenInfo();
 const currentVersion = ref(packageJson.version)
 const clientStore = client()
+const { t } = useI18n()
 
 const Router = useRouter()
 onMounted(async ()=>{
@@ -55,7 +57,7 @@ function openOTA() {
 
   clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
-    title: '版本更新',
+    title: t('router.ota'),
     width: 900,
     height: 700,
     frame: true,

+ 80 - 34
frontend/src/components/header-bar/index.vue

@@ -18,7 +18,6 @@
             <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
             <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
           </div>
-          <!-- 二级菜单 -->
           <div
             class="header-bar__submenu-body"
             :class="{ 'submenu-open': submenuOpen[index] }"
@@ -44,7 +43,6 @@
                   <img v-if="getItemIcon(child)" :src="getItemIcon(child)" class="header-bar__menu-icon" />
                   <span class="header-bar__menu-name">{{ getItemName(child) }}</span>
                 </div>
-                <!-- 三级菜单 -->
                 <div
                   class="header-bar__submenu-third-body"
                   :class="{ 'submenu-open': thirdLevelOpen[`${index}-${childIndex}`] }"
@@ -67,23 +65,33 @@
     </div>
     <div class="header-bar__title">
       <span class="header-bar__text">
-        <slot name="title">{{ title }}</slot>
+        <slot name="title">{{ $t(title) }}</slot>
       </span>
     </div>
     <div class="header-bar__buttons">
-      <!-- 版本信息 - 始终显示 -->
       <div class="header-bar__button header-bar__button__version">
-        <span class="version-text" @click="openOTA" title="点击查看版本详情">
-          当前版本:{{ currentVersion }}
+        <span class="version-text" @click="openOTA" :title="$t('header.currentVersion', { version: currentVersion })">
+          {{ $t('header.currentVersion', { version: currentVersion }) }}
         </span>
       </div>
-      <!-- 使用手册 - 始终显示 -->
       <div class="header-bar__button header-bar__button__manual">
-        <span class="manual-text" @click="openManual" title="点击查看使用手册">
-          使用手册
+        <span class="manual-text" @click="openManual" :title="$t('header.userManual')">
+          {{ $t('header.userManual') }}
         </span>
       </div>
-      <!-- 用户信息 - 仅在需要时显示 -->
+      <!-- Language switcher -->
+      <div class="header-bar__button header-bar__button__lang">
+        <el-dropdown @command="handleLanguageChange">
+          <span class="lang-text">{{ currentLangLabel }}</span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item command="zh-CN" :class="{ 'is-active': currentLang === 'zh-CN' }">简体中文</el-dropdown-item>
+              <el-dropdown-item command="en" :class="{ 'is-active': currentLang === 'en' }">English</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+      <!-- User info -->
       <div class="header-bar__button header-bar__button__user" v-if="showUser">
         <el-dropdown>
           <span class="el-dropdown-link">
@@ -91,7 +99,7 @@
           </span>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item @click="loginOut">退出登录</el-dropdown-item>
+              <el-dropdown-item @click="loginOut">{{ $t('header.logout') }}</el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -102,32 +110,37 @@
 </template>
 
 <script setup lang="ts">
-import {defineProps, reactive, onMounted, onUnmounted, ref} from 'vue'
+import {defineProps, reactive, onMounted, onUnmounted, ref, computed} from 'vue'
+import { useI18n } from 'vue-i18n'
 import useUserInfo from '@/stores/modules/user'
 import { useRouter } from 'vue-router'
 import iconsz from './assets/shezhi@2x.png'
 import iconykq from './assets/yaokong@2x.png'
-import gengxin from './assets/gengxin.svg'
 import icpList from '@/utils/ipc'
 import { getRouterUrl } from '@/utils/appfun'
 import client from '@/stores/modules/client'
 import packageJson from '@/../../package.json';
 import { getCameraMachineDoc } from '@/apis/other';
+import { getLanguage, setLanguage } from '@/utils/language'
+import { switchLanguage } from '@/locales'
 
+const { t, locale } = useI18n()
 const clientStore = client()
 const useUserInfoStore = useUserInfo()
 
 const currentVersion = ref(packageJson.version);
+const currentLang = ref(getLanguage())
+const currentLangLabel = computed(() => currentLang.value === 'zh-CN' ? '中文' : 'EN')
+
 // 定义 menu 项的类型
 interface MenuItem {
-  name?: string //名称
-  icon?: string // 图标
-  click?: () => void // 点击事件
-  type?: keyof typeof menuType // 类型 menuType里面的值
-  children?: MenuItem[] // 支持嵌套菜单
+  name?: string
+  icon?: string
+  click?: () => void
+  type?: keyof typeof menuType
+  children?: MenuItem[]
 }
 
-// 定义 props
 const props = defineProps({
   title: {
     type: String,
@@ -159,33 +172,39 @@ const toggleThirdLevel = (parentIndex: number, childIndex: number) => {
 
 const menuType = reactive({
   setting: {
-    name: '设置',
+    name: t('header.setting'),
     icon: iconsz,
     click: openSetting
   },
   remoteControl: {
-    name: '模拟遥控器',
+    name: t('header.remoteSimulator'),
     icon: iconykq,
     click: openRemoteControl
   },
   developer: {
-    name: '初始设备调频设置',
+    name: t('header.deviceSetup'),
     icon: iconsz,
     click: openDeveloper
   },
   ota: {
-    name: '当前版本:'+currentVersion.value,
+    name: t('header.currentVersion', { version: currentVersion.value }),
     click: openOTA
   }
 })
 
+// 更新 menuType 的名称(语言切换时调用)
+const updateMenuTypeNames = () => {
+  menuType.setting.name = t('header.setting')
+  menuType.remoteControl.name = t('header.remoteSimulator')
+  menuType.developer.name = t('header.deviceSetup')
+  menuType.ota.name = t('header.currentVersion', { version: currentVersion.value })
+}
+
 function getItemClick(item: MenuItem) {
   const menuItem = item.type ? { ...menuType[item.type], ...item } : item
   if (menuItem && menuItem.click) {
     menuItem.click()
   }
-
-  // 关闭所有展开的一级和三级菜单
   for (const key in submenuOpen) {
     submenuOpen[key] = false
   }
@@ -213,7 +232,7 @@ function openSetting() {
 
   clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
-    title: '设置',
+    title: t('header.setting'),
     width: 3840,
     height: 2160,
     frame: true,
@@ -230,7 +249,7 @@ function openRemoteControl() {
 
   clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
-    title: '模拟遥控器',
+    title: t('header.remoteSimulator'),
     width: 350,
     height: 600,
     frame: true,
@@ -247,7 +266,7 @@ function openDeveloper() {
 
   clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
-    title: '初始设备调频设置',
+    title: t('header.deviceSetup'),
     width: 900,
     height: 700,
     frame: true,
@@ -264,7 +283,7 @@ function openOTA() {
 
   clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
-    title: '版本更新',
+    title: t('router.ota'),
     width: 900,
     height: 700,
     frame: true,
@@ -281,15 +300,12 @@ async function openManual() {
     });
 
     if (response.data && response.data.url) {
-      // 使用系统默认浏览器打开URL
       clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
       let params = {
         action: 'openExternal',
         params: response.data.url
       };
       clientStore.ipc.send(icpList.utils.shellFun, params);
-    } else {
-      console.error('获取使用手册URL失败');
     }
   } catch (error) {
     console.error('获取使用手册失败:', error);
@@ -301,6 +317,14 @@ function loginOut() {
   useUserInfoStore.updateLoginShow(true)
 }
 
+function handleLanguageChange(lang: 'zh-CN' | 'en') {
+  switchLanguage(lang)
+  locale.value = lang
+  currentLang.value = lang
+  currentLangLabel.value = lang === 'zh-CN' ? '中文' : 'EN'
+  updateMenuTypeNames()
+}
+
 onMounted(() => {
   document.addEventListener('click', handleOutsideClick)
 })
@@ -312,11 +336,9 @@ onUnmounted(() => {
 function handleOutsideClick(event: MouseEvent) {
   const menuElement = document.querySelector('.header-bar__menu')
   if (menuElement && !menuElement.contains(event.target as Node)) {
-    // 关闭所有一级菜单
     for (const key in submenuOpen) {
       submenuOpen[key] = false
     }
-    // 关闭所有三级菜单
     for (const key in thirdLevelOpen) {
       thirdLevelOpen[key] = false
     }
@@ -447,6 +469,25 @@ function handleOutsideClick(event: MouseEvent) {
   transition: all 0.2s ease;
 }
 
+.header-bar__button__lang {
+  padding: 0 8px;
+  margin-right: 5px;
+  cursor: pointer;
+  -webkit-app-region: no-drag;
+}
+
+.lang-text {
+  font-size: 12px;
+  color: #2957FF;
+  cursor: pointer;
+  padding: 2px 6px;
+  border-radius: 3px;
+  transition: all 0.2s ease;
+  &:hover {
+    background: #e8f0ff;
+  }
+}
+
 .header-bar__button:hover {
   background-color: #e0e0e0;
 }
@@ -531,4 +572,9 @@ function handleOutsideClick(event: MouseEvent) {
     display: block;
   }
 }
+
+:deep(.el-dropdown-menu__item.is-active) {
+  color: #2957FF;
+  font-weight: 600;
+}
 </style>

+ 26 - 59
frontend/src/components/login/index.vue

@@ -14,15 +14,15 @@
             class="login-box__tabs"
             v-model="activeTab"
         >
-          <el-tab-pane label="账号密码登录" name="0" />
-          <el-tab-pane label="验证码登录" name="1" />
+          <el-tab-pane :label="$t('login.accountTab')" name="0" />
+          <el-tab-pane :label="$t('login.codeTab')" name="1" />
         </el-tabs>
         <el-form
             :model="loginForm"
             label-position="left"
         >
-          <div class="title__main">欢迎!</div>
-          <div class="title_sub">登录智惠映系统</div>
+          <div class="title__main">{{ $t('login.title') }}</div>
+          <div class="title_sub">{{ $t('login.subtitle') }}</div>
 
           <el-form-item class="login-input" prop="username">
             <div class="login-icon">
@@ -53,7 +53,7 @@
               </div>
               <el-input
                   v-model="loginForm.password"
-                  placeholder="请输入密码"
+                  :placeholder="$t('validation.inputPassword')"
                   type="password"
                   style="width: 270px;"
                   show-password
@@ -76,7 +76,7 @@
               </div>
               <el-input
                   v-model="loginForm.code"
-                  placeholder="请输入验证码"
+                  :placeholder="$t('validation.inputCode')"
                   type="text"
                   maxlength="6"
                   tabindex="2"
@@ -97,17 +97,16 @@
           </template>
 
           <el-button class="login-button" type="primary" :loading="loading" @click="handleLogin">
-            登录
+            {{ $t('login.loginBtn') }}
           </el-button>
         </el-form>
       </template>
       <template v-if="pageStatus === 2">
         <div class="company-select-box">
-        <div class="title__main">请选择企业身份</div>
-        <div class="title_sub">您在以下企业均有任职,请问您想代表那个企业进行访问</div>
+        <div class="title__main">{{ $t('login.selectCompany') }}</div>
+        <div class="title_sub">{{ $t('login.selectCompanyTip') }}</div>
 
-
-        <el-select v-model="companyId" class="mar-top-50 w-full" placeholder="请选择">
+        <el-select v-model="companyId" class="mar-top-50 w-full" :placeholder="$t('validation.pleaseSelect')">
           <el-option
               v-for="item, key in company"
               :key="key"
@@ -117,24 +116,11 @@
         </el-select>
 
         <div class="mar-top-50">
-        <el-button class="login-button s" type="primary"  @click="toggleCompany">
-          确认
-        </el-button>
+          <el-button class="login-button s" type="primary" @click="toggleCompany">
+            {{ $t('login.confirm') }}
+          </el-button>
+        </div>
         </div>
-      </div>
-<!--        <div class="bodyCompany">
-          <div class="companyItem" :class="item.is_current == 1?'activeCompany':''" v-for="item, index in company" @click="selectnowCompany(item,index)">
-            <div class="companyHeader">
-              <div class="companyTitle">{{item.abbreviation ? item.abbreviation : item.name}}</div>
-              <div class="companyName">主体信息:{{ item.name }}</div>
-            </div>
-            <div class="selectCompanyBox">
-              <img src="@/assets/images/login/dagou.png" v-if="item.is_current == 1" class="selectIcon"/>
-              <div class="unselect" v-else></div>
-            </div>
-          </div>
-        </div>-->
-
       </template>
     </div>
   </el-dialog>
@@ -149,12 +135,14 @@ export default defineComponent({
 </script>
 <script setup>
 import { ref, reactive, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
 import { ElMessage } from 'element-plus'
 import { getAccountCompany, selectCompany, sendCode } from '@/apis/user';
 import { syncAfterLogin } from "@/apis/setting";
 import useUserInfo from "@/stores/modules/user";
 import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
 import { publicKey } from '@/utils/publickey'
+const { t } = useI18n()
 const showDialog = computed(() => props.dialogVisible)
 
 const props = defineProps({
@@ -177,10 +165,10 @@ const loginForm = reactive({
 })
 
 const codeButtonText = computed(() => {
-  return isCodeSending.value ? `${countdown.value}s后重新获取` : '获取验证码'
+  return isCodeSending.value ? `${countdown.value}${t('login.countdown')}` : t('login.getCode')
 })
 
-const usernamePlaceholder = computed(() => activeTab.value === '1' ? '请输入手机号' : '请输入用户名')
+const usernamePlaceholder = computed(() => activeTab.value === '1' ? t('validation.inputPhone') : t('validation.inputUsername'))
 
 const isValidPhone = (value) => /^1\d{10}$/.test(value)
 
@@ -190,22 +178,22 @@ const handleLogin = async () => {
   const code = loginForm.code.trim()
 
   if (!username) {
-    ElMessage.warning(activeTab.value === '1' ? '请输入手机号' : '请输入用户名')
+    ElMessage.warning(activeTab.value === '1' ? t('validation.inputPhone') : t('validation.inputUsername'))
     return
   }
 
   if (activeTab.value === '0' && !password) {
-    ElMessage.warning('请输入密码')
+    ElMessage.warning(t('validation.inputPassword'))
     return
   }
 
   if (activeTab.value === '1') {
     if (!isValidPhone(username)) {
-      ElMessage.warning('请输入手机号')
+      ElMessage.warning(t('validation.invalidPhone'))
       return
     }
     if (!code) {
-      ElMessage.warning('请输入验证码')
+      ElMessage.warning(t('validation.inputCode'))
       return
     }
   }
@@ -225,23 +213,19 @@ const handleLogin = async () => {
         showCompany()
         break;
       case 40007:
-        ElMessage.error('当前没有所属组织')
+        ElMessage.error(t('login.noOrg'))
         break;
       default:
         await  useUserInfoStore.getInfo()
-        // 登录成功后同步数据
         await syncAfterLogin()
         useUserInfoStore.updateLoginShow(false)
-        // 触发首页重新检查同步状态
         window.dispatchEvent(new CustomEvent('login-success'));
         setTimeout(()=>{
-        //  window.location.reload()
         },100)
           break;
     }
   } catch (error) {
     console.error('登录失败:', error);
-   // ElMessage.error('登录失败,请重试');
   } finally {
     loading.value = false;
   }
@@ -261,17 +245,6 @@ async function  showCompany() {
 
 }
 
-/*function selectnowCompany(item,index){
-  companyId.value = item.id
-  company.value.map((v)=>{
-    v.is_current = 0
-  })
-  company.value[index].is_current = 1
-  toggleCompany()
-}*/
-
-
-// 切换组织
 async function toggleCompany() {
   if (!companyId.value) return false
 
@@ -282,17 +255,14 @@ async function toggleCompany() {
       id: companyId.value
     })
     await  useUserInfoStore.getInfo()
-    // 选择公司后也需要同步数据
     await syncAfterLogin()
     useUserInfoStore.updateLoginShow(false)
-    // 触发首页重新检查同步状态
     window.dispatchEvent(new CustomEvent('login-success'));
     setTimeout(()=>{
-      // window.location.reload()
     },100)
   } catch (error) {
     console.error('切换组织失败:', error);
-    ElMessage.error('切换组织失败,请重试');
+    ElMessage.error(t('login.switchOrg'));
   } finally {
     loading.value = false;
   }
@@ -300,11 +270,11 @@ async function toggleCompany() {
 
 const sendVerificationCode = () => {
   if (!loginForm.username) {
-    ElMessage.warning('请先输入手机号')
+    ElMessage.warning(t('validation.inputPhone'))
     return
   }
   if (!isValidPhone(loginForm.username)) {
-    ElMessage.warning('请输入手机号')
+    ElMessage.warning(t('validation.invalidPhone'))
     return
   }
 
@@ -323,9 +293,6 @@ const sendVerificationCode = () => {
     isCodeSending.value = true
     countdown.value = 60
 
-  // 这里应该调用发送验证码API
-  // await sendCode(loginForm.username)
-
   const timer = setInterval(() => {
     countdown.value--
     if (countdown.value <= 0) {

+ 2 - 0
frontend/src/main.ts

@@ -12,6 +12,7 @@ import { startGenerateServer } from './utils/generateServer'
 import { updateHttpConfig } from './utils/http'
 import client from './stores/modules/client'
 import icpList from './utils/ipc'
+import i18n from './locales'
 
 // 初始化应用配置
 function initializeAppConfig() {
@@ -44,6 +45,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
 }
 app.use(pinia)
 app.use(router)
+app.use(i18n)
 
 // 注册埋点指令和路由监听 - 确保在router使用后立即注册
 lissenLog(app)

+ 22 - 17
frontend/src/router/index.ts

@@ -1,11 +1,6 @@
-
-
 import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from "vue-router";
-
 import { authGuard } from './plugins/authGuard'
-
 import otaRoutes from "./module/ota";
-
 import tpl from './model/tpl'
 
 const routes: RouteRecordRaw[] = [
@@ -17,9 +12,8 @@ const routes: RouteRecordRaw[] = [
         path: "/home",
         name: "home",
         component: () => import("@/views/Home/index.vue"),
-        // component: () => import("@/views/Photography/processImage.vue"),
         meta: {
-            title: '首页',
+            titleKey: 'router.home',
             noAuth: true,
         },
     },
@@ -28,7 +22,7 @@ const routes: RouteRecordRaw[] = [
         name: "setting",
         component: () => import("@/views/Setting/index.vue"),
         meta: {
-            title: '设置'
+            titleKey: 'router.setting'
         }
     },
     {
@@ -36,7 +30,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographyCheck",
         component: () => import("@/views/Photography/check.vue"),
         meta: {
-            title: '拍摄物体镜头矫正'
+            titleKey: 'router.photoCheck'
         }
     },
     {
@@ -44,7 +38,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographyShot",
         component: () => import("@/views/Photography/shot.vue"),
         meta: {
-            title: '拍摄商品'
+            titleKey: 'router.photoShot'
         }
     },
     {
@@ -52,7 +46,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographyProcessImage",
         component: () => import("@/views/Photography/processImage.vue"),
         meta: {
-            title: '处理图像'
+            titleKey: 'router.photoProcess'
         }
     },
     {
@@ -60,7 +54,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographyDetail",
         component: () => import("@/views/Photography/detail.vue"),
         meta: {
-            title: '主图与详情生成'
+            titleKey: 'router.photoDetail'
         }
     },
     {
@@ -68,7 +62,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographySeniorDetail",
         component: () => import("@/views/Photography/seniorDetail.vue"),
         meta: {
-            title: '详情高级配置'
+            titleKey: 'router.photoSeniorDetail'
         }
     },
     {
@@ -76,7 +70,7 @@ const routes: RouteRecordRaw[] = [
         name: "PhotographyExpired",
         component: () => import("@/views/Photography/expired.vue"),
         meta: {
-            title: '企业账户过期',
+            titleKey: 'router.photoExpired',
             noAuth: true
         }
     },
@@ -85,7 +79,7 @@ const routes: RouteRecordRaw[] = [
         name: "RemoteControl",
         component: () => import("@/views/RemoteControl/index.vue"),
         meta: {
-            title: '遥控器'
+            titleKey: 'router.remoteControl'
         }
     },
     {
@@ -93,7 +87,7 @@ const routes: RouteRecordRaw[] = [
         name: "developer",
         component: () => import("@/views/Developer/index.vue"),
         meta: {
-            title: '初始设备调频设置'
+            titleKey: 'router.developer'
         }
     },
     ...tpl,
@@ -101,10 +95,21 @@ const routes: RouteRecordRaw[] = [
 ];
 
 const router = createRouter({
-    history: createWebHashHistory(), // 修改: 将 createWebHistory 改为 createWebHashHistory
+    history: createWebHashHistory(),
     routes
 });
 
+// Update document title when route changes
+router.afterEach((to, from) => {
+    if (to.meta?.titleKey) {
+        const { t } = (window as any).__VUE_I18N__
+            ? { t: (window as any).__VUE_I18N__.__i18nRunner }
+            : { t: (k: string) => k }
+        // Use a simple approach - the header component will handle titles
+        // document.title is set by the header component via $t
+    }
+});
+
 authGuard(router)
 
 export default router;

+ 7 - 6
frontend/src/utils/menus/generate.ts

@@ -4,32 +4,33 @@ import { getRouterUrl } from '@/utils/appfun'
 import client from '@/stores/modules/client'
 import {useRouter} from "vue-router";
 import { getWebUrlrUrl } from '@/utils/appfun'
+import i18n from '@/locales'
 const Router = useRouter()
 
 const clientStore = client()
 const generate =  {
-        name:'高级生成',
+        name: i18n.global.t('generate.advancedGenerate'),
         children:[
             {
-                name:'模特图',
+                name: i18n.global.t('generate.modelImage'),
                 click(configs){
                     console.log(configs);
                     openGaenrate('onFeetImage',configs)
                 },
             },
             {
-                name:'场景图',
+                name: i18n.global.t('generate.sceneImage'),
                 click(configs){
                     openGaenrate('attachScenarios',configs)
                 },
             },
             {
-                name:'生成视频',
+                name: i18n.global.t('generate.generateVideo'),
                 click(configs){
                     openGaenrate('video',configs)
                 },
             },{
-                name:'历史记录',
+                name: i18n.global.t('generate.history'),
                 click(configs){
                     openGaenrate('mine',configs)
                 },
@@ -69,7 +70,7 @@ export function openGaenrate(type,configs) {
 
     clientStore.ipc.removeAllListeners(icpList.utils.openMain)
     let params = {
-        title: '高级生成',
+        title: i18n.global.t('generate.advancedGenerate'),
         width: 1400,
         height: 900,
         frame: true,

+ 5 - 32
frontend/src/views/Developer/cmd.vue

@@ -1,14 +1,9 @@
 <template>
-
-
-  <headerBar
-    title="初始设备调频设置"
-  />
+  <headerBar :title="$t('developer.title')" />
 
   <el-row class="mar-top-10" >
     <el-col :span="2"></el-col>
     <el-col :span="20">
-
       <el-input type="textarea"
       v-model="command"
                 :rows="5"
@@ -19,16 +14,13 @@
 
     <el-row align="middle" justify="middle" class="mar-top-10">
       <el-col :span="24">
-        <el-button type="primary" @click="send_command" v-log="{ describe: { action: '点击RS485发送命令' } }">发送</el-button>
+        <el-button type="primary" @click="send_command" v-log="{ describe: { action: '点击RS485发送命令' } }">{{ $t('developer.sendCmd') }}</el-button>
       </el-col>
     </el-row>
 
-
   <el-row align="middle" justify="middle" class="mar-top-10">
     <el-col :span="2"></el-col>
       <el-col :span="20">
-
-
         <el-input type="textarea"
                   v-model="command1"
                   :rows="5"
@@ -37,29 +29,20 @@
       </el-col>
     <el-col :span="2"></el-col>
     </el-row>
-
 </template>
 
 <script setup lang="ts">
-
-import {ref,reactive,onMounted} from "vue";
-
+import {ref} from "vue";
 import client from "@/stores/modules/client";
 import  icpList from '@/utils/ipc'
 import socket from "@/stores/modules/socket";
-import {ElMessage} from "element-plus";
 const clientStore = client();
 const socketStore = socket()
 
+const command = ref('0x01 0x42 0x6C 0x6b');
+const command1 = ref('');
 
-const command = ref('0x01 0x42 0x6C 0x6b'); // 当前编辑行的数据
-const command1 = ref(''); // 当前编辑行的数据
-
-
-
-//设置 移动 调整
 async function send_command() {
-
   socketStore.sendMessage({
     type: 'send_command',
     data:{
@@ -67,26 +50,16 @@ async function send_command() {
     }
   })
 
-
   clientStore.ipc.on(icpList.socket.message+'_send_command', (event, result) => {
-    console.log('_send_command')
-    console.log(result)
     if(result.code === 0){
       if(result.data.type === 'input')  command.value = result.data.command
       if(result.data.type === 'output'){
         command1.value = result.data.command
         clientStore.ipc.removeAllListeners(icpList.socket.message+'_send_command');
       }
-
     }
   });
-
-
-
 }
-
-
-
 </script>
 
 <style scoped lang="scss">

+ 9 - 30
frontend/src/views/Developer/index.vue

@@ -1,34 +1,26 @@
 <template>
-
-  <headerBar
-    title="初始设备调频设置"
-  />
-
-
+  <headerBar :title="$t('router.developer')" />
   <div class="page">
   <div class="tabs">
-    <div class="tab" @click="handleSelect(1)" :class="{active:activeIndex == 1}" v-log="{ describe: { action: '点击开发者页切换Tab', tab: '设置' } }">设置</div>
-    <div class="tab" @click="handleSelect(2)" :class="{active:activeIndex == 2}" v-log="{ describe: { action: '点击开发者页切换Tab', tab: 'MCU其他配置设置' } }">MCU其他配置设置</div>
-    <div class="tab" @click="handleSelect(3)" :class="{active:activeIndex == 3}" v-log="{ describe: { action: '点击开发者页切换Tab', tab: 'RS485调试发送' } }">RS485调试发送</div>
+    <div class="tab" @click="handleSelect(1)" :class="{active:activeIndex == 1}">{{ $t('developer.tabSetting') }}</div>
+    <div class="tab" @click="handleSelect(2)" :class="{active:activeIndex == 2}">{{ $t('developer.tabMCU') }}</div>
+    <div class="tab" @click="handleSelect(3)" :class="{active:activeIndex == 3}">{{ $t('developer.tabRS485') }}</div>
   </div>
-
-
   <normal v-if="activeIndex == 1"/>
   <mcu v-if="activeIndex == 2"/>
   <cmd v-if="activeIndex == 3"/>
-
   </div>
-
 </template>
 
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import headerBar from '@/components/header-bar/index.vue'
 import normal  from './normal'
 import mcu  from './mcu'
 import cmd  from './cmd'
-
 import { ref } from 'vue'
 
+const { t } = useI18n()
 const activeIndex = ref('1')
 
 const handleSelect = (key) => {
@@ -37,20 +29,7 @@ const handleSelect = (key) => {
 </script>
 
 <style scoped lang="scss">
-.page {
-  min-height: calc(100vh - 30px);
-}
-.tabs {
-  height: 40px;
-  line-height: 40px;
-  position: sticky;
-  top:30px;
-  background: #fff;
-  z-index: 100;
-  .tab {
-    height: 38px;
-    line-height: 40px;
-
-  }
-}
+.page { min-height: calc(100vh - 30px); }
+.tabs { height: 40px; line-height: 40px; position: sticky; top:30px; background: #fff; z-index: 100; }
+.tab { height: 38px; line-height: 40px; cursor: pointer; }
 </style>

+ 6 - 35
frontend/src/views/Developer/mcu.vue

@@ -1,9 +1,5 @@
 <template>
-
-
-  <headerBar
-    title="初始设备调频设置"
-  />
+  <headerBar :title="$t('developer.title')" />
 
   <div class="page">
   <el-row v-for="item,key in editRowData" :key="index" class="mar-top-10">
@@ -18,8 +14,8 @@
 
   <el-row align="middle" justify="middle" class="bottom-wrap">
     <el-col :span="24">
-      <el-button type="primary" @click="get_deviation" v-log="{ describe: { action: '点击读取MCU其他配置' } }">读取配置</el-button>
-      <el-button type="primary" @click="set_deviation" v-log="{ describe: { action: '点击设置MCU其他配置' } }">设置配置</el-button>
+      <el-button type="primary" @click="get_deviation" v-log="{ describe: { action: '点击读取MCU其他配置' } }">{{ $t('developer.readConfig') }}</el-button>
+      <el-button type="primary" @click="set_deviation" v-log="{ describe: { action: '点击设置MCU其他配置' } }">{{ $t('developer.setConfig') }}</el-button>
     </el-col>
   </el-row>
   </div>
@@ -27,9 +23,7 @@
 </template>
 
 <script setup lang="ts">
-
-import {ref,reactive,onMounted} from "vue";
-
+import {ref, onMounted} from "vue";
 import client from "@/stores/modules/client";
 import  icpList from '@/utils/ipc'
 import socket from "@/stores/modules/socket";
@@ -37,15 +31,12 @@ import {ElMessage} from "element-plus";
 const clientStore = client();
 const socketStore = socket()
 
-
-const editRowData = ref({
-}); // 当前编辑行的数据
+const editRowData = ref({});
 
 onMounted(()=>{
   get_deviation()
 })
 
-//获取配置
 async function  get_deviation(){
   if(clientStore.isClient){
     socketStore.sendMessage({
@@ -53,13 +44,9 @@ async function  get_deviation(){
       data:"get_mcu_other_info"
     })
 
-
     clientStore.ipc.on(icpList.socket.message+'_get_mcu_other_info', (event, result) => {
-      console.log('_get_mcu_other_info')
-      console.log(result)
       if(result.code === 0){
         editRowData.value =  result.data
-
       }else if(result.msg){
         ElMessage.error(result.msg)
       }
@@ -68,12 +55,7 @@ async function  get_deviation(){
   }
 }
 
-
-
-
-//设置 移动 调整
-async function set_deviation(action_name, type, key, min, max) {
-
+async function set_deviation() {
   socketStore.sendMessage({
     type: 'set_mcu_other_info',
     data:{
@@ -81,24 +63,13 @@ async function set_deviation(action_name, type, key, min, max) {
     }
   })
 
-
   clientStore.ipc.on(icpList.socket.message+'_set_mcu_other_info', (event, result) => {
-    console.log('_set_mcu_other_info')
-    console.log(result)
     if(result.code === 0){
-
       ElMessage.success('设置成功')
-
     }
     clientStore.ipc.removeAllListeners(icpList.socket.message+'_set_mcu_other_info');
   });
-
-
-
 }
-
-
-
 </script>
 
 <style scoped lang="scss">

+ 58 - 100
frontend/src/views/Developer/normal.vue

@@ -1,138 +1,124 @@
 <template>
-
-
-  <headerBar
-    title="初始设备调频设置"
-  />
+  <headerBar :title="$t('developer.title')" />
 
   <div class="page">
   <el-row>
-    <el-col :span="24"><h3>相机设置</h3></el-col>
+    <el-col :span="24"><h3>{{ $t('developer.cameraSetting') }}</h3></el-col>
   </el-row>
   <el-row>
-    <el-col :span="6">相机高度mm:</el-col>
+    <el-col :span="6">{{ $t('developer.cameraHeight') }}</el-col>
     <el-col :span="12"><el-input
-        @change="changeNum('相机电机','move_deviation','camera_high_motor_deviation',0, 50)"
+        @change="changeNum($t('developer.cameraHeight'),'move_deviation','camera_high_motor_deviation',0, 50)"
         :min="0" :max="50"
         :step="1"
         v-model="editRowData.camera_high_motor_deviation" type="number"/>
-         <div class="error-msg">最小0,最大50</div>
+         <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 50 }) }}</div>
     </el-col>
     <el-col :span="6"><el-button
-        @click="changeNum('相机电机','set_deviation','camera_high_motor_deviation',0, 50)"
-    >设定</el-button></el-col>
+        @click="changeNum($t('developer.cameraHeight'),'set_deviation','camera_high_motor_deviation',0, 50)"
+    >{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
   <el-row class="mar-top-10">
-    <el-col :span="6">相机角度(度):</el-col>
+    <el-col :span="6">{{ $t('developer.cameraAngle') }}</el-col>
 
     <el-col :span="12"><el-input
-        @change="changeNum('相机舵机','move_deviation','camera_steering_deviation',-90, 90)"
+        @change="changeNum($t('developer.cameraAngle'),'move_deviation','camera_steering_deviation',-90, 90)"
         :min="-90" :max="90"
         :step="0.1"
         v-model="editRowData.camera_steering_deviation" type="number"/>
-      <div class="error-msg">最小-90,最大90</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: -90, max: 90 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button @click="changeNum('相机舵机','set_deviation','camera_steering_deviation',-90, 90)">设定</el-button></el-col>
+    <el-col :span="6"><el-button @click="changeNum($t('developer.cameraAngle'),'set_deviation','camera_steering_deviation',-90, 90)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
 
-
-
   <el-row>
-    <el-col :span="24"><h3>转盘设置</h3></el-col>
+    <el-col :span="24"><h3>{{ $t('developer.turntableSetting') }}</h3></el-col>
   </el-row>
   <el-row>
-    <el-col :span="6">角度偏移 (度):</el-col>
+    <el-col :span="6">{{ $t('developer.angleOffset') }}</el-col>
     <el-col :span="12"><el-input
-        @change="changeNum('转盘舵机','move_deviation','turntable_steering_deviation',-720, 720)"
+        @change="changeNum($t('developer.angleOffset'),'move_deviation','turntable_steering_deviation',-720, 720)"
         :min="-720" :max="720"
         :step="1"
         v-model="editRowData.turntable_steering_deviation" type="number"/>
-      <div class="error-msg">最小-720,最大720</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: -720, max: 720 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button  @click="changeNum('转盘舵机','set_deviation','turntable_steering_deviation',-720, 720)">设定</el-button></el-col>
+    <el-col :span="6"><el-button  @click="changeNum($t('developer.angleOffset'),'set_deviation','turntable_steering_deviation',-720, 720)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
   <el-row class="mar-top-10">
-    <el-col :span="6">前后偏移:</el-col>
+    <el-col :span="6">{{ $t('developer.frontBackOffset') }}</el-col>
 
     <el-col :span="12"><el-input
-        @change="changeNum('转盘前后电机','move_deviation','turntable_front_end_deviation',0, 950)"
+        @change="changeNum($t('developer.frontBackOffset'),'move_deviation','turntable_front_end_deviation',0, 950)"
         :min="0" :max="950"
         :step="1"
         v-model="editRowData.turntable_front_end_deviation" type="number"/>
-      <div class="error-msg">最小0,最大950</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 950 }) }}</div>
     </el-col>
-    <el-col :span="6"><!--<el-button @click="changeNum('转盘前后电机','set_deviation','turntable_front_end_deviation',0, 950)">设定</el-button>--></el-col>
+    <el-col :span="6"><!--<el-button @click="changeNum('转盘前后电机','set_deviation','turntable_front_end_deviation',0, 950)">{{ $t('developer.setValue') }}</el-button>--></el-col>
   </el-row>
 
-
-
   <el-row>
-    <el-col :span="24"><h3>翻版舵机</h3></el-col>
+    <el-col :span="24"><h3>{{ $t('developer.flipServo') }}</h3></el-col>
   </el-row>
   <el-row>
-    <el-col :span="6">中位:</el-col>
+    <el-col :span="6">{{ $t('developer.middlePos') }}</el-col>
 
     <el-col :span="12"><el-input
-        @change="changeNum('翻板舵机中位','move_deviation','overturn_steering_middle',0, 180)"
+        @change="changeNum($t('developer.middlePos'),'move_deviation','overturn_steering_middle',0, 180)"
         :min="0" :max="180"
         :step="0.5"
         v-model="editRowData.overturn_steering_middle" type="number"/>
-      <div class="error-msg">最小0,最大180</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 180 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button @click="changeNum('翻板舵机中位','set_deviation','overturn_steering_middle',0, 180)">设定</el-button></el-col>
+    <el-col :span="6"><el-button @click="changeNum($t('developer.middlePos'),'set_deviation','overturn_steering_middle',0, 180)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
   <el-row class="mar-top-10">
-    <el-col :span="6">高位:</el-col>
+    <el-col :span="6">{{ $t('developer.highPos') }}</el-col>
     <el-col :span="12"><el-input
-        @change="changeNum('翻板舵机高位','move_deviation','overturn_steering_high',0, 180)"
+        @change="changeNum($t('developer.highPos'),'move_deviation','overturn_steering_high',0, 180)"
         :min="0" :max="180"
         :step="0.5"
         v-model="editRowData.overturn_steering_high" type="number"/>
-      <div class="error-msg">最小0,最大180</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 180 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button  @click="changeNum('翻板舵机高位','set_deviation','overturn_steering_high',0, 180)">设定</el-button></el-col>
+    <el-col :span="6"><el-button  @click="changeNum($t('developer.highPos'),'set_deviation','overturn_steering_high',0, 180)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
   <el-row class="mar-top-10">
-    <el-col :span="6">上升速度:</el-col>
+    <el-col :span="6">{{ $t('developer.riseSpeed') }}</el-col>
     <el-col :span="12"><el-input
-        @change="changeNum('翻板舵机上升速度','move_deviation','overturn_steering_up_speed',0, 10)"
+        @change="changeNum($t('developer.riseSpeed'),'move_deviation','overturn_steering_up_speed',0, 10)"
         :min="0" :max="10"
         :step="1"
         v-model="editRowData.overturn_steering_up_speed" type="number"/>
-      <div class="error-msg">最小0,最大10</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 10 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button @click="changeNum('翻板舵机上升速度','set_deviation','overturn_steering_up_speed',0, 10)">设定</el-button></el-col>
+    <el-col :span="6"><el-button @click="changeNum($t('developer.riseSpeed'),'set_deviation','overturn_steering_up_speed',0, 10)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
   <el-row class="mar-top-10">
-    <el-col :span="6">下降速度:</el-col>
+    <el-col :span="6">{{ $t('developer.fallSpeed') }}</el-col>
     <el-col :span="12"><el-input
-        @change="changeNum('翻板舵机下降速度','move_deviation','overturn_steering_down_speed',0, 10)"
+        @change="changeNum($t('developer.fallSpeed'),'move_deviation','overturn_steering_down_speed',0, 10)"
         :min="0" :max="10"
         :step="1"
         v-model="editRowData.overturn_steering_down_speed" type="number"/>
-      <div class="error-msg">最小0,最大10</div>
+      <div class="error-msg">{{ $t('developer.minMaxTip', { min: 0, max: 10 }) }}</div>
     </el-col>
-    <el-col :span="6"><el-button  @click="changeNum('翻板舵机下降速度','set_deviation','overturn_steering_down_speed',0, 10)">设定</el-button></el-col>
+    <el-col :span="6"><el-button  @click="changeNum($t('developer.fallSpeed'),'set_deviation','overturn_steering_down_speed',0, 10)">{{ $t('developer.setValue') }}</el-button></el-col>
   </el-row>
 
-
   <el-row align="middle" justify="middle" class="mar-top-20 bottom-wrap">
     <el-col :span="24">
-      <el-button type="primary" @click="connect_mcu__init">设备初始化</el-button>
-      <el-button type="primary" @click="get_deviation">读取偏移量并运行</el-button>
-      <el-button type="primary" @click="AllChangeNum">整体设定</el-button>
+      <el-button type="primary" @click="connect_mcu__init">{{ $t('developer.deviceInit') }}</el-button>
+      <el-button type="primary" @click="get_deviation">{{ $t('developer.readOffset') }}</el-button>
+      <el-button type="primary" @click="AllChangeNum">{{ $t('developer.allSet') }}</el-button>
     </el-col>
   </el-row>
   </div>
-
-
-
 </template>
 
 <script setup lang="ts">
-
-import {ref,reactive,onMounted} from "vue";
-
+import {ref, onMounted} from "vue";
 import client from "@/stores/modules/client";
 import  icpList from '@/utils/ipc'
 import socket from "@/stores/modules/socket";
@@ -140,7 +126,6 @@ import {ElMessage} from "element-plus";
 const clientStore = client();
 const socketStore = socket()
 
-
 const status = ref(0);
 const editRowData = ref({
   "camera_high_motor_deviation": '',
@@ -151,13 +136,12 @@ const editRowData = ref({
   "overturn_steering_high": '',
   "overturn_steering_up_speed": '',
   "overturn_steering_down_speed": ''
-}); // 当前编辑行的数据
+});
 
 onMounted(()=>{
   get_deviation()
 })
 
-//获取配置
 async function  get_deviation(){
   if(clientStore.isClient){
     socketStore.sendMessage({
@@ -165,32 +149,23 @@ async function  get_deviation(){
       data:"get_deviation"
     })
 
-
     clientStore.ipc.on(icpList.socket.message+'_get_deviation_data', (event, result) => {
-      console.log('_get_deviation_data')
-      console.log(result)
       if(result.code === 0){
         status.value = true;
         editRowData.value.camera_high_motor_deviation = result.data.camera_high_motor_deviation
         editRowData.value.camera_steering_deviation = result.data.camera_steering_deviation
         editRowData.value.turntable_steering_deviation = result.data.turntable_steering_deviation
         editRowData.value.turntable_front_end_deviation = result.data.turntable_front_end_deviation
-
         editRowData.value.overturn_steering_middle = result.data.overturn_steering_middle
         editRowData.value.overturn_steering_high = result.data.overturn_steering_high
         editRowData.value.overturn_steering_up_speed = result.data.overturn_steering_up_speed
         editRowData.value.overturn_steering_down_speed = result.data.overturn_steering_down_speed
 
-
-
-        changeNum('相机电机','move_deviation','camera_high_motor_deviation',0, 50)
-        changeNum('相机舵机','move_deviation','camera_steering_deviation',-90, 90)
-        changeNum('转盘舵机','move_deviation','turntable_steering_deviation',-720, 720)
-        changeNum('转盘前后电机','move_deviation','turntable_front_end_deviation',0, 950)
-        changeNum('翻板舵机中位','move_deviation','overturn_steering_middle',0, 180)
-    //    changeNum('翻板舵机高位','move_deviation','overturn_steering_high',0, 180)
-    //    changeNum('翻板舵机上升速度','move_deviation','overturn_steering_up_speed',0, 10)
-     //   changeNum('翻板舵机下降速度','move_deviation','overturn_steering_down_speed',0, 10)
+        changeNum('Camera Motor','move_deviation','camera_high_motor_deviation',0, 50)
+        changeNum('Camera Servo','move_deviation','camera_steering_deviation',-90, 90)
+        changeNum('Turntable Servo','move_deviation','turntable_steering_deviation',-720, 720)
+        changeNum('Turntable Motor','move_deviation','turntable_front_end_deviation',0, 950)
+        changeNum('Flip Servo Mid','move_deviation','overturn_steering_middle',0, 180)
       }else if(result.msg){
         ElMessage.error(result.msg)
       }
@@ -199,17 +174,11 @@ async function  get_deviation(){
   }
 }
 
-
-
-//MCU初始化
 async function  connect_mcu__init(){
   if(clientStore.isClient){
-
     socketStore.sendMessage({
       type: 'init_mcu',
-      data:{
-        value:true
-      }
+      data:{ value:true }
     })
   }
 }
@@ -217,20 +186,16 @@ async function  connect_mcu__init(){
 const allTipsCount = ref(0)
 async  function  AllChangeNum (){
   allTipsCount.value = 7
-  changeNum('相机电机','set_deviation','camera_high_motor_deviation',0, 50)
-  changeNum('相机舵机','set_deviation','camera_steering_deviation',-90, 90)
-  changeNum('转盘舵机','set_deviation','turntable_steering_deviation',-720, 720)
-//  changeNum('转盘前后电机','set_deviation','turntable_front_end_deviation',0, 950)
-  changeNum('翻板舵机中位','set_deviation','overturn_steering_middle',0, 180)
-  changeNum('翻板舵机高位','set_deviation','overturn_steering_high',0, 180)
-  changeNum('翻板舵机上升速度','set_deviation','overturn_steering_up_speed',0, 10)
-  changeNum('翻板舵机下降速度','set_deviation','overturn_steering_down_speed',0, 10)
-
+  changeNum('Camera Motor','set_deviation','camera_high_motor_deviation',0, 50)
+  changeNum('Camera Servo','set_deviation','camera_steering_deviation',-90, 90)
+  changeNum('Turntable Servo','set_deviation','turntable_steering_deviation',-720, 720)
+  changeNum('Flip Servo Mid','set_deviation','overturn_steering_middle',0, 180)
+  changeNum('Flip Servo High','set_deviation','overturn_steering_high',0, 180)
+  changeNum('Flip Servo Rise','set_deviation','overturn_steering_up_speed',0, 10)
+  changeNum('Flip Servo Fall','set_deviation','overturn_steering_down_speed',0, 10)
 }
 
-
-//设置 移动 调整
-async function changeNum(action_name, type, key, min, max) {
+async function changeNum(action_name: string, type: string, key: string, min: number, max: number) {
   if(!status.value){
     ElMessage.error('请先获取设备参数');
     return;
@@ -242,7 +207,7 @@ async function changeNum(action_name, type, key, min, max) {
       }else{
         editRowData.value[key] = max;
       }
-      ElMessage.error(`${action_name}值应在${min}到${max}之间`);
+      ElMessage.error(`${action_name}${$t('developer.valueRangeTip', { name: '', min, max })}`);
     }
   }
   socketStore.sendMessage({
@@ -255,16 +220,13 @@ async function changeNum(action_name, type, key, min, max) {
 
   clientStore.ipc.removeAllListeners(icpList.socket.message+'_set_deviation');
   clientStore.ipc.on(icpList.socket.message+'_set_deviation', (event, result) => {
-    console.log('set_deviation')
-    console.log(result)
     if(result.code === 0){
       if(result.msg === "相机舵机 设置成功"){
-
         editRowData.value.camera_steering_deviation = 0
       }
       if(allTipsCount.value === 1){
         allTipsCount.value--;
-        ElMessage.success('设定成功')
+        ElMessage.success($t('developer.setSuccess'))
         return;
       }
       if(allTipsCount.value > 0){
@@ -277,11 +239,7 @@ async function changeNum(action_name, type, key, min, max) {
     }
     clientStore.ipc.removeAllListeners(icpList.socket.message+'_get_deviation_data');
   });
-
 }
-
-
-
 </script>
 
 <style scoped lang="scss">

+ 59 - 187
frontend/src/views/Home/index.vue

@@ -1,48 +1,40 @@
 <template>
-  <headerBar title="首页">
-
-    <template  #title><div @click="handleSettingClick" v-log="{ describe: { action: '点击首页标题' } }">首页</div></template>
+  <headerBar :title="$t('router.home')">
+    <template  #title><div @click="handleSettingClick" v-log="{ describe: { action: $t('home.openResource') } }">{{ $t('router.home') }}</div></template>
   </headerBar>
   <div
       class="home-container"
       v-loading="loading || !healthReady || !syncCompleted"
       :element-loading-text="loadingText"
   >
-    <!-- 背景图片 -->
-    <img src="@/assets/images/home/bg.png" alt="背景图片" class="background-image" />
-
-    <!-- 左侧图片区域 -->
-    <div class="image-container left-image" @click="goCheck" v-log="{ describe: { action: '点击拍照检查入口' } }">
-      <img src="@/assets/images/home/left.png" alt="拍摄产品并处理图像" class="zoom-on-hover" />
-      <div class="overlay-text">拍摄产品<br>并处理图像</div>
+    <img src="@/assets/images/home/bg.png" alt="background" class="background-image" />
+    <div class="image-container left-image" @click="goCheck" v-log="{ describe: { action: 'click entry: shoot and process' } }">
+      <img src="@/assets/images/home/left.png" alt="shoot and process" class="zoom-on-hover" />
+      <div class="overlay-text">{{ $t('home.shootAndProcess') }}</div>
     </div>
-
-    <!-- 右侧图片区域 -->
-    <div class="image-container right-image"  @click="goShot" v-log="{ describe: { action: '点击仅处理图像入口' } }">
-      <img src="@/assets/images/home/right.png" alt="仅处理图像" class="zoom-on-hover" />
-      <div class="overlay-text" style="line-height: 80px;">仅处理图像</div>
+    <div class="image-container right-image"  @click="goShot" v-log="{ describe: { action: 'click entry: process only' } }">
+      <img src="@/assets/images/home/right.png" alt="process only" class="zoom-on-hover" />
+      <div class="overlay-text" style="line-height: 80px;">{{ $t('home.processOnly') }}</div>
     </div>
-
-    <!-- 版本公告对话框 -->
     <el-dialog
       v-model="showAnnouncement"
-      title="版本公告"
+      :title="$t('home.versionAnnouncement')"
       width="600px"
       :close-on-click-modal="false"
       :show-close="false"
     >
       <div class="announcement-content te-l">
         <div class="version-info">
-          <p><strong>当前版本:</strong>{{ announcementData?.version }}</p>
+          <p><strong>{{ $t('ota.currentVersion') }}</strong>{{ announcementData?.version }}</p>
         </div>
         <div class="announcement-detail" v-if="announcementData?.detail" v-html="announcementData.detail"></div>
         <div class="announcement-detail" v-else>
-          <p>暂无详细公告内容</p>
+          <p>{{ $t('home.noAnnouncement') }}</p>
         </div>
       </div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="handleAnnouncementConfirm">知道了</el-button>
+          <el-button type="primary" @click="handleAnnouncementConfirm">{{ $t('common.yesGetIt') }}</el-button>
         </div>
       </template>
     </el-dialog>
@@ -62,40 +54,37 @@ import { getRouterUrl } from '@/utils/appfun';
 import useUserInfo from "@/stores/modules/user";
 import tokenInfo from "@/stores/modules/token";
 import { getVersionByRoleType,getVersionDetailByVersion } from '@/apis/other'
+import { useI18n } from 'vue-i18n';
+import { ElMessage } from 'element-plus';
 
+const { t } = useI18n()
 const router = useRouter();
 const loading = ref(true);
-const healthReady = ref(false); // 程序是否已完成自检
-const syncLoading = ref(false); // 同步配置的loading状态
-const syncCompleted = ref(false); // 同步是否完成
+const healthReady = ref(false);
+const syncLoading = ref(false);
+const syncCompleted = ref(false);
 const loadingText = computed(() => {
   if (!healthReady.value) {
-    return '程序启动中...';
+    return t('home.loadingProgram');
   }
   if (!syncCompleted.value) {
-    return '正在同步配置...';
+    return t('home.loadingSync');
   }
-  return '正在加载...';
+  return t('home.loadingGeneral');
 });
 
-// 用户状态管理 - 在 onMounted 中初始化
 let configInfoStore: any;
 let useUserInfoStore: any;
 let tokenInfoStore: any;
 
-// 版本检查相关
 const currentVersion = ref(packageJson.version);
 const latestVersion = ref('');
 const isLatest = ref(true);
 
-// 版本公告相关
 const showAnnouncement = ref(false);
 const announcementData = ref(null);
 
-
 import socket from "@/stores/modules/socket";
-import {ElMessage} from "element-plus";
-// 初始化 WebSocket 状态管理
 const socketStore = socket();
 
 function socketConnect(){
@@ -103,155 +92,109 @@ function socketConnect(){
 }
 
 const goCheck = async () => {
-  // 检查登录状态
   if (!tokenInfoStore.getToken) {
     useUserInfoStore.updateLoginShow(true);
     return;
   }
-
-  // 如果正在同步,显示提示
   if (syncLoading.value) {
-    console.log('正在同步配置,请稍候...');
     return;
   }
-
-  // 如果未同步完成,等待同步
   if (!syncCompleted.value) {
-    ElMessage.error('等待配置同步完成');
+    ElMessage.error(t('home.waitSync'));
     return;
   }
-
   configInfoStore.updateAppModel(1);
-  router.push({
-    name: 'PhotographyCheck'
-  });
+  router.push({ name: 'PhotographyCheck' });
 };
 
 const goShot = async () => {
-  // 检查登录状态
   if (!tokenInfoStore.getToken) {
     useUserInfoStore.updateLoginShow(true);
     return;
   }
-
-  // 如果正在同步,显示提示
   if (syncLoading.value) {
-    console.log('正在同步配置,请稍候...');
     return;
   }
-
-  // 如果未同步完成,等待同步
   if (!syncCompleted.value) {
-    console.log('等待配置同步完成...');
     return;
   }
-
   socketConnect();
   configInfoStore.updateAppModel(2);
-  router.push({
-    name: 'PhotographyProcessImage'
-  });
+  router.push({ name: 'PhotographyProcessImage' });
 };
 
-// 健康检查函数
 const checkHealth = async () => {
   loading.value = false;
   try {
     const healthUrl = configInfoStore?.appConfig?.pyapp ? 'http://'+configInfoStore?.appConfig?.pyapp+':7074' :  'http://127.0.0.1:7074'
     const response = await axios.get(healthUrl);
     if (response.status === 200) {
-      loading.value = false; // 健康检查成功,关闭 loading
+      loading.value = false;
       healthReady.value = true;
-
-      // 健康检查成功后,如果用户已登录则执行数据同步
       if (tokenInfoStore && tokenInfoStore.getToken) {
         const token = tokenInfoStore.getToken;
         if (token && token.trim() !== '') {
           try {
-            syncLoading.value = true; // 开始同步
-            syncCompleted.value = false; // 重置同步状态
-
-            // 导入同步函数
+            syncLoading.value = true;
+            syncCompleted.value = false;
             const { syncAfterLogin } = await import('@/apis/setting');
             await syncAfterLogin();
-            console.log('健康检查后数据同步成功');
-
-            syncCompleted.value = true; // 同步完成
+            syncCompleted.value = true;
           } catch (syncError) {
-            console.error('健康检查后数据同步失败:', syncError);
-            syncCompleted.value = false; // 同步失败
-            // 同步失败不影响主流程
+            syncCompleted.value = false;
           } finally {
-            syncLoading.value = false; // 结束同步loading
+            syncLoading.value = false;
           }
         } else {
-          // 未登录状态,直接设置同步完成
           syncCompleted.value = true;
         }
       } else {
-        // 未登录状态,直接设置同步完成
         syncCompleted.value = true;
       }
     }
   } catch (error) {
-    console.error('健康检查失败:', error);
     healthReady.value = false;
-    setTimeout(() => {
-      checkHealth(); // 延迟检查
-    }, 2000);
-    // 可以在这里处理错误,例如显示错误提示
+    setTimeout(() => { checkHealth(); }, 2000);
   }
 };
 
 const settingClickCount = ref(0);
-// 修改headerBar的点击处理函数
 function handleSettingClick() {
-  console.log('handleSettingClickhandleSettingClick')
   settingClickCount.value++;
-
   if (settingClickCount.value >= 5) {
     openResourceDirectory()
     settingClickCount.value = 0;
   }
-
-  setTimeout(() => {
-    settingClickCount.value = 0;
-  }, 3000); // 3秒内未再次点击则重置计数器
+  setTimeout(() => { settingClickCount.value = 0; }, 3000);
 }
 
 function openResourceDirectory() {
-  const clientStore = client();
-  clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
+  const cs = client();
+  cs.ipc.removeAllListeners(icpList.utils.shellFun);
   let params = {
     action: 'openPath',
     params: configInfoStore.appConfig.userDataPath.replaceAll('/', '\\')
   };
-  clientStore.ipc.send(icpList.utils.shellFun, params);
+  cs.ipc.send(icpList.utils.shellFun, params);
 }
 
-// 版本号比较函数
 const compareVersions = (v1, v2) => {
   const parts1 = v1.split('.').map(Number);
   const parts2 = v2.split('.').map(Number);
-
   for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
     const num1 = parts1[i] || 0;
     const num2 = parts2[i] || 0;
-
     if (num1 > num2) return 1;
     if (num1 < num2) return -1;
   }
-
   return 0;
 };
 
-// 本地缓存相关函数
 const getAnnouncedVersions = () => {
   try {
     const announced = localStorage.getItem('announcedVersions');
     return announced ? JSON.parse(announced) : [];
-  } catch (error) {
-    console.error('获取已公告版本失败:', error);
+  } catch {
     return [];
   }
 };
@@ -263,143 +206,85 @@ const setAnnouncedVersion = (version) => {
       announced.push(version);
       localStorage.setItem('announcedVersions', JSON.stringify(announced));
     }
-  } catch (error) {
-    console.error('设置已公告版本失败:', error);
-  }
+  } catch {}
 };
 
 const isVersionAnnounced = (version) => {
-  const announced = getAnnouncedVersions();
-  return announced.includes(version);
+  return getAnnouncedVersions().includes(version);
 };
 
-// 处理知道了按钮点击
 const handleAnnouncementConfirm = () => {
   if (announcementData.value) {
     setAnnouncedVersion(announcementData.value.version);
     showAnnouncement.value = false;
-
-    // 公告确认后,检查是否有版本更新
-    setTimeout(() => {
-      checkForUpdates();
-    }, 500);
+    setTimeout(() => { checkForUpdates(); }, 500);
   }
 };
 
-// 打开OTA窗口
 const openOTA = () => {
-  const { href } = router.resolve({
-    name: 'ota'
-  });
-
-  const clientStore = client();
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  const { href } = router.resolve({ name: 'ota' });
+  const cs = client();
+  cs.ipc.removeAllListeners(icpList.utils.openMain);
   let params = {
-    title: '版本更新',
+    title: t('router.ota'),
     width: 900,
     height: 700,
     frame: true,
     id: 'ota',
     url: getRouterUrl(href)
   };
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  cs.ipc.send(icpList.utils.openMain, params)
 };
 
-// 检查当前版本的更新公告
 const checkCurrentVersionAnnouncement = async () => {
   try {
-
-    console.log(
-        {
-          role_type: 6,
-          version: currentVersion.value
-        })
-    console.log('aaaaa')
-    const { data } = await getVersionDetailByVersion({
-      role_type: 6,
-      version: currentVersion.value
-    } as any);
-
-    // 如果有当前版本的公告数据且未查看过,则显示公告
+    const { data } = await getVersionDetailByVersion({ role_type: 6, version: currentVersion.value } as any);
     if (data && data.id && !isVersionAnnounced(currentVersion.value)) {
-      announcementData.value = {
-        ...data,
-        version: currentVersion.value
-      };
+      announcementData.value = { ...data, version: currentVersion.value };
       showAnnouncement.value = true;
-      return true; // 返回true表示有公告需要显示
+      return true;
     }
-    return false; // 没有公告或已查看过
-  } catch (error) {
-    console.error('检查当前版本公告失败:', error);
     return false;
-  }
+  } catch { return false; }
 };
 
-// 获取最新版本信息并检查更新
 const checkForUpdates = async () => {
   try {
     const { data } = await getVersionByRoleType({ role_type: 6 });
-
     if (data && data.length > 0) {
       const latest = data[0];
       latestVersion.value = latest.version;
-      // 比较版本号
       isLatest.value = compareVersions(currentVersion.value, latest.version) >= 0;
-
-      // 如果发现新版本,自动打开OTA窗口
       if (!isLatest.value) {
         openOTA();
       }
     }
-  } catch (error) {
-    console.error('检查版本更新失败:', error);
-    // 静默处理错误,不影响用户体验
-  }
+  } catch {}
 };
 
-
-
-
-
-
-// 监听登录成功事件
 const handleLoginSuccess = () => {
-  console.log('检测到登录成功,重新检查同步状态');
-  // 重新执行健康检查和同步
   checkHealth();
 };
 
-// 在组件挂载时执行健康检查和版本检查
 onMounted(() => {
-  // 初始化 store
   configInfoStore = configInfo();
   useUserInfoStore = useUserInfo();
   tokenInfoStore = tokenInfo();
-
-  // 监听登录成功事件
   window.addEventListener('login-success', handleLoginSuccess);
-
   checkHealth();
-  // 延迟执行版本检查,避免影响健康检查
   setTimeout(async () => {
-    // 首先检查当前版本的公告
     const hasAnnouncement = await checkCurrentVersionAnnouncement();
-
-    // 如果没有公告需要显示,或者公告已确认,再检查版本更新
     if (!hasAnnouncement) {
       checkForUpdates();
     }
   }, 1000);
 });
 
-// 组件卸载时清理事件监听器
 onUnmounted(() => {
   window.removeEventListener('login-success', handleLoginSuccess);
 });
 </script>
 
-
 <style lang="scss" scoped>
 .home-container {
   position: relative;
@@ -421,24 +306,22 @@ onUnmounted(() => {
 .image-container {
   position: absolute;
   cursor: pointer;
-  width: 400px; /* 设置宽度 */
+  width: 400px;
   overflow: hidden;
-  // box-shadow: 0 4px 10px rgba(0, 0, 0, 0.6); /* 添加阴影效果 */
   border-radius: 30px;
   transition: transform 0.3s ease;
 
   .zoom-on-hover {
     transition: transform 0.3s ease;
-    width: 100%; /* 确保图片充满容器 */
-    height: 100%; /* 确保图片充满容器 */
-    object-fit: cover; /* 裁剪图片以适应容器 */
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
     display: block;
   }
 
   &:hover {
     box-shadow: 0 4px 10px rgba(0, 0, 0, 0.6);
-    transform: translateY(-55%);
-    transform:translateY(-55%) scale(1.05);
+    transform: translateY(-55%) scale(1.05);
   }
 }
 
@@ -470,6 +353,7 @@ onUnmounted(() => {
   line-height: 50px;
   min-height: 80px;
   min-width: 250px;
+  white-space: pre-line;
 }
 
 .announcement-content {
@@ -479,31 +363,19 @@ onUnmounted(() => {
     background-color: #f0f9ff;
     border: 1px solid #0ea5e9;
     border-radius: 8px;
-
     p {
       margin: 5px 0;
       font-size: 14px;
       color: #0c4a6e;
     }
   }
-
   .announcement-detail {
     max-height: 300px;
     overflow-y: auto;
     line-height: 1.6;
-
-    :deep(p) {
-      margin: 10px 0;
-    }
-
-    :deep(ul), :deep(ol) {
-      margin: 10px 0;
-      padding-left: 20px;
-    }
-
-    :deep(li) {
-      margin: 5px 0;
-    }
+    :deep(p) { margin: 10px 0; }
+    :deep(ul), :deep(ol) { margin: 10px 0; padding-left: 20px; }
+    :deep(li) { margin: 5px 0; }
   }
 }
 

+ 72 - 191
frontend/src/views/OTA/index.vue

@@ -1,246 +1,127 @@
+<template>
+  <el-container>
+    <el-dialog v-model="updateVisible" :close-on-click-modal="false" :title="$t('header.softwareDownload')">
+        <div class="desc line-30 fs-16">{{updateResult.desc}}</div>
+    </el-dialog>
+
+    <el-dialog v-model="detailVisible" :title="$t('ota.versionDetail')" width="600px">
+        <div v-html="currentVersionDetail" class="version-detail-content te-l"></div>
+    </el-dialog>
+
+    <el-main>
+      <div class="version-check-container">
+        <el-card class="current-version-card fs-14 te-l">
+          <p>{{ $t('ota.currentVersion') }}<span class="fs-14"> {{ currentVersion }}</span></p>
+          <div class="flex left">
+            <p >{{ $t('ota.status') }} <span :class="{ 'text-green': isLatest, 'text-red': !isLatest }">{{ isLatest ? $t('ota.latest') : $t('ota.newVersion') }}</span></p>
+            <p class="mar-left-10" v-if="!isLatest"> {{ latest.version }}</p>
+            <el-button v-if="!isLatest" class="mar-left-20" type="primary" @click="downloadUpdate">{{ $t('ota.downloadUpdate') }}</el-button>
+          </div>
+          <p>{{ $t('ota.changelog') }}</p>
+          <div v-html="latest.detail"></div>
+        </el-card>
+
+        <el-card class="history-versions-card mar-top-10">
+          <h3>{{ $t('ota.historyVersions') }}</h3>
+          <el-table :data="paginatedVersions" border>
+            <el-table-column prop="version" :label="$t('ota.versionNumber')" width="70"></el-table-column>
+            <el-table-column prop="release_date" :label="$t('ota.releaseDate')" width="100">
+              <template #default="{ row }">{{row.release_date.substr(0,10)}}</template>
+            </el-table-column>
+            <el-table-column :label="$t('ota.description')">
+              <template #default="{ row }">{{row.overview}}</template>
+            </el-table-column>
+            <el-table-column :label="$t('ota.operation')" width="180">
+              <template #default="{ row }">
+                  <el-button style="width: 70px;" size="small" @click="downloadSpecificVersion(row.attachment)">{{ $t('common.download') }}</el-button>
+                  <el-button style="width: 70px;"  size="small" type="info" @click="showDetail(row)">{{ $t('ota.versionDetail') }}</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <el-pagination layout="prev, pager, next" :total="totalItems" :page-size="pageSize" v-model:current-page="currentPage" @current-change="handlePageChange" style="margin-top: 15px;" />
+        </el-card>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
 <script setup>
-import { ref, onMounted, computed,reactive } from 'vue';
-import axios from 'axios';
-import packageJson from '@/../../package.json';
+import { ref, onMounted, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { ElMessage } from 'element-plus';
 import client from "@/stores/modules/client";
-import  icpList from '@/utils/ipc'
-import socket from "@/stores/modules/socket";
+import icpList from '@/utils/ipc';
 import UpdateDialog from '@/components/UpdateDialog'
 import { getVersionByRoleType } from '@/apis/other'
-import { ElMessageBox } from 'element-plus'
-
-
 
-const currentVersion = ref(packageJson.version);
-const latest =  ref({});
+const { t } = useI18n()
+const currentVersion = ref('0.0.0');
+const latest = ref({});
 const isLatest = ref(true);
-const versions = ref([]); // 所有版本数据
-
-// 分页相关
+const versions = ref([]);
 const currentPage = ref(1);
 const pageSize = ref(20);
 const totalItems = ref(0);
 
-// 计算属性:获取当前页的数据
 const paginatedVersions = computed(() => {
   const start = (currentPage.value - 1) * pageSize.value;
-  const end = start + pageSize.value;
-  return (versions.value || []).slice(start, end);
+  return (versions.value || []).slice(start, start + pageSize.value);
 });
 
-// 获取版本信息
 const fetchVersions = async () => {
   try {
-    // 添加时间戳避免缓存问题
-    // const timestamp = new Date().getTime();
-    // const response = await axios.get('https://ossimg.valimart.net/frontend/html/zhihuiyin/version.json', {
-    //   params: {
-    //     _t: timestamp
-    //   }
-    // });
-
     const { data } = await getVersionByRoleType({ role_type: 6 })
-    console.log('data' , data)
-    // 确保 response.data 是 JSON 数据
-    // let data;
-    // if (typeof response.data === 'string') {
-    //   data = JSON.parse(response.data);
-    // } else {
-    //   data = response.data;
-    // }
     versions.value = data;
     if (data.length > 0) {
       latest.value = data[0];
-      // 比较版本号
       isLatest.value = compareVersions(currentVersion.value, latest.value.version) >= 0;
     }
-    // 初始化分页信息
     totalItems.value = versions.value.length;
   } catch (error) {
-    console.error('获取版本信息失败:', error);
-    ElMessage.error('无法获取版本信息');
+    ElMessage.error(t('ota.downloadFailed'));
   }
 };
 
-// 版本号比较函数
 const compareVersions = (v1, v2) => {
-  const parts1 = v1.split('.').map(Number);
-  const parts2 = v2.split('.').map(Number);
-
+  const parts1 = v1.split('.').map(Number); const parts2 = v2.split('.').map(Number);
   for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
-    const num1 = parts1[i] || 0;
-    const num2 = parts2[i] || 0;
-
-    if (num1 > num2) return 1;
-    if (num1 < num2) return -1;
+    const num1 = parts1[i] || 0; const num2 = parts2[i] || 0;
+    if (num1 > num2) return 1; if (num1 < num2) return -1;
   }
-
   return 0;
 };
 
-// 下载最新版本
-const downloadUpdate = () => {
-  downloadSpecificVersion(versions.value[0].attachment)
-};
-
-
-const updateVisible = ref( false)
+const downloadUpdate = () => { downloadSpecificVersion(versions.value[0].attachment) };
+const updateVisible = ref(false)
 const updateResult = ref({})
-
-// 详情对话框相关
 const detailVisible = ref(false)
 const currentVersionDetail = ref('')
 
 const clientStore = client();
-const socketStore = socket()
-// 下载特定版本
+
 const downloadSpecificVersion = (url) => {
-  // 添加时间戳避免缓存
   const urlWithTimestamp = url + (url.includes('?') ? '&' : '?') + '_t=' + new Date().getTime();
-
   clientStore.ipc.removeAllListeners('app.updater');
   clientStore.ipc.removeAllListeners(icpList.ota.updateVersion);
   clientStore.ipc.send(icpList.ota.updateVersion, urlWithTimestamp);
-  clientStore.ipc.on(icpList.ota.updateVersion, async (event, result) => {
-    console.log('==============================')
-    console.log('checkUpdate')
-    console.log(event)
-    console.log(result)
-
-  })
-
   clientStore.ipc.on('app.updater', async (event, result) => {
-
-
     try {
-      let res =  JSON.parse(result)
-      if([1,3,4].includes(res.status)){
-        updateResult.value =  res
-        updateVisible.value = true
-        console.log(updateResult.value)
-        console.log(updateVisible.value)
-      }else{
-        updateVisible.value = false
-        ElMessage.error('下载失败')
-      }
-    }catch (e) {
-      console.log(e)
-    }
-
-
-  })
-
-};
-
-// 处理分页变化
-const handlePageChange = (page) => {
-  currentPage.value = page;
+      let res = JSON.parse(result)
+      if([1,3,4].includes(res.status)){ updateResult.value = res; updateVisible.value = true }
+      else { updateVisible.value = false; ElMessage.error(t('ota.downloadFailed')) }
+    }catch (e) {}
+  });
 };
 
-// 显示版本详情
-const showDetail = (row) => {
-  currentVersionDetail.value = row.detail || '暂无详情';
-  detailVisible.value = true;
-};
+const handlePageChange = (page) => { currentPage.value = page; };
+const showDetail = (row) => { currentVersionDetail.value = row.detail || t('ota.noDetail'); detailVisible.value = true; };
 
-onMounted(() => {
+onMounted(async () => {
+  currentVersion.value = '1.0.0' // version from package.json
   fetchVersions();
 });
 </script>
 
-<template>
-  <el-container>
-
-
-    <el-dialog
-        v-model="updateVisible"
-        :close-on-click-modal="false"
-        title="软件下载中"
-    >
-        <div class="desc line-30 fs-16">{{updateResult.desc}}</div>
-    </el-dialog>
-
-    <el-dialog
-        v-model="detailVisible"
-        title="版本详情"
-        width="600px"
-    >
-        <div v-html="currentVersionDetail" class="version-detail-content te-l"></div>
-    </el-dialog>
-    <el-main>
-      <div class="version-check-container">
-        <el-card class="current-version-card fs-14 te-l">
-          <p>当前版本:<span class="fs-14"> {{ currentVersion }}</span></p>
-          <div class="flex left">
-            <p >状态: <span :class="{ 'text-green': isLatest, 'text-red': !isLatest }">{{ isLatest ? '您已经是最新版本' : '发现新版本' }}</span></p>
-            <p class="mar-left-10" v-if="!isLatest"> {{ latest.version }}</p>
-            <el-button v-if="!isLatest" class="mar-left-20" type="primary" @click="downloadUpdate" v-log="{ describe: { action: '点击下载最新版本'+ latest.version } }">下载更新</el-button>
-          </div>
-          <p>更新日志:</p>
-          <div v-html="latest.detail"></div>
-        </el-card>
-
-        <el-card class="history-versions-card mar-top-10">
-          <h3>历史版本</h3>
-          <!-- 使用计算属性获取分页数据 -->
-          <el-table :data="paginatedVersions" border>
-            <el-table-column prop="version" label="版本号" width="70"></el-table-column>
-            <el-table-column prop="release_date" label="发布日期" width="100">
-
-              <template #default="{ row }">
-                {{row.release_date.substr(0,10)}}
-              </template>
-            </el-table-column>
-            <el-table-column label="描述">
-              <template #default="{ row }">
-                {{row.overview}}
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" width="180">
-              <template #default="{ row }">
-                  <el-button style="width: 70px;" size="small" @click="downloadSpecificVersion(row.attachment)" v-log="{ describe: { action: '点击下载历史版本', version: row.version, url: row.attachment } }">下载</el-button>
-                  <el-button style="width: 70px;"  size="small" type="info" @click="showDetail(row)" v-log="{ describe: { action: '查看版本详情', version: row.version } }">版本详情</el-button>
-
-              </template>
-            </el-table-column>
-          </el-table>
-          <el-pagination
-            layout="prev, pager, next"
-            :total="totalItems"
-            :page-size="pageSize"
-            v-model:current-page="currentPage"
-            @current-change="handlePageChange"
-            style="margin-top: 15px;"
-          />
-        </el-card>
-      </div>
-    </el-main>
-  </el-container>
-</template>
-
 <style scoped>
-.version-describe {
-  display: inline-block;
-  width: 100%; /* 根据需要调整 */
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.operation-buttons {
-  display: flex;
-  flex-direction: column;
-  gap: 5px;
-}
-
-.operation-buttons .el-button {
-  width: 100%;
-}
-
-.version-detail-content {
-  max-height: 400px;
-  overflow-y: auto;
-  line-height: 1.6;
-}
+.version-detail-content { max-height: 400px; overflow-y: auto; line-height: 1.6; }
 </style>

+ 97 - 393
frontend/src/views/Photography/check.vue

@@ -1,30 +1,23 @@
 <template>
-
-  <headerBar
-      v-if="!isSetting"
-      title="拍摄物体镜头矫正"
-      :menu="menu"
-      showUser
-  />
+  <headerBar :title="$t('router.photoCheck')" v-if="!isSetting" :menu="menu" showUser />
   <div class="check-wrap">
-
     <div class="check-page flex-col" :class="isSetting ? 'check-page_seeting' : '' ">
       <div class="main-container flex-col">
         <div class="content-wrapper flex-row justify-between">
           <div class="left-panel flex-col justify-between">
             <div class="tips-container flex-row" v-if="show">
                 <spanc  v-if="isSetting" class="tips-tex">
-                  请在圆盘上摆上鞋子(注意左右脚),要求鞋外侧朝向拍照机,鞋子中轴线和红外线对齐,如果光亮不够,可以打开环境光源,关闭闪光灯。
+                  {{ $t('photoCheck.tipSetting') }}
                 </spanc>
                 <span v-else class="tips-tex">
-                  请在圆盘上摆上鞋子(注意左右脚),要求鞋外侧朝向拍照机,鞋子中轴线和红外线对齐,如果光亮不够,可以打开环境光源,关闭闪光灯。
+                  {{ $t('photoCheck.tipNormal') }}
                 </span>
             </div>
             <div class="camera-preview  flex col center ">
               <div class="camera-preview-img" v-if="step === 1">
                 <img v-if="previewKey" class="camera-img" :src="previewSrc" />
                 <el-button class="auto-focus-btn" @click="handleAutoFocus"  v-if="previewKey">
-                  <span>点击对焦</span>
+                  <span>{{ $t('photoCheck.clickFocus') }}</span>
                 </el-button>
                 <div class="example-image flex-col" v-if="!isSetting && previewKey > 1">
                   <img :src="exampleImage">
@@ -32,33 +25,28 @@
               </div>
               <template v-if="step === 2" >
                 <img class="camera-img"  :src="getFilePath(imageTplPath)" />
-                <span  class="camera-description">这是一张用于检查镜头是否合适的测试图</span>
+                <span  class="camera-description">{{ $t('photoCheck.testImageDesc') }}</span>
               </template>
-
             </div>
           </div>
         </div>
 
         <template v-if="!isSetting">
           <div v-if="step === 1" class="action-button flex cente">
-            <div @click="takePictures" class="check-button  button--primary1 flex-col" v-log="{ describe: { action: '点击拍照检查' } }"><span class="button-text" v-loading="loading">拍照检查</span>
+            <div @click="takePictures" class="check-button  button--primary1 flex-col" v-log="{ describe: { action: 'take photo check' } }"><span class="button-text" v-loading="loading">{{ $t('photoCheck.takePhotoCheck') }}</span>
             </div>
           </div>
 
           <div v-else class="action-button flex center">
-            <div @click="checkConfirm(false)" class="check-button  button--white flex-col" v-log="{ describe: { action: '点击重新拍照检查' } }">
-              <span class="button-text cu-p">重新拍照检查</span>
+            <div @click="checkConfirm(false)" class="check-button  button--white flex-col" v-log="{ describe: { action: 'retake check' } }">
+              <span class="button-text cu-p">{{ $t('photoCheck.retakeCheck') }}</span>
             </div>
-            <router-link class="mar-left-20 " :to="{
-              name: 'PhotographyShot'
-            }">
-              <div class="check-button   button--primary1 flex-col" v-log="{ describe: { action: '点击确认无误下一步' } }">
-                <span class="button-text cu-p">确认无误,下一步</span>
+            <router-link class="mar-left-20 " :to="{ name: 'PhotographyShot' }">
+              <div class="check-button   button--primary1 flex-col" v-log="{ describe: { action: 'confirm and next' } }">
+                <span class="button-text cu-p">{{ $t('photoCheck.confirmNext') }}</span>
               </div>
             </router-link>
-
           </div>
-
         </template>
       </div>
     </div>
@@ -76,7 +64,6 @@
       :addRowData="addRowData"
       :disablePointConfig="!isSetting"
     />
-
   </div>
   <hardware-check v-if="!isSetting"
    isInitCheck
@@ -85,109 +72,71 @@
 </template>
 <script setup lang="ts">
 import {watchEffect, ref, reactive, defineEmits, defineProps, computed, onBeforeUnmount, onMounted} from 'vue'
+import { useI18n } from 'vue-i18n'
 import client from "@/stores/modules/client";
 import socket from "@/stores/modules/socket";
 import icpList from '@/utils/ipc'
 import useUserInfo from "@/stores/modules/user";
 import headerBar from '@/components/header-bar/index.vue'
+import hardwareCheck from '@/components/check/index.vue'
 import  editRow  from './components/editRow'
+import  configInfo  from '@/stores/modules/config';
+import { digiCamControlWEB } from '@/utils/appconfig'
+import { getFilePath } from '@/utils/appfun'
+import {ElMessage} from "element-plus";
+
+const { t } = useI18n()
 const clientStore = client();
-const socketStore = socket(); // WebSocket状态管理实例
+const socketStore = socket();
+const configInfoStore = configInfo();
 
 const emit = defineEmits([ 'confirm','onClose']);
 
-
-import  configInfo  from '@/stores/modules/config';
-const configInfoStore = configInfo();
-
-const confirm = async () => {
-  console.log('confirm');
-  setTimeout(()=>{
-     hideVideo(currentPointName.value)
-  },1000)
-  emit('confirm')
-}
-const onClose = async () => {
-  console.log('onClose');
-  setTimeout(()=>{
-     hideVideo(currentPointName.value)
-  },1000)
-  emit('onClose')
-}
-// 定义 props
 const props = defineProps({
-  id:{
-    type: Number||String,
-    default: 0
-  },
-  addRowData:{
-    type: Object,
-    default: () => {
-      return { }
-    }
-  }
+  id:{ type: Number||String, default: 0 },
+  addRowData:{ type: Object, default: () => ({}) }
 })
+
 const menu = reactive([])
 const show = ref(true)
-
-const isSetting = computed(()=>{
-  return props.id || props.addRowData.mode_type
-})
-
+const isSetting = computed(()=> props.id || props.addRowData.mode_type )
 const useUserInfoStore = useUserInfo()
-import HardwareCheck from '@/components/check/index.vue'
-import { digiCamControlWEB } from  '@/utils/appconfig'
-import { getFilePath } from '@/utils/appfun'
-import {ElMessage} from "element-plus";
 const previewKey = ref(0)
-
 const preview = ref(digiCamControlWEB+'liveview.jpg')
-
-
-
 const previewSrc = computed(()=>{
   let time = new Date().getTime()
   return preview.value+'?key='+previewKey.value+'&time='+time
 })
 const step = ref(1)
-async function checkConfirm(init){
+
+function checkConfirm(init){
   step.value =1
   if(menu.length === 0){
-    menu.push({
-      type:'developer'
-    })
-    menu.push({
-      type:'toggleModel'
-    })
-
-    menu.push({
-      type:'setting'
-    })
-
+    menu.push({ type:'developer' }, { type:'toggleModel' }, { type:'setting' })
   }
   if(!init) previewKey.value++;
-
   showrEditRow.value = true
   loading.value = false;
 }
 
-// editRow 数据加载完成后调用,仅记录点位,首次不打开预览
 function onEditRowReady(point_name) {
-  console.log('onEditRowReady,记录点位:' + point_name)
   currentPointName.value = point_name
 }
 
 const init = ref(true)
 function onRunMcuSingle (){
-  if(init.value) {
-    loading.value = false
-    init.value = false
-  }
+  if(init.value) { loading.value = false; init.value = false }
 }
-const showrEditRow = ref(false)
-
-
 
+const showrEditRow = ref(false)
+const confirm = async () => {
+  setTimeout(()=>{ hideVideo(currentPointName.value) },1000)
+  emit('confirm')
+}
+const onClose = async () => {
+  setTimeout(()=>{ hideVideo(currentPointName.value) },1000)
+  emit('onClose')
+}
 
 let interval:any = null
 let currentPointName = ref('A')
@@ -195,75 +144,42 @@ let isVideoShowing = ref(false)
 let isHidingVideo = ref(false)
 let hideVideoTimer: any = null
 let pendingShowVideoPoint: string | null = null
-let isFirstShow = false  // 标记是否已首次显示预览
+let isFirstShow = false
 
 function clearPreviewInterval() {
-  if (interval) {
-    clearInterval(interval)
-    interval = null
-  }
-  if (hideVideoTimer) {
-    clearTimeout(hideVideoTimer)
-    hideVideoTimer = null
-  }
+  if (interval) { clearInterval(interval); interval = null }
+  if (hideVideoTimer) { clearTimeout(hideVideoTimer); hideVideoTimer = null }
 }
 
 function showVideo(point_name = 'A') {
-  if (isHidingVideo.value) {
-    console.log('正在关闭预览中,延迟打开:' + point_name)
-    pendingShowVideoPoint = point_name
-    return
-  }
-
-  if (isVideoShowing.value && currentPointName.value === point_name) {
-    console.log('预览已在显示,跳过重复调用')
-    return
-  }
-  console.log('打开预览=====================' + point_name)
+  if (isHidingVideo.value) { pendingShowVideoPoint = point_name; return }
+  if (isVideoShowing.value && currentPointName.value === point_name) return
   clearPreviewInterval()
   isVideoShowing.value = true
   currentPointName.value = point_name
-
   clientStore.ipc.removeAllListeners(icpList.camera.PreviewShow);
-
   clientStore.ipc.send(icpList.camera.PreviewShow, { point_name });
   clientStore.ipc.on(icpList.camera.PreviewShow, async (event, result) => {
-    console.log('打开预览=====================')
-    console.log(result)
-    if (result.device_status === -1 && result.msg){
-      ElMessage.error(result.msg)
-      return;
-    }
+    if (result.device_status === -1 && result.msg){ ElMessage.error(result.msg); return }
     clearPreviewInterval()
     hideVideoTimer = setTimeout(() => {
-      interval = setInterval(() => {
-        previewKey.value++;
-      }, 200)
+      interval = setInterval(() => { previewKey.value++; }, 200)
     }, 500)
   })
 }
 
 async function hideVideo(point_name = 'A') {
-  console.log('关闭预览=====================' + point_name)
   clearPreviewInterval()
   isVideoShowing.value = false
   isHidingVideo.value = true
-
   return new Promise((resolve) => {
     clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
     clientStore.ipc.send(icpList.camera.PreviewHide, { point_name });
     clientStore.ipc.on(icpList.camera.PreviewHide, async (event, result) => {
-      console.log('关闭预览回调=====================')
-      console.log(result)
-      if (result.device_status === -1 && result.msg){
-        console.log(result.msg)
-        resolve(true)
-      }
+      if (result.device_status === -1 && result.msg) resolve(true)
       isHidingVideo.value = false
-      // 如果有待执行的打开请求,执行它
       if (pendingShowVideoPoint) {
-        const point = pendingShowVideoPoint
-        pendingShowVideoPoint = null
+        const point = pendingShowVideoPoint; pendingShowVideoPoint = null
         setTimeout(() => showVideo(point), 100)
       }
       resolve(true)
@@ -272,176 +188,89 @@ async function hideVideo(point_name = 'A') {
 }
 
 async function switchPreviewPoint(point_name = 'A') {
-  console.log('switchPreviewPoint:' + point_name);
-  // 如果点位相同,不做任何处理
-  if (point_name === currentPointName.value) {
-    console.log('点位相同,跳过切换');
-    return;
-  }
-  // 首次显示预览之前不做切换
-/*  if (!isFirstShow) {
-    console.log('首次显示预览前,跳过切换');
-    currentPointName.value = point_name;
-    return;
-  }*/
-  // 执行切换
+  if (point_name === currentPointName.value) return;
   await hideVideo(currentPointName.value)
   showVideo(point_name)
 }
 
-function goArts(){
-
-}
-
 function handleAutoFocus() {
-  console.log('自动对焦,点位:' + currentPointName.value)
-  socketStore.sendMessage({
-    type: 'smart_shooter_auto_focus',
-    data: {
-      point_name: currentPointName.value || 'A'
-    }
-  });
+  socketStore.sendMessage({ type: 'smart_shooter_auto_focus', data: { point_name: currentPointName.value || 'A' } });
 }
+
 const loading = ref(true)
 const editData = ref(null);
 const imageTplPath = ref(null)
+
 function takePictures() {
   if(loading.value) return;
-  console.log(editData);
-  console.log(editData.value.editRowData);
-
-  if (clientStore.isClient) {
-    loading.value = true;
-    console.log('takePictures');
-    hideVideo(currentPointName.value).then(() => {
-      socketStore.sendMessage({
-        type: 'run_mcu_single',
-        data: {
-          camera_height: Number(editData.value.editRowData.camera_height),
-          camera_angle:  Number(editData.value.editRowData.camera_angle),
-          led_switch:editData.value.editRowData.led_switch,
-          id:0,
-          mode_type:editData.value.editRowData.mode_type,
-          turntable_position:Number(editData.value.editRowData.turntable_position),
-          action_name:editData.value.editRowData.action_name || '测试',
-          turntable_angle: Number(editData.value.editRowData.turntable_angle),
-          shoe_upturn: Number(editData.value.editRowData.shoe_upturn),
-          action_index:1,
-          number_focus:0,
-          take_picture:true,
-          pre_delay:0,
-          after_delay:0,
-          point_name:editData.value.editRowData.point_name || currentPointName.value  || 'A',
-          is_move_device:editData.value.editRowData.is_move_device
-        }
-      });
-    })
-  }
+  hideVideo(currentPointName.value).then(() => {
+    socketStore.sendMessage({
+      type: 'run_mcu_single',
+      data: {
+        camera_height: Number(editData.value.editRowData.camera_height),
+        camera_angle:  Number(editData.value.editRowData.camera_angle),
+        led_switch: editData.value.editRowData.led_switch,
+        id:0, mode_type: editData.value.editRowData.mode_type,
+        turntable_position: Number(editData.value.editRowData.turntable_position),
+        action_name: editData.value.editRowData.action_name || '测试',
+        turntable_angle: Number(editData.value.editRowData.turntable_angle),
+        shoe_upturn: Number(editData.value.editRowData.shoe_upturn),
+        action_index:1, number_focus:0, take_picture:true, pre_delay:0, after_delay:0,
+        point_name: editData.value.editRowData.point_name || currentPointName.value || 'A',
+        is_move_device: editData.value.editRowData.is_move_device
+      }
+    });
+  })
 }
 
-// 获取主图
 function  createMainImage (file_path){
-
   loading.value = true;
   clientStore.ipc.removeAllListeners(icpList.takePhoto.createMainImage);
-  clientStore.ipc.send(icpList.takePhoto.createMainImage,{
-    file_path:file_path
-  });
+  clientStore.ipc.send(icpList.takePhoto.createMainImage, { file_path });
   clientStore.ipc.on(icpList.takePhoto.createMainImage, async (event, result) => {
     if(result.code === 0 && result.data?.main_out_path){
-      imageTplPath.value  = result.data?.main_out_path
-      // 预览已在 takePictures 中关闭,不再重复调用 hideVideo
-      step.value = 2
-      loading.value = false;
+      imageTplPath.value = result.data?.main_out_path
+      step.value = 2; loading.value = false;
     }else if(result.msg){
       loading.value = false;
-      console.log('createMainImage');
       showVideo(currentPointName.value)
       if(result.code !== 0) ElMessage.error(result.msg)
     }
     clientStore.ipc.removeAllListeners(icpList.takePhoto.createMainImage);
-
-
   });
 }
 
-
-//拍照成功  SmartShooter
 clientStore.ipc.on(icpList.socket.message+'_smart_shooter_photo_take', async (event, result) => {
-  console.log('_smart_shooter_photo_take');
-  console.log(result);
   if(result.code === 0 && result.data?.photo_file_name){
-    imageTplPath.value  = result.data?.photo_file_name
-    step.value = 2
-    loading.value = false;
-    // 首次拍摄完成后打开预览用于检查镜头
-    if (!isFirstShow) {
-      isFirstShow = true
-      setTimeout(() => showVideo(currentPointName.value), 100)
-    }
+    imageTplPath.value = result.data?.photo_file_name; step.value = 2; loading.value = false;
+    if (!isFirstShow) { isFirstShow = true; setTimeout(() => showVideo(currentPointName.value), 100) }
   }else {
-    loading.value = false;
-    console.log('_smart_shooter_photo_take');
-    showVideo(currentPointName.value)
+    loading.value = false; showVideo(currentPointName.value)
     if(result.code !== 0 && result.msg) ElMessage.error(result.msg)
   }
-
 })
 
-
-//运行的时候  直接拍照  digiCamControl
 clientStore.ipc.on(icpList.socket.message+'_run_mcu_single', async (event, result) => {
-  console.log('_run_mcu_single_check_on')
-  console.log(result)
-
   if(result.code === 0 && result.data?.file_path){
-    imageTplPath.value  = result.data?.file_path
-    step.value = 2
-    loading.value = false;
-    // 首次拍摄完成后打开预览用于检查镜头
-    if (!isFirstShow) {
-      isFirstShow = true
-      setTimeout(() => showVideo(currentPointName.value), 100)
-    }
+    imageTplPath.value = result.data?.file_path; step.value = 2; loading.value = false;
+    if (!isFirstShow) { isFirstShow = true; setTimeout(() => showVideo(currentPointName.value), 100) }
   }else {
-    loading.value = false;
-    console.log('_run_mcu_single');
-    showVideo(currentPointName.value)
+    loading.value = false; showVideo(currentPointName.value)
     if(result.code !== 0 && result.msg) ElMessage.error(result.msg)
   }
-
-
-/*  if(result.code === 0 && result.data?.file_path){
-
-    createMainImage(result.data?.file_path)
-
-  }else if(result.msg){
-    if( result.msg.indexOf('处理失败,请重试') >= 0){
-      ElMessage.error(result.msg)
-      step.value = 1
-      loading.value = false;
-      showVideo()
-    }
-  }*/
-
-})
+});
 
 const exampleImage = ref('https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400')
 onMounted(async ()=>{
-  await  configInfoStore.getAppConfig()
+  await configInfoStore.getAppConfig()
   if(configInfoStore.appConfig.controlType === "SmartShooter"){
     preview.value = configInfoStore.appConfig.userDataPath  + "\\preview\\liveview.png"
   }
-  exampleImage.value =  configInfoStore.appConfig.exampleImage || 'https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400'
-
+  exampleImage.value = configInfoStore.appConfig.exampleImage || 'https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400'
 })
 
-/**
- * 页面卸载时移除所有事件监听器。
- */
 onBeforeUnmount(() => {
   clearPreviewInterval()
-  console.log('onBeforeUnmount');
   hideVideo(currentPointName.value)
   clientStore.ipc.removeAllListeners(icpList.camera.takePictures);
   clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
@@ -451,145 +280,20 @@ onBeforeUnmount(() => {
 })
 </script>
 <style scoped lang="scss">
-.check-wrap {
-  background-color: rgba(234, 236, 237, 1);
-  display: flex;
-  justify-content: left;
-}
-.check-page {
-  width: calc(100% - 330px) ;
-  position: relative;
-  min-height: calc(100vh - 35px);
-  overflow: hidden;
-  background: #fff;
-
-  .main-container {
-    width: 100%;
-    min-height: calc(100vh - 35px);
-    margin-bottom: 1px;
-
-    .content-wrapper {
-      width:100%;
-      padding: 10px;
-
-      .left-panel {
-        width: 100%;
-        height: calc(100% - 80px);
-
-        .tips-container {
-          background-color: rgba(255, 241, 222, 0.8);
-          border-radius: 4px;
-          width: 100%;
-          height: 40px;
-          border: 1px solid rgba(255, 228, 190, 1);
-          justify-content: flex-center;
-
-          .tips-icon {
-            width: 16px;
-            height: 16px;
-            margin: 12px 0 0 16px;
-          }
-
-          .tips-tex {
-            width: 549px;
-            height: 22px;
-            overflow-wrap: break-word;
-            color: rgba(0, 0, 0, 0.85);
-            font-size: 14px;
-            font-weight: NaN;
-            text-align: left;
-            white-space: nowrap;
-            line-height: 22px;
-            margin: 9px 0 0 7px;
-          }
-
-          .close-icon {
-            width: 12px;
-            height: 12px;
-            margin: 14px 16px 0 17px;
-          }
-        }
-
-        .camera-preview {
-          background-color: rgba(255, 255, 255, 1);
-          height:  calc(100vh - 200px);
-          position: relative;
-          margin-top: 20px;
-          position: relative;
-          .camera-description {
-            position: absolute;
-            bottom: 20px;
-            width: 60%;
-            padding: 8px 20px;
-            background: rgba($color: #ffffff, $alpha: .2);
-            border-radius: 20px;
-            font-size: 14px;
-          }
-          .camera-preview-img {
-            display: inline-block;
-            height: 100%;
-            min-width: 300px;
-            position: relative;
-          }
-
-          .auto-focus-btn {
-            position: absolute;
-            top: 10px;
-            right: 10px;
-            z-index: 10;
-          }
-
-          .camera-img {
-          //  width: 100%;
-            display: block;
-            position: relative;
-            box-shadow: 0px 2px 10px 0px rgba(0, 32, 78, 0.1);
-            height: 100%;
-            object-fit:contain;
-
-          }
-          .example-image {
-            position: absolute;
-            right: 10px;
-            bottom: 10px;
-            background-color: rgba(216, 216, 216, 1);
-            height: 135px;
-            margin-top: 10px;
-            width: 200px;
-            img {
-              width: 200px;
-              height: 135px;
-              display: block;
-            }
-          }
-        }
-      }
-
-    }
-
-    .action-button {
-      width: 100%;
-      text-align: center;
-      margin-top: 28px;
-
-      .check-button {
-        .button-text {
-          width: 180px;
-          overflow-wrap: break-word;
-          font-size: 16px;
-          text-align: center;
-          white-space: nowrap;
-          height: 40px;
-          line-height: 40px;
-        }
-      }
-    }
-  }
-}
-.check-page_seeting {
-  min-height: calc(100vh - 92px);
-  .main-container {
-    min-height: calc(100vh - 92px);
-  }
-}
+.check-wrap { background-color: rgba(234, 236, 237, 1); display: flex; justify-content: left; }
+.check-page { width: calc(100% - 330px) ; position: relative; min-height: calc(100vh - 35px); overflow: hidden; background: #fff; }
+.check-page .main-container { width: 100%; min-height: calc(100vh - 35px); margin-bottom: 1px; }
+.check-page .main-container .content-wrapper { width:100%; padding: 10px; }
+.check-page .main-container .content-wrapper .left-panel { width: 100%; height: calc(100% - 80px); }
+.check-page .main-container .content-wrapper .left-panel .tips-container { background-color: rgba(255, 241, 222, 0.8); border-radius: 4px; width: 100%; height: 40px; border: 1px solid rgba(255, 228, 190, 1); justify-content: flex-center; }
+.check-page .main-container .content-wrapper .left-panel .tips-tex { width: 549px; height: 22px; overflow-wrap: break-word; color: rgba(0, 0, 0, 0.85); font-size: 14px; text-align: left; white-space: nowrap; line-height: 22px; margin: 9px 0 0 7px; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview { background-color: rgba(255, 255, 255, 1); height: calc(100vh - 200px); position: relative; margin-top: 20px; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview .camera-description { position: absolute; bottom: 20px; width: 60%; padding: 8px 20px; background: rgba($color: #ffffff, $alpha: .2); border-radius: 20px; font-size: 14px; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview .camera-preview-img { display: inline-block; height: 100%; min-width: 300px; position: relative; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview .auto-focus-btn { position: absolute; top: 10px; right: 10px; z-index: 10; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview .camera-img { display: block; position: relative; box-shadow: 0px 2px 10px 0px rgba(0, 32, 78, 0.1); height: 100%; object-fit:contain; }
+.check-page .main-container .content-wrapper .left-panel .camera-preview .example-image { position: absolute; right: 10px; bottom: 10px; background-color: rgba(216, 216, 216, 1); height: 135px; margin-top: 10px; width: 200px; img { width: 200px; height: 135px; display: block; } }
+.check-page .main-container .action-button { width: 100%; text-align: center; margin-top: 28px; }
+.check-page .main-container .action-button .check-button .button-text { width: 180px; overflow-wrap: break-word; font-size: 16px; text-align: center; white-space: nowrap; height: 40px; line-height: 40px; }
+.check-page_seeting { min-height: calc(100vh - 92px); .main-container { min-height: calc(100vh - 92px); } }
 </style>

+ 8 - 19
frontend/src/views/Photography/components/LoadingDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    v-model.sync="visible"
+    v-model="visible"
     :show-close="!requesting"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
@@ -10,7 +10,6 @@
     append-to-body
   >
     <div class="loading-content">
-      <!-- 新的步骤进度条 -->
       <ProgressSteps
         v-if="useNewProgress && progressSteps.length > 0"
         :steps="progressSteps"
@@ -21,8 +20,6 @@
         :message="message"
       />
 
-
-      <!-- 原有的简单进度条 -->
       <div v-else class="progress-container">
         <div class="progress-bar">
           <div
@@ -34,9 +31,7 @@
       </div>
 
       <div class="message" style="display: none">{{ message }}</div>
-      <!-- 错误信息插槽 -->
       <slot name="errList"></slot>
-      <!-- 进度消息插槽 -->
       <slot name="progressMessages"></slot>
 
       <el-button
@@ -49,7 +44,6 @@
         {{ buttonText }}
       </el-button>
 
-
       <el-button
           v-if="message  === '全部货号生成失败'"
           type="primary"
@@ -74,7 +68,7 @@ interface StepData {
   current: number
   total: number
   error: number
-  folder?: string // 添加folder字段
+  folder?: string
 }
 
 interface Props {
@@ -86,7 +80,7 @@ interface Props {
   requesting?: boolean
   useNewProgress?: boolean
   progressSteps?: StepData[]
-  onOpenFolder?: (folder: string) => void // 添加打开目录的回调函数
+  onOpenFolder?: (folder: string) => void
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -97,7 +91,7 @@ const props = withDefaults(defineProps<Props>(), {
   requesting: false,
   useNewProgress: false,
   progressSteps: () => [],
-  onOpenFolder: () => {} // 默认空函数
+  onOpenFolder: () => {}
 })
 
 const emit = defineEmits<{
@@ -107,12 +101,10 @@ const emit = defineEmits<{
 
 const visible = ref(props.modelValue)
 
-// 监听visible变化
 watch(() => visible.value, (newVal) => {
   emit('update:modelValue', newVal)
 })
 
-// 监听props.modelValue变化
 watch(() => props.modelValue, (newVal) => {
   visible.value = newVal
 })
@@ -129,13 +121,11 @@ const handleButtonClick = () => {
     padding: 0;
   }
  .el-dialog__body {
-   // background-image: url(@/assets/images/Photography/loading-bg.png);  /* 添加背景图片 */
-    background-size: cover;  /* 确保图片覆盖整个区域 */
-    background-position: center;  /* 图片居中显示 */
-    background-repeat: no-repeat;  /* 防止图片重复 */
-    padding: 20px 20px;  /* 添加内边距 */
+    background-size: cover;
+    background-position: center;
+    background-repeat: no-repeat;
+    padding: 20px 20px;
   }
-
 }
 </style>
 
@@ -145,7 +135,6 @@ const handleButtonClick = () => {
     display: none;
     padding: 0;
   }
-
   :deep(.el-dialog__body) {
     padding:0;
   }

+ 38 - 37
frontend/src/views/Photography/components/editRow.vue

@@ -1,19 +1,19 @@
 <template>
   <div class="editrow_wrap" v-if="initStatus">
-    <div class="config-type">参数值编辑:
+    <div class="config-type">{{ $t('editRow.title') }}:
       <!-- <el-checkbox v-model="isDefault">开启运动调试</el-checkbox>-->
     </div>
-    <span class="fs-12  mar-left-10 mar-top-10" v-if="id"  style="position: relative; top:5px">动作名称修改后,可能会导致自定义模板生成失效!</span>
+    <span class="fs-12  mar-left-10 mar-top-10" v-if="id"  style="position: relative; top:5px">{{ $t('editRow.actionNameChangeTip') }}</span>
     <el-form class="editForm" :model="editRowData"  v-loading="captureLoading" label-width="100px" >
-      <el-form-item label="动作名称">
+      <el-form-item :label="$t('editRow.actionName')">
         <el-input v-model="editRowData.action_name" :disabled="editRowData.is_system" style="width: 170px;"/>
 
       </el-form-item>
 
       <!-- 多相机模式 - 拍摄点位 -->
-      <el-form-item label="拍摄点位" v-if="isMultiCameraMode">
-        <el-select v-model="editRowData.point_name" placeholder="请选择点位" style="width: 170px;" @focus="refreshPointList">
-          <el-option v-for="point in availablePoints" :key="point" :label="'点位 ' + point" :value="point"/>
+      <el-form-item :label="$t('editRow.shotPoint')" v-if="isMultiCameraMode">
+        <el-select v-model="editRowData.point_name" :placeholder="$t('editRow.selectPoint')" style="width: 170px;" @focus="refreshPointList">
+          <el-option v-for="point in availablePoints" :key="point" :label="$t('editRow.pointLabel', { point })" :value="point"/>
         </el-select>
       </el-form-item>
 
@@ -27,67 +27,67 @@
         </div>
       </el-form-item>-->
 
-      <el-form-item label="是否拍照" v-if="!editRowData.is_system">
+      <el-form-item :label="$t('editRow.takePhoto')" v-if="!editRowData.is_system">
         <el-radio-group v-model="editRowData.take_picture">
-          <el-radio :label="1">拍照</el-radio>
-          <el-radio :label="0">不拍照</el-radio>
+          <el-radio :label="1">{{ $t('editRow.photo') }}</el-radio>
+          <el-radio :label="0">{{ $t('editRow.noPhoto') }}</el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="相机高度(mm)" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
+      <el-form-item :label="$t('editRow.cameraHeight')" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
         <el-input v-model="editRowData.camera_height" @change="changeNum('camera_high_motor',0, maxCameraHeight)" :min="0" :max="maxCameraHeight" :step="1"  style="width: 170px;" type="number" :disabled="isMultiCameraMode && editRowData.is_move_device === 0">
         </el-input>
-        <div class="error-msg">最小0,最大{{ maxCameraHeight }}</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: 0, max: maxCameraHeight }) }}</div>
       </el-form-item>
-      <el-form-item label="相机倾角" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
+      <el-form-item :label="$t('editRow.cameraAngle')" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
         <el-input v-model="editRowData.camera_angle" :min="-5" :max="30" :step=".1" @change="changeNum('camera_steering',-5, 30)" style="width: 170px;" type="number" :disabled="isMultiCameraMode && editRowData.is_move_device === 0">
         </el-input>
-        <div class="error-msg">最小-5,最大30</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: -5, max: 30 }) }}</div>
       </el-form-item>
-      <el-form-item label="转盘前后位置" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
+      <el-form-item :label="$t('editRow.turntablePosition')" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
         <el-input v-model="editRowData.turntable_position" @change="changeNum('turntable_position_motor',0, 800)" :min="0" :max="800" :step="1"  style="width: 170px;" type="number" :disabled="isMultiCameraMode && editRowData.is_move_device === 0">
         </el-input>
-        <div class="error-msg">最小0,最大800</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: 0, max: 800 }) }}</div>
       </el-form-item>
-      <el-form-item label="转盘角度" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
+      <el-form-item :label="$t('editRow.turntableAngle')" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
         <el-input v-model="editRowData.turntable_angle" @change="changeNum('turntable_steering',-720, 720)" :min="-720" :max="720" :step="1"  style="width: 170px;" type="number" :disabled="isMultiCameraMode && editRowData.is_move_device === 0">
         </el-input>
-        <div class="error-msg">最小-720,最大720</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: -720, max: 720 }) }}</div>
       </el-form-item>
-      <el-form-item label="鞋子翻转" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
+      <el-form-item :label="$t('editRow.shoeFlip')" :class="{'is-disabled': isMultiCameraMode && editRowData.is_move_device === 0}">
         <div class="flex-row">
           <el-radio-group v-model="editRowData.shoe_upturn" :disabled="isMultiCameraMode && editRowData.is_move_device === 0">
-            <el-radio :label="1">翻转</el-radio>
-            <el-radio :label="0">不翻转</el-radio>
+            <el-radio :label="1">{{ $t('editRow.flip') }}</el-radio>
+            <el-radio :label="0">{{ $t('editRow.noFlip') }}</el-radio>
           </el-radio-group>
-          <a class="cursor-pointer" @click="changeNum('overturn_steering')" v-log="{ describe: { action: '点击测试翻转' } }" v-if="!isMultiCameraMode || editRowData.is_move_device !== 0">测试翻转</a>
+          <a class="cursor-pointer" @click="changeNum('overturn_steering')" v-log="{ describe: { action: '点击测试翻转' } }" v-if="!isMultiCameraMode || editRowData.is_move_device !== 0">{{ $t('editRow.testFlip') }}</a>
         </div>
       </el-form-item>
-      <el-form-item label="LED灯光开光" @change="changeNum('laser_position')">
+      <el-form-item :label="$t('editRow.ledSwitch')" @change="changeNum('laser_position')">
         <el-radio-group v-model="editRowData.led_switch">
-          <el-radio :label="0">关闭</el-radio>
-          <el-radio :label="1">开启</el-radio>
+          <el-radio :label="0">{{ $t('editRow.off') }}</el-radio>
+          <el-radio :label="1">{{ $t('editRow.on') }}</el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="对焦次数">
+      <el-form-item :label="$t('editRow.focusCount')">
         <el-input v-model="editRowData.number_focus" @change="changeNum('take_picture',0, 1)" :min="0" :max="1" :step="1"  style="width: 170px;" type="number">
         </el-input>
-        <div class="error-msg">最小0,最大1</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: 0, max: 1 }) }}</div>
       </el-form-item>
-      <el-form-item label="拍照前延时(秒)">
+      <el-form-item :label="$t('editRow.preDelay')">
         <el-input v-model="editRowData.pre_delay" :min="0" :max="99" :step="1" @change="changeNum('pre_delay',0, 99)" style="width: 170px;" type="number">
         </el-input>
-        <div class="error-msg">最小0,最大99</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: 0, max: 99 }) }}</div>
       </el-form-item>
-      <el-form-item label="拍照后延时(秒)">
+      <el-form-item :label="$t('editRow.postDelay')">
         <el-input v-model="editRowData.after_delay" :min="0" :max="99" :step="1" @change="changeNum('after_delay',0, 99)" style="width: 170px;" type="number">
         </el-input>
-        <div class="error-msg">最小0,最大99</div>
+        <div class="error-msg">{{ $t('editRow.minMaxTip', { min: 0, max: 99 }) }}</div>
       </el-form-item>
     </el-form>
     <div class="btn-row mar-top-20">
-      <div class="normal-btn" @click="close" v-if="id" v-log="{ describe: { action: '点击关闭编辑行' } }">取消</div>
-      <div class="normal-btn"  v-loading="captureLoading"  v-if="!id && editRowData.is_system" @click="testShoesFlip" v-log="{ describe: { action: '点击运行测试拍照' } }">运行</div>
-      <div class="primary-btn"  v-loading="captureLoading" @click="saveRow" v-log="{ describe: { action: '点击保存设备配置' } }">{{ id ? '保存并关闭' : '保存' }}</div>
+      <div class="normal-btn" @click="close" v-if="id" v-log="{ describe: { action: '点击关闭编辑行' } }">{{ $t('editRow.cancel') }}</div>
+      <div class="normal-btn"  v-loading="captureLoading"  v-if="!id && editRowData.is_system" @click="testShoesFlip" v-log="{ describe: { action: '点击运行测试拍照' } }">{{ $t('editRow.run') }}</div>
+      <div class="primary-btn"  v-loading="captureLoading" @click="saveRow" v-log="{ describe: { action: '点击保存设备配置' } }">{{ id ? $t('editRow.saveClose') : $t('common.save') }}</div>
       </div>
   </div>
 
@@ -97,6 +97,7 @@
 import { ref, defineProps, defineEmits , watch, onMounted, computed } from 'vue'
 import icpList from '@/utils/ipc';
 import { digiCamControlWEB } from  '@/utils/appconfig'
+import i18n from '@/locales';
 import {ElMessage} from "element-plus";
 import  { getDeviceConfigDetail,getDeviceConfigDetailQuery,saveDeviceConfig, getAllUserConfigs } from '@/apis/setting'
 import client from "@/stores/modules/client";
@@ -262,7 +263,7 @@ async function changeNum(type, min, max) {
       }else{
         editRowData.value[socketValue[type]] = max;
       }
-      ElMessage.error(`${type}值应在${min}到${max}之间`);
+      ElMessage.error(i18n.global.t('editRow.minMaxTip', { min, max }));
       return;
     }
   }
@@ -339,14 +340,14 @@ const saveRow = async () => {
   }
 
   if(!editRowData.value.action_name){
-    ElMessage.error('请输入动作名称')
+    ElMessage.error(i18n.global.t('editRow.pleaseInputActionName'))
     return;
   }
 
   // 多相机模式校验
   if (isMultiCameraMode.value) {
     if (!editRowData.value.point_name) {
-      ElMessage.error('请选择拍摄点位')
+      ElMessage.error(i18n.global.t('editRow.pleaseSelectPoint'))
       return;
     }
   }
@@ -368,7 +369,7 @@ const saveRow = async () => {
   captureLoading.value = false
   if (result.code == 0) {
     emit('confirm')
-    ElMessage.success('保存成功');
+    ElMessage.success(i18n.global.t('editRow.saveSuccess'));
     clientStore.ipc.removeAllListeners(icpList.setting.saveDeviceConfig);
   }
 };

+ 74 - 83
frontend/src/views/Photography/detail.vue

@@ -12,17 +12,15 @@
             @change="toggleService('is_product_scene')" @click.stop class="tab-checkbox" />
           <div class="tab-content">
             <div class="tab-img flex">
-              <img v-if="form.services.includes('is_product_scene')" src="@/assets/images/detail/cjt_h.svg" alt="场景图生成"
+              <img v-if="form.services.includes('is_product_scene')" src="@/assets/images/detail/cjt_h.svg" alt="Scene Generation"
                 class="tab-icon" />
-              <img v-else src="@/assets/images/detail/cjt.svg" alt="场景图生成" class="tab-icon" />
+              <img v-else src="@/assets/images/detail/cjt.svg" alt="Scene Generation" class="tab-icon" />
             </div>
-            <span class="tab-name">场景图生成</span>
+            <span class="tab-name">{{ $t('photoDetail2.sceneGenerate') }}</span>
           </div>
           <div class="tab-edit-btn" @click.stop="openScenePromptDialog"
             v-log="{ describe: { action: '点击编辑场景图', service: '场景图生成-弹窗' } }">
-            <el-icon>
-              <EditPen />
-            </el-icon>
+            <el-icon><EditPen /></el-icon>
           </div>
         </div>
 
@@ -34,17 +32,15 @@
             @change="toggleService('is_upper_footer')" @click.stop class="tab-checkbox" />
           <div class="tab-content">
             <div class="tab-img flex">
-              <img v-if="form.services.includes('is_upper_footer')" src="@/assets/images/detail/mtt_h.svg" alt="模特图生成"
+              <img v-if="form.services.includes('is_upper_footer')" src="@/assets/images/detail/mtt_h.svg" alt="Model Generation"
                 class="tab-icon" />
-              <img v-else src="@/assets/images/detail/mtt.svg" alt="模特图生成" class="tab-icon" />
+              <img v-else src="@/assets/images/detail/mtt.svg" alt="Model Generation" class="tab-icon" />
             </div>
-            <span class="tab-name">模特图生成</span>
+            <span class="tab-name">{{ $t('photoDetail2.modelGenerate') }}</span>
           </div>
           <div class="tab-edit-btn" @click.stop="openModelDialog"
             v-log="{ describe: { action: '点击编辑模特图', service: '模特图生成-弹窗' } }">
-            <el-icon>
-              <EditPen />
-            </el-icon>
+            <el-icon><EditPen /></el-icon>
           </div>
         </div>
 
@@ -56,51 +52,50 @@
             @click.stop class="tab-checkbox" />
           <div class="tab-content">
             <div class="tab-img flex">
-              <img v-if="form.services.includes('is_detail')" src="@/assets/images/detail/xqy_h.svg" alt="详情页生成"
+              <img v-if="form.services.includes('is_detail')" src="@/assets/images/detail/xqy_h.svg" alt="Detail Generation"
                 class="tab-icon" />
-              <img v-else src="@/assets/images/detail/xqy.svg" alt="详情页生成" class="tab-icon" />
+              <img v-else src="@/assets/images/detail/xqy.svg" alt="Detail Generation" class="tab-icon" />
             </div>
-            <span class="tab-name">详情页生成</span>
+            <span class="tab-name">{{ $t('photoDetail2.detailGenerate') }}</span>
           </div>
         </div>
 
-        <!-- 未开发的功能 -->
-        <div class="service-tab " v-if="useUserInfoStore.userInfo.brand_company_code == 1300" title="新增自定义详情页模板" @click="addCustomTemplate"
+        <div class="service-tab " v-if="useUserInfoStore.userInfo.brand_company_code == 1300" :title="$t('photoDetail2.addCustomTemplate')" @click="addCustomTemplate"
              v-log="{ describe: { action: '点击新增自定义详情页模板', service: '新增自定义详情页模板' } }">
           <div class="tab-content">
             <div class="tab-img flex">
-              <img src="@/assets/images/detail/xqmb.svg" alt="详情页模板自定义" class="tab-icon" />
+              <img src="@/assets/images/detail/xqmb.svg" alt="Detail Template Custom" class="tab-icon" />
             </div>
-            <span class="tab-name">新增自定义详情页模板 {{}}</span>
+            <span class="tab-name">{{ $t('photoDetail2.addCustomTemplate') }}</span>
           </div>
         </div>
 
-        <div class="service-tab disabled" v-else title="功能开发中">
+        <div class="service-tab disabled" v-else :title="$t('photoDetail2.functionDeveloping')">
           <div class="tab-content">
             <div class="tab-img flex">
-              <img src="@/assets/images/detail/xqmb.svg" alt="详情页模板自定义" class="tab-icon" />
+              <img src="@/assets/images/detail/xqmb.svg" alt="Detail Template Custom" class="tab-icon" />
             </div>
-            <span class="tab-name">详情页模板自定义</span>
+            <span class="tab-name">{{ $t('photoDetail2.detailTemplateCustom') }}</span>
           </div>
         </div>
 
-        <div class="service-tab external-tool" title="白底图批量导出" @click="handleWhiteBgExportClick"
+        <div class="service-tab external-tool" :title="$t('photoDetail2.whiteBgExport')" @click="handleWhiteBgExportClick"
           v-log="{ describe: { action: '点击白底图批量导出', service: '白底图批量导出' } }">
           <div class="tab-content">
             <div class="tab-img flex">
-              <img src="@/assets/images/detail/bdt.svg" alt="白底图批量导出" class="tab-icon" />
+              <img src="@/assets/images/detail/bdt.svg" alt="White BG Export" class="tab-icon" />
             </div>
-            <span class="tab-name">白底图批量导出</span>
+            <span class="tab-name">{{ $t('photoDetail2.whiteBgExport') }}</span>
           </div>
         </div>
 
-        <div class="service-tab external-tool" title="产品图册生成" @click="handleProductAlbumClick"
+        <div class="service-tab external-tool" :title="$t('photoDetail2.productAlbum')" @click="handleProductAlbumClick"
           v-log="{ describe: { action: '点击产品图册生成', service: '产品图册生成' } }">
           <div class="tab-content">
             <div class="tab-img flex">
-              <img src="@/assets/images/detail/cptc.svg" alt="产品图册生成" class="tab-icon" />
+              <img src="@/assets/images/detail/cptc.svg" alt="Product Album" class="tab-icon" />
             </div>
-            <span class="tab-name">产品图册生成</span>
+            <span class="tab-name">{{ $t('photoDetail2.productAlbum') }}</span>
           </div>
         </div>
       </div>
@@ -181,7 +176,7 @@
               <div :class="['template-section', { 'template-section--disabled': !isDetailServiceSelected }]">
                 <div class="section-header">
                   <div class="section-title">
-                    选择详情模版
+                    {{ $t('photoDetail2.selectTemplate') }}
                   </div>
                   <div class="template-pagination">
                     <el-pagination background layout="prev, pager, next" v-model:current-page="queryParams.current"
@@ -199,7 +194,7 @@
                          v-if="isDetailServiceSelected && template.template_type == 1"
                          @click.stop="downloadTplExcel(template)"
                          v-log="{ describe: { action: '点击下载EXECL模版', template_name: template.template_name } }">
-                      下载EXECL模版
+                      {{ $t('photoDetail2.downloadExcelTemplate2') }}
                     </div>
                     <el-image :src="getTemplateDisplayImage(template)" fit="contain" class="cur-p"
                       style="width: 100%; display: block;" />
@@ -214,15 +209,15 @@
                         <div class="template-view"
                              @click.stop="viewTemplate(template)"
                              v-log="{ describe: { action: '点击查看模板详情', template_name: template.template_name } }">
-                          查看
+                          {{ $t('photoDetail2.viewDetails') }}
                         </div>
                         <div class="template-view" v-if="template.template_type == 1" @click.stop="editTemplate(template)"
                           v-log="{ describe: { action: '点击编辑模板详情', template_name: template.template_name } }">
-                          编辑
+                          {{ $t('photoDetail2.edit') }}
                         </div>
                         <div class="template-view template-view-delete"  v-if="template.template_type == 1" @click.stop="deleteTemplate(template)"
                           v-log="{ describe: { action: '点击删除自定义模板', template_name: template.template_name } }">
-                          删除
+                          {{ $t('photoDetail2.delete') }}
                         </div>
                       </div>
                     </div>
@@ -233,10 +228,8 @@
                   <el-icon>
                     <Warning />
                   </el-icon>
-                  <span class="mar-left-10" v-if="!isDetailServiceSelected">请先选中详情页生成,后在选择模板详情</span>
-                  <span class="mar-left-10" v-else>该模版需提供{{ form.selectTemplate?.template_image_order?.split(',').length
-                    || 5}}张标准视角的商品图:{{ form.selectTemplate?.template_image_order ||
-                    '俯视,侧视,后跟,鞋底,内里'}}。请确保图片清晰度高,背景干净。</span>
+                  <span class="mar-left-10" v-if="!isDetailServiceSelected">{{ $t('photoDetail2.selectTemplateFirst') }}</span>
+                  <span class="mar-left-10" v-else>{{ $t('photoDetail2.templateRequires', { count: form.selectTemplate?.template_image_order?.split(',').length || 5, order: form.selectTemplate?.template_image_order || '俯视,侧视,后跟,鞋底,内里' }) }}</span>
                 </div>
               </div>
             </div>
@@ -247,18 +240,17 @@
               <div class="right-section">
                 <div class="section-title">
                   <div class="section-title-line"></div>
-                  主图LOGO
+                  {{ $t('photoDetail2.mainImageLogo') }}
                 </div>
                 <div class="logo-upload-area">
                   <div v-if="!form.logo_path" class="logo-upload-placeholder" :class="{ 'is-error': logoLoadError }"
                     @click="openLogoUpload">
                     <div class="logo-upload-icon">
                       <img src="@/assets/images/detail/sctp.png" />
-
                     </div>
-                    <div class="logo-upload-text">点击或拖拽上传</div>
-                    <div class="logo-upload-hint" v-if="logoLoadError">LOGO指向的文件不存在或加载失败,请重新上传</div>
-                    <div class="logo-upload-hint" v-else>支持PNG、JPG格式</div>
+                    <div class="logo-upload-text">{{ $t('photoDetail2.clickOrDragUpload') }}</div>
+                    <div class="logo-upload-hint" v-if="logoLoadError">{{ $t('photoDetail2.logoFileNotExist') }}</div>
+                    <div class="logo-upload-hint" v-else>{{ $t('photoDetail2.supportPngJpg') }}</div>
                   </div>
                   <div v-else class="logo-upload-preview">
                     <img :src="'file:///' + form.logo_path" alt="LOGO预览" class="logo-preview-image"
@@ -276,8 +268,8 @@
                       </span>
                     </div>
                     <div class="logo-upload-footer">
-                      <el-button type="primary" link @click.stop="openLogoUpload">重新上传</el-button>
-                      <el-button type="danger" link @click.stop="removeLogo">删除</el-button>
+                      <el-button type="primary" link @click.stop="openLogoUpload">{{ $t('photoDetail2.reupload') }}</el-button>
+                      <el-button type="danger" link @click.stop="removeLogo">{{ $t('common.delete') }}</el-button>
                     </div>
                   </div>
                 </div>
@@ -287,18 +279,18 @@
               <div class="right-section data-prep-section">
                 <div class="section-title">
                   <div class="section-title-line"></div>
-                  详情资料准备
+                  {{ $t('photoDetail2.dataPrep') }}
                 </div>
                 <div class="data-prep-content">
                   <el-radio-group v-model="form.dataType" class="data-type-radio">
                     <el-radio-button label="1" size="large">
                       <img v-if="form.dataType == 1" src="@/assets/images/detail/excel_h.png" />
                       <img v-else src="@/assets/images/detail/excel.png" />
-                      Excel上传</el-radio-button>
+                      {{ $t('photoDetail2.excelUpload') }}</el-radio-button>
                     <el-radio-button label="2" size="large">
                       <img v-if="form.dataType == 2" src="@/assets/images/detail/xtdj_h.png" />
                       <img v-else src="@/assets/images/detail/xtdj.png" />
-                      系统对接</el-radio-button>
+                      {{ $t('photoDetail2.systemIntegration') }}</el-radio-button>
                   </el-radio-group>
 
                   <div v-if="form.dataType == '1'" class="excel-upload-section">
@@ -307,7 +299,7 @@
                       <div class="excel-icon">
                         <img src="@/assets/images/detail/file-excel.png" class="tab-icon" />
                       </div>
-                      <div class="excel-upload-text">点击选择文件</div>
+                      <div class="excel-upload-text">{{ $t('photoDetail2.clickSelectFile') }}</div>
                     </div>
                     <div v-else class="excel-selected-info">
                       <div class="excel-file-info">
@@ -315,19 +307,19 @@
                           <img src="@/assets/images/detail/file-excel.png" class="tab-icon" />
                         </div>
                         <div class="excel-file-text">
-                          <div class="excel-file-label">已选择文件</div>
+                          <div class="excel-file-label">{{ $t('photoDetail2.fileSelected') }}</div>
                           <div class="excel-file-name" :title="excelFileName">{{ excelFileName }}</div>
                         </div>
                       </div>
                       <el-button type="primary" link class="reupload-btn" @click="selectExcel"
                         v-log="{ describe: { action: '重新上传Excel文件' } }">
-                        重新上传
+                        {{ $t('photoDetail2.reupload') }}
                       </el-button>
                     </div>
                     <el-button type="text" class="download-link" @click="downloadExcel"
-                               title="用于非自定义详情页的商品基础资料模版"
+                               :title="$t('photoDetail2.forNonCustomDetail')"
                       v-log="{ describe: { action: '点击下载Excel模板' } }">
-                      下载通用商品基础资料模版
+                      {{ $t('photoDetail2.downloadExcelTemplate') }}
                     </el-button>
                   </div>
                 </div>
@@ -338,20 +330,20 @@
                 v-if="onlineStoreTempList.length || onlineStoreTempListForeign.length">
                 <div class="section-title">
                   <div class="section-title-line"></div>
-                  一键上架平台
+                  {{ $t('photoDetail2.oneKeyPublish') }}
                 </div>
                 <div class="publish-content">
                   <div class="publish-form-item" v-if="onlineStoreTempList.length">
-                    <div class="publish-label">国内电商平台:</div>
-                    <el-select v-model="domesticPlatforms" multiple placeholder="请选择" class="publish-select"
+                    <div class="publish-label">{{ $t('photoDetail2.domesticEcommerce') }}</div>
+                    <el-select v-model="domesticPlatforms" multiple :placeholder="$t('validation.pleaseSelect')" class="publish-select"
                       :disabled="!canUsePublishSection">
                       <el-option v-for="store in onlineStoreTempList" :key="store.show_name" :label="store.show_name"
                         :value="store.online_store_name" :disabled="!store.channel_status" />
                     </el-select>
                   </div>
                   <div class="publish-form-item" v-if="onlineStoreTempListForeign.length">
-                    <div class="publish-label">跨境电商平台:</div>
-                    <el-select v-model="foreignPlatforms" multiple placeholder="请选择" class="publish-select"
+                    <div class="publish-label">{{ $t('photoDetail2.crossBorderEcommerce') }}</div>
+                    <el-select v-model="foreignPlatforms" multiple :placeholder="$t('validation.pleaseSelect')" class="publish-select"
                       :disabled="!canUsePublishSection">
                       <el-option v-for="store in onlineStoreTempListForeign" :key="store.show_name"
                         :label="store.show_name" :value="store.online_store_name" :disabled="!store.channel_status" />
@@ -364,7 +356,7 @@
                 <el-button v-loading="requesting" class="button--primary1 footer-button" type="primary"
                   @click="generate" v-log="{ describe: { action: '点击开始生成详情页' } }">
                   <img src="@/assets/images/processImage.vue/sc.png" />
-                  开始生成
+                  {{ $t('photoDetail2.startGenerate') }}
                   <img src="@/assets/images/processImage.vue/go.png" class="go" />
                 </el-button>
               </div>
@@ -425,9 +417,9 @@
     <template #progressMessages>
       <div class="progress-messages" v-if="progressMessages.length">
         <div class="message-header">
-          <span>处理进度</span>
+          <span>{{ $t('photoDetail2.processingProgress') }}</span>
           <div class="flex right" style="gap:8px; align-items:center;">
-            <el-button type="text" @click="openOutputDir" v-log="{ describe: { action: '点击打开输出目录' } }">打开目录</el-button>
+            <el-button type="text" @click="openOutputDir" v-log="{ describe: { action: '点击打开输出目录' } }">{{ $t('photoDetail2.openFolder') }}</el-button>
             <el-button type="text" @click="showMessageHistory = !showMessageHistory"
               v-log="{ describe: { action: '点击查看进度详情' } }">
               {{ showMessageHistory ? '收起' : '查看详情' }}
@@ -438,7 +430,7 @@
           <div v-for="(msg, index) in progressMessages" :key="index" class="message-item flex left">
             <div class="message-time">{{ formatTime(msg.timestamp) }}</div>
             <div class="message-content mar-left-10" v-if="msg">
-              <span class="goods-no" v-if="msg.goods_art_nos && msg.goods_art_nos.length > 0">货号{{
+              <span class="goods-no" v-if="msg.goods_art_nos && msg.goods_art_nos.length > 0">{{ $t('photoDetail2.goodsNo') }}{{
                 msg.goods_art_nos.join(', ') }}:</span>
               <span class="message-text">{{ msg.msg }}</span>
             </div>
@@ -452,7 +444,7 @@
   </el-dialog>
 
   <!-- LOGO预览弹窗 -->
-  <el-dialog v-model="logoPreviewVisible" title="LOGO预览">
+  <el-dialog v-model="logoPreviewVisible" :title="$t('photoDetail2.logoPreview')">
     <img style="width: 100%;" :src="logoPreviewUrl" alt="LOGO Preview" />
   </el-dialog>
 
@@ -508,9 +500,9 @@ import GoodsSelectDialog from './components/GoodsSelectDialog.vue'
 
 import { Close, Warning } from '@element-plus/icons-vue'
 import LoadingDialog from '@/views/Photography/components/LoadingDialog.vue'
-
 import configInfo from "@/stores/modules/config";
 import { deleteCustomerTemplate  , downlaodCustomerTemplate} from '@/apis/other'
+import i18n from '@/locales'
 
 const useConfigInfoStore = configInfo();
 
@@ -524,7 +516,7 @@ const { } = usePhotography()
 
 const launchExternalTool = async (pagePath: string, successMessage: string) => {
   if (!clientStore?.ipc || !clientStore.isClient) {
-    ElMessage.error('当前环境暂不支持外部工具');
+    ElMessage.error(i18n.global.t('common.error'));
     return;
   }
   if (!pagePath) return;
@@ -542,16 +534,16 @@ const launchExternalTool = async (pagePath: string, successMessage: string) => {
     if (result?.code === 0) {
       ElMessage.success(successMessage);
     } else {
-      throw new Error(result?.msg || '启动外部工具失败');
+      throw new Error(result?.msg || i18n.global.t('message.configSwitchFailed'));
     }
   } catch (error: any) {
     console.error('launchExternalTool error:', error);
-    ElMessage.error(error?.message || '打开外部工具失败');
+      ElMessage.error(error?.message || i18n.global.t('message.configSwitchFailed'));
   }
 }
 
-const handleWhiteBgExportClick = () => launchExternalTool('/copy_800_tool', '白底图批量导出工具已启动')
-const handleProductAlbumClick = () => launchExternalTool('/product_list', '产品图册生成工具已启动')
+const handleWhiteBgExportClick = () => launchExternalTool('/copy_800_tool', $t('photoDetail2.whiteBgExportStarted'))
+const handleProductAlbumClick = () => launchExternalTool('/product_list', $t('photoDetail2.productAlbumStarted'))
 
 // 货号选择弹窗相关状态
 const goodsSelectDialogVisible = ref(false)
@@ -599,7 +591,7 @@ const completeDirectory = ref('')
 
 const loadingDialogVisible = ref(false)
 const progress = ref(0)
-const message = ref('正在为您处理,请稍后')
+const message = ref(i18n.global.t('photoDetail2.processing'))
 const disabledButton = ref(true)
 
 // 新的进度条相关数据
@@ -621,10 +613,10 @@ const deleteTemplate = async (template: any) => {
   try {
     await ElMessageBox.confirm(
       `确定要删除自定义模板「${template.template_name || ''}」吗?`,
-      '提示',
+      i18n.global.t('common.tips'),
       {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
+        confirmButtonText: i18n.global.t('common.confirm'),
+        cancelButtonText: i18n.global.t('common.cancel'),
         type: 'warning',
       },
     )
@@ -633,19 +625,19 @@ const deleteTemplate = async (template: any) => {
     const data = res
 
     if (data?.code === 0) {
-      ElMessage.success('删除成功')
+      ElMessage.success(i18n.global.t('photoDetail2.deleteSuccess'))
       // 从当前模板列表中移除
       const idx = templates.value.findIndex((item: any) => item.id === template.id)
       if (idx !== -1) {
         templates.value.splice(idx, 1)
       }
     } else {
-      ElMessage.error(data?.msg || '删除失败')
+      ElMessage.error(data?.msg || i18n.global.t('photoDetail2.deleteFailed'))
     }
   } catch (e: any) {
     if (e !== 'cancel' && e !== 'close') {
       console.error('删除模板失败:', e)
-      ElMessage.error(e?.message || '删除失败')
+      ElMessage.error(e?.message || i18n.global.t('photoDetail2.deleteFailed'))
     }
   }
 
@@ -1407,11 +1399,10 @@ const generate = async function () {
   window.modelOrSceneFolderOpened = false;
 
   if (form.services.length == 0) {
-    ElMessage.error('请选择服务内容')
+    ElMessage.error(i18n.global.t('photoDetail2.pleaseSelectService'))
     return
   }
 
-  // 必填验证
   if (form.services.includes('is_upper_footer')) {
     const hasMale = selectedModels.value?.male?.id
     const hasFemale = selectedModels.value?.female?.id
@@ -1419,19 +1410,19 @@ const generate = async function () {
     if (!hasMale && !hasFemale) {
       openModelDialog();
       setTimeout(()=>{
-        ElMessage.error('请选择男模特和女模特')
+        ElMessage.error(i18n.global.t('photoDetail2.pleaseSelectMaleFemaleModel'))
       },200)
       return
     } else if (!hasMale) {
       openModelDialog('male');
       setTimeout(()=>{
-        ElMessage.error('请选择男模特')
+        ElMessage.error(i18n.global.t('photoDetail2.pleaseSelectMaleModel'))
       },200)
       return
     } else if (!hasFemale) {
       openModelDialog('female');
       setTimeout(()=>{
-        ElMessage.error('请选择女模特')
+        ElMessage.error(i18n.global.t('photoDetail2.pleaseSelectFemaleModel'))
       },200)
       return
     }
@@ -1440,14 +1431,14 @@ const generate = async function () {
   if (form.services.includes('is_product_scene') && !scenePrompt.value) {
     openScenePromptDialog();
     setTimeout(() => {
-      ElMessage.error('请设置场景提示词')
+      ElMessage.error(i18n.global.t('photoDetail2.pleaseSetScenePrompt'))
     }, 200)
     return
   }
 
   if (form.services.includes('is_detail') || form.services.includes('is_upper_footer')) {
     if (form.dataType == '1' && !form.excel_path) {
-      ElMessage.error('请上传商品基础资料')
+      ElMessage.error(i18n.global.t('photoDetail2.pleaseUploadGoodsData'))
       return
     }
   }
@@ -1762,7 +1753,7 @@ const handleLogoLoadError = () => {
     logoLoadError.value = true
     form.logo_path = ''
     saveLogoToCache('')
-    ElMessage.warning('LOGO加载出错或文件已被删除,请重新上传')
+    ElMessage.warning(i18n.global.t('photoDetail2.logoLoadError'))
   }
 }
 

+ 13 - 244
frontend/src/views/Photography/expired.vue

@@ -1,45 +1,20 @@
 <template>
   <div class="expired-page">
     <div class="expired-container">
-      <!-- 顶部警告图标区域 -->
       <div class="expired-header">
         <div class="expired-icon">
-          <el-icon size="80" color="#FF4D4F">
-            <Warning />
-          </el-icon>
+          <el-icon size="80" color="#FF4D4F"><Warning /></el-icon>
         </div>
-        <h1 class="expired-title">企业账户已过期</h1>
+        <h1 class="expired-title">{{ $t('expired.title') }}</h1>
       </div>
-
-      <!-- 主要内容区域 -->
       <div class="expired-content">
         <div class="expired-message">
           <p class="message-text">{{ expiredMessage }}</p>
-<!--          <p class="message-hint">请联系管理员处理后重新登录使用</p>-->
         </div>
-
-        <!-- 操作按钮 -->
         <div class="expired-actions">
-<!--          <el-button
-            type="primary"
-            size="large"
-            @click="handleContactAdmin"
-            class="action-btn"
-          >
-            联系管理员
-          </el-button>-->
-          <el-button
-            type="default"
-            size="large"
-            @click="handleLogout"
-            class="action-btn logout-btn"
-          >
-            重新登录
-          </el-button>
+          <el-button type="default" size="large" @click="handleLogout" class="action-btn logout-btn">{{ $t('expired.relogin') }}</el-button>
         </div>
       </div>
-
-      <!-- 底部装饰 -->
       <div class="expired-footer">
         <div class="footer-decoration">
           <div class="decoration-line"></div>
@@ -52,241 +27,35 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted } from 'vue'
+import { computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { Warning } from '@element-plus/icons-vue'
 import { ElMessageBox, ElMessage } from 'element-plus'
+import { useI18n } from 'vue-i18n'
 import useUserInfo from '@/stores/modules/user'
 
+const { t } = useI18n()
 const router = useRouter()
 const useUserInfoStore = useUserInfo()
 const { expiredMessage, loginOut } = useUserInfoStore
 
-// 获取过期消息
 const expiredMessageText = computed(() => {
-  return expiredMessage.value || '您的企业账户已过期,无法继续使用系统功能。'
+  return expiredMessage.value || t('expired.expiredMsg')
 })
 
-// 联系管理员
-const handleContactAdmin = () => {
-  ElMessageBox({
-    title: '联系方式',
-    message: '请通过以下方式联系管理员:\n\n电话:400-xxx-xxxx\n邮箱:admin@company.com\n\n或扫描下方二维码添加客服微信',
-    showCancelButton: false,
-    confirmButtonText: '知道了',
-    type: 'info'
-  })
-}
-
-// 重新登录
 const handleLogout = async () => {
   try {
     await loginOut()
-    // 退出登录后会自动跳转到首页
   } catch (error) {
-    console.error('退出登录失败:', error)
-    ElMessage.error('退出登录失败,请重试')
+    ElMessage.error(t('expired.logoutFailed'))
   }
 }
-
-onMounted(() => {
-  // 页面加载时的埋点或其他初始化逻辑
-  console.log('企业过期页面已加载')
-})
 </script>
 
 <style lang="scss" scoped>
-.expired-page {
-  min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 20px;
-}
-
-.expired-container {
-  background: #fff;
-  border-radius: 20px;
-  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
-  padding: 60px;
-  max-width: 600px;
-  width: 100%;
-  text-align: center;
-  position: relative;
-  overflow: hidden;
-
-  &::before {
-    content: '';
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    height: 4px;
-    background: linear-gradient(90deg, #FF4D4F 0%, #FF7875 100%);
-  }
-}
-
-.expired-header {
-  margin-bottom: 40px;
-
-  .expired-icon {
-    margin-bottom: 24px;
-    display: flex;
-    justify-content: center;
-  }
-
-  .expired-title {
-    font-size: 32px;
-    font-weight: 700;
-    color: #1D2129;
-    margin: 0;
-    letter-spacing: 1px;
-  }
-}
-
-.expired-content {
-  .expired-message {
-    margin-bottom: 50px;
-
-    .message-text {
-      font-size: 18px;
-      color: #4E5969;
-      line-height: 1.6;
-      margin-bottom: 16px;
-      font-weight: 500;
-    }
-
-    .message-hint {
-      font-size: 16px;
-      color: #86909C;
-      line-height: 1.5;
-    }
-  }
-
-  .expired-actions {
-    display: flex;
-    gap: 20px;
-    justify-content: center;
-    flex-wrap: wrap;
-
-    .action-btn {
-      min-width: 140px;
-      height: 48px;
-      border-radius: 8px;
-      font-size: 16px;
-      font-weight: 600;
-
-      &.logout-btn {
-        border-color: #D9D9D9;
-        color: #666;
-        background: #FAFAFA;
-
-        &:hover {
-          border-color: #BFBFBF;
-          background: #F5F5F5;
-        }
-      }
-    }
-  }
-}
-
-.expired-footer {
-  margin-top: 50px;
-
-  .footer-decoration {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 12px;
-
-    .decoration-line {
-      width: 40px;
-      height: 2px;
-      background: #E5E6EB;
-      border-radius: 1px;
-    }
-
-    .decoration-dot {
-      width: 8px;
-      height: 8px;
-      background: #E5E6EB;
-      border-radius: 50%;
-    }
-  }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
-  .expired-page {
-    padding: 10px;
-  }
-
-  .expired-container {
-    padding: 40px 30px;
-    max-width: 100%;
-  }
-
-  .expired-header {
-    .expired-title {
-      font-size: 28px;
-    }
-  }
-
-  .expired-content {
-    .expired-message {
-      margin-bottom: 40px;
-
-      .message-text {
-        font-size: 16px;
-      }
-
-      .message-hint {
-        font-size: 14px;
-      }
-    }
-
-    .expired-actions {
-      flex-direction: column;
-      align-items: center;
-
-      .action-btn {
-        width: 100%;
-        max-width: 280px;
-      }
-    }
-  }
-
-  .expired-footer {
-    margin-top: 40px;
-  }
-}
-
-@media (max-width: 480px) {
-  .expired-container {
-    padding: 30px 20px;
-  }
-
-  .expired-header {
-    margin-bottom: 30px;
-
-    .expired-icon .el-icon {
-      font-size: 60px;
-    }
-
-    .expired-title {
-      font-size: 24px;
-    }
-  }
-
-  .expired-content {
-    .expired-message {
-      margin-bottom: 30px;
-    }
-
-    .expired-actions {
-      gap: 12px;
-    }
-  }
-}
+.expired-page { min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; padding: 20px; }
+.expired-container { background: #fff; border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); padding: 60px; max-width: 600px; width: 100%; text-align: center; position: relative; overflow: hidden; &::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, #FF4D4F 0%, #FF7875 100%); } }
+.expired-header { margin-bottom: 40px; .expired-icon { margin-bottom: 24px; display: flex; justify-content: center; } .expired-title { font-size: 32px; font-weight: 700; color: #1D2129; margin: 0; letter-spacing: 1px; } }
+.expired-content { .expired-message { margin-bottom: 50px; .message-text { font-size: 18px; color: #4E5969; line-height: 1.6; margin-bottom: 16px; font-weight: 500; } } .expired-actions { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; .action-btn { min-width: 140px; height: 48px; border-radius: 8px; font-size: 16px; font-weight: 600; } .logout-btn { border-color: #D9D9D9; color: #666; background: #FAFAFA; &:hover { border-color: #BFBFBF; background: #F5F5F5; } } } }
+.expired-footer { margin-top: 50px; .footer-decoration { display: flex; align-items: center; justify-content: center; gap: 12px; .decoration-line { width: 40px; height: 2px; background: #E5E6EB; border-radius: 1px; } .decoration-dot { width: 8px; height: 8px; background: #E5E6EB; border-radius: 50%; } } }
 </style>

+ 87 - 81
frontend/src/views/Photography/mixin/usePhotography.ts

@@ -1,4 +1,6 @@
 import { ref, onMounted, onBeforeUnmount, watchEffect, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import i18n from '@/locales'
 import icpList from '@/utils/ipc'
 import client from "@/stores/modules/client";
 import socket from "@/stores/modules/socket";
@@ -6,7 +8,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import { getFilePath, getRouterUrl } from '@/utils/appfun'
 import { useRouter, useRoute } from "vue-router";
 import checkInfo from "@/stores/modules/check";
-import generate from '@/utils/menus/generate'
+import { openGaenrate } from '@/utils/menus/generate'
 import { clickLog, setLogInfo } from '@/utils/log'
 import { useUuidStore } from '@/stores/modules/uuid'
 import useUserInfo from "@/stores/modules/user";
@@ -15,6 +17,7 @@ import tokenInfo from "@/stores/modules/token";
 import { getAllUserConfigs } from '@/apis/setting';
 
 export default function usePhotography() {
+    const { t } = useI18n()
     const loading = ref(false)
     const runLoading = ref(false)
     const takePictureLoading = ref(false)
@@ -127,7 +130,7 @@ export default function usePhotography() {
     function saveGoodsArtNo() {
       if (goods_art_no_tpl.value) {
         goods_art_no.value = goods_art_no_tpl.value
-        ElMessage.success('商品货号' + goods_art_no.value + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
+        ElMessage.success(t('photoShot.goodsSuccess', { goods: goods_art_no.value }))
       }
     }
 
@@ -198,7 +201,7 @@ export default function usePhotography() {
      */
     async function runGoods(data) {
       if (runLoading.value || takePictureLoading.value) {
-        ElMessage.error('拍摄程序正在运行,请稍候')
+        ElMessage.error(t('message.shootingInProgress'))
         return
       }
 
@@ -228,7 +231,7 @@ export default function usePhotography() {
           return;
         } else {
 
-          ElMessage.success('开始拍摄,请稍后')
+          ElMessage.success(t('message.startShooting'))
         }
       })
 
@@ -251,9 +254,9 @@ export default function usePhotography() {
     async function delAll() {
       let params = goodsList.value.map(item => item.goods_art_no)
       try {
-        await ElMessageBox.confirm('确定要删除当下的历史记录吗?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
+        await ElMessageBox.confirm(t('message.confirmDeleteHistory'), t('message.tips'), {
+          confirmButtonText: t('common.confirm'),
+          cancelButtonText: t('common.cancel'),
         })
         await clickLog({ describe: { action: '点击确认一键删除', goods_art_nos: params } }, route)
         del({ goods_art_nos: params })
@@ -268,9 +271,9 @@ export default function usePhotography() {
      */
     const delGoods = async function (params) {
       try {
-        await ElMessageBox.confirm('确定要删除货号:' + params.goods_art_nos[0] + '的拍摄数据吗?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
+        await ElMessageBox.confirm(t('message.confirmDeleteGoods', { goods: params.goods_art_nos[0] }), t('message.tips'), {
+          confirmButtonText: t('common.confirm'),
+          cancelButtonText: t('common.cancel'),
         })
         await clickLog({ describe: { action: '点击确认删除货号', goods_art_no: params.goods_art_nos?.[0] } }, route)
         del(params)
@@ -292,7 +295,7 @@ export default function usePhotography() {
         console.log("icpList.takePhoto.delectGoodsArts", params);
         if (result.code === 0) {
           isDelGoodsGetList.value = true
-          ElMessage.info('货号删除成功')
+          ElMessage.info(t('message.deleteGoodsSuccess'))
           getPhotoRecords()
           if (reNosObj.value.goods_art_no) {
             runGoods(
@@ -315,9 +318,9 @@ export default function usePhotography() {
       if (!img.id) return;
       if (img.image_path) {
         try {
-          await ElMessageBox.confirm('此操作会先删除此数据,需要继续吗?', '提示', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
+          await ElMessageBox.confirm(t('message.reTakePrompt'), t('message.tips'), {
+            confirmButtonText: t('common.confirm'),
+            cancelButtonText: t('common.cancel'),
           })
           await clickLog({ describe: { action: '点击确认单张重拍', goods_art_no: img.goods_art_no, action_name: img.action_name } }, route)
         } catch (e) {
@@ -390,8 +393,8 @@ export default function usePhotography() {
       }
       async function this_re_take_picture(data) {
 
-        await ElMessageBox.alert('已复位到该视图下,请把鞋子摆放完毕之后,点击按钮开始重拍', '提示', {
-          confirmButtonText: "开始重拍",
+        await ElMessageBox.alert(t('message.reTakeAlertMsg'), t('message.tips'), {
+          confirmButtonText: t('message.reTakeAlertBtn'),
           showClose: false,
           closeOnClickModal: false,
           closeOnPressEscape: false
@@ -425,9 +428,9 @@ export default function usePhotography() {
     const reTakePictureNos = async (goods_art_no, item) => {
 
       try {
-        await ElMessageBox.confirm('此操作会先删除删除货号:' + goods_art_no + '的拍摄数据吗,需要继续吗?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
+        await ElMessageBox.confirm(t('message.reTakeGoodsPrompt', { goods: goods_art_no }), t('message.tips'), {
+          confirmButtonText: t('common.confirm'),
+          cancelButtonText: t('common.cancel'),
         })
         await clickLog({ describe: { action: '点击确认重拍货号', goods_art_no } }, route)
       } catch (e) {
@@ -448,11 +451,11 @@ export default function usePhotography() {
      */
     const next = async function () {
       if (runLoading.value) {
-        ElMessage.error('正在拍摄中,请稍候')
+        ElMessage.error(t('message.shootingInProgress'))
         return;
       }
       if (goodsList.length) {
-        ElMessage.error('请先拍摄商品。')
+        ElMessage.error(t('message.notShooting'))
         return;
       }
     }
@@ -460,7 +463,7 @@ export default function usePhotography() {
     const oneClickStop = () => {
 
       if (!(runLoading.value || takePictureLoading.value)) {
-        ElMessage.error('拍摄程序已结束,不需要单独停止!')
+        ElMessage.error(t('message.alreadyStopped'))
         return
       } else {
 
@@ -508,7 +511,7 @@ export default function usePhotography() {
       clickLog({ describe: { action: '开始生成', goods_count: goodsList.value.length, goods_art_nos: goodsList.value.map(item => item.goods_art_no) } }, route);
 
       if (runLoading.value || takePictureLoading.value) {
-        ElMessage.error('正在拍摄中,请稍候')
+        ElMessage.error(t('message.shootingInProgress'))
         return;
       }
       const { href } = Router.resolve({
@@ -520,7 +523,7 @@ export default function usePhotography() {
 
       clientStore.ipc.removeAllListeners(icpList.utils.openMain);
       let params = {
-        title: '主图与详情生成',
+        title: t('router.photoDetail'),
         width: 3840,
         height: 2160,
         frame: true,
@@ -531,10 +534,10 @@ export default function usePhotography() {
     }
 
     /*高级生成*/
-    const onGenerateCLick = (menu, item) => {
-      if (menu.name === '历史记录') {
+    const onGenerateCLick = (menuItem, item) => {
+      if (menuItem.type === 'history') {
 
-        menu.click()
+        menuItem.click()
         return
       }
       const firstWithImagePath = item.items.find(
@@ -542,13 +545,13 @@ export default function usePhotography() {
       );
 
       if (firstWithImagePath) {
-        menu.click({
+        menuItem.click({
           query: {
             image_path: firstWithImagePath.PhotoRecord.image_path
           }
         })
       } else {
-        menu.click()
+        menuItem.click()
       }
 
     }
@@ -559,7 +562,7 @@ export default function usePhotography() {
 
         const appPath = configInfoStore?.appConfig?.appPath || ''
         if (!appPath) {
-          ElMessage.error('未获取到应用目录 appPath')
+          ElMessage.error(i18n.global.t('message.openDirFailed'))
           return
         }
         const fullPath = `${appPath}\\output`
@@ -570,73 +573,75 @@ export default function usePhotography() {
         });
       } catch (e) {
         console.error(e)
-        ElMessage.error('打开目录失败')
+        ElMessage.error(i18n.global.t('message.openDirError'))
       }
     }
 
+    // 导出 generate 供外部组件(如 processImage.vue)使用,支持 i18n 响应式
+    const generate = computed(() => ({
+      name: t('generate.advancedGenerate'),
+      children: [
+        {
+          name: t('generate.modelImage'),
+          type: 'modelImage',
+          click(configs) { openGaenrate('onFeetImage', configs) },
+        },
+        {
+          name: t('generate.sceneImage'),
+          type: 'sceneImage',
+          click(configs) { openGaenrate('attachScenarios', configs) },
+        },
+        {
+          name: t('generate.generateVideo'),
+          type: 'generateVideo',
+          click(configs) { openGaenrate('video', configs) },
+        },
+        {
+          name: t('generate.history'),
+          type: 'history',
+          click() { openGaenrate('mine') },
+        }
+      ]
+    }))
+
     const menu = computed(() => {
+      const gen = generate.value
       if (configInfoStore.appModel === 2) {
         return [
+          { type: 'setting' },
           {
-            type: 'setting'
-          },
-          {
-            name: '切换模式',
+            name: t('menu.switchMode'),
             click() {
               configInfoStore.updateAppModel(1)
-              Router.push({
-                name: 'PhotographyCheck'
-              })
+              Router.push({ name: 'PhotographyCheck' })
             }
           },
           {
-            name: '生成图目录',
-            click() {
-              openOutputDir()
-            }
+            name: t('menu.generateDir'),
+            click() { openOutputDir() }
           },
-          {
-            ...generate
-          }
+          gen
         ]
       }
       if (useUserInfoStore.userInfo.brand_company_code === '1300' || configInfoStore.appConfig.debug) {
         return [
+          { type: 'setting' },
+          { type: 'developer' },
           {
-            type: 'setting'
+            name: t('menu.generateDir'),
+            click() { openOutputDir() }
           },
-          {
-            type: 'developer'
-          },
-          {
-            name: '生成图目录',
-            click() {
-              openOutputDir()
-            }
-          },
-          {
-            ...generate,
-          }
+          gen,
         ]
       }
-
-
       return [
+        { type: 'setting' },
         {
-          type: 'setting'
-        },
-        {
-          name: '生成图目录',
-          click() {
-            openOutputDir()
-          }
+          name: t('menu.generateDir'),
+          click() { openOutputDir() }
         },
-        {
-          ...generate
-        }
+        gen
       ]
-
-
     })
 
 const onRemoteControl = async (type) => {
@@ -647,11 +652,11 @@ const onRemoteControl = async (type) => {
     clickLog({ describe: { action: '点击遥控器拍照按钮', point_name: pointName } }, route);
 
     if (runLoading.value || takePictureLoading.value) {
-      ElMessage.error('拍摄程序正在运行,请稍候')
+      ElMessage.error(i18n.global.t('message.shootingInProgress'))
       return
     }
 
-    ElMessage.success('正在拍摄中,请稍候')
+    ElMessage.success(i18n.global.t('message.shootingInProgress'))
     takePictureLoading.value = true;
     socketStore.sendMessage({
       type: 'handler_take_picture',
@@ -664,7 +669,7 @@ const onRemoteControl = async (type) => {
 
 
   if (!goods_art_no.value) {
-    ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
+          ElMessage.error(i18n.global.t('message.remoteControlTip'))
     goodsArtNo.value?.focus() // 聚焦输入框
     return;
   }
@@ -690,7 +695,7 @@ const onRemoteControl = async (type) => {
         if (result.code === 0 && result.data?.data) {
           console.log(goods_art_no.value);
           if (!goods_art_no.value) {
-            ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
+            ElMessage.error(i18n.global.t('message.remoteControlTip'))
             goodsArtNo.value?.focus() // 聚焦输入框
             return;
           }
@@ -776,7 +781,7 @@ const onRemoteControl = async (type) => {
         console.log(result)
         if (result.code === 0) {
   /*        if (runLoading.value || takePictureLoading.value) {
-            ElMessage.error('拍摄程序正在运行,请稍候')
+            ElMessage.error(t('message.shootingInProgress'))
             return
           }*/
 
@@ -910,7 +915,7 @@ const onRemoteControl = async (type) => {
     checkInfoStore.set_blue_tooth_scan_NO('')
     watchEffect(async () => {
       if (checkInfoStore.blue_tooth_scan_NO) {
-        ElMessage.success('商品货号' + checkInfoStore.blue_tooth_scan_NO + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
+        ElMessage.success(i18n.global.t('message.bluetoothScanTip', { no: checkInfoStore.blue_tooth_scan_NO }))
         goods_art_no.value = checkInfoStore.blue_tooth_scan_NO
         checkInfoStore.set_blue_tooth_scan_NO('')
       }
@@ -928,7 +933,7 @@ const onRemoteControl = async (type) => {
         if (result.code === 0) {
           getPhotoRecords()
         } else {
-          ElMessage.error(result.msg || '导入图片失败')
+          ElMessage.error(result.msg || i18n.global.t('message.importImageFailed'))
         }
       });
     }
@@ -942,10 +947,10 @@ const onRemoteControl = async (type) => {
       clientStore.ipc.on(icpList.takePhoto.delete_all_goods_arts, (event, result) => {
         clientStore.ipc.removeAllListeners(icpList.takePhoto.delete_all_goods_arts);
         if (result.code === 0) {
-          ElMessage.success('删除所有货号成功')
+          ElMessage.success(i18n.global.t('message.deleteAllGoodsSuccess'))
           getPhotoRecords()
         } else {
-          ElMessage.error(result.msg || '删除所有货号失败')
+          ElMessage.error(result.msg || i18n.global.t('message.deleteAllGoodsFailed'))
         }
       });
     }
@@ -966,6 +971,7 @@ const onRemoteControl = async (type) => {
       goodsArtNo,
       searchGoodsArtNo,
       menu,
+      generate,
       configInfoStore,
       getTime,
       getFilePath,

+ 28 - 27
frontend/src/views/Photography/processImage.vue

@@ -1,6 +1,6 @@
 <template>
   <headerBar
-      title="处理图像"
+      :title="$t('router.photoProcess')"
       showUser
       :menu="menu"
   />
@@ -13,20 +13,20 @@
           <div class="search-bar">
             <el-input
               v-model="searchGoodsArtNo"
-              placeholder="搜索货号"
+              :placeholder="$t('photoProcess.searchPlaceholder')"
               clearable
               style="width: 300px"
               @keyup.enter="handleSearch"
             >
               <template #append>
-                <el-button @click="handleSearch">搜索</el-button>
+                <el-button @click="handleSearch">{{ $t('common.search') }}</el-button>
               </template>
             </el-input>
           </div>
 
           <div class="history-warp" ref="containerRef">
             <div v-if="!goodsList.length" class="fs-14 c-666 mar-top-50">
-              {{ loading ? '数据正在加载中,请稍候...' : '暂无数据,请先进行拍摄'}}
+              {{ loading ? $t('photoProcess.loading') : $t('photoProcess.noData')}}
             </div>
             <div v-else class="history-item"  v-for="item in goodsList" :key="item.goods_art_no"   >
               <div class="history-item-header">
@@ -45,22 +45,22 @@
                       {{ getTime(item.action_time) }}</span>
                     <span class="image-count mar-left-10 flex left">
                        <img src="@/assets/images/processImage.vue/tup.png" />
-                      {{ item.items?.length || 0 }}张图片</span>
+                      {{ item.items?.length || 0 }} {{ $t('photoProcess.imageCount') }}</span>
                   </div>
                 </div>
                 <div class="history-item-right">
                   <el-dropdown :disabled="runLoading || takePictureLoading" trigger="click">
-                    <el-button :disabled="runLoading || takePictureLoading" size="small" plain>高级生成</el-button>
+                    <el-button :disabled="runLoading || takePictureLoading" size="small" plain>{{ $t('generate.advancedGenerate') }}</el-button>
                     <template #dropdown>
                       <el-dropdown-menu>
                         <el-dropdown-item
-                            v-for="menu in generate.children"
-                            @click.native="onGenerateCLick(menu,item)">{{ menu.name }}</el-dropdown-item>
+                            v-for="menuItem in generate.children"
+                            @click.native="onGenerateCLick(menuItem, item)">{{ menuItem.name }}</el-dropdown-item>
                       </el-dropdown-menu>
                     </template>
                   </el-dropdown>
 
-                  <el-button style="color: #FF4C00"  size="small" class="mar-left-10" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: '删除货号', goods_art_no: item.goods_art_no } }">删除</el-button>
+                  <el-button style="color: #FF4C00"  size="small" class="mar-left-10" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: '删除货号', goods_art_no: item.goods_art_no } }">{{ $t('common.delete') }}</el-button>
                 </div>
               </div>
               <div class="history-item-images" >
@@ -108,10 +108,10 @@
                 @change="toggleSelectAll"
                 class="select-all-checkbox"
               >
-                全选
+                {{ $t('common.all') }}
               </el-checkbox>
               <span class="image-count-text">
-                已选择 <span style="color: #2957FF">{{ selectedImageCount }}</span> 张图片 共 <span style="color: #2957FF">{{ totalImageCount }}</span> 张图片
+                {{ $t('common.selected') }} <span style="color: #2957FF">{{ selectedImageCount }}</span> {{ $t('photoProcess.imageCount') }} {{ $t('common.of') }} <span style="color: #2957FF">{{ totalImageCount }}</span> {{ $t('photoProcess.imageCount') }}
               </span>
             </div>
             <div class="footer-right">
@@ -126,27 +126,27 @@
               <el-button
                 :disabled="selectedGoods.size === 0 || runLoading || takePictureLoading"
                 @click="deleteSelected"
-                v-log="{ describe: { action: '删除选中货号' } }"
+                v-log="{ describe: { action: 'delete selected goods' } }"
               >
-                删除
+                {{ $t('common.delete') }}
               </el-button>
               <el-button
                 :disabled="!goodsList.length || runLoading || takePictureLoading"
                 @click="handleDeleteAll"
                 style="color: #FF4C00"
-                v-log="{ describe: { action: '删除所有货号' } }"
+                v-log="{ describe: { action: 'delete all goods' } }"
               >
-                删除所有
+                {{ $t('common.deleteAll') }}
               </el-button>
               <el-button
                 type="primary"
                 :disabled="!goodsList.length || runLoading || takePictureLoading"
                 @click="openPhotographyDetail()"
-                v-log="{ describe: { action: '点击开始生成' } }"
+                v-log="{ describe: { action: 'start generate' } }"
               >
 
                 <img src="@/assets/images/processImage.vue/sc.png" />
-                开始生成
+                {{ $t('common.startGenerate') || '开始生成' }}
                 <img src="@/assets/images/processImage.vue/go.png"  class="go"/>
               </el-button>
             </div>
@@ -158,15 +158,15 @@
 <script setup lang="ts">
 import headerBar from '@/components/header-bar/index.vue'
 import { onMounted, onBeforeUnmount, ref, computed, nextTick, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
 import HardwareCheck from '@/components/check/index.vue'
 import usePhotography from './mixin/usePhotography'
 import { useThumbnails } from './composables/useThumbnails'
-import generate from '@/utils/menus/generate'
 import { ElMessageBox } from 'element-plus'
 import client from "@/stores/modules/client";
 import icpList from '@/utils/ipc'
-// logging helpers not needed here
 
+const { t } = useI18n()
 const clientStore = client();
 
 const {
@@ -176,6 +176,7 @@ const {
   goodsList,
   runAction,
   menu,
+  generate,
   getTime,
   getFilePath,
   getPhotoRecords,
@@ -296,11 +297,11 @@ const deleteSelected = async () => {
 
   try {
     await ElMessageBox.confirm(
-      `确定要删除选中的 ${selectedGoods.value.size} 个货号的拍摄数据吗?`,
-      '提示',
+      t('message.confirmDeleteSelectedGoods', { count: selectedGoods.value.size }),
+      t('message.tips'),
       {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
       }
     )
 
@@ -351,11 +352,11 @@ const handleImportImage = () => {
 const handleDeleteAll = async () => {
   try {
     await ElMessageBox.confirm(
-      '确定要删除所有货号的拍摄数据吗?',
-      '提示',
+      t('message.confirmDeleteAll'),
+      t('message.tips'),
       {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
+        confirmButtonText: t('common.confirm'),
+        cancelButtonText: t('common.cancel'),
       }
     )
     deleteAllGoods()

+ 30 - 61
frontend/src/views/Photography/seniorDetail.vue

@@ -1,116 +1,97 @@
 <template>
-  <headerBar
-      title="主图与详情高级配置"
-  />
+  <headerBar :title="$t('seniorDetail.title')" />
   <div class="image-config">
     <div class="config-card">
-      <!-- 图片抠图与货号图生成 -->
       <div class="section">
         <div class="section-title">
           <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
-          图片抠图与货号图生成
+          {{ $t('seniorDetail.cutoutAndGoodsGenerate') }}
         </div>
         <div class="section-content">
-
-
           <div v-if="showTips" class="instruction-out flex top left">
             <img style="fill: #000" src="@/assets/images/xinxi.svg" />
             <ol class="instruction-list">
-              <li>请在下方确认图片拍摄过程中的顺序,确保所有拍摄的图片的顺序一致。</li>
-              <li>使用中英文语号分隔。</li>
-              <li>图片的名称不能随意修改,否则无法正常生成详情页。</li>
-              <li>现有图片名称有:俯视、侧视、后视、鞋底、内里</li>
+              <li>{{ $t('seniorDetail.cutoutTip1') }}</li>
+              <li>{{ $t('seniorDetail.cutoutTip2') }}</li>
+              <li>{{ $t('seniorDetail.cutoutTip3') }}</li>
+              <li>{{ $t('seniorDetail.cutoutTip4') }}</li>
             </ol>
             <el-icon @click="showTips = false" class="close-icon" v-log="{ describe: { action: '点击关闭高级配置提示' } }">
               <Close />
             </el-icon>
           </div>
 
-          <!-- 货号文件夹 -->
           <div class="form-item">
-            <div class="label">货号文件夹:</div>
+            <div class="label">{{ $t('seniorDetail.goodsFolder') }}</div>
             <div class="folder-warp">
               <div class="folder-input">
-                <el-input style="width: 60%;" v-model="folderPath" type="textarea"  :rows="2" readonly placeholder="请选择货号文件夹" />
+                <el-input style="width: 60%;" v-model="folderPath" type="textarea"  :rows="2" readonly :placeholder="$t('seniorDetail.selectGoodsFolder')" />
                 <el-button class="check-button" type="primary" @click="selectFolder" v-log="{ describe: { action: '选择货号文件夹' } }">
                   <img src="@/assets/images/Photography/wenjian.png" style="width: 14px; " />
-                  选择目标文件夹</el-button>
+                  {{ $t('seniorDetail.selectTargetFolder') }}</el-button>
               </div>
               <div class="hint">
-                <el-icon>
-                  <Warning />
-                </el-icon> <text>选择货号的上级文件夹</text>
+                <el-icon><Warning /></el-icon>
+                <text>{{ $t('seniorDetail.selectGoodsParentFolder') }}</text>
               </div>
             </div>
           </div>
 
-          <!-- 抠图模式 -->
           <div class="form-item">
-            <div class="label">抠图模式:</div>
+            <div class="label">{{ $t('seniorDetail.cutoutMode') }}</div>
             <el-radio-group v-model="reportMode">
-              <el-radio label="normal">普通模式</el-radio>
-              <el-radio label="optimized">精细化扣图</el-radio>
+              <el-radio label="normal">{{ $t('seniorDetail.normalMode') }}</el-radio>
+              <el-radio label="optimized">{{ $t('seniorDetail.refinedCutout') }}</el-radio>
             </el-radio-group>
           </div>
         </div>
       </div>
       <el-divider />
 
-      <!-- 详情高级配置 -->
       <div class="section">
         <div class="section-title">
           <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
-          详情高级配置
+          {{ $t('seniorDetail.detailAdvancedConfig') }}
         </div>
         <div class="section-content">
-          <!-- 图片顺序 -->
           <div class="form-item">
-            <div class="label">图片顺序:</div>
-            <el-input v-model="imageOrder" placeholder="请输入图片顺序" class="specific-page-input">
+            <div class="label">{{ $t('seniorDetail.imageOrder') }}</div>
+            <el-input v-model="imageOrder" :placeholder="$t('seniorDetail.inputImageOrder')" class="specific-page-input">
               <template #append>
-                <el-button class="explain-btn" link type="primary" v-log="{ describe: { action: '点击图片顺序说明' } }">说明</el-button>
+                <el-button class="explain-btn" link type="primary" v-log="{ describe: { action: '点击图片顺序说明' } }">{{ $t('seniorDetail.imageOrderExplain') }}</el-button>
               </template>
             </el-input>
           </div>
 
-          <!-- 同款检验 -->
           <div class="form-item">
-            <div class="label">同款检验:</div>
-            <el-checkbox v-model="checkSimilar">同款下货号必须齐全</el-checkbox>
+            <div class="label">{{ $t('seniorDetail.sameStyleCheck') }}</div>
+            <el-checkbox v-model="checkSimilar">{{ $t('seniorDetail.sameStyleMustComplete') }}</el-checkbox>
           </div>
 
-          <!-- 可指定页面独修改 -->
           <div class="form-item">
-            <div class="label">可指定页面独修改:</div>
-            <el-input v-model="specificPage" placeholder="请输入入需要单独修改的页面,示例:4:1 (需修改模版的编号:第一张)"
+            <div class="label">{{ $t('seniorDetail.specifyPageModify') }}</div>
+            <el-input v-model="specificPage" :placeholder="$t('seniorDetail.inputSpecifyPage')"
               class="specific-page-input">
               <template #append>
-                <el-button class="explain-btn" link type="primary" v-log="{ describe: { action: '点击指定页面说明' } }">说明</el-button>
+                <el-button class="explain-btn" link type="primary" v-log="{ describe: { action: '点击指定页面说明' } }">{{ $t('seniorDetail.specifyPageExplain') }}</el-button>
               </template>
             </el-input>
           </div>
         </div>
       </div>
 
-      <!-- 底部按钮 -->
       <div class="footer">
-        <el-button class="button--primary1  footer-button" type="primary" @click="saveConfig" v-log="{ describe: { action: '点击保存高级配置' } }">保存配置</el-button>
-
-        <el-button class="button--primary1 footer-button" type="primary" @click="startProcess" v-log="{ describe: { action: '点击开始处理高级配置' } }">开始处理</el-button>
+        <el-button class="button--primary1  footer-button" type="primary" @click="saveConfig" v-log="{ describe: { action: '点击保存高级配置' } }">{{ $t('seniorDetail.saveConfig') }}</el-button>
+        <el-button class="button--primary1 footer-button" type="primary" @click="startProcess" v-log="{ describe: { action: '点击开始处理高级配置' } }">{{ $t('seniorDetail.startProcess') }}</el-button>
       </div>
-
     </div>
 
-
     <loading-dialog v-model="loadingDialogVisible" :progress="progress" :message="message" :show-button="showButton"
       @button-click="handleComplete" />
   </div>
-
 </template>
 
 <script setup lang="ts">
-
-/*高级配置  已废弃*/
 import headerBar from '@/components/header-bar/index.vue'
 import { ref } from 'vue'
 import { Close, Warning } from '@element-plus/icons-vue'
@@ -121,12 +102,11 @@ const clientStore = client();
 
 const showTips = ref(true)
 
-const folderPath = ref('') //货号文件夹
-const reportMode = ref('normal') // 抠图模式
-const imageOrder = ref('俯视、侧视、后跟、鞋底、内里、组合、组合2、组合3') // 图片顺序
-const checkSimilar = ref(false) // 同款检验
-const specificPage = ref('')  // 可指定页面独修改
-
+const folderPath = ref('')
+const reportMode = ref('normal')
+const imageOrder = ref('')
+const checkSimilar = ref(false)
+const specificPage = ref('')
 
 const loadingDialogVisible = ref(false)
 const progress = ref(0)
@@ -142,7 +122,6 @@ const saveConfig = () => {
     specificPage: specificPage.value
   }
   console.log("%c Line:139 🥕 params", "color:#93c0a4", params);
-  // 保存配置逻辑
   console.log('保存配置')
 }
 
@@ -150,10 +129,8 @@ const startProcess = () => {
   loadingDialogVisible.value = true
   progress.value = 0
   showButton.value = true
-  // 模拟进度更新
   const interval = setInterval(() => {
     if (progress.value < 100) {
-    //  progress.value += 10
       progress.value = Math.min(progress.value + 10, 100)
     } else {
       clearInterval(interval)
@@ -165,7 +142,6 @@ const startProcess = () => {
 
 const handleComplete = () => {
   loadingDialogVisible.value = false
-  // 这里可以添加打开目录的逻辑
   console.log('打开目录')
 }
 
@@ -177,12 +153,10 @@ const selectFolder = () => {
     clientStore.ipc.removeAllListeners(icpList.utils.openDirectory);
   })
 }
-
 </script>
 
 <style lang="scss" scoped>
 .image-config {
-
   .config-card {
     background: #EAECED;
     padding: 20px;
@@ -193,7 +167,6 @@ const selectFolder = () => {
       display: flex;
       justify-content: space-between;
       align-items: center;
-
       .header-icons {
         display: flex;
         gap: 8px;
@@ -246,13 +219,10 @@ const selectFolder = () => {
     }
   }
 
-
-
   .form-item {
     margin: 16px 0;
     display: flex;
 
-
     .label {
       min-width: 120px;
       margin-right: 12px;
@@ -275,7 +245,6 @@ const selectFolder = () => {
           color: #2957FF;
           margin-left: 20px;
         }
-
       }
     }
 

+ 90 - 779
frontend/src/views/Photography/shot.vue

@@ -1,12 +1,6 @@
 <template>
-  <headerBar
-      title="拍摄商品"
-      showUser
-      :menu="menu"
-  />
-
+  <headerBar :title="$t('router.photoShot')" showUser :menu="menu" />
   <hardware-check/>
-
   <div class="photography-page flex-col">
     <div class="main-container">
       <div class="content-wrapper flex-col">
@@ -14,36 +8,33 @@
           <div class="step-number flex-col"><span class="text_22">1</span></div>
           <div class="step-one flex-col">
             <div class="step-header flex-row">
-              <span class="step-title">第一步:获取商品货号</span>
+              <span class="step-title">{{ $t('photoShot.step1') }}</span>
             </div>
             <div class="step-content">
               <div class="input-container flex-row">
-                <el-input class="input-item" ref="goodsArtNo" clearable v-model="goods_art_no" placeholder="请输入货号"> </el-input>
+                <el-input class="input-item" ref="goodsArtNo" clearable v-model="goods_art_no" :placeholder="$t('photoShot.inputGoodsPlaceholder')"> </el-input>
               </div>
               <div class="auto-method flex-row justify-between">
                 <img class="step-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/step1-icon.png" />
-                <div class="text-method-tag flex-col mar-left-10"><span class="text_4">自动获取</span></div>
-                <span class="method-description mar-left-10">用遥控器扫描商品资料二维码</span>
+                <div class="text-method-tag flex-col mar-left-10"><span class="text_4">{{ $t('photoShot.autoGet') }}</span></div>
+                <span class="method-description mar-left-10">{{ $t('photoShot.scanTip') }}</span>
               </div>
             </div>
           </div>
-
         </div>
 
         <div class="step-section">
           <div class="step-number flex-col"><span class="text_22">2</span></div>
           <div class="step-two flex-col justify-between">
-            <span class="step-title">第二步:启动拍摄(根据按遥控器左右键启动)</span>
+            <span class="step-title">{{ $t('photoShot.step2') }}</span>
             <div class="shooting-container flex-col">
               <div class="shooting-tips flex-row justify-between">
                 <img class="info-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/info-icon.png" />
-                <span class="tips-text">遥控左右按键可启动拍摄,中间按钮可在拍摄5张主图后解锁,用于拍摄自定义图</span>
+                <span class="tips-text">{{ $t('photoShot.remoteTip') }}</span>
               </div>
               <div class="wifi mar-top-20">
-                <img  referrerpolicy="no-referrer"
-                     src="@/assets/images/Photography/wifi.png" style="width: 60px" />
+                <img  referrerpolicy="no-referrer" src="@/assets/images/Photography/wifi.png" style="width: 60px" />
               </div>
-
               <div class="remote-control-wrap">
                 <RemoteControl
                     @onRemoteControl="onRemoteControl"
@@ -56,13 +47,7 @@
       </div>
 
       <div class="last-photo" v-show="showlastPhoto && (lastPhoto as any)?.file_path" v-key="(lastPhoto as any)?.file_path">
-        <el-button
-          class="close-btn"
-          type="danger"
-          icon="Close"
-          circle
-          @click="closeLastPhoto"
-        />
+        <el-button class="close-btn" type="danger" icon="Close" circle @click="closeLastPhoto" />
         <el-image  :src="getFilePath((lastPhoto as any)?.file_path || '')"  fit="contain" ></el-image>
       </div>
 
@@ -70,19 +55,19 @@
           <div class="search-bar">
             <el-input
               v-model="searchGoodsArtNo"
-              placeholder="搜索货号"
+              :placeholder="$t('photoShot.searchPlaceholder')"
               clearable
               style="width: 300px"
               @keyup.enter="handleSearch"
             >
               <template #append>
-                <el-button @click="handleSearch">搜索</el-button>
+                <el-button @click="handleSearch">{{ $t('common.search') }}</el-button>
               </template>
             </el-input>
           </div>
           <div class="history-warp" ref="containerRef">
             <div v-if="!goodsList.length" class="fs-14 c-666 mar-top-50">
-              {{ loading ? '数据正在加载中,请稍候...' : '暂无数据,请先进行拍摄'}}
+              {{ loading ? $t('photoShot.loading') : $t('photoShot.noData')}}
             </div>
             <div v-else class="history-item"  v-for="item in goodsList" :key="item.goods_art_no"   >
               <div class="history-item-header">
@@ -93,31 +78,30 @@
                       class="goods-checkbox"
                   />
                   <span class="goods-art-no">{{ item.goods_art_no }}</span>
-
                   <div class="history-item-meta ">
                     <span class="action-time flex left">
                        <img src="@/assets/images/processImage.vue/riq.png" />
                       {{ getTime(item.action_time) }}</span>
                     <span class="image-count mar-left-10 flex left">
                        <img src="@/assets/images/processImage.vue/tup.png" />
-                      {{ item.items?.length || 0 }}张图片</span>
-                    <span v-if="!item.syncConfig" class="mar-left-10">无法读取到该商品拍摄配置,如需重新生成请删除该商品并重新拍摄</span>
+                      {{ item.items?.length || 0 }} {{ $t('photoShot.imageCount') }}</span>
+                    <span v-if="!item.syncConfig" class="mar-left-10">{{ $t('message.cannotReadConfig') }}</span>
                   </div>
                 </div>
                 <div class="history-item-right">
                   <el-dropdown :disabled="runLoading || takePictureLoading" trigger="click">
-                    <el-button :disabled="runLoading || takePictureLoading" size="small" plain>高级生成</el-button>
+                    <el-button :disabled="runLoading || takePictureLoading" size="small" plain>{{ $t('photoShot.advancedGenerate') }}</el-button>
                     <template #dropdown>
                       <el-dropdown-menu>
                         <el-dropdown-item
-                            v-for="menu in generate.children"
-                            @click.native="onGenerateCLick(menu,item)">{{ menu.name }}</el-dropdown-item>
+                            v-for="menuItem in generate.children"
+                            @click.native="onGenerateCLick(menuItem, item)">{{ menuItem.name }}</el-dropdown-item>
                       </el-dropdown-menu>
                     </template>
                   </el-dropdown>
 
-                  <el-button size="small" v-if="item.syncConfig"  class="mar-left-10"  :disabled="runLoading || takePictureLoading"  type="primary"  @click="reTakePictureNos(item.goods_art_no,item)" plain v-log="{ describe: { action: '重拍货号', goods_art_no: item.goods_art_no } }">重拍</el-button>
-                  <el-button style="color: #FF4C00"  size="small" class="mar-left-10" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: '删除货号', goods_art_no: item.goods_art_no } }">删除</el-button>
+                  <el-button size="small" v-if="item.syncConfig"  class="mar-left-10"  :disabled="runLoading || takePictureLoading"  type="primary"  @click="reTakePictureNos(item.goods_art_no,item)" plain v-log="{ describe: { action: 'retake goods', goods_art_no: item.goods_art_no } }">{{ $t('photoShot.retake') }}</el-button>
+                  <el-button style="color: #FF4C00"  size="small" class="mar-left-10" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: 'delete goods', goods_art_no: item.goods_art_no } }">{{ $t('photoShot.delete') }}</el-button>
                 </div>
               </div>
               <div class="history-item-images" >
@@ -127,252 +111,96 @@
                   class="history-item_image"
                   v-loading="!image.PhotoRecord.image_path && runAction.goods_art_no == item.goods_art_no"
                 >
-<!--                  <span class="tag" v-if="!image.PhotoRecord.image_path">{{ image.action_name }}</span>-->
-                  <div class="el-image_view" >
-                    <el-image
-                      :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
-                      :preview-src-list="getPreviewImageList(item)"
-                      hide-on-click-modal
-                      :initial-index="getPreviewIndex(item, index)"
-                      class="preview-image"
-                      fit="contain"
-                      :preview-teleported="true"
-                      lazy
-                    >
-                      <template #placeholder>
+                  <span class="tag" v-if="!image.PhotoRecord.image_path">{{ image.action_name }}</span>
+                  <el-image
+                    v-if="image.PhotoRecord.image_path"
+                    :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
+                    :preview-src-list="getPreviewImageList(item)"
+                    hide-on-click-modal
+                    :initial-index="getPreviewIndex(item, index)"
+                    class="preview-image"
+                    fit="contain"
+                    :preview-teleported="true"
+                    lazy
+                  >
+                    <template #placeholder>
+                      <span class="tag">{{ image.action_name }}</span>
+                    </template>
+                    <template #error>
+                      <div class="image-slot">
                         <span class="tag">{{ image.action_name }}</span>
-                      </template>
-                      <template #error>
-                        <div class="image-slot">
-                          <span class="tag">{{ image.action_name }}</span>
-                        </div>
-                      </template>
-                    </el-image>
-                    <el-button v-if="image.action_name && !runLoading && !takePictureLoading" :disabled="runLoading || takePictureLoading" class="reset-button" @click="reTakePicture(image.PhotoRecord)" v-log="{ describe: { action: '重拍单张图片', goods_art_no: image.PhotoRecord.goods_art_no, action_name: image.action_name } }">重拍</el-button>
+                      </div>
+                    </template>
+                  </el-image>
+                  <div v-else class="image-placeholder">
+                    <span class="tag">{{ image.action_name }}</span>
                   </div>
                 </div>
               </div>
             </div>
           </div>
-
-          <div class="footer-controls">
-            <div class="footer-left">
-              <el-checkbox
-                :model-value="isSelectAll"
-                :indeterminate="isIndeterminate"
-                @change="toggleSelectAll"
-                class="select-all-checkbox"
-              >
-                全选
-              </el-checkbox>
-              <span class="image-count-text">
-                已选择 <span style="color: #2957FF">{{ selectedImageCount }}</span> 张图片 共 <span style="color: #2957FF">{{ totalImageCount }}</span> 张图片
-              </span>
-            </div>
-            <div class="footer-right">
-
-              <div class="pagination-container" style="padding: 10px 0; text-align: center;">
-                <el-pagination
-                    :page-count="totalPages"
-                    :current-page="currentPage"
-                    @current-change="(page) => getPhotoRecords({ page })"
-                    layout="prev, pager, next"
-                    :page-size="pageSize"
-                />
-              </div>
-              <el-button
-                :disabled="selectedGoods.size === 0 || runLoading || takePictureLoading"
-                @click="deleteSelected"
-                v-log="{ describe: { action: '删除选中货号' } }"
-              >
-                删除
-              </el-button>
-              <el-button
-                :disabled="!goodsList.length || runLoading || takePictureLoading"
-                @click="handleDeleteAll"
-                style="color: #FF4C00"
-                v-log="{ describe: { action: '删除所有货号' } }"
-              >
-                删除所有
-              </el-button>
-              <el-button
-                type="primary"
-                :disabled="!goodsList.length || runLoading || takePictureLoading"
-                @click="openPhotographyDetail()"
-                v-log="{ describe: { action: '点击开始生成' } }"
-              >
-                <img src="@/assets/images/processImage.vue/sc.png" />
-                开始生成
-                <img src="@/assets/images/processImage.vue/go.png"  class="go"/>
-              </el-button>
-            </div>
-          </div>
-        </div>
+      </div>
     </div>
   </div>
 </template>
+
 <script setup lang="ts">
-import headerBar from '@/components/header-bar/index.vue'
-import { onMounted, onBeforeUnmount, ref, computed, watch, nextTick } from 'vue'
-import HardwareCheck from '@/components/check/index.vue'
-// @ts-ignore
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { ElMessageBox } from 'element-plus'
 import RemoteControl from '@/views/RemoteControl/index.vue'
+import headerBar from '@/components/header-bar/index.vue'
+import hardwareCheck from '@/components/check/index.vue'
 import usePhotography from './mixin/usePhotography'
-import { useThumbnails } from './composables/useThumbnails'
-import generate from '@/utils/menus/generate'
-import { ElMessageBox } from 'element-plus'
+
+const { t } = useI18n()
 
 const {
-  loading,
-  runLoading,
-  takePictureLoading,
-  goodsList,
-  goods_art_no,
-  runAction,
-  lastPhoto,
-  showlastPhoto,
-  goodsArtNo,
-  searchGoodsArtNo,
-  menu,
-  getTime,
-  getFilePath,
-  getPhotoRecords,
-  delGoods,
-  del,
-  deleteAllGoods,
-  reTakePicture,
-  reTakePictureNos,
-  onRemoteControl,
-  openPhotographyDetail,
-  onGenerateCLick,
-  initEventListeners,
-  cleanupEventListeners,
-  pageSize,
-  currentPage,
-  totalPages,
+  loading, runLoading, takePictureLoading, goodsList, pageSize, currentPage,
+  totalPages, goods_art_no_tpl, goods_art_no, runAction, lastPhoto,
+  showlastPhoto, goodsArtNo, searchGoodsArtNo, menu, generate,
+  getTime, getFilePath, getPhotoRecords, delGoods, importDirs, deleteAllGoods,
+  reTakePictureNos, onRemoteControl, initEventListeners, cleanupEventListeners,
 } = usePhotography()
 
-// 搜索货号
-const handleSearch = () => {
-  getPhotoRecords({ page: 1, goods_art_no: searchGoodsArtNo.value })
-}
-
-// 关闭最后拍摄的照片预览
-const closeLastPhoto = () => {
-  showlastPhoto.value = false
-}
-
-// 选中的货号列表
-const selectedGoods = ref<Set<string>>(new Set())
-
-// thumbnails
 const containerRef = ref<HTMLElement | null>(null)
-const { thumbnailMap, observe, stop } = useThumbnails(getFilePath)
-
-// 全选状态
-const isSelectAll = computed(() => {
-  return goodsList.value.length > 0 && selectedGoods.value.size === goodsList.value.length
-})
-
-// 是否半选状态
-const isIndeterminate = computed(() => {
-  return selectedGoods.value.size > 0 && selectedGoods.value.size < goodsList.value.length
-})
+const selectedGoods = ref<Set<string>>(new Set())
+const thumbnailMap = ref<Record<string, string>>({})
 
-// 切换单个货号的选中状态
 const toggleGoods = (goodsArtNo: string) => {
   if (selectedGoods.value.has(goodsArtNo)) {
     selectedGoods.value.delete(goodsArtNo)
   } else {
     selectedGoods.value.add(goodsArtNo)
   }
+  selectedGoods.value = new Set(selectedGoods.value)
 }
 
-// 全选/取消全选
-const toggleSelectAll = () => {
-  if (isSelectAll.value) {
-    selectedGoods.value.clear()
-  } else {
-    goodsList.value.forEach((item: any) => {
-      selectedGoods.value.add(item.goods_art_no)
-    })
-  }
+const handleSearch = () => {
+  getPhotoRecords({ goods_art_no: searchGoodsArtNo.value })
 }
 
-// 计算已选择的图片数量
-const selectedImageCount = computed(() => {
-  let count = 0
-  goodsList.value.forEach((item: any) => {
-    if (selectedGoods.value.has(item.goods_art_no)) {
-      count += item.items?.length || 0
-    }
-  })
-  return count
-})
-
-// 计算总图片数量
-const totalImageCount = computed(() => {
-  let count = 0
-  goodsList.value.forEach((item: any) => {
-    count += item.items?.length || 0
-  })
-  return count
-})
-
-// 删除选中的货号
-const deleteSelected = async () => {
-  if (selectedGoods.value.size === 0) {
-    return
-  }
-
-  try {
-    await ElMessageBox.confirm(
-      `确定要删除选中的 ${selectedGoods.value.size} 个货号的拍摄数据吗?`,
-      '提示',
-      {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-      }
-    )
-
-    const goodsArtNos = Array.from(selectedGoods.value)
-    await del({ goods_art_nos: goodsArtNos })
-    // 删除成功后清空选中状态
-    selectedGoods.value.clear()
-  } catch (e) {
-    // 用户取消
-  }
+const closeLastPhoto = () => {
+  showlastPhoto.value = false
 }
 
-// 删除所有货号
 const handleDeleteAll = async () => {
   try {
-    await ElMessageBox.confirm(
-      '确定要删除所有货号的拍摄数据吗?',
-      '提示',
-      {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-      }
-    )
+    await ElMessageBox.confirm(t('message.confirmDeleteAll'), t('common.tips'), {
+      confirmButtonText: t('common.confirm'),
+      cancelButtonText: t('common.cancel'),
+    })
     deleteAllGoods()
-  } catch (e) {
-    // 用户取消
-  }
+  } catch (e) {}
 }
 
-// 获取预览图片列表(只包含有图片路径的,保持原始顺序)
 const getPreviewImageList = (item: any) => {
   if (!item || !item.items) return []
-  return item.items
-    .filter((img: any) => img.PhotoRecord?.image_path)
-    .map((img: any) => getFilePath(img.PhotoRecord.image_path))
+  return item.items.filter((img: any) => img.PhotoRecord?.image_path).map((img: any) => getFilePath(img.PhotoRecord.image_path))
 }
 
-// 获取当前图片在预览列表中的索引
 const getPreviewIndex = (item: any, currentIndex: number) => {
   if (!item || !item.items) return 0
-  // 计算当前图片在过滤后的预览列表中的索引
   let previewIndex = 0
   for (let i = 0; i <= currentIndex; i++) {
     if (item.items[i]?.PhotoRecord?.image_path) {
@@ -384,20 +212,12 @@ const getPreviewIndex = (item: any, currentIndex: number) => {
 }
 
 onMounted(async () => {
-  await getPhotoRecords()
   initEventListeners()
-  nextTick(() => {
-    observe(containerRef, goodsList)
-  })
+  await getPhotoRecords()
 })
 
 onBeforeUnmount(() => {
   cleanupEventListeners()
-  stop()
-})
-
-watch(goodsList, () => {
-  nextTick(() => observe(containerRef, goodsList))
 })
 </script>
 
@@ -408,538 +228,29 @@ watch(goodsList, () => {
   top: 100px !important;
   height: calc(100vh - 170px) !important;
   transform: translate(0px, 0px) !important;
-
-  .el-image {
-    width: 100%;
-    height:100%;
-    display: block;
-
-    .el-image__inner {
-      width: 100%;
-      height:100%;
-      display: block;
-
-    }
-  }
+  .el-image { width: 100%; height: 100%; display: block; .el-image__inner { width: 100%; height: 100%; display: block; } }
 }
 </style>
 
 <style scoped lang="scss">
-.photography-page {
-  background-color: rgba(255, 255, 255, 1);
-  position: relative;
-  .main-container {
-    position: relative;
-    display: flex;
-    .content-wrapper {
-      flex-grow: 1 ;
-      position: relative;
-      top: 0;
-      left: 0;
-      right: 0;
-      bottom: 0;
-      width: 510px;
-      padding: 0 50px;
-      height: calc(100vh - 30px);
-      margin:  auto;
-      justify-content: flex-start;
-      flex-direction: column;
-      overflow: hidden;
-      border-right:1px solid rgba(0,0,0,.1);
-
-      .step-section {
-        display: flex;
-        margin-bottom: 20px;
-
-        &:last-child {
-          margin-bottom: 0;
-        }
-      }
-
-      .step-number {
-        background-color: rgba(22, 119, 255, 1);
-        border-radius: 50%;
-        height: 32px;
-        margin-top: 51px;
-        width: 32px;
-
-        .text_22 {
-          width: 6px;
-          height: 22px;
-          overflow-wrap: break-word;
-          color: rgba(255, 255, 255, 1);
-          font-size: 14px;
-          font-weight: NaN;
-          text-align: right;
-          white-space: nowrap;
-          line-height: 22px;
-          margin: 5px 0 0 13px;
-        }
-      }
-
-      .step-one {
-        width: 350px;
-        height: auto;
-        margin: 55px 0 0 5px;
-
-        .step-header {
-          width: 391px;
-          height: 24px;
-          margin-left: 3px;
-
-          .step-title {
-            width: 160px;
-            height: 24px;
-            overflow-wrap: break-word;
-            color: rgba(0, 0, 0, 0.85);
-            font-size: 16px;
-            font-family: PingFangSC-Medium;
-            font-weight: 500;
-            text-align: left;
-            white-space: nowrap;
-            line-height: 24px;
-          }
-
-          .step-icon {
-            width: 32px;
-            height: 20px;
-            margin-top: 4px;
-          }
-
-          .step-divider {
-            width: 191px;
-            height: 1px;
-            margin: 13px 0 0 8px;
-          }
-        }
-
-        .step-content {
-          width: 350px;
-
-          .input-container {
-            width: calc(100% - 20px );
-            height: 36px;
-            margin: 10px 10px 0;
-
-            .input-item {
-              :deep(.el-input__inner){
-                  height: 36px;
-                  line-height: 36px;
-                }
-            }
-          }
-
-          .auto-method {
-            width: 253px;
-            height: 24px;
-            margin: 28px 0 0 14px;
-
-            .text-method-tag {
-              background-color: rgba(0, 174, 30, 1);
-              height: 24px;
-              width: 65px;
-
-              .text_4 {
-                width: 56px;
-                height: 20px;
-                overflow-wrap: break-word;
-                color: rgba(255, 255, 255, 1);
-                font-size: 14px;
-                font-family: PingFangSC-Semibold;
-                font-weight: 600;
-                text-align: left;
-                white-space: nowrap;
-                line-height: 20px;
-                margin: 2px 0 0 4px;
-              }
-            }
-
-            .method-description {
-              width: 182px;
-              height: 20px;
-              overflow-wrap: break-word;
-              color: rgba(71, 71, 71, 1);
-              font-size: 14px;
-              font-family: PingFangSC-Semibold;
-              font-weight: 600;
-              text-align: left;
-              white-space: nowrap;
-              line-height: 20px;
-              margin-top: 2px;
-            }
-          }
-        }
-      }
-
-      .step-two {
-        width: 350px;
-        height: auto;
-        margin: 55px 0 0 5px;
-
-        .step-title {
-          width: 350px;
-          height: 24px;
-          overflow-wrap: break-word;
-          color: rgba(0, 0, 0, 0.85);
-          font-size: 16px;
-          font-family: PingFangSC-Medium;
-          font-weight: 500;
-          text-align: left;
-          white-space: nowrap;
-          line-height: 24px;
-        }
-
-        .shooting-container {
-          width: 353px;
-          height: auto;
-
-
-          .remote-control-wrap {
-            width: 353px;
-            height: 300px;
-          }
-
-          .shooting-tips {
-            width: 325px;
-            height: 40px;
-            margin: 12px 0 0 15px;
-
-            .info-icon {
-              width: 16px;
-              height: 16px;
-              margin-top: 2px;
-            }
-
-            .tips-text {
-              width: 302px;
-              height: 40px;
-              overflow-wrap: break-word;
-              color: rgba(255, 76, 0, 1);
-              font-size: 14px;
-              font-weight: NaN;
-              text-align: left;
-              line-height: 20px;
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-.history-section {
-        width:  calc(100vw - 510px);
-        height: calc(100vh - 30px);
-        display: flex;
-        flex-direction: column;
-        padding: 20px;
-        overflow-y: auto;
-         background:#F5F6F7;
-
-        .search-bar {
-          margin-bottom: 15px;
-          display: flex;
-          justify-content: flex-start;
-        }
-
-        ::v-deep {
-          .el-checkbox__input {
-            transform: scale(1.4);
-          }
-        }
-        .history-warp {
-          flex: 1;
-
-          .history-item {
-            background: #FFFFFF;
-            box-shadow: 0px 2px 4px 0px rgba(23,33,71,0.1);
-            border-radius: 10px;
-            border: 1px solid #D9DEE6;
-            margin-bottom: 20px;
-
-            .history-item-header {
-              display: flex;
-              justify-content: space-between;
-              align-items: center;
-              height: 40px;
-              padding: 0 10px;
-
-              background: linear-gradient( 90deg, #F4ECFF 0%, #DFEDFF 100%);
-              border-radius: 10px 10px 0px 0px;
-
-              .history-item-left {
-                display: flex;
-                align-items: center;
-                gap: 10px;
-
-                .goods-checkbox {
-                  margin-right: 0;
-                }
-
-                .goods-art-no {
-                  font-size: 16px;
-                  font-weight: 500;
-                  color: #333;
-                }
-              }
-
-              .history-item-right {
-                display: flex;
-                align-items: center;
-                ::v-deep {
-                  .el-button { height: 30px; line-height: 30px;}
-                }
-              }
-            }
-
-            .history-item-meta {
-              display: flex;
-              justify-content: space-between;
-              align-items: center;
-              font-size: 12px;
-              color: #666;
-
-              img {
-                height: 14px;
-                margin-right: 2px;
-              }
-
-              .action-time {
-                color: #666;
-              }
-
-              .image-count {
-                color: #666;
-              }
-            }
-
-            .history-item-images {
-              display: grid;
-              grid-template-columns: repeat(5, 1fr);
-              gap: 10px;
-              padding: 15px;
-              border-top: 1px solid #f0f0f0;
-              overflow-x: auto;
-
-              // 如果图片数量超过5个,允许横向滚动
-              @media (min-width: 1200px) {
-                grid-template-columns: repeat(5, 1fr);
-              }
-
-              // 响应式:小屏幕时每行3个
-              @media (max-width: 768px) {
-                grid-template-columns: repeat(3, 1fr);
-              }
-            }
-
-            .history-item_image_wrap {
-              padding-bottom: 0;
-              border-bottom: none;
-            }
-            .history-item_image {
-              position: relative;
-              width: 100%;
-              aspect-ratio: 1;
-              background: #F7F7F7;
-              border-radius: 10px;
-              overflow: hidden;
-              cursor: pointer;
-              border: 1px solid #D9DEE6;
-              transition: all 0.3s;
-
-
-              .tag {
-                color: #bbb;
-                position: absolute;
-                left: 0;
-                right: 0;
-                top: 50%;
-                margin-top: -10px;
-                line-height: 20px;
-                text-align: center;
-                font-size: 12px;
-                z-index: 1;
-                pointer-events: none;
-              }
-
-              .preview-image {
-                width: 100%;
-                height: 100%;
-
-                :deep(.el-image__inner) {
-                  width: 100%;
-                  height: 100%;
-                  object-fit: cover;
-                }
-              }
-
-              .image-placeholder {
-                width: 100%;
-                height: 100%;
-                display: flex;
-                align-items: center;
-                justify-content: center;
-                background: #F7F7F7;
-              }
-
-              .image-slot {
-                width: 100%;
-                height: 100%;
-                display: flex;
-                align-items: center;
-                justify-content: center;
-                background: #F7F7F7;
-              }
-
-              &:hover {
-                border-color: #409eff;
-                transform: scale(1.02);
-                box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
-              }
-
-              &.el-loading-parent--relative{
-                ::v-deep {
-                  .el-loading-mask { display: none}
-                }
-              }
-            }
-
-
-            .el-image_view {
-              display: flex;
-              width: 100%;
-              height: 100%;
-
-              .reset-button {
-                width: 40px;
-                text-align: center;
-                height: 20px;
-                position: absolute;
-                left:50%;
-                top:50%;
-                padding: 0px;
-                margin-left:-20px;
-                margin-top:-10px;
-                color: #ffffff;
-                font-size: 14px;
-                background: rgba(0,0,0,0.6);
-                border-radius: 12px;
-                display: none;
-                cursor: pointer;
-              }
-              &:hover {
-                .reset-button {
-                  display: block;
-                }
-              }
-            }
-            p:first-of-type {
-              ::v-deep {
-                .el-loading-mask { display: block !important;}
-              }
-            }
-
-
-          }
-        }
-
-        .footer-controls {
-          display: flex;
-          justify-content: space-between;
-          align-items: center;
-          padding: 0px 20px;
-          border-top: 1px solid #D9DEE6;
-          background-color: #fff;
-          min-height: 50px;
-          flex-shrink: 0;
-          position: fixed;
-          bottom:0;
-          left: 510px;
-          right: 0;
-          font-size: 14px;
-          z-index: 100;
-          img {
-            height: 12px;
-            margin: 0 5px;
-          }
-          .go {
-            height: 12px;
-            opacity: .8;
-          }
-          ::v-deep {
-            .el-button {
-              border-radius: 10px;
-              height: 40px;
-              line-height: 40px;
-            }
-          }
-
-          .footer-left {
-            display: flex;
-            align-items: center;
-            gap: 10px;
-
-            .select-all-checkbox {
-              margin-right: 0;
-              ::v-deep {
-                .el-checkbox__label {
-
-                  font-size: 14px;
-                  color: #666;
-                }
-              }
-            }
-
-            .image-count-text {
-              font-size: 14px;
-              color: #333;
-              margin-left: 0;
-            }
-          }
-
-          .footer-right {
-            display: flex;
-            align-items: center;
-            gap: 10px;
-          }
-        }
-
-      }
-
-.last-photo{
-  position: fixed;
-  padding: 10px;
-  box-shadow: 0 0 5px rgb(0 0 0 / 50%);
-  left: 510px;
-  top: 30px;
-  bottom: 0px;
-  right: 10px;
-  z-index: 1000;
-  background-color: rgba(0,0,0,.5);
-
-  .close-btn {
-    position: absolute;
-    top: 20px;
-    right: 20px;
-    z-index: 1001;
-    width: 32px;
-    height: 32px;
-
-    ::v-deep(.el-icon) {
-      font-size: 16px;
-    }
-  }
-
-  .el-image {
-    width: 100%;
-    height:100%;
-    display: block;
-
-    .el-image__inner {
-      width: 100%;
-      height:100%;
-      display: block;
-    }
-  }
-}
+.photography-page { position: relative; }
+.main-container { position: relative; display: flex; }
+.history-section { width: 100%; min-height: calc(100vh - 30px); display: flex; flex-direction: column; padding: 20px; }
+.history-warp { flex: 1; }
+.history-item { background: #FFFFFF; box-shadow: 0px 2px 4px 0px rgba(23,33,71,0.1); border-radius: 10px; border: 1px solid #D9DEE6; margin-bottom: 20px; }
+.history-item-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
+.history-item-left { display: flex; align-items: center; gap: 12px; }
+.goods-art-no { font-weight: 600; font-size: 16px; color: #333; }
+.history-item-meta { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #8C92A7; }
+.history-item-right { display: flex; align-items: center; gap: 8px; }
+.history-item-images { display: flex; flex-wrap: wrap; padding: 12px 16px; gap: 12px; }
+.history-item_image { width: 120px; height: 120px; border-radius: 8px; overflow: hidden; background: #f5f5f5; display: flex; align-items: center; justify-content: center; position: relative; }
+.preview-image { width: 120px; height: 120px; }
+.image-placeholder { width: 120px; height: 120px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; }
+.tag { position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.5); color: #fff; font-size: 10px; padding: 2px 6px; border-radius: 4px; }
+.image-count { display: flex; align-items: center; gap: 4px; }
+.last-photo { position: fixed; top: 50px; right: 20px; z-index: 9999; width: 300px; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 8px; }
+.close-btn { position: absolute; top: 8px; right: 8px; z-index: 1; }
+::v-deep .el-checkbox__input { transform: scale(1.4); }
+.search-bar { margin-bottom: 15px; display: flex; justify-content: flex-start; }
 </style>

+ 70 - 252
frontend/src/views/RemoteControl/index.vue

@@ -1,47 +1,47 @@
 <template>
+  <headerBar :title="$t('router.remoteControl')" />
+  <hardware-check/>
 
   <div class="remote-control_main-container">
-    <div class="te-c"  style="color: #8C92A7">遥控器模拟器</div>
+    <div class="te-c"  style="color: #8C92A7">{{ $t('remoteControl.title') }}</div>
 
     <el-row align="middle" class="mar-top-20">
       <el-col :span="3"></el-col>
-      <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runLeft" >左脚</div></el-col>
-      <el-col :span="6"><div class="button up" @click="run_take_picture">拍照</div></el-col>
-      <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runRight">右脚</div></el-col>
+      <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runLeft" >{{ $t('remoteControl.leftFoot') }}</div></el-col>
+      <el-col :span="6"><div class="button up" @click="run_take_picture">{{ $t('remoteControl.takePhoto') }}</div></el-col>
+      <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runRight">{{ $t('remoteControl.rightFoot') }}</div></el-col>
       <el-col :span="3"></el-col>
     </el-row>
 
-    <!-- 左脚配置、拍照配置、右脚配置按钮行 -->
     <el-row align="middle">
       <el-col :span="3"></el-col>
-      <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'left')"><span style="font-size: 12px;">左脚配置</span></div></el-col>
-      <!-- ========== 拍照配置按钮(仅多相机模式显示)========== -->
+      <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'left')"><span style="font-size: 12px;">{{ $t('remoteControl.leftConfig') }}</span></div></el-col>
       <el-col :span="6">
         <div class="button up photo-config-btn"  v-if="isMultiCameraMode" :class="{ disabled: canStop, active: showPhotoMenu }" @click="handlePhotoConfigClick">
-          <span style="font-size: 12px;">拍照配置</span>
+          <span style="font-size: 12px;">{{ $t('remoteControl.photoConfig') }}</span>
         </div>
       </el-col>
-      <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'right')"><span style="font-size: 12px;">右脚配置</span></div></el-col>
+      <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'right')"><span style="font-size: 12px;">{{ $t('remoteControl.rightConfig') }}</span></div></el-col>
       <el-col :span="3"></el-col>
     </el-row>
 
     <el-row align="middle">
       <el-col :span="3"></el-col>
       <el-col :span="6">
-        <div class="button up" @click="switchLED(1)" v-log="{ describe: { action: 'LED开启' } }">LED开</div>
+        <div class="button up" @click="switchLED(1)" v-log="{ describe: { action: 'LED开启' } }">{{ $t('remoteControl.ledOn') }}</div>
       </el-col>
       <el-col :span="6">
-        <div class="button up" :class="{ disabled: !canStop }" @click="oneClickStop" v-log="{ describe: { action: '一键停止拍摄' } }">停止</div>
+        <div class="button up" :class="{ disabled: !canStop }" @click="oneClickStop" v-log="{ describe: { action: '一键停止拍摄' } }">{{ $t('remoteControl.stop') }}</div>
       </el-col>
       <el-col :span="6">
-        <div class="button up" @click="switchLED(0)" v-log="{ describe: { action: 'LED关闭' } }">LED关</div>
+        <div class="button up" @click="switchLED(0)" v-log="{ describe: { action: 'LED关闭' } }">{{ $t('remoteControl.ledOff') }}</div>
       </el-col>
       <el-col :span="3"></el-col>
     </el-row>
 
     <!-- 左脚配置菜单 -->
     <div v-if="showLeftMenu" class="context-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
-      <div class="menu-title">左脚配置</div>
+      <div class="menu-title">{{ $t('remoteControl.leftConfig') }}</div>
       <div class="menu-items">
         <div
             v-for="tab in leftTabs"
@@ -57,7 +57,7 @@
 
     <!-- 右脚配置菜单 -->
     <div v-if="showRightMenu" class="context-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
-      <div class="menu-title">右脚配置</div>
+      <div class="menu-title">{{ $t('remoteControl.rightConfig') }}</div>
       <div class="menu-items">
         <div
           v-for="tab in rightTabs"
@@ -71,9 +71,9 @@
       </div>
     </div>
 
-    <!-- ========== 拍照配置菜单(仅多相机模式显示)========== -->
+    <!-- 拍照配置菜单(仅多相机模式显示) -->
     <div v-if="isMultiCameraMode && showPhotoMenu" class="context-menu photo-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
-      <div class="menu-title">拍照配置</div>
+      <div class="menu-title">{{ $t('remoteControl.photoConfig') }}</div>
       <div class="menu-items">
         <div
           v-for="point in pointList"
@@ -82,22 +82,21 @@
           :class="{ active: selectedPoint === point.key }"
           @click="selectPhotoPoint(point.key)"
         >
-          <span class="point-name">{{ selectedPoint === point.key ? '✓ ' : '' }}点位 {{ point.key }}</span>
+          <span class="point-name">{{ selectedPoint === point.key ? '✓ ' : '' }}{{ $t('remoteControl.pointLabel', { point: point.key }) }}</span>
           <el-tag :type="point.connected ? 'success' : 'danger'" size="small">
-            {{ point.connected ? '已连接' : '未连接' }}
+            {{ point.connected ? $t('remoteControl.connected') : $t('remoteControl.notConnected') }}
           </el-tag>
         </div>
       </div>
     </div>
 
-    <!-- 点击其他地方关闭菜单 -->
     <div v-if="showLeftMenu || showRightMenu || (isMultiCameraMode && showPhotoMenu)" class="menu-overlay" @click="closeMenus"></div>
   </div>
-
 </template>
 
 <script setup lang="ts">
 import { defineEmits, defineProps, ref, onMounted, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
 import socket from "@/stores/modules/socket";
 import { getTopTabs, setLeftRightConfig, getAllUserConfigs } from '@/apis/setting';
 import { ElMessage } from 'element-plus';
@@ -105,39 +104,30 @@ import client from "@/stores/modules/client";
 import icpList from '@/utils/ipc';
 import useUserInfo from '@/stores/modules/user';
 
-// 初始化 WebSocket 状态管理
+const { t } = useI18n()
 const socketStore = socket()
 const clientStore = client()
 const userInfoStore = useUserInfo()
 
-// 是否为多相机模式
 const isMultiCameraMode = computed(() => userInfoStore.isMultiCameraMode)
 
-const props = defineProps<{
-  canStop: boolean
-}>()
-
+const props = defineProps<{ canStop: boolean }>()
 const emit = defineEmits(['onRemoteControl'])
 
-// ========== 新增:点位配置 ==========
 const STORAGE_KEY = 'photo_point_name'
 const selectedPoint = ref('A')
 
-// 相机列表和点位列表
 const cameraList = ref<any[]>([])
 const pointList = ref<{ key: string; connected: boolean; cameraName: string }[]>([])
 
-// 初始化从 localStorage 读取配置
 onMounted(() => {
   const savedPoint = localStorage.getItem(STORAGE_KEY)
   if (savedPoint && ['A', 'B', 'C'].includes(savedPoint)) {
     selectedPoint.value = savedPoint
   }
-  // 获取相机列表
   fetchCameraList()
 })
 
-// 获取相机列表并更新点位连接状态
 const fetchCameraList = () => {
   clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
     if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
@@ -145,11 +135,10 @@ const fetchCameraList = () => {
       updatePointConnectionStatus()
     }
   }).catch(err => {
-    console.error('获取相机列表失败:', err)
+    console.error('get camera list failed:', err)
   })
 }
 
-// 更新点位连接状态(仅显示已绑定相机的点位)
 const updatePointConnectionStatus = async () => {
   let isoConfig: any = {}
   try {
@@ -158,198 +147,109 @@ const updatePointConnectionStatus = async () => {
       isoConfig = result.data.configs.camera_configs.iso_config
     }
   } catch (err) {
-    console.error('获取点位配置失败:', err)
+    console.error('get point config failed:', err)
   }
-  // 只保留已绑定相机的点位
   const validPoints = Object.keys(isoConfig).filter(key => {
     const config = isoConfig[key]
     return config && config.CameraKey && config.CameraKey !== ''
   })
-  if (validPoints.length === 0) {
-    validPoints.push('A')
-  }
+  if (validPoints.length === 0) { validPoints.push('A') }
   pointList.value = validPoints.map(key => {
     const cameraKey = isoConfig[key]?.CameraKey || ''
     let connected = false
     let cameraName = ''
-
     if (cameraKey) {
       const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
-      if (camera) {
-        connected = camera.CameraStatus === true
-        cameraName = camera.CameraName
-      }
+      if (camera) { connected = camera.CameraStatus === true; cameraName = camera.CameraName }
     }
-
     return { key, connected, cameraName }
   })
-  // 验证当前选中的点位是否有效
   if (!pointList.value.some(p => p.key === selectedPoint.value)) {
     selectedPoint.value = pointList.value[0]?.key || 'A'
   }
 }
 
+defineExpose({ getSelectedPoint: () => selectedPoint.value })
 
-// 暴露点位给父组件
-defineExpose({
-  getSelectedPoint: () => selectedPoint.value
-})
-
-// ========== 配置切换相关 ==========
 const leftConfigId = ref(0)
 const rightConfigId = ref(0)
-const leftTabs = ref([]) // 左脚配置选项
-const rightTabs = ref([]) // 右脚配置选项
-const showLeftMenu = ref(false) // 显示左脚菜单
-const showRightMenu = ref(false) // 显示右脚菜单
-const showPhotoMenu = ref(false) // 显示拍照配置菜单
-const menuPosition = ref({ x: 0, y: 0 }) // 菜单位置
+const leftTabs = ref([])
+const rightTabs = ref([])
+const showLeftMenu = ref(false)
+const showRightMenu = ref(false)
+const showPhotoMenu = ref(false)
+const menuPosition = ref({ x: 0, y: 0 })
 
-const runLeft = async () => {
-  emit('onRemoteControl','left')
-}
+const runLeft = async () => { emit('onRemoteControl','left') }
+const runRight = async () => { emit('onRemoteControl','right') }
+const run_take_picture = () => { emit('onRemoteControl','take_picture') }
 
-
-const runRight = async () => {
-  emit('onRemoteControl','right')
-}
-
-
-
-const run_take_picture = () => {
-  emit('onRemoteControl','take_picture')
-}
-
-//LED
 const switchLED = async (value) => {
-  socketStore.sendMessage({
-    type: 'control_mcu',
-    data: {
-      device_name: "laser_position",
-      value,
-    }
-  });
+  socketStore.sendMessage({ type: 'control_mcu', data: { device_name: "laser_position", value } });
 }
 
-// 一键停止
 const oneClickStop = () => {
-  if (!props.canStop) {
-    return
-  }
-  socketStore.sendMessage({
-    type: 'stop_action',
-  })
+  if (!props.canStop) return
+  socketStore.sendMessage({ type: 'stop_action' })
 }
 
-
-// 右击左脚按钮
 const handleLeftRightClick = (event, type) => {
-  if(props.canStop){
-     return
-  }
+  if(props.canStop) return
   event.preventDefault()
   menuPosition.value = { x: event.clientX, y: event.clientY }
-
-  if (type === 'left') {
-    showLeftMenu.value = true
-    showRightMenu.value = false
-    loadLeftConfig()
-  } else {
-    showRightMenu.value = true
-    showLeftMenu.value = false
-    loadRightConfig()
-  }
+  if (type === 'left') { showLeftMenu.value = true; showRightMenu.value = false; loadLeftConfig() }
+  else { showRightMenu.value = true; showLeftMenu.value = false; loadRightConfig() }
 }
 
-// 加载左脚配置
 const loadLeftConfig = async () => {
   try {
     const result = await getTopTabs({ type: 0 })
-    if (result.code === 0) {
-      leftConfigId.value = result.data.select_configs.left
-      leftTabs.value = result.data.tabs
-    }
-  } catch (error) {
-    console.error('加载左脚配置失败:', error)
-  }
+    if (result.code === 0) { leftConfigId.value = result.data.select_configs.left; leftTabs.value = result.data.tabs }
+  } catch (error) { console.error('load left config failed:', error) }
 }
 
-// 加载右脚配置
 const loadRightConfig = async () => {
   try {
     const result = await getTopTabs({ type: 1 })
-    if (result.code === 0) {
-      rightConfigId.value = result.data.select_configs.right
-      rightTabs.value = result.data.tabs
-    }
-  } catch (error) {
-    console.error('加载右脚配置失败:', error)
-  }
+    if (result.code === 0) { rightConfigId.value = result.data.select_configs.right; rightTabs.value = result.data.tabs }
+  } catch (error) { console.error('load right config failed:', error) }
 }
 
-// 选择配置
 const selectConfig = async (type, configId) => {
   try {
-    if(props.canStop){
-      ElMessage.error('正在拍摄中,请稍候')
-      return
-    }
-    const result = await setLeftRightConfig({
-      type: type,
-      id: configId
-    })
-
+    if(props.canStop) { ElMessage.error(t('remoteControl.shootingInProgress')); return }
+    const result = await setLeftRightConfig({ type, id: configId })
     if (result.code === 0) {
-      if (type === 'left') {
-        leftConfigId.value = configId
-        showLeftMenu.value = false
-      } else {
-        rightConfigId.value = configId
-        showRightMenu.value = false
-      }
-      ElMessage.success('配置切换成功')
+      if (type === 'left') { leftConfigId.value = configId; showLeftMenu.value = false }
+      else { rightConfigId.value = configId; showRightMenu.value = false }
+      ElMessage.success(t('message.configSwitchSuccess'))
     }
   } catch (error) {
-    console.error('切换配置失败:', error)
-    ElMessage.error('配置切换失败')
+    ElMessage.error(t('message.configSwitchFailed'))
   }
 }
 
-// 关闭菜单
 const closeMenus = () => {
   showLeftMenu.value = false
   showRightMenu.value = false
   showPhotoMenu.value = false
 }
 
-// ========== 新增:拍照配置菜单相关 ==========
-
-// 点击拍照配置按钮
 const handlePhotoConfigClick = (event: MouseEvent) => {
-  if(props.canStop){
-    return
-  }
+  if(props.canStop) return
   event.preventDefault()
   menuPosition.value = { x: event.clientX, y: event.clientY }
-
-  // 关闭其他菜单
-  showLeftMenu.value = false
-  showRightMenu.value = false
-
-  // 获取最新的相机列表
+  showLeftMenu.value = false; showRightMenu.value = false
   fetchCameraList()
-
   showPhotoMenu.value = true
 }
 
-// 选择拍照点位
 const selectPhotoPoint = (pointKey: string) => {
   selectedPoint.value = pointKey
   localStorage.setItem(STORAGE_KEY, pointKey)
   showPhotoMenu.value = false
-  ElMessage.success(`已切换到点位 ${pointKey}`)
+  ElMessage.success(t('remoteControl.switchToPoint', { point: pointKey }))
 }
-
 </script>
 
 <style scoped lang="scss">
@@ -357,12 +257,11 @@ const selectPhotoPoint = (pointKey: string) => {
   background: #EAECED;
   height: 450px;
   width: 300px;
-  margin:  0 auto;
+  margin: 0 auto;
   margin-top: 10px;
   padding-top: 30px;
   background: url(@/assets/images/Photography/yk.png) 0px 0px no-repeat;
   background-size: 300px 450px;
-
 }
 .button {
   width: 60px;
@@ -373,108 +272,27 @@ const selectPhotoPoint = (pointKey: string) => {
   color: #474747;
   margin: 0 auto;
   box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2);
-
   background: url(@/assets/images/Photography/hui.png) 0px 0px no-repeat;
   background-size: 60px 60px;
 }
-.el-row {
-  min-height: 100px;
-}
+.el-row { min-height: 100px; }
 .button:hover {
   background: url(@/assets/images/Photography/lan.png) 0px 0px no-repeat;
   background-size: 60px 60px;
   cursor: pointer;
 }
-
-.button.stop {
-  background: #ff4c00;
-  color: white;
-  border-radius: 10px;
-  width: 120px;
-  height: 40px;
-  line-height: 40px;
-  font-size: 14px;
-  margin: 0 auto;
-}
-
-.button.stop:hover {
-  background: #e64500;
-}
-
-.button.disabled {
-  opacity: 0.5;
-  cursor: not-allowed;
-  pointer-events: none;
-}
-
-.context-menu {
-  position: fixed;
-  background: white;
-  border: 1px solid #e4e7ed;
-  border-radius: 4px;
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-  z-index: 2000;
-  min-width: 120px;
-  max-width: 200px;
-}
-
-.menu-title {
-  padding: 8px 12px;
-  font-size: 12px;
-  font-weight: 600;
-  color: #606266;
-  border-bottom: 1px solid #ebeef5;
-  background-color: #f5f7fa;
-}
-.menu-items {
-
-  max-height: 150px;
-  overflow: auto;
-}
-.menu-item {
-  padding: 8px 12px;
-  font-size: 14px;
-  color: #606266;
-  cursor: pointer;
-  transition: background-color 0.2s;
-
-  &:hover {
-    background-color: #f5f7fa;
-  }
-
-  &.active {
-    color: #2957FF;
-    font-weight: 600;
-  }
-}
-
-.menu-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  z-index: 1999;
-}
-
-/* 新增:拍照配置样式 */
-.photo-config-btn.active {
-  background: url(@/assets/images/Photography/lan.png) 0px 0px no-repeat !important;
-  background-size: 60px 60px !important;
-}
-
-.photo-menu {
-  min-width: 180px;
-}
-
-.photo-point-item {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 12px;
-}
-
-.photo-point-item .point-name {
-  flex-shrink: 0;
-}
+.button.stop { background: #ff4c00; color: white; border-radius: 10px; width: 120px; height: 40px; line-height: 40px; font-size: 14px; margin: 0 auto; }
+.button.stop:hover { background: #e64500; }
+.button.disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
+.context-menu { position: fixed; background: white; border: 1px solid #e4e7ed; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 2000; min-width: 120px; max-width: 200px; }
+.menu-title { padding: 8px 12px; font-size: 12px; font-weight: 600; color: #606266; border-bottom: 1px solid #ebeef5; background-color: #f5f7fa; }
+.menu-items { max-height: 150px; overflow: auto; }
+.menu-item { padding: 8px 12px; font-size: 14px; color: #606266; cursor: pointer; transition: background-color 0.2s; }
+.menu-item:hover { background-color: #f5f7fa; }
+.menu-item.active { color: #2957FF; font-weight: 600; }
+.menu-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1999; }
+.photo-config-btn.active { background: url(@/assets/images/Photography/lan.png) 0px 0px no-repeat !important; background-size: 60px 60px !important; }
+.photo-menu { min-width: 180px; }
+.photo-point-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
+.photo-point-item .point-name { flex-shrink: 0; }
 </style>

+ 45 - 138
frontend/src/views/Setting/components/CameraConfig.vue

@@ -1,11 +1,6 @@
 <template>
   <div class="camera-config-container">
-    <!-- 统一使用多点位配置界面 -->
-
-
-    <!-- 点位切换Tab -->
     <div class="selectBox multi-camera-config" v-id>
-      <!-- 点位切换按钮 -->
       <div class="point-tabs" v-if="isMultiCameraMode">
         <div
           v-for="point in displayPointList"
@@ -14,7 +9,7 @@
           :class="{ active: activePoint === point.key }"
           @click="switchPoint(point.key)"
         >
-          <span class="tab-name">点位 {{ point.key }}</span>
+          <span class="tab-name">{{ $t('cameraConfig.pointLabel', { point: point.key }) }}</span>
           <el-tag :type="getPointTagInfo(point.key).type" size="small">
             {{ getPointTagInfo(point.key).text }}
           </el-tag>
@@ -27,24 +22,18 @@
           @click="handleRefresh"
         >
           <el-icon><Refresh /></el-icon>
-          刷新
+          {{ $t('cameraConfig.refresh') }}
         </el-button>
       </div>
 
-      <!-- 当前点位配置 -->
       <div class="current-point-config">
-<!--        <div class="config-header" v-if="isMultiCameraMode">
-          <span class="config-title">点位 {{ activePoint }} 配置</span>
-        </div>-->
-
         <div class="config-body">
-          <!-- 多相机模式下显示相机绑定 -->
           <div v-if="isMultiCameraMode" class="form-item">
-            <label class="iso-label">绑定相机:</label>
+            <label class="iso-label">{{ $t('cameraConfig.bindCamera') }}</label>
             <div class="camera-select-wrapper">
               <el-select
                 v-model="multiConfig[activePoint].CameraKey"
-                placeholder="请选择相机"
+                :placeholder="$t('cameraConfig.selectCamera')"
                 filterable
                 clearable
                 @change="onCameraChange(activePoint)"
@@ -58,22 +47,21 @@
                   :disabled="!camera.CameraStatus"
                 >
                   <span>{{ camera.CameraName }}</span>
-                  <el-tag v-if="!camera.CameraStatus" type="danger" size="small" style="margin-left: 8px">未连接</el-tag>
+                  <el-tag v-if="!camera.CameraStatus" type="danger" size="small" style="margin-left: 8px">{{ $t('cameraConfig.notConnected') }}</el-tag>
                 </el-option>
               </el-select>
             </div>
           </div>
 
-          <!-- ISO配置 -->
           <div class="iso-section">
             <div class="iso-group">
-              <span class="iso-label">拍照ISO:</span>
+              <span class="iso-label">{{ $t('cameraConfig.photoISO') }}</span>
               <div class="select-wrapper">
                 <el-select
                   v-model="multiConfig[activePoint].iso.low"
                   filterable
                   default-first-option
-                  placeholder="请选择"
+                  :placeholder="$t('cameraConfig.select')"
                   class="iso-select"
                 >
                   <el-option
@@ -86,13 +74,13 @@
               </div>
             </div>
             <div class="iso-group">
-              <span class="iso-label">预览ISO:</span>
+              <span class="iso-label">{{ $t('cameraConfig.previewISO') }}</span>
               <div class="select-wrapper">
                 <el-select
                   v-model="multiConfig[activePoint].iso.high"
                   filterable
                   default-first-option
-                  placeholder="请选择"
+                  :placeholder="$t('cameraConfig.select')"
                   class="iso-select"
                 >
                   <el-option
@@ -123,7 +111,6 @@ const clientStore = client();
 const socketStore = socket();
 const userInfoStore = useUserInfo();
 
-// 定义props
 const props = defineProps({
   camera_configs: {
     type: Object,
@@ -131,29 +118,21 @@ const props = defineProps({
   }
 })
 
-// 定义emits
 const emit = defineEmits(['update:camera_configs'])
 
-// 是否为多相机模式
 const isMultiCameraMode = computed(() => userInfoStore.isMultiCameraMode)
 
-// 当前选中的点位
 const activePoint = ref('A')
 
-// 相机列表(通过 IPC 获取,不再依赖 store)
 const cameraList = ref([])
-
-// 刷新中
 const refreshing = ref(false)
 
-// 点位列表(带相机名,用于显示)
 const pointList = reactive([
   { key: 'A', cameraName: '' },
   { key: 'B', cameraName: '' },
   { key: 'C', cameraName: '' },
 ])
 
-// 获取点位状态(三态:已连接/未连接/未设置)
 const getPointStatus = (pointKey) => {
   const config = multiConfig[pointKey]
   if (!config || !config.CameraKey) return 'unset'
@@ -162,7 +141,6 @@ const getPointStatus = (pointKey) => {
   return camera.CameraStatus ? 'connected' : 'disconnected'
 }
 
-// 获取点位的标签文字和类型
 const getPointTagInfo = (pointKey) => {
   const status = getPointStatus(pointKey)
   if (status === 'connected') return { text: '已连接相机', type: 'success' }
@@ -170,8 +148,6 @@ const getPointTagInfo = (pointKey) => {
   return { text: '未设置相机', type: 'info' }
 }
 
-// 多相机模式的配置结构(统一数据结构)
-// 使用原始 CameraKey 作为存储和显示
 const multiConfig = reactive({
   A: {
     iso: { low: '100', high: '6400' },
@@ -193,41 +169,28 @@ const multiConfig = reactive({
   }
 })
 
-// 当前点位可用的 ISO 选项(模板中使用)
 const iso_options = computed(() => {
   return multiConfig[activePoint.value]?.iso_options || ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
 })
 
-// 根据模式显示不同的点位列表
 const displayPointList = computed(() => {
   if (isMultiCameraMode.value) {
     return pointList
   } else {
-    // 单相机模式只显示点位A
     return pointList.filter(p => p.key === 'A')
   }
 })
 
-// 其他点位(用于多相机模式预览)
-// const otherPoints = computed(() => {
-//   return pointList.filter(p => p.key !== activePoint.value)
-// })
-
-// 从本地 cameraList 获取可用相机列表(过滤掉已被其他点位绑定的相机)
 const availableCameras = computed(() => {
   const cameras = cameraList.value || []
   if (!isMultiCameraMode.value) {
     return cameras
   }
-  // 找出当前点位绑定的相机key
   const currentBoundCameraKey = multiConfig[activePoint.value].CameraKey
-  // 返回未绑定或当前点位已绑定的相机
   return cameras.filter(cam => {
-    // 如果是当前点位已绑定的相机,允许显示
     if (cam.CameraKey === currentBoundCameraKey) {
       return true
     }
-    // 检查是否被其他点位绑定
     const isBoundByOtherPoint = Object.entries(multiConfig).some(([key, config]) => {
       return key !== activePoint.value && config.CameraKey === cam.CameraKey
     })
@@ -235,17 +198,13 @@ const availableCameras = computed(() => {
   })
 })
 
-// 切换点位
 const switchPoint = (pointKey) => {
   activePoint.value = pointKey
-  // 切换到点位时,检查该点位相机的 ISO 是否需要更新
   const cameraKey = multiConfig[pointKey].CameraKey
   if (cameraKey) {
     const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
     if (camera && camera.CameraISO && Array.isArray(camera.CameraISO)) {
-      // 更新 ISO 选项(确保包含最新相机的 ISO)
       multiConfig[pointKey].iso_options = camera.CameraISO
-      // 检查当前选中的 ISO 是否还在新列表中
       const currentLow = multiConfig[pointKey].iso.low
       const currentHigh = multiConfig[pointKey].iso.high
       if (!camera.CameraISO.includes(currentLow)) {
@@ -258,66 +217,56 @@ const switchPoint = (pointKey) => {
   }
 }
 
-// 通过 IPC 获取相机列表
-  const fetchCameraList = () => {
-    return clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
-      if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
-        cameraList.value = result.CameraLists
-        return true
-      }
-      return false
-    }).catch(err => {
-      console.error('获取相机列表失败:', err)
-      throw err
-    })
-  }
+const fetchCameraList = () => {
+  return clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
+    if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
+      cameraList.value = result.CameraLists
+      return true
+    }
+    return false
+  }).catch(err => {
+    throw err
+  })
+}
 
-  const handleRefresh = async () => {
-    if (refreshing.value) return
-    refreshing.value = true
-    try {
-      const ok = await fetchCameraList()
-      if (ok) {
-        ElMessage.success('相机连接状态已更新')
-      } else {
-        ElMessage.warning('未获取到相机列表')
-      }
-    } catch {
-      ElMessage.error('刷新失败,请稍后重试')
-    } finally {
-      refreshing.value = false
+const handleRefresh = async () => {
+  if (refreshing.value) return
+  refreshing.value = true
+  try {
+    const ok = await fetchCameraList()
+    if (ok) {
+      ElMessage.success('相机连接状态已更新')
+    } else {
+      ElMessage.warning('未获取到相机列表')
     }
+  } catch {
+    ElMessage.error('刷新失败,请稍后重试')
+  } finally {
+    refreshing.value = false
   }
+}
 
-  // 从 smart_shooter_getinfo 获取 ISO 配置(保留作为备用)
-  const fetchIsoConfig = () => {
-    clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
-    socketStore.sendMessage({
-      type: 'smart_shooter_getinfo'
-    });
-
-    clientStore.ipc.on(icpList.socket.message + '_smart_shooter_getinfo', async (event, result) => {
-      if (result.code == 0 && result.data) {
-        // 从 CameraISO 字段获取 ISO 选项
-        if (result.data.CameraISO && Array.isArray(result.data.CameraISO)) {
-          iso_options.value = result.data.CameraISO
-        }
+const fetchIsoConfig = () => {
+  clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
+  socketStore.sendMessage({ type: 'smart_shooter_getinfo' });
+  clientStore.ipc.on(icpList.socket.message + '_smart_shooter_getinfo', async (event, result) => {
+    if (result.code == 0 && result.data) {
+      if (result.data.CameraISO && Array.isArray(result.data.CameraISO)) {
+        iso_options.value = result.data.CameraISO
       }
-      clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
-    })
-  }
+    }
+    clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
+  })
+}
 
-// 相机选择变更
 const onCameraChange = (pointKey) => {
   const cameraKey = multiConfig[pointKey].CameraKey
   if (cameraKey) {
     const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
     if (camera) {
       multiConfig[pointKey].CameraName = camera.CameraName
-      // 更新当前点位的 ISO 选项
       if (camera.CameraISO && Array.isArray(camera.CameraISO)) {
         multiConfig[pointKey].iso_options = camera.CameraISO
-        // 如果当前选中的 ISO 不在新列表中,重置为默认值
         const currentLow = multiConfig[pointKey].iso.low
         const currentHigh = multiConfig[pointKey].iso.high
         if (!camera.CameraISO.includes(currentLow)) {
@@ -329,21 +278,16 @@ const onCameraChange = (pointKey) => {
       }
     }
   } else {
-    // 清除绑定时也清空 CameraName,重置 ISO 选项
     multiConfig[pointKey].CameraName = ''
     multiConfig[pointKey].iso_options = ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
   }
 }
 
-// 确保当前绑定的相机在列表中(即使断开连接也显示)
 const ensureCurrentCameraInList = () => {
   const currentCameraKey = multiConfig[activePoint.value].CameraKey
   if (!currentCameraKey) return
-
-  // 检查是否已在列表中
   const exists = cameraList.value.some(c => c.CameraKey === currentCameraKey)
   if (!exists) {
-    // 添加一个临时条目(未连接状态)用于显示
     cameraList.value.push({
       CameraKey: currentCameraKey,
       CameraName: multiConfig[activePoint.value].CameraName || currentCameraKey,
@@ -352,7 +296,6 @@ const ensureCurrentCameraInList = () => {
   }
 }
 
-// 监听相机列表变化,实时更新各点位状态
 watch(
   () => cameraList.value,
   () => {
@@ -378,9 +321,7 @@ watch(
   { deep: true }
 )
 
-// 监听multiConfig变化并更新父组件
 watch(multiConfig, (newVal) => {
-  // 过滤掉 iso_options 字段,只保留配置数据
   const isoConfig = {}
   Object.keys(newVal).forEach(key => {
     isoConfig[key] = {
@@ -394,19 +335,13 @@ watch(multiConfig, (newVal) => {
   })
 }, { deep: true })
 
-// 初始化数据
 onMounted(() => {
-  // 解析配置数据
   if (props.camera_configs.iso_config) {
     const config = props.camera_configs.iso_config
-
-    // 判断是旧格式还是新格式
     if (config.low !== undefined || config.high !== undefined) {
-      // 旧格式(单相机旧数据) -> 转换为新格式
       multiConfig.A.iso.low = String(config.low || '100')
       multiConfig.A.iso.high = String(config.high || '6400')
     } else {
-      // 新格式 - A/B/C 结构
       Object.keys(config).forEach(key => {
         if (multiConfig[key] !== undefined) {
           if (config[key].iso) {
@@ -422,16 +357,11 @@ onMounted(() => {
     }
   }
 
-  // 单相机模式默认选中点位A
   activePoint.value = 'A'
-
-  // 通过 IPC 获取相机列表(同时获取 ISO 配置)
   fetchCameraList()
 })
 
-// 暴露保存方法,给父组件调用
 const save = () => {
-  // 校验当前点位配置
   const current = multiConfig[activePoint.value]
   const isEmpty = (v) => v === undefined || v === null || v === ''
 
@@ -472,7 +402,6 @@ defineExpose({ save })
   padding-left: 100px;
 }
 
-// 点位切换Tab
 .point-tabs {
   display: flex;
   align-items: center;
@@ -504,33 +433,16 @@ defineExpose({ save })
     font-weight: bold;
     color: #303133;
   }
-
 }
 
 .refresh-btn {
   margin-left: auto;
 }
 
-// 当前点位配置卡片
 .current-point-config {
-  // background: #fff;
-  // border: 1px solid #E4E7ED;
- // border-radius: 12px;
   overflow: hidden;
   max-width: 500px;
 
-  .config-header {
-    padding: 16px 20px;
-    background: #F5F7FA;
-    border-bottom: 1px solid #E4E7ED;
-
-    .config-title {
-      font-weight: bold;
-      font-size: 16px;
-      color: #303133;
-    }
-  }
-
   .config-body {
     padding: 24px 20px;
 
@@ -541,7 +453,6 @@ defineExpose({ save })
         display: block;
         margin-bottom: 8px;
         font-size: 14px;
-       // color: #606266;
       }
 
       .camera-select-wrapper {
@@ -566,10 +477,6 @@ defineExpose({ save })
         text-align: right;
         color: #1A1A1A;
       }
-
-      .iso-select {
-      //  width: 150px;
-      }
     }
   }
 }

+ 7 - 27
frontend/src/views/Setting/components/otherConfig.vue

@@ -1,14 +1,6 @@
 <template>
-<!--
-  <div class="flex left fs-14 line-40 mar-top-10" style="margin-left: 100px">
-    剩余金币:{{ useUserInfoStore.userInfo.coin_amount }}
-    <el-button class="mar-left-10" @click="openDialog()" v-log="{ describe: { action: '点击充值金币' } }">充值金币</el-button>
-  </div>
--->
-
-
   <div class="form-item">
-    <label>剩余金币:</label>
+    <label>{{ $t('recharge.remainingCoins') }}</label>
     <div class="select-wrapper">
       <el-input
           type="text"
@@ -16,21 +8,19 @@
           v-model="useUserInfoStore.userInfo.coin_amount"
           class="w-full px-3 py-2 border rounded-md"
       >
-        <template #append> <el-button class="recharge-btn" @click="openDialog()" v-log="{ describe: { action: '点击充值' } }">充值</el-button></template>
+        <template #append> <el-button class="recharge-btn" @click="openDialog()" v-log="{ describe: { action: '点击充值' } }">{{ $t('recharge.recharge') }}</el-button></template>
       </el-input>
     </div>
-    <div class="fs-12 c-666">(用户视频生成,需单独金币支付)</div>
+    <div class="fs-12 c-666">{{ $t('recharge.coinTip') }}</div>
   </div>
 
-  <!-- 新增:充值金币弹窗 -->
   <el-dialog v-model="showRechargeDialog"
-             title="充值金币"
+             :title="$t('recharge.rechargeTitle')"
              width="700px"
              :height="630 + 'px'"
              @close="onClose"
              :destroy-on-close="true">
-    <!-- 显示 loading 提示 -->
-    <div v-if="loading" class="loading-text flex" style="width: 660px; height: 610px;" >加载中,请稍候...</div>
+    <div v-if="loading" class="loading-text flex" style="width: 660px; height: 610px;" >{{ $t('recharge.loading') }}</div>
     <iframe v-show="!loading" :src="rechargeUrl" width="660" height="610" frameborder="0" @load="onIframeLoad"></iframe>
   </el-dialog>
 </template>
@@ -44,25 +34,18 @@ import tokenInfo from '@/stores/modules/token';
 const tokenInfoStore = tokenInfo();
 import { getWebUrlrUrl } from '@/utils/appfun'
 
-
-
-// 数据定义
 const showRechargeDialog = ref(false);
 const rechargeUrl = ref(getWebUrlrUrl({
   url:'/other/recharge_gold_coin'
 }));
-const loading = ref(true); // 新增 loading 状态
+const loading = ref(true);
 
-// iframe 加载完成回调
 const onIframeLoad = () => {
   loading.value = false;
 };
 
-// 消息监听函数
 const handleWindowMessage = async (event) => {
-  // 可选:验证 event.origin 来确保消息来源可信
   const message = event.data;
-
   if (message.type === 'close') {
     showRechargeDialog.value = false;
     await useUserInfoStore.getInfo();
@@ -79,12 +62,11 @@ const openDialog = () => {
   showRechargeDialog.value = true;
   loading.value = false;
 };
-// 绑定监听
+
 onMounted(() => {
   window.addEventListener('message', handleWindowMessage);
 });
 
-// 移除监听
 onUnmounted(() => {
   window.removeEventListener('message', handleWindowMessage);
 });
@@ -97,9 +79,7 @@ onUnmounted(() => {
 }
 .select-wrapper {
   ::v-deep {
-
     .el-input-group__append{
-
     }
   }
 }

+ 172 - 775
frontend/src/views/Setting/index.vue

@@ -1,597 +1,285 @@
 <template>
-  <headerBar
-  >
-    <template  #title><div @click="handleSettingClick" v-log="{ describe: { action: '点击设置标题' } }">设置</div></template>
+  <headerBar :title="$t('router.setting')">
+    <template  #title><div @click="handleSettingClick" v-log="{ describe: { action: '点击设置标题' } }">{{ $t('router.setting') }}</div></template>
   </headerBar>
   <div class="container setting-wrap">
     <nav class="settings-nav">
       <div class="nav-item" :class="{'active': activeIndex === 0}" @click="toggleTab(0)" v-log="{ describe: { action: '点击切换设置Tab', tab: '基础配置' } }">
         <img src="@/assets/images/setting/icon1.png" class="nav-icon" v-if="activeIndex !== 0"/>
         <img src="@/assets/images/setting/icon1a.png" class="nav-icon" v-else/>
-        <span>基础配置</span>
+        <span>{{ $t('setting.basicConfig') }}</span>
       </div>
       <div class="nav-item" v-if="configInfoStore.appModel === 1" :class="{'active': activeIndex === 3}" @click="toggleTab(3)" v-log="{ describe: { action: '点击切换设置Tab', tab: '相机配置' } }">
         <img src="@/assets/images/setting/icon2.png" class="nav-icon" v-if="activeIndex !== 3"/>
         <img src="@/assets/images/setting/icon2a.png" class="nav-icon" v-else/>
-        <span>相机配置</span>
+        <span>{{ $t('setting.cameraConfig') }}</span>
       </div>
       <div class="nav-item" :class="{'active': activeIndex === 2}" @click="toggleTab(2)" v-log="{ describe: { action: '点击切换设置Tab', tab: '其他设置' } }">
         <img src="@/assets/images/setting/icon3.png" class="nav-icon" v-if="activeIndex !== 2"/>
         <img src="@/assets/images/setting/icon3a.png" class="nav-icon" v-else/>
-        <span>其他设置</span>
+        <span>{{ $t('setting.otherConfig') }}</span>
       </div>
       <div class="nav-item"  v-if="configInfoStore.appModel === 1"  :class="{'active': activeIndex === 4}"  @click="toggleTab(4)" v-log="{ describe: { action: '点击切换设置Tab', tab: '左右脚程序设置' } }">
         <img src="@/assets/images/setting/icon4.png" class="nav-icon" v-if="activeIndex !== 4"/>
         <img src="@/assets/images/setting/icon4a.png" class="nav-icon" v-else/>
-        <span>左右脚程序设置</span>
+        <span>{{ $t('setting.leftRightConfig') }}</span>
       </div>
     </nav>
 
     <div class="form-container">
-      <!--基础配置-->
-          <div class="selectBox" v-if="activeIndex === 0">
-                <div class="form-item">
-                  <label>主图尺寸:</label>
-                  <div class="select-wrapper">
-                    <el-select multiple
-                               collapse-tags
-                               multiple-limit="3"
-                               v-model="formData.basic_configs.main_image_size" placeholder="请选择">
-                      <el-option v-for="item in mainImageSizeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
-                    </el-select>
-                  </div>
-                </div>
-                 <!-- 新增自定义输入框 -->
-                <div class="form-item" v-if="formData.basic_configs.main_image_size.includes('custom')">
-                  <label>自定义尺寸:</label>
-                  <div class="select-wrapper">
-                    <el-input
-                        type="text"
-                        v-model.number="customInput"
-                        maxlength="4"
-                        placeholder="请输入1-3000的尺寸值"
-                        class="w-full px-3 py-2 border rounded-md"
-                        @keypress="handleKeyPress"
-                        @input="handleInput"
-                    />
-                  </div>
-                </div>
-                <div class="form-item">
-                    <label>图片输出格式:</label>
-                    <div class="select-wrapper">
-                    <el-select v-model="formData.basic_configs.image_out_format" placeholder="请选择">
-                      <el-option v-for="item in imageFormatList" :key="item.value" :label="item.label" :value="item.value"></el-option>
-                    </el-select>
-                    </div>
+      <div class="selectBox" v-if="activeIndex === 0">
+            <div class="form-item">
+              <label>{{ $t('setting.mainImageSize') }}</label>
+              <div class="select-wrapper">
+                <el-select multiple collapse-tags multiple-limit="3" v-model="formData.basic_configs.main_image_size" :placeholder="$t('validation.pleaseSelect')">
+                  <el-option v-for="item in mainImageSizeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+              </div>
+            </div>
+            <div class="form-item" v-if="formData.basic_configs.main_image_size.includes('custom')">
+              <label>{{ $t('setting.customSize') }}</label>
+              <div class="select-wrapper">
+                <el-input type="text" v-model.number="customInput" maxlength="4" :placeholder="$t('setting.inputSizeTip')" class="w-full px-3 py-2 border rounded-md" @keypress="handleKeyPress" @input="handleInput" />
+              </div>
+            </div>
+            <div class="form-item">
+                <label>{{ $t('setting.imageFormat') }}</label>
+                <div class="select-wrapper">
+                  <el-select v-model="formData.basic_configs.image_out_format" :placeholder="$t('validation.pleaseSelect')">
+                    <el-option v-for="item in imageFormatList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                  </el-select>
                 </div>
-                <div class="form-item">
-                    <label>图片锐化:</label>
-                    <div class="select-wrapper">
-                      <el-input-number
-                        v-model="formData.basic_configs.image_sharpening"
-                        :min="1"
-                        :max="5"
-                        style="width: 200px"
-                        :step="0.1"
-                        controls-position="right"
-                        placeholder="请输入1-5之间的值"
-                      />
-                    </div>
-                  <div class="tips fs-12 c-999 mar-left-10">数值1-5,值为1则代表不处理</div>
+            </div>
+            <div class="form-item">
+                <label>{{ $t('setting.imageSharpen') }}</label>
+                <div class="select-wrapper">
+                  <el-input-number v-model="formData.basic_configs.image_sharpening" :min="1" :max="5" style="width: 200px" :step="0.1" controls-position="right" :placeholder="$t('setting.inputSizeTip')" />
                 </div>
-
-                <div class="form-item">
-                    <label>阴影处理模式:</label>
-                    <div class="select-wrapper">
-                      <el-select v-model.number="formData.basic_configs.image_mask_config.mode" placeholder="请选择">
-                        <el-option label="默认模式" :value="0"></el-option>
-                        <el-option label="自定义模式" :value="1"></el-option>
-                      </el-select>
-                    </div>
+              <div class="tips fs-12 c-999 mar-left-10">{{ $t('setting.sharpenTip') }}</div>
+            </div>
+            <div class="form-item">
+                <label>{{ $t('setting.shadowMode') }}</label>
+                <div class="select-wrapper">
+                  <el-select v-model.number="formData.basic_configs.image_mask_config.mode" :placeholder="$t('validation.pleaseSelect')">
+                    <el-option :label="$t('setting.shadowDefault')" :value="0"></el-option>
+                    <el-option :label="$t('setting.shadowCustom')" :value="1"></el-option>
+                  </el-select>
                 </div>
-
-                <template v-if="formData.basic_configs.image_mask_config.mode === 1">
-                  <div class="form-item">
-                      <label>透明度:</label>
-                      <div class="select-wrapper">
-                        <el-input-number
-                          v-model="formData.basic_configs.image_mask_config.opacity"
-                          :min="0.5"
-                          :max="1"
-                          style="width: 200px"
-                          :step="0.01"
-                          controls-position="right"
-                          placeholder="请输入0.5-1之间的值"
-                        />
-                      </div>
+            </div>
+            <template v-if="formData.basic_configs.image_mask_config.mode === 1">
+              <div class="form-item">
+                  <label>{{ $t('setting.opacity') }}</label>
+                  <div class="select-wrapper">
+                    <el-input-number v-model="formData.basic_configs.image_mask_config.opacity" :min="0.5" :max="1" style="width: 200px" :step="0.01" controls-position="right" :placeholder="$t('setting.inputOpacityTip')" />
                   </div>
-
-                  <div class="form-item">
-                      <label>亮度范围:</label>
-                      <div class="select-wrapper">
-                        <el-input-number
-                          v-model="formData.basic_configs.image_mask_config.grenerate_main_pic_brightness"
-                          :min="200"
-                          :max="255"
-                          style="width: 200px"
-                          :step="1"
-                          controls-position="right"
-                          placeholder="请输入200-255之间的值"
-                        />
-                      </div>
+              </div>
+              <div class="form-item">
+                  <label>{{ $t('setting.brightnessRange') }}</label>
+                  <div class="select-wrapper">
+                    <el-input-number v-model="formData.basic_configs.image_mask_config.grenerate_main_pic_brightness" :min="200" :max="255" style="width: 200px" :step="1" controls-position="right" :placeholder="$t('setting.inputBrightnessTip')" />
                   </div>
-                </template>
-                <div class="form-item">
-                    <label>800图自定义边距:</label>
-                    <div class="select-wrapper">
-                      <el-input
-                          type="number"
-                          v-model.number="formData.basic_configs.padding_800image"
-                          :min="0"
-                          :max="500"
-                          placeholder="请输入0-500的整数"
-                          @keypress="handlePaddingKeyPress"
-                          @input="handlePaddingInput"
-                      />
-                    </div>
+              </div>
+            </template>
+            <div class="form-item">
+                <label>{{ $t('setting.customPadding800') }}</label>
+                <div class="select-wrapper">
+                  <el-input type="number" v-model.number="formData.basic_configs.padding_800image" :min="0" :max="500" :placeholder="$t('setting.inputPaddingTip')" @keypress="handlePaddingKeyPress" @input="handlePaddingInput" />
                 </div>
-                <div class="form-item">
-                    <label>800图是否翻转:</label>
-                    <div class="select-wrapper">
-                    <el-select v-model.number="formData.basic_configs.is_flip_800image" placeholder="请选择">
-                      <el-option v-for="item in isFlip800ImageList" :key="item.value" :label="item.label" :value="item.value"></el-option>
-                    </el-select>
-                    </div>
+            </div>
+            <div class="form-item">
+                <label>{{ $t('setting.flip800') }}</label>
+                <div class="select-wrapper">
+                  <el-select v-model.number="formData.basic_configs.is_flip_800image" :placeholder="$t('validation.pleaseSelect')">
+                    <el-option :label="$t('common.yes')" :value="1"></el-option>
+                    <el-option :label="$t('common.no')" :value="0"></el-option>
+                  </el-select>
                 </div>
-              <div class="form-item">
-                <label>800图颜色配置:</label>
+            </div>
+            <div class="form-item">
+                <label>{{ $t('setting.color800') }}</label>
                 <div class="select-wrapper flex left">
                   <ColorPickerWithRecent v-model="formData.basic_configs.color_800image" />
                 </div>
-              </div>
+            </div>
+            <DebugPanel ref="debugPanel" />
+      </div>
 
-               <DebugPanel ref="debugPanel" />
-        </div>
-      <!--基础配置-->
-      <!--相机配置-->
       <template v-if="activeIndex === 3">
          <CameraConfig ref="cameraConfigRef" :key="activeIndex" :camera_configs="formData.camera_configs" @update:camera_configs="updateCameraConfigs"/>
-
       </template>
-      <!--相机配置-->
-      <!--其他设置-->
-      <template v-if="activeIndex === 2">
 
+      <template v-if="activeIndex === 2">
         <div class="selectBox" style="padding-top: 0px" >
           <div class="form-item">
-            <label>产品类型:</label>
+            <label>{{ $t('setting.productType') }}</label>
             <div class="select-wrapper">
-              <el-select v-model="formData.other_configs.product_type" placeholder="请选择">
+              <el-select v-model="formData.other_configs.product_type" :placeholder="$t('validation.pleaseSelect')">
                 <el-option v-for="item in productTypeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
               </el-select>
             </div>
           </div>
           <div class="form-item">
-            <label>默认抠图模式:</label>
+            <label>{{ $t('setting.defaultCutout') }}</label>
             <div class="select-wrapper">
-              <el-select v-model="formData.other_configs.cutout_mode" placeholder="请选择">
+              <el-select v-model="formData.other_configs.cutout_mode" :placeholder="$t('validation.pleaseSelect')">
                 <el-option v-for="item in defaultCutoutModeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
               </el-select>
             </div>
           </div>
           <div class="form-item">
-            <label>设备运动速度:</label>
+            <label>{{ $t('setting.deviceSpeed') }}</label>
             <div class="select-wrapper">
-              <el-select v-model="formData.other_configs.device_speed" placeholder="请选择">
-                <el-option v-for="item in deviceSpeedList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+              <el-select v-model="formData.other_configs.device_speed" :placeholder="$t('validation.pleaseSelect')">
+                <el-option :label="$t('setting.speedFast')" :value="'一档'"></el-option>
+                <el-option :label="$t('setting.speedMedium')" :value="'二档'"></el-option>
+                <el-option :label="$t('setting.speedSlow')" :value="'三档'"></el-option>
               </el-select>
             </div>
           </div>
-
           <other-config/>
-          <!--                <div class="form-item">
-                              <label>运行模式:</label>
-                              <div class="select-wrapper">
-                              <el-select v-model="formData.other_configs.running_mode" placeholder="请选择">
-                                <el-option v-for="item in runModeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
-                              </el-select>
-                              </div>
-                          </div>-->
         </div>
-
       </template>
-      <!--其他设置-->
-          <div class="selectBox" style="padding-top: 0px;padding-left: 0;" v-if="activeIndex === 4">
-            <actionConfig/>
 
-          </div>
-    </div>
-      <div class="text-center mt-8">
-        <button class="bg-gradient-to-r from-primary" @click="onSava(activeIndex)" v-if="activeIndex !== 4" v-log="{ describe: { action: '点击保存设置', tabIndex: activeIndex } }">
-          保存
-        </button>
+      <div class="selectBox" style="padding-top: 0px;padding-left: 0;" v-if="activeIndex === 4">
+        <actionConfig/>
       </div>
-
+    </div>
+    <div class="text-center mt-8">
+      <button class="bg-gradient-to-r from-primary" @click="onSave(activeIndex)" v-if="activeIndex !== 4" v-log="{ describe: { action: '点击保存设置', tabIndex: activeIndex } }">
+        {{ $t('common.save') }}
+      </button>
+    </div>
   </div>
 </template>
 
 <script setup>
-/**
- * Vue组件逻辑部分,包含与设备配置相关的功能。
- * 主要功能包括:表单数据管理、设备配置列表获取、新增/编辑/删除步骤、保存配置等。
- */
-
-// 引入Vue相关功能和第三方库
 import { ref, reactive } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { onMounted, watch } from 'vue';
-import  { getAllUserConfigs,  setAllUserConfigs } from '@/apis/setting'
+import { useI18n } from 'vue-i18n';
+import { getAllUserConfigs, setAllUserConfigs } from '@/apis/setting'
 import socket from "@/stores/modules/socket";
 import headerBar from '@/components/header-bar/index.vue';
 import ColorPickerWithRecent from '@/views/components/common/ColorPickerWithRecent.vue'
 import client from "@/stores/modules/client";
 import icpList from '@/utils/ipc';
-const clientStore = client();
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { digiCamControlWEB } from  '@/utils/appconfig'
-import { useCheckInfo } from '@/composables/userCheck';
-import { preview } from '@planckdev/element-plus/utils'
+import { ElMessage } from 'element-plus';
 import actionConfig from './components/action_config.vue'
 import otherConfig from './components/otherConfig'
 import CameraConfig from './components/CameraConfig';
+import DebugPanel from './components/DebugPanel.vue';
+import { useCheckInfo } from '@/composables/userCheck';
 useCheckInfo();
 
-//点击三次  打开资源目录
-
-import DebugPanel from './components/DebugPanel.vue';
-// 在setup函数中创建调试面板实例
+const { t } = useI18n()
 const debugPanel = ref(null);
 const cameraConfigRef = ref(null);
-// 添加设置点击计数器
 const settingClickCount = ref(0);
 
-// 修改headerBar的点击处理函数
 function handleSettingClick() {
-  console.log('handleSettingClickhandleSettingClick')
   settingClickCount.value++;
-
   if (settingClickCount.value >= 3) {
-    if (debugPanel.value) {
-      debugPanel.value.showDebugPanel();
-    }
+    if (debugPanel.value) debugPanel.value.showDebugPanel();
     settingClickCount.value = 0;
   }
-
-  setTimeout(() => {
-    settingClickCount.value = 0;
-  }, 3000); // 3秒内未再次点击则重置计数器
+  setTimeout(() => { settingClickCount.value = 0; }, 3000);
 }
 
-
-
-
-// 路由和状态管理初始化
 const route = useRoute();
 const router = useRouter();
-
-// 定义响应式变量
-const folderPath = ref(''); // 文件夹路径
-const activeIndex = ref(0); // 当前激活的索引
-const socketStore = socket(); // WebSocket状态管理实例
-
-
-import  configInfo  from '@/stores/modules/config';
+const activeIndex = ref(0);
+const socketStore = socket();
+import configInfo from '@/stores/modules/config';
 const configInfoStore = configInfo();
 
-/**
- * 表单数据对象,用于存储设备配置信息。
- */
 const formData = reactive({
-  //基础配置
-  basic_configs:{
-    "main_image_size": [],//主图尺寸
-    "image_out_format": "",//图片输出格式
-    "image_sharpening": 1, //图片锐化(默认值改为1)
-    "padding_800image": 100, //800图自定义边距
-    "is_flip_800image": 1, //800图是否翻转
-    "color_800image": '#ffffff', //800图颜色配置
-    "image_mask_config": { //阴影处理配置
-      "mode": 0, //阴影处理模式:0默认模式,1自定义模式
-      "opacity": 0.5, //透明度,默认0.5,范围0.5-1
-      "grenerate_main_pic_brightness": 254 //亮度范围,默认254,范围200-255
-    }
-  },
-  //拍照配置
-  take_photo_configs:{
-    "repart_take_photo_warning": false,//重复拍摄警告
-    "single_photo_warning": "",//单次张数警告
-    "total_photo_warning": "",//累计拍照警告
-    "camera_delay": ""//拍照停留
-  },
-  other_configs:{
-    "product_type": "",//产品类型
-    "cutout_mode": "",//默认抠图模式
-    "device_speed": "",//设备运动速度
-    "running_mode": "" //运行模式
-  },
-/*  captureOneFolder: '', // Capture One文件夹路径
-  mainImageSize: '', // 主图尺寸
-  imageFormat: '', // 图片格式
-  imageSharpening: '', // 图片锐化
-  repeatWarning: '', // 重复警告
-  singleWarning: '', // 单次警告
-  totalWarning: '', // 总警告
-  focusTime: '', // 对焦时间
-  photoTime: '', // 拍照时间
-  productType: '', // 产品类型
-  defaultCutoutMode: '', // 默认抠图模式
-  deviceSpeed: '', // 设备速度
-  runMode: '', // 运行模式
-  receiver: '', // 接收器类型
-  left: '', // 左脚配置
-  right: '', // 右脚配置
-  up: '', // 上移配置
-  down: '', // 下移配置*/
+  basic_configs: { main_image_size: [], image_out_format: "", image_sharpening: 1, padding_800image: 100, is_flip_800image: 1, color_800image: '#ffffff', image_mask_config: { mode: 0, opacity: 0.5, grenerate_main_pic_brightness: 254 } },
+  take_photo_configs: { repart_take_photo_warning: false, single_photo_warning: "", total_photo_warning: "", camera_delay: "" },
+  other_configs: { product_type: "", cutout_mode: "", device_speed: "", running_mode: "" },
 });
 
-// 配置选项列表
 const mainImageSizeList = ref([
-  { label: '320*320', value: 320 },
-  { label: '512*512', value: 512 },
-  { label: '768*768', value: 768 },
-  { label: '800*800', value: 800 },
-  { label: '1024*1024', value: 1024 },
-  { label: '1200*1200', value: 1200 },
-  { label: '1400*1400', value: 1400 },
-  { label: '1600*1600', value: 1600 },
-  { label: '自定义', value: 'custom' } // 新增自定义选项
+  { label: '320*320', value: 320 }, { label: '512*512', value: 512 }, { label: '768*768', value: 768 },
+  { label: '800*800', value: 800 }, { label: '1024*1024', value: 1024 }, { label: '1200*1200', value: 1200 },
+  { label: '1400*1400', value: 1400 }, { label: '1600*1600', value: 1600 }, { label: '自定义', value: 'custom' }
 ]);
 
-const customInput = ref(null); // 新增自定义输入值
+const customInput = ref(null);
 
 const imageFormatList = ref([
-  { label: 'jpg', value: 'jpg' },
-  { label: 'png', value: 'png' },
-  { label: 'jpeg', value: 'jpeg' },
-  { label: 'webp', value: 'webp' },
-  { label: 'avif', value: 'avif' },
-]);
-// 图片锐化列表已移除,改为输入框
-const isFlip800ImageList = ref([
-  { label: '是', value: 1 },
-  { label: '否', value: 0 },
-]);
-const repeatWarningList = ref([
-  { label: '关闭', value: false },
-  { label: '开启', value: true },
-]);
-const singleWarningList = ref([
-  { label: '11', value: '11' },
-  { label: '12', value: '12' },
-  { label: '13', value: '13' },
-]);
-const totalWarningList = ref([
-  { label: '1.0', value: '1.0' },
-  { label: '1.5', value: '1.5' },
-  { label: '2.0', value: '2.0' },
+  { label: 'jpg', value: 'jpg' }, { label: 'png', value: 'png' }, { label: 'jpeg', value: 'jpeg' },
+  { label: 'webp', value: 'webp' }, { label: 'avif', value: 'avif' },
 ]);
+
+const isFlip800ImageList = ref([{ label: '是', value: 1 }, { label: '否', value: 0 }]);
 const productTypeList = ref([
-  { label: '鞋类', value: '鞋类' },
-  { label: '服装', value: '服装' },
-  { label: '箱包', value: '箱包' },
+  { label: '鞋类', value: '鞋类' }, { label: '服装', value: '服装' }, { label: '箱包', value: '箱包' },
 ]);
 const defaultCutoutModeList = ref([
-  { label: '普通抠图', value: '普通抠图' },
-  { label: '精细化抠图', value: '精细化抠图' },
-]);
-const deviceSpeedList = ref([
-  { label: '快', value: '一档' },
-  { label: '中', value: '二档' },
-  { label: '慢', value: '三档' },
-]);
-/*
-const runModeList = ref([
-  { label: '普通模式', value: '普通模式' },
-  { label: '待用户确认模式', value: '待用户确认模式' }
-]);
-const receiverList = ref([
-  { label: '蓝牙', value: '1' },
-  { label: '2.4G', value: '2' },
-  { label: '5.8G', value: '3' },
-]);
-const leftList = ref([
-  { label: '左脚', value: '1' },
-  { label: '右脚', value: '2' },
-  { label: '左右脚', value: '3' },
+  { label: '普通抠图', value: '普通抠图' }, { label: '精细化抠图', value: '精细化抠图' },
 ]);
-const rightList = ref([
-  { label: '左脚', value: '1' },
-  { label: '右脚', value: '2' },
-  { label: '左右脚', value: '3' },
-]);
-const upList = ref([
-  { label: '上移', value: '1' },
-  { label: '下移', value: '2' },
-  { label: '左右移', value: '3' },
-]);
-const downList = ref([
-  { label: '上移', value: '1' },
-  { label: '下移', value: '2' },
-  { label: '左右移', value: '3' },
-]);
-*/
-
-const indexKey  ={
-  0:"basic_configs",
-  1:"take_photo_configs",
-  2:"other_configs",
-}
-
-
-/**
- * 监听路由参数变化,更新activeIndex和activeTab。
- */
-watch(() => route.query.type, async (newType,oldType) => {
-  const typeValue = parseInt(newType) || 0;
-  getConfig()
-});
-
-
 
-/**
- * 监听activeIndex变化,更新URL中的查询参数。
- */
+watch(() => route.query.type, async (newType) => { getConfig() });
 watch(() => activeIndex.value, (newIndex) => {
-  router.push({
-    query: {
-      ...route.query,
-      type: newIndex.toString()
-    }
-  });
+  router.push({ query: { ...route.query, type: newIndex.toString() } });
 });
 
-const getConfig =  async (typeValue)=>{
-
+const getConfig = async () => {
   const resultPHP = await getAllUserConfigs();
-  if(resultPHP.code == 0 &&  resultPHP.data.configs ){
-      Object.keys(resultPHP.data.configs).map(item=>{
-        formData[item] =  resultPHP.data.configs[item]
-      })
-
+  if(resultPHP.code == 0 && resultPHP.data.configs) {
+    Object.keys(resultPHP.data.configs).map(item=>{ formData[item] = resultPHP.data.configs[item] })
     const presetSizes = mainImageSizeList.value.map(item => item.value);
     const receivedSizes = formData.basic_configs.main_image_size ? [...formData.basic_configs.main_image_size] : [];
-
-    // 分离自定义值
     const customValues = receivedSizes.filter(v => !presetSizes.includes(v));
     if (customValues.length > 0 && activeIndex.value == 0) {
-      customInput.value = customValues[0]; // 保留第一个自定义值
-      // 更新选中状态
-      console.log('111')
-      formData.basic_configs.main_image_size = receivedSizes
-          .filter(v => presetSizes.includes(v))
-          .concat('custom');
+      customInput.value = customValues[0];
+      formData.basic_configs.main_image_size = receivedSizes.filter(v => presetSizes.includes(v)).concat('custom');
     } else {
-      console.log('222')
       formData.basic_configs.main_image_size = receivedSizes;
     }
-    console.log(formData.basic_configs.main_image_size);
-
-    // 确保新字段有默认值(如果服务器没有返回)
-    if (formData.basic_configs.padding_800image === undefined || formData.basic_configs.padding_800image === null) {
-      formData.basic_configs.padding_800image = 100;
-    }
-    if (formData.basic_configs.is_flip_800image === undefined || formData.basic_configs.is_flip_800image === null) {
-      formData.basic_configs.is_flip_800image = 0;
-    }
-    if (formData.basic_configs.color_800image === undefined || formData.basic_configs.color_800image === null) {
-      formData.basic_configs.color_800image = '#ffffff';
-    }
-
-    // 确保图片锐化有默认值
-    if (formData.basic_configs.image_sharpening === undefined || formData.basic_configs.image_sharpening === null) {
-      formData.basic_configs.image_sharpening = 1;
-    }
-
-    // 确保阴影处理配置有默认值
+    if (formData.basic_configs.padding_800image === undefined || formData.basic_configs.padding_800image === null) formData.basic_configs.padding_800image = 100;
+    if (formData.basic_configs.is_flip_800image === undefined || formData.basic_configs.is_flip_800image === null) formData.basic_configs.is_flip_800image = 0;
+    if (formData.basic_configs.color_800image === undefined) formData.basic_configs.color_800image = '#ffffff';
+    if (formData.basic_configs.image_sharpening === undefined) formData.basic_configs.image_sharpening = 1;
     if (!formData.basic_configs.image_mask_config) {
-      formData.basic_configs.image_mask_config = {
-        mode: 0,
-        opacity: 0.5,
-        grenerate_main_pic_brightness: 254
-      };
+      formData.basic_configs.image_mask_config = { mode: 0, opacity: 0.5, grenerate_main_pic_brightness: 254 };
     } else {
-      // 确保每个字段都有默认值
-      if (formData.basic_configs.image_mask_config.mode === undefined || formData.basic_configs.image_mask_config.mode === null) {
-        formData.basic_configs.image_mask_config.mode = 0;
-      }
-      if (formData.basic_configs.image_mask_config.opacity === undefined || formData.basic_configs.image_mask_config.opacity === null) {
-        formData.basic_configs.image_mask_config.opacity = 0.5;
-      }
-      if (formData.basic_configs.image_mask_config.grenerate_main_pic_brightness === undefined || formData.basic_configs.image_mask_config.grenerate_main_pic_brightness === null) {
-        formData.basic_configs.image_mask_config.grenerate_main_pic_brightness = 254;
-      }
+      if (formData.basic_configs.image_mask_config.mode === undefined) formData.basic_configs.image_mask_config.mode = 0;
+      if (formData.basic_configs.image_mask_config.opacity === undefined) formData.basic_configs.image_mask_config.opacity = 0.5;
+      if (formData.basic_configs.image_mask_config.grenerate_main_pic_brightness === undefined) formData.basic_configs.image_mask_config.grenerate_main_pic_brightness = 254;
     }
-
-    // 处理图片锐化:如果值为0或小于1,自动改为1
     const sharpeningValue = parseFloat(formData.basic_configs.image_sharpening);
-    if (isNaN(sharpeningValue) || sharpeningValue < 1) {
-      formData.basic_configs.image_sharpening = 1;
-    }
+    if (isNaN(sharpeningValue) || sharpeningValue < 1) formData.basic_configs.image_sharpening = 1;
   }
-
 }
-/**
- * 组件挂载时初始化activeIndex。
- */
+
 onMounted(async () => {
   getConfig()
-
-  let  type = 0 ;
   if (route.query.type) {
     const typeValue = parseInt(route.query.type);
-    type  = typeValue
-    if (!isNaN(typeValue) && typeValue >= 0 && typeValue <= 3) {
-      activeIndex.value = typeValue;
-    }
+    if (!isNaN(typeValue) && typeValue >= 0 && typeValue <= 4) activeIndex.value = typeValue;
   }
-
-
 });
 
-
-const handleKeyPress = (event) => {
-  const char = event.key;
-  // 只允许输入数字字符
-  if (!/^\d+$/.test(char)) {
-    event.preventDefault(); // 阻止非数字输入
-  }
-};
+const handleKeyPress = (event) => { if (!/^\d+$/.test(event.key)) event.preventDefault(); };
 const handleInput = (value) => {
-  if (value > 3000) {
-    customInput.value = 3000;
-  } else if (value < 1) {
-    customInput.value = 1;
-  }
-};
-
-// 处理800图边距输入
-const handlePaddingKeyPress = (event) => {
-  const char = event.key;
-  // 只允许输入数字字符
-  if (!/^\d+$/.test(char)) {
-    event.preventDefault(); // 阻止非数字输入
-  }
+  if (value > 3000) customInput.value = 3000;
+  else if (value < 1) customInput.value = 1;
 };
-
+const handlePaddingKeyPress = (event) => { if (!/^\d+$/.test(event.key)) event.preventDefault(); };
 const handlePaddingInput = () => {
   let value = formData.basic_configs.padding_800image;
-  // 处理空值或无效值
-  if (value === null || value === undefined || value === '' || isNaN(value)) {
-    formData.basic_configs.padding_800image = 0;
-    return;
-  }
-  // 转换为数字并确保是整数
-  value = Number(value);
-  value = Math.floor(value);
-
-  // 限制范围
-  if (value > 500) {
-    formData.basic_configs.padding_800image = 500;
-  } else if (value < 0) {
-    formData.basic_configs.padding_800image = 0;
-  } else {
-    formData.basic_configs.padding_800image = value;
-  }
+  if (value === null || value === undefined || value === '' || isNaN(value)) { formData.basic_configs.padding_800image = 0; return; }
+  value = Math.floor(Number(value));
+  if (value > 500) formData.basic_configs.padding_800image = 500;
+  else if (value < 0) formData.basic_configs.padding_800image = 0;
+  else formData.basic_configs.padding_800image = value;
 };
 
-// 添加更新camera_configs的方法
-const updateCameraConfigs = (configs) => {
-  formData.camera_configs = configs;
-};
+const updateCameraConfigs = (configs) => { formData.camera_configs = configs; };
 
 const toggleTab = async (item) => {
   const oldType = activeIndex.value;
-  // 切换前保存当前 Tab 配置(包含相机配置 3)
   if ([0,1,2,3].includes(oldType)) {
     const next = await saveSetting(oldType);
     if (next === false) return false;
@@ -600,20 +288,12 @@ const toggleTab = async (item) => {
   return true;
 };
 
-const onSava = async (index)=>{
-  const next =  await  saveSetting(index)
-
-  if(next !== false){
-    ElMessage.success('保存成功')
-  }
-
+const onSave = async (index) => {
+  const next = await saveSetting(index)
+  if(next !== false) ElMessage.success(t('message.saveSuccess'))
 }
 
-/**
- * 保存当前表单配置。
- */
 const saveSetting = async (index) => {
-  // 构建临时提交数据
   if(index === 3) {
     if (cameraConfigRef.value && typeof cameraConfigRef.value.save === 'function') {
       if(! cameraConfigRef.value.save()) return false;
@@ -621,337 +301,54 @@ const saveSetting = async (index) => {
   }
   const submitData = JSON.parse(JSON.stringify(formData));
   if(index === 0) {
-    if (formData.basic_configs.main_image_size.length === 0) {
-      ElMessage.error('请选择主图尺寸!');
-      return false;
-    }
-
-    // 处理自定义尺寸逻辑
-    const selectedSizes = [...formData.basic_configs.main_image_size]; // 创建副本避免修改原始数据
-
+    if (formData.basic_configs.main_image_size.length === 0) { ElMessage.error(t('validation.selectMainImageSize')); return false; }
+    const selectedSizes = [...formData.basic_configs.main_image_size];
     if (selectedSizes.includes('custom')) {
-      if (!customInput.value || isNaN(customInput.value) ||
-          customInput.value < 1 || customInput.value > 3000) {
-        ElMessage.error('请输入1-3000之间的有效数值');
-        return false;
-      }
-
-      // 创建新数组用于提交
-      const submitSizes = selectedSizes
-          .filter(v => v !== 'custom')
-          .concat(parseInt(customInput.value));
-
-      submitData.basic_configs.main_image_size = submitSizes
+      if (!customInput.value || isNaN(customInput.value) || customInput.value < 1 || customInput.value > 3000) { ElMessage.error(t('validation.inputValidSize')); return false; }
+      submitData.basic_configs.main_image_size = selectedSizes.filter(v => v !== 'custom').concat(parseInt(customInput.value));
     }
-
-    // 验证800图自定义边距
     const paddingValue = formData.basic_configs.padding_800image;
-    if (paddingValue === undefined || paddingValue === null || isNaN(paddingValue) ||
-        paddingValue < 0 || paddingValue > 500) {
-      ElMessage.error('800图自定义边距必须在0-500之间');
-      return false;
-    }
-    // 确保是整数
+    if (paddingValue === undefined || paddingValue === null || isNaN(paddingValue) || paddingValue < 0 || paddingValue > 500) { ElMessage.error(t('validation.paddingRange')); return false; }
     submitData.basic_configs.padding_800image = Math.floor(paddingValue);
-
-    // 验证图片锐化
     const sharpeningValue = parseFloat(formData.basic_configs.image_sharpening);
-    if (isNaN(sharpeningValue) || sharpeningValue < 1 || sharpeningValue > 5) {
-      ElMessage.error('图片锐化必须在1-5之间');
-      return false;
-    }
+    if (isNaN(sharpeningValue) || sharpeningValue < 1 || sharpeningValue > 5) { ElMessage.error(t('validation.sharpenRange')); return false; }
     submitData.basic_configs.image_sharpening = sharpeningValue;
-
-    // 验证阴影处理配置
     const maskConfig = formData.basic_configs.image_mask_config;
-    if (!maskConfig || typeof maskConfig.mode !== 'number' || (maskConfig.mode !== 0 && maskConfig.mode !== 1)) {
-      ElMessage.error('请选择有效的阴影处理模式');
-      return false;
-    }
-
-    // 如果是自定义模式,验证透明度和亮度
+    if (!maskConfig || typeof maskConfig.mode !== 'number' || (maskConfig.mode !== 0 && maskConfig.mode !== 1)) { ElMessage.error(t('validation.selectMaskMode')); return false; }
     if (maskConfig.mode === 1) {
-      const opacity = parseFloat(maskConfig.opacity);
-      const brightness = parseInt(maskConfig.grenerate_main_pic_brightness);
-
-      if (isNaN(opacity) || opacity < 0.5 || opacity > 1) {
-        ElMessage.error('透明度必须在0.5-1之间');
-        return false;
-      }
-
-      if (isNaN(brightness) || brightness < 200 || brightness > 255) {
-        ElMessage.error('亮度范围必须在200-255之间');
-        return false;
-      }
-
+      const opacity = parseFloat(maskConfig.opacity); const brightness = parseInt(maskConfig.grenerate_main_pic_brightness);
+      if (isNaN(opacity) || opacity < 0.5 || opacity > 1) { ElMessage.error(t('validation.opacityRange')); return false; }
+      if (isNaN(brightness) || brightness < 200 || brightness > 255) { ElMessage.error(t('validation.brightnessRange')); return false; }
       submitData.basic_configs.image_mask_config.opacity = opacity;
       submitData.basic_configs.image_mask_config.grenerate_main_pic_brightness = brightness;
     }
-
-    // 验证800图是否翻转
     const flipValue = formData.basic_configs.is_flip_800image;
-    if (flipValue === undefined || flipValue === null || (flipValue !== 0 && flipValue !== 1)) {
-      ElMessage.error('请选择800图是否翻转');
-      return false;
-    }
+    if (flipValue === undefined || flipValue === null || (flipValue !== 0 && flipValue !== 1)) { ElMessage.error(t('validation.selectFlip')); return false; }
     submitData.basic_configs.is_flip_800image = flipValue;
-
   }
-  const params = JSON.parse(JSON.stringify({
-    configs:submitData
-  }))
+  const params = JSON.parse(JSON.stringify({ configs: submitData }))
   const result = await setAllUserConfigs(params)
-  if(result.code != 0)  return  false;
-  return  true;
-
+  if(result.code != 0) return false;
+  return true;
 };
-
-
-
 </script>
 
 <style lang="scss">
-.el-image-viewer__wrapper{
-  z-index: 9999 !important;
-}
+.el-image-viewer__wrapper{ z-index: 9999 !important; }
 .setting-wrap {
-
-  .selectBox{
-    padding-top: 30px;
-    padding-left: 100px;
-    border-bottom: 1px solid rgba(0,0,0,0.1);
-    ::v-deep(.el-tabs__header){
-      padding-left: 0;
-    }
-    ::v-deep(.el-tabs--card>.el-tabs__header){
-      border-bottom: 1px solid #CCCCCC;
-    }
-    ::v-deep(.el-tabs__item){
-      height: 30px;
-      line-height: 30px;
-    }
-    ::v-deep(.el-tabs__nav-wrap){
-      margin-bottom: 0;
-    }
-    ::v-deep(.el-tabs__item.is-active){
-      color: #333;
-      font-weight: bold;
-      background: #fff;
-    }
-  }
-
-  .form-item {
-    margin-bottom: 24px;
-    display: flex;
-    align-items: center;
-  }
-  .form-item label {
-    display: block;
-    width: 140px;
-    flex-shrink: 0;
-    text-align: right;
-    font-size: 14px;
-    color: #1A1A1A;
-    padding-right: 12px;
-  }
-
-  .select-wrapper {
-    position: relative;
-    width: 200px;
-    ::v-deep(.el-input__inner){
-      border-radius: 6px;
-    }
-  }
+  .selectBox{ padding-top: 30px; padding-left: 100px; border-bottom: 1px solid rgba(0,0,0,0.1); }
+  .form-item { margin-bottom: 24px; display: flex; align-items: center; }
+  .form-item label { display: block; width: 140px; flex-shrink: 0; text-align: right; font-size: 14px; color: #1A1A1A; padding-right: 12px; }
+  .select-wrapper { position: relative; width: 200px; ::v-deep(.el-input__inner){ border-radius: 6px; } }
 }
 </style>
 <style lang="scss" scoped>
-body {
-  background: #EAECED;
-}
-.container {
-  margin: 0 auto;
-  min-height: calc(100vh - 30px);
-  background: #EAECED;
-  display: flex;
-  flex-direction: column;
-  justify-content: flex-start;
-}
-.settings-nav {
-  display: flex;
-  justify-content: center;
-  gap: 40px;
-  padding: 30px 0;
-  background: #EDEFF0;
-  border-bottom: 1px solid rgba(0,0,0,0.1);
-}
-.nav-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  cursor: pointer;
-  color: #666;
-  transition: all 0.3s;
-  width: 100px;
-  justify-content: center;
-  height: 70px;
-  font-size: 14px;
-}
-.nav-item.active {
-    background: #DFE2E3;
-    border-radius: 10px;
-    color: #2957FF;
-}
-.nav-item i {
-  font-size: 24px;
-  margin-bottom: 8px;
-  width: 40px;
-  height: 40px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background: #fff;
-  border-radius: 8px;
-}
-.form-container {
-  border-radius: 12px;
-  padding: 30px;
-  padding-top: 10px;
-  padding-bottom: 40px;
-  width: 800px;
-  margin: 0 auto;
-  min-height: 400px;
-}
-.input-group {
-  display: flex;
-  gap: 12px;
-}
-.input-group input {
-  flex: 1;
-  border: 1px solid #e5e7eb;
-  border-radius: 4px;
-  padding: 8px 12px;
-  font-size: 14px;
-}
-.error-text {
-  color: #dc2626;
-  font-size: 12px;
-  margin-top: 4px;
-}
-.from-primary{
-    width: 150px;
-    height: 40px;
-    color: #FFFFFF;
-    background: linear-gradient( 135deg, #2FB0FF 0%, #B863FB 100%);
-}
-.nav-icon{
-    width: 32px;
-    height: 32px;
-}
-.captureBox{
-  border-bottom: 1px solid rgba(0,0,0,0.1);
-}
-.select-btn{
-  display: flex;
-  align-items: center;
-  flex-shrink: 0;
-  width: 120px;
-  height: 30px;
-  background: #DFE2E3;
-  border-radius: 6px;
-  justify-content: center;
-  font-size: 14px;
-  color: #2957FF;
-  gap: 5px;
-  img{
-    width: 16px;
-    height: 16px;
-  }
-}
-.mt-8{
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 30px 0;
-  height: 100px;
-}
-  .config-type{
-    font-size: 14px;
-    color: #333333;
-    display: flex;
-    align-items: center;
-    justify-content: flex-start;
-    padding: 10px 0;
-    .el-checkbox{
-      margin-left: 10px;
-    }
-  }
-.editForm{
-  display: grid;
-  grid-template-columns: repeat(2, 1fr);
-  gap: 0;
-  .flex-row{
-    display: flex;
-    align-items: center;
-    ::v-deep(.el-radio){
-      margin-right: 6px !important;
-    }
-    ::v-deep(.el-radio__label){
-      padding-left: 4px;
-    }
-  }
-  ::v-deep(.el-form-item) {
-    margin-bottom: 0;
-    .el-form-item__label {
-      width: 120px !important;
-      padding-right: 0 !important;
-        background: #FBFCFF;
-        border: 1px solid #EEEEEE;
-        height: 41px;
-        line-height: 41px;
-        padding-left: 5px;
-        text-align: left;
-      }
-      .el-form-item__content {
-        width: 190px;
-        position: relative;
-        height: 41px;
-        background: #FFFFFF;
-        padding-left: 7px;
-        border: 1px solid #EEEEEE;
-        .el-input__wrapper {
-          box-shadow: none;
-        }
-        .error-msg{
-          display: none;
-          position: absolute;
-          top: 41px;
-          top: 28px;
-          left: 8px;
-          z-index: 22;
-          color: #dc2626;
-          font-size: 12px;
-        }
-        &:hover{
-          .error-msg{
-            display: block;
-          }
-        }
-
-        // 确保number类型输入框的上下箭头始终显示
-        input[type="number"]::-webkit-inner-spin-button,
-        input[type="number"]::-webkit-outer-spin-button {
-          opacity: 1;
-          height: 28px;
-          position: absolute;
-          top: 2px;
-          right: 2px;
-          cursor: pointer;
-        }
-
-        input[type="number"] {
-          -moz-appearance: textfield; /* Firefox */
-        }
-      }
-    }
-}
+.container { margin: 0 auto; min-height: calc(100vh - 30px); background: #EAECED; display: flex; flex-direction: column; justify-content: flex-start; }
+.settings-nav { display: flex; justify-content: center; gap: 40px; padding: 30px 0; background: #EDEFF0; border-bottom: 1px solid rgba(0,0,0,0.1); }
+.nav-item { display: flex; flex-direction: column; align-items: center; cursor: pointer; color: #666; transition: all 0.3s; width: 100px; justify-content: center; height: 70px; font-size: 14px; }
+.nav-item.active { background: #DFE2E3; border-radius: 10px; color: #2957FF; }
+.nav-icon{ width: 32px; height: 32px; }
+.form-container { border-radius: 12px; padding: 30px; padding-bottom: 40px; width: 800px; margin: 0 auto; min-height: 400px; }
+.mt-8{ display: flex; align-items: center; justify-content: center; padding: 30px 0; height: 100px; }
+.bg-gradient-to-r.from-primary{ width: 150px; height: 40px; color: #FFFFFF; background: linear-gradient(135deg, #2FB0FF 0%, #B863FB 100%); border-radius: 8px; font-size: 14px; cursor: pointer; border: none; }
 </style>

+ 16 - 15
public/dist/index.html

@@ -1,16 +1,17 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="./vite.svg" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>智惠映AI自动拍照机</title>
-    <script type="module" crossorigin src="./assets/index-BROJaE7c.js"></script>
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="./vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>智惠映AI自动拍照机</title>
+    <script type="module" crossorigin src="./assets/index-C2B0o40u.js"></script>
     <link rel="stylesheet" crossorigin href="./assets/index-B8MPi9a0.css">
-  </head>
-  <body>
-    <div id="app"></div>
-<!--    <script  src="/src/views/components/PictureEditor/js/fabric.js"></script>-->
-    <script src="./js/fabric.js"></script>

-  </body>
-</html>
+  </head>
+  <body>
+    <div id="app"></div>
+<!--    <script  src="/src/views/components/PictureEditor/js/fabric.js"></script>-->
+    <script src="./js/fabric.js"></script>
+
+  </body>
+</html>