# [@tuya-sat/sdf-main-sdk](https://www.npmjs.com/package/@tuya-sat/sdf-main-sdk)

涂鸦 SaaS 开发框架的主应用 SDK，核心逻辑有开发框架团队维护并保障稳定性。业务方只需要基于此 SDK，进行要少量的业务开发，就可以定制符合自己业务诉求的主应用

## 如何使用
1. 快速初始化主应用项目
```shell
# 创建项目 `saas-main-app`
npx @tuya-sat/create-micro-app@latest saas-main-app -t main
```

2. 配置调试 SaaS
打开 `./micro.config.js`，配置需要调试的 saas 域名。如下：
```javascript
module.exports = {
  debuggerConfig: {
    target: 'https://<my-first-saas-host>', // SaaS 域名
    isMainApp: true,
  }
}
```

3. 启动项目
```shell
yarn start
```

## 本地联调

如果业务主应用通过 `yalc` 引用当前 SDK，可以按下面的方式进行本地联调。

### 1. SDK 仓库启动监听构建

```shell
yarn dev:yalc
```

该命令会在每次 webpack 构建成功后自动：

- 生成类型声明
- 执行 `yalc push --changed`

### 2. 业务主应用首次接入本地 SDK

在业务主应用仓库执行：

```shell
yalc add @tuya-sat/sdf-main-sdk
yarn install
```

然后启动业务主应用的本地开发服务。

### 3. 日常开发流程

1. 在 SDK 仓库修改代码
2. 等待 `yarn dev:yalc` 构建完成并自动执行 `yalc push`
3. 回到业务主应用刷新页面查看效果

### 4. 注意事项

- 当前 SDK 的运行产物依赖 `dist/main.bundle.js`，因此联调时需要先完成本地构建，再推送给业务主应用。
- 如果业务主应用没有拿到最新代码，可在业务主应用目录重新执行一次 `yarn install` 或重新启动开发服务。

## 功能介绍

### MQTT

SDK 默认导出 V6 版本 MQTT 能力，可用于订阅、发布消息，也可以监听 MQTT 连接状态。

#### 监听连接状态

`onStatusChange` 用于监听 MQTT 连接状态变化。当 MQTT 断联并重试 5 次仍未恢复时，会触发 `reconnect_failed`，业务侧可在该状态下执行兜底逻辑。

```ts
import { mqtt } from '@tuya-sat/sdf-main-sdk';

const offMqttStatus = mqtt.onStatusChange((event) => {
  if (event.status === 'reconnect_failed') {
    // MQTT 断联且重试 5 次失败，执行业务兜底
  }
});

// 不再需要监听时清理
offMqttStatus();
```

子应用可通过 qiankun props 使用：

```ts
const offMqttStatus = props.mqtt.onStatusChange((event) => {
  if (event.status === 'reconnect_failed') {
    // 子应用业务兜底
  }
});

// 推荐在子应用 unmount 或 React useEffect cleanup 中主动清理
offMqttStatus();
```

通过 `props.mqtt.onStatusChange` 注册的监听会被主应用标记为子应用作用域。子应用卸载时，主应用会自动清理这类监听；业务侧仍建议主动调用返回的清理函数，避免重复挂载期间产生多余监听。

事件结构如下：

```ts
type MqttStatus =
  | 'connected'
  | 'reconnecting'
  | 'reconnect_failed'
  | 'offline'
  | 'close'
  | 'error';

interface MqttStatusEvent {
  status: MqttStatus;
  remainingRetries?: number;
  maxRetries?: number;
  error?: any;
}
```

常用状态说明：

- `reconnecting`：正在重连，`remainingRetries` 表示剩余重试次数。
- `reconnect_failed`：重试次数耗尽，业务侧可执行兜底。
- `connected`：连接成功，重试次数会恢复为默认值。

### Hooks

hooks 的定义和功能如下，将 hooks 存放到 `./src/hooks` 目录下，sdk 会读取并加载

```typescript
interface IHooks {
  /**
   * 功能：SaaS 页面渲染前钩子，主应用生命周期中最早的钩子
   */
  enterAppHook?: () => void;

  httpHook?: {
    /**
     * 功能：请求前拦截处理。可以在此处添加自定义的请求头
     * 示例：
     * ```
     *  before: (req) => {
     *    req.headers && (req.headers["abc"] = "sdf-main-sdk");
     *  }
     * ```
     */
    before?: (req) => void;
		
    /**
     * 功能：响应拦截处理。可以在这里统一格式化数据
     * 示例：
     * ```
     *  after: (data) => {
     *    // ....数据格式化逻辑
     *    return data;
     *  }
     * ```
     */
    after?: (data) => any;
  };

  /**
   * 功能：未登录时，对 SaaS 信息拦截处理
   */
  unLoginSaaSHook?: (saas: any) => Promise<any>;

  /**
   * 功能：登录成功后，对 SaaS 信息拦截处理
   */
  loginedSaaSHook?: (saasInfo: any) => Promise<any>;
}
```

### 页面 UI 配置
相关的配置，是以 props 属性的方式透传到组件内。具体配置项与使用方法如下

