[发财UI] 开发日志1

1,311 阅读2分钟

前言

发财UI是一个主要用Vue3和TypeScript编写的UI库,代码完全开源,由于是我的第一个UI库项目,必定还存在诸多不足,还请各位不吝指正!

链接

baIder/rich-ui: 发财UI源码 (github.com)

发财UI官网 - 文档,示例,支持

主要组件

Switch组件

Button组件

Dialog组件

Tabs组件

Message组件

Vue3中的props和attributes

<Button @click="click">按钮</Button>

Vue3中默认会将外部的@click="click"传给组件的最外层元素,也就是<button></button>

<template>
  <button :class="classes" :disabled="disabled" class="rich-button">
    <span v-if="loading" class="rich-loadingIndicator"></span>
    <slot/>
  </button>
</template>

属性绑定

通过inheritAttrs可以选择是否继承组件外部的属性

<template>
  <button>按钮</button>
</template>

<script lang="ts">
export default {
  inheritAttrs:false  //button不再继承外部属性
}
</script>

通过$attrs绑定外部属性

<template>
  <div>  //默认属性会继承到div上
    <button v-bind="$attrs">按钮</button>  //手动将属性绑定到button上
  </div>
</template>

部分绑定属性

<Button @click="click" size="" xxx="xxx">按钮</Button>

<template>
  <div :size="size">  //将size绑定到div上
    <button v-bind="rest">按钮</button>  //将rest绑定到button上
  </div>                                //rest包含了click和xxx属性
</template>

<script lang="ts">
export default {
  setup(props, context){
    const {size, ...rest} = context.attrs  //将继承的属性分为两个部分
    return {size, rest}    //一部分是size,一部分是rest
  }
}
</script>

props VS attrs

1、props 要先声明才能取值,attrs 不用先声明

2、props 声明过的属性,attrs 里不会再出现

3、props 不包含事件,attrs 包含

4、props 支持 string 以外的类型,attrs 只有 string 类型

项目开发中的CSS原则

  1. 组件的CSS不能用scoped
  2. 每个CSS的类最好加上前缀,防止相互覆盖
  3. CSS最小影响原则

如何做loading动画

.rich-loadingIndicator {
  width: 14px;
  height: 14px;
  display: inline-block;
  margin-right: 4px;
  border-radius: 8px;
  border-color: $button-blue $button-blue $button-blue transparent;
  border-style: solid;
  border-width: 2px;
  animation: rich-spin 1s infinite linear;
}
  
@keyframes rich-spin {
0% {
  transform: rotate(0deg)
}
100% {
  transform: rotate(360deg)
}

具名插槽

slot一个name,就可以精准的确定slot对应的内容

<header>
  <slot name="title"/>
  <span class="rich-dialog-close" @click="close"></span>
</header>
<main>
  <slot name="content"/>
</main>
<template v-slot:content>
  <strong>hi</strong>
  <div>hi2</div>
</template>
<template v-slot:title>
  <strong>加粗的标题</strong>
</template>

Teleport 传送标签

<Teleport to="目的地"></Teleport>中的内容会被挂载到目的地下

<template>
  <Teleport to="body">
    ...
  </Teleport>
</template>

Tabs

用JS获取插槽内容

const defaults = context.slots.default()

在运行时确认子组件的类型

defaults.forEach(tag => {
  if (tag.type !== Tab) {
    throw new Error('Tabs的子组件必须是Tab');
  }
});

getBoundingClientRect()

Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。

const {width,height,top,left} = el.getBoundingClientRect()

异步加载

setup(props) {  
  const content = ref<string>('')  
  import(props.path).then(result => {  
    content.value = result.default  
  })  
  return {content}  
}

Custom Blocks

首先需要先配置好vite

vite.config.ts

import * as fs from 'fs'
import {baseParse} from '@vue/compiler-core'

export default {
    vueCustomBlockTransforms: {
        demo: (options) => {
            const {code, path} = options
            const file = fs.readFileSync(path).toString()
            const parsed = baseParse(file).children.find(n => n.tag === 'demo')
            const title = parsed.children[0].content
            const main = file.split(parsed.loc.source).join('').trim()
            return `export default function (Component) {
        Component.__sourceCode = ${
                JSON.stringify(main)
            }
        Component.__sourceCodeTitle = ${JSON.stringify(title)}
      }`.trim()
        }
    }
};

在需要变为demo的文件开头加上<demo>Title</demo>

<demo>
常规使用
</demo>
<template>
  <div>
    <Button>你好</Button>
    <Button theme="link">你好</Button>
    <Button theme="text">你好</Button>
  </div>
</template>

就可以在引用该demo的组件中使用相关的api

const Title = component.__sourceCodeTitle
const soucreCode = component.__sourceCode

动态加载bug

yarn build之后不能加载.md文件,浏览器提示404

原因是使用了动态加载,rollup不支持import()时拼字符串

const history = createWebHashHistory();
const md = filename => h(Markdown, {path: `../markdown/${filename}.md`, key: filename})
export const router = createRouter({
    history, routes: [
        {path: '/', component: Home},
        {
            path: '/doc', component: Doc, children: [
                {path: '', redirect: '/doc/intro'},
                {path: 'intro', component: md('intro')},
                {path: 'install', component: md('install')},
                {path: 'get-started', component: md('get-started')},
                {path: 'switch', component: SwitchDemo},
                {path: 'button', component: ButtonDemo},
                {path: 'dialog', component: DialogDemo},
                {path: 'tabs', component: TabsDemo},
            ]
        },
    ]
});

调整过后:

import intro from './markdown/intro.md'
import getStarted from './markdown/get-started.md'
import install from './markdown/install.md'

const history = createWebHashHistory();
const md = string => h(Markdown, {content: string, key: string})
export const router = createRouter({
    history, routes: [
        {path: '/', component: Home},
        {
            path: '/doc', component: Doc, children: [
                {path: '', redirect: '/doc/intro'},
                {path: 'intro', component: md(intro)},
                {path: 'install', component: md(install)},
                {path: 'get-started', component: md(getStarted)},
                {path: 'switch', component: SwitchDemo},
                {path: 'button', component: ButtonDemo},
                {path: 'dialog', component: DialogDemo},
                {path: 'tabs', component: TabsDemo},
            ]
        },
    ]
});