Browse Source

处理数据

zhangyh 8 months ago
parent
commit
5749ee5f9d

+ 36 - 36
python/api.py

@@ -8,7 +8,7 @@ from utils.hlm_http_request import forward_request
 from sockets.socket_client import socket_manager
 from mcu.DeviceControl import DeviceControl
 import time
-from service.base_deal import  BaseDealImage
+# from service.base_deal import  BaseDealImage
 from databases import DeviceConfig,SqlQuery,CRUD
 
 @app.get("/")
@@ -103,41 +103,41 @@ async def forwardRequest(request: HlmForwardRequest):
         raise UnicornException(e)
 
 
-    @app.post('/handle_detail')
-    async def handle_detail(request: Request):
-
-        image_dir = "{}/data".format(os.getcwd())
-        baseDealImage = BaseDealImage(image_dir=image_dir)
-        result = baseDealImage.dealMoveImage(image_dir=image_dir, callback_func=None)
-
-
-
-
-        params = json.dump(request.query_params)
-        #{'image_dir': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18', 'image_order': '俯视,侧视,后跟,鞋底,内里', 'is_check_number': True, 'resize_image_view': '后跟', 'cutout_mode': '1', 'logo_path': '', 'special_goods_art_no_folder_line': '', 'is_use_excel': True, 'excel_path': '', 'is_check_color_is_all': True, 'assigned_page_dict': {}, 'temp_class': {'huilima-2': <class 'detail_template.huilima.detail_huilima2.DetailPicGet'>, 'huilima-3': <class 'detail_template.huilima.detail_huilima3.DetailPicGet'>, 'huilima-4': <class 'detail_template.huilima.detail_huilima4.DetailPicGet'>, 'huilima-1': <class 'detail_template.huilima.detail_huilima1.DetailPicGet'>}, 'temp_name': 'huilima-2', 'temp_name_list': ['huilima-2', 'huilima-3', 'huilima-4', 'huilima-1'], 'target_error_folder': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18/软件-生成详情错误'}
-
-        config_data = {
-            'image_dir': params['image_dir'],
-            'image_order': params['image_order'],
-            'is_check_number': params['is_check_number'],
-            'resize_image_view': params['resize_image_view'],
-            'cutout_mode': '1',
-            'logo_path': params['logo_path'],
-            'special_goods_art_no_folder_line': '',
-            'is_use_excel': params['is_use_excel'],
-            'excel_path': params['excel_path'],
-            'is_check_color_is_all': params['is_check_color_is_all'],
-            'assigned_page_dict': {},
-            'temp_class': {
-                'huilima-2': 'detail_template.huilima.detail_huilima2.DetailPicGet',
-                'huilima-3': 'detail_template.huilima.detail_huilima3.DetailPicGet',
-                'huilima-4': 'detail_template.huilima.detail_huilima4.DetailPicGet',
-                'huilima-1': 'detail_template.huilima.detail_huilima1.DetailPicGet'
-            },
-            'temp_name': 'huilima-2',
-            'temp_name_list': ['huilima-2', 'huilima-3', 'huilima-4', 'huilima-1'],
-            'target_error_folder': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18/软件-生成详情错误'
-        }
+    # @app.post('/handle_detail')
+    # async def handle_detail(request: Request):
+    #
+    #     image_dir = "{}/data".format(os.getcwd())
+    #     baseDealImage = BaseDealImage(image_dir=image_dir)
+    #     result = baseDealImage.dealMoveImage(image_dir=image_dir, callback_func=None)
+    #
+
+
+        #
+        # params = json.dump(request.query_params)
+        # #{'image_dir': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18', 'image_order': '俯视,侧视,后跟,鞋底,内里', 'is_check_number': True, 'resize_image_view': '后跟', 'cutout_mode': '1', 'logo_path': '', 'special_goods_art_no_folder_line': '', 'is_use_excel': True, 'excel_path': '', 'is_check_color_is_all': True, 'assigned_page_dict': {}, 'temp_class': {'huilima-2': <class 'detail_template.huilima.detail_huilima2.DetailPicGet'>, 'huilima-3': <class 'detail_template.huilima.detail_huilima3.DetailPicGet'>, 'huilima-4': <class 'detail_template.huilima.detail_huilima4.DetailPicGet'>, 'huilima-1': <class 'detail_template.huilima.detail_huilima1.DetailPicGet'>}, 'temp_name': 'huilima-2', 'temp_name_list': ['huilima-2', 'huilima-3', 'huilima-4', 'huilima-1'], 'target_error_folder': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18/软件-生成详情错误'}
+        #
+        # config_data = {
+        #     'image_dir': params['image_dir'],
+        #     'image_order': params['image_order'],
+        #     'is_check_number': params['is_check_number'],
+        #     'resize_image_view': params['resize_image_view'],
+        #     'cutout_mode': '1',
+        #     'logo_path': params['logo_path'],
+        #     'special_goods_art_no_folder_line': '',
+        #     'is_use_excel': params['is_use_excel'],
+        #     'excel_path': params['excel_path'],
+        #     'is_check_color_is_all': params['is_check_color_is_all'],
+        #     'assigned_page_dict': {},
+        #     'temp_class': {
+        #         'huilima-2': 'detail_template.huilima.detail_huilima2.DetailPicGet',
+        #         'huilima-3': 'detail_template.huilima.detail_huilima3.DetailPicGet',
+        #         'huilima-4': 'detail_template.huilima.detail_huilima4.DetailPicGet',
+        #         'huilima-1': 'detail_template.huilima.detail_huilima1.DetailPicGet'
+        #     },
+        #     'temp_name': 'huilima-2',
+        #     'temp_name_list': ['huilima-2', 'huilima-3', 'huilima-4', 'huilima-1'],
+        #     'target_error_folder': 'D:/phpstudy_pro/WWW/auto_photo/output/2024-11-18/软件-生成详情错误'
+        # }
 
 @app.post("/get_device_configs", description="获取可执行程序命令列表")
 def get_device_configs(params: ModelGetDeviceConfig):

+ 2 - 0
python/config.ini

@@ -23,6 +23,8 @@ backup_counts=3
 # 地址
 hlm_host=https://dev2.pubdata.cn
 
+project=红蜻蜓
+
 
 
 [mcu_config]

+ 16 - 2
python/databases.py

@@ -43,8 +43,22 @@ class CRUD:
         session.refresh(db_obj)
         return db_obj
 
