# AI Code Studio 开发文档（通用智能体用）

AI Code Studio 是织信平台的 **完整 Vue 3 + Vite 前端项目** 模块，比 website 模块体验好得多，适合做**交互式、组件化、带路由/状态/图表的复杂前端**（管理后台、数据大屏、业务页面等）。

本文档教你（通用智能体）如何用 `_codestudio_*` 系列工具，在普通对话里从零搭一个 codestudio 应用。**所有 `_xxx` 方法都通过 `node call_informat.js <method> --appId <appId> --file /dev/stdin <<'EOF' {json} EOF` 调用。**

---

## 一、何时用 codestudio

- 需要交互式 / 组件化 / 带路由、状态管理、ECharts 图表的复杂前端 → **用 codestudio**。
- 纯静态展示页（一两个 html，无交互）→ 用 website 模块（`_website_*`）。
- 同一需求二选一，不要混用。

---

## 二、全流程（务必按顺序）

```
1. _codestudio_list_modules        查现有模块，能复用就复用（别重复建）
2. _codestudio_create_module       没有合适的才新建 → 拿到 moduleId
3. _query_all_table_list           看应用里有哪些表（严禁凭空造 tableId）
4. _query_table_define             拿目标表的字段定义
5. _codestudio_create_script       建后端脚本（查表/增删改）→ 拿到 scriptId
6. _codestudio_create_api          建 API 绑定脚本函数 → 前端用它的 path
7. _codestudio_write_file          写 Vue 文件（App.vue / components / views / router）
8. _codestudio_compile             编译，看结构化报错；失败就改文件再编，直到 success
9. _codestudio_preview             拿预览地址给用户
```

> 关键：你**没有项目的本地文件系统**。读写项目文件一律用 `_codestudio_read_file` / `_codestudio_write_file` / `_codestudio_list_files`，**不要**用 Read/Write/Edit/Bash 去碰文件系统。

---

## 三、建/复用模块

1. 先 `_codestudio_list_modules`（无参，只 appId）：返回 `[{moduleId, name, remark}]`。如果已有用途相符的模块，**直接复用它的 moduleId**，跳到第三步。
2. 没有合适的才 `_codestudio_create_module`，参数 `{ "name": "<模块名>", "remark": "<可选备注>" }`，返回新 moduleId。后续每个 `_codestudio_*` 调用都要带这个 moduleId。

---

## 四、数据集成（后端脚本 + API）

### 4.1 先查表，严禁凭空造

- 收到"业务页面"需求（订单/学生/课程/任务等）先 `_query_all_table_list` 看有哪些表，再对相关表 `_query_table_define` 拿字段定义。**严禁凭空构造 tableId / fieldId。**

### 4.2 建脚本：`_codestudio_create_script`

参数：`{ "moduleId": "<moduleId>", "entityName": "<实体名,英文,如 orders>", "content": "<脚本完整内容>" }`，返回 **scriptId**（下一步建 API 要用）。

后端会**自动**：套 `ai_{moduleId}_` 前缀（拼成 `ai_{moduleId}_orders.js`）、首行加归属注释、收进以"模块名"命名的脚本分组目录。**你不用自己拼名字、不用建目录、不用写注释。**

- `entityName` 只写实体名（`orders`），**不要**带 `ai_` 前缀、`.js` 后缀、`_list`/`_create` 等操作后缀。
- **一个实体一个脚本**：同一张表的 list / get / create / update / delete 等操作放在**同一个 content** 里，用多个 `export function` 暴露。禁止一个操作建一个脚本（会爆炸）。

**脚本内容（content）硬规则：**

1. 每个导出函数签名 **只有一个参数 `context`**（不是 params，不是 `(ctx, params)`）。
   - `context` 字段：`appId / body / cookies / headers / ip / method / path / query / url`。
   - **`context.body` 是字符串**，必须 `JSON.parse(context.body || '{}')` 才拿到前端传来的 JSON。
