# 27 套排版 · Studio 参数 API 与保存教程 把这篇 post 交给 agent,即可选排版、预览、调整参数并保存可再次打开的效果。每套 ref-01…ref-27 都有独立页面,与原图的 T(文字)、I(图位)、D(装饰)构件对应。 ## 先看效果,再通过 API 调整 {{LAYOUTS}} 以上页面都可以修改文字、图位颜色或图片、装饰颜色和显隐、纸色,也能输入完整参数 JSON。打开“显示 T/I/D 构件边界”核对位置;“原图与构件对应表”可查看原图及坐标。页面下方可找到已保存的此排版效果。 ## Agent 调用顺序 1. 读取 [全部 27 套排版目录与接口](/studio/api/layouts?agent_mode=True),选择 ID。 2. GET `/studio/api/layouts/ref-01` 获取当前模板的真实构件 ID、坐标、方向与可辨识文字。也可以先读 [渲染接口说明](/studio/api/layouts/ref-01?agent_mode=True)。不要把其他模板的 T/I/D ID 混用。 3. POST 同地址传入参数,得到 SVG。渲染不会自动保存;迭代参数直到效果合适。 4. POST `/api/studio/designs` 保存新效果,返回 `design.id`、`design.url` 与 `design.revision`。通过 `design.url` 可直接看效果。 5. GET `/api/studio/designs/{id}` 读取上次参数;PATCH 同地址携带 revision 更新同一 ID。要保留两个不同版本,使用 POST 保存两个新 ID。 所有 API 与 Wiki 共用现有登录 Cookie 或 Bearer JWT。浏览器修改请求必须同源。Studio 本身不配置数据库连接,保存由 Wiki 写入同一个 PostgreSQL。 ## 参数示例:ref-01 ```json { "texts": { "T01": {"value": "STUDY", "color": "#26362f", "scale": 1, "align": "start"}, "T02": {"value": "知识合集\n从记录到创作"} }, "images": { "I01": {"color": "#7f9188"}, "I02": {"color": "#b7c4bb"} }, "decorations": {"D01": {"color": "#53645b", "hidden": false}}, "paper": "#f4f2eb" } ``` 文字最多 500 字、8 行,scale 范围 0.25–2,align 为 start/middle/end。文字 value 为空则移除这一组;删除其参数键则恢复原图字形。图片可以只改色,也可先通过 `/api/upload` 的 `assetOnly=true` 上传,再传入 `assetId` UUID 与 `fit: cover/contain`;不接受任意图片网址。装饰支持颜色与 hidden。参数在画板内裁切,未知字段、错误构件 ID、非法颜色和超限输入返回 400。请求最大 64KB。 原始参考来自低分辨率拼图。默认效果保留原图;未替换的构件继续使用原始分层,图位使用原始形状蒙版。可替换文字生成 SVG 文字;无法辨识的小字标为 unreadable,保留原图层而不编造文字。纸色作用于原有底层,不替代图位色块。页面预览通过同站媒体读取图片;API 导出时会使用当前登录读取 Wiki 有尺寸上限的 WebP 预览,并内嵌到 SVG,使下载结果不依赖外部图片链接。原始大图仍保留在 Wiki,导出使用最长 960 像素的预览。 ## HTML / JavaScript:渲染与保存 ```html
``` ## React:参数变化重新预览 ```tsx import {useEffect,useState} from 'react'; export function Preview(){ const [title,setTitle]=useState('STUDY'),[src,setSrc]=useState(''),[error,setError]=useState(''); useEffect(()=>{ const controller=new AbortController();let url=''; fetch('/studio/api/layouts/ref-01',{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({texts:{T01:{value:title}}}),signal:controller.signal}) .then(async r=>{if(!r.ok)throw Error(await r.text());return r.blob()}) .then(blob=>{if(!controller.signal.aborted){url=URL.createObjectURL(blob);setSrc(url);setError('')}}) .catch(e=>{if(!controller.signal.aborted)setError(e.message)}); return()=>{controller.abort();if(url)URL.revokeObjectURL(url)}; },[title]); return <>setTitle(e.target.value)}/>{error&&{error}
}{src&&{status}
{link&&打开效果}>; } ``` 这些前端示例应放在已登录的 PROMPT 同站环境。服务端 agent 使用既有 Bearer JWT,只在进程内读取凭据,勿把令牌写入 post、源码、日志或 URL。接口错误不可当作渲染结果处理。 ## 按 ID 读取、修改、保留分支 ```js const headers = {'Content-Type':'application/json'}; const read = await fetch(`/api/studio/designs/${id}`); if (!read.ok) throw Error(await read.text()); const {design} = await read.json(); const parameters = {...design.parameters,texts:{...design.parameters.texts,T01:{value:'第二版'}}}; const update = await fetch(`/api/studio/designs/${id}`, { method:'PATCH',headers, body:JSON.stringify({name:'知识合集第二版',parameters,revision:design.revision}) }); if (update.status===409) throw Error('有人已经修改,请重新读取后合并'); if (!update.ok) throw Error(await update.text()); // 想保留旧版时,改用 POST /api/studio/designs,传 {layoutId:design.layoutId,name,parameters}。 ``` PATCH 的 parameters 整体替换,因此先 GET 并合并未修改的构件;layoutId 不可改变。错误:400 参数不合法、401 未登录、404 不存在、409 修订冲突、413 超限。[效果保存接口说明](/api/studio/designs?agent_mode=True)。列表可用 `layoutId` 筛选、`limit=1..50`;返回 nextCursor 后用 `before=