# @ithinkdt/cloud

## 许可证

MIT

## 安装

```bash
npm i @ithinkdt/cloud
```

> 需要您自行安装依赖 `vue@3`、`@ithinkdt/app` 与 `@ithinkdt/page`。

## 介绍

`@ithinkdt/cloud` 提供 iThinkDT 平台的云 API 集成能力，包括：

- 认证登录（登录、登出、修改密码、刷新令牌）
- 用户信息与模块权限获取
- 前端应用配置获取
- 国际化资源管理（增删改查）
- 字典数据获取
- 系统参数获取
- 文件上传、下载与预览
- 组织架构与用户查询
- 消息通知（未读计数、分页查询、标记已读/删除）
- 图标渲染
- 租户切换
- CRUD API 工厂

## 初始化

### 注册 HTTP 拦截器

建议在应用入口处注册 `interceptor` 以统一处理认证错误：

```ts
import { interceptor } from '@ithinkdt/cloud'

// 注册全局拦截器，自动处理 603/604/605/606 等认证错误
app.use(appCore, {
    // ...
    setup: (app) => {
        interceptor({
            language: () => app.language,
            onAuthError(type, error) {
                // type: 'NOT_VALID_TOKEN_TYPE' | 'REQUEST_TOKEN_ERROR' | 'ACCOUNT_FREEZE_ERROR' | 'LOGIN_EXPIRED'
                console.error(type, error)
            },
        })
    },
})
```

### 创建 Cloud API 客户端

```ts
import cloud from '@ithinkdt/cloud'

// 创建 cloud 服务，传入系统服务名
const api = cloud('/sys-server')

// 获取所有应用
const apps = await api.getAllApps()

// 获取应用名称
const name = await api.getAppName('admin', 'zh-CN')

// 获取前端配置
const config = await api.getFrontConfig('admin')

// 获取认证工具
const auth = api.authenticator('admin', 'zh-CN')
const userInfo = await auth.login('username', 'password')
const modules = await auth.getUserModules(token)
```

### 使用 Composable（推荐）

```ts
import { useCloud } from '@ithinkdt/cloud'
import { ref, computed } from 'vue'

const {
    renderLogo,           // 渲染 Logo
    getIconRender,        // 获取图标渲染函数
    notification,         // 消息通知
    getDictByTypes,       // 获取字典
    getSysParams,         // 获取系统参数
    uploadFile,           // 上传文件
    previewFileUrl,       // 文件预览 URL
    downloadFileUrl,      // 文件下载 URL
    getFileInfos,         // 获取文件信息
    removeFile,           // 删除文件
    changeTenant,         // 切换租户
    getModuleResources,   // 获取模块国际化资源
    saveResources,        // 保存国际化资源
    deleteResources,      // 删除国际化资源
    getResources,         // 获取国际化资源
} = useCloud({
    sysServerName: '/sys-server',
    mcServerName: '/mc-server',
    fileServerName: '/file-server',
})

// 获取字典
const dicts = await getDictByTypes(['sex', 'status'], 'zh-CN')

// 获取未读消息数
const unreadCount = await notification.getUnreadCount()

// 上传文件
const fileId = await uploadFile(file, {
    accessRight: 'INTERNAL',
    onProgress: (percent) => console.log(`${percent}%`),
})

// 获取系统参数
const params = await getSysParams('email')
```

## CRUD API 工厂

`createCrudApi` 提供标准的增删改查接口封装，自动根据资源名生成 RESTful API 调用：

```ts
import { createCrudApi } from '@ithinkdt/cloud'

// 快速创建
const userApi = createCrudApi('user')

// 查询列表
const users = await userApi.list({ filter: { status: 1 } })

// 分页查询
const page = await userApi.page({ currentPage: 1, pageSize: 20 })

// 获取详情
const detail = await userApi.get('id-xxx')

// 新增
const newUser = await userApi.save({ name: '张三' })

// 修改
await userApi.put({ id: 'id-xxx', name: '李四' })

// 删除
await userApi.delete('id-xxx')

// 批量删除
await userApi.delete(['id-1', 'id-2'])
```

### 自定义 API 路径

```ts
const api = createCrudApi({
    resource: 'user',
    list: 'post|json: /api/user/search',
    page: 'post|json: /api/user/page',
    get: 'get: /api/user/detail',
    save: 'post|json: /api/user/create',
    delete: 'post|json: /api/user/remove',
    deletes: 'post|json: /api/user/removeBatch',
})
```

## API 参考

### `cloud(sysServerName, options?)`

创建 Cloud API 客户端。