#### 1. 登录页
##### 类型定义
```ts
interface LoginPageProps {
  extra?: {
    // 标题
    TitleSolt?: React.FC<{ logo: string; title: string }>;

    // 背景图	
    BgSlot?: React.FC<{ bg: string }>;

    // 三要素
    ClauseSlot?: React.FC;

    // 右上角导航
    ExtraNavSlot?: React.FC;
  };
  customStyle?: {
    // 自定义表单样式
    formStyle?: React.CSSProperties;
  };
}
```

##### 示例
```tsx
// ./src/pages/login/index.tsx
import { LoginPage } from "@tuya-sat/sdf-main-sdk";

export default (props: LoginPageProps) => {
  return <LoginPage {...props} />
}
```

#### 2. 忘记密码页
##### 类型定义
```ts
interface ForgetPageProps {
  extra?: {
    // 背景图	
    BgSlot?: React.FC<{ bg: string }>;
  };
  customStyle?: {
	// 自定义表单样式
    formStyle?: React.CSSProperties;
  };
}
```

###### 示例
```tsx
// ./src/pages/forget/index.tsx
import { ForgetPage } from "@tuya-sat/sdf-main-sdk";

export default (props: ForgetPageProps) => {
  return <ForgetPage {...props} />
}
```

#### 3. 注册页
##### 类型定义
```ts
interface RegisterPageProps {
  extra?: {
    // 背景图	
    BgSlot?: React.FC<{ bg: string }>;
  };
  customStyle?: {
    // 自定义表单样式
    formStyle?: React.CSSProperties;
  };
}
```

##### 示例
```tsx
// ./src/pages/register/index.tsx
import { RegisterPage } from "@tuya-sat/sdf-main-sdk";

export default (props: RegisterPageProps) => {
  return <RegisterPage {...props} />
}
```

#### 4. 设置页
##### 类型定义
```ts
interface SettingPageProps {
  extra?: {
    beforeSlot?: React.FC;
    afterSlot?: React.FC;
  };
}

```

##### 示例
```tsx
// ./src/pages/setting/index.tsx
import { SettingPage } from "@tuya-sat/sdf-main-sdk";

export default (props: RegisterPageProps) => {
  return <SettingPage {...props} />
}
```

#### 5. 自定义 `Layout`
构建工具会读取 `/src/layout` 目录下的文件，作为自定义布局的实现。其类型定义如下：
```tsx
interface LayoutProps {
  // SaaS 标题
  title: string;
  // SaaS logo
  logo: string;
  bgImage: string;
  currentLanguage: string;
  supportLanguages: Array<{
    label: string;
    value: string;
  }>;
  // SaaS 菜单
  menus: Array<IMenu>;
}

interface Layout extends FC<LayoutProps> {
  Login?: FC<Partial<LayoutProps>>;
}
```

### 头部自定义配置
#### 类型定义
```ts
type NavProps = {
  extra?: {
    // 自定义logo、标题
    LogoTitleSlot?: FC<{ title?: string; logo?: string }>;
    // 自定义右上角导航
    HeaderNavSlot?: FC;
    BeforeHeaderNavSlot?: FC;
    AfterHeaderNavSlot?: FC;
    // 自定义用户信息
    UserSlot?: FC;
    // 自定义 app 二维码下载
    QrcodeSlot?: FC;
  };
  hooks?: {
    modify?: (dmenus: Dmenus) => Dmenus;
    customUserRoles?: () => Promise<{
      role_code: string;
      role_name: string;
    }[]>;
  };
};
```

#### 示例
```tsx
// ./src/nav/index.tsx
import { MNav } from "@tuya-sat/sdf-main-sdk";

export default (props: NavProps) => {
  return <SettingPage {...props} />
}
```

### 主题色
如果需要修改主题色，可以通过 `useTheme(initConfigs)`。如下：
```tsx
import { ConfigProviderProps } from 'antd';
import { initialTheme, useTheme } from "@tuya-sat/sdf-main-sdk";

// 初始化亮暗色、主题色配置
initialTheme({
  dark: {},
  light: {},
});

export default function Layout() {
  const { theme, changeTheme } = useTheme();
	
  return (
    <>
      {theme}: <button onClick={() => changeTheme()}>切换主题<button>
    </>		
  );
};
```

### 多语言
#### 1. 新增词条 :bangbang:
如果新增了自定义组件，那其多语言需要存放到 `./src/lang` 目录下，并且在组件内部需要引用 sdk 暴露的 i18n 方法进行翻译。如下：

```tsx
import { i18n } from '@tuya-sat/sdf-main-sdk';

export default function CustomComponent() {
  return (
	// 注意：必须使用 sdk 暴露的 i18n 方法
    <div>{i18n.t('some key')}</div>
  )
}
```

#### 2. 语言切换下拉框
主应用 sdk 默认有导出语言下拉框，可以直接使用。方式如下：
```tsx
import { SwitchLangSelect } from "@tuya-sat/sdf-main-sdk";

export default function Demo() {
  return (
    <>
	  <SwitchLangSelect />
    </>
  );
}
```

如果该组件无法满足需求，还可以通过 hook 方式获取 saas 语言信息，自己编写组件实现逻辑。如下：

```tsx
import { useMultiLanguages } from "@tuya-sat/sdf-main-sdk";

export default function LanguageSelect() {
  const {
    currentLanguage,
    supportLanguages,
    switchLang,
  } = useMultiLanguage();

  return (
    <>
      {/* ......自定义业务逻辑 */}
    </>
  );	
}
```
