Selaa lähdekoodia

Merge branch 'master' of http://gitlab.pubdata.cn/liangyibo/CameraMachine

rambo 5 kuukautta sitten
vanhempi
commit
1605e3789d

+ 72 - 5
electron/controller/utils.js

@@ -12,6 +12,7 @@ const errData = {
   msg :'请求失败,请联系管理员',
   code:999
 }
+const sharp = require('sharp'); // 确保安装:npm install sharp
 
 
 /**
@@ -42,12 +43,19 @@ class UtilsController extends Controller {
 
   async openMain (config) {
 
+    const { id, url } = config;
 
-    if( this.app.electron[config.id]){
-      this.app.electron[config.id].focus();
-      this.app.electron[config.id].show();
+    if (this.app.electron[id]) {
+      const win = this.app.electron[id];
+
+      // 切换到指定的 URL
+      if(id === 'generate') await win.loadURL(url);
+
+      win.focus();
+      win.show();
       return;
     }
+
     const win = new BrowserWindow({
       ...config,
 
@@ -59,9 +67,9 @@ class UtilsController extends Controller {
 
       },
     });
-    win.loadURL(config.url); // 设置窗口的 URL
+    await win.loadURL(config.url); // 设置窗口的 URL
     // 监听窗口关闭事件
-    if(this.app?.env === 'development')  win.webContents.openDevTools(config.openDevTools)
+   // if(this.app?.env === 'development')  win.webContents.openDevTools(config.openDevTools)
    //
     win.on('close', () => {
       delete this.app.electron[config.id]; // 删除窗口引用
@@ -148,6 +156,65 @@ class UtilsController extends Controller {
 
   }
 
+  async readFileImageForPath(filePath,maxWidth=1500){
+
+    const getMimeType = (fileName)=>{
+      const extension = path.extname(fileName).toLowerCase().replace('.', '');
+      let mimeType = '';
+      switch (extension) {
+        case 'jpg':
+        case 'jpeg':
+          mimeType = 'image/jpeg';
+          break;
+        case 'png':
+          mimeType = 'image/png';
+          break;
+        case 'gif':
+          mimeType = 'image/gif';
+          break;
+        case 'webp':
+          mimeType = 'image/webp';
+          break;
+        case 'avif':
+          mimeType = 'image/avif';
+          break;
+        default:
+          mimeType = 'application/octet-stream';
+          break;
+      }
+      return mimeType;
+    }
+
+    try {
+      const fileName = path.basename(filePath);
+      const image = sharp(filePath);
+      const metadata = await image.metadata();
+
+      let mimeType = getMimeType(fileName); // 调用下面定义的私有方法获取 MIME 类型
+
+      let fileBuffer;
+
+      if (metadata.width > maxWidth) {
+        // 如果宽度大于 1500px,压缩至 1500px 宽度,保持比例
+        fileBuffer = await image.resize(maxWidth).toBuffer();
+      } else {
+        // 否则直接读取原图
+        fileBuffer = fs.readFileSync(filePath);
+      }
+
+      return {
+        fileBuffer,
+        fileName,
+        mimeType
+      };
+    } catch (error) {
+      console.error('Error processing image:', error);
+      throw error;
+    }
+
+
+  }
+
 
 }
 

+ 229 - 69
frontend/src/components/header-bar/index.vue

@@ -1,21 +1,78 @@
 <template>
   <div class="header-bar">
     <div class="header-bar__menu">
-      <div v-for="(item, index) in menu" :key="index" class="header-bar__menu-item" @click="getItemClick(item)">
-        <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
-        <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
+      <div v-for="(item, index) in menu" :key="index" class="header-bar__menu-item">
+        <div
+          v-if="!item.children || item.children.length === 0"
+          @click="getItemClick(item)"
+          class="header-bar__menu-item-content flex"
+        >
+          <img v-if="getItemIcon(item)" :src="getItemIcon(item)" class="header-bar__menu-icon" />
+          <span class="header-bar__menu-name">{{ getItemName(item) }}</span>
+        </div>
+        <div v-else class="header-bar__submenu">
+          <div
+            class="header-bar__submenu-header"
+            @click.stop="toggleSubmenu(index)"
+          >
+            <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] }"
+          >
+            <div
+              v-for="(child, childIndex) in item.children"
+              :key="childIndex"
+              class="header-bar__submenu-item"
+              @click="getItemClick(child)"
+            >
+              <div
+                v-if="!child.children || child.children.length === 0"
+                class="header-bar__submenu-item-content flex left"
+              >
+                <img v-if="getItemIcon(child)" :src="getItemIcon(child)" class="header-bar__menu-icon" />
+                <span class="header-bar__menu-name">{{ getItemName(child) }}</span>
+              </div>
+              <div v-else class="header-bar__submenu-third-level">
+                <div
+                  class="header-bar__submenu-third-header"
+                  @click.stop="toggleThirdLevel(index, childIndex)"
+                >
+                  <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}`] }"
+                >
+                  <div
+                    v-for="(grandChild, grandChildIndex) in child.children"
+                    :key="grandChildIndex"
+                    class="header-bar__submenu-third-item"
+                    @click="getItemClick(grandChild)"
+                  >
+                    <img v-if="getItemIcon(grandChild)" :src="getItemIcon(grandChild)" class="header-bar__menu-icon" />
+                    <span class="header-bar__menu-name">{{ getItemName(grandChild) }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
     <div class="header-bar__title">
       <span class="header-bar__text">{{ title }}</span>
     </div>
-    <div class="header-bar__buttons" >
-      <div class="header-bar__button header-bar__button__user" v-if="showUser">
-
-
+    <div class="header-bar__buttons" v-if="showUser">
+      <div class="header-bar__button header-bar__button__user">
         <el-dropdown>
           <span class="el-dropdown-link">
-           {{useUserInfoStore.userInfo.account_name || useUserInfoStore.userInfo.real_name || useUserInfoStore.userInfo.login_name}}
+            {{ useUserInfoStore.userInfo.account_name || useUserInfoStore.userInfo.real_name || useUserInfoStore.userInfo.login_name }}
           </span>
           <template #dropdown>
             <el-dropdown-menu>
@@ -30,26 +87,26 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, reactive } from 'vue';
-import useUserInfo from "@/stores/modules/user";
-import { useRouter} from "vue-router";
+import { defineProps, reactive, onMounted, onUnmounted } from 'vue'
+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 iconMinimize from './assets/suoxiao@2x.png' // 新增
-import iconClose from './assets/guanbi@2x.png' // 新增*/
 import icpList from '@/utils/ipc'
 import { getRouterUrl } from '@/utils/appfun'
