# @doany-ai/app-core 中文文档

规范地址：https://npmcn.com/package/%40doany-ai/app-core/

版本：0\.2\.7
npm 元数据更新时间：2026-11-13T10:04:51.504Z
中文译文更新时间：2026-11-13T10:05:39.991Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40doany-ai/app-core
原文对照：https://npmcn.com/package/%40doany-ai/app-core/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：426 kB，196 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：42 个
- peer 依赖：react ^18\.2\.0 \|\| ^19\.0\.0，react-dom ^18\.2\.0 \|\| ^19\.0\.0，@doany-ai/sdk ^0\.5\.22，react-router-dom ^6\.4\.0 \|\| ^7\.0\.0，@tanstack/react-query ^5\.0\.0
- 首次发布：2026-09-24T05:28:26.620Z
- 最新版本发布：2026-11-13T10:04:51.298Z
- 近 30 天下载量：5006（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：27（近 12 个月 27 个）

## 中文简介

每个 Doany 网站共享什么: SDK 客户端和查询缓存、 登录状态和表格、 基准 UI、 主题、 图像和上传

## 安装

```sh
npm install @doany-ai/app-core
```

也可以使用：`pnpm add @doany-ai/app-core`、`yarn add @doany-ai/app-core`、`bun add @doany-ai/app-core`

## README

# @ doany-ai/ 副核心

每个Doany网站作为包而不是脚手架源:SDK 整个页面的客户端和查询缓存、 登录状态和表格、 基准 UI 组件、主题、图像和上传.

它不创建 SDK 客户端或导入 `@doany-ai/sdk`: 网站创建 一个客户并把它交给 `<DoanyProvider>`,页面上每个软件包都使用 一个签入,一个查询缓存。 它需要客户端 `@doany-ai/sdk` 0.5.22 或更晚一点.

## 条目

| 导入 | 什麽 | 需求 |
| --- | --- | --- |
| `@doany-ai/app-core` | `DoanyProvider`, (中文(简体) ), `useDoany`, (中文(简体) ), `createQueryClient`, (中文(简体) ), `readAppParams`, (中文(简体) ), `cn`, (中文(简体) ), `formatMoney` | 反应, @ tanstack/ react- query |
| `@doany-ai/app-core/auth` | `AuthProvider`, (中文(简体) ), `useAuth`, (中文(简体) ), `useTestSite`, (中文(简体) ), `AuthGate`· ; `LoginForm`, (中文(简体) ), `RegisterForm`, (中文(简体) ), `ForgotPasswordForm`, (中文(简体) ), `ResetPasswordForm`, (中文(简体) ), `AuthLayout`;钩子 表格基于: `useLogin`, (中文(简体) ), `useRegister`, (中文(简体) ), `useForgotPassword`, (中文(简体) ), `useResetPassword`· ; `defaultAuthLabels` | —— 说 |
| `@doany-ai/app-core/router` | `ProtectedRoute`, (中文(简体) ), `ScrollToTop`, (中文(简体) ), `LoginPage`, (中文(简体) ), `RegisterPage`, (中文(简体) ), `ForgotPasswordPage`, (中文(简体) ), `ResetPasswordPage` | 反应-路由-度 6.4+ |
| `@doany-ai/app-core/account` | `useMyContact`, (中文(简体) ), `useUpdateMyContact` | `AuthProvider` |
| `@doany-ai/app-core/media` | `Image` (媒体-工人调整大小,私人文件), `FileUpload` | —— 说 |
| `@doany-ai/app-core/form` | 共享窗体字段、 电话/ 国家助手, `useVisitorCountry` | —— 说 |
| `@doany-ai/app-core/ui` | 基本组件(按钮、卡片、对话框、形式、侧边栏、土司.),也逐一: `@doany-ai/app-core/ui/button` | —— 说 |
| `@doany-ai/app-core/styles.css` | 主题变量(另见 `theme.css`(中文(简体) ) | —— 说 |
| `@doany-ai/app-core/tailwind-preset` | 以 Tailwind 3 为主题预设, 并带有全页基本规则 | 尾风 3 |

`SheetContent` 完成出局动画后的同塔布片段导航: 当链接关闭工作表且 URL 匹配已存在的区域时, 它会移动 焦点和滚动到该节。 普通解雇仍以返回为重点 触发器。 习惯 `onCloseAutoFocus` 防止默认保持控制. 工作表不自动关闭链接;网站的导航拥有此功能.

`formatMoney(amount, currency, locale?)` 接受从 在给定的地块中设置平台和格式。 例如,1 200 000 派萨是PKR 12000. 本地端的显示默认不会改变转换 .

## 设置

将军脚手架`src/App.jsx`, (中文(简体) ), `src/main.jsx`, (中文(简体) ), `tailwind.config.js`)已经这样做了;留着吧.

