大纲链接 §
[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
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河
掘
思
知
简