# 环境搭建
# 创建 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
中修改配置
{
"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 信息格式
npx husky add .husky/commit-msg "node scripts/commit-msg.js"
scripts/commit-msg.js
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
,添加如下内容:
// 定义变量
$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
中添加如下内容:
<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
中添加如下内容:
<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
中添加如下内容:
<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
中添加如下内容:
<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
中添加如下内容:
<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
文件,添加下面代码:
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
中整体进行引入了:
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
中展示:
<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>