```jsx
// main.jsx
import "@doany-ai/app-core/styles.css"; // the theme variables; the site's CSS comes after and wins
import "./index.css";
import { createClient } from "@doany-ai/sdk";
import { DoanyProvider, readAppParams } from "@doany-ai/app-core";
import { AuthGate, AuthProvider } from "@doany-ai/app-core/auth";
import { LoginPage, ProtectedRoute, RegisterPage } from "@doany-ai/app-core/router";
import { Toaster } from "@doany-ai/app-core/ui";

const params = readAppParams({ appId: import.meta.env.VITE_DOANY_APP_ID });
const doany = createClient({ appId: params.appId, token: params.token, serverUrl: "" });

root.render(
  <DoanyProvider client={doany}>
    <AuthProvider token={params.token}>
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<LoginPage authenticatedHome="/" />} />
          <Route path="/register" element={<RegisterPage />} />
          <Route path="*" element={<AuthGate><SiteRoutes /></AuthGate>} />
        </Routes>
        <Toaster />
      </BrowserRouter>
    </AuthProvider>
  </DoanyProvider>
);
```

```js
// tailwind.config.cjs — one build for the site's classes and the packages'
module.exports = {
  presets: [require("@doany-ai/app-core/tailwind-preset")],
  content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}", "./node_modules/@doany-ai/*/dist/**/*.js"],
};
```

邮包船无公用设施:站台"Tailwind"建设扫描. 两个 在一个分页的基础上构建组件——每个组件都是自己订购的,所以 网站还使用的工具(`px-3`在一个部分更具体后着陆 1个(`pl-10`,其图标的登录表空间)并覆盖. 一栋楼 命令他们一起行动.

## 自定义

三个关卡,每个关卡都不碰包裹的代码. 按这个顺序试试.

**1\. CSS只——主题.** 设置变量于 `:root`在包裹之后 CSS (英语). 每个多尼软件包的每个组件都随之而来,包含对话框. 打开 `:root`,而不是在包装上:对话框和菜单在结尾处渲染 `<body>`, (中文(简体) ), 任何包装外.

```css
:root { --primary: 221 83% 53%; --radius: 1rem; --font-body: "Inter", sans-serif; }
```

变量 : `--background`, (中文(简体) ), `--foreground`, (中文(简体) ), `--card`, (中文(简体) ), `--popover`, (中文(简体) ), `--primary`, (中文(简体) ), `--secondary`, (中文(简体) ), `--muted`, (中文(简体) ), `--accent`, (中文(简体) ), `--destructive`, (中文(简体) ), `--success` (已支付,已确认), `--border`, (中文(简体) ), `--input`, (中文(简体) ), `--ring` (重点),每个 联合国 `-foreground` 文本坐在那里; `--radius`· ; `--font-body`, (中文(简体) ), `--font-heading`默认值为 `src/styles/theme.css`,带有 `.dark` 设置.

该网站自己的CSS也达到了组件. 规则 `p`, (中文(简体) ), `label`, (中文(简体) ), `input`, (中文(简体) ), `select`, (中文(简体) ), `textarea` 或者说 `button`,单独或在包装下 (单位:千美元)`.contact p`, (中文(简体) ), `.form-card input`),还重塑组件的样式 那里:字段标签、字段、帮助文本和错误消息(`<p>`),以及 复选框和无线电选择(`<button>`) (中文(简体) ). 错误消息以这种方式重现 不再读取错误; 给定按钮大小的复选框会拉伸。 给 站点本身的元素为类和样式; 只更改组件 通过以上变量或其变量 `className`。 。 。 .

