处理这个python打开紫鸟

This commit is contained in:
super
2026-03-30 23:15:08 +08:00
parent d3dd4898a4
commit bf802c1019
3 changed files with 541 additions and 127 deletions

View File

@@ -16,6 +16,7 @@ import webview
import threading import threading
import time import time
import requests import requests
import subprocess
with open("version.txt","w",encoding="utf-8") as file: with open("version.txt","w",encoding="utf-8") as file:
@@ -234,6 +235,21 @@ class WindowAPI:
except Exception as e: except Exception as e:
return {'success': False, 'error': str(e)} return {'success': False, 'error': str(e)}
def open_external_url(self, url):
if not url or not str(url).strip():
return {'success': False, 'error': '链接为空'}
target = str(url).strip()
try:
if sys.platform.startswith('win'):
os.startfile(target)
elif sys.platform == 'darwin':
subprocess.Popen(['open', target])
else:
subprocess.Popen(['xdg-open', target])
return {'success': True}
except Exception as e:
return {'success': False, 'error': str(e)}
def enqueue_json(self, data): def enqueue_json(self, data):
"""保存任务到队列""" """保存任务到队列"""
try: try:
@@ -350,7 +366,7 @@ def main():
) )
api = WindowAPI(window) api = WindowAPI(window)
window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder, window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder,
api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.enqueue_json) api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.open_external_url,api.enqueue_json)
webview.start( webview.start(
debug=True, debug=True,
storage_path=cache_path, storage_path=cache_path,

View File

@@ -99,27 +99,27 @@
<div v-if="currentSectionItems.length" class="result-subsection"> <div v-if="currentSectionItems.length" class="result-subsection">
<div class="result-subsection-title">当前任务</div> <div class="result-subsection-title">当前任务</div>
<ul class="task-list clean-result-list"> <ul class="task-list clean-result-list">
<li <li v-for="item in currentSectionItems"
v-for="item in currentSectionItems"
:key="`current-${item.taskId || item.resultId || item.sourceFilename}`" :key="`current-${item.taskId || item.resultId || item.sourceFilename}`"
class="task-item split-result-item">
class="task-item split-result-item"
>
<div class="left split-result-main"> <div class="left split-result-main">
<span class="id" :title="item.sourceFilename">{{ item.sourceFilename || '-' }}</span> <span class="id" :title="item.sourceFilename">{{ item.sourceFilename || '-' }}</span>
<div class="files">店铺名{{ item.shopName || '-' }}</div> <div class="files">店铺名{{ item.shopName || '-' }}</div>
<div class="files">匹配结果{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}</div> <div class="files">匹配结果{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}</div>
<div v-if="item.platform" class="files">平台{{ item.platform }}</div> <div v-if="item.platform" class="files">平台{{ item.platform }}</div>
<div v-if="getQueueStatus(item)" class="files">队列状态{{ getQueueStatus(item) }}</div> <div v-if="getQueueStatus(item)" class="files">队列状态{{ getQueueStatus(item) }}</div>
<div v-if="item.countryCount !== undefined && item.matched" class="files">国家数{{ item.countryCount }}</div> <div v-if="item.countryCount !== undefined && item.matched" class="files">国家数{{ item.countryCount
<div v-if="item.totalRows !== undefined && item.matched" class="time">去重后 {{ item.totalRows }} </div> }}</div>
<div v-if="item.totalRows !== undefined && item.matched" class="time">去重后 {{ item.totalRows }}
</div>
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block"> <div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
<div class="delete-brand-progress-header"> <div class="delete-brand-progress-header">
<span>任务进度</span> <span>任务进度</span>
<span>{{ formatProgressPercent(item.taskId!) }}%</span> <span>{{ formatProgressPercent(item.taskId!) }}%</span>
</div> </div>
<div class="delete-brand-progress-bar"> <div class="delete-brand-progress-bar">
<div class="delete-brand-progress-bar-fill" :style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div> <div class="delete-brand-progress-bar-fill"
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
</div> </div>
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div> <div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
</div> </div>
@@ -133,28 +133,15 @@
<span class="status" :class="getTaskStatusInfo(item).className"> <span class="status" :class="getTaskStatusInfo(item).className">
{{ getTaskStatusInfo(item).text }} {{ getTaskStatusInfo(item).text }}
</span> </span>
<button <button v-if="item.openStoreUrl" type="button" class="download" @click="triggerItemRun(item)">
v-if="item.openStoreUrl"
type="button"
class="download"
@click="triggerItemRun(item)"
>
打开紫鸟 打开紫鸟
</button> </button>
<button <button v-if="canDownloadTaskResult(item)" type="button" class="download"
v-if="canDownloadTaskResult(item)" @click="downloadTaskResult(item)">
type="button"
class="download"
@click="downloadTaskResult(item)"
>
下载 下载
</button> </button>
<button <button v-if="item.resultId" type="button" class="btn-delete"
v-if="item.resultId" @click="deleteHistoryRecord(item.resultId)">
type="button"
class="btn-delete"
@click="deleteHistoryRecord(item.resultId)"
>
删除 删除
</button> </button>
</div> </div>
@@ -165,26 +152,26 @@
<div v-if="historySectionItems.length" class="result-subsection"> <div v-if="historySectionItems.length" class="result-subsection">
<div class="result-subsection-title">历史记录</div> <div class="result-subsection-title">历史记录</div>
<ul class="task-list clean-result-list"> <ul class="task-list clean-result-list">
<li <li v-for="item in historySectionItems"
v-for="item in historySectionItems"
:key="`history-${item.taskId || item.resultId || item.sourceFilename}`" :key="`history-${item.taskId || item.resultId || item.sourceFilename}`"
class="task-item split-result-item">
class="task-item split-result-item"
>
<div class="left split-result-main"> <div class="left split-result-main">
<span class="id" :title="item.sourceFilename">{{ item.sourceFilename || '-' }}</span> <span class="id" :title="item.sourceFilename">{{ item.sourceFilename || '-' }}</span>
<div class="files">店铺名{{ item.shopName || '-' }}</div> <div class="files">店铺名{{ item.shopName || '-' }}</div>
<div class="files">匹配结果{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}</div> <div class="files">匹配结果{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}</div>
<div v-if="item.platform" class="files">平台{{ item.platform }}</div> <div v-if="item.platform" class="files">平台{{ item.platform }}</div>
<div v-if="item.countryCount !== undefined && item.matched" class="files">国家数{{ item.countryCount }}</div> <div v-if="item.countryCount !== undefined && item.matched" class="files">国家数{{ item.countryCount
<div v-if="item.totalRows !== undefined && item.matched" class="time">去重后 {{ item.totalRows }} </div> }}</div>
<div v-if="item.totalRows !== undefined && item.matched" class="time">去重后 {{ item.totalRows }}
</div>
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block"> <div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
<div class="delete-brand-progress-header"> <div class="delete-brand-progress-header">
<span>任务进度</span> <span>任务进度</span>
<span>{{ formatProgressPercent(item.taskId!) }}%</span> <span>{{ formatProgressPercent(item.taskId!) }}%</span>
</div> </div>
<div class="delete-brand-progress-bar"> <div class="delete-brand-progress-bar">
<div class="delete-brand-progress-bar-fill" :style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div> <div class="delete-brand-progress-bar-fill"
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
</div> </div>
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div> <div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
</div> </div>
@@ -199,20 +186,12 @@
{{ getTaskStatusInfo(item).text }} {{ getTaskStatusInfo(item).text }}
</span> </span>
<!-- 历史记录区不再显示打开紫鸟按钮 --> <!-- 历史记录区不再显示打开紫鸟按钮 -->
<button <button v-if="canDownloadTaskResult(item)" type="button" class="download"
v-if="canDownloadTaskResult(item)" @click="downloadTaskResult(item)">
type="button"
class="download"
@click="downloadTaskResult(item)"
>
下载 下载
</button> </button>
<button <button v-if="item.resultId" type="button" class="btn-delete"
v-if="item.resultId" @click="deleteHistoryRecord(item.resultId)">
type="button"
class="btn-delete"
@click="deleteHistoryRecord(item.resultId)"
>
删除 删除
</button> </button>
</div> </div>
@@ -793,8 +772,17 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
return return
} }
const api = getPywebviewApi()
if (item.openStoreUrl) { if (item.openStoreUrl) {
window.open(item.openStoreUrl, '_blank') if (api?.open_external_url) {
const openResult = await api.open_external_url(item.openStoreUrl)
if (!openResult?.success) {
ElMessage.error(openResult?.error || '打开紫鸟失败')
return
}
} else {
window.open(item.openStoreUrl, '_self')
}
} }
const taskId = item.taskId const taskId = item.taskId
@@ -803,7 +791,6 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId) const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
if (!sessionTask) return if (!sessionTask) return
const api = getPywebviewApi()
if (!api?.enqueue_json) { if (!api?.enqueue_json) {
ElMessage.error('当前环境未启用 pywebview enqueue_json浏览器环境不会推送') ElMessage.error('当前环境未启用 pywebview enqueue_json浏览器环境不会推送')
return return
@@ -872,71 +859,481 @@ onUnmounted(() => {
</script> </script>
<style scoped> <style scoped>
.queue-debug-card { margin-top: 18px; padding: 14px; border: 1px solid #2a2a2a; border-radius: 10px; background: #202020; } .queue-debug-card {
.queue-debug-title { margin-bottom: 8px; } margin-top: 18px;
.queue-debug-line { color: #b8c1cc; font-size: 12px; line-height: 1.6; margin-bottom: 8px; } padding: 14px;
.queue-debug-payload { margin: 0; max-height: 220px; overflow: auto; padding: 10px; border-radius: 8px; background: #141414; color: #8fd3ff; font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; } border: 1px solid #2a2a2a;
.module-page { min-height: 100vh; background: #1a1a1a; } border-radius: 10px;
.main-content { display: flex; min-height: calc(100vh - 56px); height: calc(100vh - 56px); } background: #202020;
.left-panel { width: 380px; background: #1e1e1e; padding: 20px; overflow-y: auto; border-right: 1px solid #2a2a2a; } }
.right-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #1a1a1a; }
.section-title { font-size: 13px; color: #bbb; margin-bottom: 10px; } .queue-debug-title {
.upload-zone { border: 1px dashed #3a3a3a; border-radius: 10px; padding: 24px; text-align: center; background: #252525; margin-bottom: 20px; transition: all 0.2s ease; } margin-bottom: 8px;
.upload-zone:hover { border-color: #3498db; background: #2a2a2a; } }
.hint { color: #888; font-size: 13px; margin-bottom: 12px; line-height: 1.5; }
.btns { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; } .queue-debug-line {
.opt-btn, .btn-run, .download, .btn-delete { border: none; cursor: pointer; transition: all 0.2s ease; } color: #b8c1cc;
.opt-btn { padding: 8px 16px; font-size: 13px; color: #ccc; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 6px; } font-size: 12px;
.opt-btn:hover { color: #3498db; background: #333; border-color: #3498db; } line-height: 1.6;
.selected-files { margin-top: 14px; font-size: 12px; color: #888; max-height: 112px; overflow-y: auto; text-align: left; } margin-bottom: 8px;
.selected-files span { display: block; margin: 4px 0; word-break: break-all; } }
.more-line { color: #b8c1cc; }
.split-selected-files { max-height: 120px; min-height: 72px; padding-right: 4px; } .queue-debug-payload {
.option-group { margin-bottom: 20px; } margin: 0;
.radio-item { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; padding: 10px 12px; border-radius: 8px; background: #252525; border: 1px solid #2a2a2a; margin-bottom: 8px; transition: all 0.2s ease; } max-height: 220px;
.radio-item:hover, .radio-item.active { background: #2a2a2a; border-color: #3a3a3a; } overflow: auto;
.radio-item input { margin-top: 3px; } padding: 10px;
.label { font-weight: 500; color: #e0e0e0; font-size: 13px; } border-radius: 8px;
.desc { font-size: 12px; color: #888; margin-top: 2px; line-height: 1.5; font-weight: 400; } background: #141414;
.run-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 12px; margin-top: 16px; } color: #8fd3ff;
.btn-run { padding: 10px 22px; background: #3498db; color: #fff; border-radius: 8px; font-size: 14px; } font-size: 12px;
.btn-run:hover { background: #2980b9; } line-height: 1.5;
.btn-run:disabled { background: #555; color: #999; cursor: not-allowed; } white-space: pre-wrap;
.loading-msg { color: #3498db; font-size: 13px; } word-break: break-all;
.panel-header { padding: 16px 20px; border-bottom: 1px solid #2a2a2a; font-size: 15px; font-weight: 600; color: #ddd; } }
.task-list-wrap { flex: 1; overflow-y: auto; padding: 16px; }
.task-list { list-style: none; margin: 0; padding: 0; } .module-page {
.task-item { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 16px; border: 1px solid #2a2a2a; border-radius: 8px; margin-bottom: 10px; background: #1e1e1e; } min-height: 100vh;
.split-result-item { align-items: flex-start; } background: #1a1a1a;
.left { flex: 1; min-width: 0; } }
.split-result-main { display: flex; flex-direction: column; gap: 4px; }
.id { display: inline-block; font-weight: 600; color: #e0e0e0; font-size: 13px; } .main-content {
.files { font-size: 12px; color: #888; margin-top: 4px; } display: flex;
.time { font-size: 12px; color: #666; margin-top: 2px; } min-height: calc(100vh - 56px);
.split-entry-list { line-height: 1.6; word-break: break-all; max-width: 100%; } height: calc(100vh - 56px);
.delete-brand-preview { white-space: normal; } }
.delete-brand-progress-block { margin-top: 8px; }
.delete-brand-progress-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; font-size: 12px; color: #8fbfff; } .left-panel {
.delete-brand-progress-bar { width: 100%; height: 8px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; } width: 380px;
.delete-brand-progress-bar-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, #2d8cf0 0%, #4db3ff 100%); transition: width 0.3s ease; } background: #1e1e1e;
.delete-brand-progress { color: #69b6ff; line-height: 1.6; white-space: normal; margin-top: 6px; } padding: 20px;
.task-right { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; } overflow-y: auto;
.split-result-actions { flex-shrink: 0; min-width: 180px; justify-content: flex-end; align-self: center; } border-right: 1px solid #2a2a2a;
.status { padding: 4px 10px; border-radius: 6px; font-size: 12px; white-space: nowrap; } }
.status.success { background: rgba(46, 204, 113, 0.18); color: #2ecc71; }
.status.failed { background: rgba(231, 76, 60, 0.18); color: #ff6b6b; } .right-panel {
.status.running { background: rgba(52, 152, 219, 0.18); color: #3498db; } flex: 1;
.download { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(52, 152, 219, 0.18); color: #69b6ff; } display: flex;
.download:hover { background: rgba(52, 152, 219, 0.28); } flex-direction: column;
.btn-delete { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(231, 76, 60, 0.12); color: #ff8f8f; } min-width: 0;
.btn-delete:hover { background: rgba(231, 76, 60, 0.22); } background: #1a1a1a;
.empty-tasks { color: #666; font-size: 13px; padding: 24px 8px; } }
.clean-placeholder { max-height: 112px; }
.clean-result-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 220px)); gap: 16px; margin-bottom: 18px; } .section-title {
.summary-card { padding: 16px 18px; border: 1px solid #2a2a2a; border-radius: 10px; background: #1e1e1e; } font-size: 13px;
.summary-card strong { display: block; margin-top: 8px; font-size: 24px; color: #eaf4ff; } color: #bbb;
.summary-label { font-size: 12px; color: #8d8d8d; } margin-bottom: 10px;
.result-list-wrap { border: 1px solid #2a2a2a; border-radius: 10px; background: #1e1e1e; min-height: 260px; } }
.result-list-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid #2a2a2a; font-size: 14px; color: #ddd; }
@media (max-width: 1100px) { .main-content { flex-direction: column; height: auto; } .left-panel { width: 100%; border-right: none; border-bottom: 1px solid #2a2a2a; } .right-panel { min-height: 420px; } .task-item { flex-direction: column; align-items: flex-start; } .task-right { width: 100%; justify-content: flex-start; } .split-result-actions { min-width: 0; align-self: flex-start; } .clean-result-summary { grid-template-columns: 1fr; } } .upload-zone {
border: 1px dashed #3a3a3a;
border-radius: 10px;
padding: 24px;
text-align: center;
background: #252525;
margin-bottom: 20px;
transition: all 0.2s ease;
}
.upload-zone:hover {
border-color: #3498db;
background: #2a2a2a;
}
.hint {
color: #888;
font-size: 13px;
margin-bottom: 12px;
line-height: 1.5;
}
.btns {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.opt-btn,
.btn-run,
.download,
.btn-delete {
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.opt-btn {
padding: 8px 16px;
font-size: 13px;
color: #ccc;
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 6px;
}
.opt-btn:hover {
color: #3498db;
background: #333;
border-color: #3498db;
}
.selected-files {
margin-top: 14px;
font-size: 12px;
color: #888;
max-height: 112px;
overflow-y: auto;
text-align: left;
}
.selected-files span {
display: block;
margin: 4px 0;
word-break: break-all;
}
.more-line {
color: #b8c1cc;
}
.split-selected-files {
max-height: 120px;
min-height: 72px;
padding-right: 4px;
}
.option-group {
margin-bottom: 20px;
}
.radio-item {
display: flex;
align-items: flex-start;
gap: 10px;
cursor: pointer;
padding: 10px 12px;
border-radius: 8px;
background: #252525;
border: 1px solid #2a2a2a;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.radio-item:hover,
.radio-item.active {
background: #2a2a2a;
border-color: #3a3a3a;
}
.radio-item input {
margin-top: 3px;
}
.label {
font-weight: 500;
color: #e0e0e0;
font-size: 13px;
}
.desc {
font-size: 12px;
color: #888;
margin-top: 2px;
line-height: 1.5;
font-weight: 400;
}
.run-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px 12px;
margin-top: 16px;
}
.btn-run {
padding: 10px 22px;
background: #3498db;
color: #fff;
border-radius: 8px;
font-size: 14px;
}
.btn-run:hover {
background: #2980b9;
}
.btn-run:disabled {
background: #555;
color: #999;
cursor: not-allowed;
}
.loading-msg {
color: #3498db;
font-size: 13px;
}
.panel-header {
padding: 16px 20px;
border-bottom: 1px solid #2a2a2a;
font-size: 15px;
font-weight: 600;
color: #ddd;
}
.task-list-wrap {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.task-list {
list-style: none;
margin: 0;
padding: 0;
}
.task-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 16px;
border: 1px solid #2a2a2a;
border-radius: 8px;
margin-bottom: 10px;
background: #1e1e1e;
}
.split-result-item {
align-items: flex-start;
}
.left {
flex: 1;
min-width: 0;
}
.split-result-main {
display: flex;
flex-direction: column;
gap: 4px;
}
.id {
display: inline-block;
font-weight: 600;
color: #e0e0e0;
font-size: 13px;
}
.files {
font-size: 12px;
color: #888;
margin-top: 4px;
}
.time {
font-size: 12px;
color: #666;
margin-top: 2px;
}
.split-entry-list {
line-height: 1.6;
word-break: break-all;
max-width: 100%;
}
.delete-brand-preview {
white-space: normal;
}
.delete-brand-progress-block {
margin-top: 8px;
}
.delete-brand-progress-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
font-size: 12px;
color: #8fbfff;
}
.delete-brand-progress-bar {
width: 100%;
height: 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.delete-brand-progress-bar-fill {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #2d8cf0 0%, #4db3ff 100%);
transition: width 0.3s ease;
}
.delete-brand-progress {
color: #69b6ff;
line-height: 1.6;
white-space: normal;
margin-top: 6px;
}
.task-right {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: flex-end;
}
.split-result-actions {
flex-shrink: 0;
min-width: 180px;
justify-content: flex-end;
align-self: center;
}
.status {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
white-space: nowrap;
}
.status.success {
background: rgba(46, 204, 113, 0.18);
color: #2ecc71;
}
.status.failed {
background: rgba(231, 76, 60, 0.18);
color: #ff6b6b;
}
.status.running {
background: rgba(52, 152, 219, 0.18);
color: #3498db;
}
.download {
padding: 6px 10px;
border-radius: 6px;
font-size: 12px;
background: rgba(52, 152, 219, 0.18);
color: #69b6ff;
}
.download:hover {
background: rgba(52, 152, 219, 0.28);
}
.btn-delete {
padding: 6px 10px;
border-radius: 6px;
font-size: 12px;
background: rgba(231, 76, 60, 0.12);
color: #ff8f8f;
}
.btn-delete:hover {
background: rgba(231, 76, 60, 0.22);
}
.empty-tasks {
color: #666;
font-size: 13px;
padding: 24px 8px;
}
.clean-placeholder {
max-height: 112px;
}
.clean-result-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 220px));
gap: 16px;
margin-bottom: 18px;
}
.summary-card {
padding: 16px 18px;
border: 1px solid #2a2a2a;
border-radius: 10px;
background: #1e1e1e;
}
.summary-card strong {
display: block;
margin-top: 8px;
font-size: 24px;
color: #eaf4ff;
}
.summary-label {
font-size: 12px;
color: #8d8d8d;
}
.result-list-wrap {
border: 1px solid #2a2a2a;
border-radius: 10px;
background: #1e1e1e;
min-height: 260px;
}
.result-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 16px;
border-bottom: 1px solid #2a2a2a;
font-size: 14px;
color: #ddd;
}
@media (max-width: 1100px) {
.main-content {
flex-direction: column;
height: auto;
}
.left-panel {
width: 100%;
border-right: none;
border-bottom: 1px solid #2a2a2a;
}
.right-panel {
min-height: 420px;
}
.task-item {
flex-direction: column;
align-items: flex-start;
}
.task-right {
width: 100%;
justify-content: flex-start;
}
.split-result-actions {
min-width: 0;
align-self: flex-start;
}
.clean-result-summary {
grid-template-columns: 1fr;
}
}
</style> </style>

View File

@@ -21,6 +21,7 @@ export interface PywebviewApi {
save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }> save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }>
save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }> save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }>
enqueue_json?: (data: unknown) => Promise<{ success: boolean; queue_size?: number; error?: string }> enqueue_json?: (data: unknown) => Promise<{ success: boolean; queue_size?: number; error?: string }>
open_external_url?: (url: string) => Promise<{ success: boolean; error?: string }>
} }
declare global { declare global {