| 参数 | 类型 | 说明 |
|------|------|------|
| `sysServerName` | `string` | 系统服务名，如 `'/sys-server'` |
| `options.request` | `HttpClient` | 可选，自定义 HTTP 客户端 |

**返回值方法：**

| 方法 | 说明 |
|------|------|
| `getAllApps()` | 获取所有启用的应用列表，返回 `Promise<CloudApp[]>` |
| `getAppName(appCode, lang)` | 获取应用名称（国际化），返回 `Promise<string>` |
| `getIconData(id)` | 获取图标 SVG 数据，返回 `Promise<string \| undefined>` |
| `getI18nResources(appCode, lang)` | 获取应用国际化资源，返回 `Promise<Record<string, string>>` |
| `getFrontConfig(appCode, cached?, yidaServerName?)` | 获取前端应用配置，返回 `Promise<CloudConfig>` |
| `authenticator(appCode, lang)` | 获取认证工具，返回 `CloudAuthenticator`（见下方） |

#### `authenticator` 方法

| 方法 | 参数 | 说明 |
|------|------|------|
| `login(username, password)` | `username: string` — 用户名<br>`password: string` — 密码 | 登录，返回 `Promise<UserInfo>` |
| `logout(token)` | `token: string` — 认证令牌 | 登出 |
| `changePwd(token, curPassword, newPassword)` | `token: string` — 认证令牌<br>`curPassword: string` — 当前密码<br>`newPassword: string` — 新密码 | 修改密码 |
| `getUserInfo(token)` | `token: string` — 认证令牌 | 获取用户信息，返回 `Promise<UserInfo>` |
| `refreshAuth(token)` | `token: string` — 认证令牌 | 刷新令牌，返回 `Promise<string>` |
| `getUserModules(token)` | `token: string` — 认证令牌 | 获取用户模块权限，返回 `Promise<UserModule[]>` |

#### `getFrontConfig` 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| `appCode` | `string` | 应用编码 |
| `cached` | `boolean \| string` | 可选，是否启用缓存（仅 session 会话中生效），默认 `true` |
| `yidaServerName` | `string` | 可选，易搭服务名 |

### `useCloud(options?)`

Vue Composable，提供云 API 集成能力。

| 参数 | 类型 | 说明 |
|------|------|------|
| `options.sysServerName` | `string` | 可选，系统服务名 |
| `options.mcServerName` | `string` | 可选，消息中心服务名 |
| `options.fileServerName` | `string` | 可选，文件服务名 |
| `options.request` | `HttpClient` | 可选，自定义 HTTP 客户端 |

**返回值方法：**