**2\. 道具只指——文字,部分,链接,行为.** 签入表格 `title`, (中文(简体) ), `subtitle`, (中文(简体) ), `icon`, (中文(简体) ), `providers` (单位:千美元)`[]` 隐藏谷歌,其他的页' 赫尔夫斯, `authenticatedHome`, (中文(简体) ), `renderLink`,以及 `onSignedIn` 编号 : `onAccountCreated` 编号 : `onReset` 代替默认的页面负载。 他们显示的每句话 `labels` -通过一部分 `AuthLabels` 其余者则不违约 对象可以携带一个完整的翻译到每个窗体:

```jsx
<LoginForm title="Mori" providers={[]} labels={{ email: "Work email", logIn: "Sign in", loginFailed: "Check your email and password" }} />
```

平台上说有失败( 尝试太多, 邮件被拿走) 除非网站设置其标签(`loginFailed`, (中文(简体) ), `registerFailed`,(...):然后 总是在网站里——一个被翻译的网站没有显示英语错误.

`AuthGate` 带 `loading` 财务报告和审定财务报表 `notRegistered`· ; `ProtectedRoute` 带 `fallback`, (中文(简体) ), `unauthenticatedElement`, (中文(简体) ), `notRegistered` 财务报告和审定财务报表 `loginPath`· ; `UserNotRegisteredError` 财务报告和审定财务报表 `FileUpload` 带 `labels` 我也一样.

**3\. 只有虎克——你自己的标记.** 表格是挂在钩子上的 他们的行为:当一个签入者回来时(只来自同一个来源,从来没有过) ),新账户如何获得其会话,当网站得知谁 签入.

```jsx
const { login, loginWithProvider, pending, error } = useLogin({ onSignedIn: (to) => navigate(to) });
// error: { reason: "rejected" | "account_unavailable" | …, message } — say it in your words
```

`useRegister` 联合国 `verify` 步骤, `useForgotPassword`, (中文(简体) ), `useResetPassword`,以及 `useAuth()` 编号 : `useDoany()` 国家与 客户端.

## 参考文献

### 窗体控件 : `@doany-ai/app-core/form`

`fieldClass`, (中文(简体) ), `textareaClass` 财务报告和审定财务报表 `selectClass` 共享左对齐的内容; 输入和选择高44px. `NativeSelect` 使用本地的单选道具和裁判; 它的内置箭头在右边留下了12px,为长标签保留了空间. 它保持本地键盘选择,需要验证并形成值. `selectClass` 用于未被包装的本地选择. `IconInput` 为其主要图标保留空间; `CountrySelect` 使用同样的 设置箭头为 `NativeSelect`基础 `Input` 与 `type="date"` 留着 浏览器的日期条目和取取器,取取器在左边并取取取器. 日期输入有柔软的阴影和被俯冲的取取取器图标. 控制为空 日期使用占位符颜色直到聚焦;选定的日期使用普通文本.

### 电话国家: `@doany-ai/app-core/form`

`useVisitorCountry(enabled = true)` 返回访问国为空 电话领域。 它从浏览器语言的清晰区域开始 偏好,或 `US`,然后使用支持 `loc` 来自同一国家的 来源的 `/cdn-cgi/trace` 反应。 请求每页运行一次, 绕行 3秒后缓存和出站时间;一个不可用的端点保留 浏览器倒置 。 它在浏览器存储中不存储位置或跟踪. 这页 语言不能决定访客的国家.

`Form` 财务报告和审定财务报表 `AttendeeForm` 仅在访问者选择一个前应用此默认值 国家或开始打字。 保存的电话值 保持他们的祖国。 自定义表单 使用钩子时必须保持相同的边界,并在 将本地数字转换为 `phoneFrom(value, dialOf(country))`。 。 。 .

### 签名 : `@doany-ai/app-core/auth`, (中文(简体) ), `@doany-ai/app-core/router`

四个登录页是表格上的路由组件: `LoginPage`, (中文(简体) ), `RegisterPage`, (中文(简体) ), `ForgotPasswordPage` 财务报告和审定财务报表 `ResetPasswordPage` (单位:千美元)`/router`采取 同样的道具 `LoginForm`, (中文(简体) ), `RegisterForm`, (中文(简体) ), `ForgotPasswordForm` 财务报告和审定财务报表 `ResetPasswordForm` (单位:千美元)`/auth`),并通过路由器相互连接. 每个表格已经包含 `AuthLayout`:直接通过 `title`, (中文(简体) ), `subtitle` 财务报告和审定财务报表 `className` 视需要而定。 将窗体或路由页面包入到另一个区域 `AuthLayout` 巢出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出 共享 版式使用1张应答卡,封口为30rem,手机上有页沟. 保持他们 `/login`, (中文(简体) ), `/register`, (中文(简体) ), `/forgot-password` 财务报告和审定财务报表 `/reset-password`数字 : 重设电子邮件链接到 `/reset-password?token=`。 。 。 .