-import client from "@/stores/modules/client";
-const clientStore = client();
-const useUserInfoStore = useUserInfo();
+import client from '@/stores/modules/client'
+
+const clientStore = client()
+const useUserInfoStore = useUserInfo()
 
 // 定义 menu 项的类型
 interface MenuItem {
-  name?: string;    //名称
-  icon?: string;    // 图标
-  click?: () => void;   // 点击事件
-  type?: keyof typeof menuType; // 类型 menuType里面的值
+  name?: string //名称
+  icon?: string // 图标
+  click?: () => void // 点击事件
+  type?: keyof typeof menuType // 类型 menuType里面的值
+  children?: MenuItem[] // 支持嵌套菜单
 }
 
 // 定义 props
@@ -62,12 +119,26 @@ const props = defineProps({
     type: Array as () => MenuItem[],
     default: () => []
   },
-  showUser:{
+  showUser: {
     type: Boolean,
     default: false
   }
-});
+})
+
 const Router = useRouter()
+
+const submenuOpen = reactive<{ [key: number]: boolean }>({})
+const thirdLevelOpen = reactive<{ [key: string]: boolean }>({})
+
+const toggleSubmenu = (index: number) => {
+  submenuOpen[index] = !submenuOpen[index]
+}
+
+const toggleThirdLevel = (parentIndex: number, childIndex: number) => {
+  const key = `${parentIndex}-${childIndex}`
+  thirdLevelOpen[key] = !thirdLevelOpen[key]
+}
+
 const menuType = reactive({
   setting: {
     name: '设置',
@@ -86,127 +157,139 @@ const menuType = reactive({
   },
   ota: {
     name: '版本更新',
-    icon: gengxin,
     click: openOTA
   }
-});
+})
 
 function getItemClick(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
   if (menuItem && menuItem.click) {
-    menuItem.click();
+    menuItem.click()
   }
-}
 
