npm package: @skb_skb/kb-components
Description: 通用组件库，包含 Element UI 扩展和 UniApp 组件
Author: skb
Latest version: 1.0.38

# kb-components

一个基于Vue.js的通用组件库，提供丰富的UI组件、工具方法和指令，适用于Vue 2项目开发。

## 特性

- 提供多种实用的自定义指令（滚动处理、拖拽等）
- 丰富的工具函数库（验证、格式化、防抖节流等）
- 常用混入方法（验证码、表格分页、下拉刷新等）
- 支持Element UI组件扩展
- 支持uni-app组件
- 易于集成和使用

## 快速开始

### 安装

```bash
npm i @skb_skb/kb-components
```

### 引入

在main.js中全局引入：

```javascript
import Vue from 'vue'
import KbComponents from '@skb_skb/kb-components'

Vue.use(KbComponents)
```

## 核心功能

### 1. 自定义指令

#### v-wheelX
用于将竖向滚动转换为横向滚动，适用于横向滚动数据列表。

```vue
<div v-wheelX>
  <!-- 横向滚动内容 -->
</div>
```

#### v-loadmore
用于滚动分页场景，传入加载更多的回调函数。

```vue
<div v-loadmore="loadMoreData">
  <!-- 滚动加载内容 -->
</div>

<script>
export default {
  methods: {
    loadMoreData() {
      // 加载更多数据的逻辑
    }
  }
}
</script>
```

#### v-draggable
用于实现元素拖拽功能。

```vue
<div v-draggable>
  <!-- 可拖拽元素 -->
</div>
```

### 2. 工具方法

可以局部引入工具方法：

```javascript
import { tool } from '@skb_skb/kb-components'
```

#### 字典查询

```javascript
// 根据字典查询对应value/label
const result = tool.getValueOrLabelFromDict({
  label: '', // 可选，若未传入value，根据传入label找到对应value
  value: '', // 可选，根据value找到对应label
  valueField: 'value',  // 字典对象中value对应字段，默认为value
  labelField: 'label', // 字典对象中label对应字段，默认为label
  list: [],  // 字典列表
  emptyText: '' // 如果未查找到对应值，返回的默认文本，默认为空字符串
})
```

#### 数据验证

```javascript
// 判断身份证号格式是否正确
tool.isIdCard(str)

// 判断手机号是否正确
tool.isPhone(str)

// 判断座机是否正确
tool.isLandline(str)

// 判断邮箱是否正确
tool.isEmail(str)

// 判断URL是否正确
tool.isURL(str)

// 返回小写变量类型
tool.returnType(data)
```

#### 日期和时间

```javascript
// 根据生日计算年龄
tool.calcAgeByBrithday(birthday)

// 日期格式化
tool.dateFormat(date, format = 'yyyy-MM-dd')
```

#### 函数节流和防抖

```javascript
// 防抖：在最后一次触发后延迟指定时间执行函数
const debouncedFunc = tool.debounce(func, wait)

debouncedFunc() // 调用防抖函数

// 节流：指定时间间隔内只执行一次函数
const throttledFunc = tool.throttle(func, wait, hasDebounce = false)

throttledFunc() // 调用节流函数
// hasDebounce为true时，结合防抖，停止触发后会额外执行一次
```

### 3. 混入方法

可以局部引入混入方法：

```javascript
import { mixins } from '@skb_skb/kb-components'
```

#### 发送验证码混入

```javascript
export default {
  mixins: [mixins.sendCodeMixins],
  methods: {
    sendCode() {
      // 发送验证码逻辑
      this.startNumberDown(60) // 开始60秒倒计时
    }
  }
}
```

**提供的属性和方法：**
- `this.startNumberDown(num)`: 开始倒计时，传入倒计时时间
- `this.btnDisabled`: 发送验证码按钮禁用状态
- `this.btnText`: 发送验证码按钮文字
- `this.time`: 倒计时时间
- `this.timer`: 定时器ID

#### 表格分页混入

```javascript
export default {
  mixins: [mixins.tableMixins],
  methods: {
    fetch() {
      // 重写fetch方法，实现列表数据请求逻辑
      this.tableLoading = true
      // 请求数据...
      this.tableLoading = false
    }
  }
}
```

**提供的属性和方法：**
- `this.pages.size`: 页大小
- `this.pages.current`: 当前页
- `this.pages.total`: 总计
- `this.tableList`: 列表数据
- `this.tableLoading`: 列表加载状态
- `this.fetch()`: 需要自己重新定义请求分页列表方法
- `this.searchFetch()`: 请求第一页
- `this.handleSizeChange(size)`: 页大小改变
- `this.handleCurrentChange(page)`: 当前页改变