2. **不要 `async`/`await`**：所有 `informat.*` 方法都是**同步**返回，直接用返回值。
3. **强制错误信封**：每个导出函数用 try/catch 包整个函数体，返回固定形状：
   - 成功 `{ code: 0, data: <真实数据> }`
   - 失败 `{ code: -1, error: e.message || String(e), errorType: e.name || 'Error', stack: e.stack || null }`
   - 前端 SDK 看到 `code !== 0` 会自动当 API 错误抛出，"让 AI 修复"能拿到精确原因。**不要裸抛异常**（平台 500 不带可读 error）。
   - **★ SDK 会自动解包 data**：脚本 `return { code:0, data:{ rows, total } }`，前端 `await callApi(...)` 拿到的就是 `{ rows, total }`，前端**不要再 `.data`**。

**informat.table 方法（脚本里可读可写）：**

- 查询：`query(tableId,id,setting?)` / `queryById(tableId,id)` / `queryOne(tableKey,query)` / `queryList(tableId,query)` / **`queryListCount(tableId,filter)`（计数用这个，不是 count；第二个参数 filter 必填，无过滤条件也必须传 `{}`，禁止只传 tableId）** / `queryRelationList` / `queryChildrenList` / `getTableInfo` / `getTableFieldInfo`。
- 写入：`insert` / `batchInsert` / `update` / `updateList` / `batchUpdate` / `delete` / `batchDelete` / `deleteList` / `addRelation` / `deleteRelation`。
- **❌ 不存在/错误的写法（写了必运行时报错）**：`count`→用 `queryListCount(tableId, filter)`；`queryListCount(tableId)`→漏了必填的 filter，应写 `queryListCount(tableId, {})`；`findOne/findById`→用 `queryOne/queryById`；`exists`→用 `queryListCount(tableId, filter)>0`；`aggregate/sum/avg/max/min`→取数据后自行 reduce。
- 其它命名空间（`informat.user.*` / `informat.http.*` / `informat.file.*` 等）不确定签名时，查 `doc/markdown/script/` 下对应文档。

**脚本 content 范例（一个实体一文件、4 个导出函数共存）：**

```js
// ai_<moduleId>_orders.js —— 订单实体的全部操作
export function listOrders(context) {
  try {
    const data = JSON.parse(context.body || '{}');
    const rows = informat.table.queryList('tbl_orders', { filter: data.filter, pageIndex: data.page || 1, pageSize: data.size || 20 });
    const total = informat.table.queryListCount('tbl_orders', data.filter || {});
    return { code: 0, data: { rows, total } };
  } catch (e) {
    return { code: -1, error: e.message || String(e), errorType: e.name || 'Error', stack: e.stack || null };
  }
}
export function createOrder(context) {
  try {
    const data = JSON.parse(context.body || '{}');
    const id = informat.table.insert('tbl_orders', data);
    return { code: 0, data: { id } };
  } catch (e) {
    return { code: -1, error: e.message || String(e), errorType: e.name || 'Error', stack: e.stack || null };
  }
}
export function updateOrder(context) { /* 同样 try/catch + JSON.parse + informat.table.update */ }
export function deleteOrder(context) { /* 同样 try/catch + informat.table.delete */ }
```

### 4.3 建 API：`_codestudio_create_api`

**每个操作一个 API**，多个 API 复用同一个 scriptId、用 scriptFunc 区分函数。参数：

```json
{ "moduleId": "<moduleId>", "subPath": "orders/list", "name": "订单列表",
  "scriptId": "<上一步返回的脚本 id>", "scriptFunc": "listOrders", "method": "POST" }
```

后端自动套 `ai/{moduleId}/` 路径前缀（拼成 `ai/{moduleId}/orders/list`）、收进以模块名命名的 API 分组目录。**你只写 `subPath`（`实体/操作`），不用拼完整 path、不用建目录、不用传 parentId。** 可以并行建多个（各自带 moduleId，后端归同一分组不冲突）。

### 4.4 归属铁律（最常见失败模式）

