ソースを参照

智慧映拍照机
增加产品图册制作功能

rambo 1 週間 前
コミット
da7b3dc4e4

+ 161 - 179
app.go

@@ -1,18 +1,24 @@
 package main
 
 import (
+	"Vali-Tools/handlers" // 根据实际项目路径调整
+	"Vali-Tools/utils"
 	"context"
 	"fmt"
-	"github.com/wailsapp/wails/v2/pkg/runtime"
-	"io"
 	"os"
-	"path/filepath"
 	"strings"
 )
 
 // App struct
 type App struct {
-	ctx context.Context
+	ctx              context.Context
+	Token            string
+	Env              string
+	directoryHandler *handlers.DirectoryHandler
+	dialogHandler    *handlers.DialogHandler
+	UrlHost          string // 根据实际项目路径调整
+	HomePage         string //首次要打开得页面路径
+	handlerRequest   *handlers.HandlerRequests
 }
 
 // NewApp creates a new App application struct
@@ -20,215 +26,191 @@ func NewApp() *App {
 	return &App{}
 }
 
-// startup is called when the app starts. The context is saved
-// so we can call the runtime methods
+// startup is called when the app starts
 func (a *App) startup(ctx context.Context) {
 	a.ctx = ctx
+	a.directoryHandler = handlers.NewDirectoryHandler(ctx)
+	a.dialogHandler = handlers.NewDialogHandler(ctx)
+	fmt.Printf("All startup args: %v\n", os.Args)
+	args := os.Args
+	if len(args) > 1 {
+		Args := args[1]
+		paramsMap := a.parsArguments(Args)
+		a.Token = paramsMap["token"]
+		env := paramsMap["env"]
+		page := paramsMap["page"]
+		urlHost := "https://dev2.pubdata.cn"
+		if env != "dev" && env != "" {
+			urlHost = "https://dev2.valimart.net"
+		}
+		a.UrlHost = urlHost
+		a.HomePage = page
+		println("Token:", a.Token)
+		println("UrlHost", a.UrlHost)
+		a.handlerRequest = handlers.NewHandlerRequests(ctx, a.Token, a.UrlHost)
+		fmt.Printf("获取到了Token信息: %s\n", a.Token)
+	}
 }
 
