132 lines
7.6 KiB
Markdown
132 lines
7.6 KiB
Markdown
# 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
|
||
<div id="preview"></div>
|
||
<script type="module">
|
||
const layoutId = 'ref-01';
|
||
const parameters = {texts:{T01:{value:'STUDY'}},images:{I01:{color:'#7f9188'}}};
|
||
const response = await fetch(`/studio/api/layouts/${layoutId}`, {
|
||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(parameters)
|
||
});
|
||
if (!response.ok) throw new Error(await response.text());
|
||
const previewUrl = URL.createObjectURL(await response.blob());
|
||
const image = new Image(); image.src = previewUrl;
|
||
document.querySelector('#preview').replaceChildren(image);
|
||
// 预览完成后可 URL.revokeObjectURL(previewUrl)。
|
||
const saved = await fetch('/api/studio/designs', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body:JSON.stringify({layoutId,name:'知识合集第一版',parameters})
|
||
});
|
||
if (!saved.ok) throw new Error(await saved.text());
|
||
const {design} = await saved.json();
|
||
console.log(design.id, design.url, design.revision);
|
||
</script>
|
||
```
|
||
|
||
## 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 <><input value={title} onChange={e=>setTitle(e.target.value)}/>{error&&<p>{error}</p>}{src&&<img src={src} alt="当前排版效果"/>}</>;
|
||
}
|
||
```
|
||
|
||
## Next.js:客户端编辑组件
|
||
|
||
```tsx
|
||
// app/layout-playground/page.tsx
|
||
'use client';
|
||
import {useState} from 'react';
|
||
export default function Playground(){
|
||
const [title,setTitle]=useState('STUDY'),[link,setLink]=useState(''),[status,setStatus]=useState('');
|
||
async function save(){
|
||
const response=await fetch('/api/studio/designs',{method:'POST',headers:{'Content-Type':'application/json'},
|
||
body:JSON.stringify({layoutId:'ref-01',name:title||'排版效果',parameters:{texts:{T01:{value:title}}}})});
|
||
const data=await response.json();if(!response.ok){setStatus(data.error);return}setLink(data.design.url);setStatus('已保存');
|
||
}
|
||
return <><input value={title} onChange={e=>setTitle(e.target.value)}/><button onClick={save}>保存新效果</button><p>{status}</p>{link&&<a href={link}>打开效果</a>}</>;
|
||
}
|
||
```
|
||
|
||
这些前端示例应放在已登录的 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=<nextCursor>` 继续。
|
||
|
||
## 验收标准
|
||
|
||
- 27 个 ID 都有独立效果页、参数说明与渲染 API,来源链接与 T/I/D 构件一一对应。
|
||
- 默认效果展示原图;替换一组文字、一个图位或装饰后,只改变指定构件。
|
||
- API 渲染与界面使用相同模板、验证和渲染器;导出的 SVG 不包含辅助边界。
|
||
- 保存返回独立 UUID,刷新或再次访问效果 URL 能恢复参数;更新后 revision 增加,过期修订返回 409。
|
||
- 图片 assetId 必须是 Wiki 中已上传的图片;不接受未知构件或外站 URL。
|
||
- Wiki 与 Studio 共用现有登录和数据库,Studio 无独立持久化或数据库凭据。
|