@csntgao/uni-base 0.1.3 → 0.1.5
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/AGENTS.md +14 -10
- package/CLAUDE.md +25 -14
- package/README.md +29 -3
- package/examples/vanilla/main.js +25 -6
- package/package.json +1 -1
- package/packages/web-components/package.json +1 -1
- package/packages/web-components/scripts/verify-runtime-build.mjs +1 -0
- package/packages/web-components/src/account-context.ts +29 -5
- package/packages/web-components/src/index.ts +5 -0
- package/packages/web-components/src/organization-selector.ts +435 -39
- package/packages/web-components/tests/account-context.test.ts +82 -2
- package/packages/web-components/tests/organization-selector.test.ts +226 -14
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +301 -157
package/AGENTS.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
本文档为自动化编码 Agent 在 `uni-base` 仓库工作时提供指导。除非用户明确要求,所有代码、配置、测试和文档改动都应限制在本仓库内。
|
|
4
4
|
|
|
5
|
+
> 本文件与 `CLAUDE.md` 内容一致,二者是同一套仓库规范;修改其一时须同步另一份,避免两份指导漂移。
|
|
6
|
+
|
|
5
7
|
## 项目定位
|
|
6
8
|
|
|
7
9
|
`uni-base` 是一个独立的 TypeScript 浏览器 SDK workspace,用于让原生 HTML、Vue 和 Nuxt 页面嵌入 Uniplat 数字员工能力。
|
|
@@ -37,13 +39,13 @@ examples/vanilla 原生 HTML / JavaScript 接入示例
|
|
|
37
39
|
|
|
38
40
|
组织入驻不是公开 Web Component。40.1 原型 02、A/B 企业/团队创建分支、校验/成功状态及 4.1 无归属提示均由 `<uniplat-account-context>` 内部实现;Host 在其 `organizationOnboarding.transport` 中注入创建能力,不能使用或注册 `<uniplat-organization-onboarding>`。点击账户菜单或个人态的“创建团队 / 企业”必须直接启动内部流程,不得向 Host 派发 `organization-create-requested` 或 `create-organization-requested`。真实 UniplatBase transport 复用 `createUniplatBaseOrganizationOnboardingTransport()` 与 `system.org/create_organization`。邀请码加入、企业认证、团队升级和邀请成员等未展开页面仍派发业务事件,由 Host 实现。
|
|
39
41
|
|
|
40
|
-
`<uniplat-organization-selector>` 覆盖 40.1 原型
|
|
42
|
+
`<uniplat-organization-selector>` 覆盖 40.1 原型 S.1 多归属选择弹窗,显示 Host 提供的完整企业/团队列表、类型、认证状态、角色、成员数和最近进入信息,并自行负责脱敏账户摘要、名称与统一社会信用代码搜索、全部/企业/团队类型筛选、“最近进入 / 全部组织”分组、默认选中和带目标名称的主按钮。选中与记住偏好只在组件内临时维护,点击“进入”后交由 Host 完成登录态切换,“退出登录”只派发事件。`presentation: 'modal'` 时组件自渲染 S.1 强制态弹窗:固定遮罩、无关闭按钮,遮罩点击与 `Escape` 都不关闭,`Escape` 也不冒泡到 Host 的全局关闭逻辑;默认 `inline` 只输出卡片,遮罩归 Host。S.0 的“弹不弹”判定由纯函数 `resolveOrganizationEntryDecision()` 提供,只按 0 个归属、1 个归属、命中记住选择、2 个及以上分别返回 `personal | auto-enter | prompt`;它不读写宿主存储,记住偏好由 Host 自行持久化并回传,记住的组织已不在列表时退回 `prompt`。
|
|
41
43
|
|
|
42
44
|
`<uniplat-organization-multi-employee-selector>` 是当前 JWT 所属组织的员工多选公共组件,与选择组织的 `<uniplat-organization-selector>` 语义独立。它复用 `system.org_directory/tree_full/node_contacts/contact_search/contact_get`,负责部门树、在职且启用员工的分页和搜索、初始选择回显、按员工 ID 去重、受保护项、多选上限与安全确认事件;`enable=0`、停用或离职员工不得出现在候选列表或初始回显中。组件不得接收组织身份参数,不判断业务权限,也不得调用管理员或角色写入接口。真实接口失败时不得使用 Mock 或 Host 本地名单回退。首版部门仅用于导航,不开放部门批量勾选。
|
|
43
45
|
|
|
44
46
|
`<uniplat-organization-single-employee-selector>` 是语义独立的组织员工单选公共组件。它可与多选组件复用内部数据控制器和真实 transport,但公开标签、`open()` 参数和确认 detail 必须独立。单选界面不渲染多选的已选右侧栏和移动端已选页签,候选区占满内容宽度,选择标识必须为单选圆点而非复选框。单选组件只接收 `initialSelectedEmployeeId`,确认时只返回 `{ employeeId, employee }`;不得向任一组件暴露 `selectionMode`,也不得让单选组件接收 `maxSelection` 或多选 ID 数组。候选数据继续只包含当前 JWT 组织内在职且启用的员工,JWT 和身份字段安全边界与多选组件一致。
|
|
45
47
|
|
|
46
|
-
`<uniplat-account-context>` 是 40.1 原型导航右侧的统一账户工作台组件,不渲染品牌和全局菜单。它在一个 Host 元素内包含工作台入口、账户胶囊、应用入口浮层和 6.7 企业/团队切换浮层;两个浮层互斥并绝对定位,不改变导航布局。组件根据 Host 提供的 `applicationName` 和账户 transport 自行加载当前应用对应的组织投影与企业认证状态;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得再请求 `profile_summary_v2`,Host 不再组装这些展示数据。它支持无归属个人态、企业未认证/审核中/未通过/已认证/认证过期和团队态;组织切换由 Host 响应事件,应用加载与 ticket 跳转复用独立的 Application Switcher transport
|
|
48
|
+
`<uniplat-account-context>` 是 40.1 原型导航右侧的统一账户工作台组件,不渲染品牌和全局菜单。它在一个 Host 元素内包含工作台入口、账户胶囊、应用入口浮层和 6.7 企业/团队切换浮层;两个浮层互斥并绝对定位,不改变导航布局。组件根据 Host 提供的 `applicationName` 和账户 transport 自行加载当前应用对应的组织投影与企业认证状态;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得再请求 `profile_summary_v2`,Host 不再组装这些展示数据。它支持无归属个人态、企业未认证/审核中/未通过/已认证/认证过期和团队态;组织切换由 Host 响应事件,应用加载与 ticket 跳转复用独立的 Application Switcher transport。选中组织时,账户胶囊和菜单资料区必须使用服务端投影中该组织的员工姓名与头像;没有选中组织或该组织没有员工身份时,回退到用户资料。账户胶囊宽度固定,胶囊内姓名超过 6 个字、组织名称超过 8 个字必须截断为省略号,完整值只放在 `title` 上,不得让长名称撑宽胶囊挤压导航左侧内容;展开菜单内仍显示完整名称。组织列表严格使用原型中的企业建筑与团队成员图标,不得替换成员工头像或组织首字。
|
|
47
49
|
|
|
48
50
|
`<uniplat-notification-center>` 是内嵌通知中心面板,复刻 AE-Enterprise-Admin 通知中心原型的两栏收件箱:面板顶栏、五类页签、列表、详情、关联资源、附件下载与“全部标已读”。**组件和 core 都不建立 WebSocket 连接**:Host 保留自己唯一的 `/api/user-realtime/ws` 连接并订阅 `notification` channel,把事件原始负载通过 `applyRealtimeEvent()` 喂给组件;`authenticate` 首帧、心跳、游标、重连和断连提示全部归 Host。组件只负责收件箱投影、增量更新和未读数,顶栏铃铛仍属 Host chrome。不得为通知新增第二条 socket 或平行实时通道。
|
|
49
51
|
|
|
@@ -330,7 +332,7 @@ transport 只提供 `createEnterprise()` 和 `createTeam()`;稳定冲突错误
|
|
|
330
332
|
|
|
331
333
|
真实 UniplatBase 对接使用 `createUniplatBaseOrganizationOnboardingTransport({ serviceBaseUrl, getAccessToken })`,复用 `system.org/create_organization`。每次创建前必须重新取 JWT,并只放入 `Authorization: Bearer` header;请求不得携带调用方身份字段。只对白名单冲突码做字段级映射,`INVALID_ARGUMENT`、`ORGANIZATION_CREATE_FAILED` 和所有非协议异常统一收敛为 `UNKNOWN`。
|
|
332
334
|
|
|
333
|
-
Organization Selector 的展示数据仍由 Host 通过 `configure()`
|
|
335
|
+
Organization Selector 的展示数据仍由 Host 通过 `configure()` 提供:`organizations`、`selectedOrganizationId`、`rememberSelection`、脱敏的 `displayName` 与 `maskedMobile`,以及 `presentation: 'inline' | 'modal'`。`maskedMobile` 必须是脱敏值,完整手机号一律拒绝;条目的可选 `unifiedSocialCreditCode` 只参与搜索匹配,不得渲染到 DOM 或进入事件。它不接收 JWT,`organization-enter-requested` 只包含组织 ID 和记住偏好,`organization-create-requested` 与 `logout-requested` 只派发空 detail。Account Context 必须显式接收 `applicationName` 和账户 transport,由组件调用应用组织投影接口;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `profile_summary_v2`。Host 只提供 transport 内的 `getAccessToken()/setAccessToken()`,不得再提供姓名、头像、当前组织、可选组织或认证状态。组织选择事件仍由 Host 响应,Host 可调用组件的 `switchOrganization()` 完成切换,组件自行保存投影并刷新展示。账户 transport 与 `workbench` 的 Application Switcher transport 职责不同:前者只负责账户、组织与认证上下文,后者只负责工作台应用入口和短期 ticket。有当前组织时 `showWorkbench` 默认开启,Host 可显式关闭。头像 URL 只能是无凭据、无 fragment、无认证参数的 HTTP(S) 地址,失败时回退文字头像;专用 transport 可将严格匹配且员工 ID 一致的历史受管头像路径转换为同一服务下的匿名头像接口,不得把内部存储路径写入 DOM。个人态使用无外框头像姓名。应用名、所有显示文本和头像 URL 均不得进入公共事件。旧的 `displayName/organizations` 等手工展示配置只为兼容保留,不再用于新接入。
|
|
334
336
|
|
|
335
337
|
### 事件
|
|
336
338
|
|
|
@@ -384,6 +386,7 @@ Organization Selector 当前派发:
|
|
|
384
386
|
```text
|
|
385
387
|
organization-enter-requested
|
|
386
388
|
organization-create-requested
|
|
389
|
+
logout-requested
|
|
387
390
|
```
|
|
388
391
|
|
|
389
392
|
Account Context 当前派发:
|
|
@@ -433,10 +436,10 @@ npm 包名、导出类型名、Custom Element 标签、`configure()` 结构、
|
|
|
433
436
|
- User Login 需测试组件注册与导出、三个固定页签、不可用能力禁用、原型中不存在图形验证码且不调用兼容方法、逐字段失焦校验和协议错误态、稳定后端错误码映射、短信倒计时、密码登录、扫码待确认/已扫码/过期/成功状态、注册成功过渡页、2 秒自动入驻和立即开始、页签切换与断开后的定时器清理、登录回调,以及 JWT、密码和二维码 challenge 不进入 URL、DOM、公共事件、日志或安全错误。
|
|
434
437
|
- User Menu 需测试组件注册与导出、40.1 6.7 原型关键内容、个人账号与当前组织员工信息入口、企业认证与团队样式、安全头像与文字回退、Host 受控选中状态、重新配置、空组织状态、完整手机号拒绝、创建与退出事件、普通文本插值,以及公共事件只包含必要的组织 ID,不包含显示名、手机号、组织名称、头像 URL、JWT 或内部配置。
|
|
435
438
|
- 内置组织入驻需测试所有原型步骤、逐字段校验、稳定 transport 错误映射、企业/团队成功状态、跳过与未展开流程事件、普通文本插值,以及公共事件不包含企业信息、信用代码、JWT 或原始异常。真实 UniplatBase transport 还必须测试每次请求重新取 JWT、JWT 只进入 Authorization header、企业/团队请求字段、成功响应净化和未知错误收敛。
|
|
436
|
-
- Organization Selector
|
|
439
|
+
- Organization Selector 需测试完整列表、企业认证与团队类型样式、脱敏账户摘要与完整手机号拒绝、按名称和统一社会信用代码搜索且代码不进入 DOM、类型筛选与常显计数、“最近进入 / 全部组织”分组与默认选中、带目标名称的主按钮、无匹配与空归属状态、记住偏好、创建/进入/退出登录事件、`modal` 强制态无关闭按钮且遮罩点击与 `Escape` 都不关闭并在移除后解除 `Escape` 拦截、非法 `presentation` 与重复 ID 拒绝和普通文本插值;事件只包含稳定组织 ID 与偏好,不含姓名、手机号、组织名称或信用代码。`resolveOrganizationEntryDecision()` 需测试 0 个归属、单一归属、命中与失效的记住选择、2 个及以上的默认选中项和非法输入拒绝。
|
|
437
440
|
- Organization Multi Employee Selector 需测试部门树、员工分页和搜索、初始选择补取、跨部门按员工 ID 去重、多选上限、受保护项、确认/取消事件、独立滚动、移动视图、错误重试、身份切换清理,以及 JWT、完整手机号、身份字段和后端错误不进入 DOM、事件、URL或日志。专用 transport 必须测试每次请求重新读取 JWT、只使用 Authorization header、组织级 directory scope 和无调用方身份参数。
|
|
438
441
|
- Organization Single Employee Selector 需测试独立标签与导出、初始单个 ID 回显、新选择替换旧选择、未选时禁止确认、受保护项、标量确认 detail、拒绝多选打开参数,以及与多选组件相同的数据和认证安全边界。
|
|
439
|
-
- Account Context 需测试必填应用名、组件通过 transport
|
|
442
|
+
- Account Context 需测试必填应用名、组件通过 transport 自行加载用户资料与完整应用组织投影、无归属、企业五种认证状态和团队状态、当前组织员工身份优先及无组织用户身份回退、安全头像与加载失败回退、胶囊内姓名 6 字与组织名 8 字截断及完整 `title`、未超长时不加 `title`、工作台、账户菜单与内置入驻浮层互斥、浮层不改变导航布局、内嵌应用九宫格、创建入口直接启动内置入驻、Host 响应组织切换事件、退出和公共事件安全。专用 UniplatBase transport 还必须测试不请求 `profile_summary_v2`、从 `list_available_organizations` 顶层读取用户资料、按 `application_name` 加载投影、Open Application 的 `organization_oid` 切换、组织级应用目标 `xid` 选择、JWT 只进入 Authorization header 和 Host `setAccessToken()`、响应净化、稳定错误以及公共数据无 JWT;组件范围不得扩展为品牌或完整导航。
|
|
440
443
|
- 通知中心需测试组件注册与导出、两栏首版输出、数据驱动分类页签(`includeTypeFacets` 随收件箱请求、后端 `type_facets` 优先、无 facet 回退累积、筛选后页签不塌缩、展示名取 `notification_type_name` 且为空时回退原始 code、契约四个码不得残留内置中文名、实时新分类入目录)、未读角标与 `unread-count-change`、打开未读即标记已读、全部已读、加载更多、关联资源事件只含安全业务标识、附件经 transport 下载、Markdown 净化、`applyRealtimeEvent()` 不建立任何 WebSocket 且原地更新列表与未读数、事件无法映射时降级刷新、收件箱在途时延后 resync 不丢事件、`configure()` 之前与移除之后喂入事件无副作用、错误横幅为净化文案,以及 JWT 与后端原始正文不进入 DOM、公共事件或日志。小型通知浮层还需测试初始关闭、4 条未读投影、未读总数、严格原型结构与 SVG 图标、单条/全部已读、完整通知页意图事件只含稳定 ID、外部关闭、空态以及 Host 实时事件更新。
|
|
441
444
|
- 使用 fake timer 的测试必须恢复 timer;构造 fake WebSocket 时不要把测试凭据写入 URL。
|
|
442
445
|
- 修复竞态或生命周期问题时增加回归测试,不只验证最终 DOM。
|
|
@@ -466,20 +469,21 @@ npm 包名、导出类型名、Custom Element 标签、`configure()` 结构、
|
|
|
466
469
|
- 所有关联项目的职责与代码路径统一登记在 `/Users/gaostudio/.vibe-requirements/projects.md`。查阅跨项目文档或代码前应先读取该清单,并以清单中的路径为准;如果当前负责的项目不在清单中,应按现有表格格式补充项目名称、主要职责和所在路径。
|
|
467
470
|
- 提出跨项目接口需求前,必须先核对提供方的现有文档、实现和调用方。优先复用语义相符的已有接口,并优先通过增加可选入参或新增响应字段向后兼容地扩展;只有在鉴权边界、事务语义、性能或职责归属确实无法复用时才新增接口,不能为单个前端组件盲目建立平行 API。
|
|
468
471
|
- 本项目当前基础需求:`/Users/gaostudio/.vibe-requirements/uni-base_基础建设需求_v0_1.md`。
|
|
469
|
-
- AI Employee Chat 完整右侧抽屉原型:`/Users/gaostudio/ae_design/1
|
|
472
|
+
- AI Employee Chat 完整右侧抽屉原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html#consult`。组件负责原型中的完整 420px 固定右侧抽屉,Host 只负责调用 `open()` 激活,不得重复渲染抽屉标题、消息容器或底部操作区。
|
|
470
473
|
- 用户首条消息原子创建会话联合需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_KanbanBackend_AE-Enterprise-Admin_QQXBWebsite_UniBase_HR-SaaS_用户首条消息原子创建会话联合需求_v0_1.md`。UniBase 负责本地草稿、稳定 `request_id`、原子 transport、权威 ID 绑定及实时竞态去重;不得在打开抽屉或新建对话时创建空 Session。
|
|
471
|
-
- User Login、内置组织入驻、Organization Selector、Account Context 与 User Menu 的界面和状态总原型:`/Users/gaostudio/
|
|
474
|
+
- User Login、内置组织入驻、Organization Selector、Account Context 与 User Menu 的界面和状态总原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40.1_注册登录与企业入驻流程.html`,配套 PRD 对照手册为同目录 `40.1-1_注册登录与企业入驻流程_页面与PRD对照手册.html`。修改任一能力前必须直接核对这两个文件。User Login 覆盖 01 和 03;内置组织入驻覆盖 02、A/B 分支与 4.1;Organization Selector 覆盖 5.1;Account Context 覆盖 4.2、5.2、06、6.7 以及工作台应用入口,User Menu 是其中 6.7 的独立兼容入口。原型展示但未展开页面的邀请、认证、升级和邀请成员操作只派发事件,由 Host 实现页面流程。
|
|
472
475
|
- User Login 注册判定与稳定错误码接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_UserLogin注册结果与稳定错误码需求_v0_1.md`。
|
|
473
476
|
- 企业团队入驻与归属展示接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_企业团队入驻与归属展示接口需求_v0_1.md`;UniplatBase 已反馈的接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/企业团队入驻与归属展示接口说明_v0_1.md`。对接时必须核对 `services/system/org.groovy`、统一组织分类工具和 `tests/verify_organization_onboarding_api.py`,不能只按示例响应实现。
|
|
474
477
|
- 账户上下文按组织显示员工姓名与头像的接口字段扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_账户上下文组织员工身份展示字段需求_v0_1.md`。复用 `application.identity/list_available_organizations.organizations[]` 的员工展示字段;用户资料复用该接口顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `system.user/info`、`profile_summary_v2` 或新增平行身份接口。
|
|
475
478
|
- Account Context 按当前应用筛选组织并安全切换全局应用组织上下文的接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_AccountContext按应用筛选与组织切换需求_v0_1.md`;UniplatBase 已反馈说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/AccountContext按应用筛选与组织切换接口说明_v0_1.md`。继续复用 `list_available_applications` 与 `switch_current_application`;工作台内嵌应用面板不得请求或显示组织列表,也不得把后端切换目标传入组件配置或公共事件。工作台必须优先读取 `application_scope`,只在旧响应缺失该字段时以 `oid=0` 临时兼容 Open Application。
|
|
476
|
-
- 旧 `/Users/gaostudio/
|
|
479
|
+
- 旧 `/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html#consult` 仅用于理解历史 User Menu 调用,不再作为当前菜单视觉依据;当前视觉以 40.1 的 6.7 为准。
|
|
477
480
|
- 通知中心 Web 组件动态接入需求:`/Users/gaostudio/.vibe-requirements/UniBase_AE-Enterprise-Admin_通知中心Web组件动态接入需求_v0_1.md`(v0.2 修订:实时连接归 Host,组件只经 `applyRealtimeEvent()` 消费事件)。视觉原型为 `/Users/gaostudio/ae_design/2-数字人事部/产品设计/杜星霖工作目录/过程原型文件/数字人事部_管理端_HRD视角_v0_6.html`。后端投影与实时事件契约见同目录 `UniplatBase_KanbanBackend_通知中心真实数据与实时通知接口需求_v0_1.md` 与按应用身份过滤联合需求。
|
|
478
|
-
- 小型消息通知浮层视觉与交互原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/
|
|
481
|
+
- 小型消息通知浮层视觉与交互原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html`;只复用现有通知中心接口与 Host 实时通道,不另写平行后端需求。
|
|
479
482
|
- 通知中心空 `applicationName` 的全应用聚合后端需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_通知中心全应用聚合查询与操作需求_v0_1.md`。
|
|
480
483
|
- 组织员工多选 Web Component 公共能力需求:`/Users/gaostudio/.vibe-requirements/QQXBWebsite_UniBase_组织员工多选WebComponent公共能力需求_v0_1.md`。
|
|
484
|
+
- 多归属组织选择 S.0 / S.1 接入需求:`/Users/gaostudio/.vibe-requirements/UniBase_QQXBWebsite_多归属组织选择S0S1接入需求_v0_1.md`。组件侧已交付,官网需去掉自有遮罩改用 `presentation: 'modal'`、按 `resolveOrganizationEntryDecision()` 路由并自行持久化记住偏好;组织条目的统一社会信用代码是 UniplatBase 的可选扩展,未扩展前搜索只按名称匹配。
|
|
481
485
|
- Application Switcher 对接 UniplatBase 的接口差异与扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_ApplicationSwitcher接口适配需求_v0_1.md`。
|
|
482
|
-
- UniplatBase 已反馈的 Application Switcher 接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/ApplicationSwitcher接口说明
|
|
486
|
+
- UniplatBase 已反馈的 Application Switcher 接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/ApplicationSwitcher接口说明_v0_4.md`;对接时同时核对该项目实际实现和验证脚本,不能只按示例响应猜测。
|
|
483
487
|
- 新需求优先从 `/Users/gaostudio/.vibe-requirements` 查找目标项目同名文档;默认不在该目录创建子目录。
|
|
484
488
|
- 需要确认实时协议时,可以只读核对 Kanban 和 Enterprise Admin 的现有实现;未经用户明确要求,不修改其他项目。
|
|
485
489
|
- 需求与后端现状冲突时应明确指出差异,不静默选择兼容分支,也不要在 SDK 中预埋未经确认的抽象。
|
package/CLAUDE.md
CHANGED
|
@@ -25,6 +25,8 @@ examples/vanilla 原生 HTML / JavaScript 接入示例
|
|
|
25
25
|
<uniplat-customer-support-chat></uniplat-customer-support-chat>
|
|
26
26
|
<uniplat-user-login></uniplat-user-login>
|
|
27
27
|
<uniplat-organization-selector></uniplat-organization-selector>
|
|
28
|
+
<uniplat-organization-multi-employee-selector></uniplat-organization-multi-employee-selector>
|
|
29
|
+
<uniplat-organization-single-employee-selector></uniplat-organization-single-employee-selector>
|
|
28
30
|
<uniplat-account-context></uniplat-account-context>
|
|
29
31
|
<uniplat-user-menu></uniplat-user-menu>
|
|
30
32
|
<uniplat-notification-center></uniplat-notification-center>
|
|
@@ -37,9 +39,13 @@ examples/vanilla 原生 HTML / JavaScript 接入示例
|
|
|
37
39
|
|
|
38
40
|
组织入驻不是公开 Web Component。40.1 原型 02、A/B 企业/团队创建分支、校验/成功状态及 4.1 无归属提示均由 `<uniplat-account-context>` 内部实现;Host 在其 `organizationOnboarding.transport` 中注入创建能力,不能使用或注册 `<uniplat-organization-onboarding>`。点击账户菜单或个人态的“创建团队 / 企业”必须直接启动内部流程,不得向 Host 派发 `organization-create-requested` 或 `create-organization-requested`。真实 UniplatBase transport 复用 `createUniplatBaseOrganizationOnboardingTransport()` 与 `system.org/create_organization`。邀请码加入、企业认证、团队升级和邀请成员等未展开页面仍派发业务事件,由 Host 实现。
|
|
39
41
|
|
|
40
|
-
`<uniplat-organization-selector>` 覆盖 40.1 原型
|
|
42
|
+
`<uniplat-organization-selector>` 覆盖 40.1 原型 S.1 多归属选择弹窗,显示 Host 提供的完整企业/团队列表、类型、认证状态、角色、成员数和最近进入信息,并自行负责脱敏账户摘要、名称与统一社会信用代码搜索、全部/企业/团队类型筛选、“最近进入 / 全部组织”分组、默认选中和带目标名称的主按钮。选中与记住偏好只在组件内临时维护,点击“进入”后交由 Host 完成登录态切换,“退出登录”只派发事件。`presentation: 'modal'` 时组件自渲染 S.1 强制态弹窗:固定遮罩、无关闭按钮,遮罩点击与 `Escape` 都不关闭,`Escape` 也不冒泡到 Host 的全局关闭逻辑;默认 `inline` 只输出卡片,遮罩归 Host。S.0 的“弹不弹”判定由纯函数 `resolveOrganizationEntryDecision()` 提供,只按 0 个归属、1 个归属、命中记住选择、2 个及以上分别返回 `personal | auto-enter | prompt`;它不读写宿主存储,记住偏好由 Host 自行持久化并回传,记住的组织已不在列表时退回 `prompt`。
|
|
41
43
|
|
|
42
|
-
`<uniplat-
|
|
44
|
+
`<uniplat-organization-multi-employee-selector>` 是当前 JWT 所属组织的员工多选公共组件,与选择组织的 `<uniplat-organization-selector>` 语义独立。它复用 `system.org_directory/tree_full/node_contacts/contact_search/contact_get`,负责部门树、在职且启用员工的分页和搜索、初始选择回显、按员工 ID 去重、受保护项、多选上限与安全确认事件;`enable=0`、停用或离职员工不得出现在候选列表或初始回显中。组件不得接收组织身份参数,不判断业务权限,也不得调用管理员或角色写入接口。真实接口失败时不得使用 Mock 或 Host 本地名单回退。首版部门仅用于导航,不开放部门批量勾选。
|
|
45
|
+
|
|
46
|
+
`<uniplat-organization-single-employee-selector>` 是语义独立的组织员工单选公共组件。它可与多选组件复用内部数据控制器和真实 transport,但公开标签、`open()` 参数和确认 detail 必须独立。单选界面不渲染多选的已选右侧栏和移动端已选页签,候选区占满内容宽度,选择标识必须为单选圆点而非复选框。单选组件只接收 `initialSelectedEmployeeId`,确认时只返回 `{ employeeId, employee }`;不得向任一组件暴露 `selectionMode`,也不得让单选组件接收 `maxSelection` 或多选 ID 数组。候选数据继续只包含当前 JWT 组织内在职且启用的员工,JWT 和身份字段安全边界与多选组件一致。
|
|
47
|
+
|
|
48
|
+
`<uniplat-account-context>` 是 40.1 原型导航右侧的统一账户工作台组件,不渲染品牌和全局菜单。它在一个 Host 元素内包含工作台入口、账户胶囊、应用入口浮层和 6.7 企业/团队切换浮层;两个浮层互斥并绝对定位,不改变导航布局。组件根据 Host 提供的 `applicationName` 和账户 transport 自行加载当前应用对应的组织投影与企业认证状态;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得再请求 `profile_summary_v2`,Host 不再组装这些展示数据。它支持无归属个人态、企业未认证/审核中/未通过/已认证/认证过期和团队态;组织切换由 Host 响应事件,应用加载与 ticket 跳转复用独立的 Application Switcher transport。选中组织时,账户胶囊和菜单资料区必须使用服务端投影中该组织的员工姓名与头像;没有选中组织或该组织没有员工身份时,回退到用户资料。账户胶囊宽度固定,胶囊内姓名超过 6 个字、组织名称超过 8 个字必须截断为省略号,完整值只放在 `title` 上,不得让长名称撑宽胶囊挤压导航左侧内容;展开菜单内仍显示完整名称。组织列表严格使用原型中的企业建筑与团队成员图标,不得替换成员工头像或组织首字。
|
|
43
49
|
|
|
44
50
|
`<uniplat-notification-center>` 是内嵌通知中心面板,复刻 AE-Enterprise-Admin 通知中心原型的两栏收件箱:面板顶栏、五类页签、列表、详情、关联资源、附件下载与“全部标已读”。**组件和 core 都不建立 WebSocket 连接**:Host 保留自己唯一的 `/api/user-realtime/ws` 连接并订阅 `notification` channel,把事件原始负载通过 `applyRealtimeEvent()` 喂给组件;`authenticate` 首帧、心跳、游标、重连和断连提示全部归 Host。组件只负责收件箱投影、增量更新和未读数,顶栏铃铛仍属 Host chrome。不得为通知新增第二条 socket 或平行实时通道。
|
|
45
51
|
|
|
@@ -186,10 +192,10 @@ pnpm --filter @uniplat/ai-employee-web-components pack
|
|
|
186
192
|
|
|
187
193
|
以下规则不可放宽:
|
|
188
194
|
|
|
189
|
-
- JWT 只允许出现在宿主系统的 token 获取或保存过程、WebSocket `authenticate` 首帧、Application Switcher 组织、入口和
|
|
195
|
+
- JWT 只允许出现在宿主系统的 token 获取或保存过程、WebSocket `authenticate` 首帧、Application Switcher 组织、入口和 Ticket 签发请求的 `Authorization` header、Account Context 投影查询与组织切换请求的 `Authorization` header、Organization Onboarding 创建请求的 `Authorization` header、目标页面 Ticket 登录或旧兑换响应到其 Host `setAccessToken()` 的短暂传递过程,以及 User Login 登录响应到宿主 `setAccessToken()` 的短暂传递过程。
|
|
190
196
|
- SDK 不自动读取、保存或刷新长期 JWT;每次连接通过注入的 `transport.getAccessToken()` 获取当前有效令牌。
|
|
191
197
|
- JWT 不得进入 URL、HTML attribute、DOM 文本、CustomEvent、localStorage、日志、异常消息、测试快照或构建时配置。
|
|
192
|
-
- Application Switcher 的短期
|
|
198
|
+
- Application Switcher 的短期 Ticket 只允许进入目标 URL Fragment 和登录请求体;新当前身份 Ticket 必须是不透明、一次性、固定 15 秒的 URL-safe 值。Ticket URL 只能作为函数局部导航值,不得进入 HTML attribute、DOM、组件响应式状态、CustomEvent、日志、异常、快照或构建配置;目标 helper 必须先清除 Fragment 再登录。旧应用 Ticket 协议仅作兼容保留。
|
|
193
199
|
- 如果宿主使用内存状态、sessionStorage 或 HttpOnly Cookie,读取细节必须留在宿主提供的 `getAccessToken()` 中;SDK 不硬编码存储 key。
|
|
194
200
|
- 不得向浏览器包加入 HR-SaaS 或 Kanban 的 HMAC 密钥、内部签名逻辑。
|
|
195
201
|
- 不得信任或提交调用方给出的 `org_id`、`user_id` 或角色声明;身份以服务端 JWT 鉴权结果为准。
|
|
@@ -274,7 +280,7 @@ Popover 使用 `listInbox({ pageSize: 4, readStatus: 'unread' })`,只渲染未
|
|
|
274
280
|
|
|
275
281
|
内嵌工作台不提供组织选择;应用身份切换通过 transport 的可选 `switchApplication()` 提供,该方法可以返回只供导航使用的 `{ url }`,但该 URL 不得进入 DOM、组件状态或公共事件。不实现该方法的 transport 仍可只显示应用入口。所有应用必须在新窗口打开,不得覆盖 Host 当前页面;需要异步签发 ticket 时,应在用户点击的同步阶段预先打开无凭据空白窗口,失败或事件被取消时关闭该窗口。真实 HTTP transport 必须把 JWT 放在 `Authorization: Bearer` header,不得拼入 endpoint、入口 URL、空白窗口或切换请求体。组件不得长期保存 JWT,并应在每次加载或应用切换时重新调用 `getAccessToken()`。
|
|
276
282
|
|
|
277
|
-
对接 UniplatBase 时使用 `createUniplatBaseApplicationSwitcherTransport()`,复用 `application.identity/list_available_applications
|
|
283
|
+
对接 UniplatBase 时使用 `createUniplatBaseApplicationSwitcherTransport()`,复用 `application.identity/list_available_applications`,点击应用时以当前 JWT 和空 JSON 请求 `application.identity/issue_ticket`。内嵌工作台只加载当前身份可展示的应用入口;`list` 中通过安全校验且 `oid` 等于当前组织或 `application_scope=open` 的应用必须按返回顺序显示,其他组织的应用不显示,符合条件的应用不能截断为前 9 个。本地和外部入口都使用选中项 `entry_url`,以 `#uniplat_identity_ticket=<ticket>&source_system=<签发响应来源>` 传到新窗口;不得把 `launch_target.target_system` 当作来源,Fragment 不携带应用或 `client_id`,当前 Host JWT 不更新。目标页面使用 `loginWithUniplatBaseIdentityTicket()`,必须先清除 Ticket 与来源 Fragment,再以项目初始化的 `applicationName` 调用 `anonymous/application.identity/login_with_ticket`,校验响应应用一致后才把 JWT 交给 Host `setAccessToken()`。旧 `exchangeUniplatBaseApplicationTicket()` 及旧 Fragment 继续兼容但不用于默认工作台。Ticket 和 JWT 均不得进入 DOM、公共事件、日志或异常;该 Ticket 与 Kanban WebSocket 协议无关。
|
|
278
284
|
|
|
279
285
|
User Login 同样通过 JavaScript property 配置:
|
|
280
286
|
|
|
@@ -326,7 +332,7 @@ transport 只提供 `createEnterprise()` 和 `createTeam()`;稳定冲突错误
|
|
|
326
332
|
|
|
327
333
|
真实 UniplatBase 对接使用 `createUniplatBaseOrganizationOnboardingTransport({ serviceBaseUrl, getAccessToken })`,复用 `system.org/create_organization`。每次创建前必须重新取 JWT,并只放入 `Authorization: Bearer` header;请求不得携带调用方身份字段。只对白名单冲突码做字段级映射,`INVALID_ARGUMENT`、`ORGANIZATION_CREATE_FAILED` 和所有非协议异常统一收敛为 `UNKNOWN`。
|
|
328
334
|
|
|
329
|
-
Organization Selector 的展示数据仍由 Host 通过 `configure()`
|
|
335
|
+
Organization Selector 的展示数据仍由 Host 通过 `configure()` 提供:`organizations`、`selectedOrganizationId`、`rememberSelection`、脱敏的 `displayName` 与 `maskedMobile`,以及 `presentation: 'inline' | 'modal'`。`maskedMobile` 必须是脱敏值,完整手机号一律拒绝;条目的可选 `unifiedSocialCreditCode` 只参与搜索匹配,不得渲染到 DOM 或进入事件。它不接收 JWT,`organization-enter-requested` 只包含组织 ID 和记住偏好,`organization-create-requested` 与 `logout-requested` 只派发空 detail。Account Context 必须显式接收 `applicationName` 和账户 transport,由组件调用应用组织投影接口;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `profile_summary_v2`。Host 只提供 transport 内的 `getAccessToken()/setAccessToken()`,不得再提供姓名、头像、当前组织、可选组织或认证状态。组织选择事件仍由 Host 响应,Host 可调用组件的 `switchOrganization()` 完成切换,组件自行保存投影并刷新展示。账户 transport 与 `workbench` 的 Application Switcher transport 职责不同:前者只负责账户、组织与认证上下文,后者只负责工作台应用入口和短期 ticket。有当前组织时 `showWorkbench` 默认开启,Host 可显式关闭。头像 URL 只能是无凭据、无 fragment、无认证参数的 HTTP(S) 地址,失败时回退文字头像;专用 transport 可将严格匹配且员工 ID 一致的历史受管头像路径转换为同一服务下的匿名头像接口,不得把内部存储路径写入 DOM。个人态使用无外框头像姓名。应用名、所有显示文本和头像 URL 均不得进入公共事件。旧的 `displayName/organizations` 等手工展示配置只为兼容保留,不再用于新接入。
|
|
330
336
|
|
|
331
337
|
### 事件
|
|
332
338
|
|
|
@@ -380,6 +386,7 @@ Organization Selector 当前派发:
|
|
|
380
386
|
```text
|
|
381
387
|
organization-enter-requested
|
|
382
388
|
organization-create-requested
|
|
389
|
+
logout-requested
|
|
383
390
|
```
|
|
384
391
|
|
|
385
392
|
Account Context 当前派发:
|
|
@@ -425,12 +432,14 @@ npm 包名、导出类型名、Custom Element 标签、`configure()` 结构、
|
|
|
425
432
|
- 首条消息原子建会话需测试:打开和新建仅产生本地草稿、首条请求字段映射、失败重试复用 `request_id`、成功后使用服务端稳定 ID 绑定与订阅、HTTP 与 WebSocket 竞态去重、已有会话仍走后续消息接口,以及请求 ID、执行 ID 和认证信息不进入 DOM、公共事件或日志。
|
|
426
433
|
- Web Component 注册、Shadow DOM 输出、事件、安全 detail 和断开释放放在 happy-dom 测试中。
|
|
427
434
|
- 新增 Web Component 至少测试:标签已注册、类已导出、关键首版输出正确。
|
|
428
|
-
- Account Context 内嵌工作台需测试不注册独立应用切换标签、JWT 只交给 transport、HTTP Authorization header、入口净化、当前组织级应用加 `application_scope=open` 应用的筛选、缺字段时 `oid=0`
|
|
435
|
+
- Account Context 内嵌工作台需测试不注册独立应用切换标签、JWT 只交给 transport、HTTP Authorization header、入口净化、当前组织级应用加 `application_scope=open` 应用的筛选、缺字段时 `oid=0` 兼容、`issue_ticket` 空请求体、签发响应来源进入新 Fragment、公共事件无 JWT/Ticket、Ticket 只进入局部导航 URL、目标页面先清 Fragment 再按初始化应用登录、响应应用绑定校验、加载错误、仅请求应用列表和应用选择交互。
|
|
429
436
|
- User Login 需测试组件注册与导出、三个固定页签、不可用能力禁用、原型中不存在图形验证码且不调用兼容方法、逐字段失焦校验和协议错误态、稳定后端错误码映射、短信倒计时、密码登录、扫码待确认/已扫码/过期/成功状态、注册成功过渡页、2 秒自动入驻和立即开始、页签切换与断开后的定时器清理、登录回调,以及 JWT、密码和二维码 challenge 不进入 URL、DOM、公共事件、日志或安全错误。
|
|
430
437
|
- User Menu 需测试组件注册与导出、40.1 6.7 原型关键内容、个人账号与当前组织员工信息入口、企业认证与团队样式、安全头像与文字回退、Host 受控选中状态、重新配置、空组织状态、完整手机号拒绝、创建与退出事件、普通文本插值,以及公共事件只包含必要的组织 ID,不包含显示名、手机号、组织名称、头像 URL、JWT 或内部配置。
|
|
431
438
|
- 内置组织入驻需测试所有原型步骤、逐字段校验、稳定 transport 错误映射、企业/团队成功状态、跳过与未展开流程事件、普通文本插值,以及公共事件不包含企业信息、信用代码、JWT 或原始异常。真实 UniplatBase transport 还必须测试每次请求重新取 JWT、JWT 只进入 Authorization header、企业/团队请求字段、成功响应净化和未知错误收敛。
|
|
432
|
-
- Organization Selector
|
|
433
|
-
-
|
|
439
|
+
- Organization Selector 需测试完整列表、企业认证与团队类型样式、脱敏账户摘要与完整手机号拒绝、按名称和统一社会信用代码搜索且代码不进入 DOM、类型筛选与常显计数、“最近进入 / 全部组织”分组与默认选中、带目标名称的主按钮、无匹配与空归属状态、记住偏好、创建/进入/退出登录事件、`modal` 强制态无关闭按钮且遮罩点击与 `Escape` 都不关闭并在移除后解除 `Escape` 拦截、非法 `presentation` 与重复 ID 拒绝和普通文本插值;事件只包含稳定组织 ID 与偏好,不含姓名、手机号、组织名称或信用代码。`resolveOrganizationEntryDecision()` 需测试 0 个归属、单一归属、命中与失效的记住选择、2 个及以上的默认选中项和非法输入拒绝。
|
|
440
|
+
- Organization Multi Employee Selector 需测试部门树、员工分页和搜索、初始选择补取、跨部门按员工 ID 去重、多选上限、受保护项、确认/取消事件、独立滚动、移动视图、错误重试、身份切换清理,以及 JWT、完整手机号、身份字段和后端错误不进入 DOM、事件、URL或日志。专用 transport 必须测试每次请求重新读取 JWT、只使用 Authorization header、组织级 directory scope 和无调用方身份参数。
|
|
441
|
+
- Organization Single Employee Selector 需测试独立标签与导出、初始单个 ID 回显、新选择替换旧选择、未选时禁止确认、受保护项、标量确认 detail、拒绝多选打开参数,以及与多选组件相同的数据和认证安全边界。
|
|
442
|
+
- Account Context 需测试必填应用名、组件通过 transport 自行加载用户资料与完整应用组织投影、无归属、企业五种认证状态和团队状态、当前组织员工身份优先及无组织用户身份回退、安全头像与加载失败回退、胶囊内姓名 6 字与组织名 8 字截断及完整 `title`、未超长时不加 `title`、工作台、账户菜单与内置入驻浮层互斥、浮层不改变导航布局、内嵌应用九宫格、创建入口直接启动内置入驻、Host 响应组织切换事件、退出和公共事件安全。专用 UniplatBase transport 还必须测试不请求 `profile_summary_v2`、从 `list_available_organizations` 顶层读取用户资料、按 `application_name` 加载投影、Open Application 的 `organization_oid` 切换、组织级应用目标 `xid` 选择、JWT 只进入 Authorization header 和 Host `setAccessToken()`、响应净化、稳定错误以及公共数据无 JWT;组件范围不得扩展为品牌或完整导航。
|
|
434
443
|
- 通知中心需测试组件注册与导出、两栏首版输出、数据驱动分类页签(`includeTypeFacets` 随收件箱请求、后端 `type_facets` 优先、无 facet 回退累积、筛选后页签不塌缩、展示名取 `notification_type_name` 且为空时回退原始 code、契约四个码不得残留内置中文名、实时新分类入目录)、未读角标与 `unread-count-change`、打开未读即标记已读、全部已读、加载更多、关联资源事件只含安全业务标识、附件经 transport 下载、Markdown 净化、`applyRealtimeEvent()` 不建立任何 WebSocket 且原地更新列表与未读数、事件无法映射时降级刷新、收件箱在途时延后 resync 不丢事件、`configure()` 之前与移除之后喂入事件无副作用、错误横幅为净化文案,以及 JWT 与后端原始正文不进入 DOM、公共事件或日志。小型通知浮层还需测试初始关闭、4 条未读投影、未读总数、严格原型结构与 SVG 图标、单条/全部已读、完整通知页意图事件只含稳定 ID、外部关闭、空态以及 Host 实时事件更新。
|
|
435
444
|
- 使用 fake timer 的测试必须恢复 timer;构造 fake WebSocket 时不要把测试凭据写入 URL。
|
|
436
445
|
- 修复竞态或生命周期问题时增加回归测试,不只验证最终 DOM。
|
|
@@ -460,19 +469,21 @@ npm 包名、导出类型名、Custom Element 标签、`configure()` 结构、
|
|
|
460
469
|
- 所有关联项目的职责与代码路径统一登记在 `/Users/gaostudio/.vibe-requirements/projects.md`。查阅跨项目文档或代码前应先读取该清单,并以清单中的路径为准;如果当前负责的项目不在清单中,应按现有表格格式补充项目名称、主要职责和所在路径。
|
|
461
470
|
- 提出跨项目接口需求前,必须先核对提供方的现有文档、实现和调用方。优先复用语义相符的已有接口,并优先通过增加可选入参或新增响应字段向后兼容地扩展;只有在鉴权边界、事务语义、性能或职责归属确实无法复用时才新增接口,不能为单个前端组件盲目建立平行 API。
|
|
462
471
|
- 本项目当前基础需求:`/Users/gaostudio/.vibe-requirements/uni-base_基础建设需求_v0_1.md`。
|
|
463
|
-
- AI Employee Chat 完整右侧抽屉原型:`/Users/gaostudio/ae_design/1
|
|
472
|
+
- AI Employee Chat 完整右侧抽屉原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html#consult`。组件负责原型中的完整 420px 固定右侧抽屉,Host 只负责调用 `open()` 激活,不得重复渲染抽屉标题、消息容器或底部操作区。
|
|
464
473
|
- 用户首条消息原子创建会话联合需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_KanbanBackend_AE-Enterprise-Admin_QQXBWebsite_UniBase_HR-SaaS_用户首条消息原子创建会话联合需求_v0_1.md`。UniBase 负责本地草稿、稳定 `request_id`、原子 transport、权威 ID 绑定及实时竞态去重;不得在打开抽屉或新建对话时创建空 Session。
|
|
465
|
-
- User Login、内置组织入驻、Organization Selector、Account Context 与 User Menu 的界面和状态总原型:`/Users/gaostudio/
|
|
474
|
+
- User Login、内置组织入驻、Organization Selector、Account Context 与 User Menu 的界面和状态总原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40.1_注册登录与企业入驻流程.html`,配套 PRD 对照手册为同目录 `40.1-1_注册登录与企业入驻流程_页面与PRD对照手册.html`。修改任一能力前必须直接核对这两个文件。User Login 覆盖 01 和 03;内置组织入驻覆盖 02、A/B 分支与 4.1;Organization Selector 覆盖 5.1;Account Context 覆盖 4.2、5.2、06、6.7 以及工作台应用入口,User Menu 是其中 6.7 的独立兼容入口。原型展示但未展开页面的邀请、认证、升级和邀请成员操作只派发事件,由 Host 实现页面流程。
|
|
466
475
|
- User Login 注册判定与稳定错误码接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_UserLogin注册结果与稳定错误码需求_v0_1.md`。
|
|
467
476
|
- 企业团队入驻与归属展示接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_企业团队入驻与归属展示接口需求_v0_1.md`;UniplatBase 已反馈的接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/企业团队入驻与归属展示接口说明_v0_1.md`。对接时必须核对 `services/system/org.groovy`、统一组织分类工具和 `tests/verify_organization_onboarding_api.py`,不能只按示例响应实现。
|
|
468
477
|
- 账户上下文按组织显示员工姓名与头像的接口字段扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_账户上下文组织员工身份展示字段需求_v0_1.md`。复用 `application.identity/list_available_organizations.organizations[]` 的员工展示字段;用户资料复用该接口顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `system.user/info`、`profile_summary_v2` 或新增平行身份接口。
|
|
469
478
|
- Account Context 按当前应用筛选组织并安全切换全局应用组织上下文的接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_AccountContext按应用筛选与组织切换需求_v0_1.md`;UniplatBase 已反馈说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/AccountContext按应用筛选与组织切换接口说明_v0_1.md`。继续复用 `list_available_applications` 与 `switch_current_application`;工作台内嵌应用面板不得请求或显示组织列表,也不得把后端切换目标传入组件配置或公共事件。工作台必须优先读取 `application_scope`,只在旧响应缺失该字段时以 `oid=0` 临时兼容 Open Application。
|
|
470
|
-
- 旧 `/Users/gaostudio/
|
|
479
|
+
- 旧 `/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html#consult` 仅用于理解历史 User Menu 调用,不再作为当前菜单视觉依据;当前视觉以 40.1 的 6.7 为准。
|
|
471
480
|
- 通知中心 Web 组件动态接入需求:`/Users/gaostudio/.vibe-requirements/UniBase_AE-Enterprise-Admin_通知中心Web组件动态接入需求_v0_1.md`(v0.2 修订:实时连接归 Host,组件只经 `applyRealtimeEvent()` 消费事件)。视觉原型为 `/Users/gaostudio/ae_design/2-数字人事部/产品设计/杜星霖工作目录/过程原型文件/数字人事部_管理端_HRD视角_v0_6.html`。后端投影与实时事件契约见同目录 `UniplatBase_KanbanBackend_通知中心真实数据与实时通知接口需求_v0_1.md` 与按应用身份过滤联合需求。
|
|
472
|
-
- 小型消息通知浮层视觉与交互原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/
|
|
481
|
+
- 小型消息通知浮层视觉与交互原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40_亲亲企服官网.html`;只复用现有通知中心接口与 Host 实时通道,不另写平行后端需求。
|
|
473
482
|
- 通知中心空 `applicationName` 的全应用聚合后端需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_通知中心全应用聚合查询与操作需求_v0_1.md`。
|
|
483
|
+
- 组织员工多选 Web Component 公共能力需求:`/Users/gaostudio/.vibe-requirements/QQXBWebsite_UniBase_组织员工多选WebComponent公共能力需求_v0_1.md`。
|
|
484
|
+
- 多归属组织选择 S.0 / S.1 接入需求:`/Users/gaostudio/.vibe-requirements/UniBase_QQXBWebsite_多归属组织选择S0S1接入需求_v0_1.md`。组件侧已交付,官网需去掉自有遮罩改用 `presentation: 'modal'`、按 `resolveOrganizationEntryDecision()` 路由并自行持久化记住偏好;组织条目的统一社会信用代码是 UniplatBase 的可选扩展,未扩展前搜索只按名称匹配。
|
|
474
485
|
- Application Switcher 对接 UniplatBase 的接口差异与扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_ApplicationSwitcher接口适配需求_v0_1.md`。
|
|
475
|
-
- UniplatBase 已反馈的 Application Switcher 接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/ApplicationSwitcher接口说明
|
|
486
|
+
- UniplatBase 已反馈的 Application Switcher 接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/ApplicationSwitcher接口说明_v0_4.md`;对接时同时核对该项目实际实现和验证脚本,不能只按示例响应猜测。
|
|
476
487
|
- 新需求优先从 `/Users/gaostudio/.vibe-requirements` 查找目标项目同名文档;默认不在该目录创建子目录。
|
|
477
488
|
- 需要确认实时协议时,可以只读核对 Kanban 和 Enterprise Admin 的现有实现;未经用户明确要求,不修改其他项目。
|
|
478
489
|
- 需求与后端现状冲突时应明确指出差异,不静默选择兼容分支,也不要在 SDK 中预埋未经确认的抽象。
|
package/README.md
CHANGED
|
@@ -302,11 +302,14 @@ accountContext.configure({
|
|
|
302
302
|
|
|
303
303
|
### Organization Selector
|
|
304
304
|
|
|
305
|
-
`<uniplat-organization-selector>` 实现 40.1
|
|
305
|
+
`<uniplat-organization-selector>` 实现 40.1 原型 S.1 中登录后存在多个归属时的选择弹窗。Host 传入完整组织列表、默认项、脱敏账户摘要及“记住选择”的初始值;组件负责搜索、类型筛选、“最近进入 / 全部组织”分组与选中态,只在用户点击“进入”后派发选择,不自行修改 Host 登录态。
|
|
306
306
|
|
|
307
307
|
```js
|
|
308
308
|
const selector = document.querySelector('uniplat-organization-selector')
|
|
309
309
|
selector.configure({
|
|
310
|
+
presentation: 'modal',
|
|
311
|
+
displayName: '张伟',
|
|
312
|
+
maskedMobile: '138****8000',
|
|
310
313
|
selectedOrganizationId: 'enterprise-beijing',
|
|
311
314
|
rememberSelection: true,
|
|
312
315
|
organizations: [
|
|
@@ -314,18 +317,39 @@ selector.configure({
|
|
|
314
317
|
id: 'enterprise-beijing',
|
|
315
318
|
name: '北京星河科技有限公司',
|
|
316
319
|
type: 'enterprise',
|
|
317
|
-
avatarText: '星',
|
|
318
320
|
verificationStatus: 'verified',
|
|
319
321
|
role: '超管',
|
|
320
322
|
memberCount: 128,
|
|
321
323
|
lastEnteredLabel: '上次进入 2 小时前',
|
|
324
|
+
unifiedSocialCreditCode: '91110108MA01XY7788',
|
|
322
325
|
},
|
|
323
326
|
{ id: 'team-growth', name: '星河增长项目组', type: 'team', role: '成员' },
|
|
324
327
|
],
|
|
325
328
|
})
|
|
326
329
|
```
|
|
327
330
|
|
|
328
|
-
|
|
331
|
+
`presentation` 默认 `inline`,组件只输出卡片,遮罩由 Host 负责。传 `modal` 时组件自行渲染 S.1 的强制态弹窗:固定深色遮罩、无关闭按钮,遮罩点击与 `Escape` 一律不关闭(`Escape` 也不会冒泡到 Host 的全局关闭逻辑),只能通过“退出登录”离开。遮罩颜色与层级用 `--uniplat-organization-selector-overlay`、`--uniplat-organization-selector-modal-z-index` 定制。
|
|
332
|
+
|
|
333
|
+
`maskedMobile` 只接受脱敏值,传入完整手机号会抛 `TypeError`。`unifiedSocialCreditCode` 只参与搜索匹配,不渲染到 DOM。未传 `selectedOrganizationId` 时默认选中“最近进入”分组的第一项,主按钮显示 `进入「<组织名>」`;列表超过约 5 行时只有列表区滚动,所有归属仍完整渲染。
|
|
334
|
+
|
|
335
|
+
组件派发 `organization-enter-requested`(`{ organizationId, rememberSelection }`)、`organization-create-requested`(`{}`)和 `logout-requested`(`{}`)。事件不含姓名、手机号、组织名称或统一社会信用代码。
|
|
336
|
+
|
|
337
|
+
S.0 的“弹不弹”判定由纯函数 `resolveOrganizationEntryDecision()` 提供,它不读写宿主存储:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
import { resolveOrganizationEntryDecision } from '@uniplat/ai-employee-web-components'
|
|
341
|
+
|
|
342
|
+
const decision = resolveOrganizationEntryDecision({
|
|
343
|
+
organizations,
|
|
344
|
+
rememberedOrganizationId: hostReadRememberedOrganizationId(),
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
// { kind: 'personal' } 无归属:进入个人态并常驻创建 / 加入入口
|
|
348
|
+
// { kind: 'auto-enter', organizationId } 仅 1 个归属,或命中记住选择
|
|
349
|
+
// { kind: 'prompt', selectedOrganizationId } 2 个及以上且未记住:展示选择弹窗
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
记住选择的偏好由 Host 自行持久化并在下次登录时回传;记住的组织已不在归属列表时函数会退回 `prompt`。
|
|
329
353
|
|
|
330
354
|
UniplatBase 的组织投影由 `application.identity/list_available_organizations` 返回。Host 应使用 `team_name` 作为选择器展示名,将 `organization_type` 原样映射为 `enterprise | team`,将企业的 `verification_status` 映射为 `unverified | pending | rejected | verified | expired`,并把 `role/member_count/last_active_at` 转为对应展示字段;团队的 `verification_status=not_applicable` 不传给组件。角色显示文案和最近进入时间仍由 Host 本地化,不应由组件解析后端文本。
|
|
331
355
|
|
|
@@ -397,6 +421,8 @@ selector.addEventListener('selection-confirmed', (event) => {
|
|
|
397
421
|
|
|
398
422
|
账户区和组织菜单中的工作台、选择器、认证状态、选中、创建及退出图标使用 40.1 原型指定的 Tabler Icons 2.47.0 原始 SVG 路径;组织列表左侧使用原型中的企业建筑或团队成员图标。组件将这些原型资源打入独立运行时,不依赖 Host 的图标字体或全局样式。
|
|
399
423
|
|
|
424
|
+
账户胶囊宽度固定:胶囊内姓名超过 6 个字、当前组织名称超过 8 个字会截断为省略号,完整值只放在 `title` 上,长名称不会撑宽胶囊挤压导航左侧内容;展开后的菜单仍显示完整名称。
|
|
425
|
+
|
|
400
426
|
```js
|
|
401
427
|
import {
|
|
402
428
|
createUniplatBaseAccountContextTransport,
|
package/examples/vanilla/main.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createMockApplicationSwitcherTransport,
|
|
3
3
|
createMockOrganizationOnboardingTransport,
|
|
4
4
|
createUniplatBaseOrganizationOnboardingTransport,
|
|
5
|
+
resolveOrganizationEntryDecision,
|
|
5
6
|
} from '@uniplat/ai-employee-web-components'
|
|
6
7
|
|
|
7
8
|
const config = window.AI_EMPLOYEE_EXAMPLE_CONFIG
|
|
@@ -165,13 +166,27 @@ const organizationOnboardingTransport = config.uniplatBaseServiceBaseUrl
|
|
|
165
166
|
})
|
|
166
167
|
: createMockOrganizationOnboardingTransport()
|
|
167
168
|
|
|
169
|
+
const demoSelectorOrganizations = demoOrganizations.map((organization) => ({
|
|
170
|
+
...organization,
|
|
171
|
+
type: organization.type === 'company' ? 'enterprise' : 'team',
|
|
172
|
+
verificationStatus: organization.type === 'company' ? 'verified' : undefined,
|
|
173
|
+
unifiedSocialCreditCode: organization.id === 'company-beijing' ? '91110108MA01XY7788' : undefined,
|
|
174
|
+
lastEnteredLabel: organization.id === 'company-beijing' ? '上次进入 2 小时前' : undefined,
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
// S.0:0 个归属走个人态,1 个归属或命中「记住选择」直接进入,2+ 才展示选择器。
|
|
178
|
+
const demoEntryDecision = resolveOrganizationEntryDecision({
|
|
179
|
+
organizations: demoSelectorOrganizations,
|
|
180
|
+
rememberedOrganizationId: '',
|
|
181
|
+
})
|
|
182
|
+
console.info('[organization-selector] entry decision', demoEntryDecision.kind)
|
|
183
|
+
|
|
168
184
|
organizationSelector.configure({
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
})),
|
|
185
|
+
displayName: '张伟',
|
|
186
|
+
maskedMobile: '138****6688',
|
|
187
|
+
selectedOrganizationId:
|
|
188
|
+
demoEntryDecision.kind === 'prompt' ? demoEntryDecision.selectedOrganizationId : undefined,
|
|
189
|
+
organizations: demoSelectorOrganizations,
|
|
175
190
|
})
|
|
176
191
|
|
|
177
192
|
let demoCurrentOrganizationId = 'company-beijing'
|
|
@@ -223,6 +238,10 @@ organizationSelector.addEventListener('organization-enter-requested', (event) =>
|
|
|
223
238
|
void accountContext.switchOrganization(event.detail.organizationId)
|
|
224
239
|
})
|
|
225
240
|
|
|
241
|
+
organizationSelector.addEventListener('logout-requested', () => {
|
|
242
|
+
console.info('[organization-selector] Host should clear its login state')
|
|
243
|
+
})
|
|
244
|
+
|
|
226
245
|
accountContext.addEventListener('organization-selected', (event) => {
|
|
227
246
|
// Host 决定是否响应切换请求;组件负责调用已配置 transport 并自行重绘。
|
|
228
247
|
void accountContext.switchOrganization(event.detail.organizationId)
|
package/package.json
CHANGED
|
@@ -17,6 +17,10 @@ import type { UniplatUserMenu, UserMenuConfig } from './user-menu'
|
|
|
17
17
|
|
|
18
18
|
const INTERNAL_APPLICATION_SWITCHER_TAG = 'uniplat-internal-application-switcher'
|
|
19
19
|
|
|
20
|
+
// 账户胶囊固定宽度,超出的姓名与组织名在胶囊内截断,完整值只放在 title 里。
|
|
21
|
+
const CAPSULE_DISPLAY_NAME_MAX_CHARS = 6
|
|
22
|
+
const CAPSULE_ORGANIZATION_NAME_MAX_CHARS = 8
|
|
23
|
+
|
|
20
24
|
if (
|
|
21
25
|
typeof customElements !== 'undefined' &&
|
|
22
26
|
!customElements.get(INTERNAL_APPLICATION_SWITCHER_TAG)
|
|
@@ -328,16 +332,19 @@ export class UniplatAccountContext extends LitElement {
|
|
|
328
332
|
display: none;
|
|
329
333
|
}
|
|
330
334
|
|
|
335
|
+
/* 胶囊宽度固定,姓名与组织名再长也不挤压导航左侧内容。 */
|
|
331
336
|
.copy {
|
|
332
337
|
display: flex;
|
|
338
|
+
width: 96px;
|
|
333
339
|
min-width: 0;
|
|
340
|
+
flex: none;
|
|
334
341
|
flex-direction: column;
|
|
335
342
|
line-height: 1.3;
|
|
336
343
|
}
|
|
337
344
|
|
|
338
345
|
.display-name {
|
|
339
346
|
overflow: hidden;
|
|
340
|
-
max-width:
|
|
347
|
+
max-width: 100%;
|
|
341
348
|
font-size: 12.5px;
|
|
342
349
|
font-weight: 700;
|
|
343
350
|
text-overflow: ellipsis;
|
|
@@ -348,7 +355,7 @@ export class UniplatAccountContext extends LitElement {
|
|
|
348
355
|
display: inline-flex;
|
|
349
356
|
align-items: center;
|
|
350
357
|
gap: 4px;
|
|
351
|
-
max-width:
|
|
358
|
+
max-width: 100%;
|
|
352
359
|
margin-top: 1px;
|
|
353
360
|
}
|
|
354
361
|
|
|
@@ -818,7 +825,9 @@ export class UniplatAccountContext extends LitElement {
|
|
|
818
825
|
avatarText: employeeDisplayName
|
|
819
826
|
? organization?.employeeAvatarText || Array.from(employeeDisplayName)[0] || '员'
|
|
820
827
|
: userAvatarText,
|
|
821
|
-
avatarUrl: employeeDisplayName
|
|
828
|
+
avatarUrl: employeeDisplayName
|
|
829
|
+
? organization?.employeeAvatarUrl || userAvatarUrl
|
|
830
|
+
: userAvatarUrl,
|
|
822
831
|
organization,
|
|
823
832
|
organizations,
|
|
824
833
|
organizationCount,
|
|
@@ -910,7 +919,12 @@ export class UniplatAccountContext extends LitElement {
|
|
|
910
919
|
}
|
|
911
920
|
</span>
|
|
912
921
|
<span class="copy">
|
|
913
|
-
<span
|
|
922
|
+
<span
|
|
923
|
+
class="display-name"
|
|
924
|
+
part="display-name"
|
|
925
|
+
title=${capsuleTitle(viewModel.displayName, CAPSULE_DISPLAY_NAME_MAX_CHARS)}
|
|
926
|
+
>${truncateCapsuleText(viewModel.displayName, CAPSULE_DISPLAY_NAME_MAX_CHARS)}</span
|
|
927
|
+
>
|
|
914
928
|
${
|
|
915
929
|
organization
|
|
916
930
|
? this.renderOrganizationLine(organization, viewModel.showVerificationStatus)
|
|
@@ -993,7 +1007,8 @@ export class UniplatAccountContext extends LitElement {
|
|
|
993
1007
|
<span
|
|
994
1008
|
class=${`organization-name${status === 'verified' || status === 'team' ? ' normal' : ''}`}
|
|
995
1009
|
part="organization-name"
|
|
996
|
-
|
|
1010
|
+
title=${capsuleTitle(organization.name, CAPSULE_ORGANIZATION_NAME_MAX_CHARS)}
|
|
1011
|
+
>${truncateCapsuleText(organization.name, CAPSULE_ORGANIZATION_NAME_MAX_CHARS)}</span
|
|
997
1012
|
>
|
|
998
1013
|
${
|
|
999
1014
|
status
|
|
@@ -1472,6 +1487,15 @@ function normalizeOptionalText(value: unknown, maxLength: number): string {
|
|
|
1472
1487
|
return normalized
|
|
1473
1488
|
}
|
|
1474
1489
|
|
|
1490
|
+
function truncateCapsuleText(value: string, maxChars: number): string {
|
|
1491
|
+
const characters = [...value]
|
|
1492
|
+
return characters.length > maxChars ? `${characters.slice(0, maxChars).join('')}…` : value
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function capsuleTitle(value: string, maxChars: number): string | typeof nothing {
|
|
1496
|
+
return [...value].length > maxChars ? value : nothing
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1475
1499
|
function organizationStatus(
|
|
1476
1500
|
organization: ResolvedAccountContextOrganization,
|
|
1477
1501
|
): EnterpriseVerificationStatus | 'team' {
|
|
@@ -134,12 +134,17 @@ export type {
|
|
|
134
134
|
OrganizationOnboardingTransportErrorCode,
|
|
135
135
|
} from './organization-onboarding-transport'
|
|
136
136
|
export type { UniplatBaseOrganizationOnboardingTransportOptions } from './uniplat-base-organization-onboarding-transport'
|
|
137
|
+
export { resolveOrganizationEntryDecision } from './organization-selector'
|
|
137
138
|
export type {
|
|
138
139
|
EnterpriseVerificationStatus,
|
|
140
|
+
OrganizationEntryDecision,
|
|
141
|
+
OrganizationEntryDecisionInput,
|
|
139
142
|
OrganizationSelectorConfig,
|
|
140
143
|
OrganizationSelectorEntry,
|
|
141
144
|
OrganizationSelectorEventMap,
|
|
145
|
+
OrganizationSelectorPresentation,
|
|
142
146
|
OrganizationSelectorType,
|
|
147
|
+
OrganizationSelectorTypeFilter,
|
|
143
148
|
} from './organization-selector'
|
|
144
149
|
export type {
|
|
145
150
|
UserLoginConfig,
|