Quellcode durchsuchen

feat(app): 添加阴影图重命名功能

- 在Go后端添加RenameFolderFileNameApp接口函数
- 在CameraMachineHandler中实现RenameFolderFileName方法处理批量重命名请求
- 在前端类型定义中添加新的API函数声明
- 在前端JavaScript包装器中添加重命名功能调用函数
- 添加阴影图重命名页面组件ShadowRename.vue实现批量处理界面
- 在前端路由中注册阴影图重命名页面路由
- 在主页导航中添加阴影图重命名入口并配置相应图标
- 实现分页加载、批量选择、重命名操作等完整功能流程
rambo vor 2 Tagen
Ursprung
Commit
8d0b7418c2

+ 5 - 0
app.go

@@ -275,3 +275,8 @@ func (a *App) OpenFolder(path string) error {
 	cmd = exec.Command("explorer", "/select,", windowsPath+"\\")
 	return cmd.Start()
 }
+
+func (a *App) RenameFolderFileNameApp(goodsArtNos []string) ([]interface{}, error) {
+
+	return a.cameraMachineHandler.RenameFolderFileName(goodsArtNos)
+}

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


+ 6 - 0
frontend/src/components/Home.vue

@@ -39,6 +39,7 @@ import { ViewInfo } from "../interfaces/HomeINterface"
 import pzIcon from '../assets/images/pz.png'
 import gyIcon from '../assets/images/gy.png'
 import ktIcon from '../assets/images/kt.png'
