npm package: @qxs-bns/components
Description: QXS Business Components - Web Components (framework agnostic)
Latest version: 0.0.91

# @qxs-bns/components

QXS Business System 组件库，基于 Vue 3 + TypeScript + Element Plus 构建的企业级组件库。

## ✨ 特性

- 🎯 **企业级** - 专为企业级应用设计，提供丰富的业务组件
- 🔧 **二次封装** - 基于 Element Plus 进行增强，保持 API 一致性
- 📦 **按需加载** - 支持 Tree-shaking，减少打包体积
- 🎨 **主题定制** - 支持主题定制和暗黑模式
- 🌍 **国际化** - 内置国际化支持
- 📱 **响应式** - 移动端友好的响应式设计
- ♿ **无障碍** - 遵循 WAI-ARIA 标准
- 🎪 **TypeScript** - 完整的 TypeScript 类型定义

## 📦 安装

```bash
# npm
npm install @qxs-bns/components

# yarn
yarn add @qxs-bns/components

# pnpm
pnpm add @qxs-bns/components
```

## 🚀 快速开始

### 完整引入

```typescript
import { createApp } from 'vue'
import QxsComponents from '@qxs-bns/components'
import '@qxs-bns/components/dist/index.css'
import App from './App.vue'

const app = createApp(App)
app.use(QxsComponents)
app.mount('#app')
```

### 按需引入

```vue
<template>
  <QxsButton type="primary">按钮</QxsButton>
  <QxsTinyMceEditor v-model="content" />
</template>

<script setup>
import { ref } from 'vue'
import { QxsButton, QxsTinyMceEditor } from '@qxs-bns/components'

const content = ref('<p>旧版富文本编辑内容</p>')
</script>
```

## 🏗️ 项目结构

```
packages/components/
├── src/                    # 源码目录
│   ├── button/            # 按钮组件
│   │   ├── src/           # 组件源码
│   │   │   └── button.vue
│   │   ├── style/         # 组件样式
│   │   │   └── index.scss
│   │   └── index.ts       # 组件入口
│   ├── tiny-mce-editor/   # 富文本编辑器
│   ├── utils/             # 工具函数
│   ├── constants/         # 常量定义
│   └── index.ts           # 总入口
├── dist/                  # 构建产物
├── docs/                  # 组件文档
└── package.json
```

## 🧩 核心组件

### 基础组件

| 组件名 | 说明 | 文档 |
|--------|------|------|
| Button | 按钮组件 | [查看文档](./src/button/README.md) |
| Input | 输入框组件 | [查看文档](./src/input/README.md) |
| Select | 选择器组件 | [查看文档](./src/select/README.md) |

### 数据展示

| 组件名 | 说明 | 文档 |
|--------|------|------|
| TinyMceEditor | 富文本编辑器 | [查看文档](./src/tiny-mce-editor/README.md) |
| Table | 表格组件 | [查看文档](./src/table/README.md) |
| Card | 卡片组件 | [查看文档](./src/card/README.md) |

### 导航组件

| 组件名 | 说明 | 文档 |
|--------|------|------|
| Menu | 菜单组件 | [查看文档](./src/menu/README.md) |
| Breadcrumb | 面包屑组件 | [查看文档](./src/breadcrumb/README.md) |
| Pagination | 分页组件 | [查看文档](./src/pagination/README.md) |

### 反馈组件

| 组件名 | 说明 | 文档 |
|--------|------|------|
| Dialog | 对话框组件 | [查看文档](./src/dialog/README.md) |
| Message | 消息提示组件 | [查看文档](./src/message/README.md) |
| Loading | 加载组件 | [查看文档](./src/loading/README.md) |

## 🛠️ 开发指南

### 环境要求

- Node.js >= 16
- pnpm >= 7
- Vue >= 3.3
- TypeScript >= 4.9

### 本地开发

```bash
# 克隆项目
git clone <repository-url>

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 构建组件库
pnpm build

# 运行测试
pnpm test
```

### 创建新组件

1. **创建组件目录**
   ```bash
   mkdir src/my-component
   cd src/my-component
   ```

2. **创建必要文件**
   ```
   my-component/
   ├── src/
   │   └── my-component.vue
   ├── style/
   │   └── index.scss
   ├── index.ts
   └── README.md
   ```

