fix(ui): caption 重叠问题 + 加截图工具
- caption (生成模式/宽高比/图片尺寸 等) 改用 make_field_group helper: 组内 caption + control 间距锁死 6px,组高度 fixed = 22+6+34 = 62px 解决 Qt QSS setStyleSheet 改字号不更新 sizeHint 导致的 caption 文字溢出到下方 widget 的坑 - caption 字号 11pt → 12pt,font-weight DemiBold,颜色 text_primary,视觉权重和 combo 拉开 - settings_layout 间距 18px(组与组之间) - 主窗口最小尺寸 1180x820,避免压缩破坏 layout - ImageGeneratorWindow 和 StyleDesignerTab 同步迁移 - capture_window.py:按窗口标题 PrintWindow 截图工具,迭代 UI 时不再依赖手动截图 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Showing
3 changed files
with
256 additions
and
44 deletions
capture_window.py
0 → 100644
| 1 | """按窗口标题截图。 | ||
| 2 | |||
| 3 | 用法: | ||
| 4 | python capture_window.py <title-substring> [output.png] | ||
| 5 | |||
| 6 | 例: | ||
| 7 | python capture_window.py 珠宝壹佰 shot.png | ||
| 8 | python capture_window.py 登录 login.png | ||
| 9 | |||
| 10 | 依赖:Pillow(项目已装),ctypes(标准库)。 | ||
| 11 | 仅 Windows。 | ||
| 12 | """ | ||
| 13 | import ctypes | ||
| 14 | import sys | ||
| 15 | from ctypes import wintypes | ||
| 16 | from pathlib import Path | ||
| 17 | from PIL import ImageGrab | ||
| 18 | |||
| 19 | user32 = ctypes.windll.user32 | ||
| 20 | EnumWindowsProc = ctypes.WINFUNCTYPE( | ||
| 21 | ctypes.c_bool, wintypes.HWND, wintypes.LPARAM | ||
| 22 | ) | ||
| 23 | |||
| 24 | |||
| 25 | def _get_window_text(hwnd): | ||
| 26 | length = user32.GetWindowTextLengthW(hwnd) | ||
| 27 | if length == 0: | ||
| 28 | return "" | ||
| 29 | buf = ctypes.create_unicode_buffer(length + 1) | ||
| 30 | user32.GetWindowTextW(hwnd, buf, length + 1) | ||
| 31 | return buf.value | ||
| 32 | |||
| 33 | |||
| 34 | _PROCESS_NAME_CACHE = {} | ||
| 35 | |||
| 36 | |||
| 37 | def _get_process_name(pid: int) -> str: | ||
| 38 | """通过 pid 拿到进程可执行文件名(小写,不含路径)。""" | ||
| 39 | if pid in _PROCESS_NAME_CACHE: | ||
| 40 | return _PROCESS_NAME_CACHE[pid] | ||
| 41 | PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 | ||
| 42 | kernel32 = ctypes.windll.kernel32 | ||
| 43 | psapi = ctypes.windll.psapi | ||
| 44 | h = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) | ||
| 45 | if not h: | ||
| 46 | _PROCESS_NAME_CACHE[pid] = "" | ||
| 47 | return "" | ||
| 48 | try: | ||
| 49 | buf = ctypes.create_unicode_buffer(260) | ||
| 50 | size = wintypes.DWORD(260) | ||
| 51 | if kernel32.QueryFullProcessImageNameW(h, 0, buf, ctypes.byref(size)): | ||
| 52 | name = buf.value.split("\\")[-1].lower() | ||
| 53 | else: | ||
| 54 | name = "" | ||
| 55 | finally: | ||
| 56 | kernel32.CloseHandle(h) | ||
| 57 | _PROCESS_NAME_CACHE[pid] = name | ||
| 58 | return name | ||
| 59 | |||
| 60 | |||
| 61 | def find_window(title_substring: str, exe_filter: tuple = ("pythonw.exe", "python.exe", "zb100imagegenerator.exe")): | ||
| 62 | """返回首个标题包含 title_substring 且属于 exe_filter 进程的可见窗口 hwnd。""" | ||
| 63 | found = [] | ||
| 64 | |||
| 65 | def callback(hwnd, lparam): | ||
| 66 | if not user32.IsWindowVisible(hwnd): | ||
| 67 | return True | ||
| 68 | text = _get_window_text(hwnd) | ||
| 69 | if title_substring not in text: | ||
| 70 | return True | ||
| 71 | # 检查进程 | ||
| 72 | pid = wintypes.DWORD() | ||
| 73 | user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) | ||
| 74 | name = _get_process_name(pid.value) | ||
| 75 | if name in exe_filter: | ||
| 76 | found.append((hwnd, text)) | ||
| 77 | return False # 停止枚举 | ||
| 78 | return True | ||
| 79 | |||
| 80 | user32.EnumWindows(EnumWindowsProc(callback), 0) | ||
| 81 | return found[0] if found else None | ||
| 82 | |||
| 83 | |||
| 84 | def get_window_bbox(hwnd): | ||
| 85 | """获取窗口外接矩形(含标题栏 / 边框)。""" | ||
| 86 | rect = wintypes.RECT() | ||
| 87 | # DWMWA_EXTENDED_FRAME_BOUNDS = 9,能避开 Win10/11 的不可见 shadow border | ||
| 88 | DWMWA_EXTENDED_FRAME_BOUNDS = 9 | ||
| 89 | dwmapi = ctypes.windll.dwmapi | ||
| 90 | res = dwmapi.DwmGetWindowAttribute( | ||
| 91 | wintypes.HWND(hwnd), | ||
| 92 | ctypes.c_uint(DWMWA_EXTENDED_FRAME_BOUNDS), | ||
| 93 | ctypes.byref(rect), | ||
| 94 | ctypes.sizeof(rect), | ||
| 95 | ) | ||
| 96 | if res != 0: | ||
| 97 | # 回退到 GetWindowRect | ||
| 98 | user32.GetWindowRect(hwnd, ctypes.byref(rect)) | ||
| 99 | return (rect.left, rect.top, rect.right, rect.bottom) | ||
| 100 | |||
| 101 | |||
| 102 | def main(): | ||
| 103 | if len(sys.argv) < 2: | ||
| 104 | print(__doc__) | ||
| 105 | sys.exit(1) | ||
| 106 | |||
| 107 | title = sys.argv[1] | ||
| 108 | output = Path(sys.argv[2] if len(sys.argv) > 2 else "shot.png").resolve() | ||
| 109 | |||
| 110 | hit = find_window(title) | ||
| 111 | if not hit: | ||
| 112 | print(f"找不到标题包含 '{title}' 的窗口", file=sys.stderr) | ||
| 113 | sys.exit(2) | ||
| 114 | hwnd, full_title = hit | ||
| 115 | |||
| 116 | # 用 PrintWindow 直接从窗口拿位图,不需要把窗口提前(避免打断用户) | ||
| 117 | bbox = get_window_bbox(hwnd) | ||
| 118 | width = bbox[2] - bbox[0] | ||
| 119 | height = bbox[3] - bbox[1] | ||
| 120 | |||
| 121 | img = _print_window_to_pil(hwnd, width, height) | ||
| 122 | if img is None: | ||
| 123 | # 回退:把窗口提前再截屏 | ||
| 124 | import time | ||
| 125 | user32.ShowWindow(hwnd, 9) # SW_RESTORE | ||
| 126 | user32.SetForegroundWindow(hwnd) | ||
| 127 | # 顶置-取消顶置技巧绕开 Windows 前台限制 | ||
| 128 | HWND_TOPMOST = -1 | ||
| 129 | HWND_NOTOPMOST = -2 | ||
| 130 | SWP_NOMOVE = 0x0002 | ||
| 131 | SWP_NOSIZE = 0x0001 | ||
| 132 | user32.SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) | ||
| 133 | user32.SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) | ||
| 134 | time.sleep(0.5) | ||
| 135 | img = ImageGrab.grab(bbox=bbox, all_screens=True) | ||
| 136 | |||
| 137 | img.save(output) | ||
| 138 | print(f"OK hwnd={hwnd} title={full_title!r} size={width}x{height} -> {output}") | ||
| 139 | |||
| 140 | |||
| 141 | def _print_window_to_pil(hwnd, width, height): | ||
| 142 | """用 PrintWindow API 从窗口直接拿位图(不依赖窗口在前台)。""" | ||
| 143 | from PIL import Image | ||
| 144 | gdi32 = ctypes.windll.gdi32 | ||
| 145 | |||
| 146 | hdcWindow = user32.GetDC(hwnd) | ||
| 147 | hdcMem = gdi32.CreateCompatibleDC(hdcWindow) | ||
| 148 | hbm = gdi32.CreateCompatibleBitmap(hdcWindow, width, height) | ||
| 149 | gdi32.SelectObject(hdcMem, hbm) | ||
| 150 | |||
| 151 | PW_RENDERFULLCONTENT = 0x00000002 | ||
| 152 | ok = user32.PrintWindow(hwnd, hdcMem, PW_RENDERFULLCONTENT) | ||
| 153 | |||
| 154 | if ok: | ||
| 155 | # 提取位图数据 | ||
| 156 | class BITMAPINFOHEADER(ctypes.Structure): | ||
| 157 | _fields_ = [ | ||
| 158 | ("biSize", wintypes.DWORD), | ||
| 159 | ("biWidth", wintypes.LONG), | ||
| 160 | ("biHeight", wintypes.LONG), | ||
| 161 | ("biPlanes", wintypes.WORD), | ||
| 162 | ("biBitCount", wintypes.WORD), | ||
| 163 | ("biCompression", wintypes.DWORD), | ||
| 164 | ("biSizeImage", wintypes.DWORD), | ||
| 165 | ("biXPelsPerMeter", wintypes.LONG), | ||
| 166 | ("biYPelsPerMeter", wintypes.LONG), | ||
| 167 | ("biClrUsed", wintypes.DWORD), | ||
| 168 | ("biClrImportant", wintypes.DWORD), | ||
| 169 | ] | ||
| 170 | |||
| 171 | class BITMAPINFO(ctypes.Structure): | ||
| 172 | _fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", wintypes.DWORD * 3)] | ||
| 173 | |||
| 174 | bmi = BITMAPINFO() | ||
| 175 | bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) | ||
| 176 | bmi.bmiHeader.biWidth = width | ||
| 177 | bmi.bmiHeader.biHeight = -height # top-down | ||
| 178 | bmi.bmiHeader.biPlanes = 1 | ||
| 179 | bmi.bmiHeader.biBitCount = 32 | ||
| 180 | bmi.bmiHeader.biCompression = 0 # BI_RGB | ||
| 181 | |||
| 182 | buf_len = width * height * 4 | ||
| 183 | buf = (ctypes.c_ubyte * buf_len)() | ||
| 184 | gdi32.GetDIBits(hdcMem, hbm, 0, height, buf, ctypes.byref(bmi), 0) | ||
| 185 | img = Image.frombuffer("RGBA", (width, height), bytes(buf), "raw", "BGRA", 0, 1).convert("RGB") | ||
| 186 | else: | ||
| 187 | img = None | ||
| 188 | |||
| 189 | gdi32.DeleteObject(hbm) | ||
| 190 | gdi32.DeleteDC(hdcMem) | ||
| 191 | user32.ReleaseDC(hwnd, hdcWindow) | ||
| 192 | |||
| 193 | return img | ||
| 194 | |||
| 195 | |||
| 196 | if __name__ == "__main__": | ||
| 197 | main() |
| ... | @@ -1527,6 +1527,34 @@ class LoginDialog(QDialog): | ... | @@ -1527,6 +1527,34 @@ class LoginDialog(QDialog): |
| 1527 | THUMB_REORDER_MIME = "application/x-zb100-thumb-index" | 1527 | THUMB_REORDER_MIME = "application/x-zb100-thumb-index" |
| 1528 | 1528 | ||
| 1529 | 1529 | ||
| 1530 | def make_caption_label(text: str) -> QLabel: | ||
| 1531 | """构造 caption 风格 label(设置区上方的小标签)。""" | ||
| 1532 | lbl = QLabel(text) | ||
| 1533 | lbl.setProperty("role", "caption") | ||
| 1534 | f = QFont() | ||
| 1535 | f.setPointSize(12) | ||
| 1536 | f.setWeight(QFont.Weight.DemiBold) | ||
| 1537 | lbl.setFont(f) | ||
| 1538 | lbl.setFixedHeight(22) | ||
| 1539 | return lbl | ||
| 1540 | |||
| 1541 | |||
| 1542 | def make_field_group(caption_text: str, control: QWidget) -> QWidget: | ||
| 1543 | """把一个 caption + 控件打包成独立 widget,组内间距锁死 6px。 | ||
| 1544 | 每组高度 = 22 (caption) + 6 (spacing) + 34 (control) = 62px,硬锁。 | ||
| 1545 | """ | ||
| 1546 | # 锁住 control 高度(FixedHeight 让 sizeHint = 34,layout 不能压缩) | ||
| 1547 | control.setFixedHeight(34) | ||
| 1548 | w = QWidget() | ||
| 1549 | w.setFixedHeight(22 + 6 + 34) # 锁死整个组高度 | ||
| 1550 | layout = QVBoxLayout(w) | ||
| 1551 | layout.setContentsMargins(0, 0, 0, 0) | ||
| 1552 | layout.setSpacing(6) | ||
| 1553 | layout.addWidget(make_caption_label(caption_text)) | ||
| 1554 | layout.addWidget(control) | ||
| 1555 | return w | ||
| 1556 | |||
| 1557 | |||
| 1530 | class DraggableThumbnail(QWidget): | 1558 | class DraggableThumbnail(QWidget): |
| 1531 | """可拖拽重排序的缩略图容器""" | 1559 | """可拖拽重排序的缩略图容器""" |
| 1532 | 1560 | ||
| ... | @@ -1944,8 +1972,8 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -1944,8 +1972,8 @@ class ImageGeneratorWindow(QMainWindow): |
| 1944 | def setup_ui(self): | 1972 | def setup_ui(self): |
| 1945 | """Setup the user interface""" | 1973 | """Setup the user interface""" |
| 1946 | self.setWindowTitle("珠宝壹佰图像生成器") | 1974 | self.setWindowTitle("珠宝壹佰图像生成器") |
| 1947 | self.setGeometry(100, 100, 1200, 850) | 1975 | self.setGeometry(100, 100, 1280, 880) |
| 1948 | self.setMinimumSize(1000, 700) | 1976 | self.setMinimumSize(1180, 820) |
| 1949 | 1977 | ||
| 1950 | # Central widget | 1978 | # Central widget |
| 1951 | central_widget = QWidget() | 1979 | central_widget = QWidget() |
| ... | @@ -2096,19 +2124,16 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -2096,19 +2124,16 @@ class ImageGeneratorWindow(QMainWindow): |
| 2096 | # Settings section | 2124 | # Settings section |
| 2097 | settings_group = QGroupBox("生成设置") | 2125 | settings_group = QGroupBox("生成设置") |
| 2098 | settings_layout = QVBoxLayout() | 2126 | settings_layout = QVBoxLayout() |
| 2127 | settings_layout.setSpacing(18) | ||
| 2099 | 2128 | ||
| 2100 | # 生成模式(放在最前面) | 2129 | # 生成模式(放在最前面) |
| 2101 | settings_layout.addWidget(QLabel("生成模式")) | ||
| 2102 | self.generation_mode = QComboBox() | 2130 | self.generation_mode = QComboBox() |
| 2103 | self.generation_mode.addItems(["极速模式", "慢速模式"]) | 2131 | self.generation_mode.addItems(["极速模式", "慢速模式"]) |
| 2104 | self.generation_mode.setCurrentIndex(0) # Default to 极速模式 | 2132 | self.generation_mode.setCurrentIndex(0) # Default to 极速模式 |
| 2105 | self.generation_mode.currentIndexChanged.connect(self.on_generation_mode_changed) | 2133 | self.generation_mode.currentIndexChanged.connect(self.on_generation_mode_changed) |
| 2106 | settings_layout.addWidget(self.generation_mode) | 2134 | settings_layout.addWidget(make_field_group("生成模式", self.generation_mode)) |
| 2107 | |||
| 2108 | settings_layout.addSpacing(10) | ||
| 2109 | 2135 | ||
| 2110 | # 宽高比 | 2136 | # 宽高比 |
| 2111 | settings_layout.addWidget(QLabel("宽高比")) | ||
| 2112 | self.aspect_ratio = QComboBox() | 2137 | self.aspect_ratio = QComboBox() |
| 2113 | self.aspect_ratio.addItems([ | 2138 | self.aspect_ratio.addItems([ |
| 2114 | "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", | 2139 | "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", |
| ... | @@ -2118,19 +2143,14 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -2118,19 +2143,14 @@ class ImageGeneratorWindow(QMainWindow): |
| 2118 | # 记录上一次值用于用户拒绝切换模式时回滚,避免留在一个无法提交的状态 | 2143 | # 记录上一次值用于用户拒绝切换模式时回滚,避免留在一个无法提交的状态 |
| 2119 | self._prev_aspect_ratio = self.aspect_ratio.currentText() | 2144 | self._prev_aspect_ratio = self.aspect_ratio.currentText() |
| 2120 | self.aspect_ratio.currentTextChanged.connect(self._on_aspect_ratio_changed) | 2145 | self.aspect_ratio.currentTextChanged.connect(self._on_aspect_ratio_changed) |
| 2121 | settings_layout.addWidget(self.aspect_ratio) | 2146 | settings_layout.addWidget(make_field_group("宽高比", self.aspect_ratio)) |
| 2122 | |||
| 2123 | settings_layout.addSpacing(10) | ||
| 2124 | 2147 | ||
| 2125 | # 图片尺寸 | 2148 | # 图片尺寸 |
| 2126 | settings_layout.addWidget(QLabel("图片尺寸")) | ||
| 2127 | self.image_size = QComboBox() | 2149 | self.image_size = QComboBox() |
| 2128 | self.image_size.addItems(["1K", "2K", "4K"]) | 2150 | self.image_size.addItems(["1K", "2K", "4K"]) |
| 2129 | self.image_size.setCurrentIndex(0) # Default to 1K for 极速模式 | 2151 | self.image_size.setCurrentIndex(0) # Default to 1K for 极速模式 |
| 2130 | self.image_size.currentIndexChanged.connect(self.on_image_size_changed) | 2152 | self.image_size.currentIndexChanged.connect(self.on_image_size_changed) |
| 2131 | settings_layout.addWidget(self.image_size) | 2153 | settings_layout.addWidget(make_field_group("图片尺寸", self.image_size)) |
| 2132 | |||
| 2133 | settings_layout.addSpacing(10) | ||
| 2134 | 2154 | ||
| 2135 | settings_layout.addStretch() | 2155 | settings_layout.addStretch() |
| 2136 | settings_group.setLayout(settings_layout) | 2156 | settings_group.setLayout(settings_layout) |
| ... | @@ -4085,23 +4105,16 @@ class StyleDesignerTab(QWidget): | ... | @@ -4085,23 +4105,16 @@ class StyleDesignerTab(QWidget): |
| 4085 | # Settings section | 4105 | # Settings section |
| 4086 | settings_group = QGroupBox("生成设置") | 4106 | settings_group = QGroupBox("生成设置") |
| 4087 | settings_layout = QVBoxLayout() | 4107 | settings_layout = QVBoxLayout() |
| 4108 | settings_layout.setSpacing(18) | ||
| 4088 | 4109 | ||
| 4089 | # 生成模式(放在最前面) | 4110 | # 生成模式(放在最前面) |
| 4090 | mode_label = QLabel("生成模式") | ||
| 4091 | mode_label.setProperty("role", "caption") | ||
| 4092 | settings_layout.addWidget(mode_label) | ||
| 4093 | self.generation_mode = QComboBox() | 4111 | self.generation_mode = QComboBox() |
| 4094 | self.generation_mode.addItems(["极速模式", "慢速模式"]) | 4112 | self.generation_mode.addItems(["极速模式", "慢速模式"]) |
| 4095 | self.generation_mode.setCurrentIndex(0) # Default to 极速模式 | 4113 | self.generation_mode.setCurrentIndex(0) # Default to 极速模式 |
| 4096 | self.generation_mode.currentIndexChanged.connect(self.on_generation_mode_changed) | 4114 | self.generation_mode.currentIndexChanged.connect(self.on_generation_mode_changed) |
| 4097 | settings_layout.addWidget(self.generation_mode) | 4115 | settings_layout.addWidget(make_field_group("生成模式", self.generation_mode)) |
| 4098 | |||
| 4099 | settings_layout.addSpacing(10) | ||
| 4100 | 4116 | ||
| 4101 | # 宽高比 | 4117 | # 宽高比 |
| 4102 | aspect_label = QLabel("宽高比") | ||
| 4103 | aspect_label.setProperty("role", "caption") | ||
| 4104 | settings_layout.addWidget(aspect_label) | ||
| 4105 | self.aspect_ratio = QComboBox() | 4118 | self.aspect_ratio = QComboBox() |
| 4106 | self.aspect_ratio.addItems([ | 4119 | self.aspect_ratio.addItems([ |
| 4107 | "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", | 4120 | "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", |
| ... | @@ -4111,21 +4124,14 @@ class StyleDesignerTab(QWidget): | ... | @@ -4111,21 +4124,14 @@ class StyleDesignerTab(QWidget): |
| 4111 | # 记录上一次值用于用户拒绝切换模式时回滚 | 4124 | # 记录上一次值用于用户拒绝切换模式时回滚 |
| 4112 | self._prev_aspect_ratio = self.aspect_ratio.currentText() | 4125 | self._prev_aspect_ratio = self.aspect_ratio.currentText() |
| 4113 | self.aspect_ratio.currentTextChanged.connect(self._on_aspect_ratio_changed) | 4126 | self.aspect_ratio.currentTextChanged.connect(self._on_aspect_ratio_changed) |
| 4114 | settings_layout.addWidget(self.aspect_ratio) | 4127 | settings_layout.addWidget(make_field_group("宽高比", self.aspect_ratio)) |
| 4115 | |||
| 4116 | settings_layout.addSpacing(10) | ||
| 4117 | 4128 | ||
| 4118 | # 图片尺寸 | 4129 | # 图片尺寸 |
| 4119 | size_label = QLabel("图片尺寸") | ||
| 4120 | size_label.setProperty("role", "caption") | ||
| 4121 | settings_layout.addWidget(size_label) | ||
| 4122 | self.image_size = QComboBox() | 4130 | self.image_size = QComboBox() |
| 4123 | self.image_size.addItems(["1K", "2K", "4K"]) | 4131 | self.image_size.addItems(["1K", "2K", "4K"]) |
| 4124 | self.image_size.setCurrentIndex(0) # Default to 1K for 极速模式 | 4132 | self.image_size.setCurrentIndex(0) # Default to 1K for 极速模式 |
| 4125 | self.image_size.currentIndexChanged.connect(self.on_image_size_changed) | 4133 | self.image_size.currentIndexChanged.connect(self.on_image_size_changed) |
| 4126 | settings_layout.addWidget(self.image_size) | 4134 | settings_layout.addWidget(make_field_group("图片尺寸", self.image_size)) |
| 4127 | |||
| 4128 | settings_layout.addSpacing(10) | ||
| 4129 | 4135 | ||
| 4130 | settings_layout.addStretch() | 4136 | settings_layout.addStretch() |
| 4131 | settings_group.setLayout(settings_layout) | 4137 | settings_group.setLayout(settings_layout) |
| ... | @@ -4218,22 +4224,25 @@ class StyleDesignerTab(QWidget): | ... | @@ -4218,22 +4224,25 @@ class StyleDesignerTab(QWidget): |
| 4218 | 4224 | ||
| 4219 | # 添加按钮(使用表情符号) | 4225 | # 添加按钮(使用表情符号) |
| 4220 | add_btn = QPushButton("➕") | 4226 | add_btn = QPushButton("➕") |
| 4227 | add_btn.setProperty("size", "icon") | ||
| 4221 | add_btn.clicked.connect(lambda: self.add_library_item(category)) | 4228 | add_btn.clicked.connect(lambda: self.add_library_item(category)) |
| 4222 | add_btn.setFixedWidth(40) | 4229 | add_btn.setFixedWidth(36) |
| 4223 | add_btn.setToolTip("添加词库项") | 4230 | add_btn.setToolTip("添加词库项") |
| 4224 | layout.addWidget(add_btn) | 4231 | layout.addWidget(add_btn) |
| 4225 | 4232 | ||
| 4226 | # 删除按钮(使用表情符号) | 4233 | # 删除按钮(使用表情符号) |
| 4227 | del_btn = QPushButton("🗑️") | 4234 | del_btn = QPushButton("🗑️") |
| 4235 | del_btn.setProperty("size", "icon") | ||
| 4228 | del_btn.clicked.connect(lambda: self.remove_library_item(category)) | 4236 | del_btn.clicked.connect(lambda: self.remove_library_item(category)) |
| 4229 | del_btn.setFixedWidth(40) | 4237 | del_btn.setFixedWidth(36) |
| 4230 | del_btn.setToolTip("删除词库项") | 4238 | del_btn.setToolTip("删除词库项") |
| 4231 | layout.addWidget(del_btn) | 4239 | layout.addWidget(del_btn) |
| 4232 | 4240 | ||
| 4233 | # 锁定/解锁按钮(使用表情符号) | 4241 | # 锁定/解锁按钮(使用表情符号) |
| 4234 | lock_btn = QPushButton("🔓") | 4242 | lock_btn = QPushButton("🔓") |
| 4243 | lock_btn.setProperty("size", "icon") | ||
| 4235 | lock_btn.clicked.connect(lambda: self.toggle_field_lock(category)) | 4244 | lock_btn.clicked.connect(lambda: self.toggle_field_lock(category)) |
| 4236 | lock_btn.setFixedWidth(40) | 4245 | lock_btn.setFixedWidth(36) |
| 4237 | lock_btn.setToolTip("锁定/解锁字段") | 4246 | lock_btn.setToolTip("锁定/解锁字段") |
| 4238 | self.lock_buttons[category] = lock_btn | 4247 | self.lock_buttons[category] = lock_btn |
| 4239 | layout.addWidget(lock_btn) | 4248 | layout.addWidget(lock_btn) | ... | ... |
| ... | @@ -167,10 +167,9 @@ QLabel[role="muted"] {{ | ... | @@ -167,10 +167,9 @@ QLabel[role="muted"] {{ |
| 167 | font-size: {s['font_sm']}; | 167 | font-size: {s['font_sm']}; |
| 168 | }} | 168 | }} |
| 169 | QLabel[role="caption"] {{ | 169 | QLabel[role="caption"] {{ |
| 170 | color: {c['text_secondary']}; | 170 | color: {c['text_primary']}; |
| 171 | font-size: {s['font_xs']}; | 171 | font-size: {s['font_base']}; |
| 172 | text-transform: uppercase; | 172 | font-weight: 600; |
| 173 | letter-spacing: 1px; | ||
| 174 | }} | 173 | }} |
| 175 | QLabel[role="title"] {{ | 174 | QLabel[role="title"] {{ |
| 176 | color: {c['text_primary']}; | 175 | color: {c['text_primary']}; |
| ... | @@ -190,8 +189,8 @@ QGroupBox {{ | ... | @@ -190,8 +189,8 @@ QGroupBox {{ |
| 190 | background-color: {c['bg_surface']}; | 189 | background-color: {c['bg_surface']}; |
| 191 | border: 1px solid {c['border_default']}; | 190 | border: 1px solid {c['border_default']}; |
| 192 | border-radius: {s['radius_md']}; | 191 | border-radius: {s['radius_md']}; |
| 193 | margin-top: 14px; | 192 | margin-top: 18px; |
| 194 | padding: {s['space_4']} {s['space_3']} {s['space_3']} {s['space_3']}; | 193 | padding: 20px 14px 14px 14px; |
| 195 | font-weight: 600; | 194 | font-weight: 600; |
| 196 | font-size: {s['font_sm']}; | 195 | font-size: {s['font_sm']}; |
| 197 | color: {c['text_secondary']}; | 196 | color: {c['text_secondary']}; |
| ... | @@ -203,9 +202,8 @@ QGroupBox::title {{ | ... | @@ -203,9 +202,8 @@ QGroupBox::title {{ |
| 203 | padding: 0 {s['space_2']}; | 202 | padding: 0 {s['space_2']}; |
| 204 | color: {c['text_secondary']}; | 203 | color: {c['text_secondary']}; |
| 205 | background: transparent; | 204 | background: transparent; |
| 206 | text-transform: uppercase; | 205 | font-size: {s['font_sm']}; |
| 207 | letter-spacing: 1px; | 206 | font-weight: 600; |
| 208 | font-size: {s['font_xs']}; | ||
| 209 | }} | 207 | }} |
| 210 | 208 | ||
| 211 | /* ========== 按钮 ========== */ | 209 | /* ========== 按钮 ========== */ |
| ... | @@ -672,6 +670,14 @@ QPushButton#thumbDeleteBtn {{ | ... | @@ -672,6 +670,14 @@ QPushButton#thumbDeleteBtn {{ |
| 672 | QPushButton#thumbDeleteBtn:hover {{ | 670 | QPushButton#thumbDeleteBtn:hover {{ |
| 673 | background-color: {c['danger_hover']}; | 671 | background-color: {c['danger_hover']}; |
| 674 | }} | 672 | }} |
| 673 | |||
| 674 | /* ========== 紧凑图标按钮(款式设计 tab 的 ➕🗑🔓 等)========== */ | ||
| 675 | QPushButton[size="icon"] {{ | ||
| 676 | padding: 0; | ||
| 677 | min-width: 30px; | ||
| 678 | min-height: 30px; | ||
| 679 | font-size: {s['font_base']}; | ||
| 680 | }} | ||
| 675 | """ | 681 | """ |
| 676 | 682 | ||
| 677 | 683 | ... | ... |
-
Please register or sign in to post a comment