• 使用vite+npm封装组件库并发布到npm仓库


    组件库背景:使用elementplus+vue封装了一个通过表单组件。通过JSX对el-form下的el-input和el-button等表单进行统一封装,最后达到,通过数据即可一键生成页面表单的功能。

    1.使用vite创建vue项目

    npm create vite@latest elementplus-auto-form -- --template vue

    2.项目目录

    注意此处main.js入口文件只是当前项目入口文件,组件库打包的入口文件还是封装Form表单组件下的index.js

    3.封装TFrom.vue

    表单+表单校验+JSX生成表单项

    TForm.vue:

    1. <template>
    2. <el-form ref="FormRef"
    3. :class="formBorder?'form-border':''"
    4. :model="modelForm"
    5. :rules="editable ? rules : {}"
    6. :inline="inline"
    7. :label-position="labelPosition"
    8. :label-width="labelWidth">
    9. <slot name="header">slot>
    10. <el-row :gutter="elRowGutter">
    11. <el-col v-for="(item,index) in data"
    12. :span="item.span" :key="index">
    13. <el-form-item v-if="!item.isHidden" :class="isCustom?'custom-form-item':''" :label="item.label ? item.label + ':' : ''"
    14. :prop="item.prop"
    15. :label-width="item.labelWidth">
    16. <FormItem :formData="modelForm"
    17. :editable="editable"
    18. :data="item">
    19. FormItem>
    20. <FormItem v-if="item.children" :formData="modelForm"
    21. :editable="editable"
    22. :clearable="false"
    23. :data="item.children">
    24. FormItem>
    25. el-form-item>
    26. el-col>
    27. <el-col class="button-list" v-if="btnList && btnList.length"
    28. :span="24">
    29. <el-form-item :class="isCustom?'custom-form-item':''">
    30. <div v-for="(item,index) in btnList" :key="index">
    31. <FormButton :formData="modelForm"
    32. :editable="editable"
    33. :data="item"
    34. @on-click="onClick(item)"
    35. >FormButton>
    36. div>
    37. el-form-item>
    38. el-col>
    39. <slot name="footer">slot>
    40. el-row>
    41. el-form>
    42. template>
    43. <script setup>
    44. import { ref } from 'vue'
    45. import formItem from './FormItem.jsx'
    46. import formButton from './FormButton.jsx'
    47. // 除data外其他都不是必传项
    48. const prop = defineProps({
    49. modelForm:{
    50. type: Object,
    51. require: true,
    52. },
    53. rules: {
    54. type: Object,
    55. default: {}
    56. },
    57. data: {
    58. type: Object,
    59. require: true,
    60. default: []
    61. },
    62. inline:{
    63. type: Boolean,
    64. default: true
    65. },
    66. labelWidth: {
    67. type: String,
    68. default: '120'
    69. },
    70. labelPosition: {
    71. type: String,
    72. default: 'right'
    73. },
    74. editable: {
    75. type: Boolean,
    76. default: true
    77. },
    78. colLayout: {
    79. type: Object,
    80. default(){
    81. return {
    82. xl: 5, //2K屏等
    83. lg: 8, //大屏幕,如大桌面显示器
    84. md: 12, //中等屏幕,如桌面显示器
    85. sm: 24, //小屏幕,如平板
    86. xs: 24 //超小屏,如手机
    87. }
    88. }
    89. },
    90. elRowGutter: {
    91. type: Number,
    92. default: 10
    93. },
    94. size: {
    95. type: String,
    96. default: 'default'
    97. },
    98. btnList:{
    99. type: Object,
    100. default: []
    101. },
    102. formBorder:{
    103. type: Boolean,
    104. default: false
    105. },
    106. formRef:{
    107. type: String,
    108. default: 'formRef'
    109. },
    110. customFormItem:{
    111. type: Boolean,
    112. default: false
    113. }
    114. })
    115. const FormItem = formItem();
    116. const FormButton = formButton();
    117. const FormRef = ref()
    118. const isCustom = ref(false);
    119. // 表单按钮
    120. function onClick(data) {
    121. if (!data.onClick) return
    122. data.onClick()
    123. }
    124. // 表单校验
    125. async function validate() {
    126. if (!FormRef.value) return
    127. const result = await FormRef.value.validate()
    128. return result;
    129. }
    130. // 清除表单验证
    131. async function resetFields() {
    132. if(!FormRef.value) return await FormRef.value.resetFields();
    133. return await FormRef.value.resetFields()
    134. }
    135. // 自定义el-form-item样式
    136. if(prop.customFormItem){
    137. isCustom.value = true;
    138. }
    139. defineExpose({
    140. validate,
    141. resetFields,
    142. })
    143. script>
    144. <style scoped>
    145. .button-list{
    146. display: flex;
    147. justify-content: center;
    148. }
    149. .form-border {
    150. width: 94%;
    151. border: solid 2px rgba(219, 217, 217, 0.6);
    152. border-radius: 10px;
    153. margin-left: auto;
    154. margin-right: auto;
    155. padding: 20px;
    156. }
    157. .custom-form-item {
    158. margin-bottom: 4px;
    159. margin-right: 12px;
    160. margin-left: 12px;
    161. }
    162. style>

    FormItem.jsx:

    1. import {
    2. ElInput,
    3. ElSelect,
    4. ElOption,
    5. ElButton
    6. } from 'element-plus'
    7. import { defineComponent } from 'vue'
    8. // 普通显示
    9. const Span = (form, data) => (
    10. <span>{data}span>
    11. )
    12. // 输入框
    13. const Input = (form, data) => (
    14. <ElInput
    15. v-model={form[data.field]}
    16. type={data.type}
    17. input-style={data.inputStyle}
    18. size={data.size}
    19. autocomplete={data.autocomplete}
    20. show-password={data.type == 'password'}
    21. clearable
    22. placeholder={data.placeholder}
    23. autosize = {{
    24. minRows: 3,
    25. maxRows: 4,
    26. }}
    27. {...data.props}
    28. >
    29. ElInput>
    30. )
    31. // 文本框
    32. const Textarea = (form, data) => (
    33. <ElInput
    34. v-model={form[data.field]}
    35. type={data.type}
    36. input-style={data.inputStyle}
    37. size={data.size}
    38. // 设置rows就不能设置自适应autosize
    39. rows={data.rows}
    40. clearable={data.clearable}
    41. placeholder={data.placeholder}
    42. {...data.props}
    43. >{data.rows}
    44. ElInput>
    45. )
    46. const setLabelValue = (_item, { optionsKey } = {}) => {
    47. return {
    48. label: optionsKey ? _item[optionsKey.label] : _item.label,
    49. value: optionsKey ? _item[optionsKey.value] : _item.value,
    50. }
    51. }
    52. // 选择框
    53. const Select = (form, data) => (
    54. <ElSelect
    55. size={data.size}
    56. v-model={form[data.field]}
    57. filterable
    58. style={data.style}
    59. clearable={data.clearable}
    60. placeholder={data.placeholder}
    61. {...data.props}
    62. >
    63. {data.options.map((item) => {
    64. return <ElOption {...setLabelValue(item, data)} />
    65. })}
    66. ElSelect>
    67. )
    68. const Button = (form, data) =>{
    69. <ElButton
    70. type={data.type}
    71. size={data.size}
    72. icon={data.icon}
    73. plain={data.plain}
    74. click={data.clickBtn}
    75. value={data.value}
    76. >ElButton>
    77. }
    78. const setFormItem = (
    79. form,
    80. data,
    81. editable,
    82. ) => {
    83. if (!form) return null
    84. if (!editable) return Span(form, data)
    85. switch (data.type) {
    86. case 'input':
    87. return Input(form, data)
    88. case 'textarea':
    89. return Textarea(form, data)
    90. case 'password':
    91. return Input(form, data)
    92. // 输入框只能输入数字
    93. case 'number':
    94. return Input(form, data)
    95. case 'select':
    96. return Select(form, data)
    97. case 'date':
    98. case 'daterange':
    99. return Date(form, data)
    100. case 'time':
    101. return Time(form, data)
    102. case 'radio':
    103. return Radio(form, data)
    104. case 'checkbox':
    105. return Checkbox(form, data)
    106. case 'button':
    107. return Button(form, data)
    108. default:
    109. return null
    110. }
    111. }
    112. export default () =>
    113. defineComponent({
    114. props: {
    115. data: Object,
    116. formData: Object,
    117. editable: Boolean,
    118. },
    119. setup(props) {
    120. return () =>
    121. props.data
    122. ? setFormItem(props.formData, props.data, props.editable)
    123. : null
    124. },
    125. })

     按需引入elementplus:

    1. // element-plus按需导入
    2. import AutoImport from 'unplugin-auto-import/vite'
    3. import Components from 'unplugin-vue-components/vite'
    4. import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
    5. import vueJsx from '@vitejs/plugin-vue-jsx'
    6. import path from 'path'
    7. ...
    8. plugins: [
    9. vue(),
    10. // 用到JSX语法
    11. vueJsx(),
    12. AutoImport({
    13. resolvers: [ElementPlusResolver()],
    14. }),
    15. Components({
    16. resolvers: [ElementPlusResolver()],
    17. }),
    18. ],
    19. resolve: {
    20. alias: {
    21. '@': path.resolve(__dirname, 'src')
    22. }
    23. },
    24. ...

    通过install插件方式进行使用:

    1. import TForm from "./TForm.vue";
    2. export default {
    3. install (app) {
    4. // 在app上进行扩展,app提供 component directive 函数
    5. // 如果要挂载原型 app.config.globalProperties 方式
    6. // "TForm"自定义即可
    7. app.component("TForm", TForm);
    8. }
    9. }

    4.打包配置

    设置打包文件名,包路径等

    注意打包入口为index.js文件(需要使用导出install方法中的组件),而不是main.js文件(main.js中引入index.js只是用于本地测试)

    1. build: {
    2. outDir: "elementplus-auto-form", //输出文件名称
    3. lib: {
    4. entry: path.resolve(__dirname, "./src/package/index.js"), //指定组件编译入口文件
    5. name: "elementplus-auto-form",
    6. fileName: "elementplus-auto-form",
    7. }, //库编译模式配置
    8. rollupOptions: {
    9. // 确保外部化处理那些你不想打包进库的依赖
    10. external: ["vue"],
    11. output: {
    12. // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
    13. globals: {
    14. vue: "Vue",
    15. },
    16. },
    17. },
    18. },

    npm run build进行打包  

    5.在打好的包下,创建package.json文件 

    在package.json文件中对,包版本等信息进行配置

    1. {
    2. "name": "elementplus-auto-form",
    3. "version": "1.0.0",
    4. "description": "对elementplus的form表单进行封装,达到根据数据一键生成表单功能",
    5. "keywords": ["elementplus","el-form","auto-form"],
    6. "main": "elementplus-auto-form.js",
    7. "scripts": {
    8. "test": "echo \"Error: no test specified\" && exit 1"
    9. },
    10. "author": "xxx",
    11. "license": "ISC",
    12. "private": false
    13. }

    6.上传到npm仓库

    1. 在npm官网创建自己的账号并登录。
    2. 在打包好的文件路径下:使用npm login会跳转到npm官网进行登录;
    3. 登录完成后,将镜像源改为npm官方:npm config set registry=https://registry.npmjs.org
    4. 然后使用npm publish将包上传到npm仓库

    7.从npm下载包并进行测试

    将镜像切回到淘宝源:

    npm config set registry https://registry.npm.taobao.org

    查看当前镜像源:

    npm config get registry 

    配置到淘宝镜像后,首先会到淘宝镜像中下载,没有则去npm官网进行下载 

    下载后node_modules下的包:

    8.代码中使用包elementplus-auto-form

    1. //main.js
    2. import 'elementplus-auto-form/style.css'
    3. import TForm from "elementplus-auto-form";
    4. const app = createApp(App);
    5. app.use(router).use(TForm).mount('#app')

    Form.vue页面使用:

    1. <script setup>
    2. import { reactive } from 'vue'
    3. import cronjobConfig from './cronjobConfig'
    4. const formItems = cronjobConfig.value.formItems ? cronjobConfig.value.formItems : {};
    5. const cronjobForm = reactive({
    6. iffLength: '1793',
    7. keySize: '',
    8. dataFileName: '',
    9. wfdName: '',
    10. version:''
    11. })
    12. script>
    13. <template>
    14. <t-form ref="cronjobFormRef" :btnList="cronjobConfig.buttons" :modelForm="cronjobForm" :formBorder="true"
    15. :rules="cronjobConfig.rules" :data="formItems">
    16. <template #header>
    17. <b>请输入转码程序生成条件:b><br /><br />
    18. template>
    19. t-form>
    20. template>

     测试数据:

    1. import { DocumentDelete, Edit, Download } from '@element-plus/icons-vue'
    2. import { shallowRef ,ref } from 'vue'
    3. let checkNum = (rule, value, callback) => {
    4. // 函数用于检查其参数是否是非数字值,如果参数值为 NaN 或字符串、对象、undefined等非数字值则返回 true, 否则返回 false。
    5. if (isNaN(value)) {
    6. return callback("iffLength must be a number");
    7. }
    8. return callback();
    9. }
    10. let checkVersion = (rule, value, callback) => {
    11. let regex = /^V(\d{2})[A-L]$/;
    12. if (regex.test(value)) {
    13. callback();
    14. return true;
    15. } else {
    16. callback(new Error("Version must be similar to 'V23G'"));
    17. return false;
    18. }
    19. }
    20. const cronjobConfig = ref({
    21. rules: {
    22. iffLength: [
    23. { required: true, message: 'Please input iff length', trigger: 'blur' },
    24. { validator: checkNum, trigger: "blur" }
    25. ],
    26. keySize: [
    27. { required: true, message: 'Please select key size', trigger: 'change', }
    28. ],
    29. dataFileName: [{
    30. required: true,
    31. message: 'Please input data filename',
    32. trigger: 'blur',
    33. }],
    34. wfdName: [{
    35. required: true,
    36. message: 'Please input wfd name',
    37. trigger: 'blur',
    38. }],
    39. version: [{ required: true, message: 'Please input version', trigger: 'blur' },
    40. { validator: checkVersion, trigger: "blur" }
    41. ]
    42. },
    43. formItems: [{
    44. field: 'iffLength',
    45. prop: 'iffLength',
    46. label: 'iff length',
    47. placeholder: '1793',
    48. labelWidth: '150px',
    49. type: 'input',
    50. // size: 'small',
    51. span: 12,
    52. },
    53. {
    54. field: 'keySize',
    55. prop: 'keySize',
    56. type: 'select',
    57. label: 'key size',
    58. placeholder: 'select key size',
    59. // editable: true,
    60. // size: 'small',
    61. span: 12,
    62. options: [{ label: 6, value: 6 }, { label: 9, value: 9 }]
    63. },
    64. {
    65. field: 'dataFileName',
    66. prop: 'dataFileName',
    67. type: 'input',
    68. label: 'data filename',
    69. labelWidth: '150px',
    70. placeholder: 'data filename',
    71. // isHidden: false,
    72. span: 12,
    73. },
    74. {
    75. field: 'wfdName',
    76. prop: 'wfdName',
    77. type: 'input',
    78. label: 'WFD name',
    79. placeholder: 'WFD name',
    80. span: 12,
    81. },
    82. {
    83. field: 'version',
    84. prop: 'version',
    85. type: 'input',
    86. label: 'version',
    87. labelWidth: '150px',
    88. placeholder: 'version',
    89. span: 12,
    90. },
    91. ],
    92. // 按钮
    93. buttons: [{
    94. name: '生成转码程序',
    95. title: 'generateCronjob',
    96. type: 'primary',
    97. size: 'default', //可以是default,small,large
    98. icon: shallowRef(Edit),
    99. // 按钮是否为朴素类型
    100. // plain: true,
    101. onClick: null
    102. }, {
    103. name: '重置',
    104. type: 'info',
    105. title: 'resetCronjob',
    106. size: 'default',
    107. icon: shallowRef(DocumentDelete),
    108. // plain: true,
    109. onClick: null
    110. },
    111. {
    112. name: '下载转码程序',
    113. type: 'success',
    114. title: 'downloadCronjob',
    115. size: 'default',
    116. icon: shallowRef(Download),
    117. isHidden: true,
    118. // plain: true,
    119. onClick: null
    120. }
    121. ],
    122. ref: 'cronjobFormRef',
    123. labelWidth: '120px',
    124. labelPosition: 'right',
    125. inline: true,
    126. editable: true,
    127. // 单元列之间的间隔
    128. elRowGutter: 20,
    129. // size: 'small',
    130. // 是否需要form边框
    131. formBorder: true,
    132. colLayout: {
    133. xl: 5, //2K屏等
    134. lg: 8, //大屏幕,如大桌面显示器
    135. md: 12, //中等屏幕,如桌面显示器
    136. sm: 24, //小屏幕,如平板
    137. xs: 24 //超小屏,如手机
    138. }
    139. });
    140. export default cronjobConfig;

    9.测试效果

  • 相关阅读:
    删除有序数组中的重复项Ⅱ--------题解报告
    GBase 8c结果集类型
    7.Spring — 声明式事务
    nginx反向代理负载均衡实战
    6┃音视频直播系统之 WebRTC 核心驱动SDP规范协商
    麒麟v10系统,在虚拟机上直接连公司同一个局域网,设置静态ip
    网络请求-Android篇(Okhttp和Retrofit)
    【源码+文档+调试】springboot文化传承小程序的设计与实现小程序源码分享
    Dockerfile的概述和构建
    【论文基本功】【LaTeX】个人常用易忘LaTeX命令
  • 原文地址:https://blog.csdn.net/qq_34569497/article/details/133643863