学习 Vue 3 全家桶 - 打造自己的通用组件库 - 环境搭建

2023-05-17 16:56:52 浏览数 (2)

# 环境搭建

# 创建 vite 项目

代码语言:javascript复制
npm init vite@latest

# √ Project name: ... cellinlab-ui
# √ Select a framework: » vue
# √ Select a variant: » vue-ts
# Done. Now run:
#   cd cellinlab-ui
#   npm install
#   npm run dev

# Sass

在项目里集成 CSS 预编译器,可以更快、更高效地管理和编写 CSS 代码。

代码语言:javascript复制
npm install -D sass

# ESLint

代码语言:javascript复制
npm install -D eslint

ESLint 配置初始化

代码语言:javascript复制
$ npx eslint --init

可以在 .eslintrc.json 中修改配置

代码语言:javascript复制
{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/essential",
        "plugin:@typescript-eslint/recommended"
    ],
    "parserOptions": {
        "ecmaVersion": "latest",
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    "rules": {
    }
}

运行 ESLint 检查

代码语言:javascript复制
npx eslint src

# husky

安装和初始化 husky

代码语言:javascript复制
npm install -D husky

npx husky install # 初始化 husky

新增 commit-msg 钩子,来校验 commit 信息格式

代码语言:javascript复制
npx husky add .husky/commit-msg "node scripts/commit-msg.js"

scripts/commit-msg.js

代码语言:javascript复制
const msg = require('fs')
  .readFileSync('.git/COMMIT_EDITMSG', 'utf-8')
  .trim();

const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)((. ))?: .{1,50}/;
const mergeRe = /^(Merge pull request|Merge branch)/;
if (!commitRE.test(msg)) {
  if(!mergeRe.test(msg)){
    console.log('git commit信息校验不通过');
    console.error(`git commit的信息格式不对, 需要使用 title(scope): desc的格式
      比如 fix: xxbug
      feat(test): add new 
      具体校验逻辑看 scripts/verifyCommit.js
    `);
    process.exit(1);
  }
}else{
  console.log('git commit信息校验通过');
}

# 布局容器

# 需求拆分

参考 Element3 布局容器页面,共有 container、header、footer、aside、main 五个组件,这个组合可以很方便地实现常见的页面布局:

  • <cell-container> 组件负责外层容器
    • 当子元素中包含 <cell-header><cell-footer> 时,全部子元素会垂直上下排列,否则会水平左右排列
  • <cell-header> 组件负责头部
  • <cell-aside> 组件负责侧边栏
  • <cell-main> 组件负责主体内容
  • <cell-footer> 组件负责底部

# 组件实现

组件中所有样式需要加上 cell- 前缀,可以使用 Sass 的 Mixin 来实现。

新建 src/components/styles/mixin.scss,添加如下内容:

代码语言:javascript复制
// 定义变量
$namespace: 'cell';
$state-prefix: 'is-';

// 使用 mixin 注册模块 b
@mixin b($block) {
  // 通过 传入的 $block 生成一个新的变量 $B
  $B: $namespace   '-'   $block !global; // !global 表明该变量是全局的
  .#{$B} {
    // @content 占位,@include 传入相应内容
    @content;
  }
}

@mixin when($state) {
  @at-root {
    &.#{$state-prefix   $state} {
      @content;
    }
  }
}

src/components/container/Container.vue 中添加如下内容:

代码语言:javascript复制
<template>
  <section
    class="cell-container"
    :class="{ 'is-vertical': isVertical }"
  >
    <slot/>
  </section>
</template>
<script lang="ts">
export default {
  name: 'CellContainer',
}
</script>
<script setup lang="ts">
import { Component, computed, useSlots, VNode } from 'vue'
interface Props {
  direction?: string;
}
const props = defineProps<Props>();

const slots = useSlots();

const isVertical = computed(() => {
  if (slots && slots.default) {
    return slots.default().some((vn: VNode) => {
      const tag = (vn.type as Component).name;
      return tag === 'CellHeader' || tag === 'CellFooter';
    });
  } else {
    if (props.direction === 'vertical') {
      return true;
    } else {
      return false;
    }
  }
});
</script>
<style lang="scss" scoped>
@import '../syles/mixin';
@include b(container) {
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-basis: auto;
  box-sizing: border-box;
  min-width: 0;
  @include when(vertical) {
    flex-direction: column;
  }
}
</style>

上述代码中,使用 b(container) 生成 cell-container 样式类,在内部使用 when(vertical) 生成 .cell-container.is-vertical 样式类以便修改 flex 布局方向。

container 组件内部如果没有 header 或 footer 组件,就是横向布局,否则就是垂直布局。

这里使用了两个 script 标签,是因为要确保每个组件有自己的名字,script setup 中不能返回组件的名字,所有需要单独的标签,使用 options 语法设置组件的 name 属性。