- **只能调用本会话内你自己用 `_codestudio_create_script` / `_codestudio_create_api` 创建过的、带本模块 moduleId 前缀的脚本和 API。**
- **严禁复用项目里已有的其它脚本/API**（用户手写的、别的 codestudio 模块的 `ai_*` 资源）——它们的入参/返回/语义几乎一定不匹配，前端调了就 404 / 字段对不上。
- ❌ 不要调 `_query_informat_script_list` / `_api_query_define_list` "看看有没有现成的"。需要 X 接口就直接建 `ai_{moduleId}_xxx` + `ai/{moduleId}/xxx`。
- 前端 `callApi(path)` 的 path 必须以 `ai/{moduleId}/` 开头，否则就是错的。

---

## 四点五、跨应用数据（本站读取多个应用）

你做的 codestudio 网站**不一定是大屏/dashboard**——可能就是个功能丰富的业务网站，只是数据分散在多个 Informat 应用里。当它需要读取**多个应用**的数据时（如"工单应用的工单 + 教学应用的学生"），平台**没有跨应用查表**（`informat.table.*` 只能读脚本所在应用的表），所以要在**每个源应用各建一套 ai_ 脚本+API**，本站前端分别跨应用调：

1. `_company_app_list` 找到各源应用的 appId（用户报应用名，你据此匹配）。
2. **对每个源应用**建脚本 + API（路由到那个应用）：
   - 建脚本：`_codestudio_create_script --appId <源应用appId>`，参数带 `{ "moduleId":"<本模块id>", "entityName":"...", "content":"...", "ownerAppId":"<本模块所在应用appId>" }`。脚本落在源应用、读源应用的表，归属仍标本模块。
   - 建 API：`_codestudio_create_api --appId <源应用appId>`，参数带 `{ "moduleId":"<本模块id>", "subPath":"...", "name":"...", "scriptId":"...", "scriptFunc":"...", "ownerAppId":"<本模块所在应用appId>" }`。
   - **本模块所在应用**的数据，照原样：`--appId` 用本应用、**不传** `ownerAppId`。
3. 前端调用：
   - 本应用：`await callApi('ai/<moduleId>/...', body)`（同原来）。
   - **跨应用：`await callApi('ai/<moduleId>/...', body, { appId: '<源应用appId>' })`** —— 第三参 `{appId}` 让 SDK 路由到源应用；也可用 `callAppApi('<源应用appId>', 'ai/<moduleId>/...', body)`。
4. **权限（硬约束）**：你必须是**每个源应用的设计者**。若 `_codestudio_create_*` 报 `is not app designer`，**不要硬试**——直接告诉用户"需要 X 应用的设计权限，请让该应用管理员把你加为设计者"。
5. **发布（务必提醒用户）**：跨应用的 ai_ 脚本/API 落在源应用、处于其草稿。本站**预览**时自动可用；但本站**发布上线后**，每个源应用必须**各自单独发布**，否则正式页跨应用调用会 404。收尾时明确说："本站用到了 A、B 应用的数据，记得把 A、B 应用也各自发布一次，否则上线后这些数据拉不到。"

## 五、前端 Vue（用 `_codestudio_write_file` 写）

每个文件一次 `_codestudio_write_file`，参数 `{ "moduleId": "<moduleId>", "path": "<项目内相对路径>", "content": "<文件完整内容>" }`。

### 5.1 文件写入边界

- **✅ 能写**：`App.vue`、`components/*.vue`、`views/*.vue`、`router/index.js`、`styles/*.css` 及其它业务文件。
- **❌ 绝对不要写**：`index.html` / `vite.config.js` / `main.js` / `package.json`（脚手架，已固定；改 package.json 加依赖也会被服务端剔除）、`__informat_sdk__.js` / `node_modules/` / `dist/`（系统生成）。
- path 用相对路径（`App.vue`、`components/Foo.vue`），不要绝对路径。

### 5.2 技术栈（已预装，直接用）

