大纲链接 §
[toc]
需求分析
用例图 user case 参考 ant-design
- 点击按钮
- 按钮
loading 状态 加载完毕 自动回复初始状态
- 按钮 不可点击 状态
disable 提示不可点击
- 按钮
hover 状态(手机没有hover)
- 按钮 按下状态
- 其他
按钮状态 11 种
enable默认状态
hover状态 (移动端无)
focus状态
error状态 (危险色提示文字)
error hover状态 (移动端无)
error focus状态
success状态 (原谅色提示文字)
success hover状态 (移动端无)
success focus状态
disable状态
readonly状态
注意点
- 按钮高度 和
input 输入框 一致
- 不写死按钮的宽度,而是设置
padding: 0 1em,左右的内边距空出各一个字
API设计
- 设置颜色样式
- 设置图标与图标位置
- 设置尺寸
- 设置禁用状态
- 设置加载中状态
- 按钮组
项目初始化
新建目录,自定义目录名
1
2
|
cd ~/desktop
mkdir lunzi-demo
|
创建 github 仓库
声明软件许可
- 在
github中新建文件LICENSE
choose a license template
初始化仓库
1
2
3
4
|
yarn init
# 全部使用默认选项 # yarn init -y
# 或者用
# npm init
|
安装 vue@2.6.11
1
2
|
yarn add vue@2.6.11
yarn add --dev @types/vue
|
添加 .gitignore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
.DS_Store/
node_modules/
dist/
.cache/
.yarn/
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
|
使用@vue/cli 搭建前端项目目录架子
- 过程略略略
- 页面
./public/index.html
- 入口文件
./src/main.ts
./src/App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
┣ src
┃ ┣ assets
┃ ┃ ┣ icons
┃ ┃ ┃ ┣ account.svg
┃ ┃ ┃ ┣ bills.svg
┃ ┃ ┃ ┣ ...
┃ ┃ ┃ ┗ svg.js
┃ ┃ ┗ logo.png
┃ ┣ components
┃ ┃ ┣ button
┃ ┃ ┃ ┣ VueButton.vue
┃ ┃ ┃ ┗ VueButtonGroup.vue
┃ ┃ ┣ button-group
┃ ┃ ┣ icon
┃ ┃ ┃ ┗ VueIcon.vue
┃ ┃ ┣ Buttons.vue
┃ ┃ ┗ Nav.vue
┃ ┣ router
┃ ┃ ┗ index.ts
┃ ┣ store
┃ ┃ ┗ index.ts
┃ ┣ styles
┃ ┃ ┣ components
┃ ┃ ┃ ┣ index.scss
┃ ┃ ┃ ┣ vue-button-group.scss
┃ ┃ ┃ ┣ vue-button.scss
┃ ┃ ┃ ┗ vue-icon.scss
┃ ┃ ┣ global.scss
┃ ┃ ┣ index.scss
┃ ┃ ┣ normalize.scss
┃ ┃ ┗ reset.scss
┃ ┣ types
┃ ┃ ┗ custom.d.ts
┃ ┣ views
┃ ┃ ┣ About.vue
┃ ┗ ┗ Layout.vue
┣ App.tsx
┣ main.ts
┣ shims-tsx.d.ts
┣ shims-vue.d.ts
┣ vue.config.js
┣ .eslintrc.js
┣ .gitignore.js
┣ babel.config.js
┣ index.textscr
┣ package.json
┣ README.md
┗ tsconfig.json
|
VueButton.vue使用<slot></slot> 在自定义组件的双标签(可嵌套)中传入内容
1
2
3
4
5
6
7
|
<template>
<div>
<button class="v-button">
<slot></slot>
</button>
</div>
</template>
|
在父组件中使用
1
2
3
4
5
6
7
|
<template>
<div id="app">
<VueButton>
按钮
</VueButton>
</div>
</template>
|
- 效果是显示
<button class="v-button">按钮</button>
添加icon
批量添加icon
- 使用 iconfont.cn 提供的
SVG图标,来代替过时的CSS雪碧图
- 搜索「设置」、「点赞」、「下载」、「左」,选取按钮样式的
SVG图标,添加至项目文件夹
Unicode/Font class/Symbol点选Symbol
- 在更多操作中,更改前缀为
i,命名都用英文
i-arrow-down向下箭头
i-download下载
i-settings设置
i-thumbs-up点赞
i-left左
iconfont 查看大图,是否居中对齐,调整大小,基本一致
- 点击
SVG下载,调整SVG,打开SKETCH,或者 Photoshop 制作 i-right右,翻转180
- 上传,去色,放至项目文件夹中
- 批量去色
- 查看在线链接,生成JS代码
//at.alicdn.com/t/font_2138557_5w8054iu1s4.js
- 可在帮助文档 功能介绍中查看 代码应用
- svg 图标的颜色 可在标签上添加
fill属性 fill: red;
在index.html中引入JS <script src="https://at.alicdn.com/t/font_2138557_5w8054iu1s4.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="../src/style/normalize.scss">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<div id="test"></div>
<!-- built files will be auto injected -->
</body>
<script src="////at.alicdn.com/t/font_2138557_fmq5zqg2y0j.js"></script>
</html>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<template>
<div class="vue-demo-button">
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-arrow-down`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-download`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-settings`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-thumbs-up`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-left`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-right`"></use>
</svg>
<slot></slot>
</button>
</div>
</template>
|
- 将
<svg>...</svg> 移至父组件定义在<slot></slot>中
- 写样式
.icon,使之适合按钮尺寸,一般为一个字的大小,即width: 1em; height: 1em;
App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<template>
<div id="app">
<VueButton>
<svg class="icon" aria-hidden="true"><use xlink:href="#i-settings"></use></svg>按钮
</VueButton>
</div>
</template>
<style scoped>
/* ali iconfont common css */
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
|
1em和字体一样高
- 文字对齐的样式写在
VueButton.vue组件内部样式中,而在外部App.vue直接使用不考虑具体样式
VueButton.vue接受外部数据props,将icon名称属性在<VueButton></VueButton>中传入
App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<template>
<div id="app">
<VueButton icon="settings">
按钮
</VueButton>
</div>
</template>
<script>
import VueButton from './components/vuebutton/VueButton.vue'
export default {
name: 'App',
components: {
VueButton,
}
}
</script>
|
VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
<template>
<div class="VueButton">
<button class="vue-button">
<svg class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
<slot></slot>
</button>
</div>
</template>
<script>
export default {
props: ['icon'],
</script>
<style lang="scss">
:root {
--button-height: 32px;
--font-size: 14px;
--button-bg: white;
--button-active-bg: #eee;
--border-radius: 4px;
--color: #333;
--border-color: #999;
--border-color-hover: #666;
}
.vue-button {
font-size: var(--font-size);
height: var(--button-height);
padding: 0 1em;
font: inherit;
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
background: var(--button-bg);
&:hover {
border-color: var(--border-color-hover);
}
&:active {
background-color: var(--button-active-bg);
}
&:focus {
outline: none;
}
}
/* ali iconfont common css */
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
|
- 注意在
VueButton.vue中的反引号表示JS字符串,使用插值${icon}定义字符串变量
1
2
3
4
5
6
7
8
9
10
11
|
<template>
<use :xlink:href="`#i-${icon}`"></use>
</template>
<script>
//...
export default {
props: ['icon'],
//...
}
</script>
|
- 外部数据
props导入['icon']
- 绑定属性
:xlink:href=""
- JS字符串
#i-${icon},模板字符串的插值${icon}
${icon}的值是从props中取得
props的值是从外部的<VueButton icon="settings">按钮</VueButton>自定义属性取得settings,拼接成#i-settings
- 之前引入的
iconfont JS使之生效
- 动态绑定属性 :xlink:href="`#i-${icon}`"
解决在App.vue未设置icon属性时的空字符占用的bug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- App.vue -->
<template>
<div id="app">
<VueButton>
按钮
</VueButton>
<VueButton icon="settings">
按钮
</VueButton>
</div>
</template>
<!-- 显示在页面中的 -->
<template>
<svg class="icon" aria-hidden="true">
<use :xlink:href="`#i-undefined`"></use>
</svg>
</template>
|
- 使用
v-if判断是否存在icon属性,为空,即undefined,则不显示<svg></svg>
- 隐藏默认不传
SVG
VueButton.vue
1
2
3
4
5
6
7
8
9
10
|
<template>
<div>
<button class="vue-button">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
<slot></slot>
</button>
</div>
</template>
|
- 注意:
v-if加在<svg>上,表示icon变量存在时,才出现<svg></svg>
- SVG 添加类
.icon {fill: red}添加颜色
控制图标icon位置
- 首先排除两边都加
icon的需求
- 绑定属性
:class,动态添加类icon-${iconPosition}
- 控制图标的位置( CSS flex布局 order属性 间接控制图标与文字的顺序),注意设置初始值
'left'
- 根据外部数据的不同名称,添加相应的类,切换名称即切换类
- 使用
<slot></slot>控制组件嵌中套的文本
- 注意:在外部数据
props中添加驼峰式的变量iconPosition
vue会自动转成XML可识别的属性icon-position
- 在外部组件中可识别
<VueButton icon="settings" icon-position="right">按钮</VueButton>
- 可取值
"left" || "right"
App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<template>
<div id="app">
<VueButton>
按钮
</VueButton>
<VueButton icon="settings">
按钮
</VueButton>
<VueButton icon="settings" icon-position="right">
按钮
</VueButton>
</div>
</template>
<script>
import VueButton from './components/vuebutton/VueButton.vue'
export default {
name: 'App',
components: {
VueButton,
}
}
</script>
|
VueButton.vue
1
2
3
|
export default {
props: ['icon', 'iconPosition'], // 'iconPosition': 'left' || 'right'
}
|
用v-if控制位置 VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<template>
<div class="vue-demo-button">
<button class="vue-button" v-if="!iconPosition || iconPosition === 'left'">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
<slot></slot>
</button>
<button class="vue-button" v-else>
<slot></slot>
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
</button>
</div>
</template>
|
- 使用
<button v-if='...'> <button v-else> 判断语句来改变不同位置,重复代码太多
- 默认
v-if="icon-position === right"
VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<template>
<div class="vue-demo-button">
<button class="vue-button" v-if="iconPosition === 'right'">
<slot></slot>
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
</button>
<button class="vue-button" v-else>
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
<slot></slot>
</button>
</div>
</template>
|
- 有重复就可能出现 bug,修改时可能遗漏
- 有重复就可能出现 bug,修改时可能遗漏
- 有重复就可能出现 bug,修改时可能遗漏
用CSS控制位置,做样式相关
- 动态绑定一个类的属性
:class="{ [iconPosition]: true }"
<button :class="{ 'icon-undefined': true}">
<button :class="{ 'icon-left': true}">
<button :class="{ 'icon-right': true}">
- 类名变量
iconPosition作为key,绑定:class
- 使用插值字符串
1
|
<button :class="{ [`icon-${iconPosition}`]: true }">
|
<slot></slot>上加class属性无效
- 需要在外层包一层
<div class="content"><slot></slot></div>
- 父子元素在一行中,父元素
display: index-flex,子元素加order属性
- 使用 CSS 间接控制子元素顺序
- 默认
> .icon { order: 1; } > .content { order: 2; }
- 属性
iconPosition为right,则样式为&.icon-right { > .icon { order: 2; } > .content { order: 1; } }
- 不建议使用CSS
direction,不符合文字阅读习惯
VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<template>
<div class="vue-demo-button">
<button class="vue-button" :class="{[`icon-${iconPosition}`]: true}">
<svg v-if="icon" class="icon" aria-hidden="true">
<use :xlink:href="`#i-${icon}`"></use>
</svg>
<div class="content">
<slot/>
</div>
</button>
</div>
</template>
<style lang="scss">
.vue-demo-button {
display: inline-block;
margin-right: 10px;
}
.vue-button {
&.icon-right {
> .icon {
order: 2;
}
> .content {
order: 1;
}
}
/* ali iconfont common css */
> .icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
order: 1;
}
> .content {
order: 2;
}
}
</style>
|
inline-* 元素导致 行内元素不对齐,需要加样式 vertical-align: middle;
- 使
icon和文字间有空隙,逼近法设置近似的值,使整体宽度近似设计稿
- 注意
margin两边都要设置:
&.icon-right{ > .icon {margin-left: .3em; margin-right: 0;}}
- 都要清除(覆盖之前样式)另一边的空隙:
> .icon {margin-right: .3em; margin-left: 0;}
- 调整
padding值.3em,符合设设计稿宽度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<script>
export default {
props: {
/*
'icon': 'settings' || 'loading' || 'right' ||
'left' || 'download' || 'arrow-down' || 'thumbs-up'
*/
icon: {
type: String,
},
// 'iconPosition': 'left' || 'right'
iconPosition: {
type: String,
default: 'left'
}
},
}
</script>
|
props使用对象形式{}
- 以
key作为每个外部数据值的名字
- 后面加值的配置,
type和default属性
- 防呆,用外部数据的属性检查器
validator,排除错误的(不合法的)属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<script>
export default {
props: {
/*
'icon': 'settings' || 'loading' || 'right' ||
'left' || 'download' || 'arrow-down' || 'thumbs-up'
*/
icon: {
type: String,
},
// 'iconPosition': 'left' || 'right'
iconPosition: {
type: String,
default: 'left',
validator(userValue) {
console.log(userValue)
/*
if (userValue !== 'left' && userValue !== 'right') {
return false
}else {
return true
}
*/
// simplify if-else
// return value !== 'left' && value !== 'right' ? false : true;
return !(userValue !== 'left' && userValue !== 'right');
},
}
},
</script>
|
- 使用
webStorm的智能简化if...else...代码
if (userValue !== 'left' && userValue !== 'right') {...} else {...}
return value !== 'left' && value !== 'right' ? false : true;
return !(userValue !== 'left' && userValue !== 'right');
每次使用图标时,重复定义了 <svg></svg> 及其样式,考虑代码复用
抽出组件Icon.vue
在App.vue中全局注册组件
1
2
3
4
5
6
7
8
9
10
11
|
<script>
import Vue from 'vue'
import VueButtonGroup from './components/ButtonGroup/ButtonGroup.vue'
import VueButton from './components/vuebutton/VueButton.vue'
import VueIcon from './components/icon/Icon.vue'
// 全局注册组件
Vue.component('v-button', VueButton)
Vue.component('v-icon', VueIcon)
// ...
</script>
|
Icon.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<template>
<svg class="v-icon">
<use :xlink:href="`#i-${name}`"></use>
</svg>
</template>
<script>
export default {
props: {
name: {type: String},
},
};
</script>
<style lang="scss">
.v-icon {
width: 1em;
height: 1em;
}
</style>
|
VueButton.vue 使用子组件 Icon.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<template>
<div class="vue-demo-button">
<button class="vue-button" :class="{[`icon-${iconPosition}`]: true}">
<!-- 组件化 <Icon> -->
<VueIcon v-if="!!icon" :name="icon" class="icon" />
<div class="content">
<slot />
</div>
</button>
</div>
</template>
<script>
import VueIcon from '../icon/Icon.vue'
export default {
props: {
icon: {
type: String,
},
iconPosition: {
type: String,
default: 'left',
validator(userValue) {
console.log(userValue)
return (userValue === 'left' || userValue === 'right')
},
},
},
components: {
VueIcon
},
}
|
- 注意传变量
:name="icon"
- 根据是否存在外部数据
icon,来渲染<VueIcon v-if="!!icon" class="icon" :name="icon"></VueIcon>
- 三层数据传递:
App.vue中的VueButton icon="settings" icon-position="right">,传递icon="settings"和icon-position="right"给VueButton.vue的props
VueButton.vue中的<VueIcon v-if="!!icon" :name="icon" class="icon" />和<button class="vue-button" :class="{[icon-${iconPosition}]: true}">,接受props中的icon和iconPosition,传递:name="icon"给VueIcon.vue的props
VueIcon.vue中的<svg class="v-icon"><use :xlink:href="#i-${name}"></use></svg>,接受props: {name, },
- 经过字符模板拼接,来使用引入的字体图标
<script src="//at.alicdn.com/t/font_2138557_rt8obmx2qyd.js"></script>的SVG symbol
- 在
App.vue中直接写<VueButton icon="download" icon-position="right">下载</VueButton>就可显示带对应图标和文字的按钮
添加loading状态
- 更新图标库
- 要求:
- 点击按钮,显示
loading图标,并隐藏原来的图标
- 再点击,恢复原样
添加菊花动画VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<template>
<button class="vue-button" :class="{[`icon-${iconPosition}`]: true}">
<VueIcon v-if="!!icon" :name="icon" class="icon"/>
<div class="content">
<slot/>
</div>
</button>
</template>
<style lang="scss">
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
...
.loading {
animation: spin 1s infinite linear;
}
</style>
|
控制loading出现的逻辑
- 在
VueButton.vue添加外部数据isLoading,来判断是否显示loading图标
isLoading: { type: Boolean, default: false, }
- 在
VueButton.vue添加
<VueIcon v-if="!!icon && !isLoading" :name="icon" class="icon" />
- 和
<VueIcon v-if="isLoading" name="loading" class="loading icon"/>
- 显示的逻辑,注意菊花图标同样有
.icon的样式class="loading icon"
VueButton.vue添加条件渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
<template>
<button class="vue-button"
:class="{ [`icon-${iconPosition}`]: true }"
@click="$emit('click')">
<VueIcon v-if="!!icon && !isLoading"
:name="icon"
class="icon"
@click="clickLoading"/>
<VueIcon v-if="isLoading"
name="loading"
class="icon loading"/>
<div class="content">
<slot/>
</div>
</button>
</template>
<style lang="scss">
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading {
animation: spin 1s infinite linear;
}
.vue-button {
//...
&:hover {
//...
}
&:active {
//...
}
&:focus {
//...
}
&.icon-right {
> .icon {
order: 2;
margin-left: 0.3em;
margin-right: 0;
margin-top: 0.1em;
}
> .content {
order: 1;
}
}
/* ali iconfont common css */
> .icon {
width: 1em;
height: 1em;
margin-right: 0.3em;
margin-left: 0;
margin-top: 0.1em;
fill: currentColor;
overflow: hidden;
order: 1;
}
> .content {
order: 2;
}
</style>
|
由外部传入是否loading的条件
- 在
App.vue绑定属性:isLoading="true"
- 在
App.vue添加数据
data() { return { isLoading: false }
- 更改绑定属性为变量
:isLoading="isLoading"
- 绑定点击事件
<VueButton :isLoading="isLoading" @click="isLoading = !isLoading" icon="settings">按钮</VueButton>
- 点击取反
@click="isLoading = !isLoading"
- 自定义组件中触发的自定义点击事件
- 自定义组件中,Vue并不识别点击哪一个部分算是点击(在原生的标签中,Vue可以识别默认的点击事件)
- 需要在当前对象(自定义组件)主动触发
click事件:<button class="vue-button" :class="{[icon-${iconPosition}]: true}" @click="$emit('click')" />
- 在模板中写
$emit,不用加this.,直接写@click="$emit('click')"
- 或者用
.native修饰符
- 代码如下
App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<template>
<div id="app">
<VueButton>
按钮
</VueButton>
<VueButton :isLoading="isLoading1" @click="isLoading1 = !isLoading1" icon="settings">
按钮
</VueButton>
<VueButton :is-loading="isLoading2" @click="isLoading2 = !isLoading2" icon="settings" icon-position="right">
按钮
</VueButton>
<VueButton :is-loading="isLoading3" @click="isLoading3 = !isLoading3" icon="download" icon-position="right">
下载
</VueButton>
</div>
</template>
<script>
import VueButton from './components/vuebutton/VueButton.vue'
export default {
name: 'App',
data() {
return {
isLoading1: false,
isLoading2: false,
isLoading3: false,
}
},
components: {
VueButton,
}
}
</script>
|
VueButton.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<template>
<div class="vue-demo-button">
<button class="vue-button" :class="{[`icon-${iconPosition}`]: true}" @click="$emit('click')">
<VueIcon v-if="!!icon && !isLoading" :name="icon" class="icon" @click="kClick"/>
<VueIcon v-if="isLoading" name="loading" class="loading icon"/>
<div class="content">
<slot/>
</div>
</button>
</div>
</template>
<script>
import VueIcon from '../icon/Icon.vue'
export default {
props: {
icon: {
type: String, // ['settings'. 'loading'. 'right'. 'left'. 'download'. 'arrow-down'. 'thumbs-up']
},
isLoading: {
type: Boolean,
default: false,
},
iconPosition: {
type: String,
default: 'left',
validator(userValue) {
return (userValue === 'left' || userValue === 'right')
},
},
},
methods: {
kClick() {
this.$emit('click')
},
},
components: {
VueIcon
},
}
</script>
|
ButtonGroup.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<template>
<div class="vue-button-group">
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
.vue-button-group {
display: inline-flex;
vertical-align: middle;
//...
}
</style>
|
- 不能直接使用
<slot></slot>作为组件的根节点,需要包裹一层div
<slot></slot>渲染时会消失,可能含有多个节点
在App.vue中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
<template>
<div id="app">
<VueButton>
按钮
</VueButton>
<VueButton :isLoading="isLoading1"
@click="isLoading1 = !isLoading1"
icon="settings">设置
</VueButton>
<VueButton :is-loading="isLoading2"
@click="isLoading2 = !isLoading2"
icon="settings" icon-position="right">设置
</VueButton>
<VueButton :is-loading="isLoading3"
@click="isLoading3 = !isLoading3"
icon="download" icon-position="right">下载
</VueButton>
<vue-button-group>
<VueButton icon="left">上一页</VueButton>
<VueButton icon="">更多</VueButton>
<VueButton icon="right"
icon-position="right">下一页
</VueButton>
</vue-button-group>
</div>
</template>
<script>
import Vue from 'vue'
import VueButton from './components/vuebutton/VueButton.vue'
import VueButtonGroup from './components/ButtonGroup/ButtonGroup.vue'
// 全局注册组件
Vue.component('v-button', VueButton)
Vue.component('v-button-group', VueButtonGroup)
export default {
name: 'App',
data() {
return {
isLoading1: false,
isLoading2: true,
isLoading3: false,
}
},
components: {
VueButton,
VueButtonGroup
}
}
</script>
|
border的一个 bug,所有子元素左移了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
.vue-button-group {
display: inline-flex;
vertical-align: middle;
> .vue-button {
margin-right: 0;
border-radius: 0;
&:first-child {
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
&:last-child {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
/*
* 错误地显示 border 三边显示 一边被遮盖
&:not(:first-child) {
border-left: none;
}
*/
&:not(:first-child) {
margin-left: -2px;
}
// 解决border被后面的遮挡掉一边
&:hover {
position: relative;
z-index: 1;
}
}
}
|
- 非第一个元素使用 负margin 合并一边的 border:
margin-left: -2px;
- 解决 border 被后面的遮挡掉一边:
hover时,提升z-index
防止开发者错误使用UI,挂载时处理检查逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<script>
export default {
mounted() {
// console.log(this.$children)
// console.log(this.$el)
// console.log(this.$el.children)
for (const node of this.$el.children) {
// console.log(node)
const name = node.nodeName.toLowerCase()
if (name !== 'button') {
console.warn(`vue-button-group 的子元素应该全是 VueButton,但你写的是${name}`)
}
}
},
}
</script>
|
mounted时,检查一下子元素是否为button元素
console.log(this.$children)中能识别Vue对象
- 识别非
button标签,并在后台打印警告
其他
增加 hover 样式,模拟outline radius(-moz-outline-radius)
1
2
3
4
5
|
input[type=text]:focus {
box-shadow: 0 0 0 1pt red;
outline-width: 1px;
outline-color: red;
}
|
CSS变量和SCSS变量互不兼容
darken(var(--button-hover), 5%);
webStorm右键查看本地历史local history功能
参考文章
相关文章
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河
掘
思
知
简