2026-06-16 09:25:45 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
|
|
|
|
|
|
|
|
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal
|
|
|
|
|
|
from PySide6.QtGui import QColor
|
|
|
|
|
|
from PySide6.QtWidgets import (
|
|
|
|
|
|
QApplication,
|
|
|
|
|
|
QComboBox,
|
|
|
|
|
|
QHeaderView,
|
|
|
|
|
|
QHBoxLayout,
|
|
|
|
|
|
QLabel,
|
|
|
|
|
|
QPushButton,
|
|
|
|
|
|
QSizePolicy,
|
|
|
|
|
|
QStyle,
|
|
|
|
|
|
QStyledItemDelegate,
|
|
|
|
|
|
QStyleOptionProgressBar,
|
|
|
|
|
|
QTableView,
|
|
|
|
|
|
QVBoxLayout,
|
|
|
|
|
|
QWidget,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
from core.composer import compose
|
|
|
|
|
|
from core.models import BatchMode, ExportOptions, ImageAsset, TransformState
|
|
|
|
|
|
from services.file_service import get_output_dir, make_safe_output_path
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
# ── column layout ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
_COL_NUM = 0
|
|
|
|
|
|
_COL_GARMENT = 1
|
|
|
|
|
|
_COL_PRINT = 2
|
|
|
|
|
|
_COL_FOLDER = 3
|
|
|
|
|
|
_COL_PARAM = 4
|
|
|
|
|
|
_COL_STATUS = 5
|
|
|
|
|
|
_COL_OUTPUT = 6
|
|
|
|
|
|
_HEADERS = ["#", "衣服", "印花", "来源文件夹", "参数", "状态", "进度/输出文件"]
|
|
|
|
|
|
|
|
|
|
|
|
_STATUS_COLOR = {
|
|
|
|
|
|
"已完成": QColor("#107c10"),
|
|
|
|
|
|
"导出中": QColor("#0078d4"),
|
|
|
|
|
|
"失败": QColor("#c42b1c"),
|
|
|
|
|
|
"待导出": QColor("#555555"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── data model ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class QueueItem:
|
|
|
|
|
|
"""One entry in the composition queue."""
|
|
|
|
|
|
garment: ImageAsset
|
|
|
|
|
|
print_asset: ImageAsset
|
|
|
|
|
|
transform: Optional[TransformState] = None # None → use template transform
|
|
|
|
|
|
param_source: str = "模板" # "模板" | "已微调"
|
|
|
|
|
|
status: str = "待导出" # "待导出"|"导出中"|"已完成"|"失败"
|
|
|
|
|
|
progress: float = 0.0
|
|
|
|
|
|
output_path: Optional[str] = None
|
|
|
|
|
|
error: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_pairs(
|
|
|
|
|
|
garments: List[ImageAsset],
|
|
|
|
|
|
prints: List[ImageAsset],
|
|
|
|
|
|
mode: BatchMode,
|
|
|
|
|
|
) -> List[Tuple[ImageAsset, ImageAsset]]:
|
|
|
|
|
|
if not garments or not prints:
|
|
|
|
|
|
return []
|
|
|
|
|
|
if mode == BatchMode.ONE_TO_ONE:
|
|
|
|
|
|
return list(zip(garments, prints))
|
|
|
|
|
|
if mode == BatchMode.MANY_GARMENTS:
|
|
|
|
|
|
p = prints[0]
|
|
|
|
|
|
return [(g, p) for g in garments]
|
|
|
|
|
|
if mode == BatchMode.MANY_PRINTS:
|
|
|
|
|
|
g = garments[0]
|
|
|
|
|
|
return [(g, p) for p in prints]
|
|
|
|
|
|
# FULL_COMBO
|
|
|
|
|
|
return [(g, p) for g in garments for p in prints]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _QueueModel(QAbstractTableModel):
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self._items: List[QueueItem] = []
|
|
|
|
|
|
|
|
|
|
|
|
def set_items(self, items: List[QueueItem]):
|
|
|
|
|
|
self.beginResetModel()
|
|
|
|
|
|
self._items = list(items)
|
|
|
|
|
|
self.endResetModel()
|
|
|
|
|
|
|
|
|
|
|
|
def rowCount(self, parent=QModelIndex()):
|
|
|
|
|
|
return len(self._items)
|
|
|
|
|
|
|
|
|
|
|
|
def columnCount(self, parent=QModelIndex()):
|
|
|
|
|
|
return len(_HEADERS)
|
|
|
|
|
|
|
|
|
|
|
|
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
|
|
|
|
|
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
|
|
|
|
|
return _HEADERS[section]
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def data(self, index, role=Qt.DisplayRole):
|
|
|
|
|
|
if not index.isValid():
|
|
|
|
|
|
return None
|
|
|
|
|
|
item = self._items[index.row()]
|
|
|
|
|
|
col = index.column()
|
|
|
|
|
|
|
|
|
|
|
|
if role == Qt.DisplayRole:
|
|
|
|
|
|
if col == _COL_NUM:
|
|
|
|
|
|
return str(index.row() + 1)
|
|
|
|
|
|
if col == _COL_GARMENT:
|
|
|
|
|
|
return Path(item.garment.path).name
|
|
|
|
|
|
if col == _COL_PRINT:
|
|
|
|
|
|
return Path(item.print_asset.path).name
|
|
|
|
|
|
if col == _COL_FOLDER:
|
|
|
|
|
|
return Path(item.garment.path).parent.name
|
|
|
|
|
|
if col == _COL_PARAM:
|
|
|
|
|
|
return item.param_source
|
|
|
|
|
|
if col == _COL_STATUS:
|
|
|
|
|
|
return item.status
|
|
|
|
|
|
if col == _COL_OUTPUT:
|
|
|
|
|
|
if item.status == "已完成" and item.output_path:
|
|
|
|
|
|
return Path(item.output_path).name
|
|
|
|
|
|
if item.status == "失败":
|
|
|
|
|
|
return item.error or "未知错误"
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
if role == Qt.UserRole and col == _COL_OUTPUT:
|
|
|
|
|
|
return item.progress if item.status == "导出中" else None
|
|
|
|
|
|
|
|
|
|
|
|
if role == Qt.ForegroundRole and col == _COL_STATUS:
|
|
|
|
|
|
return _STATUS_COLOR.get(item.status)
|
|
|
|
|
|
|
|
|
|
|
|
if role == Qt.TextAlignmentRole and col == _COL_NUM:
|
|
|
|
|
|
return int(Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_item(self, row: int) -> Optional[QueueItem]:
|
|
|
|
|
|
return self._items[row] if 0 <= row < len(self._items) else None
|
|
|
|
|
|
|
|
|
|
|
|
def update_row(self, row: int):
|
|
|
|
|
|
if 0 <= row < len(self._items):
|
|
|
|
|
|
self.dataChanged.emit(
|
|
|
|
|
|
self.index(row, 0),
|
|
|
|
|
|
self.index(row, len(_HEADERS) - 1),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _ProgressDelegate(QStyledItemDelegate):
|
|
|
|
|
|
"""Paints a progress bar in the output column when status is '导出中'."""
|
|
|
|
|
|
|
|
|
|
|
|
def paint(self, painter, option, index):
|
|
|
|
|
|
progress = index.data(Qt.UserRole)
|
|
|
|
|
|
if progress is not None:
|
|
|
|
|
|
opt = QStyleOptionProgressBar()
|
|
|
|
|
|
opt.rect = option.rect
|
|
|
|
|
|
opt.minimum = 0
|
|
|
|
|
|
opt.maximum = 100
|
|
|
|
|
|
opt.progress = max(0, min(100, int(progress * 100)))
|
|
|
|
|
|
opt.text = "{}%".format(opt.progress)
|
|
|
|
|
|
opt.textVisible = True
|
|
|
|
|
|
QApplication.style().drawControl(QStyle.CE_ProgressBar, opt, painter)
|
|
|
|
|
|
else:
|
|
|
|
|
|
super().paint(painter, option, index)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── main widget ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
class QueuePanel(QWidget):
|
|
|
|
|
|
"""Bottom queue: batch mode selector, stats, action buttons, item table."""
|
|
|
|
|
|
|
|
|
|
|
|
item_activated = Signal(object) # QueueItem when a row is selected
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self._garments: List[ImageAsset] = []
|
|
|
|
|
|
self._prints: List[ImageAsset] = []
|
|
|
|
|
|
self._template_transform: Optional[TransformState] = None
|
|
|
|
|
|
self._export_options: ExportOptions = ExportOptions()
|
|
|
|
|
|
self._running = False
|
|
|
|
|
|
self._setup_ui()
|
|
|
|
|
|
self._apply_stylesheet()
|
|
|
|
|
|
|
|
|
|
|
|
# ── UI construction ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self):
|
|
|
|
|
|
col = QVBoxLayout(self)
|
|
|
|
|
|
col.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
col.setSpacing(0)
|
|
|
|
|
|
col.addWidget(self._make_ctrl_bar())
|
|
|
|
|
|
col.addWidget(self._make_table(), 1)
|
|
|
|
|
|
|
|
|
|
|
|
def _make_ctrl_bar(self) -> QWidget:
|
|
|
|
|
|
bar = QWidget()
|
|
|
|
|
|
bar.setObjectName("queueCtrlBar")
|
|
|
|
|
|
row = QHBoxLayout(bar)
|
|
|
|
|
|
row.setContentsMargins(8, 4, 8, 4)
|
|
|
|
|
|
row.setSpacing(8)
|
|
|
|
|
|
|
|
|
|
|
|
mode_lbl = QLabel("批量模式:")
|
|
|
|
|
|
mode_lbl.setObjectName("queueCtrlLabel")
|
|
|
|
|
|
|
|
|
|
|
|
self._mode_combo = QComboBox()
|
|
|
|
|
|
self._mode_combo.setObjectName("queueModeCombo")
|
|
|
|
|
|
self._mode_combo.addItem("全组合(矩阵)", BatchMode.FULL_COMBO)
|
|
|
|
|
|
self._mode_combo.addItem("多衣服 × 单印花", BatchMode.MANY_GARMENTS)
|
|
|
|
|
|
self._mode_combo.addItem("单衣服 × 多印花", BatchMode.MANY_PRINTS)
|
|
|
|
|
|
self._mode_combo.addItem("一一匹配", BatchMode.ONE_TO_ONE)
|
|
|
|
|
|
self._mode_combo.currentIndexChanged.connect(self._rebuild_queue)
|
|
|
|
|
|
|
|
|
|
|
|
self._stats_lbl = QLabel("共 0 项")
|
|
|
|
|
|
self._stats_lbl.setObjectName("queueStatsLabel")
|
|
|
|
|
|
self._stats_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
|
|
|
|
|
|
|
|
|
|
self._reset_btn = QPushButton("重置全部")
|
|
|
|
|
|
self._reset_btn.setObjectName("queueActionBtn")
|
|
|
|
|
|
self._reset_btn.clicked.connect(self._reset_all)
|
|
|
|
|
|
|
|
|
|
|
|
self._export_sel_btn = QPushButton("导出选中")
|
|
|
|
|
|
self._export_sel_btn.setObjectName("queueActionBtn")
|
|
|
|
|
|
self._export_sel_btn.clicked.connect(self._export_selected)
|
|
|
|
|
|
|
|
|
|
|
|
self._batch_btn = QPushButton("开始批量导出")
|
|
|
|
|
|
self._batch_btn.setObjectName("queueBatchBtn")
|
|
|
|
|
|
self._batch_btn.clicked.connect(self._toggle_batch)
|
|
|
|
|
|
|
|
|
|
|
|
row.addWidget(mode_lbl)
|
|
|
|
|
|
row.addWidget(self._mode_combo)
|
|
|
|
|
|
row.addWidget(self._stats_lbl, 1)
|
|
|
|
|
|
row.addWidget(self._reset_btn)
|
|
|
|
|
|
row.addWidget(self._export_sel_btn)
|
|
|
|
|
|
row.addWidget(self._batch_btn)
|
|
|
|
|
|
return bar
|
|
|
|
|
|
|
|
|
|
|
|
def _make_table(self) -> QTableView:
|
|
|
|
|
|
self._model = _QueueModel()
|
|
|
|
|
|
|
|
|
|
|
|
self._table = QTableView()
|
|
|
|
|
|
self._table.setObjectName("queueTable")
|
|
|
|
|
|
self._table.setModel(self._model)
|
|
|
|
|
|
self._table.setItemDelegateForColumn(_COL_OUTPUT, _ProgressDelegate())
|
|
|
|
|
|
self._table.setSelectionBehavior(QTableView.SelectRows)
|
|
|
|
|
|
self._table.setSelectionMode(QTableView.SingleSelection)
|
|
|
|
|
|
self._table.setAlternatingRowColors(True)
|
|
|
|
|
|
self._table.setEditTriggers(QTableView.NoEditTriggers)
|
|
|
|
|
|
self._table.verticalHeader().setVisible(False)
|
|
|
|
|
|
self._table.verticalHeader().setDefaultSectionSize(24)
|
|
|
|
|
|
|
|
|
|
|
|
hdr = self._table.horizontalHeader()
|
|
|
|
|
|
hdr.setSectionResizeMode(QHeaderView.Interactive)
|
|
|
|
|
|
hdr.setStretchLastSection(True)
|
|
|
|
|
|
|
|
|
|
|
|
self._table.setColumnWidth(_COL_NUM, 36)
|
|
|
|
|
|
self._table.setColumnWidth(_COL_GARMENT, 130)
|
|
|
|
|
|
self._table.setColumnWidth(_COL_PRINT, 130)
|
|
|
|
|
|
self._table.setColumnWidth(_COL_FOLDER, 100)
|
|
|
|
|
|
self._table.setColumnWidth(_COL_PARAM, 56)
|
|
|
|
|
|
self._table.setColumnWidth(_COL_STATUS, 66)
|
|
|
|
|
|
|
|
|
|
|
|
self._table.selectionModel().currentRowChanged.connect(self._on_row_changed)
|
|
|
|
|
|
return self._table
|
|
|
|
|
|
|
|
|
|
|
|
# ── public API ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def set_garments(self, garments: List[ImageAsset]):
|
|
|
|
|
|
self._garments = garments
|
|
|
|
|
|
self._rebuild_queue()
|
|
|
|
|
|
|
|
|
|
|
|
def set_prints(self, prints: List[ImageAsset]):
|
|
|
|
|
|
self._prints = prints
|
|
|
|
|
|
self._rebuild_queue()
|
|
|
|
|
|
|
|
|
|
|
|
def set_transform(self, state: TransformState):
|
|
|
|
|
|
"""Store the template transform applied to items that have not been fine-tuned."""
|
|
|
|
|
|
self._template_transform = state
|
|
|
|
|
|
|
|
|
|
|
|
def set_export_options(self, options: ExportOptions):
|
|
|
|
|
|
self._export_options = options
|
|
|
|
|
|
|
2026-06-16 09:31:16 +08:00
|
|
|
|
def mark_item_fine_tuned(self, item: "QueueItem", state: TransformState):
|
|
|
|
|
|
"""Save a per-item transform and mark it as fine-tuned in the table."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
row = self._model._items.index(item)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return
|
|
|
|
|
|
item.transform = state
|
|
|
|
|
|
item.param_source = "已微调"
|
|
|
|
|
|
self._model.update_row(row)
|
|
|
|
|
|
|
2026-06-16 09:25:45 +08:00
|
|
|
|
# ── internal slots ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def _rebuild_queue(self):
|
|
|
|
|
|
mode = self._mode_combo.currentData()
|
|
|
|
|
|
sel_g = [g for g in self._garments if g.selected]
|
|
|
|
|
|
sel_p = [p for p in self._prints if p.selected]
|
|
|
|
|
|
pairs = _generate_pairs(sel_g, sel_p, mode)
|
|
|
|
|
|
self._model.set_items([QueueItem(garment=g, print_asset=p) for g, p in pairs])
|
|
|
|
|
|
self._update_stats()
|
|
|
|
|
|
|
|
|
|
|
|
def _update_stats(self):
|
|
|
|
|
|
items = self._model._items
|
|
|
|
|
|
n = len(items)
|
|
|
|
|
|
if n == 0:
|
|
|
|
|
|
self._stats_lbl.setText("共 0 项")
|
|
|
|
|
|
return
|
|
|
|
|
|
done = sum(1 for i in items if i.status == "已完成")
|
|
|
|
|
|
running = sum(1 for i in items if i.status == "导出中")
|
|
|
|
|
|
failed = sum(1 for i in items if i.status == "失败")
|
|
|
|
|
|
pending = sum(1 for i in items if i.status == "待导出")
|
|
|
|
|
|
self._stats_lbl.setText(
|
|
|
|
|
|
"共 {} 项 · 完成 {} · 进行 {} · 失败 {} · 待导出 {}".format(
|
|
|
|
|
|
n, done, running, failed, pending
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _on_row_changed(self, current, _previous):
|
|
|
|
|
|
item = self._model.get_item(current.row())
|
|
|
|
|
|
if item:
|
|
|
|
|
|
self.item_activated.emit(item)
|
|
|
|
|
|
|
|
|
|
|
|
def _reset_all(self):
|
|
|
|
|
|
for item in self._model._items:
|
|
|
|
|
|
item.status = "待导出"
|
|
|
|
|
|
item.progress = 0.0
|
|
|
|
|
|
item.output_path = None
|
|
|
|
|
|
item.error = None
|
|
|
|
|
|
self._model.set_items(list(self._model._items))
|
|
|
|
|
|
self._update_stats()
|
|
|
|
|
|
|
|
|
|
|
|
def _export_selected(self):
|
|
|
|
|
|
rows = self._table.selectionModel().selectedRows()
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return
|
|
|
|
|
|
row = rows[0].row()
|
|
|
|
|
|
item = self._model.get_item(row)
|
|
|
|
|
|
if item and item.status == "待导出":
|
|
|
|
|
|
self._export_item(row, item)
|
|
|
|
|
|
self._update_stats()
|
|
|
|
|
|
|
|
|
|
|
|
def _toggle_batch(self):
|
|
|
|
|
|
if self._running:
|
|
|
|
|
|
self._running = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._start_batch()
|
|
|
|
|
|
|
|
|
|
|
|
def _start_batch(self):
|
|
|
|
|
|
self._running = True
|
|
|
|
|
|
self._batch_btn.setText("停止")
|
|
|
|
|
|
items = self._model._items
|
|
|
|
|
|
|
|
|
|
|
|
for i, item in enumerate(items):
|
|
|
|
|
|
if not self._running:
|
|
|
|
|
|
break
|
|
|
|
|
|
if item.status != "待导出":
|
|
|
|
|
|
continue
|
|
|
|
|
|
self._export_item(i, item)
|
|
|
|
|
|
self._update_stats()
|
|
|
|
|
|
QApplication.processEvents()
|
|
|
|
|
|
|
|
|
|
|
|
self._running = False
|
|
|
|
|
|
self._batch_btn.setText("开始批量导出")
|
|
|
|
|
|
|
|
|
|
|
|
def _export_item(self, row: int, item: QueueItem):
|
|
|
|
|
|
transform = item.transform or self._template_transform
|
|
|
|
|
|
if not transform:
|
|
|
|
|
|
item.status = "失败"
|
|
|
|
|
|
item.error = "无印花参数(请先在画布中调整印花位置)"
|
|
|
|
|
|
self._model.update_row(row)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
out_dir = (
|
|
|
|
|
|
self._export_options.output_dir
|
|
|
|
|
|
if self._export_options.output_dir
|
|
|
|
|
|
else str(get_output_dir())
|
|
|
|
|
|
)
|
|
|
|
|
|
fmt = self._export_options.output_format or "PNG"
|
|
|
|
|
|
output_path = make_safe_output_path(
|
|
|
|
|
|
out_dir, item.garment.path, item.print_asset.path, fmt
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
item.status = "导出中"
|
|
|
|
|
|
item.progress = 0.0
|
|
|
|
|
|
self._model.update_row(row)
|
|
|
|
|
|
QApplication.processEvents()
|
|
|
|
|
|
|
|
|
|
|
|
result = compose(
|
|
|
|
|
|
item.garment.path, item.print_asset.path,
|
|
|
|
|
|
transform, self._export_options, output_path,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if result.success:
|
|
|
|
|
|
item.status = "已完成"
|
|
|
|
|
|
item.progress = 1.0
|
|
|
|
|
|
item.output_path = str(result.output_path)
|
|
|
|
|
|
logger.info("Queue export OK: %s", result.output_path)
|
|
|
|
|
|
else:
|
|
|
|
|
|
item.status = "失败"
|
|
|
|
|
|
item.error = result.error
|
|
|
|
|
|
logger.error("Queue export failed: %s", result.error)
|
|
|
|
|
|
|
|
|
|
|
|
self._model.update_row(row)
|
|
|
|
|
|
|
|
|
|
|
|
# ── stylesheet ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_stylesheet(self):
|
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
|
#queueCtrlBar {
|
|
|
|
|
|
background-color: #f7f7f7;
|
|
|
|
|
|
border-bottom: 1px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueCtrlLabel {
|
|
|
|
|
|
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #444444;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueModeCombo {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
border: 1px solid #d6d6d6;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueStatsLabel {
|
|
|
|
|
|
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #666666;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueActionBtn {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
padding: 3px 8px;
|
|
|
|
|
|
border: 1px solid #d6d6d6;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueActionBtn:hover { background: #e0e0e0; }
|
|
|
|
|
|
#queueBatchBtn {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
padding: 3px 12px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background-color: #0078d4;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueBatchBtn:hover { background-color: #106ebe; }
|
|
|
|
|
|
#queueBatchBtn:pressed { background-color: #005a9e; }
|
|
|
|
|
|
#queueTable {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
alternate-background-color: #f9f9f9;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
gridline-color: #eeeeee;
|
|
|
|
|
|
}
|
|
|
|
|
|
#queueTable::item { padding: 2px 4px; }
|
|
|
|
|
|
#queueTable::item:selected {
|
|
|
|
|
|
background-color: #cce4f7;
|
|
|
|
|
|
color: #000000;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|