-// Greet returns a greeting for the given name
-func (a *App) Greet(name string) string {
-	return fmt.Sprintf("Hello %s, It's show time!", name)
-}
+// 解析参数
+func (a *App) parsArguments(params string) map[string]string {
+	result := make(map[string]string)
+	pairs := strings.Split(params, "&")
 
-// SelectDirectory 目录选择方法
-func (a *App) SelectDirectory() (string, error) {
-	//目录选择方法
-	options := runtime.OpenDialogOptions{
-		Title:            "选择目录",
-		Filters:          []runtime.FileFilter{},
-		DefaultDirectory: "",
-		DefaultFilename:  "", // 允许选择目录
-	}
-
-	result, err := runtime.OpenDirectoryDialog(a.ctx, options)
-	if err != nil {
-		return "", err
+	for _, pair := range pairs {
+		kv := strings.Split(pair, "=")
+		if len(kv) == 2 {
+			result[kv[0]] = kv[1]
+		}
 	}
 
-	if result == "" {
-		return "", nil // 用户取消选择
-	}
+	return result
+}
 
-	return result, nil
+type UserInfo struct {
+	AccountName string `json:"account_name"`
+	CompanyName string `json:"brand_company_name"`
+	RealName    string `json:"real_name"`
 }
 
-// HandlerDirectory 处理目录复制的主要方法
-func (a *App) HandlerDirectory(sourceDir, targetDir string) error {
-	// 检查源目录是否存在
-	if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
-		return fmt.Errorf("源目录不存在: %s", sourceDir)
+// GetAppArgument 获取APP传递得参数
+func (a *App) GetAppArgument() interface{} {
+	if a.handlerRequest == nil {
+		fmt.Printf("handlerRequest未初始化\n")
+		return nil
 	}
-
-	// 检查目标目录,如果不存在则创建
-	if _, err := os.Stat(targetDir); os.IsNotExist(err) {
-		// 源目录不存在时不需要创建目录,但目标目录可以创建
-		err := os.MkdirAll(targetDir, os.ModePerm)
-		if err != nil {
-			return fmt.Errorf("创建目标目录失败: %v", err)
+	info, err := a.handlerRequest.GetUserInfo()
+	if err != nil {
+		fmt.Printf("获取用户信息失败: %v\n", err)
+		return nil
+	}
+	// 将 map 转换为 JSON 再解析为结构体
+	// 安全地提取用户信息数据
+	var userInfo interface{}
+	if infoMap, ok := info.(map[string]interface{}); ok {
+		if data, exists := infoMap["data"]; exists {
+			userInfo = data
+		} else {
+			userInfo = infoMap
 		}
+	} else {
+		userInfo = info
+	}
+	return map[string]interface {
+	}{
+		"token":     a.Token,
+		"user_info": userInfo,
+		"home_page": a.HomePage,
 	}
-
-	// 执行具体的处理逻辑
-	return a.processDirectories(sourceDir, targetDir)
 }
 
-// ProcessResult 处理结果结构体
-type ProcessResult struct {
-	Success  bool   `json:"success"`
-	Message  string `json:"message"`
-	Progress int    `json:"progress"`
+// SelectDirectory 目录选择方法
+func (a *App) SelectDirectory() (string, error) {
+	return a.dialogHandler.SelectDirectory()
 }
 
-// isValidImageFile 检查文件是否为有效的图片文件
-func (a *App) isValidImageFile(filename string) bool {
-	validExtensions := []string{".avif", ".bmp", ".png", ".jpg", ".jpeg"}
-	ext := strings.ToLower(filepath.Ext(filename))
+// HandlerDirectory 处理目录复制的主要方法
+func (a *App) HandlerDirectory(sourceDir, targetDir string) error {
+	return a.directoryHandler.HandlerDirectory(sourceDir, targetDir)
+}
 
-	for _, validExt := range validExtensions {
-		if ext == validExt {
-			return true
-		}
+// HandlerOutPutDirectory 处理输出目录
+func (a *App) HandlerOutPutDirectory() []handlers.ImageResult {
+	applicationDirectory, err := a.directoryHandler.GetApplicationDirectory()
+	if err != nil {
+		return nil
+	}
+	directory, err := a.directoryHandler.HandlerOutPutDirectory(applicationDirectory + "/output")
+	if err != nil {
+		return []handlers.ImageResult{}
 	}
-	return false
+	return directory
 }
 
-// ProcessCallback 处理过程回调函数类型
-type ProcessCallback func(result ProcessResult)
-
-// processDirectories 实际处理目录的逻辑
-func (a *App) processDirectories(sourceDir, targetDir string) error {
-	// 发送初始进度
-	a.sendProgress(ProcessResult{
-		Success:  true,
-		Message:  "开始处理目录...",
-		Progress: 0,
-	})
+type PostJsonData struct {
+	ImageUrl    string `json:"image_url"`
+	GoodsArtNo  string `json:"goods_art_no"`
+	Category    string `json:"category"`
+	Description string `json:"description"`
+	Price       string `json:"price"`
+}
 
-	// 遍历和处理逻辑
-	err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+func (a *App) MakeProducts(imageProduct []handlers.ImageResult) map[string]interface{} {
+	//	生成产品册
+	var postData []PostJsonData
+	for _, product := range imageProduct {
+		result, err := a.handlerRequest.MakeFileRequest(utils.UploadImages, product.ImagePath, "file")
 		if err != nil {
-			a.sendProgress(ProcessResult{
-				Success:  false,
-				Message:  fmt.Sprintf("访问路径出错: %v", err),
-				Progress: 0,
-			})
-			return err
+			fmt.Printf("上传图片失败: %v\n", err)
+			continue
 		}
-		fmt.Printf("正在处理: %s\n", path)
-		// 处理 800x800 目录的逻辑
-		if info.IsDir() && strings.Contains(info.Name(), "800x800") {
-			fmt.Printf("👍👍👍符合条件: %s\n", path)
-			// 获取上级目录名称
-			parentDirName := filepath.Base(filepath.Dir(path))
-
-			// 在目标目录中创建以父目录命名的子目录
-			newTargetDir := filepath.Join(targetDir, parentDirName)
-			// 检查目标目录,如果不存在则创建
-			if _, err := os.Stat(newTargetDir); os.IsNotExist(err) {
-				// 源目录不存在时不需要创建目录,但目标目录可以创建
-				err := os.MkdirAll(newTargetDir, os.ModePerm)
-				if err != nil {
-					return fmt.Errorf("创建目标目录失败: %v", err)
+		if resultMap, ok := result.(map[string]interface{}); ok {
+			// 安全获取 data 字段
+			dataValue, exists := resultMap["data"]
+			if !exists {
+				fmt.Println("响应中未找到 data 字段")
+				continue
+			}
+			// 类型断言为 map[string]interface{}
+			reqData, ok := dataValue.(map[string]interface{})
+			if !ok {
+				fmt.Printf("data字段类型不正确,期望map[string]interface{},实际类型: %T\n", dataValue)
+				continue
+			}
+			var imageUrl string
+			if urlValue, exists := reqData["url"]; exists {
+				// 安全转换为字符串
+				switch v := urlValue.(type) {
+				case string:
+					imageUrl = v
+				case fmt.Stringer:
+					imageUrl = v.String()
+				default:
+					imageUrl = fmt.Sprintf("%v", v)
 				}
 			}
-			a.sendProgress(ProcessResult{
-				Success:  true,
-				Message:  fmt.Sprintf("找到目录: %s,将在目标目录创建子目录: %s", info.Name(), parentDirName),
-				Progress: 50,
+			// 安全获取各个字段并进行类型转换
+			goodsArtNo := product.GoodsArtNo
+			category := product.Category
+			description := product.Description
+			priceValue := product.Price
+			// 添加到 postData
+			postData = append(postData, PostJsonData{
+				ImageUrl:    imageUrl,
+				GoodsArtNo:  goodsArtNo,
+				Category:    category,
+				Description: description,
+				Price:       priceValue,
 			})
-
-			// 执行复制操作到新创建的子目录
-			err := a.copyFilesFromDir(path, newTargetDir)
-			if err != nil {
-				a.sendProgress(ProcessResult{
-					Success:  false,
-					Message:  fmt.Sprintf("复制文件到:%v 失败: %v", newTargetDir, err),
-					Progress: 50,
-				})
-				return err
-			}
 		}
-
-		return nil
-	})
-
-	// 发送完成进度
-	if err != nil {
-		a.sendProgress(ProcessResult{
-			Success:  false,
-			Message:  fmt.Sprintf("处理失败: %v", err),
-			Progress: 100,
-		})
-	} else {
-		a.sendProgress(ProcessResult{
-			Success:  true,
-			Message:  "处理完成",
-			Progress: 100,
-		})
 	}
-
-	return err
-}
-
-// copyFilesFromDir 从指定目录复制所有文件到目标目录
-func (a *App) copyFilesFromDir(sourceDir, targetDir string) error {
-	entries, err := os.ReadDir(sourceDir)
+	HLMData := map[string]interface{}{
+		"data": postData,
+	}
+	result, err := a.handlerRequest.MakePostRequest(utils.CreateProduct, HLMData)
 	if err != nil {
-		return fmt.Errorf("读取目录失败 %s: %v", sourceDir, err)
+		fmt.Printf("request err:%v\n", err)
+		return nil
 	}
-
-	for _, entry := range entries {
-		// 只处理文件,跳过子目录
-		if entry.IsDir() {
-			continue // 忽略目录
+	if resultMap, ok := result.(map[string]interface{}); ok {
+		dataValue, exists := resultMap["data"]
+		if !exists {
+			return nil
 		}
-
-		// 检查是否为有效的图片文件
-		if !a.isValidImageFile(entry.Name()) {
-			continue // 跳过非图片文件
+		reqData, _ := dataValue.(map[string]interface{})
+		//a.UrlHost
+		productIndex, _ := reqData["product_index"].(string)
+		urlShuffix := "/product_catalog/index.html?product_index="
+		// 检查 URL 是否包含特定域名
+		HtmlUrl := "https://b2b2.valimart.net"
+		if strings.Contains(a.UrlHost, "pubdata.cn") {
+			// 处理 pubdata.cn 域名的逻辑
+			HtmlUrl = "https://b2b2.pubdata.cn"
 		}
-
-		sourcePath := filepath.Join(sourceDir, entry.Name())
-		targetPath := filepath.Join(targetDir, entry.Name())
-
-		err := a.copyFile(sourcePath, targetPath)
-		if err != nil {
-			return fmt.Errorf("复制文件失败 %s 到 %s: %v", sourcePath, targetPath, err)
+		returnUrl := HtmlUrl + urlShuffix + productIndex
+		return map[string]interface{}{
+			"url": returnUrl,
 		}
 	}
-
 	return nil
 }
-
-// copyFile 复制单个文件
-func (a *App) copyFile(src, dst string) error {
-	sourceFile, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-	defer sourceFile.Close()
-
-	// 创建目标文件
-	destFile, err := os.Create(dst)
-	if err != nil {
-		return err
-	}
-	defer destFile.Close()
-
-	// 复制文件内容
-	_, err = io.Copy(destFile, sourceFile)
-	if err != nil {
-		return err
-	}
-
-	// 同步文件内容到磁盘
-	return destFile.Sync()
-}
-
-// sendProgress 发送进度更新
-func (a *App) sendProgress(result ProcessResult) {
-	// 这里可以通过事件系统发送进度更新
-	// 具体实现在下一步中说明
-	runtime.EventsEmit(a.ctx, "copy800-progress", result)
-}

+ 23 - 0
build.bat

@@ -0,0 +1,23 @@
+@echo off
+echo 开始构建 Wails 应用...
+
+REM 清理之前的构建
+echo 清理构建目录...
+wails build -clean
+
+REM 构建应用并嵌入 WebView2
+echo 正在构建应用并嵌入 WebView2...
+wails build -webview2 embed
+
+REM 检查构建是否成功
+if %ERRORLEVEL% EQU 0 (
+    echo 构建成功!
+    echo 可执行文件位置: build\bin\
+) else (
+    echo 构建失败,请检查错误信息
+    pause
+    exit /b 1
+)
+
+echo 构建完成
+pause

+ 24 - 11
frontend/src/App.vue

@@ -1,29 +1,32 @@
 <script lang="ts" setup>
-import { reactive ,computed} from 'vue'
+import { reactive,computed,ref} from 'vue'
+import Header from './components/Header.vue'
 import { useRoute } from 'vue-router'
-
+const route = useRoute()
 const font = reactive({
   color: 'rgba(0, 0, 0, .15)',
 })
 const title = '智慧映拍照机辅助工具箱'
-
+// 控制返回按钮显示逻辑
+const showHeaderContainer = computed(() => {
+  // 如果路由元信息中明确设置了 hideBackButton,则不显示返回按钮
+    return route.meta?.hideBackButton
+})
 </script>
 
 <template>
+  <el-affix :offset="0">
+  <Header />
+  </el-affix>
   <router-view v-slot="{ Component, route }">
     <el-watermark class="mark-view" :font="font" :content="['智慧映拍照机辅助工具', '浙江惠利玛版权所有']">
-      <div class="header-container">
-        <router-link to="/" class="title-link">
-          <h2 class="intro-title">
-            {{ route?.path !== '/' ? route.meta?.title : title }}
-          </h2>
-        </router-link>
+      <div class="header-container" ref="header_containder" v-show="showHeaderContainer">
         <p class="intro-description" v-if="route?.path == '/'">
           🛠️ 专为【智慧映拍照机】打造的实用辅助工具集合<br>
           🔧 一站式解决拍照机使用中的各种需求
         </p>
       </div>
-      <component :is="Component" />
+        <component :is="Component" />
     </el-watermark>
   </router-view>
 </template>
@@ -71,7 +74,17 @@ html, body {
   height: 100%;
   margin: 0;
   padding: 0;
-  overflow: hidden;
+  overflow-x: hidden;
+  overflow-y: scroll; /* 或者使用 auto */
+}
+/* 针对 Webkit 浏览器(Chrome, Safari等) */
+::-webkit-scrollbar {
+  display: none;
+}
+
+/* 针对 Firefox */
+html, body {
+  scrollbar-width: none;
 }
 </style>
 

BIN
frontend/src/assets/images/gy.png


BIN
frontend/src/assets/images/pz.png


+ 43 - 0
frontend/src/components/ExternalPage.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="external-page-container">
+    <div class="external-content">
+      <!-- 使用 object 标签 -->
+      <object :data="externalUrl" type="text/html" class="external-object">
+        <p>无法加载页面,请<a :href="externalUrl" target="_blank">点击这里</a>在新窗口打开</p>
+      </object>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useRoute } from 'vue-router'
+
+const route = useRoute()
+const externalUrl = ref('')
+
+onMounted(() => {
+  const urlParam = route.query.url
+  if (urlParam) {
+    externalUrl.value = decodeURIComponent(urlParam as string)
+  }
+})
+</script>
+
+<style scoped>
+.external-page-container {
+  width: 100%;
+  height: calc(100vh - 60px); /* 减去header高度 */
+}
+
+.external-object {
+  width: 100%;
+  height: 100%;
+  border: none;
+}
+
+.external-content {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 170 - 0
frontend/src/components/Header.vue

@@ -0,0 +1,170 @@
+<!-- src/components/Header.vue -->
+<template>
+  <el-header class="app-header">
+    <div class="header-content">
+      <!-- 左侧返回按钮 -->
+      <div class="left-section">
+        <el-button
+            v-if="showBackButton"
+            link
+            @click="goBack"
+            class="back-button"
+        >
+          <el-icon ><ArrowLeftBold /></el-icon>
+          返回
+        </el-button>
+      </div>
+
+      <!-- 中间标题 -->
+      <div class="center-section">
+        <h1 class="app-title">{{ title }}</h1>
+      </div>
+
+      <!-- 右侧用户信息 -->
+      <div class="right-section">
+        <el-dropdown @command="handleUserCommand">
+          <span class="user-info">
+            <el-avatar :size="32" icon="User" />
+            <span class="username">{{ username }}</span>
+            <el-icon><ArrowDown /></el-icon>
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item disabled>
+                <div class="user-company-info">
+                  <div>企业: {{ companyName }}</div>
+                  <div>用户: {{ username }}</div>
+                </div>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+  </el-header>
+</template>
+
+<script setup lang="ts">
+import { ref, computed ,onMounted} from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import {GetAppArgument} from '../../wailsjs/go/main/App'
+import { ArrowLeftBold, ArrowDown, User } from '@element-plus/icons-vue'
+import { ElMessageBox } from 'element-plus'
+const router = useRouter()
+const route = useRoute()
+
+// 用户信息(可以从全局状态或props传入)
+let username = ref('')
+let companyName = ref('浙江惠利玛科技有限公司')
+// let HomePage = ref('/')
+// 标题显示逻辑
+const title = computed(() => {
+  return route.meta?.title || '智慧映拍照机辅助工具箱'
+})
+// 异步获取应用参数
+const fetchAppArgument = async () => {
+  try {
+    const result = await GetAppArgument()
+    console.log('result',result)
+    if(!result){
+      ElMessageBox.alert('用户信息获取失败,请重新启动vali小工具',{
+        showClose: false,        // 隐藏关闭按钮
+        closeOnClickModal: false, // 点击遮罩层不关闭
+        closeOnPressEscape: false, // 按ESC键不关闭
+        showCancelButton: false,   // 隐藏取消按钮
+        showConfirmButton: false,  // 隐藏确认按钮
+      })
+    }
+    username.value = result.user_info.account_name ||result.user_info.real_name || result.user_info.login_name
+    companyName.value = result.user_info.brand_company_name
+    await router.push({"path": result.home_page})
+    // 例如:username.value = result.username
+  } catch (error) {
+    console.error("获取应用参数失败:", error)
+    return null
+  }
+}
+
+// 在组件挂载时获取参数
+onMounted(() => {
+  fetchAppArgument()
+})
+// 控制返回按钮显示逻辑
+const showBackButton = computed(() => {
+  // 如果路由元信息中明确设置了 hideBackButton,则不显示返回按钮
+  if (route.meta?.hideBackButton === true) {
+    return false
+  }
+  // 默认情况下,如果不是首页则显示返回按钮
+  return route.path !== '/'
+})
+
+// 返回上一页
+const goBack = () => {
+  router.back()
+}
+
+// 用户菜单操作
+const handleUserCommand = (command: string) => {
+}
+</script>
+
+<style scoped>
+.app-header {
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  padding: 0;
+  height: 60px !important;
+}
+
+.header-content {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 100%;
+  padding: 0 20px;
+}
+
+.left-section {
+  flex: 1;
+}
+.center-section {
+  flex: 2;
+  text-align: center;
+}
+
+.right-section {
+  flex: 1;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.app-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.user-info {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  gap: 8px;
+}
+
+.username {
+  font-size: 14px;
+  color: #606266;
+}
+
+.back-button {
+  color: #909399;
+}
+
+.user-company-info div {
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.5;
+}
+</style>

+ 1 - 8
frontend/src/components/HelloWorld.vue

@@ -1,18 +1,11 @@
 <script lang="ts" setup>
 import {reactive} from 'vue'
-import {Greet} from '../../wailsjs/go/main/App'
 
 const data = reactive({
   name: "",
   resultText: "Please enter your name below 👇",
 })
 
-function greet() {
-  Greet(data.name).then(result => {
-    data.resultText = result
-  })
-}
-
 </script>
 
 <template>
@@ -20,7 +13,7 @@ function greet() {
     <div id="result" class="result">{{ data.resultText }}</div>
     <div id="input" class="input-box">
       <input id="name" v-model="data.name" autocomplete="off" class="input" type="text"/>
-      <button class="btn" @click="greet">Greet</button>
+      <button class="btn" >Greet</button>
     </div>
   </main>
 </template>

+ 41 - 19
frontend/src/components/Home.vue

@@ -8,11 +8,11 @@
         </div>
       </template>
 
-      <el-row :gutter="20" justify="center">
+      <el-row :gutter="5" justify="center">
         <el-col
             :span="12"
-            :md="8"
-            :lg="6"
+            :md="24"
+            :lg="12"
             v-for="item in views"
             :key="item.name"
             class="tool-item"
@@ -23,7 +23,7 @@
               @click="goToTool(item.path)"
           >
             <div class="tool-content">
-              <div class="tool-icon">{{ item.icon }}</div>
+              <div class="tool-icon"><img :src="item.icon" alt=""/></div>
               <div class="tool-name">{{ item.desc }}</div>
             </div>
           </el-card>
@@ -36,15 +36,22 @@
 import { ref } from 'vue'
 import { useRouter } from 'vue-router'
 import { ViewInfo } from "../interfaces/HomeINterface"
-
+import pzIcon from '../assets/images/pz.png'
+import gyIcon from '../assets/images/gy.png'
 const router = useRouter()
 
 const views = ref<ViewInfo[]>([
   {
-    name: '复制800*800目录',
+    name: '白底图批量导出',
     path: '/copy_800_tool',
-    desc: "复制800*800目录",
-    icon: '🔧'
+    desc: "白底图批量导出",
+    icon: pzIcon
+  },
+  {
+    name: '制作产品册',
+    path: '/product_list',
+    desc: "制作产品册",
+    icon: gyIcon
   },
 ])
 
@@ -54,8 +61,9 @@ const goToTool = (path: string) => {
 </script>
 <style scoped>
 .home-container {
-  width: 80%;
+  width: 90%;
   margin: 0 auto;
+  overflow-y: auto;
 }
 
 
@@ -83,13 +91,13 @@ const goToTool = (path: string) => {
   cursor: pointer;
   transition: all 0.3s ease;
   border-radius: 12px;
-  height: 120px;
+  height: 200px;
 }
 .tool-card {
   cursor: pointer;
   transition: all 0.3s ease;
   border-radius: 12px;
-  height: 120px;
+  height: 200px;
   border: 1px solid #e4e7ed;
   background: #37353E;
 }
@@ -101,27 +109,41 @@ const goToTool = (path: string) => {
 }
 
 .tool-content {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
   height: 100%;
   padding: 15px 10px;
+  display: flex;
+  flex-direction: row; /* 改为横向排列 */
+  align-items: center; /* 垂直居中 */
 }
 
 .tool-icon {
-  font-size: 2rem;
-  margin-bottom: 10px;
+  flex: 1; /* 设置占比 */
+  font-size: 1rem;
   color: #ffffff;
   text-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  padding: 10px;
+}
+.tool-icon img {
+  max-width: 100%;
+  max-height: 100%;
+  width: auto;
+  height: auto;
+  object-fit: contain; /* 保持图片比例 */
 }
-
 .tool-name {
-  font-size: 0.9rem;
+  flex: 2; /* 占据1份空间 */
+  font-size: 1.5rem;
   font-weight: 500;
   text-align: center;
   color: #ffffff;
   line-height: 1.4;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 :deep(.el-card){
   border: none;

+ 345 - 0
frontend/src/components/ProductList.vue

@@ -0,0 +1,345 @@
+<!-- src/components/ProductList.vue -->
+<template>
+  <div class="product-list-container">
+    <!-- 日期显示 -->
+    <div class="date-info">
+      日期: {{ currentDate }}
+    </div>
+
+    <!-- 产品列表 -->
+    <div class="product-list">
+      <div v-for="(product, index) in products" :key="index" class="product-item">
+        <div class="product-image">
+          <el-image :src="product.image_base64"
+                    :preview-src-list="[product.image_base64]"
+                    :zoom-rate="1.2"
+                    :max-scale="7"
+                    :min-scale="0.2"/>
+        </div>
+
+        <div class="product-info">
+          <div class="product-code">
+            <el-icon><PriceTag /></el-icon>
+            <el-input
+                v-model="product.goods_art_no"
+                size="default"
+                placeholder="请输入货号"
+                :bordered="false"
+                class="full-width-input"
+                @input="saveProduct(index)"
+            />
+          </div>
+
+          <div class="product-category">
+            <span class="category-label">分类</span>
+            <el-input
+                v-model="product.category"
+                size="default"
+                placeholder="请输入分类"
+                class="full-width-input"
+                @input="saveProduct(index)"
+            />
+          </div>
+
+          <div class="product-description">
+            <el-icon><Document /></el-icon>
+            <el-input
+                v-model="product.description"
+                size="default"
+                placeholder="请输入描述"
+                class="full-width-input"
+                @input="saveProduct(index)"
+            />
+          </div>
+
+          <div class="product-price">
+            ¥
+            <el-input
+                v-model="product.price"
+                placeholder="请输入价格"
+                size="default"
+                type="number"
+                class="full-width-input"
+                @input="saveProduct(index)"
+            />
+          </div>
+        </div>
+
+
+      </div>
+    </div>
+
+    <!-- 生成报价图册按钮 -->
+    <div class="generate-button">
+      <el-button type="primary" size="default" @click="generateCatalog">
+        生成报价图册 →
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ElLoading,ElMessageBox } from 'element-plus'
+import {computed, onMounted, ref} from 'vue'
+import { useRouter } from 'vue-router'
+import {Document, PriceTag} from '@element-plus/icons-vue'
+import {HandlerOutPutDirectory,MakeProducts} from '../../wailsjs/go/main/App'
+import {handlers} from "../../wailsjs/go/models";
+const router = useRouter()
+// 当前日期
+const currentDate = computed(() => {
+  const now = new Date()
+  return now.toLocaleDateString('zh-CN')
+})
+// 产品数据
+let products = ref<handlers.ImageResult[]>([])
+
+// 生成报价图册方法
+const generateCatalog = async () => {
+  let productList = JSON.parse(JSON.stringify(products.value));
+  for (let i = 0; i < productList.length; i++) {
+    productList[i].image_base64 = "";
+  }
+  console.log('生成报价图册', productList)
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在处理请稍后',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+  try{
+    const productResult = await MakeProducts(productList)
+    if(!productResult){
+      await ElMessageBox.alert('生成报价图册失败,请重试', {})
+      return
+    }
+    console.log('生成报价图册结果', productResult.url)
+    await router.push({"path": "/external","query":{"url":productResult.url}})
+  }catch (error) {
+    console.error("获取应用参数失败:", error)
+    return null
+  }finally {
+    loading.close()
+  }
+}
+// 异步获取应用参数
+const fetchOutPut = async () => {
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在处理请稍后',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+  try {
+    products.value = await HandlerOutPutDirectory()
+    // 例如:username.value = result.username
+  } catch (error) {
+    console.error("获取应用参数失败:", error)
+    return null
+  }finally {
+    loading.close()
+  }
+}
+
+// 在组件挂载时获取参数
+onMounted(() => {
+  fetchOutPut()
+})
+
+// 实时保存产品信息
+const saveProduct = (index: number) => {
+  // 可以在这里添加防抖逻辑或者调用后端保存接口
+  console.log('保存产品信息:', products.value[index])
+  // 如果需要调用后端接口保存,可以在这里添加
+}
+</script>
+
+<style scoped>
+.product-list-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+.date-info {
+  text-align: center;
+  margin-bottom: 20px;
+  color: #606266;
+  font-size: 14px;
+}
+
+.product-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.product-item {
+  display: flex;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  padding: 16px;
+  border: 1px solid #e4e7ed;
+}
+
+.product-image {
+  width: 100px;
+  height: 100px;
+  position: relative;
+  margin-right: 16px;
+}
+
+.product-image img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  border-radius: 8px;
+}
+
+.camera-icon {
+  position: absolute;
+  bottom: 8px;
+  right: 8px;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  width: 24px;
+  height: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 12px;
+}
+
+.product-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+}
+
+.product-code {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  font-weight: 500;
+  color: #303133;
+  font-size: 14px;
+}
+
+.product-category {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  font-size: 14px;
+}
+
+.category-label {
+  color: #909399;
+  font-size: 12px;
+  background: #f0f0f0;
+  padding: 4px 8px;
+  border-radius: 4px;
+}
+
+.category-value {
+  color: #606266;
+  font-size: 14px;
+}
+
+.product-description {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  font-size: 14px;
+  color: #606266;
+  line-height: 1.4;
+}
+
+.product-price {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  margin-top: 12px;
+  font-size: 18px;
+  font-weight: 600;
+  color: #ff9900;
+  text-align: left;
+}
+
+.generate-button {
+  margin-top: 20px;
+  text-align: center;
+}
+
+.generate-button .el-button {
+  background-color: #000;
+  color: white;
+  border: none;
+  font-size: 20px;
+  font-weight: 600;
+  padding: 12px 40px;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  min-width: 90vw; /* 固定最小宽度,让按钮更长 */
+  height: 8vh;
+}
+
+.generate-button .el-button:hover {
+  background-color: #1a1a1a;
+}
+
+.product-info .el-input {
+  margin-left: 4px;
+}
+
+.full-width-input {
+  width: 100% !important;
+  border: none !important;
+  border-bottom: 1px solid #dcdfe6 !important;
+  border-radius: 0 !important;
+}
+
+.full-width-input:focus {
+  outline: none !important;
+  border-bottom: 2px solid #409eff !important;
+}
+
+.product-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.product-code,
+.product-category,
+.product-description,
+.product-price {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.product-code .el-icon,
+.product-description .el-icon {
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+
+.category-label {
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+/* 在现有样式基础上添加 */
+:deep(.full-width-input .el-input__wrapper) {
+  border: none !important;
+  box-shadow: none !important;
+  border-radius: 0 !important;
+  padding: 0 !important;
+}
+
+:deep(.full-width-input .el-input__inner) {
+  border: none !important;
+}
+</style>

+ 15 - 7
frontend/src/components/Tools_800_Copy.vue

@@ -102,8 +102,10 @@ watch(() => progressList.value.length, () => {
 
 <template>
 <!--复制800*800目录文件-->
+<div>
   <div class="container">
-    <el-alert title="第一步:请选择需要处理的目录" type="success" effect="dark" :closable="false" style="max-width: 95%">
+    <br/>
+    <el-alert class="container-1" title="第一步:请选择需要处理的目录" type="success" effect="dark" :closable="false">
       <el-input
           class="first-select"
           v-model="select_root_path"
@@ -116,7 +118,7 @@ watch(() => progressList.value.length, () => {
       </el-input>
     </el-alert>
     <br/>
-    <el-alert title="第二步:请选择需要保存的路径" type="primary" effect="dark" :closable="false" style="max-width: 95%">
+    <el-alert class="container-2" title="第二步:请选择需要保存的路径" type="primary" effect="dark" :closable="false">
       <el-input
           class="first-select"
           v-model="second_select_root_path"
@@ -140,9 +142,10 @@ watch(() => progressList.value.length, () => {
       </el-scrollbar>
     </el-card>
     <div class="bottom-container">
-      <el-button :disabled="progressing" type="primary" style="width: 80vh" effect="dark" class="handler_btn" @click="startSubmit()">开始处理</el-button>
+      <el-button :disabled="progressing" type="primary" effect="dark" class="handler_btn" @click="startSubmit()">开始处理</el-button>
     </div>
   </div>
+</div>
 </template>
 
 <style scoped>
@@ -151,17 +154,22 @@ watch(() => progressList.value.length, () => {
   flex-direction: column;
   align-items: center; /* 添加此属性实现水平居中 */
   min-height: 93vh;
-  width: 100%;
+  width: 95%;
+  margin: 0 auto;
 }
 .first-select{
-  margin: 20px 5px 10px 5px;
-  width: 80vh
+  width: 88vw;
+  margin: 0 auto;
 }
 .bottom-container{
   position: absolute;
-  bottom: 10%;
+  bottom: 15%;
   padding: 0 5%;
   left: 0;
+  text-align: center;
+}
+.handler_btn{
+  width: 90vw;
 }
 :deep(.el-card__body){
 padding: 0 3%;

+ 9 - 0
frontend/src/interfaces/ProductsInterface.ts

@@ -0,0 +1,9 @@
+
+export interface Products {
+    // 首页路由对象
+    goods_art_no: string
+    category: string
+    description: string
+    price: string
+    image_path: string
+}

+ 19 - 2
frontend/src/router/index.ts

@@ -1,19 +1,36 @@
 import {createRouter, createWebHashHistory, Router,RouteRecordRaw} from "vue-router";
 import home from "../components/Home.vue";
 import Tools_800_Copy from "../components/Tools_800_Copy.vue";
+import ProductList from "../components/ProductList.vue";
+import ExternalPage from "../components/ExternalPage.vue";
 
 const routes:RouteRecordRaw[] = [
     {
         path:"/", //路径描述
         name:"home",
         component: home, // 主动引用,无论是否访问均加载页面
-        meta: { title: "首页" }
+        meta: { title: "智慧映拍照机辅助工具箱",hideBackButton: true  // 首页隐藏返回按钮
+             }
     },
     {
         path:"/copy_800_tool", //路径描述
         name:"copy_800_tool",
         component: Tools_800_Copy, // 主动引用,无论是否访问均加载页面
-        meta: { title: "复制800*800目录文件" }
+        meta: { title: "复制800*800目录文件",hideBackButton: false }
+    },
+    {
+        path:"/product_list", //路径描述
+        name:"product_list",
+        component: ProductList, // 主动引用,无论是否访问均加载页面
+        meta: { title: "制作产品册",hideBackButton: false }
+    },
+    {
+        path: '/external',
+        name: 'ExternalPage',
+        component: () => ExternalPage,
+        meta: {
+            title: "产品报价图册",hideBackButton: false // 如果需要隐藏返回按钮
+        }
     }
 ]
 

+ 1 - 1
frontend/vite.config.ts

@@ -3,5 +3,5 @@ import vue from '@vitejs/plugin-vue'
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [vue()]
+  plugins: [vue()],
 })

+ 6 - 1
frontend/wailsjs/go/main/App.d.ts

@@ -1,8 +1,13 @@
 // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 // This file is automatically generated. DO NOT EDIT
+import {handlers} from '../models';
 
-export function Greet(arg1:string):Promise<string>;
+export function GetAppArgument():Promise<any>;
 
 export function HandlerDirectory(arg1:string,arg2:string):Promise<void>;
 
+export function HandlerOutPutDirectory():Promise<Array<handlers.ImageResult>>;
+
+export function MakeProducts(arg1:Array<handlers.ImageResult>):Promise<Record<string, any>>;
+
 export function SelectDirectory():Promise<string>;

+ 10 - 2
frontend/wailsjs/go/main/App.js

@@ -2,14 +2,22 @@
 // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 // This file is automatically generated. DO NOT EDIT
 
-export function Greet(arg1) {
-  return window['go']['main']['App']['Greet'](arg1);
+export function GetAppArgument() {
+  return window['go']['main']['App']['GetAppArgument']();
 }
 
 export function HandlerDirectory(arg1, arg2) {
   return window['go']['main']['App']['HandlerDirectory'](arg1, arg2);
 }
 
+export function HandlerOutPutDirectory() {
+  return window['go']['main']['App']['HandlerOutPutDirectory']();
+}
+
+export function MakeProducts(arg1) {
+  return window['go']['main']['App']['MakeProducts'](arg1);
+}
+
 export function SelectDirectory() {
   return window['go']['main']['App']['SelectDirectory']();
 }

+ 35 - 0
handlers/dialog.go

@@ -0,0 +1,35 @@
+package handlers
+
+import (
+	"context"
+	"github.com/wailsapp/wails/v2/pkg/runtime"
+)
+
+type DialogHandler struct {
+	ctx context.Context
+}
+
+func NewDialogHandler(ctx context.Context) *DialogHandler {
+	return &DialogHandler{ctx: ctx}
+}
+
+// SelectDirectory 目录选择方法
+func (dh *DialogHandler) SelectDirectory() (string, error) {
+	options := runtime.OpenDialogOptions{
+		Title:            "选择目录",
+		Filters:          []runtime.FileFilter{},
+		DefaultDirectory: "",
+		DefaultFilename:  "",
+	}
+
+	result, err := runtime.OpenDirectoryDialog(dh.ctx, options)
+	if err != nil {
+		return "", err
+	}
+
+	if result == "" {
+		return "", nil
+	}
+
+	return result, nil
+}

+ 327 - 0
handlers/directory.go

@@ -0,0 +1,327 @@
+package handlers
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"github.com/wailsapp/wails/v2/pkg/runtime"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// ProcessResult 处理结果结构体
+type ProcessResult struct {
+	Success  bool   `json:"success"`
+	Message  string `json:"message"`
+	Progress int    `json:"progress"`
+}
+
+// ProcessCallback 处理过程回调函数类型
+type ProcessCallback func(result ProcessResult)
+
+type DirectoryHandler struct {
+	ctx context.Context
+}
+
+func NewDirectoryHandler(ctx context.Context) *DirectoryHandler {
+	return &DirectoryHandler{ctx: ctx}
+}
+
+// HandlerDirectory 处理目录复制的主要方法
+func (dh *DirectoryHandler) HandlerDirectory(sourceDir, targetDir string) error {
+	// 检查源目录是否存在
+	if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
+		return fmt.Errorf("源目录不存在: %s", sourceDir)
+	}
+
+	// 检查目标目录,如果不存在则创建
+	if _, err := os.Stat(targetDir); os.IsNotExist(err) {
+		// 源目录不存在时不需要创建目录,但目标目录可以创建
+		err := os.MkdirAll(targetDir, os.ModePerm)
+		if err != nil {
+			return fmt.Errorf("创建目标目录失败: %v", err)
+		}
+	}
+
+	// 执行具体的处理逻辑
+	return dh.processDirectories(sourceDir, targetDir)
+}
+
+// processDirectories 实际处理目录的逻辑
+func (dh *DirectoryHandler) processDirectories(sourceDir, targetDir string) error {
+	// 发送初始进度
+	dh.sendProgress(ProcessResult{
+		Success:  true,
+		Message:  "开始处理目录...",
+		Progress: 0,
+	})
+
+	// 遍历和处理逻辑
+	err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			dh.sendProgress(ProcessResult{
+				Success:  false,
+				Message:  fmt.Sprintf("访问路径出错: %v", err),
+				Progress: 0,
+			})
+			return err
+		}
+		fmt.Printf("正在处理: %s\n", path)
+		// 处理 800x800 目录的逻辑
+		if info.IsDir() && strings.Contains(info.Name(), "800x800") {
+			fmt.Printf("👍👍👍符合条件: %s\n", path)
+			// 获取上级目录名称
+			parentDirName := filepath.Base(filepath.Dir(path))
+
+			// 在目标目录中创建以父目录命名的子目录
+			newTargetDir := filepath.Join(targetDir, parentDirName)
+			// 检查目标目录,如果不存在则创建
+			if _, err := os.Stat(newTargetDir); os.IsNotExist(err) {
+				// 源目录不存在时不需要创建目录,但目标目录可以创建
+				err := os.MkdirAll(newTargetDir, os.ModePerm)
+				if err != nil {
+					return fmt.Errorf("创建目标目录失败: %v", err)
+				}
+			}
+			dh.sendProgress(ProcessResult{
+				Success:  true,
+				Message:  fmt.Sprintf("找到目录: %s,将在目标目录创建子目录: %s", info.Name(), parentDirName),
+				Progress: 50,
+			})
+
+			// 执行复制操作到新创建的子目录
+			err := dh.copyFilesFromDir(path, newTargetDir)
+			if err != nil {
+				dh.sendProgress(ProcessResult{
+					Success:  false,
+					Message:  fmt.Sprintf("复制文件到:%v 失败: %v", newTargetDir, err),
+					Progress: 50,
+				})
+				return err
+			}
+		}
+
+		return nil
+	})
+
+	// 发送完成进度
+	if err != nil {
+		dh.sendProgress(ProcessResult{
+			Success:  false,
+			Message:  fmt.Sprintf("处理失败: %v", err),
+			Progress: 100,
+		})
+	} else {
+		dh.sendProgress(ProcessResult{
+			Success:  true,
+			Message:  "处理完成",
+			Progress: 100,
+		})
+	}
+
+	return err
+}
+
+// copyFilesFromDir 从指定目录复制所有文件到目标目录
+func (dh *DirectoryHandler) copyFilesFromDir(sourceDir, targetDir string) error {
+	entries, err := os.ReadDir(sourceDir)
+	if err != nil {
+		return fmt.Errorf("读取目录失败 %s: %v", sourceDir, err)
+	}
+
+	for _, entry := range entries {
+		// 只处理文件,跳过子目录
+		if entry.IsDir() {
+			continue // 忽略目录
+		}
+
+		// 检查是否为有效的图片文件
+		if !dh.isValidImageFile(entry.Name()) {
+			continue // 跳过非图片文件
+		}
+
+		sourcePath := filepath.Join(sourceDir, entry.Name())
+		targetPath := filepath.Join(targetDir, entry.Name())
+
+		err := dh.copyFile(sourcePath, targetPath)
+		if err != nil {
+			return fmt.Errorf("复制文件失败 %s 到 %s: %v", sourcePath, targetPath, err)
+		}
+	}
+
+	return nil
+}
+
+// copyFile 复制单个文件
+func (dh *DirectoryHandler) copyFile(src, dst string) error {
+	sourceFile, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer sourceFile.Close()
+
+	// 创建目标文件
+	destFile, err := os.Create(dst)
+	if err != nil {
+		return err
+	}
+	defer destFile.Close()
+
+	// 复制文件内容
+	_, err = io.Copy(destFile, sourceFile)
+	if err != nil {
+		return err
+	}
+
+	// 同步文件内容到磁盘
+	return destFile.Sync()
+}
+
+// isValidImageFile 检查文件是否为有效的图片文件
+func (dh *DirectoryHandler) isValidImageFile(filename string) bool {
+	validExtensions := []string{".avif", ".bmp", ".png", ".jpg", ".jpeg"}
+	ext := strings.ToLower(filepath.Ext(filename))
+
+	for _, validExt := range validExtensions {
+		if ext == validExt {
+			return true
+		}
+	}
+	return false
+}
+
+type ImageResult struct {
+	GoodsArtNo  string `json:"goods_art_no"`
+	ImagePath   string `json:"image_path"`
+	Category    string `json:"category"`
+	Description string `json:"description"`
+	Price       string `json:"price"`
+	ImageBase64 string `json:"image_base64"`
+}
+
+// sendProgress 发送进度更新
+func (dh *DirectoryHandler) sendProgress(result ProcessResult) {
+	runtime.EventsEmit(dh.ctx, "copy800-progress", result)
+}
+
+// HandlerOutPutDirectory 处理输出目录的主要方法
+func (dh *DirectoryHandler) HandlerOutPutDirectory(sourceDir string) ([]ImageResult, error) {
+	// 检查源目录是否存在
+	if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
+		return nil, fmt.Errorf("源目录不存在: %s", sourceDir)
+	}
+	var Results []ImageResult
+	// 执行具体的处理逻辑
+	// 遍历和处理逻辑
+	err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		fmt.Printf("正在处理: %s\n", path)
+		// 处理 800x800 目录的逻辑
+		if info.IsDir() && strings.Contains(info.Name(), "800x800") {
+			fmt.Printf("👍👍👍符合条件: %s\n", path)
+			// 获取父目录名作为 goods_art_no
+			parentDirName := filepath.Base(filepath.Dir(path))
+			goodsArtNo := parentDirName
+
+			// 查找该目录下的第一张有效图片
+			imagePath, err := dh.findFirstImageInDir(path)
+			if err == nil {
+				toBase64, err := dh.ImageToBase64(imagePath)
+				if err != nil {
+					toBase64 = ""
+				}
+				// 添加到结果数组
+				Results = append(Results, ImageResult{
+					GoodsArtNo:  goodsArtNo,
+					ImagePath:   imagePath,
+					Description: "",
+					Category:    "",
+					Price:       "0",
+					ImageBase64: toBase64,
+				})
+			}
+		}
+
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	return Results, nil
+}
+
+// findFirstImageInDir 查找目录下的第一张有效图片
+func (dh *DirectoryHandler) findFirstImageInDir(dirPath string) (string, error) {
+	entries, err := os.ReadDir(dirPath)
+	if err != nil {
+		return "", fmt.Errorf("读取目录失败: %v", err)
+	}
+
+	for _, entry := range entries {
+		if entry.IsDir() {
+			continue
+		}
+
+		if dh.isValidImageFile(entry.Name()) {
+			return filepath.Join(dirPath, entry.Name()), nil
+		}
+	}
+
+	return "", fmt.Errorf("未找到有效图片文件")
+}
+
+// ImageToBase64 将图片文件转换为Base64编码
+func (dh *DirectoryHandler) ImageToBase64(imagePath string) (string, error) {
+	// 读取图片文件
+	fileData, err := os.ReadFile(imagePath)
+	if err != nil {
+		return "", fmt.Errorf("读取图片文件失败: %v", err)
+	}
+
+	// 获取文件的MIME类型
+	mimeType := getMimeType(imagePath)
+
+	// 将文件数据编码为Base64
+	encoded := base64.StdEncoding.EncodeToString(fileData)
+
+	// 返回带有数据URI方案的Base64字符串
+	return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded), nil
+}
+
+// getMimeType 根据文件扩展名获取MIME类型
+func getMimeType(filePath string) string {
+	ext := strings.ToLower(filePath[strings.LastIndex(filePath, "."):])
+
+	mimeTypes := map[string]string{
+		".png":  "image/png",
+		".jpg":  "image/jpeg",
+		".jpeg": "image/jpeg",
+		".gif":  "image/gif",
+		".bmp":  "image/bmp",
+		".webp": "image/webp",
+	}
+
+	if mime, exists := mimeTypes[ext]; exists {
+		return mime
+	}
+
+	return "application/octet-stream"
+}
+
+// GetCurrentWorkingDirectory 获取当前工作目录
+func (dh *DirectoryHandler) GetCurrentWorkingDirectory() (string, error) {
+	return os.Getwd()
+}
+
+// GetApplicationDirectory 获取应用程序所在目录
+func (dh *DirectoryHandler) GetApplicationDirectory() (string, error) {
+	execPath, err := os.Executable()
+	if err != nil {
+		return "", err
+	}
+	return filepath.Dir(execPath), nil
+}

+ 132 - 0
handlers/handler_requests.go

@@ -0,0 +1,132 @@
+package handlers
+
+import (
+	"Vali-Tools/utils"
+	"context"
+	"fmt"
+)
+
+type HandlerRequests struct {
+	token      string
+	httpClient *utils.HTTPClient // 添加HTTP客户端
+	ctx        context.Context
+	host       string
+}
+
+func NewHandlerRequests(context context.Context, token string, host string) *HandlerRequests {
+	return &HandlerRequests{
+		token:      token,
+		ctx:        context,
+		host:       host,
+		httpClient: utils.NewHTTPClient(host, token),
+	}
+}
+
+// MakeGetRequest 网络请求示例方法
+func (c *HandlerRequests) MakeGetRequest(endpoint string) (interface{}, error) {
+	if c.httpClient == nil {
+		return nil, fmt.Errorf("HTTP客户端未初始化")
+	}
+
+	var result interface{}
+	err := c.httpClient.GetJSON(c.ctx, endpoint, &result, nil)
+	if err != nil {
+		return nil, fmt.Errorf("GET请求失败: %w", err)
+	}
+	if result != nil {
+		// 类型断言检查result是否为map[string]interface{}
+		if resultMap, ok := result.(map[string]interface{}); ok {
+			var codeValue int
+			if code, ok := resultMap["code"].(float64); ok {
+				codeValue = int(code)
+			} else if code, ok := resultMap["code"].(int); ok {
+				codeValue = code
+			}
+
+			if codeValue != 0 {
+				// 处理错误码不为0的情况
+				if msg, msgExists := resultMap["message"]; msgExists {
+					return nil, fmt.Errorf("API返回错误: code=%v, message=%s", codeValue, msg)
+				}
+				return nil, fmt.Errorf("API返回错误: code=%v", codeValue)
+			}
+		}
+	}
+	return result, nil
+}
+
+func (c *HandlerRequests) MakePostRequest(endpoint string, data interface{}) (interface{}, error) {
+	if c.httpClient == nil {
+		return nil, fmt.Errorf("HTTP客户端未初始化")
+	}
+	// 添加调试信息
+	fmt.Printf("发送POST请求到: %s, 数据: %+v\n", endpoint, data)
+	var result interface{}
+	err := c.httpClient.PostJSON(c.ctx, endpoint, data, &result, nil)
+	if err != nil {
+		return nil, fmt.Errorf("POST请求失败: %w", err)
+	}
+	if result != nil {
+		// 类型断言检查result是否为map[string]interface{}
+		if resultMap, ok := result.(map[string]interface{}); ok {
+			var codeValue int
+			if code, ok := resultMap["code"].(float64); ok {
+				codeValue = int(code)
+			} else if code, ok := resultMap["code"].(int); ok {
+				codeValue = code
+			}
+
+			if codeValue != 0 {
+				// 处理错误码不为0的情况
+				if msg, msgExists := resultMap["message"]; msgExists {
+					return nil, fmt.Errorf("API返回错误: code=%v, message=%s", codeValue, msg)
+				}
+				return nil, fmt.Errorf("API返回错误: code=%v", codeValue)
+			}
+		}
+	}
+	return result, nil
+}
+
+// MakeFileRequest 文件上传
+func (c *HandlerRequests) MakeFileRequest(endpoint string, filePath string, fieldName string) (interface{}, error) {
+	if c.httpClient == nil {
+		return nil, fmt.Errorf("HTTP客户端未初始化")
+	}
+
+	var result interface{}
+	err := c.httpClient.UploadFile(c.ctx, endpoint, filePath, fieldName, nil, &result)
+	if err != nil {
+		return nil, fmt.Errorf("文件上传失败: %w", err)
+	}
+	if result != nil {
+		// 类型断言检查result是否为map[string]interface{}
+		if resultMap, ok := result.(map[string]interface{}); ok {
+			var codeValue int
+			if code, ok := resultMap["code"].(float64); ok {
+				codeValue = int(code)
+			} else if code, ok := resultMap["code"].(int); ok {
+				codeValue = code
+			}
+
+			if codeValue != 0 {
+				// 处理错误码不为0的情况
+				if msg, msgExists := resultMap["message"]; msgExists {
+					return nil, fmt.Errorf("API返回错误: code=%v, message=%s", codeValue, msg)
+				}
+				return nil, fmt.Errorf("API返回错误: code=%v", codeValue)
+			}
+		}
+	}
+	return result, nil
+}
+
+// GetUserInfo 获取用户信息
+func (c *HandlerRequests) GetUserInfo() (interface{}, error) {
+	println("userInfoApi:", utils.GetUserInfo)
+	userInfo, err := c.MakeGetRequest(utils.GetUserInfo)
+	if err != nil {
+		return nil, err
+	}
+	return userInfo, nil
+}

+ 3 - 2
main.go

@@ -18,11 +18,12 @@ func main() {
 	// Create application with options
 	err := wails.Run(&options.App{
 		Title:         "Vali-Tools",
-		Width:         680,
-		Height:        768,
+		Width:         805,
+		Height:        800,
 		DisableResize: true, //禁止调整尺寸
 		AssetServer: &assetserver.Options{
 			Assets: assets,
+			//Handler: utils.NewFileLoader(),
 		},
 		BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 1},
 		OnStartup:        app.startup,

+ 10 - 0
utils/apis.go

@@ -0,0 +1,10 @@
+package utils
+
+const (
+	//GetUserInfo 获取用户信息
+	GetUserInfo = "/api/auth/user"
+	//UploadImages 上传图片
+	UploadImages = "/api/upload"
+	// 创建产品册
+	CreateProduct = "/api/ai_image/auto_photo/create_product"
+)

+ 22 - 0
utils/file.go

@@ -0,0 +1,22 @@
+package utils
+
+//type FileLoader struct {
+//	http.Handler
+//}
+//
+//func NewFileLoader() *FileLoader {
+//	return &FileLoader{}
+//}
+//
+//func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
+//	var err error
+//	requestedFilename := strings.TrimPrefix(req.URL.Path, "/")
+//	println("Requesting file:", requestedFilename)
+//	fileData, err := os.ReadFile(requestedFilename)
+//	if err != nil {
+//		res.WriteHeader(http.StatusBadRequest)
+//		res.Write([]byte(fmt.Sprintf("Could not load file %s", requestedFilename)))
+//	}
+//
+//	res.Write(fileData)
+//}

+ 188 - 0
utils/network_client.go

@@ -0,0 +1,188 @@
+package utils
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+// HTTPClient 封装HTTP客户端
+type HTTPClient struct {
+	client        *http.Client
+	baseURL       string
+	Authorization string
+}
+
+// NewHTTPClient 创建新的HTTP客户端
+func NewHTTPClient(baseURL, token string) *HTTPClient {
+	return &HTTPClient{
+		client: &http.Client{
+			Timeout: 30 * time.Second,
+		},
+		baseURL:       baseURL,
+		Authorization: token,
+	}
+}
+
+// SetToken 设置认证token
+func (c *HTTPClient) SetToken(token string) {
+	c.Authorization = token
+}
+
+// Get 发起GET请求
+func (c *HTTPClient) Get(ctx context.Context, endpoint string, headers map[string]string) (*http.Response, error) {
+	url := c.baseURL + endpoint
+	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %w", err)
+	}
+
+	// 设置默认headers
+	req.Header.Set("Content-Type", "application/json")
+	if c.Authorization != "" {
+		req.Header.Set("Authorization", "Bearer "+c.Authorization)
+	}
+
+	// 设置自定义headers
+	for key, value := range headers {
+		req.Header.Set(key, value)
+	}
+	println("Header====>>>>", req.Header)
+	return c.client.Do(req)
+}
+
+// Post 发起POST请求
+func (c *HTTPClient) Post(ctx context.Context, endpoint string, data interface{}, headers map[string]string) (*http.Response, error) {
+	url := c.baseURL + endpoint
+
+	var reqBody io.Reader
+	if data != nil {
+		jsonData, err := json.Marshal(data)
+		if err != nil {
+			return nil, fmt.Errorf("序列化请求数据失败: %w", err)
+		}
+		reqBody = bytes.NewBuffer(jsonData)
+	}
+
+	req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody)
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %w", err)
+	}
+
+	// 设置默认headers
+	req.Header.Set("Content-Type", "application/json")
+	if c.Authorization != "" {
+		req.Header.Set("Authorization", "Bearer "+c.Authorization)
+	}
+
+	// 设置自定义headers
+	for key, value := range headers {
+		req.Header.Set(key, value)
+	}
+
+	return c.client.Do(req)
+}
+
+// UploadFile 上传文件
+func (c *HTTPClient) UploadFile(ctx context.Context, endpoint string, filePath string, fieldName string, headers map[string]string, result interface{}) error {
+	url := c.baseURL + endpoint
+
+	// 打开文件
+	file, err := os.Open(filePath)
+	if err != nil {
+		return fmt.Errorf("打开文件失败: %w", err)
+	}
+	defer file.Close()
+
+	// 创建multipart表单
+	var buf bytes.Buffer
+	writer := multipart.NewWriter(&buf)
+
+	// 添加文件字段
+	fileWriter, err := writer.CreateFormFile(fieldName, filepath.Base(filePath))
+	if err != nil {
+		return fmt.Errorf("创建表单文件字段失败: %w", err)
+	}
+
+	// 复制文件内容到表单
+	_, err = io.Copy(fileWriter, file)
+	if err != nil {
+		return fmt.Errorf("复制文件内容失败: %w", err)
+	}
+
+	// 关闭表单写入器
+	err = writer.Close()
+	if err != nil {
+		return fmt.Errorf("关闭表单写入器失败: %w", err)
+	}
+
+	// 创建请求
+	req, err := http.NewRequestWithContext(ctx, "POST", url, &buf)
+	if err != nil {
+		return fmt.Errorf("创建请求失败: %w", err)
+	}
+
+	// 设置内容类型为multipart表单
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	// 设置认证头
+	if c.Authorization != "" {
+		req.Header.Set("Authorization", "Bearer "+c.Authorization)
+	}
+
+	// 设置自定义headers
+	for key, value := range headers {
+		req.Header.Set(key, value)
+	}
+
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return err
+	}
+	return c.ParseJSON(resp, result)
+}
+
+// ParseJSON 解析JSON响应
+func (c *HTTPClient) ParseJSON(resp *http.Response, result interface{}) error {
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("读取响应体失败: %w", err)
+	}
+
+	if resp.StatusCode >= 400 {
+		return fmt.Errorf("请求失败 status=%d body=%s", resp.StatusCode, string(body))
+	}
+
+	if err := json.Unmarshal(body, result); err != nil {
+		return fmt.Errorf("解析JSON失败: %w, body=%s", err, string(body))
+	}
+
+	return nil
+}
+
+// GetJSON 发起GET请求并解析JSON响应
+func (c *HTTPClient) GetJSON(ctx context.Context, endpoint string, result interface{}, headers map[string]string) error {
+	resp, err := c.Get(ctx, endpoint, headers)
+	if err != nil {
+		return err
+	}
+	return c.ParseJSON(resp, result)
+}
+
+// PostJSON 发起POST请求并解析JSON响应
+func (c *HTTPClient) PostJSON(ctx context.Context, endpoint string, data interface{}, result interface{}, headers map[string]string) error {
+	resp, err := c.Post(ctx, endpoint, data, headers)
+	if err != nil {
+		return err
+	}
+	return c.ParseJSON(resp, result)
+}