项目记录:使用vue3、vite打包组件库

前言

经理给了我一个比较奇葩的需求:在自身的项目(物联网中台)把组件分成"私有组件"和"公共组件"两个模块,私有组件是这个项目自身才能使用的,公共组件就是把该文件夹内的组件当成组件库打包放到公司的库里面去。当时的想法是公共组件抽出来当成一个项目来打包发布,不过经理的意见是这个公共组件,在中台这个项目里面导进来就能用而不需要当成依赖来用,emmm。行吧行吧,菜鸡前端只能听从上级的。

项目目录结构、组件库结构

iotPublicComponents可以放在src里面的components文件夹,不过我这里抽了出来。

 iotPublicComponents结构:里面有组件文件夹、静态资源文件夹、打包配置、以及一些请求和路由的封装。

组件封装

<!-- 空间选择组件 -->
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { Search, ArrowRight } from '@element-plus/icons-vue';
import componentApis from '../api/componentApis';

/**
 * @note 组件所需配置
 * @note isShowSelectSpace  控制组件显隐
 * @note selectSpaceType     单选raion、多选multiple
 * @note spaceType               可选属性,限制空间的类型
 * @note selectSpaceData     父组件传入的数组(子组件所抛出给父的选中数据)
 */
interface Props {
  isShowSelectSpace: boolean;
  selectSpaceType: string;
  spaceType?: string;
  selectSpaceData?: any[];
}

interface SelectSpaceInfo {
  id: string;
  parentId: string;
  name: string;
  spaceType: string;
  hierarchy: number;
  subNumber: number;
  spacePathText: string;
  check?: string;
  active?: string;
}

const props = withDefaults(defineProps<Props>(), {
  selectSpaceType: 'radio',
});

/**
 * @function closeDialog          弹窗关闭
 * @function confirmSelection 确认所选数据
 */
const emit = defineEmits<{
  (e: 'closeDialog', isShowSelectSpace: boolean): void;
  (e: 'confirmSelection', data: SelectSpaceInfo[]): void;
}>();

// let flag = false;
let parentId: string = '0';
const spaceType = ref<string>('All');
const searchSpaceName = ref<string>('');
const spaceLoading = ref<boolean>(false);
const showArrowRight = ref<boolean>(true);

//  面包屑
const breadcrumbList: SelectSpaceInfo[] = reactive([
  {
    id: '0',
    name: '全部',
    parentId: '',
    active: '1',
    hierarchy: 0,
    spaceType: 'All',
    subNumber: 1,
    spacePathText: '',
  },
]);
// 空间列表
const spaceData: SelectSpaceInfo[] = reactive([]);
// 所选空间
const selectData: SelectSpaceInfo[] = reactive([]);

/**
 * @function 处理列表多选
 * @param event 点击列表或点击勾选框
 * @param data  所点击的数据
 */
function handleMultipleSelect(data: SelectSpaceInfo): void {
  // if (typeof event !== 'string') {
  //   data.check = data.check == '1' ? '0' : '1';
  // }
  let index = selectData.findIndex((item: SelectSpaceInfo) => {
    return item.id === data.id;
  });
  if (index == -1) {
    selectData.push(data);
  } else {
    selectData.splice(index, 1);
  }
}

/**
 * @function 处理列表单选
 * @param event 点击列表或点击勾选框
 * @param data  所点击的数据
 */
function handleRadioSelect(event: Event, data: SelectSpaceInfo): void {
  if (typeof event === 'string') {
    spaceData.map((item: SelectSpaceInfo) => {
      if (item.id !== data.id) {
        item.check = '0';
      }
    });
  }
  let index = selectData.findIndex((item: SelectSpaceInfo) => {
    return item.id === data.id;
  });
  if (index === -1) {
    if (selectData.length === 0) {
      selectData.push(data);
    } else {
      selectData.splice(0, 1, data);
    }
  } else {
    selectData.splice(index, 1);
  }
}
/**
 * @function 根据多选、单选条件以及是否空间类型限制触发相应事件
 * @param event 点击列表或点击勾选框
 * @param data  所点击的数据
 * @note multiple 多选、Radio 单选;
 * @note spaceType:All为不限制空间类型,其它类型需在使用组件时传入
 */
function handleChangeSpaceDataCheck(event: Event, data: SelectSpaceInfo): void {
  const type = props.selectSpaceType;
  const flag = data.spaceType === spaceType.value ? true : false;
  switch (type) {
    case 'multiple':
      if (spaceType.value === 'All') {
        handleMultipleSelect(data);
      }
      if (spaceType.value !== 'All') {
        if (flag) {
          handleMultipleSelect(data);
        } else {
          return;
        }
      }
      break;
    case 'radio':
      if (spaceType.value === 'All') {
        handleRadioSelect(event, data);
      }
      if (spaceType.value !== 'All') {
        if (flag) {
          handleRadioSelect(event, data);
        } else {
          return;
        }
      }
      break;
  }
}