#### 下拉刷新混入

```javascript
export default {
  mixins: [mixins.pullRefreshMixins],
  methods: {
    fetch() {
      // 重写fetch方法，实现列表数据请求逻辑
      // 请求数据...
      this.handlePageResult(res)
    }
  }
}
```

**提供的属性和方法：**
- `this.search.size`: 页大小
- `this.search.current`: 当前页
- `this.search.total`: 总计
- `this.tableList`: 列表数据
- `this.loading`: 列表加载状态
- `this.finished`: 全部加载完成
- `this.refreshing`: 上拉刷新状态
- `this.finishedText`: 加载完成文案
- `this.isEmpty`: 是否为空
- `this.fetch()`: 需要自己重新定义请求分页列表方法
- `this.onRefresh()`: 下拉刷新
- `this.handleLoadMore()`: 上拉加载更多
- `this.handlePageResult()`: 请求结果处理
- `onPullDownRefresh`: 小程序监听下拉刷新生命周期
- `onReachBottom`: 小程序监听触底生命周期

## uni-app组件

在pages.json中配置easycom：

```json
{
  "easycom": {
    "autoscan": true,
    "custom": {
      "^fe-(.*)": "kb-components/uniapp/fe-$1/fe-$1.vue"
    }
  }
}
```

### fe-addr-picker 地址选择组件

```vue
<fe-addr-picker 
  ref="addr"
  @change="handleAddrChange"
  @cancel="dialogShow = false"
  :requireAddrMethod="requireAddrMethod"
  :maxLevel="4"
></fe-addr-picker>

<script>
export default {
  methods: {
    initAddrPicker() {
      this.$refs.addr.init(selectAddrIds); // selectAddrIds默认选中地址id列表
    },
    requireAddrMethod() {
      // 请求收货地址列表方法
    },
    handleAddrChange(selected) {
      // 处理选择结果
    }
  }
}
</script>
```

**属性说明：**
- `maxLevel`: 地址级数，省1/市2/区3/街道4，默认3级，最大4级
- `requireAddrMethod`: 请求收货地址列表方法
- **事件**：
  - `@cancel`: 取消事件
  - `@change`: 确定事件

### fe-cutdown 倒计时组件

```vue
<fe-cutdown 
  :endTime="endTime"
  class="fz-12 c-dark"
  bgColor="transparent"
  color="var(--fe-content-color)"
  @finish="handleFinish"
></fe-cutdown>
```

**属性说明：**
- `endTime`: 结束时间
- `separator`: 时间间隔符，默认:
- `bgColor`: 文字背景颜色，默认transparent
- `color`: 文字颜色，默认#000
- **事件**：
  - `@finish`: 倒计时结束事件

### fe-fixed-btn 底部悬浮按钮

```vue
<!-- 最外层容器增加class="has-fixed-btn" -->
<div class="has-fixed-btn">
  <!-- 页面内容 -->
  <fe-fixed-btn></fe-fixed-btn>
</div>
```

### fe-fixed-tabs 顶部悬浮选项卡

```vue
<!-- 最外层容器增加class="has-fixed-tab" -->
<div class="has-fixed-tab">
  <fe-fixed-tabs 
    v-model="type"
    :list="tabList"
    @input="handleTabChange"
  ></fe-fixed-tabs>
  <!-- 页面内容 -->
</div>
```

**属性说明：**
- `v-model`: 选中的值
- `border`: 是否有下边框，默认有
- `activeColor`: 选中颜色
- `inactiveColor`: 未选中颜色
- `list`: 选项列表，格式 [{value:'',label:""}]
- **事件**：
  - `@input`: 选中值变化事件

### fe-empty 空组件

```vue
<fe-empty 
  :icon="customIcon"
  :message="'暂无数据'"
  :size="100"
>
  <!-- 额外内容插槽 -->
  <template v-slot:default>
    <button>添加数据</button>
  </template>
  <!-- 自定义图片插槽 -->
  <template v-slot:image>
    <img src="custom-img.png" />
  </template>
  <!-- 自定义描述插槽 -->
  <template v-slot:description>
    <span>暂无相关数据</span>
  </template>
</fe-empty>
```

**属性说明：**
- `icon`: 图片链接
- `message`: 提示文案
- `size`: 图片大小

### fe-modal 模态窗