每一种形式 `icon`, (中文(简体) ), `title`, (中文(简体) ), `subtitle` (赢得标签), `labels` 财务报告和审定财务报表 `className`,并:

| 表单 | 道具 |
| --- | --- |
| `LoginForm` | `authenticatedHome` (当没有) `from_url`; 默认 `/`, (中文(简体) ), `fromUrl` (默认: 页面的) `?from_url=`, (中文(简体) ), `registerHref`, (中文(简体) ), `forgotPasswordHref` (违约) `/register` 财务报告和审定财务报表 `/forgot-password`,带有当地 `from_url`, (中文(简体) ), `providers` (默认) `["google"]`· ; `[]` 隐藏谷歌, `renderLink`, (中文(简体) ), `onSignedIn(destination)` (默认:装入) |
| `RegisterForm` | `fromUrl` (默认: 页面的) `?from_url=`新账户就在那里, `authenticatedHome`, (中文(简体) ), `loginHref` (默认) `/login`,携带 `from_url`, (中文(简体) ), `providers`, (中文(简体) ), `renderLink`, (中文(简体) ), `onAccountCreated()` (已核实:在下一个中签名), `onSignedIn(destination)` |
| `ForgotPasswordForm` | `loginHref` (默认) `/login`,带有页面 `from_url`, (中文(简体) ), `renderLink` |
| `ResetPasswordForm` | `resetToken` (默认: 页面的) `?token=`, (中文(简体) ), `loginHref`, (中文(简体) ), `forgotPasswordHref`, (中文(简体) ), `renderLink`, (中文(简体) ), `onReset()` (默认:负载) `loginHref`(中文(简体) ) |

表格的钩子基于:

| 钩子 | 回返 |
| --- | --- |
| `useLogin({ fromUrl?, authenticatedHome?, onSignedIn? })` | `login(email, password)` → `true` 当签入, `loginWithProvider("google")`, (中文(简体) ), `destination`, (中文(简体) ), `pending`, (中文(简体) ), `error`, (中文(简体) ), `clearError()` |
| `useRegister({ fromUrl?, authenticatedHome?, loginHref?, onAccountCreated?, onSignedIn? })` | `step` (单位:千美元)`details` → `verify`, (中文(简体) ), `email`, (中文(简体) ), `register({ email, password, confirmPassword? })` → `"verify"` (一个代码已发电子邮件) / `"created"` (不需要代码) / `null`, (中文(简体) ), `verify(code)`, (中文(简体) ), `resend()`, (中文(简体) ), `loginWithProvider`, (中文(简体) ), `destination`, (中文(简体) ), `pending`, (中文(简体) ), `error`, (中文(简体) ), `clearError()` |
| `useForgotPassword()` | `request(email)`, (中文(简体) ), `sent`, (中文(简体) ), `pending` |
| `useResetPassword({ resetToken?, loginHref?, onReset? })` | `token`, (中文(简体) ), `reset(newPassword, confirmPassword?)`, (中文(简体) ), `pending`, (中文(简体) ), `error`, (中文(简体) ), `clearError()` |

`error` 实值 `{ reason, message }`数字 : `reason` 实值 `rejected`, (中文(简体) ), `account_unavailable`, (中文(简体) ), `passwords_differ` 或者说 `resend_failed`· ; `message` 是平台的词。 说啊 用网站的话来说.

### 签名状态

`useAuth()` (需求数) `AuthProvider`返回时 `user` (单位:千美元)`{ id, email, full_name, role }` 或者说 `null`, (中文(简体) ), `isAuthenticated`, (中文(简体) ), `isLoadingAuth`, (中文(简体) ), `authChecked`, (中文(简体) ), `appPublicSettings`, (中文(简体) ), `logout(returnTo?)` (穿过站台就来了) 返回; 默认当前页面 , `navigateToLogin()`,以及 `refresh(token?)` (签入后再次读入,没有页面载荷).