/**
 * @function 处理点击父级空间获取子空间、头部面包屑的增减
 * @param data 所点击的父级空间
 * @param type 事件类型
 * @note showArrowRight为true进行查询操作,为false则return
 */
function handleAddOrRemoveBreadcrumbList(data: SelectSpaceInfo, type: 'Remove' | 'Add'): void {
  if (showArrowRight.value) {
    if (parentId !== data.id) {
      parentId = data.id;
      let index = breadcrumbList.findIndex((item: SelectSpaceInfo) => {
        return item.id === data.id;
      });
      switch (type) {
        case 'Remove':
          breadcrumbList.splice(index + 1);
          breadcrumbList.map((item: SelectSpaceInfo, idx: number) => {
            item.active = '0';
            if (idx == index) {
              item.active = '1';
            }
          });
          break;
        case 'Add':
          if (index === -1) {
            breadcrumbList.push(data);
            breadcrumbList.map((item: SelectSpaceInfo, idx: number) => {
              item.active = '0';
              if (idx == breadcrumbList.length - 1) {
                breadcrumbList;
                item.active = '1';
              }
            });
          }
          break;
      }
      handleChangeSearchParams('ParentId', data, parentId);
    } else {
      return;
    }
  } else {
    return;
  }
}

/**
 * @function 处理根据名称模糊查询、根据父级Id的查找
 * @param type SearchSpaceName名称模糊查询 ParentId父级Id查找
 * @param data 父级Id查找时所选数据
 * @param parentId  父级Id
 */
function handleChangeSearchParams(type?: 'SearchSpaceName' | 'ParentId', data?: SelectSpaceInfo, parentId?: string): void {
  const params: any = {
    criteria: {
      key: '$and',
      items: [{ key: 'deleteFlag', data: { '$eq': '0' } }],
    },
  };
  if (type) {
    switch (type) {
      case 'SearchSpaceName':
        if (searchSpaceName.value === '') {
          showArrowRight.value = true;
          breadcrumbList.splice(1);
          breadcrumbList.map((item: SelectSpaceInfo) => {
            item.active = '1';
          });
          parentId = '0';
          const searchSpaceHierarchy = { key: 'hierarchy', data: { $eq: 1 } };
          params.criteria.items.push(searchSpaceHierarchy);
        } else {
          showArrowRight.value = false;
          const searchSpaceNameObject = { key: 'name', data: { $regex: searchSpaceName.value } };
          params.criteria.items.push(searchSpaceNameObject);
        }
        break;
      case 'ParentId':
        showArrowRight.value = true;
        const searchSpaceHierarchy = { key: 'hierarchy', data: { $eq: Number(data?.hierarchy) + 1 } };
        const searchSpaceParentId = { key: 'parentItemList.parentId', data: { $eq: parentId } };
        if (parentId == '0') {
          params.criteria.items.push(searchSpaceHierarchy);
        } else {
          const obj = { key: '$and', items: [searchSpaceHierarchy, searchSpaceParentId] };
          params.criteria.items.push(obj);
        }
        break;
    }
  } else {
    const searchSpaceHierarchy = { key: 'hierarchy', data: { $eq: 1 } };
    params.criteria.items.push(searchSpaceHierarchy);
  }
  spaceLoading.value = true;
  componentApis
    .getSpaceList(params)
    .then((res) => {
      spaceLoading.value = false;
      if (res) {
        spaceData.length = 0;
        let arr = res.map((item: SelectSpaceInfo) => {
          return {
            id: item.id,
            parentId: item.parentId,
            name: item.name,
            spaceType: item.spaceType,
            hierarchy: item.hierarchy,
            subNumber: item.subNumber,
            spacePathText: item.spacePathText.replace(new RegExp('/', 'g'), '>'),
            check: '0',
            active: '0',
          };
        });
        if (selectData.length > 0) {
          selectData.forEach((selectItem: SelectSpaceInfo) => {
            arr.map((item: SelectSpaceInfo) => {
              if (selectItem.id === item.id) {
                item.check = '1';
              }
            });
          });
        }
        Object.assign(spaceData, arr);
      }
    })
    .catch((err) => {
      console.log('err==>', err);
    });
}

/**
 * @function 处理清空搜索名称
 * @note 清空时会显示面包屑
 */