+import rnIcon from '../assets/images/rn.png'
 const router = useRouter()
 
 const views = ref<ViewInfo[]>([
@@ -59,6 +60,11 @@ const views = ref<ViewInfo[]>([
     path: '/cut_out',
     desc: "批量抠图",
     icon: ktIcon
+  },{
+    name: '阴影图重命名',
+    path: '/shadow_rename',
+    desc: "阴影图重命名",
+    icon: rnIcon
   },
 ])
 

+ 435 - 0
frontend/src/components/ShadowRename.vue

@@ -0,0 +1,435 @@
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import { GetPhotoListApp,RenameFolderFileNameApp,OpenFolder } from '../../wailsjs/go/main/App';
+import { ElMessageBox ,ElLoading} from 'element-plus'
+// 页面状态
+const loading = ref(false);
+const loadingMore = ref(false);
+const error = ref<string | null>(null);
+const fullscreenLoading = ref(false)
+// 数据
+const photoList = ref<any[]>([]);
+const selectedProducts = ref<number[]>([]); // 存储选中的货号索引
+
+// 分页状态
+const currentPage = ref(1);
+const pageSize = ref(20); // 每页显示10条记录
+const totalCount = ref(0);
+const totalPages = ref(1);
+const hasPrev = ref(false);
+const hasNext = ref(true); // 默认有下一页,直到加载完成
+const dialogTableVisible = ref(false)
+const output_folder = ref<any[]>([])
+// 计算已选择的图片数量
+const selectedCount = computed(() => {
+  return selectedProducts.value.length;
+});
+
+// 获取拍照列表数据
+const fetchPhotoList = async (page: number = 1, append = false) => {
+  if (page === 1) {
+    loading.value = true;
+  } else {
+    loadingMore.value = true;
+  }
+
+  error.value = null;
+
+  try {
+    // 调用API时传递分页参数
+    const data = await GetPhotoListApp(page.toString());
+    console.log("获取的拍照列表数据:", data);
+
+    if (data) {
+      if (append) {
+        // 追加数据到现有列表
+        photoList.value = [...photoList.value, ...data.list];
+      } else {
+        // 替换整个列表
+        photoList.value = data.list || [];
+      }
+
+      // 设置分页信息
+      currentPage.value = data.current_page || 1;
+      pageSize.value = data.size || 10;
+      totalCount.value = data.total_count || 0;
+      totalPages.value = data.total_pages || 1;
+      hasPrev.value = data.has_prev || false;
+      hasNext.value = data.has_next || false;
+    } else {
+      console.warn('返回的数据格式不正确:', data);
+      error.value = '获取的数据格式不正确';
+    }
+  } catch (err) {
+    console.error('获取拍照列表失败:', err);
+    error.value = err instanceof Error ? err.message : '获取数据失败';
+  } finally {
+    loading.value = false;
+    loadingMore.value = false;
+  }
+};
+
+// 加载更多数据
+const loadMore = async () => {
+  if (!hasNext.value) return;
+
+  const nextPage = currentPage.value + 1;
+  await fetchPhotoList(nextPage, true); // 追加数据
+};
+
+// 刷新数据(重新从第一页开始)
+const refreshData = async () => {
+  await fetchPhotoList(1, false);
+};
+
+// 检查货号是否被选中
+const isProductSelected = (index: number) => {
+  // 由于列表现在是追加的,我们需要根据实际数据的索引判断
+  return selectedProducts.value.includes(index);
+};
+
+// 处理货号选择变化
+const handleProductSelectionChange = (index: number) => {
+  const isSelected = selectedProducts.value.includes(index);
+  if (isSelected) {
+    // 如果已选中,则取消选中
+    selectedProducts.value = selectedProducts.value.filter(i => i !== index);
+  } else {
+    // 如果未选中,则添加到选中列表
+    selectedProducts.value.push(index);
+  }
+};
+
+// 发起批量抠图的事件处理函数
+const handleBatchCutout = async() => {
+  console.log('发起批量抠图请求');
+  console.log('选中的货号索引:', selectedProducts.value);
+  // 获取选中的货号和对应的图片
+  const selectedData = selectedProducts.value.map(index => {
+    // 确保索引在当前列表范围内
+    if (index >= 0 && index < photoList.value.length) {
+      return photoList.value[index].goods_art_no;
+    }
+    return null;
+  }).filter(item => item !== null);
+  console.log('选中的数据:', selectedData);
+  const loading = ElLoading.service({
+    lock: true,
+    text: '处理中,请稍后...',
+    background: 'rgba(0, 0, 0, 0.7)',
+  })
+  try {
+    // 这里可以调用 API 或触发其他逻辑
+    output_folder.value = await RenameFolderFileNameApp(selectedData)
+    if (output_folder.value.length>0) {
+      console.log('抠图成功,输出文件夹:', output_folder.value)
+      // await ElMessageBox.confirm('重命名成功,点击确认打开目录',{
+      //   closeOnClickModal: false, // 点击遮罩层不关闭
+      //   closeOnPressEscape: false, // 按ESC键不关闭
+      //   showCancelButton: false,   // 隐藏取消按钮
+      // }).then(async () => {
+      //   // await OpenFolder(output_folder)
+      // })
+      dialogTableVisible.value = true
+      selectedProducts.value = []
+    } else {
+      console.error('抠图失败')
+      await ElMessageBox.confirm('重命名失败,请选择其他货号重试',{
+        closeOnClickModal: false, // 点击遮罩层不关闭
+        closeOnPressEscape: false, // 按ESC键不关闭
+        showCancelButton: false,   // 隐藏取消按钮
+      })
+    }
+  }catch (err){
+    loading.close ()
+    await ElMessageBox.confirm('重命名失败,请选择其他货号重试',{
+      closeOnClickModal: false, // 点击遮罩层不关闭
+      closeOnPressEscape: false, // 按ESC键不关闭
+      showCancelButton: false,   // 隐藏取消按钮
+    })
+  }finally {
+    loading.close ()
+  }
+};
+
+onMounted(() => {
+  fetchPhotoList(1, false);
+});
+</script>
+
+<template>
+  <div class="cutout-page">
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-state">
+      <el-spin size="large" />
+      <p>正在加载拍照记录,请稍候...</p>
+    </div>
+
+    <!-- 错误状态 -->
+    <div v-else-if="error" class="error-state">
+      <el-alert
+          :title="'错误: ' + error"
+          type="error"
+          :closable="false"
+      />
+      <el-button @click="refreshData" type="primary" style="margin-top: 20px;">
+        重新加载
+      </el-button>
+    </div>
+
+    <!-- 正常内容 -->
+    <div v-else>
+      <!-- 货号卡片列表 -->
+      <div class="product-cards-container">
+        <div
+            v-for="(photo, index) in photoList"
+            :key="`${photo.goods_art_no}-${index}`"
+            class="product-card"
+            :class="{ 'selected': isProductSelected(index) }"
+        >
+          <!-- 卡片头部 -->
+          <div class="card-header">
+            <div class="checkbox-wrapper">
+              <el-checkbox
+                  :model-value="isProductSelected(index)"
+                  @change="handleProductSelectionChange(index)"
+              ></el-checkbox>
+            </div>
+            <div class="card-info">
+              <span class="product-code">{{ photo.goods_art_no }}</span>
+              <span class="timestamp">{{ photo.action_time }}</span>
+            </div>
+          </div>
+
+          <!-- 图片区域 -->
+          <div class="image-container">
+            <div class="image-row">
+              <div
+                  v-for="(img, imgIndex) in photo.items"
+                  :key="imgIndex"
+                  class="image-item"
+              >
+                <el-image
+                    :src="`data:image/jpg;base64,${img}`"
+                    alt="产品图"
+                    class="product-image"
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 加载更多按钮 -->
+      <div class="load-more-container">
+        <el-button
+            v-if="hasNext"
+            :loading="loadingMore"
+            @click="loadMore"
+            type="primary"
+            plain
+        >
+          {{ loadingMore ? '加载中...' : '点击加载更多' }}
+        </el-button>
+        <div v-else class="no-more-data">
+          已加载全部数据
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部按钮区域(固定在底部) -->
+    <div class="fixed-bottom-bar">
+      <div class="selected-info">
+        已选择 <span class="selected-count">{{ selectedCount }}</span> 个货号
+      </div>
+      <el-button
+          type="primary"
+          @click="handleBatchCutout"
+          :disabled="selectedProducts.length === 0"
+          size="medium"
+      >
+        批量修改名称
+      </el-button>
+    </div>
+  </div>
+  <el-dialog v-model="dialogTableVisible" title="重命名结果">
+    <el-table :data="output_folder">
+      <el-table-column property="goods_art_no" label="货号" width="150" />
+      <el-table-column property="path" label="操作" width="200" fixed="right">
+        <template  #default="scope">
+          <el-button link type="primary" size="small" @click="OpenFolder(scope.row.path)">
+            打开目录
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-dialog>
+</template>
+
+<style scoped>
+.cutout-page {
+  padding: 20px;
+  background-color: #f9f9f9;
+  min-height: 100vh;
+  padding-bottom: 10px; /* 为固定底部按钮留出空间 */
+}
+
+.product-cards-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(48%, 1fr));
+  gap: 16px;
+  margin-bottom: 16px; /* 调整间距 */
+}
+
+/* 加载更多容器 */
+.load-more-container {
+  margin: 20px 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding-bottom: 80px; /* 为底部按钮留出空间 */
+}
+
+.no-more-data {
+  color: #999;
+  font-size: 14px;
+  padding: 10px;
+}
+
+/* 货号卡片 */
+.product-card {
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  border: 1px solid #ddd;
+  overflow: hidden;
+  transition: all 0.3s ease;
+  min-width: 0;
+  width: 100%;
+}
+
+.product-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.product-card.selected {
+  border-color: #409eff;
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+/* 卡片头部 */
+.card-header {
+  padding: 12px 16px;
+  background-color: #eef2ff;
+  border-bottom: 1px solid #d9d9d9;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.checkbox-wrapper {
+  width: 20px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.card-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.product-code {
+  font-weight: bold;
+  color: #333;
+  font-size: 14px;
+}
+
+.timestamp {
+  color: #666;
+  font-size: 12px;
+}
+
+/* 图片容器 */
+.image-container {
+  padding: 16px;
+}
+
+.image-row {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+  padding: 8px 0;
+}
+
+.image-item {
+  position: relative;
+  border-radius: 4px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.product-image {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
+  padding: 8px;
+}
+
+/* 固定底部按钮栏 */
+.fixed-bottom-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 16px 20px;
+  background-color: white;
+  border-top: 1px solid #ddd;
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 100;
+}
+
+.selected-info {
+  color: #666;
+  font-size: 14px;
+}
+
+.selected-count {
+  color: #409eff;
+  font-weight: bold;
+}
+
+.el-button {
+  padding: 12px 24px;
+  font-size: 16px;
+}
+
+/* 加载状态 */
+.loading-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40px 20px;
+  text-align: center;
+}
+
+/* 错误状态 */
+.error-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40px 20px;
+  text-align: center;
+}
+</style>

