Skip to content

组件开发

APlus-UI通用组件和业务组件编写指南

目录结构

组件开发写在目录aplus-ui/src下,比如Button组件目录结构如下:

-- aplus-ui
  -- src
     -- button
        __test__ 测试用例目录
        -- style 样式目录
          -- index.ts 样式文件
        -- button.vue 组件
        -- index.ts 组件导出入口
     -- index.ts 所有组件导出入口文件
     -- locale 组件国际化配置目录
     -- utils 组件通用的utils目录
     -- type.ts 组件库通用类型定义
     -- compoennt.d.ts 全局组件类型导出文件
  -- index.ts 组件库入口文件

对于业务组件而言,则需要写在aplus-ui/src/business目录下。

如何确定业务组件?

业务组件一般是通用组件(如antdv中的组件)的上层封装,更低的抽象、包含特定的业务逻辑和交互、特定的适用场景。

如果组件有通用的适用场景(如对通用组件做功能增强),在APlus-UI中仍然被认为通用组件。

开发流程

以下流程假定你在开发组件的同时编写文档

在根目录运行pnpm docs:dev启动文档的开发服务,该服务会在组件/样式/文档变动后自动重启(或热更新)

为组件编写样式

编写样式

所有的组件的样式都应该编写在src/[component-name]/style目录下。

一个完整的样式文件包含以下几个部分:

  1. 确定需要将那些属性设置为Design Token,并且创建对应组件的token类型,比如:
ts
export type ActionToken = {
  /**
   * 加载中时的组件透明度
   */
  loadingOpacity: number;
};

上例为ApAction组件创建了token类型,其中包含一个属性loadingOpacity即自定义加载中时的组件透明度。

  1. 编写具体的样式,创建一个类型为GenStyleFunc的函数(一般命名为gen[componentName]Style),然后编写样式即可,比如:
ts
const genActionStyle: GenStyleFunc<ActionToken> = (token) => {
  const { componentCls } = token;
  return {
    [componentCls]: {
      width: '33.3333%',
      border: '1px solid gray',

      '&--loading': {
        opacity: token.loadingOpacity
      }
    }
  }
}
  1. 使用genComponentStyleHook函数组装编写的样式,并返回一个可组合函数(Hook)。
ts
export default genComponentStyleHook<ActionToken>(
  'ApAction',
  (token) => {
    return [genActionStyle(token)];
  },
  { loadingOpacity: 0.35 },
  {
    skipUnit: ['loadingOpacity']
  }
);

上例传递了token的默认值,并通过option.skipUnit跳过为token添加单位(px)。

在组件中使用样式

在组件中,需要使用useNamespace提供的api来获取类名,例如:

ts
const { b, m } = useNamespace('action-item');

导入样式文件中的可组合函数,

ts
import useStyle from './style';

然后使用这些useStyle和这些api来生成最终的类名,例如:

vue
<template>
  <div :class="[b(), m(color), hashId]" />
</template>
<script setup lang="ts">
  const { b, m } = useNamespace('action-item');
  const hashId = useStyle('action-item');
</script>

useStyle会返回一个hash后的类名,需要将其绑定节点上,如果遇到组件中包含全局组件(Modal等),可能需要将hashId多次绑定。

TIP

以上在编写CSS时,请遵循BEM命名规范。

主题和 UI Mode

Aplus-UI基于@aplus-frontend/antdv开发,antdv通过Design Token实现主题,而APlus-UI使用Design Token以及UI模式来确定主题。

你可以在src/design-token目录下通过design-token来修改组件的token,同时此目录下也托管了antdv在不同UI模式下的token预设以及APlus-UI自身在uiMode='admin'下的token预设。

使用全局样式覆盖?

如果你想通过全局样式覆盖的方式修改antdv组件,请不要这么做,其可能会造成难以估量的后果!

更好的方式是,使用design-token或者开发一个新组件,在该组件下通过样式覆盖的方式修改 css 被认为是允许的。

如果新开发的组件则建议使用以下两种方式来兼容 UI 风格:

  1. 如果不同的UI风格会导致渲染结构或是样式属性上的区别,建议使用uiMode加以判断。
vue
<template>
  <div :class="[b(), m(uiMode)]" />
</template>
<script setup lang="ts">
const { b, m } = useNamespace('action-item');
const uiMode = useGlobalConfig('uiMode'); // aplus | admin
</script>

在编写css时,建议分为通用类、UI 风格类。

  1. 如果 UI 风格并不会导致结构上的差异,则可以使用Design Token,默认情况下组件支持uiMode='aplus'的UI模式,然后将另一个UI模式的token维护到design-token目录中对应的预设下。

多语言

如果组件有多语言的需求,请将多语言词条在locale/lang对应语言文件中,首先查看common下是否有合适的多语言词条,如果没有则需要按照规则新建词条即可,请注意,为了让多语言能够正常工作,你需要在语言文件中保持一致的结构。

维护好词条后,需要在组件代码中使用useLocale来获取多语言和翻译函数。

vue
<template>
  <p>{{ t('ap.common.more') }}</p>
</template>
<script setup lang="ts">
import { useLocale } from '../../config-provider';

const { t } = useLocale();
</script>

全局化配置

在开发组件时,你还可以通过ConfigProvider组件设置全局生效的配置,这对于某些业务组件而言可能会很有用,使用全局化配置很简单,主要有两步:

  1. 定义类型

config-provider目录下的config-provider-props.ts定义配置的类型

提示

如果是配置全局接口,请在ApiType下添加即可。

  1. 添加到全局配置中

config-provider/config-provider.tsx文件中,修改以下代码:

ts
const cfg = computed(() => ({
   ...,
   myConfig: props.myConfig
  // 添加你的全局化配置
}));

设置完后,就可以通过useGlobalConfig('myConfig')在组件中,拿到全局化配置了。

全局注册

如果有全局注册的需求(例如Button),请使用如下代码:

ts
import _Button from './button.vue';
import { withInstall } from '@aplus-frontend/utils';

export const Button = withInstall(_Button);
export default Button;

并在components.d.ts中添加该组件的类型声明。

编写测试用例

本项目内置了vitest@vue/test-utils,因此你可以很方便为组件编写单元测试。

  1. 在组件目录下新建__test__目录用于存放测试用例。
  2. 新建xx.test.ts文件并编写测试用例,例如:
ts
describe('ApFieldSlider', () => {
  test('should read mode works', async () => {
    const wrapper = mount(ApFieldSlider, {
      props: {
        value: 10,
        mode: 'read'
      }
    });
    expect(wrapper.text()).toBe('10');
    await wrapper.setProps({
      range: true,
      value: [10, 90]
    });
    expect(wrapper.text()).toBe('10-90');
  });
});
  1. 运行pnpm test运行测试用例,你也可以安装vitestVSCode插件使用图形化界面来运行用例。