function handleClearSearchSpaceName(): void {
  showArrowRight.value = true;
  breadcrumbList.splice(1);
  breadcrumbList.map((item: SelectSpaceInfo) => {
    item.active = '1';
  });
  parentId = '0';
  handleChangeSearchParams('SearchSpaceName');
}

/**
 * @function 处理移除标签
 * @param id 所选数据的Id
 */
function handleRmoveTag(id: string): void {
  let index = selectData.findIndex((item: SelectSpaceInfo) => {
    return item.id === id;
  });
  if (index !== -1) {
    selectData.splice(index, 1);
  }
  spaceData.map((item: SelectSpaceInfo) => {
    if (item.id === id) {
      item.check = '0';
    }
  });
}

function handleCloseDialog(): void {
  emit('closeDialog', false);
}

function handleConfirmSelection(): void {
  emit('closeDialog', false);
  emit('confirmSelection', selectData);
}

onMounted(() => {
  handleChangeSearchParams();
  Object.assign(selectData, props.selectSpaceData);
  spaceType.value = props.spaceType ? props.spaceType : 'All';
});
</script>

<template>
  <div>
    <el-dialog v-model="props.isShowSelectSpace" width="35%" align-center @close="handleCloseDialog()">
      <template #header>
        <span class="h4">选择空间</span>
      </template>
      <template #default>
        <div class="mt-20">
          <el-input v-model="searchSpaceName" placeholder="搜索" @keyup.enter.native="handleChangeSearchParams('SearchSpaceName')" clearable @clear="handleClearSearchSpaceName()">
            <template #suffix>
              <el-icon @click="handleChangeSearchParams('SearchSpaceName')">
                <Search />
              </el-icon>
            </template>
          </el-input>
        </div>
        <div class="mt-20" v-if="showArrowRight">
          <el-breadcrumb :separator-icon="ArrowRight">
            <el-breadcrumb-item
              class="p-10"
              :class="item.active == '1' ? 'dialog-body-active' : ''"
              v-for="item in breadcrumbList"
              :key="item.id"
              @click="handleAddOrRemoveBreadcrumbList(item, 'Remove')"
            >
              <el-tooltip placement="top" :show-after="500">
                <template #content> {{ item.name }}</template>
                <div class="w-50 dialog-body-breadcrumb">{{ item.name }}</div>
              </el-tooltip>
            </el-breadcrumb-item>
          </el-breadcrumb>
        </div>
        <div class="mt-20">
          <el-scrollbar height="400px" v-loading="spaceLoading">
            <div v-if="spaceData.length > 0">
              <div class="mh-10 mb-10" v-for="item in spaceData" :key="item.id">
                <div class="ds-fx ai-c">
                  <el-checkbox v-if="spaceType == 'All'" @change="handleChangeSpaceDataCheck($event, item)" v-model="item.check" size="large" true-label="1" false-label="0" />
                  <el-checkbox
                    v-if="spaceType !== 'All' && spaceType === item.spaceType"
                    @change="handleChangeSpaceDataCheck($event, item)"
                    v-model="item.check"
                    size="large"
                    true-label="1"
                    false-label="0"
                  />
                  <div class="hideBox" v-else></div>
                  <div
                    @click.stop="handleAddOrRemoveBreadcrumbList(item, 'Add')"
                    :class="item.check == '1' ? 'dialog-body-items-active' : ''"
                    class="dialog-body-items ml-10 pv-20 ds-fx ai-c jc-sb w-full"
                  >
                    <div class="ds-fx ai-c">
                      <img class="ml-10 dialog-body-items-image" src="../assets/images/spaceIcon.png" />
                      <span class="ml-10 dialog-body-items-spacePathText" v-if="showArrowRight">{{ item.name }}</span>
                      <el-tooltip v-else placement="top" :show-after="500">
                        <template #content> {{ item.spacePathText }} </template>
                        <span class="ml-10 dialog-body-items-tooltip">{{ item.spacePathText }}</span>
                      </el-tooltip>
                    </div>
                    <el-icon v-if="showArrowRight" class="pr-10"><ArrowRight /></el-icon>
                  </div>
                </div>
              </div>
            </div>
            <div v-else>
              <el-empty description="暂无数据" />
            </div>
          </el-scrollbar>
        </div>
      </template>
      <template #footer>
        <div class="ds-fx jc-sb dialog-footer">
          <div class="ds-fx ai-c">
            <el-tag class="mr-5" v-for="tag in selectData.slice(0, 3)" :key="tag.id" closable type="primary" @close="handleRmoveTag(tag.id)">
              <el-tooltip placement="top" :show-after="500">
                <template #content> {{ tag.name }} </template>
                <div class="w-50 dialog-footer-tag">
                  {{ tag.name }}
                </div>
              </el-tooltip>
            </el-tag>
            <el-dropdown v-if="selectData.length > 3">
              <span class="tagList-link">+{{ selectData.length - 3 }}</span>
              <template #dropdown>
                <el-scrollbar max-height="200px">
                  <el-dropdown-menu class="ds-fx fd-c ml-10">
                    <el-tag class="mr-5 mb-5" v-for="tag in selectData.slice(3)" :key="tag.id" closable :disable-transitions="false" @close="handleRmoveTag(tag.id)">
                      <el-tooltip placement="right" :show-after="500">
                        <template #content> {{ tag.name }} </template>
                        <div class="w-50 dialog-footer-tag">
                          {{ tag.name }}
                        </div>
                      </el-tooltip>
                    </el-tag>
                  </el-dropdown-menu>
                </el-scrollbar>
              </template>
            </el-dropdown>
          </div>
          <div class="ds-fx">
            <el-button @click="handleCloseDialog()">取消</el-button>
            <el-button type="primary" @click="handleConfirmSelection()">确定</el-button>
          </div>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped lang="scss">
