openxiangda 1.0.263 → 1.0.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  <!-- OpenXiangda-Policy-Version: 7 -->
2
+
2
3
  # OpenXiangda
3
4
 
4
5
  OpenXiangda is a lightweight CLI and skill package for private low-code platforms.
@@ -361,7 +362,7 @@ The dev host also calls the backend runtime route resolver for the current `/vie
361
362
  Built-in route defaults come from the `openxiangda` SDK: `StandardFormPage`, `FormSubmitTemplate`, `FormDetailTemplate`, `ProcessDetailTemplate`, and `DataManagementList`. Workspaces can override whole built-in pages from `src/runtime/builtin-overrides.tsx`; exact `formUuid` entries win before the `*` fallback.
362
363
 
363
364
  ```tsx
364
- import type { BuiltinRouteOverrides } from "openxiangda/runtime"
365
+ import type { BuiltinRouteOverrides } from "openxiangda/runtime";
365
366
 
366
367
  export const runtimeRouteOverrides: BuiltinRouteOverrides = {
367
368
  "form-submit": {
@@ -371,7 +372,7 @@ export const runtimeRouteOverrides: BuiltinRouteOverrides = {
371
372
  "form-detail": {},
372
373
  "process-detail": {},
373
374
  "data-manage-list": {},
374
- }
375
+ };
375
376
  ```
376
377
 
377
378
  AI / Playwright verification should use real user identity, not a bypass mode. The backend exposes `POST /openxiangda-api/v1/apps/:appType/verification-login-links` for platform admins to create a short-lived, single-use `loginUrl` for a target user and redirect URI. Playwright can open the returned URL directly; the backend writes cookies and redirects back to the local or tenant `/view/...` page. Test users can be created with `POST /openxiangda-api/v1/apps/:appType/test-users`; they use the `__ox_ai_test__:<appType>:<key>` ID prefix and are intended to be used through verification login links.
@@ -379,13 +380,13 @@ AI / Playwright verification should use real user identity, not a bypass mode. T
379
380
  An independent production user shell can call `resolveBrowserRuntimeRoute()` from `openxiangda/runtime` first. It posts the current `/view/...` path to `/openxiangda-api/v1/apps/:appType/runtime/routes/resolve`; the backend classifies the route as `custom-page`, `builtin-route`, `legacy-fallback`, or `not-found`. Code pages return backend-authorized bootstrap data directly, so the shell can pass that to `mountBrowserPageRuntime()` or mount with `createBrowserPageContext()` without reusing the legacy view workbench logic. Built-in routes can be rendered with `BuiltinRouteRenderer`, which loads form schema through the same `/service` proxy and keeps backend permission checks authoritative.
380
381
 
381
382
  ```ts
382
- import { resolveBrowserRuntimeRoute } from "openxiangda/runtime"
383
+ import { resolveBrowserRuntimeRoute } from "openxiangda/runtime";
383
384
 
384
385
  const route = await resolveBrowserRuntimeRoute({
385
386
  appType: "APP_XXXX",
386
387
  path: window.location.pathname,
387
388
  search: window.location.search,
388
- })
389
+ });
389
390
  ```
390
391
 
391
392
  Local workspace state is authoritative. If the current folder has no `.openxiangda/state.json` app binding, create a new app instead of searching the platform for a similar app name:
@@ -415,16 +416,16 @@ openxiangda form export customer --mode package --profile dev --output ./exports
415
416
 
416
417
  运行时页面读取当前用户信息时,优先使用 `sdk.user.getCurrent<PageUserRecord>()`。用户对象会返回常规组织成员关系 `departments`,也会返回系统维护的所属单位字段 `affiliatedDepartmentId` / `affiliatedDepartment`。`departments` 表示用户真实所在的部门、班级、专业等成员关系;`affiliatedDepartment` 表示业务上用于统计、筛选和展示的归属单位,通常是学院、单位或在源单位缺失时可用的具体部门节点,不用于替代权限部门成员关系。
417
418
 
418
- 钉钉家校通讯录的家长、学生、教师身份和明确监护关系使用 `sdk.organization.schoolContact.*`(页面)或 `ctx.organization.schoolContact.*`(App Function)。已登录非游客用户无需给应用角色额外绑定权限,默认可查询当前租户全部关系,返回平台用户 ID、手机号、钉钉 userid 和姓名;应用可显式声明 `:self:read` / `:class:read` 收紧范围。完整示例见 [`docs/dingtalk-school-contact-relations.md`](docs/dingtalk-school-contact-relations.md)。
419
+ 钉钉家校通讯录的家长、学生、教师身份和明确监护关系使用 `sdk.organization.schoolContact.*`(页面)或 `ctx.organization.schoolContact.*`(App Function)。已登录非游客用户无需给应用角色额外绑定权限,默认可查询当前租户全部关系,返回平台用户 ID、手机号、钉钉 userid 和姓名;`teachers.list` 还返回 `teacher.managedClasses`、`class.headTeachers` 和同步/人工 `source`,用于班主任双向查询。应用可显式声明 `:self:read` / `:class:read` 收紧范围。完整示例见 [`docs/dingtalk-school-contact-relations.md`](docs/dingtalk-school-contact-relations.md)。
419
420
 
420
421
  ```ts
