ソースを参照

Merge remote-tracking branch 'origin/anheng' into anheng

rambo 1 日 前
親
コミット
2a34907a6e

+ 47 - 23
electron/utils/camera.js

@@ -90,26 +90,47 @@ function controllerChoiceMatches(config, type, dir) {
     && sameDir(config[spec.pathKey], dir)
 }
 
+function uniqueDirs(dirs) {
+  const result = []
+  for (const dir of dirs) {
+    if (!dir) continue
+    if (result.some((item) => sameDir(item, dir))) continue
+    result.push(dir)
+  }
+  return result
+}
+
+function rememberController(config, detected, dir) {
+  if (!controllerChoiceMatches(config, detected, dir)) {
+    Log.info(`按安装目录识别为 ${CONTROLLERS[detected].label}: ${dir}`)
+    saveControllerChoice(detected, dir)
+  }
+  return {
+    type: detected,
+    ...CONTROLLERS[detected],
+    dir,
+  }
+}
+
 function getController() {
   const config = readConfigFile() || {}
   const type = normalizeControlType(config.controlType)
   const spec = CONTROLLERS[type]
   const dir = config.controlPath || config[spec.pathKey] || ""
 
-  // 以目录里实际存在的 exe 为准。
-  // 否则会出现 controlPath 已是 Smart Shooter 6,controlType 仍是 digiCamControl,
-  // 程序却去这个目录里找 CameraControl.exe。
-  const detected = detectController(dir)
-  if (detected) {
-    if (!controllerChoiceMatches(config, detected, dir)) {
-      Log.info(`按安装目录识别为 ${CONTROLLERS[detected].label}: ${dir}`)
-      saveControllerChoice(detected, dir)
-    }
-    return {
-      type: detected,
-      ...CONTROLLERS[detected],
-      dir,
-    }
+  // 上次选中的目录里有 exe 时,以那个 exe 为准。
+  // 避免 controlPath 已是 Smart Shooter 6,controlType 仍是 digiCamControl。
+  const selected = detectController(config.controlPath)
+  if (selected) return rememberController(config, selected, config.controlPath)
+
+  // controlPath 无效时,再找该类型自己的目录和出厂目录,避免坏路径挡住已经装好的程序。
+  const fallbackDirs = uniqueDirs([
+    config[spec.pathKey],
+    defaultConfig[spec.pathKey],
+    type === "SmartShooter6" ? defaultConfig.controlPath : "",
+  ])
+  for (const candidate of fallbackDirs) {
+    if (detectController(candidate) === type) return rememberController(config, type, candidate)
   }
 
   // 默认 Smart Shooter 6 目录下没有程序时,再找 Smart Shooter 5。只影响本次启动,不回写配置。
@@ -177,10 +198,14 @@ async function ensureCameraController() {
 
     if (!exists) {
       // 弹出文件夹选择对话框
-      const { canceled, filePaths } = await dialog.showOpenDialog({
+      const dialogOptions = {
         title: '选择 相机控制安装软件 文件夹',
         properties: ['openDirectory']
-      });
+      }
+      const mainWindow = CoreWindow.getMainWindow()
+      const { canceled, filePaths } = mainWindow && !mainWindow.isDestroyed()
+        ? await dialog.showOpenDialog(mainWindow, dialogOptions)
+        : await dialog.showOpenDialog(dialogOptions);
 
       if (!canceled && filePaths.length > 0) {
         const selectedPath = filePaths[0];
@@ -203,7 +228,7 @@ async function ensureCameraController() {
       }
     }
     const res = await openCameraControlCmd();
-    
+
     // Smart Shooter 5 / 6 启动后最小化窗口;digiCamControl 走自己的最小化命令
     if(isSmartShooterType(getController().type)){
       // 等待软件启动完成
@@ -215,7 +240,7 @@ async function ensureCameraController() {
         }
       }, 3000); // 等待3秒让软件完全启动
     }
-    
+
     return res;
   } catch (error) {
     Log.error('检查 第三方相机控制器 是否存在时出错:', error);
@@ -237,7 +262,10 @@ function openCameraControlCmd() {
 
       let child
       try {
-        child = spawn(exePath, [], { cwd: path.dirname(exePath) })
+        child = spawn(exePath, [], {
+          cwd: path.dirname(exePath),
+          stdio: 'ignore',
+        })
       } catch (error) {
         Log.error('无法启动第三方相机控制器:', error)
         finish(reject, error)
@@ -248,10 +276,6 @@ function openCameraControlCmd() {
       isProcessRunning = true
       console.log('[CAMERA-DEBUG] spawn PID:', child.pid, 'cwd:', path.dirname(exePath), '时间:', new Date().toISOString());
 
-      child.stdout.on('data', (data) => {
-        console.log('[CAMERA-DEBUG] stdout 收到数据:', data.toString().substring(0, 100));
-      });
-
       child.on('close', (code) => {
         console.log('[CAMERA-DEBUG] close 事件触发! PID:', cameraProcess?.pid, '退出码:', code, '时间:', new Date().toISOString());
         isProcessRunning = false;

+ 67 - 47
electron/utils/config.js

@@ -14,72 +14,92 @@ const LEGACY_SMARTSHOOTER5 = {
   controlPath: "C:\\Program Files\\Smart Shooter 5",
 }
 
-function migrateLegacySmartShooter(config) {
-  if (!config || config.smartShooter6Migrated) return { config, changed: false }
-  const stillFactoryDefault = config.controlType === LEGACY_SMARTSHOOTER5.controlType
-    && config.controlPath === LEGACY_SMARTSHOOTER5.controlPath
-  config.smartShooter6Migrated = true
-  if (!stillFactoryDefault) return { config, changed: true }
-  config.controlType = defaultConfig.controlType
-  config.controlPath = defaultConfig.controlPath
-  config.SmartShooterPath = config.SmartShooterPath || LEGACY_SMARTSHOOTER5.controlPath
-  config.SmartShooter6Path = defaultConfig.SmartShooter6Path
-  return { config, changed: true }
+function sameValue(a, b) {
+  return JSON.stringify(a) === JSON.stringify(b)
+}
+
+// 用户目录只保留和 utils 默认值不同的项
+function stripDefaults(overrides) {
+  const next = { ...overrides }
+  for (const key of Object.keys(next)) {
+    if (Object.prototype.hasOwnProperty.call(defaultConfig, key) && sameValue(next[key], defaultConfig[key])) {
+      delete next[key]
+    }
+  }
+  return next
+}
+
+function overridesEqual(a, b) {
+  const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})])
+  for (const key of keys) {
+    if (!sameValue(a[key], b[key])) return false
+  }
+  return true
+}
+
+function readOverrides() {
+  if (!fs.existsSync(configPath)) return {}
+  const data = fs.readFileSync(configPath, 'utf8');
+  const parsed = JSON.parse(data);
+  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
+  return parsed
+}
+
+function writeOverrides(overrides) {
+  fs.writeFileSync(configPath, JSON.stringify(overrides, null, 2), 'utf8');
+}
+
+function migrateLegacySmartShooter(overrides) {
+  if (!overrides || overrides.smartShooter6Migrated) return { overrides, changed: false }
+  const merged = { ...defaultConfig, ...overrides }
+  const stillFactoryDefault = merged.controlType === LEGACY_SMARTSHOOTER5.controlType
+    && merged.controlPath === LEGACY_SMARTSHOOTER5.controlPath
+  const next = { ...overrides, smartShooter6Migrated: true }
+  if (!stillFactoryDefault) return { overrides: next, changed: true }
+  next.controlType = defaultConfig.controlType
+  next.controlPath = defaultConfig.controlPath
+  next.SmartShooterPath = overrides.SmartShooterPath || LEGACY_SMARTSHOOTER5.controlPath
+  next.SmartShooter6Path = defaultConfig.SmartShooter6Path
+  return { overrides: next, changed: true }
 }
 
 /**
- * 读取配置文件
+ * 读取配置:utils 里的默认值打底,用户目录里只覆盖改过的项
  */
 function readConfigFile() {
 
   try {
-    if (fs.existsSync(configPath)) {
-      const data = fs.readFileSync(configPath, 'utf8');
-      const parsed = JSON.parse(data);
-      const { config: merged, changed } = migrateLegacySmartShooter({
-        ...defaultConfig,
-        ...parsed,
-      });
-      if (changed) {
-        fs.writeFileSync(configPath, JSON.stringify(merged, null, 2), 'utf8');
-      }
-
-      return merged;
-    } else {
-      console.log('配置文件不存在');
-      // 创建空白JSON文件
-      fs.writeFileSync(configPath, '{}', 'utf8');
-      return {
-        ...defaultConfig,
-      };
+    const overrides = readOverrides()
+    const { overrides: migrated, changed } = migrateLegacySmartShooter(overrides)
+    const stored = stripDefaults(migrated)
+    if (changed || !overridesEqual(overrides, stored)) {
+      writeOverrides(stored)
     }
+
+    return {
+      ...defaultConfig,
+      ...stored,
+    };
   } catch (error) {
     console.error('读取配置文件出错:', error);
     return null;
   }
 }
 
-function loadConfigFile() {
-  if (!fs.existsSync(configPath)) {
-    return { ...defaultConfig }
-  }
-  const data = fs.readFileSync(configPath, 'utf8');
-  return {
-    ...defaultConfig,
-    ...JSON.parse(data),
-  }
-}
-
 /**
- * 一次写入多项配置,避免 controlType 和安装目录被分成几次写入后互相覆盖
+ * 一次写入多项调整。和默认值相同的项不写进用户目录
  */
 function updateConfigFile(partial) {
   try {
-    const config = {
-      ...loadConfigFile(),
-      ...partial,
+    const next = { ...readOverrides() }
+    for (const [key, value] of Object.entries(partial)) {
+      if (Object.prototype.hasOwnProperty.call(defaultConfig, key) && sameValue(value, defaultConfig[key])) {
+        delete next[key]
+      } else {
+        next[key] = value
+      }
     }
-    fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
+    writeOverrides(stripDefaults(next))
     console.log(`成功写入配置项: ${Object.keys(partial).join(', ')}`);
   } catch (error) {
     console.error('写入配置文件出错:', error);

+ 5 - 0
frontend/src/components/check/index.vue

@@ -7,6 +7,7 @@
     :show-close="false"
     width="680px"
     class="hardware-check-dialog"
+    :z-index="zIndex"
   >
     <div class="hardware-check-container">
       <el-timeline>
@@ -117,6 +118,10 @@ const props = defineProps({
   title: {
     type: String,
     default: '检测硬件'
+  },
+  zIndex: {
+    type: Number,
+    default: undefined
   }
 });
 

+ 244 - 0
frontend/src/components/quick-actions/index.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="quick-actions" :class="{ 'is-open': open, 'is-compact': isSmallScreen }">
+    <div v-if="open" class="quick-actions__mask" @click="open = false"></div>
+    <div class="quick-actions__list">
+      <div
+        class="quick-actions__item"
+        @click="handleOneClickRelease"
+        v-log="{ describe: { action: '点击一键释放' } }"
+      >
+        <span>一键释放</span>
+      </div>
+      <div
+        class="quick-actions__item"
+        @click="handleOneKeyDown"
+        v-log="{ describe: { action: '点击一键回落' } }"
+      >
+        <span>一键回落</span>
+      </div>
+      <div
+        class="quick-actions__item is-danger"
+        @click="handlePowerOff"
+        v-log="{ describe: { action: '点击设备关机' } }"
+      >
+        <span>设备关机</span>
+      </div>
+    </div>
+    <div
+      class="quick-actions__toggle"
+      @click="open = !open"
+      v-log="{ describe: { action: '点击常用操作' } }"
+    >
+      <span>{{ open ? '收起' : '常用' }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import socket from '@/stores/modules/socket'
+
+const SMALL_SCREEN_MAX = 1366
+
+const socketStore = socket()
+const open = ref(false)
+const isSmallScreen = ref(window.screen.width <= SMALL_SCREEN_MAX)
+
+function syncScreen() {
+  isSmallScreen.value = window.screen.width <= SMALL_SCREEN_MAX
+}
+
+onMounted(() => {
+  syncScreen()
+  window.addEventListener('resize', syncScreen)
+})
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', syncScreen)
+})
+
+function closeMenu() {
+  open.value = false
+}
+
+async function handleOneClickRelease() {
+  closeMenu()
+  try {
+    await ElMessageBox.confirm(
+      '确定要一键释放设备吗?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      },
+    )
+    socketStore.sendMessage({
+      type: 'oneclick_release',
+      data: 'oneclick_release',
+    })
+    ElMessage.success('已发送一键释放指令')
+  } catch {
+    // 用户取消
+  }
+}
+
+async function handleOneKeyDown() {
+  closeMenu()
+  try {
+    await ElMessageBox.confirm(
+      '确定要一键回落设备吗?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      },
+    )
+    socketStore.sendMessage({
+      type: 'one_key_down',
+      data: 'one_key_down',
+    })
+    ElMessage.success('已发送一键回落指令')
+  } catch {
+    // 用户取消
+  }
+}
+
+async function handlePowerOff() {
+  closeMenu()
+  try {
+    await ElMessageBox.confirm(
+      '确定要关闭拍照机吗?关机后需要手动重新开机。',
+      '设备关机',
+      {
+        confirmButtonText: '确定关机',
+        cancelButtonText: '取消',
+        type: 'warning',
+      },
+    )
+    socketStore.sendMessage({
+      type: 'mcu_power_off',
+      data: 'mcu_power_off',
+    })
+    ElMessage.success('已发送关机指令')
+  } catch {
+    // 用户取消
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.quick-actions {
+  position: fixed;
+  left: 24px;
+  bottom: 24px;
+  z-index: 2000;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  pointer-events: none;
+  -webkit-app-region: no-drag;
+  app-region: no-drag;
+
+  .quick-actions__mask {
+    display: none;
+    position: fixed;
+    inset: 0;
+    z-index: 1999;
+    background: transparent;
+    pointer-events: auto;
+  }
+
+  .quick-actions__list {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+    position: relative;
+    z-index: 2001;
+    opacity: 1;
+    pointer-events: auto;
+    transform: none;
+  }
+
+  .quick-actions__item {
+    min-width: 112px;
+    height: 40px;
+    padding: 0 16px;
+    background: #fff;
+    border-radius: 20px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    user-select: none;
+
+    span {
+      font-size: 14px;
+      color: #333;
+      font-weight: 500;
+    }
+
+    &:hover {
+      background: #f5f7fa;
+    }
+
+    &.is-danger span {
+      color: #ff4c00;
+    }
+  }
+
+  .quick-actions__toggle {
+    display: none;
+    width: 56px;
+    height: 56px;
+    margin-top: 12px;
+    border-radius: 50%;
+    background: linear-gradient(135deg, #2fb0ff 0%, #b863fb 100%);
+    color: #fff;
+    font-size: 14px;
+    font-weight: 600;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    box-shadow: 0 6px 16px rgba(47, 176, 255, 0.4);
+    user-select: none;
+    pointer-events: auto;
+    position: relative;
+    z-index: 2001;
+
+    &:hover {
+      opacity: 0.92;
+    }
+  }
+}
+
+/* 屏幕宽度 1366 及以下视为小屏,保留「常用 / 收起」 */
+.quick-actions.is-compact {
+  .quick-actions__list {
+    opacity: 0;
+    pointer-events: none;
+    transform: translateY(12px);
+    transition: all 0.2s ease;
+  }
+
+  &.is-open {
+    .quick-actions__mask {
+      display: block;
+    }
+
+    .quick-actions__list {
+      opacity: 1;
+      pointer-events: auto;
+      transform: translateY(0);
+    }
+  }
+
+  .quick-actions__toggle {
+    display: flex;
+  }
+}
+</style>

+ 10 - 1
frontend/src/views/Photography/check.vue

@@ -84,8 +84,10 @@
   </div>
   <hardware-check v-if="!isSetting"
    isInitCheck
+   :z-index="2000"
    @confirm="checkConfirm(true)"
   />
+  <QuickActions v-if="!isSetting && mcuConnected" />
 </template>
 <script setup lang="ts">
 import {watchEffect, ref, defineEmits, defineProps, computed, onBeforeUnmount, onMounted} from 'vue'
@@ -95,6 +97,8 @@ import icpList from '@/utils/ipc'
 import useUserInfo from "@/stores/modules/user";
 import headerBar from '@/components/header-bar/index.vue'
 import  editRow  from './components/editRow'
+import QuickActions from '@/components/quick-actions/index.vue'
+import checkInfo from '@/stores/modules/check'
 const clientStore = client();
 const socketStore = socket(); // WebSocket状态管理实例
 
@@ -104,6 +108,8 @@ const emit = defineEmits([ 'confirm','onClose']);
 import  configInfo  from '@/stores/modules/config';
 const configInfoStore = configInfo();
 const useUserInfoStore = useUserInfo();
+const checkInfoStore = checkInfo();
+const mcuConnected = computed(() => checkInfoStore.mcuConnected)
 
 const confirm = async () => {
   console.log('confirm');
@@ -369,7 +375,7 @@ function handleAutoFocus() {
     }
   });
 }
-const loading = ref(true)
+const loading = ref(false)
 const editData = ref(null);
 const imageTplPath = ref(null)
 function backToPreview() {
@@ -508,6 +514,9 @@ clientStore.ipc.on(icpList.socket.message+'_run_mcu_single', async (event, resul
 
 const exampleImage = ref('https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400')
 onMounted(async ()=>{
+  if (!isSetting.value) {
+    checkInfoStore.requestHardwareCheckInfo()
+  }
   await  configInfoStore.getAppConfig()
   if(configInfoStore.appConfig.controlType === "SmartShooter" || configInfoStore.appConfig.controlType === "SmartShooter6"){
     preview.value = configInfoStore.appConfig.userDataPath  + "\\preview\\liveview.png"

+ 2 - 191
frontend/src/views/Photography/shot.vue

@@ -299,40 +299,7 @@
     </el-button>
   </div>
 
-  <!-- 常用操作悬浮按钮 -->
-  <div class="shot-fab" v-if="!isDemoMode" :class="{ open: fabOpen }">
-    <div v-if="fabOpen" class="shot-fab-mask" @click="fabOpen = false"></div>
-    <div class="shot-fab-actions">
-      <div
-        class="shot-fab-action"
-        @click="handleOneClickRelease"
-        v-log="{ describe: { action: '点击一键释放' } }"
-      >
-        <span class="shot-fab-action-text">一键释放</span>
-      </div>
-      <div
-        class="shot-fab-action"
-        @click="handleOneKeyDown"
-        v-log="{ describe: { action: '点击一键回落' } }"
-      >
-        <span class="shot-fab-action-text">一键回落</span>
-      </div>
-      <div
-        class="shot-fab-action danger"
-        @click="handlePowerOff"
-        v-log="{ describe: { action: '点击设备关机' } }"
-      >
-        <span class="shot-fab-action-text">设备关机</span>
-      </div>
-    </div>
-    <div
-      class="shot-fab-btn"
-      @click="fabOpen = !fabOpen"
-      v-log="{ describe: { action: '点击常用操作' } }"
-    >
-      <span>{{ fabOpen ? '收起' : '常用' }}</span>
-    </div>
-  </div>
+  <QuickActions v-if="!isDemoMode" />
 </template>
 <script setup lang="ts">
 import headerBar from '@/components/header-bar/index.vue'
@@ -345,10 +312,8 @@ import { useThumbnails } from './composables/useThumbnails'
 import ImagePreview, { PreviewItem } from '@/components/PreviewImage/index.vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import useUserInfo from '@/stores/modules/user'
-import socket from '@/stores/modules/socket'
 import { formatScaleWeight, useScale } from '@/composables/useScale'
-
-const socketStore = socket()
+import QuickActions from '@/components/quick-actions/index.vue'
 const {
   everConnected: scaleEverConnected,
   connected: scaleConnected,
@@ -435,74 +400,6 @@ const headerMenu = computed(() => {
   return items
 })
 
-const fabOpen = ref(false)
-
-async function handleOneClickRelease() {
-  fabOpen.value = false
-  try {
-    await ElMessageBox.confirm(
-      '确定要一键释放设备吗?',
-      '提示',
-      {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning',
-      },
-    )
-    socketStore.sendMessage({
-      type: 'oneclick_release',
-      data: 'oneclick_release',
-    })
-    ElMessage.success('已发送一键释放指令')
-  } catch {
-    // 用户取消
-  }
-}
-
-async function handleOneKeyDown() {
-  fabOpen.value = false
-  try {
-    await ElMessageBox.confirm(
-      '确定要一键回落设备吗?',
-      '提示',
-      {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning',
-      },
-    )
-    socketStore.sendMessage({
-      type: 'one_key_down',
-      data: 'one_key_down',
-    })
-    ElMessage.success('已发送一键回落指令')
-  } catch {
-    // 用户取消
-  }
-}
-
-async function handlePowerOff() {
-  fabOpen.value = false
-  try {
-    await ElMessageBox.confirm(
-      '确定要关闭拍照机吗?关机后需要手动重新开机。',
-      '设备关机',
-      {
-        confirmButtonText: '确定关机',
-        cancelButtonText: '取消',
-        type: 'warning',
-      },
-    )
-    socketStore.sendMessage({
-      type: 'mcu_power_off',
-      data: 'mcu_power_off',
-    })
-    ElMessage.success('已发送关机指令')
-  } catch {
-    // 用户取消
-  }
-}
-
 // 搜索货号
 const handleSearch = () => {
   getPhotoRecords({ page: 1, goods_art_no: searchGoodsArtNo.value })
@@ -1357,90 +1254,4 @@ watch(goodsList, () => {
   }
 }
 
-.shot-fab {
-  position: fixed;
-  left: 24px;
-  bottom: 24px;
-  z-index: 2000;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-
-  .shot-fab-mask {
-    position: fixed;
-    inset: 0;
-    z-index: 1999;
-    background: transparent;
-  }
-
-  .shot-fab-actions {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 10px;
-    margin-bottom: 12px;
-    opacity: 0;
-    pointer-events: none;
-    transform: translateY(12px);
-    transition: all 0.2s ease;
-    position: relative;
-    z-index: 2001;
-  }
-
-  &.open .shot-fab-actions {
-    opacity: 1;
-    pointer-events: auto;
-    transform: translateY(0);
-  }
-
-  .shot-fab-action {
-    min-width: 112px;
-    height: 40px;
-    padding: 0 16px;
-    background: #fff;
-    border-radius: 20px;
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    user-select: none;
-
-    .shot-fab-action-text {
-      font-size: 14px;
-      color: #333;
-      font-weight: 500;
-    }
-
-    &:hover {
-      background: #f5f7fa;
-    }
-
-    &.danger .shot-fab-action-text {
-      color: #ff4c00;
-    }
-  }
-
-  .shot-fab-btn {
-    width: 56px;
-    height: 56px;
-    border-radius: 50%;
-    background: linear-gradient(135deg, #2fb0ff 0%, #b863fb 100%);
-    color: #fff;
-    font-size: 14px;
-    font-weight: 600;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    box-shadow: 0 6px 16px rgba(47, 176, 255, 0.4);
-    user-select: none;
-    position: relative;
-    z-index: 2001;
-
-    &:hover {
-      opacity: 0.92;
-    }
-  }
-}
 </style>

+ 1 - 0
frontend/src/views/Setting/index.vue

@@ -405,6 +405,7 @@ const defaultCutoutModeList = ref([
   { label: '精细化抠图', value: '精细化抠图' },
   { label: '旗舰AI抠图', value: '旗舰AI抠图' },
   { label: '旗舰AI抠图-精细', value: '旗舰AI抠图-精细' },
+  { label: '旗舰AI抠图-透明物品', value: '旗舰AI抠图-透明物品' }
 ]);
 const deviceSpeedList = ref([
   { label: '快', value: '一档' },