| 方法 | 参数 | 说明 |
|------|------|------|
| `renderLogo()` | — | 渲染应用 Logo 组件，返回 `VNodeChild` |
| `getIconRender(id)` | `id: string` — 图标 ID | 获取图标渲染函数，返回 `(placeholder?) => VNodeChild` |
| `getIconData(id)` | `id: string` — 图标 ID | 获取图标数据，返回 `Promise<string \| undefined>` |
| `changeTenant(tenantId)` | `tenantId: string` — 目标租户 ID | 切换租户，返回 `Promise<string>` |
| `getDictByTypes(types, lang?, appCode?)` | `types: string[]` — 字典类型数组<br>`lang?: string` — 语言，如 `'zh-CN'`<br>`appCode?: string` — 应用编码 | 按类型获取字典数据，返回 `Promise<Record<string, DictItem[]>>` |
| `getSysParams(group)` | `group: string` — 参数分组名 | 获取系统参数，返回 `Promise<T>` |
| `getModuleResources(lang, moduleCode, appCode?, contentCode?)` | `lang: string` — 语言<br>`moduleCode: string` — 模块编码<br>`appCode?: string` — 应用编码<br>`contentCode?: string` — 内容编码 | 获取模块国际化资源，返回 `Promise<Record<string, string>>` |
| `saveResources(moduleCode, contentCode, resources, appCode)` | `moduleCode: string` — 模块编码<br>`contentCode: string` — 内容编码<br>`resources: Record<string, string \| undefined>` — 资源键值对<br>`appCode: string` — 应用编码 | 保存国际化资源 |
| `deleteResources(moduleCode, contentCode, appCode)` | `moduleCode: string` — 模块编码<br>`contentCode: string` — 内容编码<br>`appCode: string` — 应用编码 | 删除国际化资源 |
| `getResources(moduleCode, contentCode, appCode)` | `moduleCode: string` — 模块编码<br>`contentCode: string` — 内容编码<br>`appCode: string` — 应用编码 | 获取国际化资源，返回 `Promise<Record<string, string>>` |
| `uploadFile(file, options?)` | `file: File` — 文件对象<br>`options.fileName?: string` — 自定义文件名<br>`options.filePath?: string` — 文件路径<br>`options.accessRight?: 'INTERNAL' \| 'PRIVATE' \| 'PUBLIC'` — 访问权限<br>`options.onProgress?: (percent: number) => void` — 上传进度回调 | 上传文件，返回 `Promise<string>`（文件 ID） |
| `removeFile(fileId)` | `fileId: string` — 文件 ID | 删除文件 |
| `getFileInfos(fileIds)` | `fileIds: string[]` — 文件 ID 数组 | 批量获取文件信息，返回 `Promise<FileInfo[]>` |
| `previewFileUrl(fileId, credentials?)` | `fileId: string` — 文件 ID<br>`credentials?: boolean` — 是否携带凭证 | 获取文件预览 URL，返回 `string` |
| `downloadFileUrl(fileId, credentials?)` | `fileId: string` — 文件 ID<br>`credentials?: boolean` — 是否携带凭证 | 获取文件下载 URL，返回 `string` |
| `getUserGroups()` | — | 获取用户组列表，返回 `Promise<{code: string, name: string}[]>` |
| `getUsersByGroup(code)` | `code: string` — 用户组编码 | 按用户组获取用户，返回 `Promise<{username: string, nickname: string}[]>` |
| `getUsersByOrg(code)` | `code: string` — 组织编码 | 按组织获取用户，返回 `Promise<{username: string, nickname: string}[]>` |
| `getUsersByUsername(usernames?)` | `usernames?: string[]` — 用户名数组 | 按用户名获取用户，返回 `Promise<{username: string, nickname: string}[]>` |
| `getOrgsByCode(codes?)` | `codes?: string[]` — 组织编码数组 | 按编码获取组织树，返回 `Promise<OrgTreeNode[]>` |
| `getServerTimestamp()` | — | 获取服务器时间戳，返回 `Promise<number>` |

#### `notification` 消息通知

| 方法 | 参数 | 说明 |
|------|------|------|
| `getUnreadCount()` | — | 获取未读消息数，返回 `Promise<number>` |
| `getPage(type, page, size)` | `type: 'all' \| 'unread'` — 消息类型<br>`page: number` — 页码<br>`size: number` — 每页条数 | 分页查询消息，返回 `Promise<{records, total}>` |
| `markRead(keys)` | `keys: string[]` — 消息 key 数组 | 标记消息为已读 |
| `markDelete(keys)` | `keys: string[]` — 消息 key 数组 | 标记消息为删除 |

### `createCrudApi(resource)`

创建标准增删改查接口。

| 参数 | 类型 | 说明 |
|------|------|------|
| `resource` | `string \| CrudApiOptions` | 资源名称字符串，或自定义 API 配置对象 |

**CrudApiOptions 配置：**

| 属性 | 类型 | 说明 |
|------|------|------|
| `resource` | `string` | 可选，资源名称 |
| `list` | `ApiString` | 可选，列表查询 API，如 `'get: /api/user/list'` |
| `page` | `ApiString` | 可选，分页查询 API，如 `'post\|json: /api/user/page'` |
| `get` | `ApiString` | 可选，详情查询 API |
| `save` | `ApiString` | 可选，新增/修改 API |
| `post` | `ApiString` | 可选，新增 API（同 save） |
| `put` | `ApiString` | 可选，修改 API |
| `delete` | `ApiString` | 可选，删除 API |
| `deletes` | `ApiString` | 可选，批量删除 API |

**返回值方法：**

| 方法 | 参数 | 说明 |
|------|------|------|
| `list(params?)` | `params.filter?: object` — 过滤条件<br>`params.sortField?: string` — 排序字段<br>`params: any` — 其他查询参数 | 查询列表 |
| `page(params?)` | `params.filter?: object` — 过滤条件<br>`params.currentPage?: number` — 当前页码<br>`params.sortField?: string` — 排序字段<br>`params: any` — 其他查询参数 | 分页查询 |
| `get(id)` | `id: string` — 记录 ID | 获取详情 |
| `save(params)` | `params: object` — 新增数据（不含 id） | 新增记录，返回含 id 的结果 |
| `put(params)` | `params: object` — 修改数据（含 id） | 修改记录 |
| `delete(idOrIds)` | `idOrIds: string \| string[]` — 记录 ID 或 ID 数组 | 删除（单个或批量） |
