From dec44ec4ea94a4f629c5ead05c3e6384cbe7715a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 3 Oct 2026 17:47:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=90=8E=E5=8F=B0=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=A2=84=E8=A7=88):=20=E4=BC=9A=E5=91=98=E6=A1=A3=E4=BD=8D?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=8C=89=E8=AF=B7=E6=B1=82=E4=BD=93=E5=9B=9E?= =?UTF-8?q?=E6=98=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 假后台读取 POST 请求体,把提交字段覆盖到对应档位行再回显,保存非 starter 档不再把表单与列表重置成 starter。 - 抽出 UNLIMITED_CONCURRENCY_THRESHOLD 常量,列表与回显共用同一哨兵。 --- scripts/admin-web-fake-api.mjs | 60 +++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/scripts/admin-web-fake-api.mjs b/scripts/admin-web-fake-api.mjs index 2ebb1b7b5..7e3b21909 100644 --- a/scripts/admin-web-fake-api.mjs +++ b/scripts/admin-web-fake-api.mjs @@ -4,6 +4,9 @@ import http from 'node:http'; const port = Number(process.argv[2] ?? 8099); +/** 与后端 `MEMBERSHIP_UNLIMITED_CONCURRENCY` 一致的假数据哨兵。 */ +const UNLIMITED_CONCURRENCY_THRESHOLD = 128; + const admin = { subject: 'admin:local', username: 'owner', @@ -473,7 +476,15 @@ const userDetail = { tags: ['vip', '内测'], }; -function route(pathname, method, url) { +function parseJsonBody(requestBody) { + try { + return JSON.parse(requestBody); + } catch { + return null; + } +} + +function route(pathname, method, url, requestBody = '') { if (pathname === '/admin/api/login' && method === 'POST') { return { token: 'local-fake-token', admin }; } @@ -756,7 +767,8 @@ function route(pathname, method, url) { periodPoints, modelAccess, concurrentJobLimit, - unlimitedConcurrency: concurrentJobLimit >= 128, + unlimitedConcurrency: + concurrentJobLimit >= UNLIMITED_CONCURRENCY_THRESHOLD, enabled, }); const plans = [ @@ -766,10 +778,22 @@ function route(pathname, method, url) { plan('pro', '专业会员', 3, 29_900, 299_000, 3_650, 'full', 5, true), plan('max', '旗舰会员', 4, 69_900, 699_000, 8_650, 'full', 128, true), ]; - // 本地预览不解析请求体:POST 统一回显 starter,仅用于页面回填。 - return method === 'POST' - ? plans[1] - : { entries: plans, unlimitedConcurrencyThreshold: 128 }; + // 本地预览按请求体回显被保存的档位:把提交字段覆盖到对应档位行,避免保存非 starter 档后表单被回填成 starter。 + if (method === 'POST') { + const submitted = parseJsonBody(requestBody); + const target = + plans.find((entry) => entry.plan === submitted?.plan) ?? plans[1]; + const merged = { ...target, ...(submitted ?? {}) }; + return { + ...merged, + unlimitedConcurrency: + Number(merged.concurrentJobLimit) >= UNLIMITED_CONCURRENCY_THRESHOLD, + }; + } + return { + entries: plans, + unlimitedConcurrencyThreshold: UNLIMITED_CONCURRENCY_THRESHOLD, + }; } if (pathname === '/admin/api/profile/recharge-products') { const product = ( @@ -1152,13 +1176,25 @@ function route(pathname, method, url) { const server = http.createServer((request, response) => { const url = new URL(request.url ?? '/', `http://127.0.0.1:${port}`); - const payload = route(url.pathname, request.method ?? 'GET', url); - const body = JSON.stringify({ data: payload }); - response.writeHead(200, { - 'Content-Type': 'application/json; charset=utf-8', - 'x-genarrative-response-envelope': 'v1', + const chunks = []; + request.on('data', (chunk) => { + chunks.push(chunk); + }); + request.on('end', () => { + const requestBody = Buffer.concat(chunks).toString('utf8'); + const payload = route( + url.pathname, + request.method ?? 'GET', + url, + requestBody, + ); + const body = JSON.stringify({ data: payload }); + response.writeHead(200, { + 'Content-Type': 'application/json; charset=utf-8', + 'x-genarrative-response-envelope': 'v1', + }); + response.end(body); }); - response.end(body); }); server.listen(port, '127.0.0.1', () => {