```vue
<fe-modal 
  v-model="dialogShow"
  position="bottom"
  title="选择收货地址"
  :showClose="true"
  :closeOnClickOverlay="true"
>
  <!-- 模态窗内容 -->
</fe-modal>
```

**属性说明：**
- `v-model/value`: 控制显示隐藏
- `closeOnClickOverlay`: 点击空白处背景是否关闭，默认false
- `showTitle`: 是否展示标题，默认true
- `showClose`: 点击展示关闭按钮，默认true
- `showCancel`: 是否展示取消按钮，默认true
- `showBtn`: 是否展示下方按钮，默认true
- `custom`: 是否自定义弹窗，默认false
- `width`: 宽度，默认0
- `height`: 高度，默认0
- `title`: 弹窗标题，默认"温馨提示"
- `cancelText`: 取消按钮文案，默认"取消"
- `confirmText`: 确定按钮文案，默认"确定"
- `confirmLoading`: 确定按钮加载状态，加载状态按钮禁用，默认false
- `position`: 弹窗位置，默认"center"，可选 "center" "bottom" "right"
- **事件**：
  - `@confirm`: 点击确定按钮事件
  - `@cancel`: 点击取消按钮事件
  - `@close`: 弹窗关闭事件

### fe-navbar 自定义标题栏

```vue
<fe-navbar 
  :show-home="showHome"
  title="页面标题"
  @home="handleHome"
  @back="handleBack"
></fe-navbar>
```

**属性说明：**
- `title`: 标题
- `showBack`: 是否展示返回按钮，默认true
- `showHome`: 是否展示返回首页按钮，默认false
- `background`: 背景色，默认#fff
- **事件**：
  - `@back`: 返回上一页事件
  - `@home`: 返回首页事件

### fe-sku 规格组件

```vue
<fe-sku 
  v-model="localSkuId"
  :defaultOptions="defaultOptions"
  ref="sku"
  @change="handleSkuChange"
></fe-sku>

<script>
export default {
  data() {
    return {
      localSkuId: '',
      defaultOptions: {
        id: 'skuId', // id
        name: 'skuName', // 名称
        price: 'price', // 价格
        stock_num: 'stocks', // 库存
        prop: 'properties' // 规格属性
      }
    }
  },
  mounted() {
    // 初始化规格数据
    this.$refs.sku.init(this.skuList);
  },
  methods: {
    handleSkuChange(sku) {
      // 处理规格变更
    }
  }
}
</script>
```

**属性说明：**
- `v-model/value`: 当前skuId
- `defaultOptions`: 规格对象字段配置
- **事件**：
  - `@change`: 当前选中sku变更事件

## Element UI组件

### CommonSearch 搜索栏组件

```javascript
import CommonSearch from 'kb-components/element/common-search'

export default {
  components: {
    CommonSearch
  }
}
```

```vue
<common-search 
  :labelWidth="100"
  :search="search"
  @search="handleSearch"
  @reset="handleReset"
></common-search>
```

**属性说明：**
- `labelWidth`: label宽度，传入数字，默认60
- `isRow`: 是否使用栅格布局24格，默认false
- `actionColSpan`: 按钮在栅格布局中所占比例
- `search`: 搜索配置列表，格式如下：

```javascript
search: [
  {
    label: '科室', 
    type: 'select', // 搜索类型 select/input/date/cascader/radio/numberRange/pageSelect
    name: 'deptId', // 搜索key
    default: '', // 默认值，重置时恢复到默认值
    placeholder: '请选择科室', 
    span: 6, // 栅格布局中占用格数
    immediately: false, // change时是否立即触发查询
    clearable: false, // 是否展示清空内容按钮，默认true
    extraData: {}, // type为pageSelect时分页方法额外的参数
    requestMethod: function() {}, // type为pageSelect时传入分页请求方法
    options: [{label:"",value:""}], // 可选项，type为select/cascader/radio需传入该数据
    defaultProps: {
      // 除日期组件外字段
      multiple: false,
      emitPath: false,
      label: 'label',
      value: 'value',
      // 日期组件使用字段
      valueFormat: 'yyyy-MM-dd',
      type: 'daterange',
      startPlaceholder: '开始日期',
      endPlaceholder: '结束日期',
      placeholder: '请选择日期',
      pickerOptions: {}
    }
  }
]
```

**事件：**
- `@search`: 搜索事件
- `@reset`: 重置事件

### DialogContainer 模态框组件