src/components/container/Header.vue 中添加如下内容:

代码语言:javascript复制
<template>
  <header
    class="cell-header"
    :style="{ height }"
  >
    <slot/>
  </header>
</template>

<script lang="ts">
export default {
  name: 'CellHeader',
}
</script>

<script setup lang="ts">
import { withDefaults } from 'vue'

interface Props {
  height?: string;
}
withDefaults(defineProps<Props>(), {
  height: '60px',
});
</script>


<style lang="scss">
@import '../syles/mixin';
@include b(header) {
  padding: $--header-padding;
  box-sizing: border-box;
  flex-shrink: 0;
}
</style>

src/components/container/Aside.vue 中添加如下内容:

代码语言:javascript复制
<template>
  <aside
    class="cell-aside"
    :style="{ width }"
  >
    <slot/>
  </aside>
</template>

<script lang="ts">
export default {
  name: 'CellAside',
}
</script>

<script setup lang="ts">
import { withDefaults } from 'vue';

type PropValues = {
  width: string;
};

withDefaults(defineProps<PropValues>(), {
  width: '300px',
});
</script>

<style lang="scss">
@import '../styles/mixin';
@include b(aside) {
  overflow: auto;
  box-sizing: border-box;
  flex-shrink: 0;
}
</style>

src/components/container/Footer.vue 中添加如下内容:

代码语言:javascript复制
<template>
  <footer
    class="cell-footer"
    :style="{ height }"  
  >
    <slot/>
  </footer>
</template>

<script lang="ts">
export default {
  name: 'CellFooter',
}
</script>

<script setup lang="ts">
import { withDefaults } from 'vue';

interface Props {
  height?: string;
}

withDefaults(defineProps<Props>(), {
  height: '60px',
});
</script>

<style lang="scss">
@import '../syles/mixin';
@include b(footer) {
  padding: $--footer-padding;
  box-sizing: border-box;
  flex-shrink: 0;
}
</style>

src/components/container/Math.vue 中添加如下内容:

代码语言:javascript复制
<template>
  <main class="cell-main">
    <slot/>
  </main>
</template>

<script lang="ts">
export default {
  name: 'CellMain',
}
</script>

<script setup lang="ts">

</script>

<style lang="scss">
@import '../styles/mixin';
@include b(main) {
  display: block;
  flex: 1;
  flex-basis: auto;
  overflow: auto;
  box-sizing: border-box;
  padding: $--main-padding;
}
</style>

# 组件注册

为了方便注册,这里使用插件机制对外暴露安装的接口,新建 scr/components/container/index.ts 文件,添加下面代码:

代码语言:javascript复制
import { App } from 'vue';
import CellContainer from './Container.vue';
import CellHeader from './Header.vue';
import CellFooter from './Footer.vue';
import CellAside from './Aside.vue';
import CellMain from './Main.vue';

export default {
  install(app: App) {
    app.component(CellContainer.name, CellContainer);
    app.component(CellHeader.name, CellHeader);
    app.component(CellFooter.name, CellFooter);
    app.component(CellAside.name, CellAside);
    app.component(CellMain.name, CellMain);
  }
};

现在,就可以在 src/main.ts 中整体进行引入了:

代码语言:javascript复制
import { createApp } from 'vue'
import App from './App.vue'

import CellContainer from './components/container'

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

# 组件使用

src/App.vue 中展示:

代码语言:javascript复制
<template>
  <cell-container>
    <cell-header>Header</cell-header>
    <cell-main>Main</cell-main>
    <cell-footer>Footer</cell-footer>
  </cell-container>
  <hr>
  <cell-container>
    <cell-header>Header</cell-header>
    <cell-container>
      <cell-aside width="200px">Aside</cell-aside>
      <cell-main>Main</cell-main>
    </cell-container>
  </cell-container>
  <hr>
  <cell-container>
    <cell-aside width="200px">Aside</cell-aside>
    <cell-container>
      <cell-header>Header</cell-header>
      <cell-main>Main</cell-main>
      <cell-footer>Footer</cell-footer>
    </cell-container>
  </cell-container>
</template>

<script setup lang="ts">
</script>

<style>
body {
  width: 1000px;
  margin: 10px auto;
}
.cell-header,
.cell-footer {
  background-color: #b3c0d1;
  color: #333;
  text-align: center;
  line-height: 60px;
}
.cell-aside {
  background-color: #d3dce6;
  color: #333;
  text-align: center;
  line-height: 200px;
}
.cell-main {
  background-color: #e9eef3;
  color: #333;
  text-align: center;
  line-height: 160px;
}

body > .cell-container {
  margin-bottom: 40px;
}

.cell-container:nth-child(5) .cell-aside,
.cell-container:nth-child(6) .cell-aside {
  line-height: 260px;
}
.cell-container:nth-child(7) .cell-aside {
  line-height: 320px;
}
</style>

0 人点赞