@lovrabet/rabetbase-cli 2.5.2-beta.3 → 2.5.2-beta.4
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 +1 -1
- package/lib/api/api-doc.js +1 -1
- package/lib/api/fetch-model-list.js +1 -1
- package/lib/api/generate-api-file.js +1 -1
- package/lib/api/generate-sdk-config-file.js +1 -1
- package/lib/auth/auth-server-ui.js +1 -1
- package/lib/auth/auth-server.js +1 -1
- package/lib/auth/constant.js +1 -1
- package/lib/auth/get-cookie.js +1 -1
- package/lib/auth/get-session-user.js +1 -1
- package/lib/auth/is-session-valid.js +1 -1
- package/lib/auth/login-success-html.js +1 -1
- package/lib/auth/logout.js +1 -1
- package/lib/cli-flags.js +1 -1
- package/lib/cli.js +1 -1
- package/lib/commands/api/generate.js +1 -1
- package/lib/commands/api/index.js +1 -1
- package/lib/commands/api/list.js +1 -1
- package/lib/commands/api/pull.js +1 -1
- package/lib/commands/api/shared.js +1 -1
- package/lib/commands/app/index.js +1 -1
- package/lib/commands/app/list.js +1 -1
- package/lib/commands/app/members-list.js +1 -1
- package/lib/commands/app/remote-directory.js +1 -1
- package/lib/commands/app/shared.js +1 -1
- package/lib/commands/app-config/delete.js +1 -1
- package/lib/commands/app-config/get.js +1 -1
- package/lib/commands/app-config/index.js +1 -1
- package/lib/commands/app-config/list.js +1 -1
- package/lib/commands/app-config/set.js +1 -1
- package/lib/commands/app-config/shared.js +1 -1
- package/lib/commands/auth/index.js +1 -1
- package/lib/commands/bff/create.js +1 -1
- package/lib/commands/bff/delete.js +1 -1
- package/lib/commands/bff/detail.js +1 -1
- package/lib/commands/bff/index.js +1 -1
- package/lib/commands/bff/list.js +1 -1
- package/lib/commands/bff/pull.js +1 -1
- package/lib/commands/bff/push.js +1 -1
- package/lib/commands/bff/status.js +1 -1
- package/lib/commands/cli-skill/index.js +1 -1
- package/lib/commands/cli-update.js +1 -1
- package/lib/commands/codegen/index.js +1 -1
- package/lib/commands/codegen/sdk.js +1 -1
- package/lib/commands/codegen/sql.js +1 -1
- package/lib/commands/common/app-registry.js +1 -1
- package/lib/commands/common/app-selector.js +1 -1
- package/lib/commands/common/async-task.js +1 -1
- package/lib/commands/common/dry-run.js +1 -1
- package/lib/commands/common/flags.js +1 -1
- package/lib/commands/common/local-file.js +1 -1
- package/lib/commands/common/validate.js +1 -1
- package/lib/commands/config/delete.js +1 -1
- package/lib/commands/config/get.js +1 -1
- package/lib/commands/config/index.js +1 -1
- package/lib/commands/config/init.js +1 -1
- package/lib/commands/config/list.js +1 -1
- package/lib/commands/config/set.js +1 -1
- package/lib/commands/config/shared.js +1 -1
- package/lib/commands/dataset/business-group-update.js +1 -1
- package/lib/commands/dataset/business-groups.js +1 -1
- package/lib/commands/dataset/capability.js +1 -1
- package/lib/commands/dataset/delete.js +1 -1
- package/lib/commands/dataset/detail.js +1 -1
- package/lib/commands/dataset/extend-update.js +1 -1
- package/lib/commands/dataset/field-restore.js +1 -1
- package/lib/commands/dataset/field-update.js +1 -1
- package/lib/commands/dataset/generate.js +1 -1
- package/lib/commands/dataset/index.js +1 -1
- package/lib/commands/dataset/list.js +1 -1
- package/lib/commands/dataset/operations.js +1 -1
- package/lib/commands/dataset/relation-audit.js +1 -1
- package/lib/commands/dataset/relation-create.js +1 -1
- package/lib/commands/dataset/relation-delete.js +1 -1
- package/lib/commands/dataset/relation-shared.js +1 -1
- package/lib/commands/dataset/relation-update.js +1 -1
- package/lib/commands/dataset/relations.js +1 -1
- package/lib/commands/dataset/rename.js +1 -1
- package/lib/commands/dataset/restore.js +1 -1
- package/lib/commands/dataset/user-deleted-field-list.js +1 -1
- package/lib/commands/db/analysis-batching.js +1 -1
- package/lib/commands/db/analyze-batch-plan.js +1 -1
- package/lib/commands/db/analyze-cancel.js +1 -1
- package/lib/commands/db/analyze-start.js +1 -1
- package/lib/commands/db/analyze-status.js +1 -1
- package/lib/commands/db/create.js +1 -1
- package/lib/commands/db/delete.js +1 -1
- package/lib/commands/db/detail.js +1 -1
- package/lib/commands/db/diff-refresh-start.js +1 -1
- package/lib/commands/db/diff-refresh-status.js +1 -1
- package/lib/commands/db/diff.js +1 -1
- package/lib/commands/db/index.js +1 -1
- package/lib/commands/db/list.js +1 -1
- package/lib/commands/db/shared.js +1 -1
- package/lib/commands/db/table-diff-shared.js +1 -1
- package/lib/commands/db/tables.js +1 -1
- package/lib/commands/db/test.js +1 -1
- package/lib/commands/db/update.js +1 -1
- package/lib/commands/deployment/auto-sync.js +1 -1
- package/lib/commands/deployment/index.js +1 -1
- package/lib/commands/deployment/sync-all.js +1 -1
- package/lib/commands/deployment/sync-jobs.js +1 -1
- package/lib/commands/deployment/sync-status.js +1 -1
- package/lib/commands/deployment/sync.js +1 -1
- package/lib/commands/doctor.js +1 -1
- package/lib/commands/file/index.js +1 -1
- package/lib/commands/instant-api-policy/current.js +1 -1
- package/lib/commands/instant-api-policy/index.js +1 -1
- package/lib/commands/instant-api-policy/init.js +1 -1
- package/lib/commands/instant-api-policy/publish.js +1 -1
- package/lib/commands/instant-api-policy/pull.js +1 -1
- package/lib/commands/instant-api-policy/revision.js +1 -1
- package/lib/commands/instant-api-policy/revisions.js +1 -1
- package/lib/commands/instant-api-policy/rollback.js +1 -1
- package/lib/commands/instant-api-policy/shared.js +1 -1
- package/lib/commands/instant-api-policy/validate.js +1 -1
- package/lib/commands/issue/index.js +1 -1
- package/lib/commands/issue/nudge.js +1 -1
- package/lib/commands/issue/report.js +1 -1
- package/lib/commands/issue/shared.js +1 -1
- package/lib/commands/kb/create.js +1 -1
- package/lib/commands/kb/delete.js +1 -1
- package/lib/commands/kb/detail.js +1 -1
- package/lib/commands/kb/index.js +1 -1
- package/lib/commands/kb/list.js +1 -1
- package/lib/commands/kb/search.js +1 -1
- package/lib/commands/kb/shared.js +1 -1
- package/lib/commands/kb/update.js +1 -1
- package/lib/commands/logs/index.js +1 -1
- package/lib/commands/menu/asset-update.js +1 -1
- package/lib/commands/menu/delete.js +1 -1
- package/lib/commands/menu/external-link-create.js +1 -1
- package/lib/commands/menu/external-link-update.js +1 -1
- package/lib/commands/menu/group-create.js +1 -1
- package/lib/commands/menu/group-update.js +1 -1
- package/lib/commands/menu/index.js +1 -1
- package/lib/commands/menu/list.js +1 -1
- package/lib/commands/menu/move.js +1 -1
- package/lib/commands/menu/regroup-start.js +1 -1
- package/lib/commands/menu/rename.js +1 -1
- package/lib/commands/menu/shared/compare-table.js +1 -1
- package/lib/commands/menu/shared/delete-plan.js +1 -1
- package/lib/commands/menu/shared/facts.js +1 -1
- package/lib/commands/menu/shared/index.js +1 -1
- package/lib/commands/menu/shared/inquirer.js +1 -1
- package/lib/commands/menu/shared/local-pages.js +1 -1
- package/lib/commands/menu/shared/logic.js +1 -1
- package/lib/commands/menu/shared/mutations.js +1 -1
- package/lib/commands/menu/shared/service.js +1 -1
- package/lib/commands/menu/shared/sync-core.js +1 -1
- package/lib/commands/menu/shared/update-core.js +1 -1
- package/lib/commands/menu/shared/valid-url.js +1 -1
- package/lib/commands/menu/sync.js +1 -1
- package/lib/commands/menu/visibility-update.js +1 -1
- package/lib/commands/notification/config-create.js +1 -1
- package/lib/commands/notification/config-delete.js +1 -1
- package/lib/commands/notification/config-list.js +1 -1
- package/lib/commands/notification/config-update.js +1 -1
- package/lib/commands/notification/index.js +1 -1
- package/lib/commands/notification/shared.js +1 -1
- package/lib/commands/ocr/index.js +1 -1
- package/lib/commands/page/create.js +1 -1
- package/lib/commands/page/custom/detail.js +1 -1
- package/lib/commands/page/custom/list.js +1 -1
- package/lib/commands/page/custom/publish.js +1 -1
- package/lib/commands/page/custom/shared.d.ts +0 -8
- package/lib/commands/page/custom/shared.js +1 -1
- package/lib/commands/page/custom/syntax.js +1 -1
- package/lib/commands/page/custom/update.js +1 -1
- package/lib/commands/page/data-list-status.js +1 -1
- package/lib/commands/page/generate-start.js +1 -1
- package/lib/commands/page/generate-status.js +1 -1
- package/lib/commands/page/index.js +1 -1
- package/lib/commands/page/pull.js +1 -1
- package/lib/commands/page/push.js +1 -1
- package/lib/commands/page/relation-audit.js +1 -1
- package/lib/commands/page/restore.js +1 -1
- package/lib/commands/page/shared.js +1 -1
- package/lib/commands/page/sync.js +1 -1
- package/lib/commands/project/create/enhanced-guided-create.js +1 -1
- package/lib/commands/project/create/format-elapsed.js +1 -1
- package/lib/commands/project/create/main.js +1 -1
- package/lib/commands/project/create/materialize-project-template.js +1 -1
- package/lib/commands/project/create/project-name.js +1 -1
- package/lib/commands/project/create/project-template-archive.js +1 -1
- package/lib/commands/project/create/project-template-path.js +1 -1
- package/lib/commands/project/create/use-copy-project-template.js +1 -1
- package/lib/commands/project/create/use-format-code.js +1 -1
- package/lib/commands/project/create/use-install-dependencies.js +1 -1
- package/lib/commands/project/domain-routing-sync.js +1 -1
- package/lib/commands/project/index.js +1 -1
- package/lib/commands/project/upgrade.js +1 -1
- package/lib/commands/registry.js +1 -1
- package/lib/commands/role/delete.js +1 -1
- package/lib/commands/role/detail.js +1 -1
- package/lib/commands/role/index.js +1 -1
- package/lib/commands/role/list.js +1 -1
- package/lib/commands/role/shared.js +1 -1
- package/lib/commands/role/update.js +1 -1
- package/lib/commands/role/user-add.js +1 -1
- package/lib/commands/role/user-remove.js +1 -1
- package/lib/commands/role/user-resolve.js +1 -1
- package/lib/commands/rule/get.js +1 -1
- package/lib/commands/rule/index.js +1 -1
- package/lib/commands/rule/list.js +1 -1
- package/lib/commands/rule/set.js +1 -1
- package/lib/commands/rule/shared.js +1 -1
- package/lib/commands/run/index.d.ts +4 -0
- package/lib/commands/run/index.js +1 -1
- package/lib/commands/schema.js +1 -1
- package/lib/commands/sql/create.js +1 -1
- package/lib/commands/sql/delete.js +1 -1
- package/lib/commands/sql/detail.js +1 -1
- package/lib/commands/sql/exec.js +1 -1
- package/lib/commands/sql/index.js +1 -1
- package/lib/commands/sql/list.js +1 -1
- package/lib/commands/sql/pull.js +1 -1
- package/lib/commands/sql/push.js +1 -1
- package/lib/commands/sql/shared.js +1 -1
- package/lib/commands/sql/status.js +1 -1
- package/lib/commands/sql/validate.js +1 -1
- package/lib/commands/task/index.js +1 -1
- package/lib/commands/task/status.js +1 -1
- package/lib/commands/tenant/index.js +1 -1
- package/lib/commands/tenant/members-list.js +1 -1
- package/lib/commands/tenant/shared.js +1 -1
- package/lib/commands/user-account/dingding-sandbox-bind.js +1 -1
- package/lib/commands/user-account/index.js +1 -1
- package/lib/commands/workspace/add.js +1 -1
- package/lib/commands/workspace/index.js +1 -1
- package/lib/commands/workspace/remove.js +1 -1
- package/lib/config/domain-config.js +1 -1
- package/lib/config/project-domain-routing.d.ts +5 -1
- package/lib/config/project-domain-routing.js +1 -1
- package/lib/config/schema.js +1 -1
- package/lib/constant/cdn.js +1 -1
- package/lib/constant/cli.js +1 -1
- package/lib/constant/defaults.js +1 -1
- package/lib/constant/domain.js +1 -1
- package/lib/constant/env.js +1 -1
- package/lib/constant/output.js +1 -1
- package/lib/constant/paths.js +1 -1
- package/lib/constant/region.js +1 -1
- package/lib/constant/risk.js +1 -1
- package/lib/constant/routing-profile.js +1 -1
- package/lib/context/app-resolver.js +1 -1
- package/lib/context/auth-resolver.js +1 -1
- package/lib/context/config-loader.js +1 -1
- package/lib/context.js +1 -1
- package/lib/core/alias-resolver.js +1 -1
- package/lib/core/api-client.d.ts +0 -26
- package/lib/core/api-client.js +1 -1
- package/lib/core/bff/config.js +1 -1
- package/lib/core/bff/file-system.js +1 -1
- package/lib/core/bff/hash.js +1 -1
- package/lib/core/bff/lock.js +1 -1
- package/lib/core/bff/utils.js +1 -1
- package/lib/core/db-resolver.js +1 -1
- package/lib/core/instant-api-policy/config.js +1 -1
- package/lib/core/kb-search-client.js +1 -1
- package/lib/core/page/file-system.d.ts +43 -0
- package/lib/core/page/file-system.js +1 -1
- package/lib/core/page/hash.js +1 -1
- package/lib/core/page/lock.js +1 -1
- package/lib/core/sql-index-auditor.js +1 -1
- package/lib/core/sql-sync/config.js +1 -1
- package/lib/core/sql-sync/file-system.js +1 -1
- package/lib/core/sql-sync/hash.js +1 -1
- package/lib/core/sql-sync/lock.js +1 -1
- package/lib/core/sql-sync/utils.js +1 -1
- package/lib/core/sql-validator.js +1 -1
- package/lib/errors.js +1 -1
- package/lib/framework/build-all-flags.js +1 -1
- package/lib/framework/error-output.js +1 -1
- package/lib/framework/explicit-yes.js +1 -1
- package/lib/framework/flags.js +1 -1
- package/lib/framework/help.js +1 -1
- package/lib/framework/index.js +1 -1
- package/lib/framework/output.js +1 -1
- package/lib/framework/response.js +1 -1
- package/lib/framework/runner-alias.js +1 -1
- package/lib/framework/runner.js +1 -1
- package/lib/framework/schema-export.js +1 -1
- package/lib/framework/supported-flags.js +1 -1
- package/lib/framework/types.js +1 -1
- package/lib/generated/build-info.d.ts +4 -4
- package/lib/generated/build-info.js +1 -1
- package/lib/generated/official-routing.js +1 -1
- package/lib/generated/routing-contract.js +1 -1
- package/lib/help.js +1 -1
- package/lib/postinstall.js +1 -1
- package/lib/runtime/confirmation.js +1 -1
- package/lib/runtime/event.js +1 -1
- package/lib/runtime/index.js +1 -1
- package/lib/runtime/queue.js +1 -1
- package/lib/runtime/resolve.js +1 -1
- package/lib/skills/builtin-skill.js +1 -1
- package/lib/skills/main.js +1 -1
- package/lib/skills/npx-skills-add.js +1 -1
- package/lib/skills/skill-presence.js +1 -1
- package/lib/telemetry/cli-command-trace.js +1 -1
- package/lib/telemetry/cli-help-trace.js +1 -1
- package/lib/telemetry/ensure-cli-otel-config.js +1 -1
- package/lib/telemetry/register-otel-exit-shutdown.js +1 -1
- package/lib/telemetry/send-cli-trace-log.js +1 -1
- package/lib/telemetry/send-platform-issue-collect-log.js +1 -1
- package/lib/utils/ai_config.js +1 -1
- package/lib/utils/apply-jq-filter.js +1 -1
- package/lib/utils/cdn-config.js +1 -1
- package/lib/utils/check-sdk-version.js +1 -1
- package/lib/utils/cli-version-check.js +1 -1
- package/lib/utils/cli-version-policy.js +1 -1
- package/lib/utils/config.js +1 -1
- package/lib/utils/entity-with-id.js +1 -1
- package/lib/utils/file-utils.js +1 -1
- package/lib/utils/guides-cdn.js +1 -1
- package/lib/utils/http-client.js +1 -1
- package/lib/utils/is-non-interactive.js +1 -1
- package/lib/utils/logger.js +1 -1
- package/lib/utils/params.js +1 -1
- package/lib/utils/platform.js +1 -1
- package/lib/utils/sleep.js +1 -1
- package/lib/utils/template-replacer.js +1 -1
- package/lib/utils/update-notice.js +1 -1
- package/lib/utils/version.js +1 -1
- package/lib/utils/with-active-cookie.js +1 -1
- package/lib/utils/write-cli-side-channel.js +1 -1
- package/package.json +2 -1
- package/skills/rabetbase/SKILL.md +5 -5
- package/skills/rabetbase/guides/custom-page-workflow.md +35 -17
- package/skills/rabetbase/guides/page-development-workflow.md +1 -1
- package/skills/rabetbase/knowledge/components.md +292 -0
- package/skills/rabetbase/knowledge/custom-page/generation-standards.md +74 -4
- package/skills/rabetbase/knowledge/custom-page/page-templates.md +68 -0
- package/skills/rabetbase/references/rabetbase-page-create.md +21 -9
- package/skills/rabetbase/references/rabetbase-page-custom-detail.md +1 -1
- package/skills/rabetbase/references/rabetbase-page-custom-update.md +13 -9
- package/skills/rabetbase/references/rabetbase-project-create.md +5 -2
- package/skills/rabetbase/references/rabetbase-project-domain-routing-sync.md +2 -2
- package/skills/rabetbase/references/rabetbase-run.md +1 -0
- package/skills/rabetbase.manifest.json +5 -4
- package/templates/README.md +10 -4
- package/templates/custom-pages/blank.json +5 -0
- package/templates/custom-pages/dashboard.json +5 -0
- package/templates/custom-pages/onepage.json +5 -0
- package/templates/generate-api/api.ts.tpl +1 -1
- package/lib/commands/page/custom/template.d.ts +0 -8
- package/lib/commands/page/custom/template.js +0 -1
- package/skills/rabetbase/knowledge/custom-page/components.md +0 -225
|
@@ -34,6 +34,7 @@ rabetbase run build -- --mode production
|
|
|
34
34
|
## 特殊行为
|
|
35
35
|
|
|
36
36
|
- **自动检测包管理器**:按 lock 文件判断 bun / pnpm / yarn / npm
|
|
37
|
+
- **公开 Domain 快照**:在 Rabetbase 项目中执行 `start`、`dev`、`build` 或 `preview` 前,根据当前有效配置刷新 `rabetbase.domain-routing.json`;内容未变化时不重写、不输出提示
|
|
37
38
|
- **`start` / `dev` 脚本**:执行前自动检查 CLI 和 SDK 版本,有更新时打印升级警告
|
|
38
39
|
- **`preview --watch`**:并行启动 `vite build --watch` 和 `vite preview`,适合开发时预览生产构建
|
|
39
40
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "rabetbase",
|
|
4
4
|
"packageName": "@lovrabet/rabetbase-cli",
|
|
5
|
-
"packageVersion": "2.5.2-beta.
|
|
6
|
-
"skillVersion": "2.5.2-beta.
|
|
5
|
+
"packageVersion": "2.5.2-beta.4",
|
|
6
|
+
"skillVersion": "2.5.2-beta.4",
|
|
7
7
|
"files": [
|
|
8
8
|
"SKILL.md",
|
|
9
9
|
"guides/backend-function.md",
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
"guides/sql-mybatis.md",
|
|
23
23
|
"guides/troubleshooting.md",
|
|
24
24
|
"guides/typescript-sdk.md",
|
|
25
|
-
"knowledge/
|
|
25
|
+
"knowledge/components.md",
|
|
26
26
|
"knowledge/custom-page/generation-standards.md",
|
|
27
|
+
"knowledge/custom-page/page-templates.md",
|
|
27
28
|
"knowledge/page-schema/LrSmartCreate.md",
|
|
28
29
|
"knowledge/page-schema/LrSmartDetail.md",
|
|
29
30
|
"knowledge/page-schema/LrSmartFilter.md",
|
|
@@ -139,5 +140,5 @@
|
|
|
139
140
|
"references/rabetbase-user-account.md",
|
|
140
141
|
"references/rabetbase-workspace.md"
|
|
141
142
|
],
|
|
142
|
-
"digest": "
|
|
143
|
+
"digest": "dffe4df9136a202fd8cb263d8c4c6e4cb4f0f50b18b59e8745eb2fc282a50f58"
|
|
143
144
|
}
|
package/templates/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
```
|
|
8
8
|
templates/
|
|
9
9
|
├── README.md # 本说明文档
|
|
10
|
+
├── custom-pages/ # React 自定义页面内置模板
|
|
10
11
|
├── generate-api/ # API 生成模板
|
|
11
12
|
└── projects/ # 项目模板,用于 `rabetbase project create` 命令
|
|
12
13
|
└── sub-app-react-demo/ # React 微前端项目模板真源
|
|
@@ -16,6 +17,10 @@ templates/
|
|
|
16
17
|
|
|
17
18
|
`generate-api/` 目录包含用于生成 API 配置的模板。
|
|
18
19
|
|
|
20
|
+
## 自定义页面模板
|
|
21
|
+
|
|
22
|
+
`custom-pages/` 目录包含 React 自定义页面内置模板。模板选型、内容边界和使用方式统一见 [`page-templates.md`](../skills/rabetbase/knowledge/custom-page/page-templates.md);命令参数见 [`page create` 命令说明](../skills/rabetbase/references/rabetbase-page-create.md)。
|
|
23
|
+
|
|
19
24
|
## 项目模板
|
|
20
25
|
|
|
21
26
|
### 模板真源与 CDN 资产
|
|
@@ -67,10 +72,11 @@ npm run sync:cdn
|
|
|
67
72
|
|
|
68
73
|
添加新模板时:
|
|
69
74
|
|
|
70
|
-
1.
|
|
71
|
-
2.
|
|
72
|
-
3.
|
|
73
|
-
4.
|
|
75
|
+
1. **自定义页面模板**:放置在 `templates/custom-pages/` 中;模板标识使用大写,JSON 文件名使用对应的小写形式,并同步命令枚举、测试和 [`page-templates.md`](../skills/rabetbase/knowledge/custom-page/page-templates.md)
|
|
76
|
+
2. **项目模板**:放置在 `templates/projects/` 中
|
|
77
|
+
3. **文件扩展名**:需要处理的项目模板文件使用 `.tpl`
|
|
78
|
+
4. **本地测试**:提交前使用新模板生成页面或项目进行测试
|
|
79
|
+
5. **更新此 README**:记录任何新功能
|
|
74
80
|
|
|
75
81
|
## 注意事项
|
|
76
82
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"src/app/index.css": ".page-container {\n padding: 12px;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n}\n",
|
|
3
|
+
"src/app/index.jsx": "import { Card, ConfigProvider, theme as antdTheme } from \"antd\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\nconst App = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useAppTheme();\n\n $i18n.addLocale(locales);\n\n return (\n <ConfigProvider\n theme={{\n algorithm:\n theme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n cssVar: { key: \"blank-theme\" },\n ...(theme?.skinColor\n ? { token: { colorPrimary: theme.skinColor } }\n : {}),\n }}\n >\n <main className=\"blank-theme page-container\">\n <Card>{$i18n.t(\"pageDescription\")}</Card>\n </main>\n </ConfigProvider>\n );\n};\n\nexport default App;\n",
|
|
4
|
+
"src/locales/index.js": "export const locales = {\n 'zh-CN': {\n pageDescription: '这是一个基础页面模板,你可以在此基础上进行开发',\n },\n 'en-US': {\n pageDescription: 'This is a basic page template, you can develop based on it',\n },\n 'ja-JP': {\n pageDescription: 'これは基本ページテンプレートです。あなたは、これに基づいて開発できます',\n },\n 'tr-TR': {\n pageDescription: 'Bu bir temel sayfa şablonudur. Bu temaplana dayanarak geliştirilebilir',\n },\n 'id-ID': {\n pageDescription: 'Ini adalah sebuah halaman dasar. Anda dapat mengembangkan ini',\n },\n};\n"
|
|
5
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"src/app/index.css": ".dashboard-container {\n padding: 12px;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n}\n.dashboard-toolbar {\n display: flex;\n justify-content: flex-end;\n margin-bottom: 20px;\n}\n.dashboard-metrics__list,\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid {\n display: grid;\n gap: 12px;\n}\n.dashboard-card__content,\n.dashboard-metrics__list,\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid,\n.dashboard-chart-card,\n.dashboard-chart-card .ant-card-body,\n.dashboard-chart {\n min-width: 0;\n}\n.dashboard-metrics,\n.dashboard-section {\n margin-bottom: 12px;\n}\n.dashboard-metrics__list {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.dashboard-card {\n min-width: 0;\n padding: 12px;\n border-radius: 8px;\n background: var(--ant-color-bg-container);\n box-shadow: var(--ant-box-shadow-tertiary);\n}\n.dashboard-card__title {\n display: block;\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 600;\n}\n.dashboard-card__content {\n margin-top: 12px;\n}\n.dashboard-metric {\n height: 100%;\n border-radius: 8px;\n transition:\n box-shadow 0.3s ease,\n transform 0.3s ease;\n}\n.dashboard-metric:hover {\n box-shadow: var(--ant-box-shadow-secondary);\n cursor: pointer;\n transform: translateY(-2px);\n}\n.dashboard-metric__body {\n display: flex;\n min-height: 118px;\n flex-direction: column;\n}\n.dashboard-metric__statistic {\n text-align: left;\n}\n.dashboard-metric .ant-statistic-content {\n color: var(--ant-color-text-heading);\n font-weight: 700;\n}\n.dashboard-metric .ant-statistic-content-suffix {\n color: var(--ant-color-text);\n font-size: 14px;\n}\n.dashboard-metric__trend {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: auto;\n color: var(--ant-color-text-secondary);\n font-size: 12px;\n line-height: 1.4;\n}\n.dashboard-metric__trend-value {\n color: var(--ant-color-error);\n font-weight: 700;\n}\n.dashboard-chart-card {\n display: flex;\n height: 100%;\n flex-direction: column;\n}\n.dashboard-chart-card .ant-card-body {\n flex: 1;\n}\n.dashboard-chart-card__header {\n padding: 0 12px;\n border-bottom: 2px solid var(--ant-color-primary);\n}\n.dashboard-chart-card__title {\n color: var(--ant-color-text);\n font-weight: 600;\n}\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.dashboard-chart {\n width: 100%;\n height: 220px;\n}\n.dashboard-chart--trend {\n height: 244px;\n}\n@media (max-width: 960px) {\n .dashboard-metrics__list {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n@media (max-width: 600px) {\n .dashboard-toolbar .ant-picker {\n width: 100%;\n }\n .dashboard-metrics__list,\n .dashboard-grid,\n .dashboard-chart-grid,\n .dashboard-split {\n grid-template-columns: 1fr;\n }\n}\n",
|
|
3
|
+
"src/app/index.jsx": "import { useEffect, useRef, useState } from \"react\";\nimport {\n Card,\n ConfigProvider,\n DatePicker,\n Statistic,\n Tag,\n theme as antdTheme,\n} from \"antd\";\nimport * as echarts from \"echarts/core\";\nimport { BarChart, FunnelChart, LineChart, PieChart } from \"echarts/charts\";\nimport {\n GridComponent,\n LegendComponent,\n TooltipComponent,\n} from \"echarts/components\";\nimport { CanvasRenderer } from \"echarts/renderers\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\necharts.use([\n BarChart,\n FunnelChart,\n LineChart,\n PieChart,\n GridComponent,\n LegendComponent,\n TooltipComponent,\n CanvasRenderer,\n]);\n\nconst DETAIL_PAGE_CODE = \"/replace-with-dashboard-detail-page-code\";\nconst metrics = [\n {\n key: \"dealAmount\",\n value: 128.6,\n precision: 1,\n suffix: \"unit.tenThousand\",\n },\n {\n key: \"paymentAmount\",\n value: 96.2,\n precision: 1,\n suffix: \"unit.tenThousand\",\n },\n { key: \"newOpportunities\", value: 36, suffix: \"unit.count\" },\n { key: \"newCustomers\", value: 18, suffix: \"unit.count\" },\n];\nconst documents = [\n [\"sales.opportunity\", 86],\n [\"sales.quotation\", 54],\n [\"sales.order\", 31],\n [\"sales.payment\", 22],\n];\nconst channels = [\n [\"channel.online\", 42],\n [\"channel.referral\", 28],\n [\"channel.event\", 18],\n [\"channel.other\", 12],\n];\nconst customerLevels = [\n [\"customer.a\", 28],\n [\"customer.b\", 35],\n [\"customer.c\", 24],\n [\"customer.d\", 13],\n];\nconst opportunityStages = [\n [\"stage.contact\", 34],\n [\"stage.requirement\", 29],\n [\"stage.solution\", 22],\n [\"stage.contract\", 15],\n];\nconst revenueTrend = [42, 54, 48, 68, 73, 65, 82, 91, 86, 102, 109, 128];\nconst departments = [\n [\"department.east\", 92],\n [\"department.south\", 86],\n [\"department.north\", 79],\n [\"department.west\", 72],\n];\n\nconst getThemeConfig = (appTheme) => ({\n algorithm:\n appTheme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n components: {\n Card: { bodyPadding: 12 },\n Statistic: { contentFontSize: 24, titleFontSize: 14 },\n },\n cssVar: { key: \"dashboard-theme\" },\n token: {\n borderRadius: 8,\n ...(appTheme?.skinColor ? { colorPrimary: appTheme.skinColor } : {}),\n },\n});\n\nconst Section = ({ children, className = \"\", title }) => (\n <section className={[\"dashboard-card\", className].filter(Boolean).join(\" \")}>\n <span className=\"dashboard-card__title\">{title}</span>\n <div className=\"dashboard-card__content\">{children}</div>\n </section>\n);\n\nconst ChartSection = ({ children, title }) => (\n <Card\n className=\"dashboard-chart-card\"\n classNames={{\n header: \"dashboard-chart-card__header\",\n title: \"dashboard-chart-card__title\",\n }}\n title={title}\n >\n {children}\n </Card>\n);\n\nconst createPieOption = (data, $i18n, colors, token) => ({\n color: colors,\n legend: {\n bottom: 0,\n left: \"center\",\n textStyle: { color: token.colorTextSecondary },\n },\n series: [\n {\n data: data.map(([key, value]) => ({ name: $i18n.t(key), value })),\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowColor: token.colorBgMask,\n shadowOffsetX: 0,\n },\n },\n radius: \"50%\",\n type: \"pie\",\n },\n ],\n tooltip: { trigger: \"item\" },\n});\n\nconst createBarOption = (data, $i18n, colors, token, horizontal = false) => {\n const labels = data.map(([key]) => $i18n.t(key));\n const categoryAxis = {\n axisLabel: { color: token.colorTextSecondary },\n axisLine: { lineStyle: { color: token.colorBorderSecondary } },\n data: labels,\n type: \"category\",\n };\n const valueAxis = {\n axisLabel: { color: token.colorTextSecondary },\n splitLine: { lineStyle: { color: token.colorBorderSecondary } },\n type: \"value\",\n };\n\n return {\n grid: horizontal\n ? { bottom: 16, containLabel: true, left: 16, right: 16, top: 20 }\n : { bottom: 52, containLabel: true, left: 16, right: 16, top: 20 },\n series: [\n {\n barMaxWidth: 18,\n data: data.map(([, value], index) => ({\n itemStyle: { color: colors[index % colors.length] },\n value,\n })),\n type: \"bar\",\n },\n ],\n tooltip: { trigger: \"axis\" },\n xAxis: horizontal ? valueAxis : categoryAxis,\n yAxis: horizontal ? categoryAxis : valueAxis,\n };\n};\n\nconst Chart = ({ className = \"\", onItemClick, option }) => {\n const elementRef = useRef(null);\n const chartRef = useRef(null);\n\n useEffect(() => {\n const chart = echarts.init(elementRef.current);\n const resize = () => chart.resize();\n\n chartRef.current = chart;\n window.addEventListener(\"resize\", resize);\n\n return () => {\n window.removeEventListener(\"resize\", resize);\n chart.dispose();\n chartRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n chartRef.current?.setOption(option, { notMerge: true });\n }, [option]);\n\n useEffect(() => {\n const chart = chartRef.current;\n\n if (!chart || !onItemClick) return;\n\n chart.on(\"click\", onItemClick);\n return () => {\n if (!chart.isDisposed()) chart.off(\"click\", onItemClick);\n };\n }, [onItemClick]);\n\n return (\n <div\n className={[\"dashboard-chart\", className].filter(Boolean).join(\" \")}\n ref={elementRef}\n />\n );\n};\n\nconst DashboardContent = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const { token } = antdTheme.useToken();\n const [filter, setFilter] = useState({ factor: 1, range: [] });\n const chartColors = [\n token.colorPrimary,\n token.colorInfo,\n token.colorSuccess,\n token.colorWarning,\n ];\n\n const scale = (data) =>\n data.map(([key, value]) => [key, Math.round(value * filter.factor)]);\n const filteredRevenueTrend = revenueTrend.map((value) =>\n Math.round(value * filter.factor),\n );\n\n $i18n.addLocale(locales);\n\n const openDetail = ({ name, value }) =>\n navigate(DETAIL_PAGE_CODE, {\n onePage: true,\n state: {\n dimension: name,\n filterRange: filter.range,\n from: location.pathname + location.search,\n value,\n },\n });\n\n const changeRange = (dates) => {\n if (!dates) {\n setFilter({ factor: 1, range: [] });\n return;\n }\n\n const days = dates[1].diff(dates[0], \"day\") + 1;\n setFilter({\n factor: Math.max(0.75, Math.min(1.25, days / 30)),\n range: dates.map((date) => date.format(\"YYYY-MM-DD\")),\n });\n };\n\n return (\n <main className=\"dashboard-container\">\n <div className=\"dashboard-toolbar\">\n <DatePicker.RangePicker\n allowClear={false}\n onChange={changeRange}\n placeholder={[\n $i18n.t(\"date.startPlaceholder\"),\n $i18n.t(\"date.endPlaceholder\"),\n ]}\n />\n </div>\n\n <Section className=\"dashboard-metrics\" title={$i18n.t(\"metrics.title\")}>\n <div className=\"dashboard-metrics__list\">\n {metrics.map((metric) => {\n const value = Number(\n (metric.value * filter.factor).toFixed(metric.precision ?? 0),\n );\n const openMetric = () =>\n openDetail({\n name: $i18n.t(\"metrics.\" + metric.key),\n value,\n });\n\n return (\n <Card\n className=\"dashboard-metric\"\n classNames={{ body: \"dashboard-metric__body\" }}\n key={metric.key}\n onClick={openMetric}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") openMetric();\n }}\n role=\"button\"\n tabIndex={0}\n title={$i18n.t(\"detail.pageCodeHint\")}\n >\n <Statistic\n className=\"dashboard-metric__statistic\"\n precision={metric.precision}\n suffix={$i18n.t(metric.suffix)}\n title={$i18n.t(\"metrics.\" + metric.key)}\n value={value}\n />\n <span className=\"dashboard-metric__trend\">\n {$i18n.t(\"monthOnMonth\")}\n <span className=\"dashboard-metric__trend-value\">\n +{(8 + filter.factor * 4.8).toFixed(1)}%\n </span>\n </span>\n </Card>\n );\n })}\n </div>\n </Section>\n\n <Section\n className=\"dashboard-section\"\n title={$i18n.t(\"salesConversion\")}\n >\n <div className=\"dashboard-chart-grid\">\n <ChartSection title={$i18n.t(\"salesDocuments\")}>\n <Chart\n onItemClick={openDetail}\n option={{\n color: chartColors,\n series: [\n {\n data: scale(documents).map(([key, value]) => ({\n name: $i18n.t(key),\n value,\n })),\n label: { formatter: \"{b} {c}\", position: \"inside\" },\n type: \"funnel\",\n },\n ],\n tooltip: { trigger: \"item\" },\n }}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"salesChannels\")}>\n <Chart\n onItemClick={openDetail}\n option={createBarOption(\n scale(channels),\n $i18n,\n chartColors,\n token,\n )}\n />\n </ChartSection>\n </div>\n </Section>\n\n <Section\n className=\"dashboard-section\"\n title={$i18n.t(\"customerOpportunityAnalysis\")}\n >\n <div className=\"dashboard-split\">\n <ChartSection title={$i18n.t(\"customerLevel\")}>\n <Chart\n onItemClick={openDetail}\n option={createPieOption(\n scale(customerLevels),\n $i18n,\n chartColors,\n token,\n )}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"opportunityStage\")}>\n <Chart\n onItemClick={openDetail}\n option={createBarOption(\n scale(opportunityStages),\n $i18n,\n chartColors,\n token,\n true,\n )}\n />\n </ChartSection>\n </div>\n </Section>\n\n <section className=\"dashboard-grid\">\n <ChartSection title={$i18n.t(\"revenueDealTrendAnalysis\")}>\n <Chart\n className=\"dashboard-chart--trend\"\n onItemClick={openDetail}\n option={{\n color: [token.colorPrimary],\n grid: { bottom: 28, left: 40, right: 16, top: 20 },\n tooltip: { trigger: \"axis\" },\n xAxis: {\n axisLabel: { color: token.colorTextSecondary },\n axisLine: {\n lineStyle: { color: token.colorBorderSecondary },\n },\n boundaryGap: false,\n data: filteredRevenueTrend.map(\n (_, index) => index + 1 + $i18n.t(\"month\"),\n ),\n type: \"category\",\n },\n yAxis: {\n axisLabel: { color: token.colorTextSecondary },\n splitLine: {\n lineStyle: { color: token.colorBorderSecondary },\n },\n type: \"value\",\n },\n series: [\n {\n areaStyle: { opacity: 0.16 },\n data: filteredRevenueTrend,\n lineStyle: { width: 3 },\n smooth: true,\n type: \"line\",\n },\n ],\n }}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"departmentEfficiency\")}>\n <Chart\n onItemClick={openDetail}\n option={createPieOption(\n scale(departments),\n $i18n,\n chartColors,\n token,\n )}\n />\n <Tag color={token.colorPrimary}>{$i18n.t(\"efficiencyHint\")}</Tag>\n </ChartSection>\n </section>\n </main>\n );\n};\n\nconst Dashboard = () => {\n const theme = useAppTheme();\n\n return (\n <ConfigProvider theme={getThemeConfig(theme)}>\n <DashboardContent />\n </ConfigProvider>\n );\n};\n\nexport default Dashboard;\n",
|
|
4
|
+
"src/locales/index.js": "export const locales = {\n \"zh-CN\": {\n monthOnMonth: \"环比\",\n month: \"月\",\n \"date.startPlaceholder\": \"开始日期\",\n \"date.endPlaceholder\": \"结束日期\",\n \"unit.tenThousand\": \"万\",\n \"unit.count\": \"个\",\n \"detail.pageCodeHint\": \"示例页面地址,请替换为实际明细页面 code\",\n \"metrics.title\": \"核心指标\",\n \"metrics.dealAmount\": \"成交金额\",\n \"metrics.paymentAmount\": \"回款金额\",\n \"metrics.newOpportunities\": \"新增商机\",\n \"metrics.newCustomers\": \"新增客户\",\n salesConversion: \"销售转化\",\n salesDocuments: \"销售单据\",\n salesChannels: \"销售渠道\",\n \"sales.opportunity\": \"销售机会\",\n \"sales.quotation\": \"报价单\",\n \"sales.order\": \"订单\",\n \"sales.payment\": \"回款\",\n \"channel.online\": \"线上获客\",\n \"channel.referral\": \"客户转介绍\",\n \"channel.event\": \"市场活动\",\n \"channel.other\": \"其他渠道\",\n customerOpportunityAnalysis: \"客户与商机分析\",\n customerLevel: \"客户等级\",\n opportunityStage: \"商机阶段\",\n \"customer.a\": \"A 级客户\",\n \"customer.b\": \"B 级客户\",\n \"customer.c\": \"C 级客户\",\n \"customer.d\": \"D 级客户\",\n \"stage.contact\": \"初步接触\",\n \"stage.requirement\": \"需求确认\",\n \"stage.solution\": \"方案制定\",\n \"stage.contract\": \"合同签约\",\n revenueDealTrendAnalysis: \"营收与成交趋势分析\",\n departmentEfficiency: \"部门效能\",\n \"department.east\": \"华东销售部\",\n \"department.south\": \"华南销售部\",\n \"department.north\": \"华北销售部\",\n \"department.west\": \"华西销售部\",\n efficiencyHint: \"整体转化效率较上月提升 8.6%\",\n },\n \"en-US\": {\n monthOnMonth: \"MoM\",\n month: \"M\",\n \"date.startPlaceholder\": \"Start date\",\n \"date.endPlaceholder\": \"End date\",\n \"unit.tenThousand\": \"10k\",\n \"unit.count\": \"items\",\n \"detail.pageCodeHint\": \"Replace the example path with the detail page code\",\n \"metrics.title\": \"Key metrics\",\n \"metrics.dealAmount\": \"Deal amount\",\n \"metrics.paymentAmount\": \"Payment amount\",\n \"metrics.newOpportunities\": \"New opportunities\",\n \"metrics.newCustomers\": \"New customers\",\n salesConversion: \"Sales conversion\",\n salesDocuments: \"Sales documents\",\n salesChannels: \"Sales channels\",\n \"sales.opportunity\": \"Opportunity\",\n \"sales.quotation\": \"Quotation\",\n \"sales.order\": \"Order\",\n \"sales.payment\": \"Payment\",\n \"channel.online\": \"Online\",\n \"channel.referral\": \"Referral\",\n \"channel.event\": \"Event\",\n \"channel.other\": \"Other\",\n customerOpportunityAnalysis: \"Customer and opportunity analysis\",\n customerLevel: \"Customer level\",\n opportunityStage: \"Opportunity stage\",\n \"customer.a\": \"Level A\",\n \"customer.b\": \"Level B\",\n \"customer.c\": \"Level C\",\n \"customer.d\": \"Level D\",\n \"stage.contact\": \"Initial contact\",\n \"stage.requirement\": \"Requirement\",\n \"stage.solution\": \"Solution\",\n \"stage.contract\": \"Contract\",\n revenueDealTrendAnalysis: \"Revenue and deal trend analysis\",\n departmentEfficiency: \"Department efficiency\",\n \"department.east\": \"East sales\",\n \"department.south\": \"South sales\",\n \"department.north\": \"North sales\",\n \"department.west\": \"West sales\",\n efficiencyHint: \"Overall conversion efficiency is up 8.6% month over month\",\n },\n};\n"
|
|
5
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"src/app/index.css": ".crm-page {\n display: flex;\n gap: 12px;\n min-width: 1180px;\n align-items: start;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n font-size: 14px;\n padding: 12px;\n}\n.crm-page *,\n.crm-page *::before,\n.crm-page *::after {\n box-sizing: border-box;\n}\n.crm-panel-actions,\n.crm-record-card__top,\n.crm-record-card__bottom,\n.crm-note-card__top {\n display: flex;\n align-items: center;\n}\n.crm-column {\n display: grid;\n flex: 1 1 270px;\n gap: 12px;\n min-width: 0;\n}\n.crm-column--main {\n flex: 2 1 550px;\n}\n.crm-panel.ant-card {\n overflow: hidden;\n border-radius: 8px;\n background: var(--ant-color-bg-container);\n box-shadow: var(--ant-box-shadow-tertiary);\n}\n.crm-panel .ant-card-head {\n min-height: 40px;\n padding: 0 12px;\n border-bottom: 0;\n}\n.crm-panel .ant-card-head-title {\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 500;\n}\n.crm-panel .ant-card-extra {\n padding: 12px 0;\n}\n.crm-panel .ant-card-body {\n padding: 12px;\n}\n.crm-customer-card__select {\n width: 100%;\n height: 54px;\n}\n.crm-customer-card__select .ant-select-selector {\n height: 54px !important;\n padding: 0 12px !important;\n border-color: var(--ant-color-border) !important;\n border-radius: 8px !important;\n}\n.crm-customer-card__select .ant-select-selection-item {\n overflow: hidden;\n color: var(--ant-color-text-heading);\n font-size: 20px;\n font-weight: 700;\n line-height: 52px !important;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.crm-customer-card__select .ant-select-arrow {\n top: 0;\n display: flex;\n height: 54px;\n margin-top: 0;\n align-items: center;\n color: var(--ant-color-text-secondary);\n font-size: 16px;\n}\n.crm-customer-card__contact {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 12px;\n margin-top: 12px;\n}\n.crm-customer-card__contact span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.crm-tag-row {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n}\n.crm-tag-row .ant-tag,\n.crm-record-card .ant-tag,\n.crm-note-card .ant-tag {\n margin-inline-end: 0;\n font-size: 13px;\n}\n.crm-metric-panel > .ant-card-body {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 14px;\n}\n.crm-metric-card {\n padding: 12px;\n border: 1px solid var(--ant-color-border-secondary);\n border-radius: 12px;\n}\n.crm-metric-card .ant-typography {\n color: var(--ant-color-text);\n}\n.crm-metric-card__value {\n margin-top: 12px;\n color: var(--ant-color-text-heading);\n font-size: 31px;\n font-weight: 700;\n line-height: 1;\n}\n.crm-metric-card__unit {\n margin-left: 4px;\n font-size: 15px;\n font-weight: 500;\n}\n.crm-info-list {\n display: grid;\n gap: 12px;\n margin: 0;\n}\n.crm-info-list > div {\n display: grid;\n gap: 7px;\n}\n.crm-info-list dt,\n.crm-info-list dd {\n margin: 0;\n}\n.crm-info-list dt {\n color: var(--ant-color-text-secondary);\n}\n.crm-info-list dd {\n overflow-wrap: anywhere;\n}\n.crm-info-link {\n color: var(--ant-color-primary);\n text-decoration: underline;\n}\n.crm-record-panel > .ant-card-body,\n.crm-note-panel > .ant-card-body {\n display: grid;\n gap: 12px;\n}\n.crm-record-card,\n.crm-note-card {\n padding: 12px;\n border: 1px solid var(--ant-color-border-secondary);\n border-radius: 10px;\n cursor: pointer;\n}\n.crm-record-card.is-selected,\n.crm-note-card.is-active {\n border-color: var(--ant-color-primary);\n background: var(--ant-color-primary-bg);\n}\n.crm-record-card__top,\n.crm-record-card__bottom,\n.crm-note-card__top {\n justify-content: space-between;\n}\n.crm-record-card__title {\n margin: 6px 0;\n font-weight: 500;\n}\n.crm-record-card__bottom {\n color: var(--ant-color-text-secondary);\n}\n.crm-record-card__amount {\n color: var(--ant-color-text-heading);\n font-size: 17px;\n font-weight: 700;\n}\n.crm-panel-actions {\n gap: 8px;\n}\n.crm-opportunity-title {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr) max-content max-content;\n gap: 12px 18px;\n align-items: center;\n}\n.crm-opportunity-title .ant-typography {\n color: var(--ant-color-text-secondary);\n}\n.crm-opportunity-title__name,\n.crm-opportunity-title__id {\n color: var(--ant-color-text-heading);\n}\n.crm-opportunity-title__name {\n font-size: 16px;\n font-weight: 500;\n}\n.crm-opportunity-title__id {\n font-weight: 700;\n}\n.crm-stage-bar {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n margin: 20px 0 26px;\n overflow: hidden;\n border-radius: 2px;\n}\n.crm-stage-bar span {\n padding: 10px 6px;\n color: var(--ant-color-text);\n background: var(--ant-color-primary-bg);\n text-align: center;\n}\n.crm-stage-bar span + span {\n border-left: 2px solid var(--ant-color-bg-container);\n}\n.crm-stage-bar .is-current {\n color: var(--ant-color-white);\n background: var(--ant-color-primary);\n font-weight: 600;\n}\n.crm-info-list--two-column {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n column-gap: 44px;\n row-gap: 12px;\n}\n.crm-info-list--two-column > div {\n grid-template-columns: 92px minmax(0, 1fr);\n gap: 8px;\n}\n.crm-description {\n margin-top: 20px;\n}\n.crm-info-list dd,\n.crm-record-card__title,\n.crm-description p,\n.crm-note-card p,\n.crm-note-card__owner {\n color: var(--ant-color-text);\n}\n.crm-description p,\n.crm-note-card p {\n margin: 6px 0;\n}\n.crm-followup-panel__subtitle {\n display: block;\n margin: 28px 0 18px;\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 600;\n}\n.crm-calendar-button {\n margin-top: 28px;\n}\n@media (max-width: 1500px) {\n .crm-page {\n min-width: 0;\n }\n .crm-column--main {\n flex-basis: 520px;\n }\n}\n@media (max-width: 1080px) {\n .crm-column--main {\n flex-basis: 100%;\n }\n}\n@media (max-width: 680px) {\n .crm-page {\n padding: 12px;\n }\n .crm-column {\n flex-basis: 100%;\n }\n .crm-panel .ant-card-head,\n .crm-panel .ant-card-body {\n padding-inline: 14px;\n }\n .crm-metric-card {\n min-height: 96px;\n padding: 14px 12px;\n }\n .crm-metric-card__value {\n font-size: 25px;\n }\n .crm-opportunity-title,\n .crm-info-list--two-column {\n grid-template-columns: 1fr;\n }\n .crm-opportunity-title {\n gap: 6px;\n }\n .crm-info-list--two-column > div {\n grid-template-columns: 84px minmax(0, 1fr);\n }\n .crm-stage-bar {\n overflow-x: auto;\n grid-template-columns: repeat(4, 112px);\n }\n}\n",
|
|
3
|
+
"src/app/index.jsx": "import { useEffect, useState } from \"react\";\nimport {\n Button,\n Card,\n ConfigProvider,\n Select,\n Tag,\n Typography,\n theme as antdTheme,\n} from \"antd\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\nconst { Text } = Typography;\nconst CHILD_PAGE_CODE = \"/replace-with-page-code\";\nconst localizedFieldKeys = new Set([\n \"customer.level\",\n \"customer.type\",\n \"followup.method\",\n \"followup.nextMethod\",\n \"opportunity.source\",\n \"opportunity.stage\",\n]);\nconst stages = [\n \"stage.contact\",\n \"stage.requirement\",\n \"stage.solution\",\n \"stage.contract\",\n];\nconst metrics = [\n { key: \"historyAmount\", value: \"4,500\", unit: \"unit.tenThousandYuan\" },\n { key: \"orderAmount\", value: \"502\", unit: \"unit.tenThousandYuan\" },\n { key: \"opportunityCount\", value: \"12\", unit: \"unit.record\" },\n { key: \"quotationCount\", value: \"5\", unit: \"unit.record\" },\n];\nconst customerFields = [\n [\"customer.name\", \"科技有限公司\"],\n [\"customer.id\", \"000001\"],\n [\"customer.level\", \"customer.levelValue\"],\n [\"customer.type\", \"customer.typeValue\"],\n [\"customer.address\", \"浙江省/杭州市/余杭区/五常街道/***软件园 A6\"],\n];\nconst createNote = ({\n id,\n document,\n customer,\n date,\n type,\n content,\n nextDate,\n nextMethod,\n remark,\n}) => ({\n date,\n type,\n content,\n followup: {\n summaryFields: [\n [\"followup.id\", id],\n [\"followup.document\", document],\n [\"followup.customer\", customer],\n [\"followup.owner\", \"张明明\"],\n ],\n infoFields: [\n [\"followup.method\", type],\n [\"followup.date\", date],\n [\"followup.content\", content],\n ],\n planFields: [\n [\"followup.nextDate\", nextDate],\n [\"followup.nextMethod\", nextMethod],\n [\"followup.remark\", remark],\n ],\n },\n});\nconst opportunities = [\n {\n id: \"12321\",\n name: \"员工绩效系统改造项目\",\n owner: \"张明明\",\n amount: \"30,000\",\n stage: \"stage.contact\",\n color: \"blue\",\n fields: [\n [\"opportunity.stage\", \"stage.contact\"],\n [\"opportunity.customer\", \"科技有限公司\"],\n [\"opportunity.contact\", \"张明明\"],\n [\"opportunity.phone\", \"1808888****\"],\n [\"opportunity.amount\", \"30,000\"],\n [\"opportunity.currency\", \"CNY\"],\n [\"opportunity.rate\", \"50%\"],\n [\"opportunity.closeDate\", \"2026-05-30\"],\n [\"opportunity.source\", \"source.clueConversion\"],\n [\"opportunity.createdAt\", \"2026-01-03 14:31:22\"],\n [\"opportunity.clue\", \"4523671\"],\n [\"opportunity.owner\", \"张明明\"],\n [\"opportunity.competitor\", \"云科技\"],\n [\"opportunity.updatedAt\", \"2026-01-05 14:31:22\"],\n ],\n description:\n \"客户计划由传统 KPI 考核切换为 OKR 管理模式,需要采购并实施一套新的绩效管理系统\",\n notes: [\n createNote({\n id: \"691\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-20 10:22:55\",\n type: \"method.phone\",\n content: \"了解客户需求,客户正在调研绩效管理系统市场并制定新的管理制度\",\n nextDate: \"2026-03-24\",\n nextMethod: \"method.offlineMeeting\",\n remark: \"安排技术人员参与,确认系统迁移方案\",\n }),\n createNote({\n id: \"690\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-16 14:02:30\",\n type: \"method.offlineMeeting\",\n content: \"介绍系统能力与成功案例,客户表示还需要进一步研究管理制度\",\n nextDate: \"2026-03-20\",\n nextMethod: \"method.phone\",\n remark: \"收集客户对 OKR 管理模式的反馈\",\n }),\n createNote({\n id: \"689\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-12 16:11:37\",\n type: \"method.phone\",\n content: \"管理层希望通过新的制度化管理提升员工积极性和目标管理效率\",\n nextDate: \"2026-03-16\",\n nextMethod: \"method.offlineMeeting\",\n remark: \"准备绩效管理案例与实施建议\",\n }),\n ],\n },\n {\n id: \"12323\",\n name: \"积分兑换系统采购\",\n owner: \"张明明\",\n amount: \"52,000\",\n stage: \"stage.solution\",\n color: \"purple\",\n fields: [\n [\"opportunity.stage\", \"stage.solution\"],\n [\"opportunity.customer\", \"积分科技有限公司\"],\n [\"opportunity.contact\", \"张明明\"],\n [\"opportunity.phone\", \"1862688****\"],\n [\"opportunity.amount\", \"52,000\"],\n [\"opportunity.currency\", \"CNY\"],\n [\"opportunity.rate\", \"60%\"],\n [\"opportunity.closeDate\", \"2026-06-18\"],\n [\"opportunity.source\", \"source.onlineInquiry\"],\n [\"opportunity.createdAt\", \"2026-01-09 10:20:18\"],\n [\"opportunity.clue\", \"4523672\"],\n [\"opportunity.owner\", \"张明明\"],\n [\"opportunity.competitor\", \"叮咚科技\"],\n [\"opportunity.updatedAt\", \"2026-01-12 16:45:03\"],\n ],\n description:\n \"客户拟升级会员积分体系,需要建设积分兑换商城,并与现有会员系统打通\",\n notes: [\n createNote({\n id: \"692\",\n document: \"OP-12323\",\n customer: \"积分科技有限公司\",\n date: \"2026-03-21 14:30:18\",\n type: \"method.videoMeeting\",\n content: \"确认积分兑换商品范围,并沟通与现有会员系统的接口方案\",\n nextDate: \"2026-03-26\",\n nextMethod: \"method.phone\",\n remark: \"整理方案与报价,确认接口对接范围\",\n }),\n ],\n },\n];\nconst quotations = [\n {\n id: \"12321\",\n code: \"BJ-20240901-095\",\n date: \"2026-03-18\",\n amount: \"30,000\",\n status: \"quotation.sent\",\n color: \"blue\",\n },\n {\n id: \"12323\",\n code: \"BJ-20240833-012\",\n date: \"2026-03-20\",\n amount: \"52,000\",\n status: \"quotation.accepted\",\n color: \"green\",\n },\n];\n\nconst InfoList = ({ fields, twoColumn = false }) => {\n const $i18n = useI18n();\n\n return (\n <dl\n className={`crm-info-list${twoColumn ? \" crm-info-list--two-column\" : \"\"}`}\n >\n {fields.map(([label, value]) => (\n <div key={label}>\n <dt>{$i18n.t(label)}</dt>\n <dd>\n {label === \"opportunity.clue\" ? (\n <a className=\"crm-info-link\" href=\"#clue-4523671\">\n {value}\n </a>\n ) : (\n localizedFieldKeys.has(label) ? $i18n.t(value) : value\n )}\n </dd>\n </div>\n ))}\n </dl>\n );\n};\n\nconst CustomerColumn = () => {\n const $i18n = useI18n();\n\n return (\n <aside className=\"crm-column\">\n <Card className=\"crm-panel\" variant=\"borderless\">\n <Select\n className=\"crm-customer-card__select\"\n defaultValue=\"default\"\n options={[{ label: $i18n.t(\"customer.nameValue\"), value: \"default\" }]}\n />\n <div className=\"crm-tag-row\">\n <Tag color=\"cyan\" variant=\"solid\">\n {$i18n.t(\"customer.active\")}\n </Tag>\n <Tag color=\"blue\" variant=\"solid\">\n {$i18n.t(\"customer.levelValue\")}\n </Tag>\n </div>\n <div className=\"crm-customer-card__contact\">\n <Text type=\"secondary\">{$i18n.t(\"customer.primaryContact\")}</Text>\n <span>张明明</span>\n </div>\n </Card>\n <Card className=\"crm-metric-panel crm-panel\" variant=\"borderless\">\n {metrics.map((metric) => (\n <div className=\"crm-metric-card\" key={metric.key}>\n <Text>{$i18n.t(`metrics.${metric.key}`)}</Text>\n <div className=\"crm-metric-card__value\">\n {metric.value}\n <span className=\"crm-metric-card__unit\">\n {$i18n.t(metric.unit)}\n </span>\n </div>\n </div>\n ))}\n </Card>\n <Card\n className=\"crm-panel\"\n title={$i18n.t(\"customer.title\")}\n variant=\"borderless\"\n >\n <InfoList fields={customerFields} />\n </Card>\n </aside>\n );\n};\n\nconst RecordCard = ({ item, onClick, selected, quotation }) => {\n const $i18n = useI18n();\n\n return (\n <article\n className={`crm-record-card ${selected ? \"is-selected\" : \"\"}`}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n >\n <div className=\"crm-record-card__top\">\n <Text type=\"secondary\">ID: {item.id}</Text>\n <Tag color={item.color}>\n {$i18n.t(quotation ? item.status : item.stage)}\n </Tag>\n </div>\n <div className=\"crm-record-card__title\">\n {quotation ? item.code : item.name}\n </div>\n <div className=\"crm-record-card__bottom\">\n <span>\n {quotation ? item.date : `${$i18n.t(\"related.owner\")}: ${item.owner}`}\n </span>\n <span className=\"crm-record-card__amount\">¥{item.amount}</span>\n </div>\n </article>\n );\n};\n\nconst RelatedColumn = ({\n onOpenChildPage,\n onSelectOpportunity,\n selectedOpportunityId,\n}) => {\n const $i18n = useI18n();\n const [selectedQuotationId, setSelectedQuotationId] = useState(\n quotations[0].id,\n );\n\n return (\n <section className=\"crm-column\">\n <Card\n className=\"crm-panel crm-record-panel\"\n extra={\n <Button\n ghost\n onClick={() => onOpenChildPage(\"createOpportunity\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.add\")}\n </Button>\n }\n title={`${$i18n.t(\"related.opportunities\")} (${opportunities.length})`}\n variant=\"borderless\"\n >\n {opportunities.map((item) => (\n <RecordCard\n item={item}\n key={item.id}\n onClick={() => onSelectOpportunity(item)}\n selected={item.id === selectedOpportunityId}\n />\n ))}\n </Card>\n <Card\n className=\"crm-panel crm-record-panel\"\n title={`${$i18n.t(\"related.quotations\")} (${quotations.length})`}\n variant=\"borderless\"\n >\n {quotations.map((item) => (\n <RecordCard\n item={item}\n key={item.id}\n onClick={() => setSelectedQuotationId(item.id)}\n quotation\n selected={item.id === selectedQuotationId}\n />\n ))}\n </Card>\n </section>\n );\n};\n\nconst OpportunityColumn = ({\n onOpenChildPage,\n onRefresh,\n onSelectNote,\n opportunity,\n selectedNoteDate,\n}) => {\n const $i18n = useI18n();\n return (\n <section className=\"crm-column crm-column--main\">\n <Card\n className=\"crm-panel\"\n extra={\n <div className=\"crm-panel-actions\">\n <Button onClick={onRefresh} size=\"small\">\n {$i18n.t(\"actions.refresh\")}\n </Button>\n <Button\n ghost\n onClick={() => onOpenChildPage(\"confirmOpportunity\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.confirm\")}\n </Button>\n </div>\n }\n title={$i18n.t(\"opportunity.basicInfo\")}\n variant=\"borderless\"\n >\n <div className=\"crm-opportunity-title\">\n <Text type=\"secondary\">{$i18n.t(\"opportunity.name\")}</Text>\n <span className=\"crm-opportunity-title__name\">\n {opportunity.name}\n </span>\n <Text type=\"secondary\">{$i18n.t(\"opportunity.id\")}</Text>\n <span className=\"crm-opportunity-title__id\">{opportunity.id}</span>\n </div>\n <div\n className=\"crm-stage-bar\"\n aria-label={$i18n.t(\"opportunity.stage\")}\n >\n {stages.map((stage) => (\n <span\n className={stage === opportunity.stage ? \"is-current\" : \"\"}\n key={stage}\n >\n {$i18n.t(stage)}\n </span>\n ))}\n </div>\n <InfoList fields={opportunity.fields} twoColumn />\n <div className=\"crm-description\">\n <Text type=\"secondary\">{$i18n.t(\"opportunity.description\")}</Text>\n <p>{opportunity.description}</p>\n </div>\n </Card>\n <Card\n className=\"crm-note-panel crm-panel\"\n extra={\n <Button\n ghost\n onClick={() => onOpenChildPage(\"createNote\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.addNote\")}\n </Button>\n }\n title={`${$i18n.t(\"notes.title\")} (${opportunity.notes.length})`}\n variant=\"borderless\"\n >\n {opportunity.notes.map((note) => (\n <article\n className={`crm-note-card ${note.date === selectedNoteDate ? \"is-active\" : \"\"}`}\n key={note.date}\n onClick={() => onSelectNote(note)}\n role=\"button\"\n tabIndex={0}\n >\n <div className=\"crm-note-card__top\">\n <Text type=\"secondary\">{note.date}</Text>\n <Tag color=\"blue\">{$i18n.t(note.type)}</Tag>\n </div>\n <p>{note.content}</p>\n <Text type=\"secondary\">{$i18n.t(\"notes.owner\")}: </Text>\n <span className=\"crm-note-card__owner\">张明明</span>\n </article>\n ))}\n </Card>\n </section>\n );\n};\n\nconst FollowupColumn = ({ note, onOpenChildPage }) => {\n const $i18n = useI18n();\n const { followup } = note;\n\n return (\n <aside className=\"crm-column\">\n <Card\n className=\"crm-panel\"\n title={$i18n.t(\"followup.title\")}\n variant=\"borderless\"\n >\n <InfoList fields={followup.summaryFields} />\n <span className=\"crm-followup-panel__subtitle\">\n {$i18n.t(\"followup.info\")}\n </span>\n <InfoList fields={followup.infoFields} />\n <span className=\"crm-followup-panel__subtitle\">\n {$i18n.t(\"followup.plan\")}\n </span>\n <InfoList fields={followup.planFields} />\n <Button\n block\n className=\"crm-calendar-button\"\n onClick={() => onOpenChildPage(\"createCalendar\")}\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.addCalendar\")}\n </Button>\n </Card>\n </aside>\n );\n};\n\nconst CrmPage = ({ location, navigate }) => {\n const [selectedOpportunity, setSelectedOpportunity] = useState(\n opportunities[0],\n );\n const [selectedNote, setSelectedNote] = useState(opportunities[0].notes[0]);\n\n const selectOpportunity = (opportunity) => {\n setSelectedOpportunity(opportunity);\n setSelectedNote();\n };\n\n const refreshSelectedOpportunity = () => {\n const opportunity = opportunities.find(\n ({ id }) => id === selectedOpportunity.id,\n );\n\n setSelectedOpportunity({ ...opportunity });\n setSelectedNote(\n (note) => note && opportunity.notes.find(({ date }) => date === note.date),\n );\n };\n\n const openChildPage = (action) =>\n navigate(CHILD_PAGE_CODE, {\n onePage: true,\n state: {\n action,\n from: location.pathname,\n opportunityId: selectedOpportunity.id,\n },\n });\n\n useEffect(refreshSelectedOpportunity, [location.key]);\n\n return (\n <main className=\"crm-page onepage-theme\">\n <CustomerColumn />\n <RelatedColumn\n onOpenChildPage={openChildPage}\n onSelectOpportunity={selectOpportunity}\n selectedOpportunityId={selectedOpportunity.id}\n />\n <OpportunityColumn\n onOpenChildPage={openChildPage}\n onRefresh={refreshSelectedOpportunity}\n onSelectNote={setSelectedNote}\n opportunity={selectedOpportunity}\n selectedNoteDate={selectedNote?.date}\n />\n {selectedNote && (\n <FollowupColumn note={selectedNote} onOpenChildPage={openChildPage} />\n )}\n </main>\n );\n};\n\nconst OnePage = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useAppTheme();\n\n $i18n.addLocale(locales);\n\n return (\n <ConfigProvider\n theme={{\n algorithm:\n theme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n cssVar: { key: \"onepage-theme\" },\n ...(theme?.skinColor\n ? { token: { colorPrimary: theme.skinColor } }\n : {}),\n }}\n >\n <CrmPage location={location} navigate={navigate} />\n </ConfigProvider>\n );\n};\n\nexport default OnePage;\n",
|
|
4
|
+
"src/locales/index.js": "export const locales = {\n \"zh-CN\": {\n \"customer.title\": \"客户信息\",\n \"customer.name\": \"客户名称\",\n \"customer.nameValue\": \"科技有限公司\",\n \"customer.id\": \"客户 ID\",\n \"customer.level\": \"客户等级\",\n \"customer.levelValue\": \"A级客户\",\n \"customer.type\": \"客户类型\",\n \"customer.typeValue\": \"互联网\",\n \"customer.address\": \"公司所在地\",\n \"customer.active\": \"活跃客户\",\n \"customer.primaryContact\": \"主要联系人\",\n \"metrics.historyAmount\": \"历史成交总额\",\n \"metrics.orderAmount\": \"当前订单总金额\",\n \"metrics.opportunityCount\": \"当前商机数量\",\n \"metrics.quotationCount\": \"当前报价数量\",\n \"unit.tenThousandYuan\": \"万元\",\n \"unit.record\": \"条\",\n \"related.opportunities\": \"销售机会\",\n \"related.quotations\": \"报价\",\n \"related.owner\": \"负责人\",\n \"opportunity.basicInfo\": \"基本信息\",\n \"opportunity.name\": \"商机名称\",\n \"opportunity.id\": \"商机 ID\",\n \"opportunity.stage\": \"商机阶段\",\n \"opportunity.customer\": \"客户\",\n \"opportunity.contact\": \"联系人\",\n \"opportunity.phone\": \"联系电话\",\n \"opportunity.amount\": \"预计成交金额\",\n \"opportunity.currency\": \"币种\",\n \"opportunity.rate\": \"成功概率\",\n \"opportunity.closeDate\": \"预计成交日期\",\n \"opportunity.source\": \"来源类型\",\n \"opportunity.createdAt\": \"创建时间\",\n \"opportunity.clue\": \"来源线索\",\n \"opportunity.owner\": \"负责人\",\n \"opportunity.competitor\": \"主要竞争对手\",\n \"opportunity.updatedAt\": \"最后更新时间\",\n \"opportunity.description\": \"需求详细描述\",\n \"stage.contact\": \"初步接触\",\n \"stage.requirement\": \"需求确认\",\n \"stage.solution\": \"方案制定\",\n \"stage.contract\": \"合同签约\",\n \"source.clueConversion\": \"线索转化\",\n \"source.onlineInquiry\": \"线上咨询\",\n \"quotation.sent\": \"已发送\",\n \"quotation.accepted\": \"已接受\",\n \"method.phone\": \"电话\",\n \"method.offlineMeeting\": \"线下会议\",\n \"method.videoMeeting\": \"视频会议\",\n \"notes.title\": \"跟进小记\",\n \"notes.owner\": \"跟进人\",\n \"followup.title\": \"跟进小记\",\n \"followup.id\": \"跟进记录 ID\",\n \"followup.document\": \"关联单据\",\n \"followup.customer\": \"客户名称\",\n \"followup.owner\": \"跟进人\",\n \"followup.info\": \"跟进信息\",\n \"followup.method\": \"跟进方式\",\n \"followup.date\": \"跟进时间\",\n \"followup.content\": \"跟进内容\",\n \"followup.plan\": \"跟进计划\",\n \"followup.nextDate\": \"下次跟进时间\",\n \"followup.nextMethod\": \"跟进方式\",\n \"followup.remark\": \"备注\",\n \"actions.add\": \"新增\",\n \"actions.refresh\": \"更新\",\n \"actions.confirm\": \"确认需求\",\n \"actions.addNote\": \"新增小记\",\n \"actions.addCalendar\": \"添加日程\",\n \"actions.pageCodeHint\": \"示例页面地址,请替换为实际关联页面 code\",\n },\n \"en-US\": {\n \"customer.title\": \"Customer information\",\n \"customer.name\": \"Customer name\",\n \"customer.nameValue\": \"Mayou CRM Technology\",\n \"customer.id\": \"Customer ID\",\n \"customer.level\": \"Customer level\",\n \"customer.levelValue\": \"Level A\",\n \"customer.type\": \"Customer type\",\n \"customer.typeValue\": \"Internet\",\n \"customer.address\": \"Company address\",\n \"customer.active\": \"Active customer\",\n \"customer.primaryContact\": \"Primary contact\",\n \"metrics.historyAmount\": \"Historical revenue\",\n \"metrics.orderAmount\": \"Current order value\",\n \"metrics.opportunityCount\": \"Current opportunities\",\n \"metrics.quotationCount\": \"Current quotations\",\n \"unit.tenThousandYuan\": \"10k CNY\",\n \"unit.record\": \"items\",\n \"related.opportunities\": \"Opportunities\",\n \"related.quotations\": \"Quotations\",\n \"related.owner\": \"Owner\",\n \"opportunity.basicInfo\": \"Basic information\",\n \"opportunity.name\": \"Opportunity name\",\n \"opportunity.id\": \"Opportunity ID\",\n \"opportunity.stage\": \"Opportunity stage\",\n \"opportunity.customer\": \"Customer\",\n \"opportunity.contact\": \"Contact\",\n \"opportunity.phone\": \"Phone\",\n \"opportunity.amount\": \"Expected amount\",\n \"opportunity.currency\": \"Currency\",\n \"opportunity.rate\": \"Win rate\",\n \"opportunity.closeDate\": \"Expected close date\",\n \"opportunity.source\": \"Source type\",\n \"opportunity.createdAt\": \"Created at\",\n \"opportunity.clue\": \"Source lead\",\n \"opportunity.owner\": \"Owner\",\n \"opportunity.competitor\": \"Main competitor\",\n \"opportunity.updatedAt\": \"Updated at\",\n \"opportunity.description\": \"Requirement details\",\n \"stage.contact\": \"Initial contact\",\n \"stage.requirement\": \"Requirement confirmation\",\n \"stage.solution\": \"Solution design\",\n \"stage.contract\": \"Contract signing\",\n \"source.clueConversion\": \"Lead conversion\",\n \"source.onlineInquiry\": \"Online inquiry\",\n \"quotation.sent\": \"Sent\",\n \"quotation.accepted\": \"Accepted\",\n \"method.phone\": \"Phone\",\n \"method.offlineMeeting\": \"In-person meeting\",\n \"method.videoMeeting\": \"Video meeting\",\n \"notes.title\": \"Follow-up notes\",\n \"notes.owner\": \"Owner\",\n \"followup.title\": \"Follow-up note\",\n \"followup.id\": \"Follow-up ID\",\n \"followup.document\": \"Related document\",\n \"followup.customer\": \"Customer\",\n \"followup.owner\": \"Owner\",\n \"followup.info\": \"Follow-up information\",\n \"followup.method\": \"Method\",\n \"followup.date\": \"Follow-up time\",\n \"followup.content\": \"Content\",\n \"followup.plan\": \"Follow-up plan\",\n \"followup.nextDate\": \"Next follow-up\",\n \"followup.nextMethod\": \"Method\",\n \"followup.remark\": \"Remark\",\n \"actions.add\": \"Add\",\n \"actions.refresh\": \"Refresh\",\n \"actions.confirm\": \"Confirm requirement\",\n \"actions.addNote\": \"Add note\",\n \"actions.addCalendar\": \"Add calendar\",\n \"actions.pageCodeHint\": \"Replace the example path with the related page code\",\n },\n};\n"
|
|
5
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function a167_0x3e52(_0x443b5e,_0x2abd7e){_0x443b5e=_0x443b5e-0x8e;const _0x4be5f7=a167_0x4be5();let _0x3e52da=_0x4be5f7[_0x443b5e];return _0x3e52da;}(function(_0x59ccd2,_0xebf997){const _0x11be92=a167_0x3e52,_0x2b97fa=_0x59ccd2();while(!![]){try{const _0x273a50=-parseInt(_0x11be92(0x8e))/0x1+-parseInt(_0x11be92(0x9a))/0x2*(parseInt(_0x11be92(0xa3))/0x3)+-parseInt(_0x11be92(0xa5))/0x4+parseInt(_0x11be92(0x90))/0x5+-parseInt(_0x11be92(0x8f))/0x6+-parseInt(_0x11be92(0x9b))/0x7+-parseInt(_0x11be92(0xa8))/0x8*(-parseInt(_0x11be92(0x93))/0x9);if(_0x273a50===_0xebf997)break;else _0x2b97fa['push'](_0x2b97fa['shift']());}catch(_0x4b2c6b){_0x2b97fa['push'](_0x2b97fa['shift']());}}}(a167_0x4be5,0xdf6a1));function buildIndexFile(_0x5a99f9){const _0x242d7c=a167_0x3e52;return _0x242d7c(0xa2)+JSON[_0x242d7c(0x98)](_0x5a99f9)+_0x242d7c(0x99);}function a167_0x4be5(){const _0x4019d3=['export\x20const\x20locales\x20=\x20{\x0a','pageDescription','Bu\x20bir\x20temel\x20sayfa\x20şablonudur.\x20Bu\x20temaplana\x20dayanarak\x20geliştirilebilir.','import\x20React\x20from\x20\x27react\x27;\x0aimport\x20{\x20Card,\x20Typography\x20}\x20from\x20\x27antd\x27;\x0aimport\x20{\x20useSdkClient,\x20useI18n,\x20useNavigate,\x20useLocation\x20}\x20from\x20\x22@/context/app-context\x22;\x0aimport\x20{\x20locales\x20}\x20from\x20\x27../locales\x27;\x0aimport\x20\x27./index.css\x27;\x0a\x0aconst\x20App\x20=\x20()\x20=>\x20{\x0a\x20\x20const\x20client\x20=\x20useSdkClient();\x20//\x20client\x20可以用于获取其他数据集的数据\x0a\x20\x20const\x20$i18n\x20=\x20useI18n();\x20//\x20国际化多语言\x20i18n\x20实例\x0a\x20\x20const\x20navigate\x20=\x20useNavigate();\x20//\x20react-router\x20navigate\x20导航实例\x0a\x20\x20const\x20location\x20=\x20useLocation();\x20//\x20浏览器\x20location\x20实例\x0a\x0a\x20\x20$i18n.addLocale(locales);\x20//\x20添加多语言词包\x0a\x0a\x20\x20return\x20(\x0a\x20\x20\x20\x20<div\x20className=\x22page-container\x22>\x0a\x20\x20\x20\x20\x20\x20<Card>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<Typography>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<Typography.Title\x20level={2}>{','129gbqkyE','Ini\x20adalah\x20sebuah\x20halaman\x20dasar.\x20Anda\x20dapat\x20mengembangkan\x20ini.','5908660HZQRXw','id-ID','这是一个基础页面模板,你可以在此基础上进行开发。','672qlcvTm','en-US','1013936tZZkUI','2490708dDNZlM','8811340BYYtIV',':\x20{\x0a\x20\x20\x20\x20',',\x0a\x20\x20}','384165ElZrkP','\x0a};\x0a','zh-CN','This\x20is\x20a\x20basic\x20page\x20template,\x20you\x20can\x20develop\x20based\x20on\x20it.','tr-TR','stringify','}</Typography.Title>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<Typography.Paragraph>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{$i18n.t(\x27pageDescription\x27,\x20\x27这是一个基础页面模板,你可以在此基础上进行开发。\x27)}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</Typography.Paragraph>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</Typography>\x0a\x20\x20\x20\x20\x20\x20</Card>\x0a\x20\x20\x20\x20</div>\x0a\x20\x20);\x0a};\x0a\x0aexport\x20default\x20App;\x0a','46534ShSkNg','3682021oVMpTV','ja-JP','map','これは基本ページテンプレートです。あなたは、これに基づいて開発できます。'];a167_0x4be5=function(){return _0x4019d3;};return a167_0x4be5();}function buildStyleFile(){return'.page-container\x20{\x0a\x20\x20padding:\x2024px;\x0a\x20\x20background:\x20#f0f2f5;\x0a\x20\x20min-height:\x20100vh;\x0a}\x0a';}function buildLocalesFile(_0x2a5b68){const _0x2434d2=a167_0x3e52,_0x21f86c=_0x2a5b68[_0x2434d2(0x9d)](_0x15154a=>{const _0x3d2ca0=_0x2434d2;let _0x323bf6='';switch(_0x15154a){case _0x3d2ca0(0x95):_0x323bf6=_0x3d2ca0(0xa7);break;case _0x3d2ca0(0xa9):_0x323bf6=_0x3d2ca0(0x96);break;case _0x3d2ca0(0x9c):_0x323bf6=_0x3d2ca0(0x9e);break;case _0x3d2ca0(0x97):_0x323bf6=_0x3d2ca0(0xa1);break;case _0x3d2ca0(0xa6):_0x323bf6=_0x3d2ca0(0xa4);break;default:_0x323bf6=_0x3d2ca0(0x96);}return'\x20\x20'+JSON['stringify'](_0x15154a)+_0x3d2ca0(0x91)+JSON['stringify'](_0x3d2ca0(0xa0))+':\x20'+JSON[_0x3d2ca0(0x98)](_0x323bf6)+_0x3d2ca0(0x92);})['join'](',\x0a');return _0x2434d2(0x9f)+_0x21f86c+_0x2434d2(0x94);}export function buildInitialFiles(_0x164a3e,_0x5ef213){return{'src/app/index.jsx':buildIndexFile(_0x164a3e),'src/app/index.css':buildStyleFile(),'src/locales/index.js':buildLocalesFile(_0x5ef213)};}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
# 自定义页面组件用法
|
|
2
|
-
|
|
3
|
-
本文件登记自定义页面可复用的 UI 组件规范。生成或修改页面前先阅读 [`generation-standards.md`](generation-standards.md)。
|
|
4
|
-
|
|
5
|
-
## 使用边界
|
|
6
|
-
|
|
7
|
-
- 可导入的组件包仅限 [`generation-standards.md`](generation-standards.md)“依赖白名单”中的包及其子路径
|
|
8
|
-
- `@lovrabet/components` 使用包根入口的公开具名导出,不导入内部实现路径
|
|
9
|
-
- 组件的用户可见文案必须使用 `$i18n.t("key")`
|
|
10
|
-
- 组件样式使用 Ant Design CSS 变量,不写死颜色或其他样式常量
|
|
11
|
-
- 未登记的业务组件、组件属性或行为不得猜测
|
|
12
|
-
|
|
13
|
-
## `@lovrabet/components` 组件
|
|
14
|
-
|
|
15
|
-
本节基于组件库 README、公开类型声明和 Storybook 用例整理。组件、属性和行为以当前发布版本的公开导出为准。页面代码从包根入口导入:
|
|
16
|
-
|
|
17
|
-
```js
|
|
18
|
-
import {
|
|
19
|
-
YTAIButton,
|
|
20
|
-
YTAIInput,
|
|
21
|
-
YtAddressPicker,
|
|
22
|
-
YtCodeEditor,
|
|
23
|
-
YTRichEditor,
|
|
24
|
-
YTRichEditorPreview,
|
|
25
|
-
YtUpload,
|
|
26
|
-
YtUserSelect,
|
|
27
|
-
} from "@lovrabet/components";
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
| 组件 | 适用场景 | 关键使用方式 |
|
|
31
|
-
| --- | --- | --- |
|
|
32
|
-
| `YTAIButton` | 通过已配置的提示词或 Skill 发起 AI 调用 | 配置 `promptId` 或 `skillId` 与必填 `userPrompt`,处理成功和失败回调 |
|
|
33
|
-
| `YTAIInput` | 需要输入内容后发起 AI 调用 | 配置 AI 调用参数,并使用 `inputType`、`userPromptTemplate` 和提交前校验控制输入流程 |
|
|
34
|
-
| `YtUserSelect` | 选择、编辑或只读展示应用用户 | 受控传入用户 code,使用 `preview` 切换只读展示 |
|
|
35
|
-
| `YtAddressPicker` | 选择或只读展示地址层级 | 受控传入地址 value 路径;需要自定义地址时传入 `options` 或 `optionsUrl` |
|
|
36
|
-
| `YtUpload` | 上传、展示、预览或下载附件 | 传入当前 `appCode`,以 `value` 和 `onChange` 管理已完成的业务文件 |
|
|
37
|
-
| `YTRichEditor` | 编辑富文本内容 | 用 `defaultValue` 初始化,通过 `onChange` 接收 HTML,并为图片上传提供 `upload` |
|
|
38
|
-
| `YTRichEditorPreview` | 只读展示富文本 HTML | 传入必填 `content` |
|
|
39
|
-
| `YtCodeEditor` | 编辑或只读展示代码、JSON 等文本 | 以 `value` 和 `onChange` 受控,按需设置 `language`、`readOnly` 和编辑器选项 |
|
|
40
|
-
|
|
41
|
-
## 使用 Demo
|
|
42
|
-
|
|
43
|
-
以下示例基于组件库 Storybook 用例调整而来。示例中的 `appCode`、提示词或 Skill 标识、数据和回调均由页面上下文提供;所有用户可见文案使用 `$i18n.t("key")`。示例均假设已通过 `useI18n()` 获取 `$i18n`,并按需从 React 导入 `useState`。
|
|
44
|
-
|
|
45
|
-
### AI 按钮
|
|
46
|
-
|
|
47
|
-
```jsx
|
|
48
|
-
<YTAIButton
|
|
49
|
-
promptId={promptId}
|
|
50
|
-
userPrompt={$i18n.t("ai.userPrompt")}
|
|
51
|
-
onSuccess={setGeneratedContent}
|
|
52
|
-
onError={setError}
|
|
53
|
-
>
|
|
54
|
-
{$i18n.t("ai.generate")}
|
|
55
|
-
</YTAIButton>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
使用 `skillId` 替代 `promptId` 时,同时传入 `appCode`。需要在调用前校验页面状态时,传入 `onClick` 并在不满足条件时返回 `false`。
|
|
59
|
-
|
|
60
|
-
### AI 输入框
|
|
61
|
-
|
|
62
|
-
```jsx
|
|
63
|
-
<YTAIInput
|
|
64
|
-
inputType="textarea"
|
|
65
|
-
promptId={promptId}
|
|
66
|
-
userPromptTemplate={$i18n.t("ai.userPromptTemplate")}
|
|
67
|
-
placeholder={$i18n.t("ai.placeholder")}
|
|
68
|
-
buttonText={$i18n.t("ai.generate")}
|
|
69
|
-
onBeforeSubmit={validateInput}
|
|
70
|
-
onSuccess={setGeneratedContent}
|
|
71
|
-
onError={setError}
|
|
72
|
-
/>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`userPromptTemplate` 中使用 `{input}` 引用输入内容。`validateInput` 返回 `false` 时不会调用 AI。
|
|
76
|
-
|
|
77
|
-
### 用户选择
|
|
78
|
-
|
|
79
|
-
```jsx
|
|
80
|
-
const [userCodes, setUserCodes] = useState([]);
|
|
81
|
-
|
|
82
|
-
<YtUserSelect
|
|
83
|
-
appCode={appCode}
|
|
84
|
-
value={userCodes}
|
|
85
|
-
onChange={setUserCodes}
|
|
86
|
-
placeholder={$i18n.t("userSelect.placeholder")}
|
|
87
|
-
/>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
只读展示时使用 `<YtUserSelect appCode={appCode} value={userCodes} preview />`。
|
|
91
|
-
|
|
92
|
-
### 地址选择
|
|
93
|
-
|
|
94
|
-
```jsx
|
|
95
|
-
const [address, setAddress] = useState([]);
|
|
96
|
-
|
|
97
|
-
<YtAddressPicker
|
|
98
|
-
options={addressOptions}
|
|
99
|
-
value={address}
|
|
100
|
-
onChange={setAddress}
|
|
101
|
-
/>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
`addressOptions` 的用户可见 `label` 必须使用词条或已确认的业务数据。只读展示时传入 `preview`。
|
|
105
|
-
|
|
106
|
-
### 附件上传
|
|
107
|
-
|
|
108
|
-
```jsx
|
|
109
|
-
const [files, setFiles] = useState([]);
|
|
110
|
-
|
|
111
|
-
<YtUpload
|
|
112
|
-
appCode={appCode}
|
|
113
|
-
accept="image/*"
|
|
114
|
-
max={maxFiles}
|
|
115
|
-
maxSizeMB={maxFileSizeMB}
|
|
116
|
-
value={files}
|
|
117
|
-
onChange={setFiles}
|
|
118
|
-
/>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
展示已有附件时,将已确认的 `YtUploadFile[]` 传给 `value`;只读预览、下载和列表展示时传入 `preview`。
|
|
122
|
-
|
|
123
|
-
### 富文本编辑与预览
|
|
124
|
-
|
|
125
|
-
```jsx
|
|
126
|
-
const [content, setContent] = useState(initialContent);
|
|
127
|
-
|
|
128
|
-
<YTRichEditor
|
|
129
|
-
defaultValue={initialContent}
|
|
130
|
-
upload={uploadImage}
|
|
131
|
-
onChange={setContent}
|
|
132
|
-
onImageUploadError={setError}
|
|
133
|
-
/>
|
|
134
|
-
|
|
135
|
-
<YTRichEditorPreview content={content} />
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
`uploadImage` 接收 `File` 并返回可访问图片 URL。图片上传服务和内容持久化的实现必须遵循已确认的服务契约。
|
|
139
|
-
|
|
140
|
-
### 代码编辑
|
|
141
|
-
|
|
142
|
-
```jsx
|
|
143
|
-
const [code, setCode] = useState("");
|
|
144
|
-
|
|
145
|
-
<YtCodeEditor
|
|
146
|
-
language="json"
|
|
147
|
-
value={code}
|
|
148
|
-
onChange={setCode}
|
|
149
|
-
placeholder={$i18n.t("codeEditor.placeholder")}
|
|
150
|
-
wordWrap="on"
|
|
151
|
-
/>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
只读展示时使用 `readOnly`,按需使用 `minimap`、`lineNumbers`、`tabSize` 或 `options` 配置编辑器。
|
|
155
|
-
|
|
156
|
-
### AI 调用组件
|
|
157
|
-
|
|
158
|
-
#### `YTAIButton`
|
|
159
|
-
|
|
160
|
-
- `userPrompt` 必填
|
|
161
|
-
- `promptId` 与 `skillId` 二选一,组件优先使用 `promptId`
|
|
162
|
-
- 使用 `skillId` 时传入当前 `appCode`;`showPopover` 只在 Skill 场景生效
|
|
163
|
-
- `onClick` 返回 `false` 可阻止本次调用;通过 `onSuccess`、`onResponse` 和 `onError` 分别处理结果、完整响应和失败
|
|
164
|
-
- `options` 仅用于传入已确认的 `temperature`、`maxTokens` 等 AI 调用选项
|
|
165
|
-
- 提示词标识、权限和可用环境未确认时,不生成调用代码
|
|
166
|
-
|
|
167
|
-
#### `YTAIInput`
|
|
168
|
-
|
|
169
|
-
- 继承 `YTAIButton` 的 AI 调用配置和回调,且不直接传入 `userPrompt`
|
|
170
|
-
- 使用 `inputType="input"` 或 `inputType="textarea"` 选择输入形态,使用 `display="inline"` 或 `display="block"` 控制按钮布局
|
|
171
|
-
- `userPromptTemplate` 中的 `{input}` 会替换为当前输入内容;需要特殊拼接时使用 `formatUserPrompt`
|
|
172
|
-
- 使用 `onBeforeSubmit` 校验输入,返回 `false` 时阻止调用;使用 `onInputChange` 同步页面状态
|
|
173
|
-
- `placeholder`、`buttonText` 和其他用户可见字符串必须传入 `$i18n.t("key")` 的结果
|
|
174
|
-
|
|
175
|
-
### 选择与上传组件
|
|
176
|
-
|
|
177
|
-
#### `YtUserSelect`
|
|
178
|
-
|
|
179
|
-
- 当前用户列表由组件加载,页面不自行猜测或复刻用户查询接口
|
|
180
|
-
- `value` 支持用户 code 数组、单个 code,以及兼容历史数据的 `{ code }` 数组;`onChange` 始终返回用户 code 数组
|
|
181
|
-
- 需要只读展示时传入 `preview`
|
|
182
|
-
- 传入当前 `appCode` 以明确查询上下文;`env` 只在需要覆盖当前环境识别时设置
|
|
183
|
-
|
|
184
|
-
#### `YtAddressPicker`
|
|
185
|
-
|
|
186
|
-
- `value` 与 `onChange` 使用 `string[]` 形式的地址层级路径
|
|
187
|
-
- 传入 `options` 时使用调用方提供的级联选项;未传时使用组件内置地址数据,也可通过 `optionsUrl` 配置地址选项来源
|
|
188
|
-
- 只读展示时传入 `preview`
|
|
189
|
-
- 自定义选项的 `label` 为用户可见内容时,必须来自已翻译词条或已确认的业务数据
|
|
190
|
-
|
|
191
|
-
#### `YtUpload`
|
|
192
|
-
|
|
193
|
-
- 传入当前 `appCode`,不依赖页面自行读取宿主全局变量
|
|
194
|
-
- `value` 支持 `YtUploadFile[]` 或兼容的 JSON 字符串,`onChange` 返回上传完成的业务文件列表
|
|
195
|
-
- 使用 `max`、`maxSizeMB` 和 `accept` 限制上传数量、体积和文件类型;需要只读展示、预览和下载时传入 `preview`
|
|
196
|
-
- 文件持久化、提交和删除由页面业务逻辑处理;先确认数据集 SDK 或其他已确认服务契约,再写入文件字段
|
|
197
|
-
|
|
198
|
-
### 内容编辑组件
|
|
199
|
-
|
|
200
|
-
#### `YTRichEditor` 与 `YTRichEditorPreview`
|
|
201
|
-
|
|
202
|
-
- `YTRichEditor` 使用 `defaultValue` 初始化内容,`onChange` 返回 HTML 字符串或空值
|
|
203
|
-
- 启用图片上传时,`upload` 必须返回可访问图片 URL;用 `onImageUploadError` 和 `onLoadError` 处理上传或资源加载失败
|
|
204
|
-
- 通过 `disabled` 控制编辑状态;通过 `locale` 设置工具栏和错误提示语言
|
|
205
|
-
- 使用 `YTRichEditorPreview` 或 `YTRichEditor.Preview` 展示 HTML,且必须传入 `content`
|
|
206
|
-
- 需要覆盖运行资源或加载占位时,使用已确认的 `resourceUrl` 和 `loadingFallback`,不得猜测资源地址
|
|
207
|
-
|
|
208
|
-
#### `YtCodeEditor`
|
|
209
|
-
|
|
210
|
-
- 使用 `value` 与 `onChange` 管理编辑内容;空值按空字符串处理
|
|
211
|
-
- `language`、`theme`、`minimap`、`lineNumbers`、`wordWrap`、`fontSize`、`tabSize` 和 `options` 用于配置 Monaco 编辑器
|
|
212
|
-
- 需要展示不可编辑内容时使用 `readOnly` 或 `disabled`
|
|
213
|
-
- 未传 `theme` 时组件跟随外层 Ant Design 主题;页面不自行猜测 Monaco 运行资源或加载方式
|
|
214
|
-
|
|
215
|
-
### 非组件公开能力
|
|
216
|
-
|
|
217
|
-
- `queryLLM` 是 `YTAIButton` 对应的直接 AI 调用方法。仅在已确认提示词或 Skill 标识、调用权限和错误处理策略时使用
|
|
218
|
-
- `useUserList` 是 `YtUserSelect` 对应的用户列表 Hook。默认优先使用 `YtUserSelect`;仅在需要自定义交互且已确认返回结构时使用
|
|
219
|
-
|
|
220
|
-
### 使用检查
|
|
221
|
-
|
|
222
|
-
- 组件名称与属性以公开类型声明为准,不使用 README、Storybook 或包内部文件中未公开的实现路径
|
|
223
|
-
- 所有用户可见字符串,包括按钮文本、占位文本和自定义选项 label,均使用 `$i18n.t("key")` 或来自已确认的业务数据
|
|
224
|
-
- 组件的加载、空态、失败和禁用状态与页面交互一致
|
|
225
|
-
- 自定义数据读取或写入仍遵循 [`generation-standards.md`](generation-standards.md) 中的数据客户端文档流程
|