3. **组件模板**
   ```vue
   <!-- src/my-component.vue -->
   <template>
     <div :class="ns.b()">
       <slot />
     </div>
   </template>

   <script setup lang="ts">
   import { useNamespace } from '@qxs-bns/hooks'

   defineOptions({
     name: 'QxsMyComponent'
   })

   const ns = useNamespace('my-component')
   </script>
   ```

4. **样式文件**
   ```scss
   // style/index.scss
   @use 'sass:map';
   @use '@qxs-bns/theme-chalk/src/mixins/mixins' as *;
   @use '@qxs-bns/theme-chalk/src/common/var' as *;

   @include b(my-component) {
     // 组件样式
   }
   ```

5. **入口文件**
   ```typescript
   // index.ts
   import { withInstall } from '@qxs-bns/utils'
   import MyComponent from './src/my-component.vue'

   export const QxsMyComponent = withInstall(MyComponent)
   export default QxsMyComponent

   export * from './src/my-component'
   ```

## 📋 开发规范

### 组件命名规范

#### Element Plus 二次封装组件
- **组件名**：保持与 Element Plus 一致，添加 `Qxs` 前缀
- **文件名**：使用 kebab-case
- **样式类名**：使用 Element Plus 的 BEM 命名规范

```typescript
// ✅ 正确示例
export const QxsButton = withInstall(Button)
export const QxsInput = withInstall(Input)

// ❌ 错误示例
export const QxsBtn = withInstall(Button)
export const QxsInputField = withInstall(Input)
```

#### 自定义组件
- **组件名**：使用 `@qxs-bns/hooks` 中的 `useNamespace` 函数
- **文件名**：使用 kebab-case
- **样式类名**：使用 BEM 命名规范

```vue
<script setup lang="ts">
import { useNamespace } from '@qxs-bns/hooks'

defineOptions({
  name: 'QxsMyComponent' // 组件名使用 PascalCase
})

const ns = useNamespace('my-component') // 使用 kebab-case
</script>

<template>
  <div :class="ns.b()"> <!-- 生成 .qxs-my-component -->
    <div :class="ns.e('header')"> <!-- 生成 .qxs-my-component__header -->
      <div :class="ns.em('header', 'title')"> <!-- 生成 .qxs-my-component__header--title -->
```

### Props 设计规范

```typescript
// 使用 TypeScript 接口定义 Props
interface ButtonProps {
  /**
   * 按钮类型
   * @default 'default'
   */
  type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default'

  /**
   * 按钮尺寸
   * @default 'default'
   */
  size?: 'large' | 'default' | 'small'

  /**
   * 是否禁用
   * @default false
   */
  disabled?: boolean

  /**
   * 是否加载中
   * @default false
   */
  loading?: boolean
}

// 使用 withDefaults 设置默认值
const props = withDefaults(defineProps<ButtonProps>(), {
  type: 'default',
  size: 'default',
  disabled: false,
  loading: false
})
```

### 事件设计规范

```typescript
// 定义事件类型
interface ButtonEmits {
  /**
   * 点击事件
   * @param event 原生事件对象
   */
  click: [event: MouseEvent]

  /**
   * 焦点事件
   * @param event 原生事件对象
   */
  focus: [event: FocusEvent]

  /**
   * 失焦事件
   * @param event 原生事件对象
   */
  blur: [event: FocusEvent]
}

const emit = defineEmits<ButtonEmits>()

// 事件处理
const handleClick = (event: MouseEvent) => {
  if (props.disabled || props.loading) return
  emit('click', event)
}
```

### 样式规范

#### SCSS 变量使用

```scss
// 使用主题变量
@use '@qxs-bns/theme-chalk/src/common/var' as *;

@include b(button) {
  // 使用 CSS 变量
  background-color: var(#{getCssVarName('button', 'bg-color')});
  border-color: var(#{getCssVarName('button', 'border-color')});
  color: var(#{getCssVarName('button', 'text-color')});

  // 状态样式
  @include when(disabled) {
    opacity: var(#{getCssVarName('disabled-opacity')});
    cursor: not-allowed;
  }

  // 修饰符样式
  @include m(primary) {
    background-color: var(#{getCssVarName('color-primary')});
    border-color: var(#{getCssVarName('color-primary')});
  }
}
```

#### BEM 命名规范