+  // 关闭所有展开的一级和三级菜单
+  for (const key in submenuOpen) {
+    submenuOpen[key] = false
+  }
+  for (const key in thirdLevelOpen) {
+    thirdLevelOpen[key] = false
+  }
+}
 function getItemName(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
-  return menuItem.name;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
+  return menuItem.name
 }
 
 function getItemIcon(item: MenuItem) {
-  const menuItem = item.type ? { ...menuType[item.type], ...item } : item;
-  return menuItem.icon;
+  const menuItem = item.type ? { ...menuType[item.type], ...item } : item
+  return menuItem.icon
 }
 
 function openSetting() {
-
   const { href } = Router.resolve({
     name: 'setting',
-    query:{
-      type:0,
+    query: {
+      type: 0
     }
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '设置',
     width: 1920,
     height: 1080,
     frame: true,
-    id: "seeting",
+    id: 'seeting',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openRemoteControl(){
-
+function openRemoteControl() {
   const { href } = Router.resolve({
-    name: 'RemoteControl',
+    name: 'RemoteControl'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '模拟遥控器',
     width: 350,
     height: 600,
     frame: true,
-    id: "RemoteControl",
+    id: 'RemoteControl',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openDeveloper(){
-
+function openDeveloper() {
   const { href } = Router.resolve({
-    name: 'developer',
+    name: 'developer'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '初始设备调频设置',
     width: 900,
     height: 700,
     frame: true,
-    id: "developer",
+    id: 'developer',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-
-function openOTA(){
-
+function openOTA() {
   const { href } = Router.resolve({
-    name: 'ota',
+    name: 'ota'
   })
 
-  clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+  clientStore.ipc.removeAllListeners(icpList.utils.openMain)
   let params = {
     title: '版本更新',
     width: 900,
     height: 700,
     frame: true,
-    id: "ota",
+    id: 'ota',
     url: getRouterUrl(href)
   }
-  clientStore.ipc.send(icpList.utils.openMain, params);
+  clientStore.ipc.send(icpList.utils.openMain, params)
 }
 
-function loginOut(){
-  useUserInfoStore.loginOut();
+function loginOut() {
+  useUserInfoStore.loginOut()
   useUserInfoStore.updateLoginShow(true)
 }
 
-// 新增
-function minimizeWindow() {
-  clientStore.ipc.send(icpList.utils.minimizeWindow);
-}
+onMounted(() => {
+  document.addEventListener('click', handleOutsideClick)
+})
+
+onUnmounted(() => {
+  document.removeEventListener('click', handleOutsideClick)
+})
 
-// 新增
-function closeWindow() {
-  clientStore.ipc.send(icpList.utils.closeWindow);
+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
+    }
+  }
 }
 </script>
 
-<style  lang="scss" scoped>
+<style lang="scss" scoped>
 .header-bar_blank {
   width: 100%;
-  height:30px;
+  height: 30px;
 }
+
 .header-bar {
   position: fixed;
   app-drag: drag;
@@ -295,6 +378,7 @@ function closeWindow() {
 .header-bar__button__user {
   padding: 0 10px;
 }
+
 .header-bar__button:hover {
   background-color: #e0e0e0;
 }
@@ -303,4 +387,80 @@ function closeWindow() {
   width: 16px;
   height: 16px;
 }
+
+.header-bar__submenu {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.header-bar__submenu-header {
+  display: flex;
+  align-items: center;
+  padding: 2px 10px;
+  cursor: pointer;
+}
+
+.header-bar__submenu-body {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  background: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  min-width: 160px;
+  border-radius: 4px;
+  overflow: hidden;
+  display: none;
+  margin-top: 5px;
+
+  &.submenu-open {
+    display: block;
+  }
+}
+
+.header-bar__submenu-item {
+  display: flex;
+  align-items: center;
+  padding: 6px 12px;
+  white-space: nowrap;
+
+  &:hover {
+    background-color: #f0f0f0;
+  }
+}
+
+.header-bar__submenu-third-level {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.header-bar__submenu-third-header {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+
+  &:hover {
+    background-color: #f0f0f0;
+  }
+}
+
+.header-bar__submenu-third-body {
+  position: fixed;
+  margin-left: 150px;
+  top: 0;
+  left: 100%;
+  background: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  min-width: 160px;
+  border-radius: 4px;
+  overflow: hidden;
+  display: none;
+
+  &.submenu-open {
+    display: block;
+  }
+}
 </style>

+ 2 - 1
frontend/src/components/login/index.vue

@@ -199,7 +199,8 @@ const handleLogin = async () => {
     "username":loginForm.username,
     "password":activeTab.value === '0' ? loginForm.password : loginForm.code,
     "type": activeTab.value,
-    "device":"aigc-photo"
+    "device":"aigc-camera",
+    "platform":"aigc-camera"
   })
   switch(res.data.is_need_select_company){
     case 40006:

+ 4 - 0
frontend/src/config.json

@@ -0,0 +1,4 @@
+{
+    "api": "https://dev2.valimart.net",
+    "tkkWebUrl": "https://tkk.valimart.net"
+}

+ 3 - 1
frontend/src/stores/modules/user.ts

@@ -87,7 +87,9 @@ export const useUserInfo = defineStore('userInfo', () => {
    */
   const getInfo = async () => {
     try {
-      const res = await getUserInfo(); // 调用获取用户信息接口
+      const res = await getUserInfo({
+        device:'aigc',
+      }); // 调用获取用户信息接口
       const { data } = res;
       if (!data) {
         updateToken(''); // 如果没有数据,清空令牌

+ 4 - 0
frontend/src/utils/appconfig.ts

@@ -1,3 +1,7 @@
+
+import config from "@/config.json";
+
+export const configs = config;
 export const imagesUpload = {
   accept: ['jpg', 'png', 'gif'],
   fileSize: 2048 * 10,

+ 23 - 0
frontend/src/utils/appfun.ts

@@ -1,4 +1,8 @@
 
+import { configs } from '@/utils/appconfig';
+import tokenInfo from '@/stores/modules/token';
+const tokenInfoStore = tokenInfo();
+
 //获取文件路径
 export  function getFilePath (file_path){
     if(file_path) return file_path;
@@ -10,3 +14,22 @@ export  function getFilePath (file_path){
 export  function getRouterUrl (href){
     return window.location.origin+window.location.pathname+href
 }
+
+
+
+//获取TKK地址
+export  function getWebUrlrUrl (config:{
+    url:string,
+    query:Object
+}){
+
+    let params = '?source=camera&token=' + tokenInfoStore.getToken
+    if(config.query){
+        params +=  '&'
+        params += Object.keys(config.query).map(key => {
+            return encodeURIComponent(key) + '=' + encodeURIComponent(config.query[key])
+        }).join('&')
+    }
+    let url  = configs.tkkWebUrl + config.url + params
+    return url
+}

+ 84 - 0
frontend/src/utils/menus/generate.ts

@@ -0,0 +1,84 @@
+
+import icpList from '@/utils/ipc'
+import { getRouterUrl } from '@/utils/appfun'
+import client from '@/stores/modules/client'
+import {useRouter} from "vue-router";
+import { getWebUrlrUrl } from '@/utils/appfun'
+const Router = useRouter()
+
+const clientStore = client()
+const generate =  {
+        name:'高级生成',
+        children:[
+            {
+                name:'模特图',
+                click(configs){
+                    console.log(configs);
+                    openGaenrate('onFeetImage',configs)
+                },
+            },
+            {
+                name:'场景图',
+                click(configs){
+                    openGaenrate('attachScenarios',configs)
+                },
+            },
+            {
+                name:'生成视频',
+                click(configs){
+                    openGaenrate('video',configs)
+                },
+            },{
+                name:'历史记录',
+                click(configs){
+                    openGaenrate('mine',configs)
+                },
+            }
+        ]
+    }
+
+
+
+
+
+export function openGaenrate(type,configs) {
+
+    const config= {
+        "onFeetImage":{
+            url:"/onFeetImage",
+        },
+        "attachScenarios":{
+            url:"/attachScenarios"
+        },
+        "video":{
+            url:"/create_video"
+        },
+        "mine":{
+            url:"/mine"
+        }
+    }
+
+    let urlParams = config[type]
+    if(configs){
+        urlParams = {
+            ... config[type],
+            ...configs
+        }
+    }
+
+
+    clientStore.ipc.removeAllListeners(icpList.utils.openMain)
+    let params = {
+        title: '高级生成',
+        width: 1400,
+        height: 900,
+        frame: true,
+        id: 'generate',
+        url: getWebUrlrUrl(urlParams)
+    }
+    clientStore.ipc.send(icpList.utils.openMain, params)
+}
+
+
+
+export default generate;

+ 1 - 1
frontend/src/views/OTA/index.vue

@@ -30,7 +30,7 @@ const fetchVersions = async () => {
   try {
     // 添加时间戳避免缓存问题
     const timestamp = new Date().getTime();
-    const response = await axios.get('https://huilimaimg.cnhqt.com/frontend/html/zhihuiyin/version.json', {
+    const response = await axios.get('https://ossimg.valimart.net/frontend/html/zhihuiyin/version.json', {
       params: {
         _t: timestamp
       }

+ 9 - 0
frontend/src/views/Photography/mixin/generate.vue

@@ -0,0 +1,9 @@
+<script>
+export default {
+name: "generate"
+}
+</script>
+
+<style scoped>
+
+</style>

+ 49 - 5
frontend/src/views/Photography/shot.vue

@@ -93,6 +93,21 @@
                 <div class="flex  between flex-item  c-333">
                   <div class="chaochu flex-item flex left">货号:{{ item.goods_art_no }}</div>
                   <div >
+<!--
+                    <el-button :disabled="runLoading || takePictureLoading" size="small"  plain >高级生成</el-button>
+-->
+
+                    <el-dropdown @command="handleGenerateCommand" :disabled="runLoading || takePictureLoading" >
+                      <el-button :disabled="runLoading || takePictureLoading" size="small"  plain style="margin-right: 5px" >高级生成</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>
+                        </el-dropdown-menu>
+                      </template>
+                    </el-dropdown>
+
                     <el-button size="small" :disabled="runLoading || takePictureLoading"  type="primary"  @click="reTakePictureNos(item.goods_art_no,item)" plain v-if="configInfoStore.appModel === 1">重拍</el-button>
                     <el-button size="small" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})">删除</el-button>
                   </div>
@@ -167,7 +182,7 @@
             <span class="empty-text">暂无数据</span>
           </div>-->
           <div v-if="goodsList.length" class="next-step button--primary1 flex-col" @click="openPhotographyDetail"><span
-              class="next-step-text">{{ configInfoStore.appModel === 1 ? '拍摄完毕,进入下一步处理' : '下一步'}}</span></div>
+              class="next-step-text">{{ configInfoStore.appModel === 1 ? '开始生成' : '开始生成'}}</span></div>
         </div>
 
     </div>
@@ -185,8 +200,7 @@ import {useRouter} from "vue-router";
 import HardwareCheck from '@/components/check/index.vue'
 import checkInfo from "@/stores/modules/check";
 import RemoteControl from '@/views/RemoteControl/index'
-
-
+import  generate from '@/utils/menus/generate'
 const loading = ref(false)
 const runLoading = ref(false)
 const takePictureLoading = ref(false)
@@ -206,8 +220,7 @@ const menu = computed(()=>{
         type:'setting'
       },
       {
-        name:'切换到拍摄模式',
-        icon:qiehuan,
+        name:'切换模式',
         click(){
           configInfoStore.updateAppModel(1)
           Router.push({
@@ -218,6 +231,9 @@ const menu = computed(()=>{
       {
         type:'ota'
       },
+      {
+        ...generate
+      }
     ]
   }
   if(useUserInfoStore.userInfo.brand_company_code === '1300'){
@@ -230,6 +246,9 @@ const menu = computed(()=>{
       },
       {
         type:'ota'
+      },
+      {
+          ...generate,
       }
     ]
   }
@@ -797,6 +816,31 @@ function openPhotographyDetail() {
   clientStore.ipc.send(icpList.utils.openMain, params);
 }
 
+
+
+/*高级生成*/
+const onGenerateCLick = (menu,item)=>{
+  if(menu.name === '历史记录'){
+
+    menu.click()
+    return
+  }
+  const firstWithImagePath = item.items.find(
+      (image) => image.PhotoRecord.image_path
+  );
+
+  if (firstWithImagePath) {
+    menu.click({
+      query:{
+        image_path:firstWithImagePath.PhotoRecord.image_path
+      }
+    })
+  } else {
+    menu.click()
+  }
+
+}
+
 </script>
 
 

+ 17 - 1
frontend/src/views/Setting/components/action_config.vue

@@ -61,7 +61,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, defineProps, defineEmits , watch,onMounted, reactive } from 'vue'
+import { ref, defineProps, defineEmits , watch,onMounted, reactive,onBeforeUnmount } from 'vue'
 import EditDialog from "./EditDialog.vue";
 import { ElMessage, ElMessageBox } from 'element-plus';import client from "@/stores/modules/client";
 import icpList from '@/utils/ipc';
@@ -81,11 +81,27 @@ const tabs = ref([]); // 所有标签页
 const editId = ref(0); // 当前编辑行的索引
 const selectID = ref(0) //当前默认的ID
 
+
+onBeforeUnmount(()=>{
+    window.removeEventListener('beforeunload', handleBeforeUnload);
+})
+
 onMounted(()=>{
+  window.addEventListener('beforeunload', handleBeforeUnload);
   topsTab.value = 'left';
   getTopList()
 })
 
+const handleBeforeUnload = (e)=>{
+  if(dialogVisible.value){
+    e.preventDefault();
+    const message = '您已打开实时预览弹出框,请先取消或者保存后,关闭编辑弹出框,后再关闭此窗口';
+    e.returnValue = message; // 标准方式
+    ElMessage.error('您已打开实时预览弹出框,请先取消或者保存后,关闭编辑弹出框,后再关闭此窗口,')
+    return message; // 兼容某些浏览器
+  }
+
+}
 /**
  * 监听topsTab变化,获取对应标签页的设备配置列表。
  */

+ 73 - 0
frontend/src/views/Setting/components/otherConfig.vue

@@ -0,0 +1,73 @@
+<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()">充值金币</el-button>
+  </div>
+
+  <!-- 新增:充值金币弹窗 -->
+  <el-dialog v-model="showRechargeDialog"
+             title="充值金币"
+             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>
+    <iframe v-show="!loading" :src="rechargeUrl" width="660" height="610" frameborder="0" @load="onIframeLoad"></iframe>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { configs } from '@/utils/appconfig';
+import { ref, onMounted, onUnmounted } from "vue";
+import useUserInfo from "@/stores/modules/user";
+const useUserInfoStore = useUserInfo();
+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 状态
+
+// 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();
+    loading.value = false;
+  }
+};
+
+const onClose = async () => {
+  await useUserInfoStore.getInfo();
+  loading.value = false;
+};
+
+const openDialog = () => {
+  showRechargeDialog.value = true;
+  loading.value = false;
+};
+// 绑定监听
+onMounted(() => {
+  window.addEventListener('message', handleWindowMessage);
+});
+
+// 移除监听
+onUnmounted(() => {
+  window.removeEventListener('message', handleWindowMessage);
+});
+</script>

+ 160 - 62
frontend/src/views/Setting/index.vue

@@ -4,17 +4,17 @@
   />
   <div class="container">
     <nav class="settings-nav">
-      <div class="nav-item" :class="{'active': activeIndex === 0}" @click="activeIndex = 0">
+      <div class="nav-item" :class="{'active': activeIndex === 0}" @click="toggleTab(0)">
         <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>
       </div>
-      <div class="nav-item" :class="{'active': activeIndex === 2}" @click="activeIndex = 2">
+      <div class="nav-item" :class="{'active': activeIndex === 2}" @click="toggleTab(2)">
         <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>
       </div>
-      <div class="nav-item" v-if="configInfoStore.appModel === 1" :class="{'active': activeIndex === 4}" @click="activeIndex = 4">
+      <div class="nav-item" v-if="configInfoStore.appModel === 1" :class="{'active': activeIndex === 4}"  @click="toggleTab(4)">
         <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>
@@ -25,15 +25,30 @@
       <!--基础配置-->
           <div class="selectBox" v-if="activeIndex === 0">
                 <div class="form-item">
-                    <label>主图尺寸:</label>
-                    <div class="select-wrapper">
+                  <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>
+                 <!-- 新增自定义输入框 -->
+                <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-2000的尺寸值"
+                        class="w-full px-3 py-2 border rounded-md"
+                        @keypress="handleKeyPress"
+                        @input="handleInput"
+                    />
+                  </div>
                 </div>
                 <div class="form-item">
                     <label>图片输出格式:</label>
@@ -54,40 +69,46 @@
         </div>
       <!--基础配置-->
       <!--其他设置-->
-          <div class="selectBox" style="padding-top: 0px" v-if="activeIndex === 2">
-                <div class="form-item">
-                    <label>产品类型:</label>
-                    <div class="select-wrapper">
-                    <el-select v-model="formData.other_configs.product_type" placeholder="请选择">
-                      <el-option v-for="item in productTypeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
-                    </el-select>
-                    </div>
+          <template v-if="activeIndex === 2">
+
+            <div class="selectBox" style="padding-top: 0px" >
+              <div class="form-item">
+                <label>产品类型:</label>
+                <div class="select-wrapper">
+                  <el-select v-model="formData.other_configs.product_type" placeholder="请选择">
+                    <el-option v-for="item in productTypeList" :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-select v-model="formData.other_configs.cutout_mode" placeholder="请选择">
-                      <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>
+                <div class="select-wrapper">
+                  <el-select v-model="formData.other_configs.cutout_mode" placeholder="请选择">
+                    <el-option v-for="item in defaultCutoutModeList" :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-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>
-                    </div>
+              </div>
+              <div class="form-item">
+                <label>设备运动速度:</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>
                 </div>
-<!--                <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>
+              </div>
+
+              <!--                <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>
+
+            <other-config/>
+          </template>
       <!--其他设置-->
           <div class="selectBox" style="padding-top: 0px;padding-left: 0;" v-if="activeIndex === 4">
             <actionConfig/>
@@ -95,7 +116,7 @@
           </div>
     </div>
       <div class="text-center mt-8">
-        <button class="bg-gradient-to-r from-primary" @click="saveSetting(activeIndex)" v-if="activeIndex !== 4">
+        <button class="bg-gradient-to-r from-primary" @click="onSava(activeIndex)" v-if="activeIndex !== 4">
           保存
         </button>
       </div>
@@ -123,6 +144,7 @@ import { digiCamControlWEB } from  '@/utils/appconfig'
 import { useCheckInfo } from '@/composables/userCheck';
 import { preview } from '@planckdev/element-plus/utils'
 import actionConfig from './components/action_config.vue'
+import otherConfig from './components/otherConfig'
 useCheckInfo();
 
 // 路由和状态管理初始化
@@ -191,11 +213,17 @@ const mainImageSizeList = ref([
   { label: '1200*1200', value: 1200 },
   { label: '1400*1400', value: 1400 },
   { label: '1600*1600', value: 1600 },
+  { label: '自定义', value: 'custom' } // 新增自定义选项
 ]);
+
+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 imageSharpeningList = ref([
   { label: '0', value: '0' },
@@ -227,9 +255,9 @@ const defaultCutoutModeList = ref([
   { label: '精细化抠图', value: '精细化抠图' },
 ]);
 const deviceSpeedList = ref([
-  { label: '一档', value: '1' },
-  { label: '二档', value: '2' },
-  { label: '三档', value: '3' },
+  { label: '一档', value: '一档' },
+  { label: '二档', value: '二档' },
+  { label: '三档', value: '三档' },
 ]);
 /*
 const runModeList = ref([
@@ -275,7 +303,7 @@ const indexKey  ={
 watch(() => route.query.type, async (newType,oldType) => {
 
   if(['0','1','2'].includes(oldType)){
-    await  saveSetting(oldType)
+ //   await  saveSetting(oldType)
   }
   const typeValue = parseInt(newType) || 0;
   if(typeValue === 4) return;
@@ -287,7 +315,22 @@ watch(() => route.query.type, async (newType,oldType) => {
         });
         clientStore.ipc.on(icpList.setting.getSysConfig, (event, result) => {
           if(result.code == 0 && result.data){
+
             formData[indexKey[typeValue]] = result.data
+            const presetSizes = mainImageSizeList.value.map(item => item.value);
+            const receivedSizes = result.data.main_image_size ? [...result.data.main_image_size] : [];
+
+            // 分离自定义值
+            const customValues = receivedSizes.filter(v => !presetSizes.includes(v));
+            if (customValues.length > 0) {
+              customInput.value = customValues[0]; // 保留第一个自定义值
+              // 更新选中状态
+              formData.basic_configs.main_image_size = receivedSizes
+                .filter(v => presetSizes.includes(v))
+                .concat('custom');
+            } else {
+              formData.basic_configs.main_image_size = receivedSizes;
+            }
           }
           console.log('icpList.setting.getSysConfig')
           console.log(result)
@@ -324,37 +367,92 @@ onMounted(() => {
 
 });
 
+
+const handleKeyPress = (event) => {
+  const char = event.key;
+  // 只允许输入数字字符
+  if (!/^\d+$/.test(char)) {
+    event.preventDefault(); // 阻止非数字输入
+  }
+};
+const handleInput = (value) => {
+  if (value > 2000) {
+    customInput.value = 2000;
+  } else if (value < 1) {
+    customInput.value = 1;
+  }
+};
+
+const toggleTab = async (item) => {
+  let oldType = activeIndex.value;
+
+  if([0,1,2].includes(oldType)){
+   const next =  await  saveSetting(oldType)
+    if(next === false) return ;
+  }
+   activeIndex.value = item;
+};
+
+const onSava = async (index)=>{
+  const next =  await  saveSetting(index)
+
+  if(next !== false){
+    ElMessage.success('保存成功')
+  }
+
+}
+
 /**
  * 保存当前表单配置。
  */
 const saveSetting = async (index) => {
+  // 构建临时提交数据
+  const submitData = {
+    ...formData[indexKey[index]]
+  };
+  if(index === 0) {
+    if (formData.basic_configs.main_image_size.length === 0) {
+      ElMessage.error('请选择主图尺寸!');
+      return false;
+    }
 
-  if(index === 0){
-    if(formData.basic_configs.main_image_size.length === 0){
+    // 处理自定义尺寸逻辑
+    const selectedSizes = [...formData.basic_configs.main_image_size]; // 创建副本避免修改原始数据
 
-      ElMessage.error('请选择主图尺寸!');
-      return;
+    if (selectedSizes.includes('custom')) {
+      if (!customInput.value || isNaN(customInput.value) ||
+          customInput.value < 1 || customInput.value > 2000) {
+        ElMessage.error('请输入1-2000之间的有效数值');
+        return false;
+      }
+
+      // 创建新数组用于提交
+      const submitSizes = selectedSizes
+          .filter(v => v !== 'custom')
+          .concat(parseInt(customInput.value));
+
+      submitData.main_image_size = submitSizes
     }
+
   }
-  await new Promise((resolve, reject) => {
-
-    clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
-    clientStore.ipc.send(icpList.setting.updateSysConfigs,{
-      key: indexKey[index],
-      value:JSON.stringify({
-        ...formData[indexKey[index]]
-      })
-    });
-    clientStore.ipc.on(icpList.setting.updateSysConfigs, async (event, result) => {
-      clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
-      if(result.code === 0 && result.msg){
-        resolve(result)
-      }
+      console.log(submitData);
+      await new Promise((resolve, reject) => {
+        clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
+        clientStore.ipc.send(icpList.setting.updateSysConfigs,{
+          key: indexKey[index],
+          value: JSON.stringify(submitData)
+        });
 
-    });
-  });
 
 
+        clientStore.ipc.on(icpList.setting.updateSysConfigs, async (event, result) => {
+          clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
+          if(result.code === 0 && result.msg){
+            resolve(true)
+          }
+
+        });
+      });
 };
 
 

+ 4 - 1
frontend/vite.config.ts

@@ -1,8 +1,11 @@
 import { defineConfig,loadEnv } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import replace from '@rollup/plugin-replace'
+import config from "./src/config.json";
 import path from 'path'
 
+
+
 // https://vite.dev/config/
 export default ({mode, command})=> {
   const env = loadEnv(mode, process.cwd(), 'API_') as Record<
@@ -61,7 +64,7 @@ export default ({mode, command})=> {
         // 当请求以'/api'开头时,将其代理到目标服务器
         '/api': {
           // 目标服务器的地址
-          target: 'https://dev2.valimart.net',
+          target: config.api,
           // 允许更改目标服务器的来源
           changeOrigin: true
         },

+ 1 - 0
package.json

@@ -63,6 +63,7 @@
     "electron-updater": "5.3.0",
     "lodash": "4.17.21",
     "node-window-manager": "2.2.4",
+    "sharp": "^0.34.2",
     "ws": "8.18.1"
   }
 }