421
- import type { PageUserRecord } from "openxiangda/runtime"
422
+ import type { PageUserRecord } from "openxiangda/runtime";
422
423
 
423
- const currentUser = await sdk.user.getCurrent<PageUserRecord>()
424
- const user = currentUser.result
424
+ const currentUser = await sdk.user.getCurrent<PageUserRecord>();
425
+ const user = currentUser.result;
425
426
 
426
- const affiliatedDepartmentName = user?.affiliatedDepartment?.name
427
- const affiliatedDepartmentExternalId = user?.affiliatedDepartment?.externalId
427
+ const affiliatedDepartmentName = user?.affiliatedDepartment?.name;
428
+ const affiliatedDepartmentExternalId = user?.affiliatedDepartment?.externalId;
428
429
  ```
429
430
 
430
431
  工程化资源放在工作区 `src/resources/` 下,由 `openxiangda resource validate|plan|publish|pull` 管理。`workspace publish` 会先构建并注册 workspace 表单/页面,再执行非破坏性资源 upsert,这样菜单、权限组、流程和表单设置可以解析最新的 profile-local ID。需要删除平台中 manifest 未声明的资源时,显式传 `--prune`。连接器页面运行时通过 `sdk.connector.invoke()` / `sdk.connector.call("connector.api")` 调用平台运行时接口,第三方密钥只保存在后端连接器配置中。
@@ -460,7 +461,15 @@ React SPA 新应用的无需登录访问统一使用 `/view/:appType/public/*`
460
461
  "text/csv",
461
462
  "application/json"
462
463
  ],
463
- "allowedExtensions": ["pdf", "docx", "xlsx", "pptx", "txt", "csv", "json"],
464
+ "allowedExtensions": [
465
+ "pdf",
466
+ "docx",
467
+ "xlsx",
468
+ "pptx",
469
+ "txt",
470
+ "csv",
471
+ "json"
472
+ ],
464
473
  "maxSizeBytes": 31457280,
465
474
  "visibility": "private",
466
475
  "pathPrefix": "public/register/attachments/"
@@ -477,10 +486,10 @@ React SPA 新应用的无需登录访问统一使用 `/view/:appType/public/*`
477
486
  公开页面里的图片和附件分两类处理。真正可以对公网长期公开的资产,例如仪器封面图、公开说明书、门户 banner,应上传到平台公开文件区,默认 bucket 为 `public-assets`,上传后保存的 `url` / `previewUrl` / `downloadUrl` 会是 `/file/public/...`,未登录浏览器可以直接读取并被浏览器/CDN 缓存。仍可能包含隐私、审批材料、订单报告、结算单、维保现场照片的附件不要放入公开区,应继续保存在私有 bucket,并通过登录态或受控文件票据访问。
478
487
 
479
488
  ```ts
480
- import { createFormRuntimeApi } from "openxiangda"
489
+ import { createFormRuntimeApi } from "openxiangda";
481
490
 
482
- const api = createFormRuntimeApi({ baseUrl: "/service" })
483
- const file = await api.uploadPublicFile(imageFile, "public-assets")
491
+ const api = createFormRuntimeApi({ baseUrl: "/service" });
492
+ const file = await api.uploadPublicFile(imageFile, "public-assets");
484
493
 
485
494
  // file.url === "/service/file/public/public-assets/..."
486
495
  // file.visibility === "public"
@@ -503,7 +512,7 @@ import {
503
512
  这些独立 API 必须在模板默认提供的 `OpenXiangdaProvider` 和 `OpenXiangdaPageProvider` 内使用。它们会从 PageSdk 获取当前 `appType`,并统一处理 preview ticket、metadata 和二进制文件响应。默认覆盖 PDF、常见媒体、文本、DOCX 和 XLSX,更多 DOC/XLS/PPT、ODF 格式由部署侧 ONLYOFFICE 配置决定。只有在需要复制、分享或新窗口打开预览页时,才通过 PageSdk `sdk.createFileAccessTicket(..., "preview", { appType })` 获取并打开 `previewPageUrl`;不要直接打开文件内容流 `previewUrl`。
504
513
 
505
514
  ```tsx
506
- import { ImageField } from "openxiangda"
515
+ import { ImageField } from "openxiangda";
507
516
 
508
517
  <ImageField
509
518
  fieldId="photos"
@@ -514,9 +523,9 @@ import { ImageField } from "openxiangda"
514
523
  enabled: true,
515
524
  skipBelowBytes: 200 * 1024,
516
525
  thumb: { maxWidth: 320, maxHeight: 320, quality: 0.72 },
517
- preview: { maxWidth: 1280, maxHeight: 1280, quality: 0.82 }
526
+ preview: { maxWidth: 1280, maxHeight: 1280, quality: 0.82 },
518
527
  }}
519
- />
528
+ />;
520
529
  ```
521
530
 
522
531
  压缩默认 `format: "source"`,会尽量沿用原图片格式。如果显式输出 `webp`、`png` 或 `jpeg`,对应 OSS storage resource 的 `allowedExtensions` 也要包含该扩展名。
@@ -602,15 +611,15 @@ openxiangda organization account-reset-password user-alice --profile dev --body-
602
611
  页面 SDK 和 App Function 使用同一套后端权限:
603
612
 
604
613
  ```ts
605
- const capabilities = await sdk.organization.capabilities()
614
+ const capabilities = await sdk.organization.capabilities();
606
615
  if (!capabilities.result?.canManage) {
607
- throw new Error("当前账号没有组织账号管理权限")
616
+ throw new Error("当前账号没有组织账号管理权限");
608
617
  }
609
618
 
610
619
  await sdk.organization.departments.create({
611
620
  name: "销售部",
612
621
  parentId: "dept-root",
613
- })
622
+ });
614
623
 
615
624
  await sdk.organization.accounts.create({
616
625
  id: "user-alice",
@@ -618,12 +627,12 @@ await sdk.organization.accounts.create({
618
627
  password: initialPassword,
619
628
  name: "Alice",
620
629
  departmentIds: ["dept-sales"],
621
- })
630
+ });
622
631
 
623
632
  // App Function / trusted JS uses ctx.organization with the same permission check.
624
633
  await ctx.organization.accounts.resetPassword("user-alice", {
625
634
  newPassword,
626
- })
635
+ });
627
636
  ```
628
637
 
629
638
  应用登录能力通过 auth resource 和 runtime SDK 提供:登录配置放在 `src/resources/auth/<code>.json`,默认 React SPA 模板已包含 `/view/:appType/login`,自定义页面可使用 `createAuthClient({ appType, servicePrefix })` 或 `LoginPage` / `useAuth` from `openxiangda/runtime/react`。钉钉登录默认使用 `dingtalkFlow="auto"`:只有明确位于钉钉容器且 JSAPI 可用时才走免登码,普通浏览器会请求应用级 OAuth 地址并跳转钉钉认证页;自定义页可调用 `getDingTalkOAuthUrl({ returnUrl })` 接入相同能力。手机号验证码、CAS/SSO 或其他外部登录可以由 App Function provider 校验外部凭证,但 provider 只能返回 `phone` / `email` / `externalId` / `unionId` 等身份声明;平台后端按 auth resource 策略执行账号匹配、绑定、创建或拒绝,并由平台统一签发 token/cookie。默认注册策略是拒绝,开启自动注册或白名单注册前必须确认身份匹配键、默认角色、验证码 TTL/频率/失败次数、审计字段和错误文案策略。
@@ -4,6 +4,7 @@ description: "Use OpenXiangda for private low-code platform work: app workspaces
4
4
  ---
5
5
 
6
6
  <!-- OpenXiangda-Policy-Version: 7 -->
7
+
7
8
  # OpenXiangda
8
9
 
9
10
  OpenXiangda connects an AI coding workspace to the private low-code platform through a normal-user profile and `/openxiangda-api/v1`. External backends using AK/SK are a separate `openxiangda-open-api` flow.
@@ -57,18 +58,18 @@ content/source/parent verification and never overwrites immutable storage.
57
58
 
58
59
  ## Decide the track
59
60
 
60
- | Intent | Read next | First action |
61
- | --- | --- | --- |
62
- | Diagnose, snapshot, compare, explain an error | `openxiangda-inspect` | read-only snapshot/inspect |
63
- | Login, profile, release, CLI/state issue | `openxiangda-core` | resolve explicit profile and workspace |
64
- | Create or bind an app | `openxiangda-app` | inspect local binding, then init/bind |
65
- | Form/schema/data view | `openxiangda-form` | inspect the target form and schema |
66
- | React/classic page | `openxiangda-page` | inspect the target route/page |
67
- | Function, automation, workflow, JS_CODE | `openxiangda-workflow-automation` | inspect the exact resource codes |
68
- | Roles, permission groups, data scope | `openxiangda-permission-settings` | produce the permission matrix |
61
+ | Intent | Read next | First action |
62
+ | ------------------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------- |
63
+ | Diagnose, snapshot, compare, explain an error | `openxiangda-inspect` | read-only snapshot/inspect |
64
+ | Login, profile, release, CLI/state issue | `openxiangda-core` | resolve explicit profile and workspace |
65
+ | Create or bind an app | `openxiangda-app` | inspect local binding, then init/bind |
66
+ | Form/schema/data view | `openxiangda-form` | inspect the target form and schema |
67
+ | React/classic page | `openxiangda-page` | inspect the target route/page |
68
+ | Function, automation, workflow, JS_CODE | `openxiangda-workflow-automation` | inspect the exact resource codes |
69
+ | Roles, permission groups, data scope | `openxiangda-permission-settings` | produce the permission matrix |
69
70
  | DingTalk school-contact guardian/student relationships | `openxiangda-page` plus `references/school-contact-relations.md` | use the platform relationship SDK, defaulting to unrestricted tenant scope |
70
- | New app architecture, auth, public access | `openxiangda-architecture-design` plus the relevant domain skill | run the design gate |
71
- | External backend/OpenAPI/AK-SK | `openxiangda-open-api` | describe the exact operation |
71
+ | New app architecture, auth, public access | `openxiangda-architecture-design` plus the relevant domain skill | run the design gate |
72
+ | External backend/OpenAPI/AK-SK | `openxiangda-open-api` | describe the exact operation |
72
73
 
73
74
  ## Scope before work
74
75
 
@@ -191,8 +192,12 @@ The lease is app-level promotion ownership, while worktree ownership prevents tw
191
192
  - Use platform roles, permission groups, public grants, and backend Function checks for authorization; frontend hiding is presentation only.
192
193
  - Keep tokens and secrets out of project files and chat.
193
194
  - Use official public SDK entrypoints and platform form/file components.
194
- - Read school-contact guardian/student relationships through
195
- `sdk.organization.schoolContact` or the trusted App Function bridge. Keep the
195
+ - Read school-contact guardian/student and teacher/class relationships through
196
+ `sdk.organization.schoolContact` or the trusted App Function bridge. Use
197
+ `SCHOOL_HEAD_TEACHER` only for global identity and `teachers.list` for the
198
+ exact class relationship; consume `teacher.managedClasses` and
199
+ `class.headTeachers` for the two relationship directions and inspect
200
+ `source` when synced/manual provenance matters. Keep the
196
201
  authenticated non-guest default tenant-wide scope without adding an app-role
197
202
  permission binding unless the product explicitly requires self/class
198
203
  restrictions; never infer relations from department membership.
@@ -19,7 +19,7 @@ Guidelines:
19
19
  - Use `sdk.export.create` and `sdk.export.get` for asynchronous XLSX exports. Simple pages may send a declarative workbook definition. Complex or reusable exports should send only a published App Function `definitionCode`, the current query snapshot, export scope, and stable selected row IDs. The server executes `structured_export_provider_v1` with fresh user permissions; never upload executable rendering code from the browser. See `docs/structured-export-v1.md`.
20
20
  - Use `AttachmentPreviewList`, `ImagePreviewGrid`, or `useFilePreview` from `openxiangda/runtime/react` for in-page attachment previews in a custom React SPA page. They use the current PageSdk context and enforce the platform capability and ticket contracts. Use `sdk.createFileAccessTicket(bucketName, objectName, fileName, "preview", { appType })` only when the page needs a shareable or new-window preview link. `appType` defaults to the current page context. Open `response.result.previewPageUrl`; do not use `previewUrl` or `/service/file/preview-by-ticket/:ticket` as the page entry.
21
21
  - Use `sdk.organization.departments.*` and `sdk.organization.accounts.*` only for intentional organization pages. Read-only list/detail pages need `app:organization:read` or `app:organization:manage`; writes and password operations need `app:organization:manage`. Do not call legacy `/user` or `/department` endpoints from pages.
22
- - Use `sdk.organization.schoolContact.*` for synchronized guardian/student relationships. Authenticated non-guest users default to all relationships in the current tenant without an app-role permission binding. Use the optional `self:read` or `class:read` permission only when the product explicitly needs a narrower data scope. The response intentionally includes platform user ID, mobile, DingTalk userid, and name. Read `references/school-contact-relations.md` before building a family or class page.
22
+ - Use `sdk.organization.schoolContact.*` for synchronized guardian/student and teacher/class relationships. `SCHOOL_HEAD_TEACHER` identifies a user who currently heads at least one class; use `teachers.list({ classId, isHeadTeacher: true })` to resolve the exact class. Authenticated non-guest users default to all relationships in the current tenant without an app-role permission binding. Use the optional `self:read` or `class:read` permission only when the product explicitly needs a narrower data scope. The response intentionally includes platform user ID, mobile, DingTalk userid, and name. Read `references/school-contact-relations.md` before building a family or class page.
23
23
  - Synchronized school-contact users also receive platform identity codes: `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`, and `SCHOOL_TEACHER`. Page permission groups select them with `platformRoleCodes` (additive to app `roles`), and runtime permissions expose them in `permissions.platformRoleCodes`. App Functions can use a `platform_roles` invocation audience and trusted `ctx.operator.platformRoleCodes`; these roles are maintained only by the daily organization sync.
24
24
  - Use `sdk.workCenter.listItems({ boxType })` and `sdk.workCenter.getStats()` for current-user work center lists and counts. `boxType` is one of `todo`, `done`, `cc`, or `initiated`. This is the end-user task/handled/cc/initiated surface; do not build todo pages by reading workflow operation logs, automation logs, or raw process-task tables.
25
25
  - `sdk.form.create` returns identifiers and generated serial number values only. Treat `formInstId` / `formInstanceId` as the required success contract; `serialNumber` / `serialNumbers` are present only when the form has `SerialNumberField`. Do not expect the save response to contain the full row, formula results, or other server-generated field values. Call `sdk.form.getDetail` explicitly after create when those values are needed.
@@ -253,7 +253,7 @@ await sdk.organization.accounts.resetPassword("alice", {
253
253
 
254
254
  Use `ctx.organization.departments.*` and `ctx.organization.accounts.*` inside App Functions or trusted JS nodes for the same capability. Runtime service principals must resolve a real operator/audit actor; permission is checked against that user, not against the runtime service account. Keep password changes on dedicated endpoints: update account metadata with `update`, reset another user's password with `resetPassword`, and change the current user's password with `changeMyPassword({ oldPassword, newPassword })`.
255
255
 
256
- For synchronized school-contact relationships, use `sdk.organization.schoolContact.relations.list`, `children.list`, `guardians.list`, or `myFamily.get`; App Functions use the same paths under `ctx.organization.schoolContact`. Do not infer a family relationship from class membership or persist a second relationship master inside application forms. See `references/school-contact-relations.md` for permission codes, filters, response fields, and examples.
256
+ For synchronized school-contact relationships, use `sdk.organization.schoolContact.relations.list`, `teachers.list`, `children.list`, `guardians.list`, or `myFamily.get`; App Functions use the same paths under `ctx.organization.schoolContact`. Do not infer a family relationship from class membership, infer a specific head-teacher class from the global role, or persist a second relationship master inside application forms. See `references/school-contact-relations.md` for permission codes, filters, response fields, and examples.
257
257
 
258
258
  Work center:
259
259
 
@@ -1,6 +1,6 @@
1
1
  # 家校通讯录关系
2
2
 
3
- 家校关系由平台组织中心统一同步和维护。应用不得直连钉钉,也不得根据班级成员自行推断家长和学生关系。
3
+ 家校关系由平台组织中心统一同步和维护。同步成员和人工成员都直接位于班级部门下,通过 `guardian`、`student`、`teacher` 和班主任标记区分,不再依赖“家长/学生/老师”子部门。应用不得直连钉钉,也不得根据班级成员自行推断家长和学生关系。
4
4
 
5
5
  ## 权限与默认范围
6
6
 
@@ -10,9 +10,7 @@
10
10
 
11
11
  ```json
12
12
  {
13
- "apiPermissionCodes": [
14
- "app:organization:school-contact:read"
15
- ]
13
+ "apiPermissionCodes": ["app:organization:school-contact:read"]
16
14
  }
17
15
  ```
18
16
 
@@ -29,13 +27,14 @@
29
27
 
30
28
  ### 平台内置身份角色
31
29
 
32
- 每次成功的钉钉家校通讯录全量同步都会对账并维护三种平台层系统身份角色:
30
+ 每次成功的钉钉家校通讯录全量同步都会对账并维护四种平台层系统身份角色:
33
31
 
34
- | 展示名 | 稳定角色编码 | 含义 |
35
- | --- | --- | --- |
36
- | 家长 | `SCHOOL_GUARDIAN` | 在家校成员关系中作为 guardian |
37
- | 学生 | `SCHOOL_STUDENT` | 在家校成员关系中作为 student |
38
- | 老师 | `SCHOOL_TEACHER` | 在家校成员关系中作为 teacher |
32
+ | 展示名 | 稳定角色编码 | 含义 |
33
+ | ------ | --------------------- | --------------------------------------------- |
34
+ | 家长 | `SCHOOL_GUARDIAN` | 在家校成员关系中作为 guardian |
35
+ | 学生 | `SCHOOL_STUDENT` | 在家校成员关系中作为 student |
36
+ | 老师 | `SCHOOL_TEACHER` | 在家校成员关系中作为 teacher |
37
+ | 班主任 | `SCHOOL_HEAD_TEACHER` | 至少一个 teacher 班级成员带 `is_adviser=true` |
39
38
 
40
39
  身份角色可组合,一个平台用户可以同时是老师和家长;角色由同步维护,不能在平台角色管理中手工分配、修改、删除,也不会被当前平台角色切换隐藏。应用权限组把这些编码填入 `platformRoleCodes`,与应用角色 `roles` 是并列的 OR 条件;两个数组都为空仍表示不限制。
41
40
 
@@ -43,9 +42,15 @@
43
42
 
44
43
  ```ts
45
44
  const isGuardian =
46
- sdk.context.permissions?.platformRoleCodes?.includes("SCHOOL_GUARDIAN")
45
+ sdk.context.permissions?.platformRoleCodes?.includes("SCHOOL_GUARDIAN");
46
+
47
+ const isHeadTeacher = sdk.context.permissions?.platformRoleCodes?.includes(
48
+ "SCHOOL_HEAD_TEACHER",
49
+ );
47
50
  ```
48
51
 
52
+ `SCHOOL_HEAD_TEACHER` 只说明当前用户至少担任一个班的班主任。班主任是班级维度的关系;要判断具体班级,必须使用下方 `teachers.list` 返回的 `isHeadTeacher`,不能用全局角色推断。
53
+
49
54
  App Function 的调用受众可以声明平台身份角色,服务端按真实操作者的同步结果校验:
50
55
 
51
56
  ```json
@@ -69,9 +74,9 @@ const response = await sdk.organization.schoolContact.relations.list({
69
74
  classId,
70
75
  page: 1,
71
76
  pageSize: 20,
72
- })
77
+ });
73
78
 
74
- const relations = response.result?.items || []
79
+ const relations = response.result?.items || [];
75
80
  ```
76
81
 
77
82
  常用方法:
@@ -80,16 +85,31 @@ const relations = response.result?.items || []
80
85
  await sdk.organization.schoolContact.children.list(guardianUserId, {
81
86
  page: 1,
82
87
  pageSize: 20,
83
- })
88
+ });
84
89
 
85
90
  await sdk.organization.schoolContact.guardians.list(studentUserId, {
86
91
  page: 1,
87
92
  pageSize: 20,
88
- })
93
+ });
89
94
 
90
- await sdk.organization.schoolContact.myFamily.get({ pageSize: 50 })
95
+ await sdk.organization.schoolContact.myFamily.get({ pageSize: 50 });
96
+
97
+ await sdk.organization.schoolContact.teachers.list({
98
+ classId,
99
+ isHeadTeacher: true,
100
+ page: 1,
101
+ pageSize: 20,
102
+ });
91
103
  ```
92
104
 
105
+ `teachers.list` 的每个结果同时提供两个方向的班主任信息:
106
+
107
+ - `teacher.managedClasses`:该老师当前作为班主任管理的全部班级。
108
+ - `class.headTeachers`:该班级当前的全部班主任。
109
+ - `source`:班级身份来源,值为 `dingtalk_school_contact` 或 `manual`。
110
+
111
+ 这两个字段可直接用于“我管理的班级”和“班级班主任”场景,不要在应用中自行维护第二份映射。
112
+
93
113
  关系列表支持以下服务端过滤条件:
94
114
 
95
115
  - `userId`、`guardianUserId`、`studentUserId`:平台用户 ID。
@@ -100,29 +120,53 @@ await sdk.organization.schoolContact.myFamily.get({ pageSize: 50 })
100
120
  - `role`:`guardian` 或 `student`,用于限定通用人员过滤作用在哪一端。
101
121
  - `relationCode`、`page`、`pageSize`;`pageSize` 最大为 100。
102
122
 
123
+ 老师班级成员列表 `teachers.list` 支持:
124
+
125
+ - `userId`:老师的平台用户 ID。
126
+ - `dingtalkUserId`:老师的钉钉 userid。
127
+ - `mobile`:精确手机号。
128
+ - `name`:老师姓名。
129
+ - `classId`:平台家校班级 ID 或钉钉教育 `class_id`。
130
+ - `isHeadTeacher`:布尔值;`true` 只返回班主任,`false` 只返回普通任课老师。
131
+ - `page`、`pageSize`;`pageSize` 最大为 100。
132
+
103
133
  ## App Function
104
134
 
105
135
  App Function、Automation 或 Workflow 中需要复用关系时,调用对应的受控后端桥接:
106
136
 
107
137
  ```ts
108
- export default async function(ctx, input) {
138
+ export default async function (ctx, input) {
109
139
  const result = await ctx.organization.schoolContact.relations.list({
110
140
  guardianUserId: input.guardianUserId,
111
141
  page: 1,
112
142
  pageSize: 20,
113
- })
143
+ });
144
+
145
+ return result.items;
146
+ }
147
+ ```
114
148
 
115
- return result.items
149
+ 查询某位老师担任班主任的班级:
150
+
151
+ ```ts
152
+ export default async function (ctx, input) {
153
+ return await ctx.organization.schoolContact.teachers.list({
154
+ userId: input.teacherUserId,
155
+ isHeadTeacher: true,
156
+ page: 1,
157
+ pageSize: 100,
158
+ });
116
159
  }
117
160
  ```
118
161
 
119
162
  对应方法为:
120
163
 
121
164
  ```ts
122
- ctx.organization.schoolContact.relations.list(params)
123
- ctx.organization.schoolContact.children.list(userId, params)
124
- ctx.organization.schoolContact.guardians.list(userId, params)
125
- ctx.organization.schoolContact.myFamily.get(params)
165
+ ctx.organization.schoolContact.relations.list(params);
166
+ ctx.organization.schoolContact.teachers.list(params);
167
+ ctx.organization.schoolContact.children.list(userId, params);
168
+ ctx.organization.schoolContact.guardians.list(userId, params);
169
+ ctx.organization.schoolContact.myFamily.get(params);
126
170
  ```
127
171
 
128
172
  运行时使用平台提供的真实操作者做权限校验。不要信任页面 input 中伪造的租户、角色或当前用户 ID。
@@ -131,36 +175,65 @@ ctx.organization.schoolContact.myFamily.get(params)
131
175
 
132
176
  ```ts
133
177
  interface SchoolContactRelation {
134
- relationId: string
135
- relationCode: string | null
136
- relationName: string | null
178
+ relationId: string;
179
+ relationCode: string | null;
180
+ relationName: string | null;
137
181
  guardian: {
138
- userId: string
139
- dingtalkUserId: string
140
- name: string
141
- mobile: string | null
142
- }
182
+ userId: string;
183
+ dingtalkUserId: string | null;
184
+ name: string;
185
+ mobile: string | null;
186
+ };
143
187
  student: {
144
- userId: string
145
- dingtalkUserId: string
146
- name: string
147
- mobile: string | null
148
- }
188
+ userId: string;
189
+ dingtalkUserId: string | null;
190
+ name: string;
191
+ mobile: string | null;
192
+ };
149
193
  class: {
150
- id: string
151
- dingtalkClassId: string
152
- name: string
153
- campusName: string | null
154
- periodName: string | null
155
- gradeName: string | null
156
- }
157
- syncedAt: string
158
- syncState: "current" | "not_synced" | "disabled"
194
+ id: string;
195
+ dingtalkClassId: string;
196
+ name: string;
197
+ campusName: string | null;
198
+ periodName: string | null;
199
+ gradeName: string | null;
200
+ };
201
+ syncedAt: string;
202
+ syncState: "current" | "not_synced" | "disabled";
203
+ }
204
+ ```
205
+
206
+ 老师班级成员的返回项为:
207
+
208
+ ```ts
209
+ interface SchoolContactTeacherMembership {
210
+ membershipId: string;
211
+ teacher: {
212
+ userId: string;
213
+ dingtalkUserId: string | null;
214
+ name: string;
215
+ mobile: string | null;
216
+ managedClasses: SchoolContactRelation["class"][];
217
+ };
218
+ class: SchoolContactRelation["class"] & {
219
+ headTeachers: Array<{
220
+ userId: string;
221
+ dingtalkUserId: string | null;
222
+ name: string;
223
+ mobile: string | null;
224
+ }>;
225
+ };
226
+ isHeadTeacher: boolean;
227
+ source: "dingtalk_school_contact" | "manual";
228
+ syncedAt: string;
229
+ syncState: "current" | "not_synced" | "disabled";
159
230
  }
160
231
  ```
161
232
 
162
233
  手机号可能为空。`sync.state=disabled` 表示管理员关闭了后续同步,现有数据仍保留但可能过期;业务页面在对时效敏感时应展示 `lastSuccessfulSyncAt`。
163
234
 
235
+ 平台管理员人工添加的班级身份也会由 `teachers.list` 返回,并标记 `source=manual`。人工家长/学生身份会维护对应平台身份和班级归属,但不会自动生成监护关系;只有明确存在的家庭关系才会出现在 `relations`、`children`、`guardians` 和 `myFamily`。
236
+
164
237
  ## 持久化边界
165
238
 
166
239
  - 平台关系是当前事实,不要复制全量关系到应用表单或 Data View。
@@ -36,7 +36,7 @@ openxiangda workspace publish --profile <name> --page <pageCode>
36
36
  - ✅ For external APIs use `src/resources/connectors/<code>.json` + `sdk.connector.invoke()`; for joined read-only lists use row data views + `sdk.dataView.query`; for stable dashboard metrics use aggregate data views + `sdk.dataView.stats`.
37
37
  - ✅ Choose data view `storageMode` deliberately: `materialized` for refreshed reports/lists that tolerate delay, `live` for bounded real-time multi-form reads.
38
38
  - ✅ For reusable backend orchestration use App Function (`src/functions/<functionCode>/index.ts` + `src/resources/functions/<functionCode>.json`) and call `sdk.function.invoke(code, { input })`; keep page components out of multi-table business orchestration.
39
- - ✅ 家长、学生、教师身份或监护关系页面使用 `sdk.organization.schoolContact.*`;先读 `references/school-contact-relations.md`,默认使用已登录用户的租户内全部关系,仅在业务要求时声明 self/class 收紧范围,并保持服务端分页。
39
+ - ✅ 家长、学生、教师、班主任身份或班级/监护关系页面使用 `sdk.organization.schoolContact.*`;先读 `references/school-contact-relations.md`。用 `SCHOOL_HEAD_TEACHER` 判断全局身份,用 `teachers.list({ classId, isHeadTeacher: true })` 判断具体班主任班级,并直接读取 `teacher.managedClasses` / `class.headTeachers`;默认使用已登录用户的租户内全部关系,仅在业务要求时声明 self/class 收紧范围,并保持服务端分页。
40
40
  - ✅ Filters, search bars, modal forms, drawers, and inline edits use platform components for platform data fields, otherwise `antd` / `antd-mobile` controls.
41
41
  - ❌ Single-file giant pages. Split per `references/best-practices.md`.
42
42
  - ❌ Hardcoded `/view/...&isRenderNav=false` URLs scattered through page code; use the runtime navigation helper.
@@ -132,7 +132,7 @@ Read these references only when editing page code:
132
132
  - All visible page copy must be end-user-facing business text. Do not put developer explanations, implementation notes, schema descriptions, or "this module is generated by..." text into sections, cards, alerts, empty states, tooltips, or helper copy.
133
133
  - Store live `pageId`, `routeKey`, and `legacyFormUuid` under the current profile only.
134
134
  - Use `openxiangda/runtime` for platform data access instead of hardcoding backend URLs in page code.
135
- - 家校关系必须通过 `sdk.organization.schoolContact` / `ctx.organization.schoolContact` 读取;不要直连钉钉、查询系统表或用同班成员推断亲属关系。
135
+ - 家校关系必须通过 `sdk.organization.schoolContact` / `ctx.organization.schoolContact` 读取;班主任班级使用 `teachers.list` 的 `isHeadTeacher`,不要用全局角色推断具体班级,也不要直连钉钉、查询系统表或用同班成员推断亲属关系。
136
136
  - For reminders, alerts, and business messages, declare `src/resources/notifications/` first and call `sdk.notification`; do not hardcode notification API URLs.
137
137
  - For backend business logic shared by pages, automations, or workflows, declare an App Function and call `sdk.function.invoke`; do not duplicate the same multi-form query/connector/notification orchestration in page code.
138
138
  - Before hand-writing mature UI behavior, consult `references/component-guide.md` and use established libraries: platform components for platform data fields, antd/antd-mobile for controls and overlays, ECharts for charts, GSAP for complex animation timelines, and maintained packages such as dnd-kit for drag/drop. Do not rebuild mature controls with raw DOM/native inputs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.263",
3
+ "version": "1.0.265",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {