This commit is contained in:
parent
0d4315431d
commit
1de79c6fe0
49414
appTranslator.log
49414
appTranslator.log
File diff suppressed because one or more lines are too long
19
config.ini
19
config.ini
|
|
@ -32,18 +32,23 @@ image_selector_template = 'div#productMainContentContainerId li:nth-child({index
|
|||
price_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{index}]/div/div[1]/div/div[3]/div[1]/div[2]/button/span/sup'
|
||||
|
||||
# 옵션 상자
|
||||
option_box_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc'
|
||||
excluded_option_marker = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dfauwV.bXsMpn'
|
||||
delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) span:has-text("삭제")'
|
||||
; option_box_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc'
|
||||
|
||||
option_box_selector = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{index}]/div/div[1]/div/div[2]/div'
|
||||
; excluded_option_marker = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dfauwV.bXsMpn'
|
||||
excluded_option_marker = '.bXsMpn.sc-dfauwV'
|
||||
; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) span:has-text("삭제")'
|
||||
; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) div.sc-igZIGL.kQDmyq'
|
||||
delete_button_selector = '.kQDmyq.sc-igZIGL'
|
||||
confirm_delete_button_selector = '.ant-modal.css-1li46mu.ant-modal-confirm.ant-modal-confirm-confirm button:has-text("삭제")'
|
||||
; add_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dRGYJT.hmQUGb'
|
||||
add_button_selector = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div'
|
||||
add_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dRGYJT.hmQUGb'
|
||||
; add_button_selector = 'div.hmQUGb.sc-dRGYJT'
|
||||
; add_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-krITIZ.ckztYT'
|
||||
; file_upload_button_selector = '.ant-modal-content button:has-text("클릭 or 드레그로 파일 업로드")'
|
||||
file_upload_button_selector = 'div.ant-modal-body > span > div > span > div'
|
||||
file_upload_button_selector = 'span.ant-upload-btn input[type="file"]'
|
||||
|
||||
; confirm_upload_button_selector = '.ant-modal-content button:has-text("이미지 삽입")'
|
||||
confirm_upload_button_selector = 'div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary'
|
||||
confirm_upload_button_selector = 'button.ant-btn-primary span:text('이미지 삽입')'
|
||||
|
||||
low_order_button_locator = 'button:has-text("가격 낮은 순")'
|
||||
AtoZ_button_locator = 'button:has-text("A-Z")'
|
||||
|
|
|
|||
19
gui.py
19
gui.py
|
|
@ -1,4 +1,4 @@
|
|||
from PySide6.QtWidgets import QInputDialog, QWidget, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
|
||||
from PySide6.QtWidgets import QInputDialog, QWidget, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
|
||||
from PySide6.QtCore import Qt, QRect, QSettings, QTimer
|
||||
from toggleSwitch import ToggleSwitch
|
||||
from browser_control import BrowserController
|
||||
|
|
@ -94,6 +94,7 @@ class TranslationApp(QWidget):
|
|||
'watermark': False, # 워터마크 토글 추가
|
||||
'watermark_text': "WaterMark", # 워터마크 텍스트 저장
|
||||
'opacity_percent': 25, # 워터마크 투명도
|
||||
'max_option_count': 20, # 최대 선택가능한 옵션 수
|
||||
}
|
||||
|
||||
# 이전에 저장된 설정 불러오기
|
||||
|
|
@ -332,6 +333,15 @@ class TranslationApp(QWidget):
|
|||
# 확인 버튼 클릭 시 watermark_text 업데이트
|
||||
self.watermark_confirm_button.clicked.connect(self.update_watermark_text)
|
||||
|
||||
# 최대 옵션수
|
||||
self.max_option_count_label = QLabel("최대옵션수", self)
|
||||
self.max_option_count_input = QSpinBox(self)
|
||||
self.max_option_count_input.setMinimum(0) # 최소값 0
|
||||
self.max_option_count_input.setMaximum(100) # 최대값 100
|
||||
self.max_option_count_input.setValue(20) # 기본값 0
|
||||
self.max_option_count_input.setToolTip("0으로 설정시 최대") # 툴팁 설정
|
||||
self.max_option_count_input.valueChanged.connect(self.update_max_option_count) # 값 변경 시 update_max_option_count 메서드 호출
|
||||
|
||||
# 워터마크 관련 요소들을 하나의 QHBoxLayout에 추가 (비율 2:3:1)
|
||||
watermark_layout = QHBoxLayout()
|
||||
watermark_layout.addWidget(self.watermark_text_label, 2)
|
||||
|
|
@ -340,6 +350,8 @@ class TranslationApp(QWidget):
|
|||
|
||||
# 필요한 레이아웃에 추가 (toggle_layout에 추가)
|
||||
self.toggle_layout.addLayout(watermark_layout, 7, 0, 1, 4)
|
||||
self.toggle_layout.addWidget(self.max_option_count_label, 8, 0)
|
||||
self.toggle_layout.addWidget(self.max_option_count_input, 8, 1)
|
||||
|
||||
# 초기에는 워터마크 입력창과 버튼 숨김
|
||||
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.watermark_confirm_button, None)])
|
||||
|
|
@ -591,6 +603,11 @@ class TranslationApp(QWidget):
|
|||
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
||||
self.logger.debug(f"Updated watermark text: {self.toggle_states['watermark_text']}")
|
||||
|
||||
def update_max_option_count(self):
|
||||
"""QSpinBox에 입력된 값을 toggle_states['max_option_count']에 저장"""
|
||||
self.toggle_states['max_option_count'] = self.max_option_count_input.value() # 정수 값 가져오기
|
||||
self.logger.debug(f"최대 선택 가능 옵션 수 업데이트: {self.toggle_states['max_option_count']}")
|
||||
|
||||
def on_admin_toggle_clicked(self, is_checked):
|
||||
"""관리자 토글 상태에 따라 관리자와 직원 필드를 표시/숨김"""
|
||||
if is_checked:
|
||||
|
|
|
|||
126
option.py
126
option.py
|
|
@ -207,7 +207,7 @@ class OptionHandler:
|
|||
return self.option_info
|
||||
|
||||
# 3. 가격 낮은 순 정렬 클릭
|
||||
await self.low_order_click()
|
||||
# await self.low_order_click()
|
||||
|
||||
try:
|
||||
self.logger.info(f"옵션 정보 수집")
|
||||
|
|
@ -620,8 +620,44 @@ class OptionHandler:
|
|||
except Exception as e:
|
||||
self.logger.error(f"옵션 필터링 및 조정 중 오류 발생: {e}", exc_info=True)
|
||||
|
||||
|
||||
async def adjust_options(self, filtered_option_names, max_option_count):
|
||||
"""
|
||||
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
|
||||
:param filtered_option_names: 필터링된 옵션 리스트
|
||||
:param max_option_count: 최대 선택 가능한 옵션 수 (0이면 제한 없음)
|
||||
"""
|
||||
try:
|
||||
selected_count = 0 # 현재까지 선택된 옵션 수
|
||||
|
||||
for i, name in enumerate(self.option_info['original_names'].values()):
|
||||
# 최대 옵션 수에 도달하면 더 이상 선택하지 않음
|
||||
if max_option_count > 0 and selected_count >= max_option_count:
|
||||
break
|
||||
|
||||
checkbox_selector = self.checkbox_selector_template.format(index=i+1)
|
||||
checkbox_element = await self.page.query_selector(checkbox_selector)
|
||||
|
||||
# 디버깅 로그: 현재 옵션 이름과 필터링된 옵션 이름 확인
|
||||
self.logger.debug(f"옵션 이름: {name}, 필터링된 옵션에 포함 여부: {name in filtered_option_names}")
|
||||
|
||||
if checkbox_element:
|
||||
# 필터링된 옵션에 포함되고, 선택 가능한 수량 내라면 선택
|
||||
if name in filtered_option_names and (max_option_count == 0 or selected_count < max_option_count):
|
||||
await checkbox_element.click()
|
||||
self.logger.debug(f"옵션 '{name}' 체크함")
|
||||
self.option_info['checked_states'][name] = True
|
||||
selected_count += 1
|
||||
# 필터링된 옵션에 포함되지 않으면 선택 해제
|
||||
else:
|
||||
await checkbox_element.click()
|
||||
self.logger.debug(f"옵션 '{name}' 체크 해제함")
|
||||
self.option_info['checked_states'][name] = False
|
||||
|
||||
self.logger.debug(f"옵션 체크 상태 조정 완료. 선택된 옵션 수: {selected_count}/{max_option_count if max_option_count > 0 else '무제한'}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
|
||||
|
||||
async def adjust_options_without_max_count(self, filtered_option_names, max_option_count):
|
||||
"""
|
||||
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
|
||||
:param filtered_options: 필터링된 옵션 리스트
|
||||
|
|
@ -680,34 +716,38 @@ class OptionHandler:
|
|||
|
||||
try:
|
||||
# 모든 옵션 상자 요소 가져오기
|
||||
option_boxes = await self.page.query_selector_all(self.option_box_selector)
|
||||
# option_boxes = await self.page.query_selector_all(self.option_box_selector)
|
||||
|
||||
option_boxes = await self.page.query_selector_all("div#productMainContentContainerId li > div > div:nth-child(1) > div > div:nth-child(2) > div")
|
||||
|
||||
# option_image_element = await self.page.locator("xpath=//*[@id='productMainContentContainerId']/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[1]/div/div[1]/div/div[2]/div/img").element_handle()
|
||||
|
||||
total_options = len(option_boxes)
|
||||
self.logger.debug(f"총 {total_options}개의 옵션 이미지 번역을 시작합니다.")
|
||||
|
||||
# 각 옵션 상자를 인덱스로 순회
|
||||
for index in range(1, total_options + 1):
|
||||
# 실제 옵션 이미지가 존재하는 항목에만 인덱스를 적용하기 위해 별도 카운터 사용
|
||||
translated_index = 1
|
||||
# 각 옵션 상자를 순회
|
||||
for index, option_box in enumerate(option_boxes, start=1):
|
||||
# 선택자에서 인덱스를 반영해 동적 선택자를 생성
|
||||
excluded_marker_selector = self.excluded_option_marker.format(index=index)
|
||||
delete_button_selector = self.delete_button_selector.format(index=index)
|
||||
add_button_selector = self.add_button_selector.format(index=index)
|
||||
|
||||
# 제외된 옵션 확인
|
||||
excluded_marker = await self.page.query_selector(excluded_marker_selector)
|
||||
if excluded_marker:
|
||||
self.logger.debug(f"{index}번째 옵션은 제외된 옵션입니다. 번역을 생략합니다.")
|
||||
continue
|
||||
|
||||
# 옵션 이미지 URL 가져오기
|
||||
option_image = await option_boxes[index - 1].query_selector("img")
|
||||
if not option_image:
|
||||
# 옵션 이미지가 존재하는지 확인
|
||||
option_image = await option_box.query_selector("img")
|
||||
if option_image is None:
|
||||
self.logger.debug(f"{index}번째 옵션에 이미지가 없습니다. 다음 옵션으로 이동합니다.")
|
||||
continue
|
||||
|
||||
option_image_url = await option_image.get_attribute("src")
|
||||
self.logger.debug(f"{index}번째 옵션 이미지 URL: {option_image_url}")
|
||||
|
||||
# 이미지가 SVG 형식일 경우 번역을 건너뜀
|
||||
if option_image_url.endswith(".svg"):
|
||||
self.logger.debug(f"{index}번째 옵션은 SVG 이미지입니다. 번역을 생략합니다.")
|
||||
continue
|
||||
|
||||
# 이미지 번역 및 업로드
|
||||
translated_image_path = os.path.join(temp_dir, f"translated_option_{index}.png") # 이미지 저장 경로 설정
|
||||
translated_image_path = os.path.join(temp_dir, f"translated_option_{translated_index}.png") # 이미지 저장 경로 설정
|
||||
|
||||
try:
|
||||
# 이미지 번역
|
||||
|
|
@ -715,10 +755,14 @@ class OptionHandler:
|
|||
is_success_translated = self.whale_translator.translate_image(option_image_url)
|
||||
self.clipboardImageManager.process_clipboard(option_image_url, is_success_translated, toggle_states, translated_image_path)
|
||||
self.logger.debug(f"{index}번째 옵션의 번역 이미지 저장 완료: {translated_image_path}")
|
||||
|
||||
self.browser_controller.switch_to_chrome() # 크롬으로 포커스 이동
|
||||
|
||||
if is_success_translated and os.path.exists(translated_image_path):
|
||||
# 삭제 버튼 클릭
|
||||
delete_button = await self.page.query_selector(delete_button_selector)
|
||||
# delete_button = await self.page.query_selector(delete_button_selector)
|
||||
delete_button = await option_box.query_selector(".sc-igZIGL.kQDmyq")
|
||||
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지 삭제 버튼 가져오기")
|
||||
|
||||
if delete_button:
|
||||
|
|
@ -729,23 +773,35 @@ class OptionHandler:
|
|||
if confirm_delete_button:
|
||||
await confirm_delete_button.click()
|
||||
self.logger.debug(f"{index}번째 옵션의 기존 이미지가 삭제되었습니다.")
|
||||
|
||||
# '+ 버튼' 클릭 후 파일 업로드
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 가져오기")
|
||||
add_button = await self.page.query_selector(add_button_selector)
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 : {add_button}")
|
||||
if add_button:
|
||||
await add_button.click()
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지 추가 버튼 클릭")
|
||||
file_upload_button = await self.page.wait_for_selector(self.file_upload_button_selector)
|
||||
self.logger.debug(f"{index}번째 옵션의 파일업로드 버튼 가져오기")
|
||||
await file_upload_button.set_input_files(translated_image_path)
|
||||
self.logger.debug(f"{index}번째 옵션의 파일 업로드")
|
||||
|
||||
# '+ 버튼' 클릭 후 파일 업로드
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 가져오기")
|
||||
# add_button = await self.page.query_selector(add_button_selector)
|
||||
add_button = option_box
|
||||
# add_button = await option_box.query_selector('.//div[contains(@class, "sc-krITIZ") and contains(@class, "ckztYT")]')
|
||||
|
||||
# add_button = await option_box.query_selector(f'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div')
|
||||
# add_button = await option_box.query_selector(f'div:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div')
|
||||
|
||||
if add_button:
|
||||
await add_button.click()
|
||||
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 클릭")
|
||||
|
||||
# 파일 업로드 영역의 input 요소 직접 선택 (수정된 부분)
|
||||
file_input = await self.page.query_selector(self.file_upload_button_selector) # Ant Design의 클래스 사용
|
||||
|
||||
if file_input:
|
||||
# Playwright의 set_input_files를 사용하여 파일 업로드 처리
|
||||
await file_input.set_input_files(translated_image_path)
|
||||
self.logger.debug(f"{index}번째 옵션의 파일 업로드 완료")
|
||||
|
||||
# '이미지 삽입' 버튼 클릭
|
||||
confirm_upload_button = await self.page.wait_for_selector(self.confirm_upload_button_selector)
|
||||
await confirm_upload_button.click()
|
||||
self.logger.debug(f"{index}번째 옵션에 이미지가 업로드되었습니다.")
|
||||
else:
|
||||
self.logger.error(f"{index}번째 옵션의 파일 입력 요소를 찾을 수 없습니다.")
|
||||
|
||||
# '이미지 삽입' 버튼 클릭
|
||||
confirm_upload_button = await self.page.wait_for_selector(self.confirm_upload_button_selector)
|
||||
await confirm_upload_button.click()
|
||||
self.logger.debug(f"{index}번째 옵션에 이미지가 업로드되었습니다.")
|
||||
finally:
|
||||
# 파일 사용 후 0.5초 대기하여 접근 완료 보장
|
||||
time.sleep(0.5)
|
||||
|
|
@ -753,6 +809,8 @@ class OptionHandler:
|
|||
if not debug_flag and os.path.exists(translated_image_path):
|
||||
os.remove(translated_image_path)
|
||||
self.logger.debug(f"{index}번째 옵션의 임시 번역 이미지 파일 삭제 완료: {translated_image_path}")
|
||||
# 실제 번역이 완료된 경우에만 인덱스 증가
|
||||
translated_index += 1
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"옵션 이미지 업데이트 중 오류 발생: {e}", exc_info=True)
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 277 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
Loading…
Reference in New Issue