$BackgroundColor: #eff0f1;
.hideBox {
  width: 18px;
}
.dialog-footer {
  &-tag {
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
.dialog-body-active {
  background-color: $BackgroundColor;
}
.dialog-body-breadcrumb {
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dialog-body-items {
  border-radius: 4px;
  &-active {
    background-color: $BackgroundColor;
  }
  &-image {
    width: 30px;
    height: 30px;
  }
  &-spacePathText {
    letter-spacing: 0.2rem;
    line-height: 25px;
  }
  &-tooltip {
    width: 500px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    letter-spacing: 0.1rem;
    line-height: 25px;
  }
}

:deep(.el-dialog__header) {
  margin-right: 0;
  padding: 20px;
  background-color: $BackgroundColor;
  .el-dialog__headerbtn {
    top: 5px;
  }
}
:deep(.el-dialog__body) {
  padding: 0 20px;
}
:deep(.el-input__wrapper) {
  border-radius: 5px;
}

:deep(.el-checkbox.el-checkbox--large .el-checkbox__inner) {
  width: 18px;
  height: 18px;
}
:deep(.el-checkbox.el-checkbox--large .el-checkbox__inner)::after {
  top: 3px;
  left: 5px;
}

:deep(.el-dropdown) {
  padding: 5px;
  background-color: var(--el-color-primary-light-9);
  color: var(--el-color-primary);
}

:deep(.el-input__suffix-inner) {
  flex-direction: row-reverse;
  -webkit-flex-direction: row-reverse;
  display: flex;
}
</style>

创建index.ts来抛出组件

import type { App } from 'vue';
import DsSelectSpace from './commons/DsSelectSpace.vue';

// 所有组件列表
const components = [DsSelectSpace];

// 定义 install 方法
const install = (app: App): void => {
  // 遍历注册所有组件
  /*
    component.__name ts报错
    Argument of type 'string | undefined' is not assignable to parameter of type 'string'.

    Type 'undefined' is not assignable to type 'string'.ts(2345)
    解决方式一:使用// @ts-ignore
    解决方式二:使用类型断言 尖括号语法(component.__name) 或 as语法(component.__name as string)
  */
  components.forEach((component) => app.component(component.__name as string, component));
};

const VueAmazingUI = {
  install,
};

export { DsSelectSpace };
export default VueAmazingUI;

vite.config.ts配置

 接下来就是比较重要的配置打包,当然自己也是个菜鸡,看了一下打包配置的文章和框架的打包方式才写出来的。我这里试了两种配置方式来打包,最后采用了第一种

第一种配置:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";

export default defineConfig({
  build: {
    emptyOutDir: true,
    target: "modules",
    outDir: "es",
    minify: false,
    sourcemap: true,
    cssCodeSplit: false,
    rollupOptions: {
      external: ["vue"],
      input: ["'../iotPublicComponents/src/index.ts'"],
      output: [
        {
          format: "es",
          entryFileNames: "[name].js",
          preserveModules: true,
          dir: "es",
          preserveModulesRoot: "src",
        },
        {
          format: "cjs",
          entryFileNames: "[name].js",
          preserveModules: true,
          dir: "lib",
          preserveModulesRoot: "src",
        },
      ],
    },
    lib: {
      entry: path.resolve(__dirname, "'../iotPublicComponents/src/index.ts'"),
      name: "dscloud",
      formats: ["es", "cjs"],
    },
  },
  plugins: [vue()],
});

 第二种配置:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: 'lib',
    lib: {
      entry: path.resolve(__dirname, '../iotPublicComponents/src/index.ts'),
      name: 'dscloudIotPublicComponent',
      fileName: 'dscloud-iot-public-component',
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
});

package.json配置

{
  "name": "dscloud-iot-public-component",
  "version": "0.0.12", // 每次推到库上的版本都需要与历史版本不一样才行
  "description": "Iot公共组件",
  "type": "module",
  "scripts": {
    "build:dev": "vite build --mode dev",
    "build:prod": "vite build --mode prod",
  },
  "files": [
    "lib"
  ],
  "main": "./lib/dscloud-iot-public-component.umd.js", 
  "module": "./lib/dscloud-iot-public-component.js",
  "exports": {
    "./lib/style.css": "./lib/style.css",
    ".": {
      "import": "./lib/dscloud-iot-public-component.js",
      "require": "./lib/dscloud-iot-public-component.umd.js"
    }
  },
  "git-checks": false,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "repository": "", // 仓库地址
  "dependencies": {
    "@element-plus/icons-vue": "^2.0.6",
    "axios": "^1.3.4",
    "element-plus": "^2.3.7",
    "nprogress": "^0.2.0",
    "vue": "^3.2.37"
  },
  "devDependencies": {
    "@types/nprogress": "^0.2.0",
    "@vitejs/plugin-vue": "3.0.1",
    "sass": "^1.54.3",
    "typescript": "^4.7.4",
    "vite": "^3.0.2",
    "vite-plugin-dts": "^1.4.0",
    "vue-tsc": "^0.38.9"
  }
}

gitignore配置,上传时所忽略的文件

.vscode
node_modules
es
lib
dist
package-lock.json
pnpm-lock.yaml

打包

根据package.json配置的build来打包。

发布

用npm publish推送到公司的库上面。

 全局注册、局部注册和使用

在项目里面拉取依赖

使用方式有两种:全局注册、局部注册

全局注册:

import { createApp } from 'vue';
import App from './App.vue';
import dscloudIotPublicComponent from 'dscloud-iot-public-component';
import 'dscloud-iot-public-component/lib/style.css'; // 样式
const app = createApp(App);
app.use(dscloudIotPublicComponent ).mount('#app');

 局部注册:

在所需要用组件的地方导入:
import { DsSelectSpace } from 'dscloud-iot-public-component';

 页面使用:

    <DsSelectSpace
      v-if="isShowSelectSpace"
      v-model="isShowSelectSpace"
      :is-show-select-space="isShowSelectSpace"
      :select-space-data="formData.spaceList"
      select-space-type="radio"
      @close-dialog="isShowSelectSpace = false"
      @confirm-selection="handleConfirmSelection($event)"
    >
    </DsSelectSpace>

组件效果

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以按照以下步骤使用 Rollup 打包 Vue 3 Vite 项目: 1. 首先,确保你已经安装了 Rollup 和相关的插件。可以使用以下命令进行安装: ```shell npm install --save-dev rollup rollup-plugin-vue@next rollup-plugin-terser ``` 2. 在项目根目录下创建一个 `rollup.config.js` 文件。 3. 在 `rollup.config.js` 文件中,导入必要的插件和模块: ```javascript import { defineConfig } from 'rollup'; import vue from 'rollup-plugin-vue'; import { terser } from 'rollup-plugin-terser'; ``` 4. 定义 Rollup 配置: ```javascript export default defineConfig({ input: 'src/main.js', // 入口文件路径 output: { file: 'dist/bundle.js', // 输出文件路径 format: 'iife', // 输出模块格式 name: 'MyApp', // 全局变量名称(可选) }, plugins: [ vue(), // 处理 .vue 单文件组件 terser(), // 压缩代码(可选) ], }); ``` 这里的 `input` 配置应该指向你项目中的入口文件,一般是 `main.js` 或者 `index.js`。`output` 配置指定了打包后的输出文件路径和格式,这里使用了立即执行函数(IIFE)格式,你可以根据需要选择其他格式。`name` 可选,它指定了全局变量名称,如果你希望在浏览器中直接引入打包后的文件,可以设置该值。 5. 在项目的 `package.json` 文件中添加一个脚本命令以运行 Rollup: ```json { "scripts": { "build": "rollup -c" } } ``` 6. 运行以下命令进行打包: ```shell npm run build ``` 打包完成后,你将在 `dist` 目录下找到打包后的文件。 这样,你就可以使用 Rollup 打包 Vue 3 Vite 项目了。如果需要更详细的配置,可以参考 Rollup 和相关插件的文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值