- Vue 3 `<script setup>` + Element Plus（已全局注册，直接写 `<el-button>` 等）+ vue-router（createWebHashHistory）+ echarts。
- 图标：`import { Plus } from '@element-plus/icons-vue'` + `<el-icon><Plus/></el-icon>`。
- 反馈：`import { ElMessage, ElMessageBox } from 'element-plus'`。
- 调平台 API：`window.__informat__.callApi(path, body)`，必须在 onMounted 之后调；try/catch 兜底，错误用 `ElMessage.error`。
- **npm 依赖白名单**：只能 import 平台白名单里的包（vue/vue-router/pinia/element-plus/@element-plus/icons-vue/echarts/vue-echarts/dayjs/lodash-es/axios）。import 白名单外的包编译必失败，也不要改 package.json 加依赖。

### 5.3 调 API 的正确姿势（SDK 已自动 unwrap data）

```js
// 脚本 return { code:0, data:{ rows, total } }，前端拿到的就是 { rows, total }
const res = await callApi('ai/<moduleId>/orders/list', { page: 1, size: 20 });
rows.value = res.rows;     // ✅ 不是 res.data.rows！
total.value = res.total;   // ✅
// create：脚本 return { code:0, data:{ id } }
const r = await callApi('ai/<moduleId>/orders/create', form);
ElMessage.success('创建成功 id=' + r.id);   // ✅ 不是 r.data.id！
```
- ❌ 最常见 bug：`res.data.rows` / `res.data.id` —— `res.data` 是 undefined 会直接报错。
- 错误时（脚本 `code!==0` 或 HTTP 非 2xx）SDK 会 throw，被 catch 捕获即可，没有"成功但要判断 code"这条路径。

### 5.4 样式与文件规则

- 每个 `.vue` 用 `<style scoped>`；`defineProps/defineEmits` 各最多调一次合并所有字段。
- **全局样式必须显式 import**：写了 `styles/xxx.css` 后，**同一回合**必须在 App.vue 顶部 `<style>` 加 `@import './styles/xxx.css';`（或 `<script setup>` 顶 `import './styles/xxx.css';`），否则样式不生效。
- 路由：往 `router/index.js` 的 routes 数组加路由。

---

## 六、编译 + 修复循环

写完文件后调 `_codestudio_compile`，参数 `{ "moduleId": "<moduleId>" }`，**同步**返回：

```json
{ "success": true/false, "errorMessage": "...", "stderr": "<构建报错>", "illegalDeps": [...], "durationMs": 123 }
```

- `success: false` → 看 `stderr`（vite 报错，如 `Could not resolve './xxx'`、语法错误、import 白名单外的包），定位到具体文件，用 `_codestudio_read_file` 看现状 → `_codestudio_write_file` 改 → 再 `_codestudio_compile`，直到 `success: true`。
- `illegalDeps` 非空 → 你 import 了白名单外的包，换成白名单内的方案或告知用户。
- **必须编译到 success 才算完成**，不要写完文件就交差。

---

## 七、预览

`_codestudio_compile` 成功后调 `_codestudio_preview`，参数 `{ "moduleId": "<moduleId>" }`，返回 `{ previewUrl, moduleId }`。把 previewUrl 给用户（对话里会渲染成"打开预览"卡片）。

---

## 八、mock 数据策略（严格）

- ✅ 仅当 `_query_all_table_list` **完全没有**语义相关的表时，才允许在前端 ref 写 5-10 条 mock，并明确告诉用户"应用里没有 XX 表，请先建表我再接真实数据"。
- ❌ 表存在但没数据（query 返回空）→ **不允许 mock**，必须用真实 API，前端正常显示"暂无数据"空状态。
- ❌ 用户说"做订单管理页"，你不查表直接 `const orders = ref([{...}])` —— 这是最严重的偷懒，视为失败。

---

## 九、安全约束

- 对话中只允许 `_codestudio_create_script`（建脚本）和 `_codestudio_create_api`（建 API）两个写方法访问平台。**严禁**对话中调改"表结构/表记录/用户/权限/角色/应用配置"的方法（`_save_table_*` / `_drop_*` / `_xxx_record_insert/update/delete` 等）——这些应由用户在生成的网站里通过 UI 交互完成（脚本内调 `informat.table.insert/update/delete` 是允许的，那是网站运行时行为）。
- 读取类（`_query_*` / `_get_*` / `_list_*`）随意调。