```javascript
import DialogContainer from 'kb-components/element/dialog-container'

export default {
  components: {
    DialogContainer
  }
}
```

```vue
<dialog-container 
  :type="2"
  :visible.sync="dialogShow"
  title="弹窗标题"
  :showFooter="true"
>
  <!-- 弹窗内容 -->
  <template v-slot:footer>
    <!-- 自定义页尾 -->
  </template>
</dialog-container>
```

**属性说明：**
- `title`: 标题
- `visible.sync`: 显示隐藏双向绑定
- `width`: 宽度，默认30%
- `showFooter`: 是否展示页尾，默认false
- `type`: 弹窗类型，1-dialog/2-drawer，默认1

**插槽：**
- `default`: 弹窗内容插槽
- `footer`: 页尾插槽

### MyEchart ECharts组件

```javascript
import MyEchart from 'kb-components/element/my-echart'

export default {
  components: {
    MyEchart
  },
  mounted() {
    // 初始化图表
    const option = {
      // ECharts配置项
    }
    this.$refs.bar.init(option)
  }
}
```

```vue
<my-echart ref="bar"></my-echart>
```

**注意：** 需要在main.js中引入echarts

```javascript
import echarts from 'echarts'
```

### PreviewPrint 打印预览组件

```javascript
import PreviewPrint from 'kb-components/element/preview-print'

export default {
  components: {
    PreviewPrint
  }
}
```

```vue
<PreviewPrint 
  v-model="recipeDialogShow"
  printId="recipePrint"
  :url="printPDFUrl"
  @print="handleRecipePrint"
>
  <!-- 预览/打印内容插槽 -->
</PreviewPrint>
```

**属性说明：**
- `url`: 预览pdf链接
- `printId`: 指定预览的id，要求唯一，默认 "print"
- `value/v-model`: 显示隐藏

**注意：** 需要在main.js中引入 vue-print-nb

```javascript
import Print from 'vue-print-nb'
Vue.use(Print)
```

### TableContainer 分页列表容器组件

```javascript
import TableContainer from 'kb-components/element/table-container'

export default {
  components: {
    TableContainer
  }
}
```

```vue
<table-container 
  v-bind="pages"
  @current="handleCurrentChange"
  @size="handleSizeChange"
></table-container>
```

**属性说明：**
- `layout`: 分页操作栏配置，默认 "total, sizes, prev, pager, next, jumper"
- `total`: 分页总计
- `current`: 当前页
- `size`: 页大小
- `pagesSizes`: 页大小可选项，默认 [10,20,50,100]

**事件：**
- `@current`: 当前页变更事件，传参当前页码
- `@size`: 页大小变更事件，传参当前页大小

### WheelxTableBox el-table横向滚动容器

```javascript
import WheelxTableBox from 'kb-components/element/wheelx-table-box'

export default {
  components: {
    WheelxTableBox
  }
}
```

```vue
<wheelx-table-box 
  :left="leftLength"
  ref="timeBox"
>
  <!-- el-table或其他需要横向滚动的内容 -->
</wheelx-table-box>
```

**属性说明：**
- `left`: 初始左偏移量，默认0

**注意：** 需注册v-wheelX指令

### MyEditor 富文本编辑器

```javascript
import MyEditor from 'kb-components/element/my-editor'

export default {
  components: {
    MyEditor
  },
  methods: {
    ossUpload(file) {
      // 文件上传方法
      return new Promise((resolve, reject) => {
        // 上传逻辑
        // resolve({path: '相对路径', fullPath: '完整路径'})
      })
    }
  }
}
```

```vue
<MyEditor 
  ref="editor"
  v-model="formData.eduContent"
  :cache="false"
  :upload-method="ossUpload"
></MyEditor>
```

**属性说明：**
- `value/v-model`: 文本内容
- `cache`: 是否缓存内容
- `upload-method`: 文件上传方法，promise，入参 {raw 文件二进制流,size 文件大小,name 文件名称}，出参 {path 相对路径,fullPath 完整路径}

## 开发指南

### 打包

```bash
npm run package
```

### 发布

```bash
npm publish --access public
```

## 浏览器兼容性

- Chrome (推荐)
- Firefox
- Safari
- Edge
- IE 11 (部分功能可能受限)

## 注意事项

1. 本组件库基于Vue 2开发，不兼容Vue 3
2. 使用Element UI相关组件前，请确保已正确引入Element UI
3. 使用uni-app组件前，请确保项目环境为uni-app
4. 部分组件需要额外的依赖支持，请参考各组件的说明文档

## License

MIT