+ 9 - 1
frontend/src/router/index.ts

@@ -4,7 +4,7 @@ import Tools_800_Copy from "../components/Tools_800_Copy.vue";
 import ProductList from "../components/ProductList.vue";
 import ExternalPage from "../components/ExternalPage.vue";
 import CutoutPage from "../components/CutoutPage.vue";
-
+import ShadowRename from "../components/ShadowRename.vue";
 const routes:RouteRecordRaw[] = [
     {
         path:"/", //路径描述
@@ -40,6 +40,14 @@ const routes:RouteRecordRaw[] = [
         meta: {
             title: "批量抠图",hideBackButton: false // 如果需要隐藏返回按钮
         }
+    },
+    {
+        path: '/shadow_rename',
+        name: 'ShadowRename',
+        component: ShadowRename,
+        meta: {
+            title: "阴影图重命名",hideBackButton: false // 如果需要隐藏返回按钮
+        }
     }
 ]
 

+ 2 - 0
frontend/wailsjs/go/main/App.d.ts

@@ -18,4 +18,6 @@ export function OpenFolder(arg1:string):Promise<void>;
 
 export function RemoveBackgroundApp(arg1:Array<string>):Promise<string>;
 
+export function RenameFolderFileNameApp(arg1:Array<string>):Promise<Array<any>>;
+
 export function SelectDirectory():Promise<string>;

+ 4 - 0
frontend/wailsjs/go/main/App.js

@@ -34,6 +34,10 @@ export function RemoveBackgroundApp(arg1) {
   return window['go']['main']['App']['RemoveBackgroundApp'](arg1);
 }
 
+export function RenameFolderFileNameApp(arg1) {
+  return window['go']['main']['App']['RenameFolderFileNameApp'](arg1);
+}
+
 export function SelectDirectory() {
   return window['go']['main']['App']['SelectDirectory']();
 }

+ 85 - 0
frontend/wailsjs/go/models.ts

@@ -0,0 +1,85 @@
+export namespace handlers {
+	
+	export class ImageResult {
+	    goods_art_no: string;
+	    image_path: string;
+	    category: string;
+	    description: string;
+	    price: string;
+	    image_base64: string;
+	
+	    static createFrom(source: any = {}) {
+	        return new ImageResult(source);
+	    }
+	
+	    constructor(source: any = {}) {
+	        if ('string' === typeof source) source = JSON.parse(source);
+	        this.goods_art_no = source["goods_art_no"];
+	        this.image_path = source["image_path"];
+	        this.category = source["category"];
+	        this.description = source["description"];
+	        this.price = source["price"];
+	        this.image_base64 = source["image_base64"];
+	    }
+	}
+	export class PhotoRecordList {
+	    goods_art_no: string;
+	    action_time: string;
+	    items: string[];
+	
+	    static createFrom(source: any = {}) {
+	        return new PhotoRecordList(source);
+	    }
+	
+	    constructor(source: any = {}) {
+	        if ('string' === typeof source) source = JSON.parse(source);
+	        this.goods_art_no = source["goods_art_no"];
+	        this.action_time = source["action_time"];
+	        this.items = source["items"];
+	    }
+	}
+	export class PhotoRecordResponse {
+	    list: PhotoRecordList[];
+	    current_page: number;
+	    size: number;
+	    total_count: number;
+	    total_pages: number;
+	    has_prev: boolean;
+	    has_next: boolean;
+	
+	    static createFrom(source: any = {}) {
+	        return new PhotoRecordResponse(source);
+	    }
+	
+	    constructor(source: any = {}) {
+	        if ('string' === typeof source) source = JSON.parse(source);
+	        this.list = this.convertValues(source["list"], PhotoRecordList);
+	        this.current_page = source["current_page"];
+	        this.size = source["size"];
+	        this.total_count = source["total_count"];
+	        this.total_pages = source["total_pages"];
+	        this.has_prev = source["has_prev"];
+	        this.has_next = source["has_next"];
+	    }
+	
+		convertValues(a: any, classs: any, asMap: boolean = false): any {
+		    if (!a) {
+		        return a;
+		    }
+		    if (a.slice && a.map) {
+		        return (a as any[]).map(elem => this.convertValues(elem, classs));
+		    } else if ("object" === typeof a) {
+		        if (asMap) {
+		            for (const key of Object.keys(a)) {
+		                a[key] = new classs(a[key]);
+		            }
+		            return a;
+		        }
+		        return new classs(a);
+		    }
+		    return a;
+		}
+	}
+
+}
+

+ 42 - 1
handlers/camera_machine_handler.go

@@ -20,7 +20,8 @@ const (
 	// GetPhotoRecordUrl 获取照片记录
 	GetPhotoRecordUrl = "/get_photo_records"
 	// RemoveBackground 移除背景
-	RemoveBackground = "/remove_background"
+	RemoveBackground   = "/remove_background"
+	RenameShadowFolder = "/rename_shadow_folder"
 )
 
 type CameraMachineHandler struct {
@@ -257,3 +258,43 @@ func (t *CameraMachineHandler) RemoveBackground(goodsArtNos []string) (string, e
 	fmt.Printf("输出文件夹路径: %s\n", outputFolderPath)
 	return outputFolderPath, nil
 }
+
+// RenameFolderFileName 批量处理抠图操作
+func (t *CameraMachineHandler) RenameFolderFileName(goodsArtNos []string) ([]interface{}, error) {
+	t.handlerRequest = NewHandlerRequests(t.ctx, t.token, cameraMachineUrl)
+	postData := map[string]interface{}{}
+	postData["goods_art_nos"] = goodsArtNos
+	postData["token"] = t.token
+	request, err := t.handlerRequest.MakePostRequest(RenameShadowFolder, postData)
+	if err != nil {
+		return nil, err
+	}
+	// 将返回结果转换为 map 以提取 data 字段
+	resultMap, ok := request.(map[string]interface{})
+	if !ok {
+		return nil, fmt.Errorf("无法将响应转换为 map")
+	}
+
+	// 提取 data 字段
+	dataValue, exists := resultMap["data"]
+	if !exists {
+		return nil, fmt.Errorf("响应中不存在 data 字段")
+	}
+	// 解析 data 字段为 map
+	dataMap, ok := dataValue.(map[string]interface{})
+	if !ok {
+		return nil, fmt.Errorf("data 字段不是有效的 map 类型")
+	}
+
+	// 获取 output_folder 值
+	result, exists := dataMap["result"]
+	if !exists {
+		return nil, fmt.Errorf("data 字段中不存在 output_folder")
+	}
+	outputFolderPath, ok := result.([]interface{})
+	if !ok {
+		return nil, fmt.Errorf("类型错误")
+	}
+	fmt.Printf("输出文件夹路径: %s\n", outputFolderPath)
+	return outputFolderPath, nil
+}