feat(settings): use radio buttons for gateway
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
| [tab6-suite-prompt-dialog-T637.svg](tab6-suite-prompt-dialog-T637.svg) | 商品套图(T-637 提案):标题行「AI帮写/取消」紧随 label、最右新增「提示词设置」;提示词设置弹窗——左编辑基础模板(行序对齐调研笔记组装顺序,套图名称+补充描述、插入变量菜单、占位符校验、只读规则占位符内联)、右只读实时最终预览(预览分类下拉)、保存/恢复默认共用原子写盘;第一版不做同类多张序号/差异化 |
|
||||
| [tab6-suite-gateway-T679b.svg](tab6-suite-gateway-T679b.svg) | 商品套图(T-679b 已实现):自定义网关生成确认展示主图/参考图、近似比例尺寸、服务商费用与主体一致性边界;生成中锁定当前任务输入,停止等待已发出同步请求返回,同时仍可切换其它任务与查看历史/结果 |
|
||||
| [tab6-suite-recovery-T679c.svg](tab6-suite-recovery-T679c.svg) | 商品套图(T-679c 已实现):结果卡和历史按已保存 job 显示默认网关/自定义网关;默认网关任务可继续查询,直连失败图只能手动重新生成并再次确认可能重复收费 |
|
||||
| [settings-gateway-radio-prototype-v1.html](settings-gateway-radio-prototype-v1.html) | 设置(T-680 已实现):在原 AI 模型配置标题下保留同行位置,仅以原生单选框替换默认网关/自定义网关的分段按钮;可在浏览器中模拟切换、待保存状态和保存,不改其它设置组件 |
|
||||
| [workbench-redesign-v1.html](workbench-redesign-v1.html) | 工作台视觉提案(HTML 可交互原型):以当前 6 Tab 信息架构为基础,统一「数据准备区→表格命令区→表格→状态反馈区→完成任务区」页面骨架;命令按作用域分区(导入/筛选/选择集/工作流完成),表格补齐当前行、选择集、三态全选与排序契约;范围确认、部分成功、取消中间态和破坏性确认按干扰层级分配到对话框、信息条与 Toast;覆盖浅色/深色/高对比度近似、键盘(Tab/方向键/F6/Ctrl+F/F5)与 Windows 断点。商品状态相关交互已对齐 T-662b/c/d、T-663~T-665、T-667 定稿(无状态列与状态下拉筛选,③无强制更新入口)。颜色 Token 取自 `app/gui/widgets.py`。使用模拟数据,不接生产接口;页脚「原型说明与验收边界」列出假设与不可仅靠 HTML 验收的项。 |
|
||||
|
||||
应用图标(非效果图,是随程序发布的资源):`app/assets/cmshopee-logo.svg` 是可编辑母版,`cmshopee.ico`(16–256 共 9 档)用于 exe 图标和 GUI 窗口左上角,`cmshopee-256.png` 为预览与非 Windows 回退。三者由 `py -3.10 scripts/gen_logo.py` 从同一份几何定义生成,改样式请改脚本后重新生成,不要单独手改某一个文件。
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>设置 - 生成网关单选框原型</title>
|
||||
<style>
|
||||
:root {
|
||||
--canvas: #f6f8fa;
|
||||
--surface: #ffffff;
|
||||
--surface-subtle: #f6f8fa;
|
||||
--text: #24292f;
|
||||
--muted: #57606a;
|
||||
--border: #d0d7de;
|
||||
--border-strong: #8c959f;
|
||||
--accent: #0969da;
|
||||
--accent-soft: #ddf4ff;
|
||||
--warning: #9a6700;
|
||||
--warning-soft: #fff8c5;
|
||||
--radius: 4px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
min-width: 640px;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: var(--canvas);
|
||||
color: var(--text);
|
||||
font: 14px/1.45 "Segoe UI", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
button, input, select { font: inherit; }
|
||||
button:focus-visible, input:focus-visible, select:focus-visible {
|
||||
outline: 3px solid rgba(9, 105, 218, .35);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.window {
|
||||
width: min(1180px, calc(100% - 40px));
|
||||
min-height: 720px;
|
||||
margin: 20px auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 30px rgba(31, 35, 40, .12);
|
||||
overflow: hidden;
|
||||
}
|
||||
.titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
background: #203451;
|
||||
color: #ffffff;
|
||||
font-weight: 650;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 7px 14px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface-subtle);
|
||||
}
|
||||
.tab {
|
||||
min-width: 104px;
|
||||
min-height: 34px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tab[aria-selected="true"] {
|
||||
border-color: var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--accent);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.settings-scroll { padding: 28px 18px 18px; }
|
||||
.settings-panel { width: min(920px, 100%); margin: 0 auto; }
|
||||
.section-title {
|
||||
margin: 0 0 8px;
|
||||
padding-top: 4px;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.gateway-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
min-height: 42px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.gateway-selector fieldset {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
clip-path: inset(50%);
|
||||
}
|
||||
.gateway-radio {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 108px;
|
||||
min-height: 30px;
|
||||
padding: 5px 12px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.gateway-radio:hover { color: var(--accent); }
|
||||
.gateway-radio input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 7px 0 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.gateway-radio:has(input:focus-visible) {
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.section-gap { height: 14px; }
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 104px minmax(0, 1fr) 104px minmax(0, 1fr) 104px minmax(0, 1fr);
|
||||
gap: 8px 18px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
.form-grid label { color: var(--text); }
|
||||
.form-grid input, .form-grid select {
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
.span-wide { grid-column: span 3; }
|
||||
.helper {
|
||||
grid-column: 2 / -1;
|
||||
margin: -2px 0 2px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.panel-note {
|
||||
margin: 8px 0 0 122px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.hidden { display: none; }
|
||||
|
||||
.divider { height: 1px; margin: 18px 0; background: var(--border); }
|
||||
.placeholder-section {
|
||||
display: grid;
|
||||
grid-template-columns: 104px minmax(0, 1fr) 104px minmax(0, 1fr) 104px minmax(0, 1fr);
|
||||
gap: 8px 18px;
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
}
|
||||
.placeholder-input {
|
||||
min-height: 30px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-subtle);
|
||||
}
|
||||
|
||||
.save-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.button {
|
||||
min-height: 30px;
|
||||
min-width: 86px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-subtle);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover { background: #eef4ff; }
|
||||
.button.primary { border-color: var(--accent); background: var(--accent); color: #ffffff; }
|
||||
.unsaved {
|
||||
color: var(--warning);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.status {
|
||||
margin-left: auto;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 840px) {
|
||||
.window { width: 100%; min-height: 100vh; margin: 0; border: 0; border-radius: 0; }
|
||||
.tabs { overflow-x: auto; }
|
||||
.form-grid, .placeholder-section { grid-template-columns: 96px minmax(180px, 1fr); }
|
||||
.form-grid label:nth-of-type(n+2), .placeholder-section span:nth-of-type(n+2) { grid-column: 1; }
|
||||
.form-grid input, .form-grid select, .placeholder-input { grid-column: 2; }
|
||||
.span-wide { grid-column: 2; }
|
||||
.helper { grid-column: 2; }
|
||||
.panel-note { margin-left: 104px; }
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
.gateway-radio:has(input:checked) { color: HighlightText; background: Highlight; }
|
||||
.gateway-radio:has(input:focus-visible) { outline: 2px solid Highlight; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="window">
|
||||
<header class="titlebar">蝦皮圈優化助手</header>
|
||||
<nav class="tabs" role="tablist" aria-label="主模块">
|
||||
<button class="tab" type="button" role="tab" aria-selected="false">① 导入采集</button>
|
||||
<button class="tab" type="button" role="tab" aria-selected="false">② AI生成</button>
|
||||
<button class="tab" type="button" role="tab" aria-selected="false">③ 更新蝦皮</button>
|
||||
<button class="tab" type="button" role="tab" aria-selected="false">账号管理</button>
|
||||
<button class="tab" type="button" role="tab" aria-selected="true">设置</button>
|
||||
<button class="tab" type="button" role="tab" aria-selected="false">商品套图</button>
|
||||
</nav>
|
||||
|
||||
<main class="settings-scroll">
|
||||
<div class="settings-panel">
|
||||
<h1 class="section-title">AI 模型配置</h1>
|
||||
<div class="gateway-selector">
|
||||
<fieldset>
|
||||
<legend class="visually-hidden">生成网关</legend>
|
||||
<label class="gateway-radio">
|
||||
<input type="radio" name="gateway" value="cmhub" checked>
|
||||
默认网关
|
||||
</label>
|
||||
<label class="gateway-radio">
|
||||
<input type="radio" name="gateway" value="direct">
|
||||
自定义网关
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<section id="cmhubPanel" aria-label="默认网关配置">
|
||||
<div class="form-grid">
|
||||
<label for="baseUrl">网关 Base URL</label>
|
||||
<input class="span-wide" id="baseUrl" value="https://cmhub.example.com">
|
||||
<p class="helper">只填默认网关根地址,不要带 /api 或 /api/v1。</p>
|
||||
|
||||
<label for="apiKey">API Key</label>
|
||||
<input id="apiKey" type="password" value="cmhub-local-key">
|
||||
<label for="titleAlias">标题生成别名</label>
|
||||
<select id="titleAlias"><option>title-standard</option></select>
|
||||
<label for="imageAlias">图片生成别名</label>
|
||||
<select id="imageAlias"><option>image-hd</option></select>
|
||||
|
||||
<label for="visionAlias">图片理解别名</label>
|
||||
<select id="visionAlias"><option>vision-standard</option></select>
|
||||
<label for="connectTimeout">连接超时</label>
|
||||
<input id="connectTimeout" value="66">
|
||||
<span>批量生成前检查余额</span>
|
||||
<label><input type="checkbox"> 启用</label>
|
||||
</div>
|
||||
<p class="panel-note">默认网关的输入框、顺序和保存规则保持现有实现不变。</p>
|
||||
</section>
|
||||
|
||||
<section id="directPanel" class="hidden" aria-label="自定义网关模型配置">
|
||||
<div class="form-grid">
|
||||
<label for="modelList">模型列表</label>
|
||||
<select class="span-wide" id="modelList"><option>图片编辑模型 · 图像</option></select>
|
||||
<p class="helper">选择、保存或删除模型的原有交互保持不变。</p>
|
||||
|
||||
<label for="modelName">模型名称</label>
|
||||
<input id="modelName" value="图片编辑模型">
|
||||
<label for="modelId">模型 ID</label>
|
||||
<input id="modelId" value="image-edit">
|
||||
<label for="modelUrl">网址</label>
|
||||
<input id="modelUrl" value="https://example.com/v1">
|
||||
</div>
|
||||
<p class="panel-note">此处仅按现有面板显示,不新增字段或状态组件。</p>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
<h2 class="section-title">AI 生成参数</h2>
|
||||
<div class="placeholder-section" aria-label="未修改的生成参数示意">
|
||||
<span>标题并发</span><div class="placeholder-input"></div>
|
||||
<span>图片并发</span><div class="placeholder-input"></div>
|
||||
<span>失败重试</span><div class="placeholder-input"></div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<h2 class="section-title">路径与端口</h2>
|
||||
<div class="placeholder-section" aria-label="未修改的路径与端口示意">
|
||||
<span>Chrome 路径</span><div class="placeholder-input"></div>
|
||||
<span>默认端口</span><div class="placeholder-input"></div>
|
||||
<span>端口范围</span><div class="placeholder-input"></div>
|
||||
</div>
|
||||
|
||||
<div class="save-row">
|
||||
<button class="button primary" id="saveButton" type="button">保存设置</button>
|
||||
<span class="unsaved hidden" id="unsavedLabel">● 未保存更改</span>
|
||||
<span class="status" id="statusText" role="status" aria-live="polite">当前设置已保存</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const radios = [...document.querySelectorAll('input[name="gateway"]')];
|
||||
const cmhubPanel = document.getElementById("cmhubPanel");
|
||||
const directPanel = document.getElementById("directPanel");
|
||||
const unsavedLabel = document.getElementById("unsavedLabel");
|
||||
const statusText = document.getElementById("statusText");
|
||||
const saveButton = document.getElementById("saveButton");
|
||||
const names = { cmhub: "默认网关", direct: "自定义网关" };
|
||||
let saved = "cmhub";
|
||||
let selected = "cmhub";
|
||||
|
||||
function render() {
|
||||
const dirty = selected !== saved;
|
||||
radios.forEach((radio) => { radio.checked = radio.value === selected; });
|
||||
cmhubPanel.classList.toggle("hidden", selected !== "cmhub");
|
||||
directPanel.classList.toggle("hidden", selected !== "direct");
|
||||
unsavedLabel.classList.toggle("hidden", !dirty);
|
||||
statusText.textContent = dirty
|
||||
? `待保存:${names[selected]}`
|
||||
: `当前使用${names[saved]}`;
|
||||
}
|
||||
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener("change", () => {
|
||||
if (radio.checked) {
|
||||
selected = radio.value;
|
||||
render();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
saveButton.addEventListener("click", () => {
|
||||
saved = selected;
|
||||
render();
|
||||
statusText.textContent = `设置已保存,当前使用${names[saved]}`;
|
||||
});
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user