### 网站编辑器

在网站编辑器中,访问者所做的被保存为测试:它在 商业记录有测试标记, 计数为一无所有, 也达不到客户端. `useTestSite()` (单位:千美元) `AuthProvider`,来自平台的公共环境)是 其中一个方法 一个组件学习它。 网站本身只显示客户 在编辑中也可以看到:编辑在页上说所保存的东西.

编辑部的帐号是测试帐号:它只在帐号上签字,而且 真实账户不在那里签名. `RegisterForm` 财务报告和审定财务报表 `LoginForm` 一样 那里; 编辑器中的真实账户签名在平台上被拒绝 字("在这里用一个测试账户签名").

| 构成部分 | 道具 |
| --- | --- |
| `AuthGate` | 将网站的路线包裹起来:一旦网站设置和登录信息公布,就显示这些路线,并派一位客人前往 `/login` 在只登录的网站上. `loading`, (中文(简体) ), `notRegistered` |
| `ProtectedRoute` (单位:千美元)`/router`(中文(简体) ) | 需要登录账户的页面的布局路由 : `<Route element={<ProtectedRoute />}>…</Route>`。 。 。 . `fallback` (在检查设置或登录时), `labels` (单位:千美元)`loadError`, (中文(简体) ), `retry` 可收回的已读失败, `unauthenticatedElement` (而不是去登录页; `null` 计数, `loginPath` (默认) `/login`, (中文(简体) ), `notRegistered` |
| `ScrollToTop` (单位:千美元)`/router`(中文(简体) ) | 装入路由器 它还告诉每个访问者的平台(`usePageViews`:访问的一页登陆了,然后每次导航到另一个路径,每个路径都有页面设定的标题;SDK,0.5.22或后期,等待第一页,所以登陆页面记录一次,并决定是否收集任何东西. 向上新建页面导航滚动; 初始或新导航 `#id` 等待其部分制作(包括后面) `AuthGate`然后在那里滚动。 后/前保持浏览器的卷轴修复. 找到目标时等待结束,导航会改变,或者组件会卸载. |
| `UserNotRegisteredError` | 显示一个不是这个网站的用户的屏幕. `title`, (中文(简体) ), `message`, (中文(简体) ), `labels` |
| `AuthLayout` | 签入表格中的卡片. `icon`, (中文(简体) ), `title` (必须), `subtitle`, (中文(简体) ), `footer` |

失败的设置或账户已读取( 例如, 网络错误或 HTTP 500) 保留被保护的页面并用再次尝试按钮显示出错误. 按钮调用已有 `checkAppState()` 以读取设置并签名 ; 重新检查本身不提交商业行动。 短暂的失败 不丢弃会话。 只有活动读取使用 `fallback`; 认证 拒绝和未注册的屏幕保留其现有行为。 错误使用 固定文本或 `labels`, (中文(简体) ), 不披露后端错误消息.

### 账户 : `@doany-ai/app-core/account`

| 钩子 | 回返 |
| --- | --- |
| `useMyContact()` | 查询; `data` 是注册账户的联系人 : `display_name`, (中文(简体) ), `given_name`, (中文(简体) ), `family_name`, (中文(简体) ), `email`, (中文(简体) ), `phone`, (中文(简体) ), `address` (单位:千美元)`line`, (中文(简体) ), `city`, (中文(简体) ), `region`, (中文(简体) ), `postal_code`, (中文(简体) ), `country`, (中文(简体) ), `timezone`, (中文(简体) ), `preferred_language`, (中文(简体) ), `version`没有客人 |
| `useUpdateMyContact()` | a 变种: `mutateAsync({ version, display_name?, given_name?, family_name?, phone?, address?, timezone?, preferred_language? })`发送 `version` 最后读; a 409 表示同时更改:再读再试. 邮件跟随登录, 无法在此更改 |

### 媒体: `@doany-ai/app-core/media`

`imageDisplayUrl(src, width, height?)` 返回一个显示大小的 WebP URL 已存在的平台图像,而不更改其存储的正本。 宽度和高度 是输出像素; 既通过一个中心收成, 也只通过宽度来保存 图像。 现有的私人签名被保留,GIFs保留了动画和SVG 和外部 URL 不变。 它不签署或修改供应商缩略图.

使用 `Image` 对于可编辑的网站图像:它服务于图像大小调整到其框中, 并签名一个私人文件 `media://` 显示前URI. 框大小为 `className`。加载占位符留在图像框内,并遵循其 边界半径。 在框中添加阴影 `className` 或者说 `style`。 。 。 . 对于固定课程缩略图和可以使用海报 URL的控制, 使用情况 `imageDisplayUrl` 以直接请求适当的输出大小.

| 构成部分 | 道具 |
| --- | --- |
| `Image` | 每个 `<img>` 道具,和 `fittingType` (单位:千美元)`fill`默认的作物; `fit` 显示全部), `originWidth` 编号 : `originHeight` (在加载前保持宽比), `focalPointX` 编号 : `focalPointY` (0-1,收割时注意), `quality` (1-100,默认为90) |
| `FileUpload` | 每个文件 `<input>` 道具除外 `value`, (中文(简体) ), `onChange`, (中文(简体) ), `type`,并: `value` (存储文件的 URL), `onChange(url)` (称为一旦上传完成, 所以表的提交从来不等待它), `privateFile` (私人商店;价值是 `media://` 乌里, `onUploadError`, (中文(简体) ), `children` (一旦有值时显示:预览、删除按钮), `labels` (单位:千美元)`uploading`, (中文(简体) ), `uploadFailed`(中文(简体) ) |

### 基础UI : `@doany-ai/app-core/ui`

阴影组件(新约克),带有常用道具:手风琴, 警示对讲机 警示 纵横 外形 徽章 面包屑 按钮 日历, 卡片、木马、图表、复选框、可折叠、命令、上下文菜单、对话框、, 抽屉,下拉菜单,形式,悬浮卡,输入-otp,输入,标签,菜单栏, 导航菜单, 平面图, 弹出, 进步, 无线电组, 可扩展, 滚动区域, 选择, 分隔符, 工作表, 侧边栏, 骨架, 滑块, 音标, 切换、表格、标签、文本区域、土司/烤面包机(`useToast`),切换组, 切换,工具提示。 从 `@doany-ai/app-core/ui`,或从 `@doany-ai/app-core/ui/<name>`。 。 。 . `cn` 进入 `@doany-ai/app-core`图标来了 从 `lucide-react`没有 `@/components/ui` 在现场.

旁边有两个人: `DataTable` (单位:千美元)`columns: { key, header, cell?, align? }[]`, (中文(简体) ), `rows`, (中文(简体) ), `pageSize` (10), (中文(简体) ), `empty` (什么显示无行), `rowKey`, (中文(简体) ), `labels`将行划入表格并翻转页面本身; `EmptyState` (单位:千美元)`icon`, (中文(简体) ), `title`, (中文(简体) ), `description`, (中文(简体) ), `action`)是列表显示的内容 在它有东西之前。 侧边栏的颜色是 `--sidebar-*` 变量 内 `styles.css` (背景,前地,初级,初级前地,口音, 重音前缘、边框、环等,用于光和 `.dark`。 。 。 .

#### 长 CTA 标签

`Button` 保留其通常的单行、 固定高的默认值(包括图标) 按钮)。 长篇行动呼吁 `wrap`:标签可以包接,按钮 无法超过其容器,其高度从所选大小的 最低身高。 这也与 `asChild` 链接.

```jsx
<Button wrap size="lg" className="min-h-12 rounded-full">
  See a cleaning-business example
</Button>
```

使用 `min-h-*`,不是固定的 `h-*`,在自定义一个包接按钮时。 留着它 网格/弹性父母可缩小(`min-w-0`; 不给标签 `whitespace-nowrap`。 。 。 .

#### 纸牌网格和全宽行

`cardGrid(count, size = "narrow", layout = "cards")` (从根入出 返回共享网格类. `cards` 使用可用的容器宽度 以选择列,在 24rem 时为每块瓦盖(`wide`: 28雷姆)和中心1或 两块瓷砖. `list` 是一个没有瓦片或小数宽度的全宽列 盖;用于自定义服务行和多栏内容. `CardGridLayout` 是导出 `"cards" | "list"` 类型.

两种模式都无法推断任意自定义标记中的布局. 内部a 行,从行可用的空间中选择列,而不是取景口. 对于 实例, `grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))]` 与 `min-w-0` 当行是窄的,甚至放在桌面屏幕上时,孩子会堆放. 对于不平等的列,在行周围的包装上使用 CSS 容器查询. 切勿将窄卡覆盖到两列并带有视图 `lg:` 规则.

### `Embed`: 内容相同,四种方式

`Embed` (单位:千美元) `@doany-ai/app-core/ui`)将其所包裹的东西——一种形式,一种订票流——放入页面(`inline`,默认值),在对话框中(`popup`),在被固定到最小化为分页的角上的面板上(`sidebar`),或者在从一侧滑入并可以被关闭的面板上(`slide-in`) (中文(简体) ). `trigger` 打开它的时候说: `{ type: "always" }` (违约), `{ type: "after", seconds }`, (中文(简体) ), `{ type: "scroll", percent }`。 。 。 . `deactivate` 说当它停止打开时: `{ afterShows: n }`, (中文(简体) ), `{ onceSubmitted: true }` ——计算数 `storageKey` 在浏览器中 `localStorage`内部, `useEmbed()` 给 `close()` 财务报告和审定财务报表 `markSubmitted()`。 。 。 .

```jsx
<Embed layout="popup" label="Get in touch" trigger={{ type: "after", seconds: 5 }} deactivate={{ afterShows: 2 }}>
  <Form formId={siteConfig.contactFormId} showTitle={false} />
</Embed>
```

### 认证标签

每一个字的登录表格和屏幕显示在 `AuthLabels`· ; `defaultAuthLabels` 持有默认值。 形式参与并保持 其余的默认值.

| 何处 | 密钥 |
| --- | --- |
| 共享 | `email`, (中文(简体) ), `emailPlaceholder`, (中文(简体) ), `password`, (中文(简体) ), `confirmPassword`, (中文(简体) ), `or`, (中文(简体) ), `continueWithGoogle` |
| 登录 | `loginTitle`, (中文(简体) ), `loginSubtitle`, (中文(简体) ), `logIn`, (中文(简体) ), `loggingIn`, (中文(简体) ), `forgotPassword`, (中文(简体) ), `noAccount`, (中文(简体) ), `createOne`, (中文(简体) ), `loginFailed`, (中文(简体) ), `accountUnavailable` |
| 登记册 | `registerTitle`, (中文(简体) ), `registerSubtitle`, (中文(简体) ), `createAccount`, (中文(简体) ), `creatingAccount`, (中文(简体) ), `haveAccount`, (中文(简体) ), `logInLink`, (中文(简体) ), `passwordsDiffer`, (中文(简体) ), `registerFailed`, (中文(简体) ), `accountCreated`, (中文(简体) ), `accountCreatedBody` |
| 电子邮件代码 | `verifyTitle`, (中文(简体) ), `codeSentTo`, (中文(简体) ), `verify`, (中文(简体) ), `verifying`, (中文(简体) ), `noCode`, (中文(简体) ), `resend`, (中文(简体) ), `codeResent`, (中文(简体) ), `codeResentBody`, (中文(简体) ), `invalidCode`, (中文(简体) ), `resendFailed`, (中文(简体) ), `newAccountUnavailable` |
| 忘记密码 | `forgotTitle`, (中文(简体) ), `forgotSubtitle`, (中文(简体) ), `emailAddress`, (中文(简体) ), `sendResetLink`, (中文(简体) ), `sending`, (中文(简体) ), `resetLinkSent`, (中文(简体) ), `backToLogin` |
| 重置密码 | `resetTitle`, (中文(简体) ), `resetSubtitle`, (中文(简体) ), `newPassword`, (中文(简体) ), `resetPassword`, (中文(简体) ), `resetting`, (中文(简体) ), `resetFailed`, (中文(简体) ), `invalidResetLink`, (中文(简体) ), `invalidResetLinkSubtitle`, (中文(简体) ), `invalidResetLinkBody`, (中文(简体) ), `requestNewLink` |
| 不是这个网站的账户 | `notRegisteredTitle`, (中文(简体) ), `notRegisteredMessage`, (中文(简体) ), `notRegisteredHelp`, (中文(简体) ), `notRegisteredSteps` |

从不编辑此软件包 `node_modules` 或将其来源复制到网站.

## 开发

### 账户简介

`@doany-ai/app-core/account` 出口 `ProfileForm` 财务报告和审定财务报表 `useProfileForm`两者 使用现有的 `contacts.me` / 已编辑 `contacts.updateMe` 合同。 这个 表格编辑联系人显示名称;签名电子邮件只读。 装入, 缺少配置文件、保存失败和版本冲突都可见。 冲突 在另一个保存之前需要重新装入。 草稿和反馈遵循当前 账户,所以账户切换从不显示前一个账户的草稿.

使用 `labels`, (中文(简体) ), `footer`, (中文(简体) ), `className` 财务报告和审定财务报表 `classNames` 用于支持的定制. `ProfileFormExample` 显示 `mode="default" | "css" | "props" | "hook"`· ; 每个账户都拥有一个孤立的内存账户,不能写入真实账户. CSS示例更改了主题符号,道具示例更改了标签/脚印, 和钩子示例绘制自定义标记 `useProfileForm` 拥有者.

### 从哪里来

从v5脚手架上取出 `src` (已删除; `doany-general-scaffold` 以这些软件包为基础,保留其行为:

| 给 | 脚手架 |
| --- | --- |
| `DoanyProvider`, (中文(简体) ), `createQueryClient` | `App.jsx`是因为 `QueryClientProvider`, (中文(简体) ), `lib/query-client.js`;客户来自 `api/doanyClient.js` |
| `readAppParams` | `lib/app-params.js` (违约经过而不是读取自 `import.meta.env`(中文(简体) ) |
| `auth/` | `lib/AuthContext.jsx`, (中文(简体) ), `lib/login-code.js`, (中文(简体) ), `lib/login-navigation.js`, (中文(简体) ), `pages/*`, (中文(简体) ), `components/AuthLayout.jsx`, (中文(简体) ), `GoogleIcon.jsx`, (中文(简体) ), `UserNotRegisteredError.jsx`, (中文(简体) ), `App.jsx`认证门 |
| `router/` | `components/ProtectedRoute.jsx`, (中文(简体) ), `components/ScrollToTop.jsx` |
| `media/` | `components/ui/image.jsx`, (中文(简体) ), `file-upload.jsx`, (中文(简体) ), `lib/image-layout.js`, (中文(简体) ), `hooks/use-size.jsx` |
| `ui/` | `components/ui/*`, (中文(简体) ), `hooks/use-mobile.jsx` |

`AuthProvider` 读取网站设置 `doany.app.getPublicSettings()` 并交换登录代码 `doany.auth.exchangeLoginCode()` 改为 从SDK内部建立自己的轴心客户端.

这个 `ui/` 组件是 Shadcn 的新黄种人类型 脚手架的差异被应用,所以每个文件, 及其类型剥离,匹配 脚手架的 `.jsx` - 类弦, `TOAST_LIMIT = 20`, (中文(简体) ), `SidebarTrigger`是因为 `asChild`从上游到最安全的地方: `FormMessage` 财务报告和审定财务报表 `ChartTooltipContent` 守护一个未定义的值,移动边栏带有一个 屏幕阅读标题.

### 构建和测试

```bash
npm ci
npm test          # vitest + jsdom
npm run typecheck
npm run build     # dist/: ESM + .d.ts per module, styles.css (the theme variables)
```

## 常见问题

### @doany-ai/app-core 是什么？

每个 Doany 网站共享什么: SDK 客户端和查询缓存、 登录状态和表格、 基准 UI、 主题、 图像和上传

### 如何安装 @doany-ai/app-core？

在项目目录运行 npm install @doany-ai/app-core，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（^18\.2\.0 \|\| ^19\.0\.0）、react-dom（^18\.2\.0 \|\| ^19\.0\.0）、@tanstack/react-query（^5\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @doany-ai/app-core 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @doany-ai/app-core 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @doany-ai/app-core 可以商用吗？

@doany-ai/app-core 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @doany-ai/app-core 的下载量有多少？

根据 npm 官方下载统计，@doany-ai/app-core 在 2026/10/13 – 2026/11/11 这 30 天内被下载 5,006 次，日均约 167 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @doany-ai/app-core 还在维护吗？

最新版本 0\.2\.7 发布于 2026/11/13（今天）；至今共发布 27 个版本，其中过去 12 个月发布了 27 个；首次发布于 2026/09/24。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

