正版情趣王
+多格式图片批处理工作台
+激活当前设备
+输入服务商提供的激活码。激活后,此授权将绑定到设备「{{ appInfo.deviceName }}」。
+{{ licenseState.message }}
+多格式图片批处理工作台
+输入服务商提供的激活码。激活后,此授权将绑定到设备「{{ appInfo.deviceName }}」。
+使用 AI 混淆算法处理图片,并生成指定的交付文件格式。
+支持 JPG、PNG、WebP、GIF、TIFF、AVIF、SVG;动图取首帧
+ +查看当前设备绑定与授权有效期。
+当前设备已通过服务端校验,可以使用全部图片处理功能。
+清除后需要重新输入激活码;不会自动释放服务端设备绑定。
支持多种图片输入、统一生成 JPEG 交付文件的本地批量处理工具。
+专业多格式图片批处理软件
+ 版本 {{ appInfo.version }} +{{ currentReleaseNotes || '版本已准备好。' }}
图片处理过程不上传服务器。
自动完成图片特征混淆与兼容处理。
文件与文件夹统一加入处理队列。
1、优化体验
', + downloadUrl: 'https://cdn.example.com/genuine-qingquwang.exe', + enforce: true, + } + + const activeLicense: LicenseViewState = { + status: 'active', + message: '授权有效', + offline: false, + session: { + licenseCodeMask: 'JXIP-****-****-****-8Q7M', + deviceMask: 'A2C91D…7F06B8', + expiresAt: '2027-08-26T06:00:00+08:00', + maxDevices: 1, + }, + version: mode === 'update' ? availableUpdate : undefined, + } + + return { + getAppInfo: async () => ({ + name: '正版情趣王', + version: '1.0.0', + platform: 'win32', + deviceName: 'DESKTOP-TENCENT', + }), + getLicenseState: async () => mode === 'activation' + ? { status: 'unlicensed', message: '输入激活码后即可开始使用', offline: false } + : activeLicense, + activate: async () => activeLicense, + validateLicense: async () => activeLicense, + clearLicense: async () => ({ status: 'unlicensed', message: '本机授权凭据已清除', offline: false }), + checkVersion: async () => mode === 'update' + ? availableUpdate + : { updateAvailable: false, enforce: false }, + downloadUpdate: async () => undefined, + selectImages: async () => [ + 'C:\\Product\\主图-蓝色.jpg', + 'C:\\Product\\详情页-01.jpg', + 'C:\\Product\\详情页-02.jpg', + ], + selectFolder: async () => ['C:\\Product\\新品批次'], + selectOutputDirectory: async () => 'D:\\正版情趣王\\输出', + pathsForFiles: () => [], + inspectImages: async (paths) => paths.flatMap((filePath, pathIndex) => { + const count = filePath.endsWith('新品批次') ? 4 : 1 + return Array.from({ length: count }, (_, nestedIndex): ImageFileItem => { + const index = pathIndex + nestedIndex + 1 + const name = count > 1 ? `新品-${String(index).padStart(2, '0')}.jpg` : filePath.split('\\').pop() || `图片-${index}.jpg` + return { + id: `${filePath}-${nestedIndex}`, + path: count > 1 ? `${filePath}\\${name}` : filePath, + name, + size: 485_000 + index * 127_000, + width: 1200, + height: 1600, + status: 'pending', + } + }) + }), + processImages: async (options) => { + for (let index = 0; index < options.paths.length; index++) { + const item: ImageFileItem = { + id: options.paths[index], + path: options.paths[index], + name: options.paths[index].split('\\').pop() || '图片.jpg', + size: 612_000, + width: 1200, + height: 1600, + status: 'completed', + outputPath: `${options.paths[index]}.${options.outputFormat === 'file-jpg' ? 'file.jpg' : 'file'}`, + } + progressListeners.forEach((listener) => listener({ + current: index + 1, + total: options.paths.length, + percent: Math.round(((index + 1) / options.paths.length) * 100), + item, + cancelled: false, + })) + } + return { total: options.paths.length, completed: options.paths.length, failed: 0, cancelled: false } + }, + cancelProcessing: async () => undefined, + revealItem: async () => undefined, + onProcessProgress: (callback) => { + progressListeners.add(callback) + return () => progressListeners.delete(callback) + }, + } +} diff --git a/src/renderer/styles.css b/src/renderer/styles.css new file mode 100644 index 0000000..4cf01db --- /dev/null +++ b/src/renderer/styles.css @@ -0,0 +1,277 @@ +:root { + font-family: "HarmonyOS Sans SC", "Microsoft YaHei UI", "PingFang SC", system-ui, sans-serif; + color: #202938; + background: #f4f7fb; + font-synthesis: none; + text-rendering: optimizeLegibility; + --blue: #1769e0; + --blue-deep: #0d55bd; + --blue-pale: #edf4ff; + --teal: #1cab9c; + --ink: #202938; + --muted: #717b8d; + --subtle: #99a2b0; + --line: #e4e9f0; + --canvas: #f4f7fb; + --surface: #ffffff; + --danger: #d94f4f; + --shadow: 0 14px 40px rgba(31, 49, 79, 0.09); +} + +* { box-sizing: border-box; } +html, body, #app { width: 100%; height: 100%; margin: 0; overflow: hidden; } +body { min-width: 1080px; min-height: 680px; } +button, input { font: inherit; } +button { border: 0; } +button:focus-visible, input:focus-visible { outline: 3px solid rgba(23, 105, 224, 0.2); outline-offset: 2px; } +button:disabled { cursor: not-allowed; opacity: 0.52; } + +.app-frame { width: 100%; height: 100%; background: var(--canvas); position: relative; } +.titlebar { + height: 48px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 156px 0 18px; + background: #f7f9fc; + border-bottom: 1px solid #e9edf3; + -webkit-app-region: drag; + user-select: none; +} +.brand-mini { display: flex; align-items: center; gap: 9px; font-size: 13px; font-weight: 650; color: #293244; letter-spacing: 0.01em; } +.brand-mini-mark { display: grid; place-items: center; width: 27px; height: 27px; color: white; background: var(--blue); border-radius: 7px; } +.titlebar-edition { padding: 2px 7px; color: #5f6d83; background: #e9eef6; border-radius: 4px; font-size: 10px; font-weight: 600; } +.titlebar-status { display: flex; align-items: center; gap: 7px; font-size: 11px; color: #6d7787; } +.status-dot { width: 7px; height: 7px; border-radius: 50%; background: #1bad84; box-shadow: 0 0 0 3px rgba(27, 173, 132, 0.12); } +.status-dot.offline { background: #e0a62e; box-shadow: 0 0 0 3px rgba(224, 166, 46, 0.14); } + +.splash-screen { height: calc(100% - 48px); display: flex; flex-direction: column; align-items: center; justify-content: center; color: #758094; } +.splash-mark { width: 76px; height: 76px; display: grid; place-items: center; color: white; background: var(--blue); border-radius: 20px; box-shadow: 0 18px 36px rgba(23, 105, 224, 0.22); } +.splash-screen h1 { margin: 22px 0 6px; color: var(--ink); font-size: 25px; } +.splash-screen p { margin: 0 0 20px; font-size: 13px; } +.spin { animation: spin 0.85s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } + +.activation-shell { height: calc(100% - 48px); overflow: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 38px; background-color: #f1f5fa; background-image: radial-gradient(circle at 1px 1px, rgba(67, 90, 123, 0.075) 1px, transparent 0); background-size: 24px 24px; } +.activation-card { width: min(980px, 92vw); min-height: 570px; display: grid; grid-template-columns: 0.86fr 1.14fr; overflow: hidden; background: white; border: 1px solid rgba(218, 225, 235, 0.9); border-radius: 18px; box-shadow: 0 30px 70px rgba(35, 55, 84, 0.14); } +.activation-brand-panel { position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; padding: 42px 40px; color: white; background: #1769e0; } +.activation-brand-panel::before { content: ""; position: absolute; inset: 0; opacity: 0.18; background-image: radial-gradient(circle at 1px 1px, #fff 1px, transparent 0); background-size: 20px 20px; mask-image: linear-gradient(to bottom right, black, transparent 72%); } +.activation-brand-panel > * { position: relative; z-index: 1; } +.activation-kicker { display: flex; align-items: center; gap: 7px; width: max-content; padding: 7px 10px; border: 1px solid rgba(255,255,255,.24); border-radius: 6px; font-size: 12px; font-weight: 650; letter-spacing: .08em; } +.activation-visual { position: relative; width: 220px; height: 210px; margin: 5px auto -4px; display: grid; place-items: center; } +.visual-shield { width: 142px; height: 142px; display: grid; place-items: center; color: var(--blue); background: white; border-radius: 44% 44% 50% 50% / 34% 34% 62% 62%; box-shadow: 0 20px 50px rgba(8, 42, 98, 0.24); } +.visual-orbit { position: absolute; inset: 18px; border: 1px solid rgba(255,255,255,.25); border-radius: 50%; transform: rotate(-14deg); } +.orbit-two { inset: 2px 34px; transform: rotate(40deg); } +.visual-check { position: absolute; right: 28px; bottom: 29px; display: grid; place-items: center; width: 36px; height: 36px; color: #0a6b61; background: #72eadc; border: 5px solid var(--blue); border-radius: 50%; } +.activation-brand-panel h1 { margin: 0; font-size: 31px; letter-spacing: .02em; } +.activation-brand-panel p { margin: 8px 0 0; color: rgba(255,255,255,.72); font-size: 14px; } +.activation-features { display: flex; gap: 14px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,.18); } +.activation-features span { display: flex; align-items: center; gap: 6px; color: rgba(255,255,255,.8); font-size: 11px; white-space: nowrap; } +.activation-form-panel { display: flex; flex-direction: column; justify-content: center; padding: 56px 68px; } +.eyebrow { color: var(--blue); font-family: Bahnschrift, "DIN Alternate", sans-serif; font-size: 11px; font-weight: 700; letter-spacing: .14em; } +.activation-heading h2 { margin: 12px 0 10px; color: #1d2635; font-size: 28px; line-height: 1.25; } +.activation-heading p { max-width: 470px; margin: 0 0 34px; color: #778195; font-size: 13px; line-height: 1.8; } +.field-label { display: block; margin-bottom: 9px; color: #404959; font-size: 13px; font-weight: 650; } +.activation-input { height: 54px; display: flex; align-items: center; gap: 11px; padding: 0 16px; color: #8190a6; background: #fbfcfe; border: 1px solid #dce3ec; border-radius: 9px; transition: border .18s, box-shadow .18s; } +.activation-input:focus-within { color: var(--blue); border-color: var(--blue); box-shadow: 0 0 0 4px rgba(23,105,224,.08); } +.activation-input.invalid { border-color: #e8a2a2; } +.activation-input input { width: 100%; border: 0; outline: 0; color: #253044; background: transparent; font-family: Bahnschrift, Consolas, monospace; font-size: 17px; letter-spacing: .055em; } +.activation-input input::placeholder { color: #aeb7c5; } +.activation-error { display: flex; align-items: flex-start; gap: 7px; margin-top: 10px; color: #c64747; font-size: 12px; line-height: 1.5; } +.activation-error svg { flex: 0 0 auto; margin-top: 1px; } +.primary-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; color: white; background: var(--blue); border-radius: 7px; font-weight: 650; cursor: pointer; transition: background .18s, transform .18s, box-shadow .18s; } +.primary-button:hover:not(:disabled) { background: var(--blue-deep); box-shadow: 0 8px 20px rgba(23,105,224,.2); transform: translateY(-1px); } +.activation-button { width: 100%; height: 48px; margin-top: 20px; } +.text-button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; color: var(--blue); background: transparent; cursor: pointer; } +.retry-button { margin: 12px auto 0; font-size: 12px; } +.activation-note { display: flex; align-items: flex-start; gap: 8px; margin-top: 28px; padding-top: 20px; color: #8a93a2; border-top: 1px solid #edf0f4; font-size: 11px; line-height: 1.6; } +.activation-note svg { flex: 0 0 auto; margin-top: 1px; } +.activation-footer { margin-top: 22px; color: #9aa4b2; font-size: 11px; } + +.workspace-shell { height: calc(100% - 48px); display: grid; grid-template-columns: 218px 1fr; } +.sidebar { display: flex; flex-direction: column; min-height: 0; padding: 20px 14px 16px; background: #f8fafc; border-right: 1px solid var(--line); } +.sidebar-profile { display: flex; align-items: center; gap: 11px; padding: 12px; background: white; border: 1px solid #e7ebf1; border-radius: 9px; } +.profile-mark { width: 36px; height: 36px; display: grid; place-items: center; color: #0b776a; background: #dff8f3; border-radius: 8px; } +.sidebar-profile div:last-child { min-width: 0; display: flex; flex-direction: column; gap: 3px; } +.sidebar-profile strong { font-size: 12px; } +.sidebar-profile span { overflow: hidden; color: #8b94a4; font-family: Bahnschrift, monospace; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; } +.sidebar-navigation { display: flex; flex-direction: column; gap: 5px; margin-top: 24px; } +.sidebar-navigation button { height: 43px; display: grid; grid-template-columns: 22px 1fr 15px; align-items: center; gap: 9px; padding: 0 12px; color: #687386; background: transparent; border-radius: 7px; text-align: left; cursor: pointer; transition: background .16s, color .16s; } +.sidebar-navigation button:hover { color: #344155; background: #eef2f7; } +.sidebar-navigation button.active { color: var(--blue); background: #eaf2ff; font-weight: 650; } +.sidebar-navigation button svg:last-child { opacity: 0; transform: translateX(-3px); transition: opacity .16s, transform .16s; } +.sidebar-navigation button.active svg:last-child { opacity: 1; transform: translateX(0); } +.sidebar-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 8px; padding: 0 7px; color: #99a2b0; font-size: 10px; } +.version-chip { width: max-content; padding: 4px 7px; color: #66748a; background: #e9eef4; border-radius: 4px; font-family: Bahnschrift, sans-serif; } + +.workspace-main, .detail-page { min-width: 0; min-height: 0; overflow: auto; padding: 28px 30px 30px; } +.update-banner { min-height: 58px; display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 12px; margin-bottom: 18px; padding: 10px 12px; color: #1555a9; background: #eaf3ff; border: 1px solid #cfe2ff; border-radius: 9px; } +.update-banner.force { color: #9b5620; background: #fff4e6; border-color: #f2d3aa; } +.update-banner-icon { width: 34px; height: 34px; display: grid; place-items: center; background: rgba(255,255,255,.72); border-radius: 7px; } +.update-banner div:nth-child(2) { display: flex; flex-direction: column; gap: 4px; } +.update-banner strong { font-size: 12px; } +.update-banner span { overflow: hidden; font-size: 11px; opacity: .76; text-overflow: ellipsis; white-space: nowrap; } +.update-banner button { display: flex; align-items: center; gap: 5px; padding: 8px 10px; color: inherit; background: white; border-radius: 6px; font-size: 11px; font-weight: 650; cursor: pointer; } +.workspace-header { display: flex; align-items: center; justify-content: space-between; gap: 40px; margin-bottom: 22px; } +.workspace-header h1, .detail-header h1 { margin: 8px 0 5px; color: #1d2635; font-size: 25px; line-height: 1.25; } +.workspace-header p, .detail-header p { margin: 0; color: #7c8696; font-size: 12px; } +.process-rail { display: flex; align-items: center; min-width: 390px; padding: 15px 18px; background: white; border: 1px solid var(--line); border-radius: 10px; } +.rail-step { display: flex; align-items: center; gap: 7px; color: #a0a8b4; white-space: nowrap; } +.rail-step span { width: 23px; height: 23px; display: grid; place-items: center; color: #8994a6; background: #eef1f5; border-radius: 50%; font-family: Bahnschrift, sans-serif; font-size: 10px; } +.rail-step b { font-size: 11px; font-weight: 550; } +.rail-step.active { color: #334055; } +.rail-step.active span { color: white; background: var(--blue); } +.rail-line { width: 34px; height: 2px; margin: 0 8px; background: #e4e8ee; transition: background .25s; } +.rail-line.active { background: var(--blue); } +.processor-layout { height: calc(100% - 105px); min-height: 570px; display: grid; grid-template-columns: minmax(0, 1fr) 306px; gap: 18px; } +.queue-card, .settings-card { background: white; border: 1px solid var(--line); border-radius: 11px; box-shadow: 0 4px 15px rgba(43, 59, 84, 0.035); } +.queue-card { min-height: 0; display: flex; flex-direction: column; overflow: hidden; } +.queue-toolbar { min-height: 64px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 0 18px; border-bottom: 1px solid #edf0f4; } +.queue-title { display: flex; align-items: baseline; gap: 9px; } +.queue-title h2, .settings-heading h2 { margin: 0; font-size: 14px; } +.queue-title span { color: #919aa8; font-size: 10px; } +.queue-actions { display: flex; gap: 7px; } +.secondary-button, .icon-button, .primary-quiet-button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 32px; padding: 0 10px; color: #526074; background: white; border: 1px solid #dfe5ed; border-radius: 6px; font-size: 11px; cursor: pointer; transition: color .16s, border .16s, background .16s; } +.secondary-button:hover:not(:disabled), .icon-button:hover:not(:disabled) { color: var(--blue); border-color: #aecaf1; background: #f8fbff; } +.icon-button { width: 32px; padding: 0; } +.drop-zone { flex: 1; margin: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 390px; background: #fbfcfe; border: 1px dashed #cfd8e5; border-radius: 10px; cursor: pointer; transition: border .18s, background .18s, transform .18s; } +.drop-zone:hover, .drop-zone.active { background: #f4f8ff; border-color: var(--blue); } +.drop-visual { position: relative; width: 104px; height: 88px; margin-bottom: 18px; } +.file-tile { position: absolute; width: 58px; height: 66px; display: grid; place-items: center; border-radius: 9px; } +.tile-back { left: 14px; top: 4px; color: #7ea9e5; background: #e5f0ff; transform: rotate(-9deg); } +.tile-front { right: 10px; bottom: 2px; color: white; background: var(--blue); box-shadow: 0 12px 26px rgba(23,105,224,.18); transform: rotate(5deg); } +.drop-zone h3 { margin: 0 0 8px; font-size: 15px; } +.drop-zone p { margin: 0 0 18px; color: #929baa; font-size: 11px; } +.primary-quiet-button { color: var(--blue); background: #edf4ff; border-color: transparent; font-weight: 650; } +.queue-table { flex: 1; min-height: 0; display: flex; flex-direction: column; } +.queue-table-head, .queue-row { display: grid; grid-template-columns: minmax(250px, 1fr) 100px 82px 110px 32px; align-items: center; gap: 10px; } +.queue-table-head { min-height: 39px; padding: 0 18px; color: #98a1af; background: #fafbfd; border-bottom: 1px solid #eef1f5; font-size: 10px; } +.queue-rows { flex: 1; min-height: 0; overflow: auto; } +.queue-row { min-height: 64px; padding: 8px 18px; border-bottom: 1px solid #f0f2f5; transition: background .15s; } +.queue-row:hover { background: #fafcff; } +.queue-row.is-processing { background: #f7faff; } +.file-cell { min-width: 0; display: flex; align-items: center; gap: 10px; } +.file-icon { flex: 0 0 auto; width: 34px; height: 34px; display: grid; place-items: center; color: var(--blue); background: #eaf3ff; border-radius: 7px; } +.file-cell > div:last-child { min-width: 0; display: flex; flex-direction: column; gap: 4px; } +.file-cell strong, .file-cell span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.file-cell strong { color: #374154; font-size: 11px; font-weight: 650; } +.file-cell span { color: #9aa3b1; font-size: 9px; } +.data-cell { color: #747f90; font-family: Bahnschrift, "Microsoft YaHei UI", sans-serif; font-size: 10px; } +.status-cell { min-width: 0; display: flex; align-items: center; gap: 5px; color: #8792a3; font-size: 10px; } +.status-cell span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.status-cell.processing { color: var(--blue); } +.status-cell.completed { color: #149173; } +.status-cell.failed { color: var(--danger); } +.row-action { width: 28px; height: 28px; display: grid; place-items: center; color: #919baa; background: transparent; border-radius: 5px; cursor: pointer; } +.row-action:hover:not(:disabled) { color: var(--blue); background: #edf4ff; } +.progress-dock { padding: 14px 18px; border-top: 1px solid #e8ecf2; background: #fbfcfe; } +.progress-meta { display: flex; align-items: center; justify-content: space-between; margin-bottom: 9px; } +.progress-meta div { display: flex; align-items: baseline; gap: 8px; } +.progress-meta strong { font-size: 11px; } +.progress-meta span { color: #929baa; font-family: Bahnschrift, sans-serif; font-size: 10px; } +.progress-meta b { color: var(--blue); font-family: Bahnschrift, sans-serif; font-size: 11px; } +.progress-track { height: 5px; overflow: hidden; background: #e7ebf1; border-radius: 3px; } +.progress-track span { display: block; height: 100%; background: var(--blue); border-radius: inherit; transition: width .2s ease; } + +.settings-card { display: flex; flex-direction: column; padding: 18px; overflow: auto; } +.settings-heading { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; } +.settings-heading svg { color: var(--blue); } +.setting-group { padding: 16px 0; border-bottom: 1px solid #edf0f4; } +.setting-group > label, .setting-label-row label { display: block; margin-bottom: 9px; color: #4c5667; font-size: 11px; font-weight: 650; } +.path-picker { width: 100%; height: 39px; display: grid; grid-template-columns: 18px 1fr 16px; align-items: center; gap: 7px; padding: 0 10px; color: #566277; background: #f9fafc; border: 1px solid #e1e6ed; border-radius: 7px; cursor: pointer; } +.path-picker span { overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; } +.path-picker svg:last-child { color: #a2aab7; } +.check-row { position: relative; display: flex !important; align-items: center; gap: 7px; margin: 10px 0 0 !important; color: #758092 !important; font-size: 10px !important; font-weight: 400 !important; cursor: pointer; } +.check-row input { position: absolute; opacity: 0; } +.custom-check { width: 17px; height: 17px; display: grid; place-items: center; color: transparent; background: white; border: 1px solid #cfd7e2; border-radius: 4px; } +.check-row input:checked + .custom-check { color: white; background: var(--blue); border-color: var(--blue); } +.format-switch { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; } +.format-switch button { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; padding: 11px; color: #697488; background: #fafbfd; border: 1px solid #e2e7ee; border-radius: 7px; cursor: pointer; } +.format-switch button.active { color: var(--blue); background: #f1f6ff; border-color: #9fc2f3; box-shadow: inset 0 0 0 1px rgba(23,105,224,.08); } +.format-switch b { font-family: Bahnschrift, Consolas, monospace; font-size: 12px; } +.format-switch span { font-size: 9px; opacity: .72; } +.setting-label-row { display: flex; align-items: center; justify-content: space-between; } +.setting-label-row label { margin: 0; } +.junk-size-input { display: flex; align-items: center; overflow: hidden; height: 28px; background: #f8fafc; border: 1px solid #dde4ed; border-radius: 6px; } +.junk-size-input input { width: 58px; height: 100%; padding: 0 6px; color: var(--blue); background: transparent; border: 0; outline: 0; font-family: Bahnschrift, sans-serif; font-size: 11px; text-align: right; } +.junk-size-input span { padding-right: 7px; color: #8993a2; font-size: 9px; } +.range-control { width: 100%; height: 4px; margin: 16px 0 7px; accent-color: var(--blue); cursor: pointer; } +.range-labels { display: flex; justify-content: space-between; color: #a0a8b5; font-family: Bahnschrift, sans-serif; font-size: 8px; } +.setting-help { margin: 10px 0 0; color: #949dab; font-size: 9px; line-height: 1.6; } +.algorithm-note { margin-top: 15px; padding: 12px; color: #34605a; background: #edf9f6; border: 1px solid #d7efe9; border-radius: 8px; } +.algorithm-note div { display: flex; align-items: center; gap: 7px; } +.algorithm-note strong { font-size: 10px; } +.algorithm-note p { margin: 7px 0 0; color: #6e8e89; font-size: 9px; line-height: 1.55; } +.start-area { margin-top: auto; padding-top: 18px; } +.start-button, .cancel-button { width: 100%; height: 43px; } +.cancel-button { display: flex; align-items: center; justify-content: center; gap: 8px; color: #b44444; background: #fff2f2; border: 1px solid #efcaca; border-radius: 7px; font-weight: 650; cursor: pointer; } +.start-area > span { display: block; margin-top: 9px; color: #9aa3b0; font-size: 9px; text-align: center; } + +.detail-page { padding: 40px 48px; } +.detail-header { margin-bottom: 24px; } +.license-hero-card { display: grid; grid-template-columns: 72px 1fr auto; align-items: center; gap: 20px; padding: 28px; color: white; background-color: #1769e0; background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,.16) 1px, transparent 0); background-size: 22px 22px; border-radius: 12px; box-shadow: 0 18px 40px rgba(23,105,224,.18); } +.license-shield { width: 68px; height: 68px; display: grid; place-items: center; color: var(--blue); background: white; border-radius: 18px; } +.license-status { display: flex; align-items: center; gap: 7px; color: rgba(255,255,255,.76); font-size: 10px; } +.license-status span { width: 7px; height: 7px; background: #70ead9; border-radius: 50%; } +.license-hero-copy h2 { margin: 9px 0 7px; font-family: Bahnschrift, Consolas, monospace; font-size: 22px; letter-spacing: .04em; } +.license-hero-copy p { margin: 0; color: rgba(255,255,255,.72); font-size: 11px; } +.license-expiry { display: flex; flex-direction: column; align-items: flex-end; gap: 7px; padding-left: 30px; border-left: 1px solid rgba(255,255,255,.2); } +.license-expiry span { color: rgba(255,255,255,.64); font-size: 10px; } +.license-expiry strong { font-size: 14px; } +.detail-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 18px; } +.detail-card { display: flex; flex-direction: column; min-height: 132px; padding: 20px; background: white; border: 1px solid var(--line); border-radius: 10px; } +.detail-card span { color: #8993a2; font-size: 10px; } +.detail-card strong { margin: 15px 0 7px; font-size: 18px; } +.detail-card small { margin-top: auto; color: #9ba4b2; font-size: 9px; } +.danger-zone { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; padding: 20px; background: white; border: 1px solid #f0dede; border-radius: 10px; } +.danger-zone strong { font-size: 12px; } +.danger-zone p { margin: 6px 0 0; color: #929aa7; font-size: 10px; } +.danger-outline-button { display: flex; align-items: center; gap: 6px; padding: 9px 12px; color: #c14949; background: white; border: 1px solid #e7baba; border-radius: 6px; font-size: 10px; cursor: pointer; } +.about-card { display: grid; grid-template-columns: 64px 1fr auto; align-items: center; gap: 18px; padding: 25px; background: white; border: 1px solid var(--line); border-radius: 11px; } +.about-mark { width: 62px; height: 62px; display: grid; place-items: center; color: white; background: var(--blue); border-radius: 16px; } +.about-copy h2 { margin: 0 0 5px; font-size: 20px; } +.about-copy p { margin: 0 0 8px; color: #7f8999; font-size: 11px; } +.about-copy span { color: #a0a8b5; font-family: Bahnschrift, sans-serif; font-size: 9px; } +.release-card { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; padding: 18px; background: #edf4ff; border: 1px solid #d4e4fa; border-radius: 10px; } +.release-card > div { display: flex; align-items: flex-start; gap: 12px; color: var(--blue); } +.release-card strong { color: #2e3c51; font-size: 12px; } +.release-card p { margin: 5px 0 0; color: #748095; font-size: 10px; } +.release-card button { padding: 9px 14px; } +.about-features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 14px; } +.about-features > div { min-height: 150px; display: flex; flex-direction: column; padding: 20px; color: var(--blue); background: white; border: 1px solid var(--line); border-radius: 10px; } +.about-features strong { margin-top: 20px; color: #313c4f; font-size: 12px; } +.about-features p { margin: 7px 0 0; color: #8d96a4; font-size: 10px; line-height: 1.6; } + +.drop-overlay { position: fixed; z-index: 50; inset: 48px 0 0; display: grid; place-items: center; padding: 32px; background: rgba(238, 245, 254, .9); backdrop-filter: blur(6px); } +.drop-overlay > div { width: min(620px, 70vw); height: 310px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--blue); background: white; border: 2px dashed #81adea; border-radius: 16px; box-shadow: var(--shadow); } +.drop-overlay strong { margin-top: 18px; color: #2f3b4e; font-size: 18px; } +.drop-overlay span { margin-top: 8px; color: #8c96a5; font-size: 11px; } +.toast-message { position: fixed; z-index: 80; left: 50%; bottom: 28px; display: flex; align-items: center; gap: 8px; max-width: 520px; padding: 11px 15px; color: #2d384a; background: white; border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 14px 34px rgba(36, 51, 74, .16); font-size: 11px; transform: translateX(-50%); } +.toast-message.success svg { color: #159271; } +.toast-message.error svg { color: var(--danger); } +.toast-message.info svg { color: var(--blue); } +.toast-enter-active, .toast-leave-active { transition: opacity .2s, transform .2s; } +.toast-enter-from, .toast-leave-to { opacity: 0; transform: translate(-50%, 10px); } + +@media (max-width: 1220px) { + .workspace-shell { grid-template-columns: 196px 1fr; } + .workspace-main { padding: 24px 22px; } + .process-rail { min-width: 350px; } + .processor-layout { grid-template-columns: minmax(0, 1fr) 286px; } + .activation-form-panel { padding: 50px 48px; } +} + +/* 保证小屏笔记本在允许的最小窗口高度下仍能看到核心处理操作。 */ +@media (max-height: 760px) { + .workspace-main { padding-top: 18px; padding-bottom: 18px; } + .workspace-header { margin-bottom: 14px; } + .processor-layout { height: calc(100% - 91px); min-height: 0; } + .settings-card { padding: 14px; } + .setting-group { padding: 12px 0; } + .algorithm-note { margin-top: 10px; padding: 10px; } + .start-area { padding-top: 12px; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; } +} diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..35aadc7 --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,90 @@ +export type OutputFormat = 'file' | 'file-jpg' +export type ImageStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled' + +export interface ImageFileItem { + id: string + path: string + name: string + size: number + width: number | null + height: number | null + status: ImageStatus + outputPath?: string + error?: string +} + +export interface ProcessOptions { + paths: string[] + useSourceDirectory: boolean + outputDirectory?: string + outputFormat: OutputFormat + junkSizeMb: number +} + +export interface ProcessProgress { + current: number + total: number + percent: number + item: ImageFileItem + cancelled: boolean +} + +export interface ProcessSummary { + total: number + completed: number + failed: number + cancelled: boolean +} + +export interface VersionInfo { + updateAvailable: boolean + latestVersion?: string + releaseNotes?: string + downloadUrl?: string + enforce: boolean +} + +export interface LicenseSession { + token?: string + licenseCodeMask: string + deviceMask: string + expiresAt?: string + maxDevices: number + version?: VersionInfo +} + +export type LicenseStatus = 'checking' | 'unlicensed' | 'active' | 'invalid' | 'network-error' + +export interface LicenseViewState { + status: LicenseStatus + message: string + offline: boolean + session?: LicenseSession + version?: VersionInfo +} + +export interface AppInfo { + name: string + version: string + platform: string + deviceName: string +} + +export interface DesktopBridge { + getAppInfo(): Promise