```scss
// Block（块）
.qxs-button { }

// Element（元素）
.qxs-button__icon { }
.qxs-button__text { }

// Modifier（修饰符）
.qxs-button--primary { }
.qxs-button--large { }

// State（状态）
.qxs-button.is-disabled { }
.qxs-button.is-loading { }
```

## 🧪 测试规范

### 单元测试

```typescript
// button.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Button from '../src/button.vue'

describe('Button', () => {
  it('should render correctly', () => {
    const wrapper = mount(Button, {
      props: { type: 'primary' },
      slots: { default: 'Click me' }
    })

    expect(wrapper.classes()).toContain('qxs-button--primary')
    expect(wrapper.text()).toBe('Click me')
  })

  it('should emit click event', async () => {
    const wrapper = mount(Button)
    await wrapper.trigger('click')

    expect(wrapper.emitted('click')).toHaveLength(1)
  })

  it('should not emit click when disabled', async () => {
    const wrapper = mount(Button, {
      props: { disabled: true }
    })
    await wrapper.trigger('click')

    expect(wrapper.emitted('click')).toBeUndefined()
  })
})
```

### 组件快照测试

```typescript
// button.snap.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Button from '../src/button.vue'

describe('Button Snapshots', () => {
  it('should match snapshot with different types', () => {
    const types = ['primary', 'success', 'warning', 'danger', 'info']

    types.forEach(type => {
      const wrapper = mount(Button, {
        props: { type },
        slots: { default: 'Button' }
      })
      expect(wrapper.html()).toMatchSnapshot(`button-${type}`)
    })
  })
})
```

## 📖 文档规范

### 组件文档模板

每个组件都应该包含完整的 README.md 文档：

```markdown
# Button 按钮

常用的操作按钮。

## 基础用法

基础的按钮用法。

:::demo 使用 `type`、`plain`、`round` 和 `circle` 属性来定义 Button 的样式。

button/basic

:::

## API

### Button Attributes

| 属性名 | 说明 | 类型 | 可选值 | 默认值 |
|--------|------|------|--------|--------|
| type | 类型 | string | primary / success / warning / danger / info / text | — |
| size | 尺寸 | string | large / small / mini | — |
| disabled | 是否禁用状态 | boolean | — | false |

### Button Events

| 事件名 | 说明 | 参数 |
|--------|------|------|
| click | 点击时触发 | (event: Event) |

### Button Slots

| 插槽名 | 说明 |
|--------|------|
| — | 默认插槽 |
```

## 🤝 贡献指南

### 提交规范

使用 [Conventional Commits](https://conventionalcommits.org/) 规范：

```bash
# 新功能
feat(button): add loading state support

# 修复 bug
fix(data-chart): resolve chart rendering issue

# 文档更新
docs(readme): update installation guide

# 样式调整
style(button): adjust padding and margin

# 重构代码
refactor(utils): optimize namespace function

# 性能优化
perf(table): improve virtual scrolling performance

# 测试相关
test(button): add unit tests for disabled state
```

### 开发流程

1. **Fork 项目**
2. **创建功能分支**
   ```bash
   git checkout -b feat/new-component
   ```
3. **开发组件**
   - 编写组件代码
   - 添加单元测试
   - 编写文档
4. **提交代码**
   ```bash
   git add .
   git commit -m "feat(new-component): add new component"
   ```
5. **推送分支**
   ```bash
   git push origin feat/new-component
   ```
6. **创建 Pull Request**

### 代码审查清单

- [ ] 组件命名符合规范
- [ ] TypeScript 类型定义完整
- [ ] 单元测试覆盖率 > 80%
- [ ] 文档完整且示例可运行
- [ ] 样式符合设计规范
- [ ] 无障碍性支持
- [ ] 国际化支持

## 🔧 常见问题

### Q: 如何自定义主题？

A: 通过 CSS 变量覆盖默认主题：

```css
:root {
  --qxs-color-primary: #409eff;
  --qxs-color-success: #67c23a;
  --qxs-color-warning: #e6a23c;
  --qxs-color-danger: #f56c6c;
  --qxs-color-info: #909399;
}
```

### Q: 如何按需引入组件？

A: 使用 ES6 模块语法：

```typescript
import { QxsButton, QxsInput } from '@qxs-bns/components'
```

### Q: 如何贡献新组件？

A: 请参考 [贡献指南](#🤝-贡献指南) 部分。

## 📄 许可证

MIT License - 详见 [LICENSE](./LICENSE) 文件。