-    def read(self, session: Session, obj_id: int):
-        return session.get(self.model, obj_id)
+    def read(
+            self,
+            session: Session,
+            conditions: Optional[Dict] = None,
+            order_by: Optional[str] = None,
+            ascending: bool = True,
+    ):
+        query = select(self.model)
+        if conditions:
+            query = query.where(and_(*(getattr(self.model, key) == value for key, value in conditions.items())))
+        if order_by:
+            if ascending:
+                query = query.order_by(asc(getattr(self.model, order_by)))
+            else:
+                query = query.order_by(desc(getattr(self.model, order_by)))
+        return session.exec(query).first()
 
     def read_all(
         self,

+ 133 - 20
python/service/base_deal.py

@@ -1,5 +1,5 @@
 import json
-from module_generate_goods_art_no_table import GenerateGoodsArtNoTable
+# from module_generate_goods_art_no_table import GenerateGoodsArtNoTable
 
 from grenerate_main_image_test import GeneratePic
 from threading import Lock
@@ -14,12 +14,15 @@ from data import DataModeAutoDealPics
 import time
 from image_pic_deal import OnePicDeal
 
-from natsort import natsorted
+from natsort import natsorted,ns
 import os
 import  shutil
 import exifread
 import datetime
 from databases import DeviceConfig,SqlQuery,CRUD
+from model.photo_record import PhotoRecord
+import requests
+import copy
 
 """
 照片自动货号匹配 将图片放置在指定文件夹下,并自动对应不同的货号进行整理
@@ -31,7 +34,6 @@ _Type = ['.png', '.PNG', '.jpg', '.JPG', '.gif', '.GIF', ".jpge", ".JPGE"]
 class BaseDealImage(object):
     def __init__(self, image_dir=None):
         self.goods_images_count_dict = defaultdict(int)
-        self.dataModeMatchPhoto = DataModeMatchPhoto()
         # 数据模型
         self.data_mode_auto_deal_pics = DataModeAutoDealPics()
         self.image_dir = image_dir
@@ -495,7 +497,7 @@ class BaseDealImage(object):
 
         if goods_art_no_list:
             # goods_art_no_dict 文件夹与货号的字典
-            goods_art_no_dict = self.dataModeMatchPhoto.get_data_from_hqt_with_goods_art_no(
+            goods_art_no_dict = self.get_data_from_hqt_with_goods_art_no(
                 goods_art_no_list=goods_art_no_list)
             for goods_art_no_folder_data in all_goods_art_no_folder_data:
                 if goods_art_no_folder_data["label"] != "待处理":
@@ -815,15 +817,8 @@ class BaseDealImage(object):
 
         session = SqlQuery()
         configModel = CRUD(DeviceConfig)
-        configList = configModel.read_all(
-            session, conditions={"mode_type": mode_type}, order_by="action_index", ascending=True
-        )
-
-        result = (
-            self.session.query(PhotoRecord)
-            .filter(PhotoRecord.photo_create_time == datetime_obj)
-            .order_by(PhotoRecord.id.desc())
-            .first()
+        result = configModel.read(
+            session, conditions={"photo_create_time": datetime_obj}, order_by="id", ascending=True
         )
         if result:
             return result.goods_art_no, result.image_index, result.image_deal_mode
@@ -831,6 +826,128 @@ class BaseDealImage(object):
             return None
 
 
+
+    def get_goods_art_no_info(self, numbers_list=None, goods_art_list=None, headers=None):
+        # 获取商品基础信息,入参为商品的编号
+        url = "{domain}/api/backend/goods_client/goods_query".format(
+            domain=settings.APP_HOST
+        )
+        data = {
+            'goods_art_list': goods_art_list
+        }
+        data = json.dumps(data)
+        _s = requests.session().post(url=url, data=data, headers=headers)
+        response_data = _s.json()
+
+
+        goods_number_data = {}
+        # ["", "", "", "", "", "", "", "", "", "", "", ]
+        if "data" not in response_data:
+            return {}
+
+        for data in response_data["data"]:
+            goods_number_data[data["goods_art_no"]] = {}
+            goods_number_data[data["goods_art_no"]]["商品货号"] = data["goods_art_no"].upper()
+            goods_number_data[data["goods_art_no"]]["款号"] = data["goods_number"].upper()
+            goods_number_data[data["goods_art_no"]]["商品面料"] = data["fabric"]
+            goods_number_data[data["goods_art_no"]]["商品内里"] = data["lining"]
+            goods_number_data[data["goods_art_no"]]["商品鞋底"] = data["sole"]
+            goods_number_data[data["goods_art_no"]]["鞋垫"] = data["insole"]
+            goods_number_data[data["goods_art_no"]]["颜色名称"] = data["color"]
+
+        return goods_number_data
+
+    def get_data_from_hqt_with_goods_art_no(self, goods_art_no_list):
+        _goods_art_no_list = copy.deepcopy(goods_art_no_list)
+        _list = []
+        # 单次请求数少于20个
+        goods_art_no_dict = {}
+
+        while _goods_art_no_list:
+            goods_art_no = _goods_art_no_list.pop()
+
+            _list.append(goods_art_no)
+            if len(_list) == 20 or len(_goods_art_no_list) == 0:
+                online_goods_art_data = self.get_goods_art_no_info(
+                    goods_art_list=_list
+                )
+                if online_goods_art_data:
+                    for _goods_art_no in online_goods_art_data:
+                        goods_art_no_dict[_goods_art_no] = online_goods_art_data[
+                            _goods_art_no
+                        ]
+                _list = []
+        return goods_art_no_dict
+
+
+
+
+    def get_goods_art_no_info(self, numbers_list=None, goods_art_list=None, headers=None):
+        # 获取商品基础信息,入参为商品的编号
+        url = "{domain}/api/backend/goods_client/goods_query".format(
+            domain=settings.APP_HOST
+        )
+        data = {
+            'goods_art_list': goods_art_list
+        }
+
+
+        data = json.dumps(data)
+
+
+        _s = requests.session().post(url=url, data=data, headers=headers)
+        # _s = self.s.get(url=url, params=params, headers=settings.Headers)
+        response_data = _s.json()
+
+
+        goods_number_data = {}
+        # ["", "", "", "", "", "", "", "", "", "", "", ]
+        if "data" not in response_data:
+            return {}
+
+        for data in response_data["data"]:
+            goods_number_data[data["goods_art_no"]] = {}
+            goods_number_data[data["goods_art_no"]]["商品货号"] = data["goods_art_no"].upper()
+            goods_number_data[data["goods_art_no"]]["款号"] = data["goods_number"].upper()
+            goods_number_data[data["goods_art_no"]]["商品面料"] = data["fabric"]
+            goods_number_data[data["goods_art_no"]]["商品内里"] = data["lining"]
+            goods_number_data[data["goods_art_no"]]["商品鞋底"] = data["sole"]
+            goods_number_data[data["goods_art_no"]]["鞋垫"] = data["insole"]
+            goods_number_data[data["goods_art_no"]]["颜色名称"] = data["color"]
+
+        return goods_number_data
+
+
+
+
+    def get_data_from_hqt(self, goods_number_list):
+        _goods_number_list = copy.deepcopy(goods_number_list)
+        _list = []
+        # 单次请求数少于20个
+        goods_number_dict = {}
+
+        while _goods_number_list:
+            goods_art_no = _goods_number_list.pop()
+            if "NUM" in goods_art_no:
+                goods_art_no = goods_art_no.replace("NUM", "")
+
+            _list.append(goods_art_no)
+            if len(_list) == 20 or len(_goods_number_list) == 0:
+                online_goods_art_data = self.get_goods_art_no_info(
+                    numbers_list=_list
+                )
+                if online_goods_art_data:
+                    for number in online_goods_art_data:
+                        goods_number_dict["NUM" + number] = online_goods_art_data[
+                            number
+                        ]
+                _list = []
+        return goods_number_dict
+
+
+
+
+
     def dealMoveImage(self, image_dir: str, callback_func=None) -> dict:
         if not self.check_path(image_dir=image_dir + "/历史"):
             return {'code': 1, 'msg': '文件夹创建失败', 'data': {}}
@@ -851,7 +968,7 @@ class BaseDealImage(object):
             date_time_original = self.get_date_time_original(file_path)  # 获取照片拍照时间
             if date_time_original:
                 # 基于照片的时间,与数据库匹配goods_art_no
-                _data = self.dataModeMatchPhoto.get_goods_art_no(date_time_original)
+                _data = self.get_goods_art_no(date_time_original)
                 if _data:
                     # 能匹配上数据库
                     goods_art_no, image_index, image_deal_mode = _data
@@ -875,10 +992,6 @@ class BaseDealImage(object):
                 shutil.move(file_path, image_dir + "/历史/" + file)
 
         if not original_photo_list:
-            # self.show_progress_detail("没有任何匹配的图片~")
-            if callback_func:
-                callback_func('没有任何匹配的图片')
-            # self.set_state(state_value=2)
             return {"code": 1, "msg": "没有任何匹配的图片", 'data': {}}
 
         if settings.PROJECT == "红蜻蜓":
@@ -888,7 +1001,7 @@ class BaseDealImage(object):
             goods_art_no_list = [x for x in goods_art_no_list if "NUM" not in x]
 
             if goods_art_no_list:
-                goods_art_no_dict = self.dataModeMatchPhoto.get_data_from_hqt_with_goods_art_no(
+                goods_art_no_dict = self.get_data_from_hqt_with_goods_art_no(
                     goods_art_no_list=goods_art_no_list)
 
                 for i in original_photo_list:
@@ -902,7 +1015,7 @@ class BaseDealImage(object):
             goods_number_list = [x for x in goods_number_list if "NUM" in x]
 
             if goods_number_list:
-                goods_number_dict = self.dataModeMatchPhoto.get_data_from_hqt(goods_number_list=goods_number_list)
+                goods_number_dict = self.get_data_from_hqt(goods_number_list=goods_number_list)
                 for i in original_photo_list:
                     if i["goods_art_no"] in goods_number_dict:
                         i["real_goods_number"] = i["goods_art_no"]

+ 1 - 2
python/service/deal_cutout.py

@@ -2,9 +2,8 @@ import os.path
 import time
 from concurrent.futures import ThreadPoolExecutor, wait
 import threading
-from import_qt_mode import *
 
-from module.cutout_mode.deal_one_image import DealOneImage, DealOneImageBeforehand
+from deal_one_image import DealOneImage, DealOneImageBeforehand
 from module.log.log import MyLogger
 from module.online_request.module_online_data import GetOnlineDataHLM
 

+ 423 - 0
python/service/deal_one_image.py

@@ -0,0 +1,423 @@
+import copy
+import os.path
+# from module.other.module_online_data import GetOnlineData
+from module.online_request.module_online_data import GetOnlineDataHLM
+
+import time
+from module.log.log import MyLogger
+
+import os
+from PIL import Image
+# from module.other.remove_bg_ali import RemoveBgALi
+from module.base_mode.remove_bg_pixian import RemoveBgPiXian
+from module.base_mode.remove_bg_ali import RemoveBgALi
+from module.base_mode.remove_bg_ali import Picture
+import cv2
+import numpy as np
+import settings
+import math
+
+class Base(object):
+    def __init__(self, image_data, lock, windows, num):
+        self.lock = lock
+        self.windows = windows
+        self.image_data = image_data
+        self.num = num
+        self.get_online_data = GetOnlineDataHLM()
+        self.file_path = image_data["file_path"]
+        self.file_name = image_data["file_name"]
+        self.file = os.path.split(self.file_path)[1]
+        self.is_once_data = {}
+        self.logger = MyLogger().logger
+
+    def add_log(self, text, _type="info"):
+        self.logger.info("第{}个,图片名称:{},内容:{}".format(self.num, self.file, text))
+
+    def send_info(self, text="", is_success=None, _type="show_info", need_point_return=False):
+        with self.lock:
+            if is_success is not None:
+                if is_success:
+                    self.windows.remaining_times = self.windows.remaining_times - 1
+                else:
+                    # 分数返回
+                    if need_point_return:
+                        # 分数返回
+                        if self.is_once("add_point"):
+                            print("第{}个,图片名称:{},内容:{}".format(self.num, self.file, "扣分返回"))
+                            self.dispose_point(_type="add")
+                            self.windows.remaining_times = self.windows.remaining_times + 1
+
+            if text:
+                data = {"_type": _type,
+                        "data": text,
+                        }
+                self.windows.send_sign(data)
+
+    def refresh_times(self, cumulative_frequency_times_change):
+        if cumulative_frequency_times_change > 0:
+            self.windows.remaining_times = self.windows.remaining_times - 1
+
+    def check_path(self, _path):
+        if not os.path.exists(_path):
+            os.mkdir(_path)
+        return True
+
+    def is_once(self, key):
+        if key not in self.is_once_data:
+            self.is_once_data[key] = 0
+            return True
+        return False
+
+    def dispose_point(self, _type):
+        n = 3
+        while n:
+            n -= 1
+            try:
+                _r = self.get_online_data.dispose_point(_type)
+                balance = _r["data"]["balance"]
+                return True
+            except:
+                time.sleep(0.5)
+                continue
+
+        return False
+
+
+class DealOneImage(Base):
+    def __init__(self, image_data, lock, windows, num):
+        super().__init__(image_data, lock, windows, num)
+        self.image_data = image_data
+        self.lock = lock
+        self.windows = windows
+        self.num = num
+        self.file_path = image_data["file_path"]
+        self.file = os.path.split(self.file_path)[1]
+        self.r_pixian = RemoveBgPiXian()
+        self.file_name = image_data["file_name"]
+        self.out_path = image_data["out_path"]
+
+    def run(self):
+        # 直接调用抠图
+        # 1、增加获取key,2、key需要加密、3、429报错 重试再来拿一个KEY
+        self.add_log("开始处理")
+        self.send_info(text="{} 处理中".format(self.file_name))
+
+        if self.windows.remaining_times <= 0:
+            self.send_info(text="次数不足,处理失败", is_success=False)
+            return
+
+        # 检查图片上传是否有结束
+        n = 60
+        while 1:
+            if self.windows.state != 1:
+                return
+            n -= 1
+            if self.file_path in self.windows.upload_pic_dict:
+                break
+
+            else:
+                time.sleep(1)
+                if n <= 0:
+                    self.send_info(text="{} 处理超时", is_success=False)
+                    return
+                continue
+
+        s = time.time()
+        # print(self.upload_pic_dict[file_path])
+        _flag = self.windows.upload_pic_dict[self.file_path]["flag"]
+        if not _flag:
+            self.add_log("未查到上传的图片地址")
+            self.send_info(text="{} 上传错误", is_success=False)
+            return
+
+        image_deal_info = self.windows.upload_pic_dict[self.file_path]["image_deal_info"]
+        original_im = self.windows.upload_pic_dict[self.file_path]["_im"]
+
+        self.add_log("抠图中")
+
+        with self.lock:
+            self.windows.is_upload_pic_num -= 1
+
+        try:
+            balance = self.get_online_data.get_cutout_image_times()["balance"]
+            self.add_log("查询balance:{}成功".format(balance))
+            if balance <= 0:
+                self.add_log("次数不足,处理失败")
+                self.send_info(text="次数不足,处理失败", is_success=False)
+                return
+        except:
+            self.add_log("查询balance失败")
+            self.send_info(text="查询balance失败", is_success=False)
+            return
+
+        n = 0
+        while 1:
+            # 获取key
+            if self.windows.state != 1:
+                return
+            n += 1
+            data = self.get_online_data.get_key_secret()
+            key = (data["api_info"]["api_key"], data["api_info"]["api_serect"])
+            self.add_log("查询key成功")
+
+            if not key:
+                _data = {"text": "出错/超时",
+                         "info": "多次获取key失败",
+                         }
+                self.add_log(text="多次获取key失败")
+                self.send_info(text="{} 处理失败,请联系管理员".format(self.file_name), is_success=False)
+                return
+
+            if self.is_once("sub_point"):
+                # 调用扣分
+                with self.lock:
+
+                    self.refresh_times(cumulative_frequency_times_change=1)
+                    f = self.dispose_point(_type="sub")
+                if not f:
+                    self.add_log(text="多次获取调用余额扣减失败")
+                    self.send_info(text="多次获取调用余额扣减失败", is_success=False)
+                    return
+
+            pixian_cutout_data = self.r_pixian.run_by_image_im(original_im, key)
+
+            if pixian_cutout_data["status_code"] == 200:
+                second_cut_image = pixian_cutout_data["im"]
+
+                self.add_log(text="调用抠图完成")
+                break
+
+            elif pixian_cutout_data["status_code"] == 402:
+                if n >= 2:
+                    self.add_log(text="多次抠图失败:{}".format(pixian_cutout_data["status_code"]))
+                    self.send_info(text="处理失败,请联系管理员", is_success=False, need_point_return=True)
+                    if self.is_once("余额不足报错"):
+                        # 余额不足报错,钉钉消息通知
+                        self.get_online_data.send_message("Pixian:{} 余额不足".format(key))
+                        pass
+                    return
+                self.add_log(text="抠图失败:{},延迟6秒".format(pixian_cutout_data["status_code"]))
+                time.sleep(6)
+                continue
+
+            elif pixian_cutout_data["status_code"] == 429:
+                if n >= 2:
+                    self.add_log(text="多次抠图失败:{}".format(pixian_cutout_data["status_code"]))
+                    self.send_info(text="处理失败,请联系管理员", is_success=False, need_point_return=True)
+                    return
+
+                self.add_log(text="{}抠图失败:{},延迟10秒".format(self.file_name, pixian_cutout_data["status_code"]))
+                time.sleep(10)
+                continue
+            else:
+                self.send_info(text="{} 处理失败,请联系管理员".format(self.file_name), is_success=False, need_point_return=True)
+                if "message" in pixian_cutout_data:
+                    text = "抠图异常,code:{},message:{}".format(pixian_cutout_data["status_code"],
+                                                            pixian_cutout_data["message"])
+                else:
+                    text = "抠图异常,code:{}".format(pixian_cutout_data["status_code"])
+                self.add_log(text)
+                return
+
+        # 拼接处理
+        # print("耗时1:", time.time() - s)
+
+        try:
+            if image_deal_info["二次抠图是否缩放"]:
+                # print("图片尺寸还原")
+                self.add_log(text="图片尺寸进行还原")
+                original_im = image_deal_info["抠图扩边后PIL对象"]
+                second_cut_image = self.picture_resize_to_original(second_cut_image, original_im)
+            # 创建空白图片并粘贴回去
+            _img_im = Image.new(mode="RGBA", size=image_deal_info["原始图片大小"], color=(0, 0, 0, 0))
+            _img_im.paste(second_cut_image, box=(image_deal_info["抠图扩边后位置"][0], image_deal_info["抠图扩边后位置"][1]))
+            _img_im.save(self.out_path)
+
+            self.send_info(text="{} 抠图已完成".format(self.file_name), is_success=True)
+            return self.file_path
+
+        except BaseException as e:
+            # print(e)
+            text = "{} 图片处理错误,代码44".format(e)
+            self.add_log(text)
+            self.send_info(text=text, is_success=False, need_point_return=True)
+            return
+
+    def picture_resize_to_original(self, _img, original_im):
+        """
+
+        Parameters
+        ----------
+        _img 需要还原的PIL对象
+        original_im 原图对象
+
+        Returns
+        -------
+
+        """
+
+        # 将抠图结果转成mask
+        # 将抠图结果放大到原始图大小
+        _img = _img.resize(original_im.size,resample=1)
+        new_big_mask = Image.new('RGB', _img.size, (0, 0, 0))
+        white = Image.new('RGB', _img.size, (255, 255, 255))
+        new_big_mask.paste(white, mask=_img.split()[3])
+
+        # ---------制作选区缩小的mask
+        mask = cv2.cvtColor(np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY)  # 将PIL 格式转换为 CV对象
+        mask[mask != 255] = 0
+        # 黑白反转
+        # mask = 255 - mask
+        # 选区缩小10
+        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
+        erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
+
+        # -------再进行抠图处理
+        mask = Image.fromarray(cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA))  # CV 对象转 PIL
+        transparent_im = Image.new('RGBA', original_im.size, (0, 0, 0, 0))
+        transparent_im.paste(original_im, (0, 0), mask.convert('L'))
+        # 上述抠图结果进行拼接
+        _img.paste(transparent_im, (0, 0), transparent_im)
+
+        return _img
+
+
+class DealOneImageBeforehand(Base):
+    def __init__(self, image_data, lock, windows, num):
+        super().__init__(image_data, lock, windows, num)
+        self.image_data = image_data
+        self.lock = lock
+        self.windows = windows
+        self.get_online_data = GetOnlineDataHLM()
+        self.num = num
+        self.file_path = image_data["file_path"]
+        self.file = os.path.split(self.file_path)[1]
+        self.r_ali = RemoveBgALi()
+        self.file_name = image_data["file_name"]
+
+    def run(self):
+        while 1:
+            if self.windows.state != 1:
+                return
+
+            with self.lock:
+                if self.windows.is_upload_pic_num >= 4:
+                    f = False
+                else:
+                    self.windows.is_upload_pic_num += 1
+                    f = True
+            if f:
+                break
+            else:
+                time.sleep(1)
+                continue
+        image_deal_info = {}
+        try:
+            cut_image, image_deal_info = self.get_image_cut()
+
+            f = True
+            url = ""
+        except BaseException as e:
+            f = False
+            url = ""
+            cut_image = ""
+
+        with self.lock:
+            self.windows.upload_pic_dict[self.file_path] = {"url": url,
+                                                            "image_deal_info": image_deal_info,
+                                                            "flag": f,
+                                                            "_im": cut_image, }
+
+    def get_image_cut(self):
+        original_pic = Picture(self.file_path)
+        original_pic.im = self.get_image_orientation(original_pic.im)
+        original_pic.x, original_pic.y = original_pic.im.size
+
+        original_pic.im = original_pic.im.convert("RGB")
+        image_deal_info = {}
+        image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
+
+        # 原始图过小,则不需要使用阿里进行预处理
+        if original_pic.x * original_pic.y < 1000000:
+            cut_image = original_pic.im
+            image_deal_info["抠图扩边后图片大小"] = cut_image.size
+            image_deal_info["二次抠图是否缩放"] = False
+            image_deal_info["抠图扩边后位置"] = (0, 0, original_pic.x, original_pic.y)
+        else:
+            self.add_log("开始预抠图处理")
+            cut_image = self.r_ali.get_image_cut(file_path=None, out_file_path=None, original_im=original_pic.im)
+
+            self.add_log("预抠图处理结束")
+
+            x1, y1, x2, y2 = cut_image.getbbox()
+            image_deal_info["鞋子原始位置"] = (x1, y1, x2, y2)
+            o_w, o_h = cut_image.size
+            image_deal_info["鞋子原始抠图后大小"] = (o_w, o_h)
+            # 扩边处理
+            _w, _h = x2 - x1, y2 - y1
+            out_px = 0.025
+            _w, _h = int(out_px * _w), int(out_px * _h)
+            n_x1, n_y1, n_x2, n_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h
+            if n_x1 < 0:
+                n_x1 = 0
+            if n_y1 < 0:
+                n_y1 = 0
+            if n_x2 > o_w:
+                n_x2 = o_w
+            if n_y2 > o_h:
+                n_y2 = o_h
+            image_deal_info["抠图扩边后位置"] = (n_x1, n_y1, n_x2, n_y2)
+            cut_image = original_pic.im.crop(image_deal_info["抠图扩边后位置"])
+
+            image_deal_info["抠图扩边后图片大小"] = cut_image.size
+            x, y = image_deal_info["抠图扩边后图片大小"]
+
+            # 12000000
+            max_size = settings.MAX_PIXIAN_SIZE
+            if x * y > max_size:
+                r = math.sqrt(max_size) / math.sqrt(x * y)
+                r = r*0.9
+                size = (int(x * r), int(y * r))
+                # print("图片:{} pixian触发二次缩放,原尺寸{}*{},新尺寸:{}".format(self.file_name, x, y, size))
+                self.add_log(text="图片进行压缩,压缩前:{},压缩后:{}".format(image_deal_info["抠图扩边后图片大小"], size))
+                image_deal_info["抠图扩边后PIL对象"] = copy.deepcopy(cut_image)
+                cut_image = cut_image.resize(size=size,resample=1)
+                # print(cut_image.size)
+                # print(image_deal_info["抠图扩边后PIL对象"].size)
+                image_deal_info["二次抠图是否缩放"] = True
+            else:
+                image_deal_info["二次抠图是否缩放"] = False
+        return cut_image, image_deal_info
+
+    def get_image_orientation(self, img):
+        # 获取EXIF数据
+        exif = img._getexif()
+        if exif is not None:
+            # EXIF标签274对应的是Orientation
+            orientation = exif.get(0x0112)
+            if orientation == 2:
+                # 水平翻转
+                img = img.transpose(Image.FLIP_LEFT_RIGHT)
+            elif orientation == 3:
+                # 旋转180度
+                img = img.rotate(180, expand=True)
+            elif orientation == 4:
+                # 垂直翻转
+                img = img.transpose(Image.FLIP_TOP_BOTTOM)
+            elif orientation == 5:
+                # 水平翻转后顺时针旋转90度
+                img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
+            elif orientation == 6:
+                # 顺时针旋转90度
+                img = img.transpose(Image.ROTATE_270)
+            elif orientation == 7:
+                # 水平翻转后逆时针旋转90度
+                img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90)
+            elif orientation == 8:
+                # 逆时针旋转90度
+                img = img.transpose(Image.ROTATE_90)
+        else:
+            print("没有EXIF数据或没有方向信息")
+            orientation = 1
+
+        return img

+ 138 - 0
python/service/excel_base_func.py

@@ -0,0 +1,138 @@
+from PySide6.QtWidgets import QApplication
+from PIL import Image
+import settings
+import time
+import os
+
+def get_clip_image(image_path):
+    try:
+        cb = QApplication.clipboard()
+        if cb.mimeData().hasImage():
+            qt_img = cb.image()
+            pil_img = Image.fromqimage(qt_img)  # 转换为PIL图像
+            if pil_img.width > 10:
+                pil_img = pil_img.convert("RGB")
+                pil_img.save(image_path)
+                print("图片剪切板保存成功:{}".format(image_path))
+
+            return True
+        return False
+    except BaseException as e:
+        print(e)
+        settings.logger.info("获取剪切板图片异常:{}".format(e))
+        return False
+
+
+def get_one_cell_shape_image(shape, image_out_path):
+    try:
+        flag = True
+        _address = shape.TopLeftCell.Address
+        print("_address", _address)  # $E$2
+        shape.LockAspectRatio = True
+        old_w = shape.Width
+        shape.Width = 500
+        print("shape.Copy")
+        shape.Copy()
+
+        time.sleep(0.1)
+
+        if not get_clip_image(image_out_path):
+            flag = False
+        shape.Width = old_w
+    except BaseException as e:
+        print("get_one_cell_shape_image", e)
+        return False
+
+    return flag
+
+
+def add_pic_with_wps(sheet, row, column, image_path, pic_w=35):
+    cell = sheet.Cells(row, column)
+    cell_value = cell.Value
+    if cell_value:
+        if "DISPIMG" in cell_value:
+            print("已有图片")
+            return
+    cell.ColumnWidth = 10
+    cell.RowHeight = 42
+    im = Image.open(image_path)
+    w = pic_w
+    h = int((im.height * w) / im.width)
+
+    # pic = sheet.Shapes.AddPicture(FileName=image_path, LinkToFile=False, SaveWithDocument=True,
+    #                               Left=cell.Left + 2, Top=cell.Top + 2, Width=-1, Height=-1)
+    # https://learn.microsoft.com/zh-tw/office/vba/api/excel.shapes.addpicture
+    pic = sheet.Shapes.AddPicture(image_path, 0, 1, cell.Left + 2, cell.Top + 2, w, h)
+    pic.LockAspectRatio = True
+    pic.Placement = 1  # 随单元格大小变化
+    # os.remove(image_path)
+    # r = pic.ShapeRange
+    # r.LockAspectRatio = True
+
+
+def add_pic_with_office(sheet, row, column, image_path, pic_w=35):
+    # self.add_pic_with_wps(sheet, row, column, image_path, pic_w=35)
+    # return
+    cell = sheet.Cells(row, column)
+    cell_value = cell.Value
+    if cell_value:
+        if "DISPIMG" in cell_value:
+            print("已有图片")
+            return
+
+    cell.ColumnWidth = 10
+    cell.RowHeight = 42
+
+    im = Image.open(image_path)
+    w = pic_w
+    h = int((im.height * w) / im.width)
+
+    pic_shape = sheet.Shapes.AddShape(1, cell.Left + 2, cell.Top + 2, w, h)
+    pic_shape.Fill.UserPicture(image_path)
+    pic_shape.Line.Weight = 0
+    pic_shape.Placement = 1  # 随单元格大小变化
+
+
+def get_all_row_sheet_images(sheet):
+    """
+    返回每行中是否存在形状,第二行为index=0
+    """
+    row_address_data_dict = {}
+
+    for i, shape in enumerate(sheet.Shapes):
+        try:
+            _address = shape.TopLeftCell.Address
+            _row = 0
+            if isinstance(_address, str):
+                _row = _address.split("$")[2]
+                _row = int(_row) - 2
+            if _row == -1:
+                break
+            row_address_data_dict[_row] = shape
+        except BaseException as e:
+            print(e)
+            break
+
+    return row_address_data_dict
+
+
+def save_log_text():
+    # 移除30条以外的文件
+    f_list = []
+    for new_file_name in os.listdir(r"log\upload_log"):
+        _file_path = "{}\{}".format(r"log\upload_log", new_file_name)
+        f_list.append({"file_name": new_file_name,
+                       "file_path": _file_path,
+                       "create_time": os.path.getctime(_file_path)
+                       })
+
+    if f_list:
+        f_list.sort(key=lambda x: x["create_time"], reverse=True)
+        while 1:
+            if len(f_list) > 30:
+                del_file_dict = f_list.pop()
+                del_file_path = "{}\{}".format(os.getcwd(), del_file_dict["file_path"])
+                os.remove(del_file_path)
+            else:
+                break
+    pass

+ 1 - 2
python/service/grenerate_main_image_test.py

@@ -1,11 +1,10 @@
 import os
 import copy
 import time
-from module.view_control.generate_main_image.image_deal_base_func import *
+from image_deal_base_func import *
 from PIL import Image, ImageDraw
 from blend_modes import multiply
 import os
-from module.log.log import MyLogger
 import settings
 from functools import wraps
 

+ 46 - 0
python/service/handle_detail.py

@@ -0,0 +1,46 @@
+from functools import partial
+import os
+from upload_pic import UploadPic  # 假设 UploadPic 类在 upload_pic 模块中
+from run_main import RunMain
+
+class HandleDetail():
+
+    def __int__(self):
+        self.run_main = RunMain(windows=self)
+
+    def deal_run_end_sign(self, config_data: dict):
+        if config_data["sign_text"] == "开始抠图":
+            # 先做整体校验
+            self.run_main.check_for_cutout_image_first_call_back(return_data=self.run_main.check_before_cutout,
+                                                                 config_data=config_data)
+
+        print(config_data)
+        if config_data["sign_text"] == "已结束抠图处理":
+            if config_data["detail_is_enable"]:
+                # 先做整体校验
+                # temp_name=self.last_temp, temp_name_list=self.temp_list
+                func = partial(self.run_main.check_before_detail, config_data=config_data)
+                self.do_thread_run(func=func,
+                                   call_back=self.run_main.check_for_detail_first_call_back,
+                                   time_out=30,
+                                   is_show_mask=False)
+            else:
+                self.set_state(state_value=2)
+
+        if config_data["sign_text"] == "已结束详情处理":
+            if config_data["upload_is_enable"]:
+                to_deal_dir = "{}/软件-详情图生成".format(config_data["image_dir"])
+                print("to_deal_dir", to_deal_dir)
+                if os.path.exists(to_deal_dir):
+                    self.upload_pic = UploadPic(windows=self, to_deal_dir=to_deal_dir, config_data=config_data)
+                    self.upload_pic.run_end_sign.connect(self.deal_run_end_sign)
+                    self.upload_pic.show_progress_detail_sign.connect(self.show_progress_detail)
+                    self.upload_pic.run()
+                    # threading.Thread(target=self.upload_pic.run_by_thread, args=()).start()
+                else:
+                    self.set_state(state_value=2)
+            else:
+                self.set_state(state_value=2)
+
+        if config_data["sign_text"] == "结束":
+            self.set_state(state_value=2)

+ 264 - 0
python/service/image_deal_base_func.py

@@ -0,0 +1,264 @@
+import cv2
+import numpy as np
+from PIL import Image, ImageEnhance, ImageFilter, ImageOps
+import settings
+
+# 锐化图片
+def sharpen_image(img, factor=1.0):
+    # 创建一个ImageEnhance对象
+    enhancer = ImageEnhance.Sharpness(img)
+    # 应用增强,值为0.0给出模糊图像,1.0给出原始图像,大于1.0给出锐化效果
+    # 调整这个值来增加或减少锐化的程度
+    sharp_img = enhancer.enhance(factor)
+    return sharp_img
+
+
+def to_resize(_im, width=None, high=None) -> Image:
+    _im_x, _im_y = _im.size
+    if width and high:
+        if _im_x >= _im_y:
+            high = None
+        else:
+            width = None
+    if width:
+        re_x = int(width)
+        re_y = int(_im_y * re_x / _im_x)
+    else:
+        re_y = int(high)
+        re_x = int(_im_x * re_y / _im_y)
+    _im = _im.resize((re_x, re_y),resample=settings.RESIZE_IMAGE_MODE)
+    return _im
+
+
+def pil_to_cv2(pil_image):
+    # 将 PIL 图像转换为 RGB 或 RGBA 格式
+    if pil_image.mode != 'RGBA':
+        pil_image = pil_image.convert('RGBA')
+    # 将 PIL 图像转换为 numpy 数组
+    cv2_image = np.array(pil_image)
+    # 由于 PIL 的颜色顺序是 RGB,而 OpenCV 的颜色顺序是 BGR,因此需要交换颜色通道
+    cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_RGBA2BGRA)
+    return cv2_image
+
+
+def cv2_to_pil(cv_img):
+    return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
+
+
+def get_mini_crop_img(img):
+    old_x, old_y = img.size
+    x1, y1, x2, y2 = img.getbbox()
+
+    goods_w, goods_h = x2 - x1, y2 - y1
+    _w, _h = int(goods_w / 10), int(goods_h / 10)  # 上下左右扩展位置
+    new_x1, new_y1, new_x2, new_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h  # 防止超限
+    new_x1 = 0 if new_x1 < 0 else new_x1
+    new_y1 = 0 if new_y1 < 0 else new_y1
+    new_x2 = old_x if new_x2 > old_x else new_x2
+    new_y2 = old_y if new_y2 > old_y else new_y2
+    img = img.crop((new_x1, new_y1, new_x2, new_y2))  # 切图
+    box = (new_x1, new_y1, new_x2, new_y2)
+    return img, box
+
+def expand_or_shrink_mask(pil_image, expansion_radius=5, iterations=1, blur_radius=0):
+    """
+    对输入的PIL黑白图像(掩膜)进行膨胀或腐蚀操作,以扩大或缩小前景区域。
+
+    :param pil_image: 输入的PIL黑白图像对象
+    :param expansion_radius: 结构元素大小,默认是一个3x3的小正方形;负值表示收缩
+    :param iterations: 操作迭代次数,默认为1次
+    :param blur_radius: 高斯模糊的半径,默认不应用模糊
+    :return: 修改后的PIL黑白图像对象
+    """
+
+    # 将PIL图像转换为numpy数组,并确保其为8位无符号整数类型
+    img_np = np.array(pil_image).astype(np.uint8)
+
+    # 如果不是二值图像,则应用阈值处理
+    if len(np.unique(img_np)) > 2:  # 检查是否为二值图像
+        _, img_np = cv2.threshold(img_np, 127, 255, cv2.THRESH_BINARY)
+
+    # 定义结构元素(例如正方形)
+    abs_expansion_radius = abs(expansion_radius)
+    kernel = np.ones((abs_expansion_radius, abs_expansion_radius), np.uint8)
+
+    # 根据expansion_radius的符号选择膨胀或腐蚀操作
+    if expansion_radius >= 0:
+        modified_img_np = cv2.dilate(img_np, kernel, iterations=iterations)
+    else:
+        modified_img_np = cv2.erode(img_np, kernel, iterations=iterations)
+
+    # 如果提供了blur_radius,则应用高斯模糊
+    if blur_radius > 0:
+        modified_img_np = cv2.GaussianBlur(modified_img_np, (blur_radius * 2 + 1, blur_radius * 2 + 1), 0)
+
+    # 将numpy数组转换回PIL图像
+    modified_pil_image = Image.fromarray(modified_img_np)
+
+    return modified_pil_image
+
+def expand_mask(mask, expansion_radius=5, blur_radius=0):
+    # 对蒙版进行膨胀处理
+    mask = mask.filter(ImageFilter.MaxFilter(expansion_radius * 2 + 1))
+    # 应用高斯模糊滤镜
+    if blur_radius > 0:
+        mask = mask.filter(ImageFilter.GaussianBlur(blur_radius))
+    return mask
+
+
+def find_lowest_non_transparent_points(cv2_png):
+    # cv2_png 为cv2格式的带有alpha通道的图片
+
+    alpha_channel = cv2_png[:, :, 3]
+    """使用Numpy快速查找每列的最低非透明点"""
+    h, w = alpha_channel.shape
+    # 创建一个掩码,其中非透明像素为True
+    mask = alpha_channel > 0
+    # 使用np.argmax找到每列的第一个非透明像素的位置
+    # 因为是从底部向上找,所以需要先翻转图像
+    flipped_mask = np.flip(mask, axis=0)
+    min_y_values = h - np.argmax(flipped_mask, axis=0) - 1
+    # 将全透明列的值设置为-1
+    min_y_values[~mask.any(axis=0)] = -1
+    return min_y_values
+
+
+def draw_shifted_line(image, min_y_values, shift_amount=15,
+                      one_line_pos=(0, 100),
+                      line_color=(0, 0, 0),
+                      line_thickness=20,
+                      app=None):
+    """
+    image:jpg cv2格式的原始图
+    min_y_values 透明图中,不透明区域的最低那条线
+    shift_amount:向下偏移值
+    line_color:线颜色
+    line_thickness:线宽
+    """
+    # 将最低Y值向下迁移20个像素,但确保不超过图片的高度
+    # 创建空白图片
+    if app:
+        # TODO 待移除
+        app.processEvents()
+    image = np.ones((image.shape[0], image.shape[1], 3), dtype=np.uint8) * 255
+
+    # 对线条取转成图片
+    shifted_min_y_values = np.clip(min_y_values + shift_amount, 0, image.shape[0] - 1)
+    if app:
+        # TODO 待移除
+        app.processEvents()
+    # 使用Numpy索引批量绘制直线
+    min_y_threshold = 50  # Y轴像素小于50的不处理
+    valid_x = (shifted_min_y_values >= min_y_threshold) & (shifted_min_y_values != -1)
+    if app:
+        # TODO 待移除
+        app.processEvents()
+    # 对曲线取平均值
+    # # 对曲线取平均值
+    # min_y = np.max(min_y_values)
+    # min_y_values_2 = min_y_values + min_y
+    # min_y_values_2 = min_y_values_2 / 2
+    # min_y_values_2 = min_y_values_2.astype(int)
+    # shifted_min_y_values = np.clip(min_y_values_2 + shift_amount, 0, image.shape[0] - 1)
+
+    x_coords = np.arange(image.shape[1])[valid_x]
+    y_start = shifted_min_y_values[valid_x]
+    y_end = y_start + line_thickness
+
+    # 使用Numpy广播机制创建线条区域的索引
+    for x, start, end in zip(x_coords, y_start, y_end):
+        image[start:end, x, :3] = line_color  # 只修改RGB通道
+    if app:
+        # TODO 待移除
+        app.processEvents()
+    # 计算整个图像的最低非透明点
+    lowest_y = np.max(min_y_values[min_y_values != -1]) if np.any(min_y_values != -1) else -1
+    # 绘制原最低非透明点处的线
+    cv2.line(image, (one_line_pos[0], lowest_y + 5), (one_line_pos[1], lowest_y + 5), line_color,
+             thickness=line_thickness)
+    if app:
+        # TODO 待移除
+        app.processEvents()
+
+    _y = lowest_y + 18
+    if _y > image.shape[0]:  # 超过图片尺寸
+        _y = image.shape[0] - 5
+    return image, _y
+
+
+def clean_colors(img):
+    # 转成灰度图
+    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    return img
+
+
+def calculated_shadow_brightness(img: Image):
+    # 打开图片并转换为灰度模式
+    image = img.convert('L')
+    # 将图片数据转为numpy数组
+    image_data = np.array(image)
+    # 创建布尔掩码以识别非白色区域
+    non_white_mask = image_data < 252
+
+    # 使用掩码提取非白色像素的亮度值
+    non_white_values = image_data[non_white_mask]
+
+    # print(len(non_white_values),len(image_data))
+    # 如果存在非白色像素,则计算平均亮度;否则返回0
+    if len(non_white_values) > 0:
+        average_brightness = np.mean(non_white_values)
+    else:
+        average_brightness = 0  # 没有非白色像素时的情况
+
+    return average_brightness
+
+
+def levels_adjust(img, Shadow, Midtones, Highlight, OutShadow, OutHighlight, Dim):
+    # 色阶处理
+    # img 为cv2格式
+
+    # dim = 3的时候调节RGB三个分量, 0调节B,1调节G,2调节R
+    if Dim == 3:
+        mask_shadow = img < Shadow
+        img[mask_shadow] = Shadow
+        mask_Highlight = img > Highlight
+        img[mask_Highlight] = Highlight
+    else:
+        mask_shadow = img[..., Dim] < Shadow
+        img[mask_shadow] = Shadow
+        mask_Highlight = img[..., Dim] > Highlight
+        img[mask_Highlight] = Highlight
+
+    if Dim == 3:
+        Diff = Highlight - Shadow
+        rgbDiff = img - Shadow
+        clRgb = np.power(rgbDiff / Diff, 1 / Midtones)
+        outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow
+        data = np.array(outClRgb * 255, dtype='uint8')
+        img = data
+    else:
+        Diff = Highlight - Shadow
+        rgbDiff = img[..., Dim] - Shadow
+        clRgb = np.power(rgbDiff / Diff, 1 / Midtones)
+        outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow
+        data = np.array(outClRgb * 255, dtype='uint8')
+        img[..., Dim] = data
+    return img
+
+
+def calculate_average_brightness_opencv(img_gray, rows_to_check):
+    # 二值化的图片 CV对象
+    # 计算图片亮度
+    height, width = img_gray.shape
+
+    brightness_list = []
+    for row in rows_to_check:
+        if 0 <= row < height:
+            # 直接计算该行的平均亮度
+            row_data = img_gray[row, :]
+            average_brightness = np.mean(row_data)
+            brightness_list.append(average_brightness)
+        else:
+            print(f"警告:行号{row}超出图片范围,已跳过。")
+
+    return brightness_list

+ 10 - 44
python/service/module_generate_goods_art_no_table.py

@@ -1,51 +1,20 @@
-from UI.generate_goods_art_no_table.generate_goods_art_no_table_ui import (
-    Ui_Form as generate_goods_art_no_table_Ui_Form,
-)
-from import_qt_mode import *
-import os
-import time
+
 import threading
 import xlsxwriter
 import shutil
-from module.base_mode.pic_deal import Picture
-from PIL import Image
-from module.view_control.MineQWidget import MineQWidget
-from module.base_mode.excel_base_func import *
+from pic_deal import Picture
+
+
+from excel_base_func import *
 
 
-class GenerateGoodsArtNoTable(MineQWidget, generate_goods_art_no_table_Ui_Form):
-    # 货号表生成
-    progress_sign = Signal(dict)
-    info_sign = Signal(str)
+class GenerateGoodsArtNoTable():
+
 
     def __init__(self):
-        super().__init__()
-        self.is_del = False
-        self.setupUi(self)
-        # 0禁用  1进行中  2已结束
-        self.state = 2
-        self.init()
-        self.show()
-
-    def init(self):
-        self.label_4.setText("请选择需要生成货号表的文件夹路径")
-        self.label_3.setText("请选择目录")
-        self.label_3.mousePressEvent = self.get_img_dir
-        self.textBrowser_2.hide()
-
-        self.progressBar.setValue(0)
-        self.progressBar.setMaximum(100)
-        self.progressBar.setValue(0)
-        self.progressBar.hide()
-        self.label_5.setText("")
-
-        self.progress_sign.connect(self.show_progress)
-        self.pushButton.clicked.connect(self.run)
-        self.textBrowser_2.hide()
-
-    def get_img_dir(self, *args):
-        folder = QFileDialog.getExistingDirectory(self, "选取文件夹", "./")
-        self.label_4.setText(folder)
+        pass
+
+
 
     def show_progress(self, data):
         progress_bar_value = data["progress_bar_value"]
@@ -308,6 +277,3 @@ class GenerateGoodsArtNoTable(MineQWidget, generate_goods_art_no_table_Ui_Form):
                 sheet.write_row("B{}".format(index + 2), [goods_art_no])
                 sheet.write_row("E{}".format(index + 2), [goods_pic_total])
             close_book(book)
-
-    def __del__(self):
-        self.is_del = True

+ 102 - 0
python/service/pic_deal.py

@@ -0,0 +1,102 @@
+from PIL import Image
+from io import BytesIO
+
+from remove_bg_ali import RemoveBgALi as RemoveBg
+
+
+
+
+class Picture:
+    def __init__(self, in_path):
+        self.im = Image.open(in_path)
+        self.x, self.y = self.im.size
+        # print(self.x, self.y)
+
+    def resize_regular(self, width, high):
+        self.im = self.im.resize((width, high), Image.Resampling.LANCZOS)
+
+    def save_img(self, out_path, quality=90):
+        # self.im = self.im.convert("RGB")
+        self.im.save(out_path, quality=quality)
+
+    def crop_with_png(self):
+        self.im = self.im.crop(self.im.getbbox())
+        self.x, self.y = self.im.size
+
+    def resize(self, width=None, high=None):
+        if width:
+            re_x = int(width)
+            re_y = int(self.y * re_x / self.x)
+        else:
+            re_y = int(high)
+            re_x = int(self.x * re_y / self.y)
+        self.im = self.im.resize((re_x, re_y))
+        self.x, self.y = self.im.size
+
+    def save_to_io(self, format):
+        img = BytesIO()
+        self.im.save(img, format=format)  # format: PNG or JPEG
+        img.seek(0)  # rewind to the start
+        return img
+
+    def corp_square(self):
+        if self.y < self.x:
+            return
+        self.im = self.im.crop((0, int((self.y - self.x) / 2), self.x, self.y - int((self.y - self.x) / 2)))
+
+
+class PicDeal(object):
+    def __init__(self):
+        self.mode = "ali"
+
+    def remove_bg(self, in_path, out_path):
+        """
+        1、三方软件进行抠图处理
+        2、图片进行清晰度转化,转化成原始图片尺寸
+        """
+
+        if Remove_Bg_Mode == "zuo_tang" or Remove_Bg_Mode == "ali":
+            remove_bg_ins = RemoveBg()
+            if remove_bg_ins.run(in_path, out_path):
+                return True
+        else:
+            return False
+
+    def create_800image(self, original_image, cut_image_path, out_path, image_mode=None):
+        """
+        image_mode: 处理模式,主要用于图片缩放大小确定
+        original_image:原始图,用于获取商品阴影
+        cut_image_path:抠图结果
+        """
+
+        image = Picture(original_image)
+        image.crop_with_png()
+        # image.im.show()
+        if image.x >= image.y:
+            image.resize(width=575)
+        else:
+            image.resize(high=575)
+
+            image_bg = Image.open("resources/goods_bg.jpg")
+            image_bg_x, image_bg_y = image_bg.size
+            image_x, image_y = image.x, image.y
+            _x = int((image_bg_x - image_x) / 2)
+            _y = int((image_bg_y - image_y) / 2)
+            image_bg.paste(image.im, (_x, _y), image.im)
+            # image_bg.show()
+            image_bg.save(out_path)
+
+    def resize_and_save(self, image_path, out_path, width):
+        im = Image.open(image_path)
+        x, y = im.size
+        re_x = int(width)
+        re_y = int(y * re_x / x)
+        im = im.resize((re_x, re_y), Image.ANTIALIAS)
+        im.save(out_path)
+
+    def save(self, out_path):
+        pass
+
+
+if __name__ == '__main__':
+    pass

+ 1004 - 0
python/service/run_main.py

@@ -0,0 +1,1004 @@
+import settings
+from module.view_control.generate_goods_no_detail_pic import detail_func
+import json
+from base import *
+from import_qt_mode import *
+from module.view_control.match_and_cutout_mode_control.base_deal_image_v2 import BaseDealImage
+from module.view_control.MineQWidget import DialogShow, WorkerOneThread
+import threading
+from concurrent.futures import ThreadPoolExecutor
+import concurrent.futures
+from module.view_control.generate_goods_no_detail_pic.data import DataModeGenerateDetail, DataModeUploadPic
+from module.view_control.generate_goods_no_detail_pic.detail_func import create_folder
+import time
+from PIL import Image
+from io import BytesIO
+import os, re
+from functools import partial
+# from multiprocessing import Process, Queue
+import pickle
+from  base_deal import BaseDealImage
+
+
+class RunMain(QThread):
+    run_end_sign = Signal(dict)
+    show_dialog_sign = Signal(dict)
+    show_progress_detail_sign = Signal(str)
+
+    # 定义一个信号用于请求显示对话框
+    show_dialog_signal = Signal()
+    # 定义一个信号用于返回对话框的结果
+    dialog_result_signal = Signal(str)
+
+    # dialog_result_signal = Signal(str)
+
+    def __init__(self, windows):
+        super().__init__()
+        self.windows = windows
+        self.dialog_result = ""
+        self.data_mode_generate_detail = DataModeGenerateDetail()
+        self.dialog_result_signal.connect(self.on_dialog_result)
+        self.event = threading.Event()
+
+    def set_state(self, state_value: int):
+        # 0禁用  1进行中  2已结束
+        if state_value not in [0, 1, 2, 99]:
+            return
+        # self.windows.change_state_sign.emit(state_value)
+        # self.windows.set_state(state_value)
+
+    # 抠图前先做数据规整处理;类似详情图生成逻辑
+    def check_before_cutout(self, config_data):
+        return_data = {
+            "code": 99,
+            "message": "",
+            "data": {
+                "all_goods_art_no_folder_data": [],
+                "config_data": config_data,
+            },
+        }
+
+        image_dir = config_data["image_dir"]
+        image_order = config_data["image_order"]
+        is_check_number = config_data["is_check_number"]
+        is_filter = config_data["cutout_is_pass"]
+        resize_image_view = config_data["resize_image_view"]
+        logo_path = config_data["logo_path"]
+        cutout_mode = config_data["cutout_mode"]  # 是否精细化抠图,默认为普通抠图
+        special_goods_art_no_folder_line = config_data["special_goods_art_no_folder_line"]
+
+        # 自动处理红蜻蜓货号,进行重命名
+        if settings.PROJECT == "红蜻蜓":
+            # 规整红蜻蜓货号图
+            all_goods_art_no_folder_data = get_all_goods_art_no_folder(path=image_dir)
+            BaseDealImage().rename_folder_for_hqt(all_goods_art_no_folder_data=all_goods_art_no_folder_data)
+
+        # 重新获取文件夹信息
+        all_goods_art_no_folder_data = get_all_goods_art_no_folder(path=image_dir)
+
+        f = True
+        is_do_other = False
+        if is_do_other:
+            for i in all_goods_art_no_folder_data:
+                i["label"] = "不处理"
+            is_filter = False
+            specified_goods_art_no_folder = special_goods_art_no_folder_line
+            specified_goods_art_no_folder = specified_goods_art_no_folder.strip()
+            specified_goods_art_no_folder = specified_goods_art_no_folder.replace(",", ",")
+            specified_goods_art_no_folder_list = specified_goods_art_no_folder.split(",")
+            specified_goods_art_no_folder_list = [x for x in specified_goods_art_no_folder_list if x]
+            if not specified_goods_art_no_folder_list:
+                return_data["message"] += '请手动输入文件夹名称(多选),或关闭指定文件夹模式\n'
+            else:
+                for i in all_goods_art_no_folder_data:
+                    if i["folder_path"] in specified_goods_art_no_folder_list:
+                        i["label"] = "待处理"
+            # 哪些数据不合规
+            all_folder_name_list = [x["folder_name"] for x in all_goods_art_no_folder_data]
+            for goods_art_no_folder_name in specified_goods_art_no_folder_list:
+                if goods_art_no_folder_name not in all_folder_name_list:
+                    return_data["message"] += '文件夹:{},在您选的目录下不存在\n'.format(goods_art_no_folder_name)
+                    f = False
+
+        if not f:
+            self.set_state(state_value=2)
+            return
+
+        # 清空指定文件夹的已抠图文件
+        if is_do_other:
+            for folder_data in all_goods_art_no_folder_data:
+                goods_art_no_folder_path = "{}/原始图_已抠图".format(folder_data["folder_path"])
+                if os.path.exists(goods_art_no_folder_path):
+                    remove_all_file(goods_art_no_folder_path)
+
+        return_data["data"]["succeed_folder_list"] = 1
+
+        # ==================检查填写的图片视角是否符合要求
+        res = BaseDealImage().getImageOrder(image_order=image_order, resize_image_view=resize_image_view)
+        if res['code'] != 0:
+            return_data["message"] += "{}\n".format(res['msg'])
+            return return_data
+        else:
+            # 图片命名顺序
+            image_order_list = res['imageOrderList']
+            for goods_art_no_folder_data in all_goods_art_no_folder_data:
+                if goods_art_no_folder_data["label"] != "待处理":
+                    continue
+                goods_art_no_folder_data["image_order_list"] = image_order_list
+
+        # ================是否过滤已有生成的文件夹
+        if is_filter:
+            for goods_art_no_folder_data in all_goods_art_no_folder_data:
+                if goods_art_no_folder_data["label"] != "待处理":
+                    continue
+                folder_path = goods_art_no_folder_data["folder_path"]
+                _p = "{}/800x800".format(folder_path)
+                if os.path.exists(_p):
+                    if len(os.listdir(_p)):
+                        goods_art_no_folder_data["label"] = "不处理"
+
+        # ================检查每个货号文件夹图片数量是否符合要求
+        all_goods_art_no_folder_data, message = BaseDealImage().check_folders_image_amount(all_goods_art_no_folder_data,
+                                                                                           image_order_list)
+        if message:
+            return_data["message"] += "{}\n".format(message)
+        return_data["code"] = 0
+        return_data["data"]["all_goods_art_no_folder_data"] = all_goods_art_no_folder_data
+        return_data["data"]["image_dir"] = image_dir
+        return_data["data"]["resize_image_view"] = resize_image_view
+        return_data["data"]["cutout_mode"] = cutout_mode
+        return_data["data"]["image_order_list"] = image_order_list
+        return_data["data"]["logo_path"] = logo_path
+        return return_data
+
+    # 抠图校验后的回调函数处理
+    def check_for_cutout_image_first_call_back(self, return_data):
+        # return_data = {
+        #     "code": 99,
+        #     "message": "",
+        #     "data": {
+        #         "all_goods_art_no_folder_data": [],
+        #     },
+        # }
+        code = return_data["code"]
+        config_data = return_data["data"]["config_data"]
+        config_data["sign_text"] = ""
+        # if code != 0:
+        #     # self.windows.show_message(return_data["message"])
+        #     # self.show_progress_detail(return_data["message"])
+        #     _dialog_dict = {"text": return_data["message"],
+        #                     "windows": self,
+        #                     }
+        #     self.show_dialog_sign.emit(_dialog_dict)
+        #     self.event.wait()
+        #     return
+
+        do_next = False
+        text = ""
+        all_goods_art_no_folder_data = return_data["data"]["all_goods_art_no_folder_data"]
+        button_1, button_2, button_3 = None, None, None
+        text += return_data["message"]
+        # 存在错误文件夹
+        error_folder = [x for x in all_goods_art_no_folder_data if x["label"] == "错误"]
+        todo_folder = [x for x in all_goods_art_no_folder_data if x["label"] == "待处理"]
+        if error_folder:
+            button_2 = "移除错误文件"
+        if error_folder and todo_folder:
+            button_1 = "移除并继续"
+            button_3 = "继续(忽略其他)"
+        if not error_folder and todo_folder:
+            button_1 = "继续"
+            # do_next = True
+        text += "\n==================\n错误数据:{}个,校验无误数据:{}个".format(len(error_folder), len(todo_folder))
+        self.show_progress_detail(text)
+        if button_1 is None and button_2 is None and button_3 is None:
+            pass
+        elif button_1 == "继续" and button_2 is None and button_3 is None:
+            do_next = True
+        else:
+            # print("runmain  179----------------")
+            # # print(self)
+            # # print(self.windows)
+            # _dialog_dict = {"text": text,
+            #                 "button_1": button_1,
+            #                 "button_2": button_2,
+            #                 "button_3": button_3,
+            #                 "windows": self,
+            #                 }
+            # self.show_dialog_sign.emit(_dialog_dict)
+            # # self.exec_()
+            # # 等待事件被设置
+            # self.event.wait()
+            # print("self.dialog_result", self.dialog_result)
+            # #
+            # # my_dialog = DialogShow(self.windows, text=text, button_1=button_1, button_2=button_2,
+            # #                        button_3=button_3)
+            # # ret = my_dialog.exec()
+            # print("460  ===============my_dialog.flag_name===============")
+            # print(my_dialog.flag_name)
+
+            if "移除" in self.dialog_result:
+                for error_folder_data in [x for x in all_goods_art_no_folder_data if x["label"] == "错误"]:
+                    self.move_error_folders(
+                        one_path=error_folder_data["folder_path"],
+                        target_folder="{}/软件-处理失败".format(config_data["image_dir"]),
+                    )
+            if "继续" in self.dialog_result:
+                do_next = True
+
+        if do_next:
+            all_goods_art_no_folder_data = [x for x in all_goods_art_no_folder_data if x["label"] == "待处理"]
+            print("===============all_goods_art_no_folder_data===============")
+            print(all_goods_art_no_folder_data)
+
+            new_func = partial(self.do_run_cutout_image,
+                               all_goods_art_no_folder_data=all_goods_art_no_folder_data,
+                               callback_func=self.show_progress_detail,
+                               image_order_list=return_data["data"]["image_order_list"],
+                               cutout_mode=return_data["data"]["cutout_mode"],
+                               resize_image_view=return_data["data"]["resize_image_view"],
+                               windows=self.windows,
+                               logo_path=return_data["data"]["logo_path"],
+                               config_data=config_data)
+            self._w_3 = WorkerOneThread(func=new_func, name="_w_3")
+            self._w_3.start()
+
+            # self.t = threading.Thread(target=self.do_run_cutout_image,
+            #                           kwargs={"all_goods_art_no_folder_data": all_goods_art_no_folder_data,
+            #                                   "callback_func": self.show_progress_detail,
+            #                                   "image_order_list": return_data["data"]["image_order_list"],
+            #                                   "cutout_mode": return_data["data"]["cutout_mode"],
+            #                                   "resize_image_view": return_data["data"]["resize_image_view"],
+            #                                   "windows": self.windows,
+            #                                   "logo_path": return_data["data"]["logo_path"],
+            #                                   "config_data": config_data,
+            #                                   })
+            # self.t.start()
+
+        else:
+            config_data["sign_text"] = "已结束抠图处理"
+            self.run_end_sign.emit(config_data)
+
+    def do_run_cutout_image(self,
+                            all_goods_art_no_folder_data,
+                            callback_func,
+                            image_order_list,
+                            cutout_mode,
+                            resize_image_view,
+                            windows,
+                            logo_path,
+                            config_data):
+
+        BaseDealImage().run_main(all_goods_art_no_folder_data=all_goods_art_no_folder_data,
+                                 callback_func=callback_func,
+                                 image_order_list=image_order_list,
+                                 cutout_mode=cutout_mode,
+                                 resize_image_view=resize_image_view,
+                                 windows=windows,
+                                 logo_path=logo_path,
+                                 )
+
+        # ==============完成处理==============
+        # self.set_state(state_value=2)
+        callback_func("已结束抠图处理")
+        config_data["sign_text"] = "已结束抠图处理"
+        self.run_end_sign.emit(config_data)
+
+    def do_run_cutout_image1111(self, all_goods_art_no_folder_data,
+                                callback_func,
+                                image_order_list,
+                                cutout_mode,
+                                resize_image_view,
+                                windows,
+                                logo_path,
+                                config_data):
+
+        max_workers = 1
+        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
+            futures = []
+            futures.append(executor.submit(
+                BaseDealImage().run_main,
+                all_goods_art_no_folder_data=all_goods_art_no_folder_data,
+                callback_func=callback_func,
+                image_order_list=image_order_list,
+                cutout_mode=cutout_mode,
+                resize_image_view=resize_image_view,
+                windows=windows,
+                logo_path=logo_path,
+            ))
+
+            # 使用 wait 方法等待所有任务完成
+            done, not_done = concurrent.futures.wait(futures, timeout=60)
+            # 处理完成的任务
+            for future in done:
+                if settings.IS_TEST:
+                    result = future.result()
+                else:
+                    try:
+                        result = future.result()
+                    except BaseException as e:
+                        print("2039 图片处理失败.{}".format(e))
+                        callback_func("2039 处理失败.{}".format(e))
+
+        # ==============完成处理==============
+
+        # self.set_state(state_value=2)
+        callback_func("已结束抠图处理")
+        config_data["sign_text"] = "已结束抠图处理"
+        self.run_end_sign.emit(config_data)
+
+    def check_before_detail(self, config_data):
+
+        # =============
+        # 整体数据校验,返回错误内容,以及
+        # temp_name:模板名称
+        """
+        步骤:
+        1、 整体文件夹检查,并输出数据结构
+        2、数据进行对应规整(可能有excel,红蜻蜓等)
+        3、执行单个款数据处理
+        """
+        return_data = {
+            "code": 99,
+            "message": "",
+            "data": {
+                "error_folder_list": [],
+                "goods_no_dict": {},
+                "succeed_folder_list": [],
+                "temp_name": config_data["temp_name"],
+                "temp_name_list": config_data["temp_name_list"],
+                "assigned_page_dict": config_data["assigned_page_dict"],
+                "excel_temp_goods_no_data": {},  # 表格数据可能存在多模板,数据结构为一个款号下的多个模板的数据列表
+                "finally_goods_no_need_temps": {},  # 每个款号需要生成的模板数据
+                "config_data": config_data,
+            },
+        }
+        temp_name = config_data["temp_name"]
+        temp_name_list = config_data["temp_name_list"]
+        assigned_page_dict = config_data["assigned_page_dict"]
+        image_dir = config_data["image_dir"]
+        is_use_excel = config_data["is_use_excel"]
+        excel_path = config_data["excel_path"]
+        temp_class = config_data["temp_class"]
+        is_check_color_is_all = config_data["is_check_color_is_all"]
+        detail_is_pass = config_data["detail_is_pass"]
+
+        error_folder_list = []
+        # 遍历货号获取所有货号--可能为编号
+        folder_name_list = detail_func.get_all_dir_info(image_dir=image_dir)
+        if not folder_name_list:
+            return_data["message"] += "不存在任何货号/编号文件夹\n"
+            print("不存在任何货号/编号文件夹")
+            return return_data
+
+        # =========================组装数据---数据来源多种途径=========================
+        _result = {"code": 99, "message": "无法解析到数据,请检查登录企业"}
+        if not is_use_excel:
+            if settings.PROJECT == "红蜻蜓":
+                # goods_no_dict输出为文件夹下涉及到的所有款为key的字典,后续通过解析字典,进行提取对应文件夹
+                _result = self.data_mode_generate_detail.get_basic_goods_art_data_by_hqt_and_hlm(
+                    folder_name_list
+                )
+
+            elif settings.PROJECT == "惠利玛":
+                if settings.Company:
+                    if "惠利玛" in settings.Company:
+                        _result = self.data_mode_generate_detail.get_basic_goods_art_data_by_hqt_and_hlm(
+                            folder_name_list
+                        )
+        else:
+            keys = settings.keys
+            _result = (
+                self.data_mode_generate_detail.get_basic_goods_art_data_form_excel(
+                    folder_name_list,
+                    excel_path,
+                    keys,
+                )
+            )
+
+        if _result["code"] == 0:
+            remote_data = _result["data"]
+        else:
+            return_data["message"] += _result["message"] + "\n"
+            return return_data
+        # print(json.dumps(remote_data))
+        # =========================拼接数据组合为款数据=========================
+        """
+        1、获取所有文件夹基础数据内容
+        2、结合上述返回结果进行数据组合拼接
+        3、输出结果为按款为主键信息
+        4、注意模板ID
+        """
+        # 获取所有文件夹基础数据内容  检查不满足要求的文件不满足要求移动到错误文件夹
+        need_view_list = temp_class[temp_name].need_view
+        _all_dir_info_data = detail_func.get_all_dir_info_and_pic_info(
+            image_dir, folder_name_list, need_view_list
+        )
+        all_dir_info_data = {}
+        for one_folder, value in _all_dir_info_data.items():
+            if "message" in value:
+                if value["message"]:
+                    error_folder_list.append(
+                        {
+                            "folder_name": one_folder,
+                            "folder_path": "{}/{}".format(image_dir, one_folder),
+                        }
+                    )
+                    return_data["message"] += "文件夹:{} 结构错误:{}\n".format(
+                        one_folder, value["message"]
+                    )
+                    continue
+
+            # 符合要求的数据处理
+            all_dir_info_data[one_folder] = value
+
+        # 结合上述返回结果进行数据组合拼接
+        # 返回可能存在的情况:1、存在文件夹不匹配数据,2、存在货号数据缺失
+        goods_no_dict, error_folder_name_list = detail_func.merge_local_and_remote_data(
+            all_dir_info_data=all_dir_info_data, remote_data=remote_data
+        )
+        # 文件在系统中没有匹配的结果
+        if error_folder_name_list:
+            for one_folder in error_folder_name_list:
+                error_folder_list.append(
+                    {
+                        "folder_name": one_folder,
+                        "folder_path": "{}/{}".format(image_dir, one_folder),
+                    }
+                )
+                return_data["message"] += "文件夹:{} 找不到对应数据\n".format(
+                    one_folder
+                )
+
+        print("===============goods_no_dict==================")
+        if settings.IS_TEST:
+            _text = json.dumps(goods_no_dict)
+            print(goods_no_dict)
+            create_folder("qt_test")
+            with open("qt_test/goods_no_dict.txt", 'w', encoding='utf-8') as file:
+                file.write(_text)
+        print("===============goods_no_dict==================")
+
+        # ===================================是否齐色检查=============================================
+        # 如为红蜻蜓企业则还需要检查同款下是否齐全;数据返回结果为款号列表
+        error_data_dict = {}
+        if is_check_color_is_all:
+            if not is_use_excel:
+                if settings.PROJECT == "红蜻蜓":
+                    error_data_dict = (
+                        self.data_mode_generate_detail.check_goods_is_not_deficiency(
+                            goods_no_dict
+                        )
+                    )
+            else:
+                error_data_dict = self.data_mode_generate_detail.check_goods_is_not_deficiency_form_excel(
+                    goods_no_dict, excel_path
+                )
+
+        if error_data_dict:
+            print("----error_data_dict----")
+            print(json.dumps(error_data_dict))
+
+            # 款号反向映射;因为部分key键格式为KUM9999999
+            _x = {}
+            for i, v in goods_no_dict.items():
+                _x[v["款号"]] = i
+
+            for goods_no, value in error_data_dict.items():
+                if goods_no in _x:
+                    goods_no = _x[goods_no]
+                if goods_no in goods_no_dict:
+                    # =====移动到错误文件夹======
+                    for _folder_name in [
+                        x["文件夹名称"] for x in goods_no_dict[goods_no]["货号资料"]
+                    ]:
+                        error_folder_list.append(
+                            {
+                                "folder_name": _folder_name,
+                                "folder_path": "{}\{}".format(
+                                    image_dir, _folder_name
+                                ),
+                            }
+                        )
+                        return_data["message"] += "文件夹:{};{}\n".format(
+                            _folder_name, value["message"]
+                        )
+                    # 从 正确 款下面进行数据剔除
+                    goods_no_dict.pop(goods_no)
+
+        print("-----------------1goods_no_dict---------------")
+        print(json.dumps(goods_no_dict, ensure_ascii=False))
+        print("-----------------1goods_no_dict---------------")
+
+        # 如果没有有效数据则进行退出
+        if not goods_no_dict:
+            return_data["message"] += "没有任何有效数据\n"
+            return return_data
+
+        # 校验无误的文件夹数据  goods_no_dict为最终有效数据
+        for goods_no, value in goods_no_dict.items():
+            return_data["data"]["succeed_folder_list"].extend(
+                [x["文件夹名称"] for x in goods_no_dict[goods_no]["货号资料"]]
+            )
+
+        # 如果是表格数据,则获取表格数据中需要生成的货号
+        # 数据结构
+        # {“款号1”:
+        # {“模板名称1”:data_dict1,
+        # “模板名称2”:data_dict2,
+        # }}
+
+        if is_use_excel:
+            excel_temp_goods_no_data = self.data_mode_generate_detail.get_basic_template_information(
+                _goods_no_dict=goods_no_dict, excel_path=excel_path)
+        else:
+            excel_temp_goods_no_data = {}
+
+        print("731===================excel_temp_goods_no_data,is_use_excel:{}".format(is_use_excel))
+        print(json.dumps(excel_temp_goods_no_data))
+
+        # ===========数据组装,统计每个款需要生成哪些模板==============
+        # 不适用表格指定模板
+        goods_no_need_temps = {}
+        # isUseTemplate 为false 表示使用excel表格数据
+        if not is_use_excel:
+            for i in goods_no_dict:
+                goods_no_need_temps[i] = [temp_name]
+        else:
+            for i in goods_no_dict:
+                # i 为款号
+                if i in excel_temp_goods_no_data:
+                    # 获取 某个款号的所有允许生成的模板列表
+                    goods_no_need_temps[i] = list(excel_temp_goods_no_data[i].keys())
+
+        # ========开始执行详情图生成===================
+        # _goods_no_dict = goods_no_dict
+        _goods_no_dict = {}
+
+        # assigned_page_dict 如存在对应模板的,则不管是否有过滤都需要生成
+        # 检查是否已存在模板,已存在的需要进行跳过;可能部分模板已存在,部分不存在。
+        finally_goods_no_need_temps = {}
+        for goods_no, value in goods_no_dict.items():
+            if goods_no not in goods_no_need_temps:
+                continue
+            for __temp_name in goods_no_need_temps[goods_no]:
+                _path = "{}/{}/{}/{}".format(image_dir, "软件-详情图生成", __temp_name, goods_no)
+                if not os.path.exists(_path):
+                    print("款号详情图不存在", _path)
+                    if goods_no not in finally_goods_no_need_temps:
+                        finally_goods_no_need_temps[goods_no] = []
+                        _goods_no_dict[goods_no] = value  # 需要生成的数据
+                    finally_goods_no_need_temps[goods_no].append(__temp_name)
+                else:
+                    print("款号详情图存在", _path)
+                    # 如果在指定模板中存在,则也需要生成
+                    if __temp_name in assigned_page_dict:
+                        print("指定模板需要更新", _path)
+                        if goods_no not in finally_goods_no_need_temps:
+                            finally_goods_no_need_temps[goods_no] = []
+                            _goods_no_dict[goods_no] = value  # 需要生成的数据
+                        finally_goods_no_need_temps[goods_no].append(__temp_name)
+                    else:
+                        if detail_is_pass:
+                            return_data["message"] += "\n款号:{},模板:{} 已存在".format(goods_no, __temp_name)
+                        else:
+                            if goods_no not in finally_goods_no_need_temps:
+                                finally_goods_no_need_temps[goods_no] = []
+                                _goods_no_dict[goods_no] = value  # 需要生成的数据
+                            finally_goods_no_need_temps[goods_no].append(__temp_name)
+
+            pass
+            # _path = "{}/{}".format(self.image_dir, "软件-详情图生成")
+            # if os.path.exists(_path):
+            #     _goods_no_dict = {}
+            #     # 数据返回为 已有的款数据,为款号列表
+            #     is_pass_goods_no = detail_func.get_all_detail_info(_path)
+            #     for goods_no, value in goods_no_dict.items():
+            #         if "软件" in goods_no:
+            #             continue
+            #
+            #         if value["模板名称"] in assigned_page_dict:
+            #             need_todo = True
+            #         else:
+            #             if goods_no in is_pass_goods_no:
+            #                 need_todo = False
+            #             else:
+            #                 need_todo = True
+            #         if need_todo:
+            #             _goods_no_dict[goods_no] = value
+
+        print("-----------------2goods_no_dict---------------")
+        print(json.dumps(_goods_no_dict, ensure_ascii=False))
+        print("-----------------2goods_no_dict---------------")
+        return_data["data"]["error_folder_list"] = error_folder_list
+
+        if len(_goods_no_dict) == 0:
+            return_data["message"] += "\n没有任何文件夹需要执行"
+
+        return_data["data"]["goods_no_dict"] = _goods_no_dict
+        return_data["data"]["excel_temp_goods_no_data"] = excel_temp_goods_no_data
+        return_data["data"]["finally_goods_no_need_temps"] = finally_goods_no_need_temps
+
+        return_data["code"] = 0
+        return return_data
+
+    def move_error_folders(self, one_path, target_folder, message=""):
+        if os.path.exists(one_path):
+            check_path(target_folder)
+            move_folders(path_list=[one_path], target_folder=target_folder)
+
+    def check_for_detail_first_call_back(self, data):
+        # 首次数据校验的信息返回
+        # self.show_message(text="22222222222222222222222")
+        # QMessageBox.critical(self, "警告", "1111111", QMessageBox.Ok)
+        code = data["code"]
+        config_data = data["data"]["config_data"]
+        target_error_folder = config_data["target_error_folder"]
+        print("635  check_for_detail_first_call_back")
+        print(data)
+        if code != 0:
+            # self.windows.show_message(data["message"])
+            # my_dialog = DialogShow(self.windows.windows, text=data["message"])
+            # ret = my_dialog.exec()
+            self.show_progress_detail(text=data["message"])
+            _dialog_dict = {"text": data["message"],
+                            "windows": self,
+                            }
+            self.show_dialog_sign.emit(_dialog_dict)
+            self.event.wait()
+
+            # self.windows.set_state(2)
+            config_data["sign_text"] = "已结束详情处理"
+            self.run_end_sign.emit(config_data)
+            return
+
+        do_next = False
+        if data["message"]:
+            button_1, button_2, button_3 = None, None, None
+            text = data["message"]
+            if code == 0:
+                if data["data"]:
+                    if data["data"]["error_folder_list"]:
+                        print("22----------error_folder_list------------")
+                        print(json.dumps(data["data"]["error_folder_list"]))
+                        button_2 = "移除错误文件"
+                    if data["data"]["goods_no_dict"]:
+                        if button_2:
+                            button_1 = "移除并继续"
+                            button_3 = "继续(忽略其他)"
+                        else:
+                            button_1 = "继续"
+                    if data["data"]["succeed_folder_list"]:
+                        text += "\n==================\n校验无误数据:{}个文件夹".format(
+                            len(data["data"]["succeed_folder_list"])
+                        )
+            self.show_progress_detail(text=data["message"])
+            if button_1 is None and button_2 is None and button_3 is None:
+                pass
+            elif button_1 == "继续" and button_2 is None and button_3 is None:
+                do_next = True
+            else:
+                print("runmain  642----------------")
+                # todo 弹框修改处理等
+                _dialog_dict = {"text": text,
+                                "button_1": button_1,
+                                "button_2": button_2,
+                                "button_3": button_3,
+                                "windows": self,
+                                }
+                self.show_dialog_sign.emit(_dialog_dict)
+                # 等待事件被设置
+                self.event.wait()
+                print("self.dialog_result", self.dialog_result)
+
+                # my_dialog = DialogShow(
+                #     self.windows, text=text, button_1=button_1, button_2=button_2, button_3=button_3
+                # )
+                print("my_dialog.flag_name", self.dialog_result)
+                if "移除" in self.dialog_result:
+                    for error_folder_data in data["data"]["error_folder_list"]:
+                        self.move_error_folders(
+                            one_path=error_folder_data["folder_path"],
+                            target_folder=target_error_folder,
+                        )
+                if "继续" in self.dialog_result:
+                    do_next = True
+
+        if data["data"]["goods_no_dict"] and not data["data"]["error_folder_list"]:
+            do_next = True
+
+        if do_next:
+            # self.set_state(state_value=1)
+            getAllData = data["data"]
+            base_temp_name = getAllData["temp_name"]
+            set_temp_name = getAllData.get("template_name", "")
+            kwargs = {
+                "config_data": config_data,
+                "_goods_no_dict": data["data"]["goods_no_dict"],
+                "temp_name": base_temp_name,
+                "temp_name_list": data["data"]["temp_name_list"],
+                "assigned_page_dict": data["data"]["assigned_page_dict"],
+                "excel_temp_goods_no_data": data["data"]["excel_temp_goods_no_data"],
+                # 表格数据可能存在多模板,数据结构为一个款号下的多个模板的数据列表
+                "finally_goods_no_need_temps": data["data"]["finally_goods_no_need_temps"],  # 每个款号需要生成的模板数据
+            }
+            # todo work
+            new_func = partial(self.detail_run_by_thread,
+                               config_data=kwargs["config_data"],
+                               _goods_no_dict=kwargs["_goods_no_dict"],
+                               temp_name=kwargs["temp_name"],
+                               temp_name_list=kwargs["temp_name_list"],
+                               assigned_page_dict=kwargs["assigned_page_dict"],
+                               excel_temp_goods_no_data=kwargs["excel_temp_goods_no_data"],
+                               finally_goods_no_need_temps=kwargs["finally_goods_no_need_temps"])
+            self._w_3 = WorkerOneThread(func=new_func, name="_w_3")
+            self._w_3.start()
+            # threading.Thread(target=self.detail_run_by_thread, kwargs=kwargs).start()
+        else:
+            config_data["sign_text"] = "已结束详情处理"
+            self.run_end_sign.emit(config_data)
+
+    def detail_run_by_thread(self, config_data, _goods_no_dict, temp_name, temp_name_list, assigned_page_dict,
+                             excel_temp_goods_no_data,
+                             finally_goods_no_need_temps):
+        """
+        excel_temp_goods_no_data: {},  # 表格数据可能存在多模板,数据结构为一个款号下的多个模板的数据列表
+        finally_goods_no_need_temps: {},  # 每个款号需要生成的模板数据
+        """
+
+        # 开始处理
+        self.n = 0
+        self.total_num = len(_goods_no_dict)
+        self.fail_num = 0
+        is_use_excel = config_data["is_use_excel"]
+        image_dir = config_data["image_dir"]
+
+        # 详情图生成结果文件夹
+        out_put_dir = "{}\软件-详情图生成".format(image_dir)
+        if settings.IS_TEST:
+            print("==============_goods_no_dict  打印=================")
+
+            print(json.dumps(_goods_no_dict))
+
+            print("==============_goods_no_dict  打印-end=================")
+
+        all_detail_path_list = []
+        for goods_no, temp_name_list in finally_goods_no_need_temps.items():
+            try:
+                for _temp_name in temp_name_list:
+                    # if _temp_name != "xiaosushuoxie-4":
+                    #     continue
+                    assigned_page_list = []
+                    if _temp_name in assigned_page_dict:
+                        assigned_page_list = assigned_page_dict[_temp_name]
+                    # 如果为使用表格,则获取表格中的数据作为款号的基础数据
+                    temp_info_data = copy.copy(_goods_no_dict[goods_no])
+                    if is_use_excel:
+                        # 将表格中的数据进行替换
+                        if goods_no in excel_temp_goods_no_data:
+                            if _temp_name in excel_temp_goods_no_data[goods_no]:
+                                # 将表格中的特定的模板的行,替换到goods_no的data中,因为不同的模板有数据特殊性
+                                for _key, _key_value in excel_temp_goods_no_data[goods_no][_temp_name].items():
+                                    if _key in temp_info_data:
+                                        temp_info_data[_key] = _key_value
+                    print("temp_info_data")
+                    print("goods_no:{},_temp_name:{}".format(goods_no, _temp_name))
+                    all_detail_path_list.append("{}/{}/".format(out_put_dir, _temp_name, goods_no))
+                    # continue
+                    self.detail_deal_one_data(goods_no=goods_no,
+                                              value=temp_info_data,
+                                              out_put_dir=out_put_dir,
+                                              temp_name=_temp_name,
+                                              assigned_page_list=assigned_page_list)
+            except BaseException as e:
+                self.show_progress_detail(
+                    "款:{}生成详情异常:{}".format(goods_no, e))
+                print(e)
+
+        # ==============完成处理==============
+        self.set_state(state_value=2)
+        if self.total_num:
+            if self.fail_num:
+                self.show_progress_detail("处理完成,-----处理失败数据:{}个款".format(self.fail_num))
+            else:
+                self.show_progress_detail("处理完成")
+        else:
+            self.show_progress_detail("没有任何数据")
+
+        config_data["sign_text"] = "已结束详情处理"
+        config_data["all_detail_path_list"] = all_detail_path_list
+        # 打开文件夹
+        os.startfile(out_put_dir)
+
+        self.run_end_sign.emit(config_data)
+
+    def detail_run_by_thread11111(self, config_data, _goods_no_dict, temp_name, temp_name_list, assigned_page_dict,
+                                  excel_temp_goods_no_data,
+                                  finally_goods_no_need_temps):
+        """
+        excel_temp_goods_no_data: {},  # 表格数据可能存在多模板,数据结构为一个款号下的多个模板的数据列表
+        finally_goods_no_need_temps: {},  # 每个款号需要生成的模板数据
+        """
+
+        # 开始处理
+        self.n = 0
+        self.total_num = len(_goods_no_dict)
+        self.fail_num = 0
+        is_use_excel = config_data["is_use_excel"]
+        image_dir = config_data["image_dir"]
+
+        # 详情图生成结果文件夹
+        out_put_dir = "{}\软件-详情图生成".format(image_dir)
+        if settings.IS_TEST:
+            print("==============_goods_no_dict  打印=================")
+
+            print(json.dumps(_goods_no_dict))
+
+            print("==============_goods_no_dict  打印-end=================")
+
+        if settings.IS_TEST:
+            max_workers = 1
+        else:
+            max_workers = 1
+
+        all_detail_path_list = []
+
+        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
+            futures = []
+            for goods_no, temp_name_list in finally_goods_no_need_temps.items():
+                for _temp_name in temp_name_list:
+                    # if _temp_name != "xiaosushuoxie-4":
+                    #     continue
+
+                    assigned_page_list = []
+                    if _temp_name in assigned_page_dict:
+                        assigned_page_list = assigned_page_dict[_temp_name]
+                    # 如果为使用表格,则获取表格中的数据作为款号的基础数据
+                    temp_info_data = copy.copy(_goods_no_dict[goods_no])
+                    if is_use_excel:
+                        # 将表格中的数据进行替换
+                        if goods_no in excel_temp_goods_no_data:
+                            if _temp_name in excel_temp_goods_no_data[goods_no]:
+                                # 将表格中的特定的模板的行,替换到goods_no的data中,因为不同的模板有数据特殊性
+                                for _key, _key_value in excel_temp_goods_no_data[goods_no][_temp_name].items():
+                                    if _key in temp_info_data:
+                                        temp_info_data[_key] = _key_value
+
+                    print("temp_info_data")
+                    print("goods_no:{},_temp_name:{}".format(goods_no, _temp_name))
+                    all_detail_path_list.append("{}/{}/".format(out_put_dir, _temp_name, goods_no))
+                    # continue
+                    futures.append(executor.submit(
+                        self.detail_deal_one_data,
+                        goods_no=goods_no,
+                        value=temp_info_data,
+                        out_put_dir=out_put_dir,
+                        temp_name=_temp_name,
+                        assigned_page_list=assigned_page_list,
+                    ))
+
+            # for goods_no, value in _goods_no_dict.items():
+            #     _temp_name = temp_name
+            #     # 使用自定义的表格数据
+            #     if self.isUseTemplate is False:
+            #         if "模板名称" in value:
+            #             if value["模板名称"] in temp_name_list:
+            #                 _temp_name = value["模板名称"]
+            #     assigned_page_list = []
+            #     if _temp_name in assigned_page_dict:
+            #         assigned_page_list = assigned_page_dict[_temp_name]
+            #
+            #     futures.append(executor.submit(
+            #         self.deal_one_data,
+            #         goods_no=goods_no,
+            #         value=value,
+            #         out_put_dir=out_put_dir,
+            #         temp_name=_temp_name,
+            #         assigned_page_list=assigned_page_list,
+            #     ))
+
+            # 使用 wait 方法等待所有任务完成
+            done, not_done = concurrent.futures.wait(futures)
+
+            # 处理完成的任务
+            for future in done:
+                if settings.IS_TEST:
+                    result = future.result()
+
+        # ==============完成处理==============
+        self.set_state(state_value=2)
+        if self.total_num:
+            if self.fail_num:
+                self.show_progress_detail(
+                    "处理完成,-----------处理失败数据:{}个款".format(self.fail_num)
+                )
+            else:
+                self.show_progress_detail("处理完成")
+        else:
+            self.show_progress_detail("没有任何数据")
+
+        config_data["sign_text"] = "已结束详情处理"
+        config_data["all_detail_path_list"] = all_detail_path_list
+        self.run_end_sign.emit(config_data)
+
+    def show_progress_detail(self, text):
+        # self.show_progress_detail_sign.emit(text)
+        # self.windows.show_progress_detail(text)
+
+    def detail_deal_one_data(self, goods_no, value, out_put_dir, temp_name, assigned_page_list):
+        if self.windows.state == 99:
+            self.show_progress_detail("用户主动取消:{}".format(goods_no))
+            return
+
+        self.show_progress_detail("正在生成:{},模板:{}".format(goods_no, temp_name))
+        is_deal_success = False
+        print("=================deal_one_data=====================")
+        print("goods_no", goods_no)
+        print("模板:", temp_name)
+        print("value:", value)
+
+        if settings.IS_TEST:
+            d = self.windows.temp_class[temp_name](goods_no, value,
+                                                   out_put_dir=out_put_dir,
+                                                   windows=self.windows,
+                                                   assigned_page_list=assigned_page_list)
+            is_deal_success = True
+        else:
+            try:
+                # # 处理图片详情图生成
+                d = self.windows.temp_class[temp_name](goods_no, value,
+                                                       out_put_dir=out_put_dir,
+                                                       windows=self.windows,
+                                                       assigned_page_list=assigned_page_list)
+                is_deal_success = True
+            except BaseException as e:
+                self.show_progress_detail("{}处理失败".format(goods_no))
+                error_text = "{}".format(e)
+                if "Unable to allocate" in error_text:
+                    error_text = "电脑内存不足,生成失败"
+
+                self.show_progress_detail("失败原因:{}".format(error_text))
+                self.fail_num += 1
+
+        self.n += 1
+
+        if not is_deal_success:
+            goods_art_no_list = value["货号资料"]
+            self.show_progress_detail("处理失败")
+            self.show_progress_detail(
+                "相关货号:{}".format([x["货号"] for x in goods_art_no_list])
+            )
+            # 将相关的文件夹统一移动至错误文件夹
+            detail_func.move_folders(
+                path_list=[
+                    "{}/{}".format(self.windows.image_dir, x)
+                    for x in [x["文件夹名称"] for x in goods_art_no_list]
+                ],
+                target_folder=self.windows.target_error_folder,
+            )
+            pass
+        # 更新进度
+        print(self.n, self.total_num)
+        self.windows.progress_sign.emit(
+            {
+                "type": "详情图生成",
+                "progress_bar_value": int(self.n / self.total_num * 100),
+            }
+        )
+
+    def check_serializable(self, obj):  # 检查某个对象其中的属性哪些不能被序列化
+        for attr_name in dir(obj):
+            if not attr_name.startswith('__'):
+                try:
+                    attr_value = getattr(obj, attr_name)
+                    serialized = pickle.dumps(attr_value)
+                    print(f"属性 {attr_name} 是可序列化的。")
+                except (TypeError, pickle.PicklingError):
+                    print(f"属性 {attr_name} 不可序列化。")
+
+    def on_dialog_result(self, result):
+        """处理对话框结果"""
+        self.dialog_result = result
+        print("972  处理对话框结果:{}".format(result))
+        # self.quit()  # 结束事件循
+        self.event.set()

+ 460 - 0
python/service/upload_pic.py

@@ -0,0 +1,460 @@
+from import_qt_mode import *
+from module.view_control.generate_goods_no_detail_pic.data import DataModeUploadPic
+from module.view_control.MineQWidget import DialogShow, WorkerOneThread
+import os
+import threading
+import time
+import concurrent.futures
+import re
+from PIL import Image
+from io import BytesIO
+
+
+# 详情图上传
+class UploadPic(QObject):
+    signal_data = Signal(dict)
+    run_end_sign = Signal(dict)
+    show_progress_detail_sign = Signal(str)
+
+    def __init__(self, windows, to_deal_dir, config_data):
+        super().__init__()
+        self.windows = windows
+        self.data_mode_upload_pic = DataModeUploadPic()
+        self.goods_no_data = {}
+        # ------------------
+        # 0未选择文件,1已选文件未开始,2进行中
+        self.state = 0
+        self.state_change(self.state)
+        self.to_deal_dir = to_deal_dir
+        self.config_data = config_data
+
+    def check_path(self, _path):
+        if not os.path.exists(_path):
+            os.mkdir(_path)
+        return True
+
+    def run_by_thread(self):
+        self.run_by_thread_real()
+        self.config_data["sign_text"] = "结束"
+        self.run_end_sign.emit(self.config_data)
+
+    def run(self):
+        self._w_5 = WorkerOneThread(func=self.run_by_thread, name="_w_5")
+        self._w_5.start()
+
+    def run_by_thread_real(self, *args):
+        self.show_info("====开始处理====")
+        # 统计哪些需要处理
+        total_num = 0
+        flag = True
+        # group_folders 分组文件夹
+        for group_folders in os.listdir(self.to_deal_dir):
+            path = "{}\{}".format(self.to_deal_dir, group_folders)
+            if not os.path.isdir(path):
+                continue
+            for goods_no in os.listdir(path):
+                # 检测是不是货号或款号文件夹
+                goods_no_path = "{}\{}\{}".format(self.to_deal_dir, group_folders, goods_no)
+                if not os.path.isdir(goods_no_path):
+                    continue
+
+                total_num += 1
+                # 检查每个款号文件夹是不是合规
+                # 款的一级目录文件
+                files = os.listdir(goods_no_path)
+                if "details" not in files:
+                    continue
+
+                for f in files:
+                    f_path = "{}\{}\{}\{}".format(self.to_deal_dir, group_folders, goods_no, f)
+                    if os.path.isdir(f_path):
+                        # 处理货号文件夹
+                        if "details" != f and "货号素材" not in f and "main" not in f and "拼接图" not in f:
+                            # 是否货号文件夹判断
+                            if not self.is_goods_art_dir(f, goods_no):
+                                self.show_info("{}文件夹下  {} {} 疑似不是货号".format(group_folders, goods_no, f))
+                                flag = False
+        if not flag:
+            self.show_info("请解决以上问题后重试")
+            return
+
+        if total_num == 0:
+            self.show_info("没有任何款号文件夹 需要上传")
+            return
+
+        self.lock = threading.Lock()
+
+        error_data = []
+        n = 0
+        for group_folders in os.listdir(self.to_deal_dir):
+            path = "{}\{}".format(self.to_deal_dir, group_folders)
+            if not os.path.isdir(path):
+                continue
+
+            for goods_no in os.listdir(path):
+                goods_no_path = "{}\{}\{}".format(self.to_deal_dir, group_folders, goods_no)
+                if not os.path.isdir(goods_no_path):
+                    self.show_info("{} 不是文件夹".format(goods_no))
+                    continue
+                if goods_no in self.data_mode_upload_pic.is_deal_goods_no:
+                    continue
+                else:
+                    self.data_mode_upload_pic.is_deal_goods_no.append(goods_no)
+
+                # ================处理单个款号信息,图片上传处理============
+                self.show_info("开始处理  {}".format(goods_no))
+                try:
+                    if self.deal_goods_pic(goods_no, goods_no_path):
+                        # 移动文件到已完成
+                        # dest = "{}\{}\{}".format(self.is_deal_dir, group_folders, goods_no)
+                        # if os.path.exists(dest):
+                        #     self.show_info("{} 文件夹在目标目录已存在".format(goods_no))
+                        # else:
+                        #     shutil.move(goods_no_path, dest)
+                        self.show_info("{} 上传成功".format(goods_no))
+                    else:
+                        self.show_info("{} 处理失败".format(goods_no))
+                        error_data.append(goods_no)
+                        pass
+                except BaseException as e:
+                    self.show_info("上传异常:{},{}".format(goods_no, e))
+
+                # n += 1
+                # self.show_info({"type": "show_p",
+                #                 "data": int(n * 100 / total_num)})
+
+        self.show_info("========已结束========== ")
+        if error_data:
+            self.show_info("以下编号/款号处理失败")
+            for data in error_data:
+                self.show_info("{}".format(data))
+
+        # self.show_info({"type": "change_state",
+        #                 "data": 2})
+
+    def deal_goods_pic(self, goods_no, goods_no_path):
+        """
+        两种模式:货号模式、编号模式
+        :param goods_no:
+        :param goods_no_path:
+        :return:
+        """
+        print("==========开始上传系统=============")
+
+        s = time.time()
+
+        self.show_info("{} 开始上传 ".format(goods_no))
+
+        if "NUM" in goods_no.upper():
+            mode = "编号"
+        else:
+            mode = "货号"
+
+        # 校验所有编号是否存在,是否是同个款
+        _numbers = []
+        if mode == "编号":
+            _numbers.append(goods_no)
+
+        self.goods_no_data[goods_no] = {}
+        # 收集该款号下可能存在的货号或编号
+        files = os.listdir(goods_no_path)
+        if "details" not in files:
+            self.show_info("{} 缺少details 文件夹".format(goods_no, ))
+            return False
+        for f in files:
+            f_path = "{}\{}".format(goods_no_path, f)
+            if os.path.isdir(f_path):
+                if "details" != f and "货号素材" not in f and "main" not in f and "拼接图" not in f:
+                    if not self.is_goods_art_dir(f, goods_no):
+                        self.show_info("{} {} 疑似不是货号".format(goods_no, f))
+                        # return False
+                        continue
+                    else:
+                        _numbers.append(f)
+
+                if "货号素材" in f:
+                    if "_" not in f:
+                        self.show_info("{} 货号素材文件夹 格式错误".format(goods_no))
+                        return False
+
+        # 校验所有编号是否存在,是否是同个款
+        if mode == "编号":
+            _numbers = [x.replace("KNUM", "").replace("NUM", "") for x in _numbers]
+            _numbers = list(set(_numbers))
+            goods_number_data = self.data_mode_upload_pic.get_goods_art_no_info(numbers_list=_numbers)
+
+        else:
+            goods_number_data = self.data_mode_upload_pic.get_goods_art_no_info(goods_art_list=_numbers)
+
+        t = True
+        for num in _numbers:
+            if num not in goods_number_data:
+                t = False
+                if mode == "编号":
+                    self.show_info("{} 编号在系统中不存在".format(num))
+                else:
+                    self.show_info("{} 货号在系统中不存在".format(num))
+            else:
+                if self.config_data["upload_is_pass"]:
+                    if self.data_mode_upload_pic.check_is_uploaded(num):
+                        t = False
+                        self.show_info("{} 货号在系统已有详情页,已跳过".format(num))
+                        return True
+
+        if not t:
+            return False
+
+        _ = set([goods_number_data[x]["款号"] for x in goods_number_data])
+        if len(_) != 1:
+            self.show_info("{} 存在多个不同的款号".format(_))
+            return False
+
+        # 重新定义款号
+        goods_no = _.pop()
+
+        # 上传所有图片
+        self.pic_data = {}
+        ignore_text = ["main", "拼接图", "货号素材"]
+        _Type = ['.png', '.PNG', '.jpg', '.JPG', '.gif', '.GIF']
+        for dirpath, dirnames, filenames in os.walk(goods_no_path):
+            for file in filenames:
+                if os.path.splitext(file)[1] in _Type:
+                    # 获取当前路径的文件夹名称
+                    f_path = dirpath + '/' + file
+                    f = True
+                    for i in ignore_text:
+                        if i in f_path:
+                            f = False
+                            break
+                    if f:
+                        print(f_path)
+                        f_path = f_path.replace("\\", "/")
+                        self.pic_data[f_path] = {"url": ""}
+
+        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+            for key in self.pic_data:
+                if "货号素材" in key:
+                    # 货号素材图不上传
+                    is_resize = False
+                    continue
+                else:
+                    is_resize = True
+                executor.submit(self.to_upload_pic, file_path=key, is_resize=is_resize)
+
+        for key in self.pic_data:
+            if "货号素材" in key:
+                is_resize = False
+                continue
+
+            if not self.pic_data[key]["url"]:
+                self.show_info("{} {} 图片未上传完成".format(key, goods_no))
+                return
+
+        up_data = {"goods_art_no": [],
+                   "details": [],
+                   "goods_no": [],
+                   "goods_art_no_material": []
+                   }
+
+        details_index = 0
+        goods_no_index = 0
+        # 款号转换
+        # files 为当前款下的所有文件或文件夹
+        for f in files:
+            f_path = "{}\{}".format(goods_no_path, f)
+            f_path = f_path.replace("\\", "/")
+            # 如果为文件夹则
+            if os.path.isdir(f_path):
+                if "details" == f:
+                    # 处理详情图
+                    for pic in os.listdir(f_path):
+                        pic_path = "{}\{}".format(f_path, pic)
+                        pic_path = pic_path.replace("\\", "/")
+
+                        details_index += 1
+                        data = {
+                            "index": details_index,
+                            "name": goods_no,
+                            "number": goods_no,
+                            "status": "success",
+                            "type": "2",  # 2 为详情图
+                            "uid": "",
+                            "url": self.pic_data[pic_path]["url"],
+                        }
+                        up_data["details"].append(data)
+                    pass
+                elif "货号素材" in f:
+                    continue
+                    x = f.split("_")[0]
+                    if mode == "编号":
+                        x = f.replace("NUM", "")
+                        x = x.replace("_货号素材", "")
+                        goods_art_no = goods_number_data[x]["商品货号"]
+                    else:
+                        goods_art_no = x
+                    n = 0
+                    for pic in os.listdir(f_path):
+                        pic_path = "{}\{}".format(f_path, pic)
+                        pic_path = pic_path.replace("\\", "/")
+                        n += 1
+                        data = {
+                            "index": n,
+                            "name": goods_art_no,
+                            "number": goods_art_no,
+                            "status": "success",
+                            "type": "3",
+                            "uid": "",
+                            "url": self.pic_data[pic_path]["url"],
+                        }
+                        up_data["goods_art_no_material"].append(data)
+                else:
+                    if "拼接图" in f:
+                        continue
+                    if "main" in f:
+                        continue
+
+                    # 处理货号图
+                    if mode == "编号":
+                        x = f.replace("NUM", "")
+                        goods_art_no = goods_number_data[x]["商品货号"]
+                    else:
+                        goods_art_no = f
+
+                    for pic in os.listdir(f_path):
+                        pic_path = "{}\{}".format(f_path, pic)
+                        pic_path = pic_path.replace("\\", "/")
+                        data = {
+                            "index": 1,
+                            "name": goods_art_no,
+                            "number": goods_art_no,
+                            "status": "success",
+                            "type": "0",  # 0为货号图
+                            "uid": "",
+                            "url": self.pic_data[pic_path]["url"],
+                        }
+                        up_data["goods_art_no"].append(data)
+                        break
+            else:
+                # 处理款号图
+                goods_no_index += 1
+                data = {
+                    "index": goods_no_index,
+                    "name": goods_no,
+                    "number": goods_no,
+                    "status": "success",
+                    "type": "1",
+                    "uid": "",
+                    "url": self.pic_data[f_path]["url"],
+                }
+                up_data["goods_no"].append(data)
+                pass
+
+        # -----------上传数据-----------------
+        # print(up_data["goods_no"])
+        # print(up_data["goods_art_no_material"])
+        # print(up_data["details"])
+
+        # 上传图片
+        for key, value in up_data.items():
+            if value:
+                if not self.data_mode_upload_pic.upload_pic_list_data(data={"images": value}):
+                    self.show_info("{} {}上传错误".format(goods_no, key))
+                    return False
+
+        print("---{}  {} 完成".format(goods_no, time.time() - s))
+        return True
+
+    def to_upload_pic(self, file_path, is_resize=True):
+        file_name = os.path.split(file_path)[1]
+        e = os.path.splitext(file_name)[1][1:]
+
+        im = Picture(file_path)
+        if im.x > 1200:
+            im.resize(width=1200)
+
+        # print(file_name, e)
+        # if e == "png":
+        #     im.im.show()
+        # return
+        _ = {"jpg": "JPEG",
+             "JPG": "JPEG",
+             "JPEG": "JPEG",
+             "jpeg": "JPEG",
+             "png": "PNG",
+             "PNG": "PNG", }
+        e = _[e]
+        image_io = im.save_to_io(e)
+
+        # if is_resize:
+        #     im = Picture(file_path)
+        #     if im.x > 1200:
+        #         im.resize(width=1200)
+        #     image_io = im.save_to_io()
+        #     e = "JPEG"
+        # else:
+        #     file_name = os.path.split(file_path)[1]
+        #     e = os.path.splitext(file_name)[0][1:]
+        #     image_io = open(file_path, 'rb')
+
+        # image_io = open(file_path, 'rb')
+
+        # self.lock.acquire()
+        # self.pic_data[file_path]["url"] = "1111"
+        # self.lock.release()
+
+        # return
+        goods_data = {"file_path": os.path.split(file_path)[1],
+                      "image_io": image_io,
+                      "e": e
+                      }
+
+        url = self.data_mode_upload_pic.upload_pic(goods_data=goods_data)
+        self.lock.acquire()
+        self.pic_data[file_path]["url"] = url
+        self.lock.release()
+
+    def is_goods_art_dir(self, file, goods_no):
+        if goods_no in file:
+            return True
+        # 判断是不是货号文件夹
+        _r_text = re.findall("[a-zA-Z0-9]+", file)
+        if _r_text:
+            # print(_r_text)
+            if _r_text[0] != file:
+                # 文件夹中包含有非字母或数字判断不是货号
+                return False
+            else:
+                return True
+        else:
+            # 非字母和数字 判断为非货号
+            return False
+
+    def show_info(self, text: str):
+        self.show_progress_detail_sign.emit(text)
+        # self.windows.show_progress_detail(text)
+
+    def show_text_browser(self, text):
+        pass
+
+    def state_change(self, to_state: int):
+        self.state = to_state
+
+
+class Picture:
+
+    def __init__(self, in_path):
+        self.im = Image.open(in_path)
+        self.x, self.y = self.im.size
+        # print(self.x, self.y)
+
+    def resize(self, width):
+        re_x = int(width)
+        re_y = int(self.y * re_x / self.x)
+        self.im = self.im.resize((re_x, re_y), Image.Resampling.LANCZOS)
+        self.x, self.y = self.im.size
+
+    def save_to_io(self, format):
+        img = BytesIO()
+        self.im.save(img, format=format)  # format: PNG or JPEG
+        img.seek(0)  # rewind to the start
+        return img

+ 2 - 0
python/settings.py

@@ -92,6 +92,8 @@ BACKUP_COUNTS = config.get("log", "backup_counts")
 # 远程服务器地址
 HLM_HOST = config.get("log", "hlm_host")
 
+PROJECT = config.get("log", "project")
+
 
 # ----------------------------------
 mcu_config_dict = config.items("mcu_config")