组件的初始化
全局注册:
<div id="app">
<ksd-button></ksd-button>
<ksd-time></ksd-time>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
}
},
created() {
},
methods: {
}
});
app.component("ksdButton", {
template: "<button>登录</button>",
data() {
return {
}
},
created() {
},
methods: {
}
});
app.component("ksdTime", {
template: "<div>我是时间</div>",
data() {
return {
}
},
created() {
},
methods: {
}
});
app.mount("#app");
</script>
局部注册:
<div id="app">
<ksd-button></ksd-button>
<ksd-time></ksd-time>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
}
},
created() {
},
methods: {
},
components: {
"KsdButton": {
template: "<button>登录</button>",
data() {
return {
}
},
created() {
},
methods: {
}
},
"KsdTime":{
template:"<div>我是时间</div>",
data() {
return {
}
},
created() {
},
methods: {
}
}
}
}).mount("#app");
</script>
组件的特点
1:复用性。
2:隔离性。
<div id="app">
<ksd-button></ksd-button>
<ksd-button></ksd-button>
<ksd-button></ksd-button>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
}
},
created() {
},
methods: {
}
});
app.component("KsdButton", {
template:"<button @click='countNum'>登录 { {count} }</button>",
data() {
return {
count: 1
}
},
created() {
},
methods: {
countNum() {
this.count++;
}
}
});
app.mount("#app");
</script>
父传子 - 自定义属性 props
书写规范:
1:属性名称全小写。
2:props 使用对象形式,不使用数组形式。
3:props 指定 type 如果错误,浏览器会提示警告不会报错,它不会影响 vue 的渲染。
4:使用组件自定义属性,属性可以直接通过指令和插值表达式使用,将其看成正常的 div 即可。
<div id="app">
<ksd-button :ksdtext="num" :users="users"></ksd-button>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
num: 1234,
users: [1, 2, 3, 4]
}
},
created() {
},
methods: {
}
});
app.component("ksdButton", {
props: {
ksdtext: {
type: Number,
required: true
},
users: Array
},
template: "<button @click='countNum' v-for='(user, index) in users'>{ {ksdtext} } == { {count} }</button>",
data() {
return {
count: 1
}
},
created() {
},
methods: {
countNum() {
this.count++;
}
}
});
app.mount("#app");
</script>
子传父 - 自定义事件 $emit
<div id="app">
<ksd-load-more @loadmore="loadMoreUsers"></ksd-load-more>
<p v-for="(user, index) in parentUsers" :key="user.id">
<span>{ {user} }</span>
</p>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
parentUsers: []
}
},
methods: {
loadMoreUsers(users) {
this.parentUsers = users;
}
}
});
app.component("KsdLoadMore", {
template: `<div><button @click="loadMore">加载更多</button></div>`,
methods: {
loadMore() {
this.$emit("loadmore", [{ id: 1, name: "zhangsan" }]);
}
}
});
app.mount("#app");
</script>
执行流程:
封装组件 - ksd-button
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ksd-button</title>
<link rel="stylesheet" href="css/ksdui.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2882935_q0cj641ukuh.css">
</head>
<body>
<div id="app">
<ksd-button>默认按钮</ksd-button>
<ksd-button type="primary" icon="vip1">主要按钮</ksd-button>
<ksd-button type="success" icon="setting" round>成功按钮</ksd-button>
<ksd-button type="info" icon="28" round>信息按钮</ksd-button>
<ksd-button type="warning" icon="houtai12">警告按钮</ksd-button>
<ksd-button type="danger" icon="xihuan" round>危险按钮</ksd-button>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
}
},
created() {
},
methods: {
}
});
app.component("ksdButton", {
props: {
type: {
type: String,
default: "default",
},
icon: {
type: String,
default: "default",
},
round: {
type: Boolean,
default: false,
}
},
template: "<button type='button' class='ksd-button' :class='[type ? `ksd-button--${type}` : ``, round ? `is-round` : ``]'>" +
"<span>" +
"<i v-if='icon' :class='`iconfont icon-${icon}`'></i>" +
"<slot></slot>" +
"</span>" +
"</button>",
});
app.mount("#app");
</script>
</body>
</html>
封装组件 - ksd-page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ksd-page</title>
<link rel="stylesheet" href="css/ksdui.css">
</head>
<body>
<div id="app">
<ksd-page
:page-index="currentPage"
:total="count"
:is-go="false"
:is-total="false"
:page-size="pageSize"
@change="pageChange">
</ksd-page>
<ksd-page
:page-index="currentPage"
:total="count"
:is-go="true"
:is-total="true"
:page-size="pageSize"
@change="pageChange">
</ksd-page>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
count: 100,
pageSize: 10,
currentPage: 1,
items: []
}
},
created() {
},
methods: {
pageChange(pageNo) {
}
}
});
app.component("ksdPage", {
props: {
perPages: {
type: Number,
default: 5
},
pageIndex: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
total: {
type: Number,
default: 1
},
isGo: {
type: Boolean,
default: false
},
isTotal: {
type: Boolean,
default: false
}
},
template: "<div><ul class=\"ksd-paging\">\n" +
" <!-- prev -->\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--prev', {'paging-item--disabled' : index == 1}]\"\n" +
" @click=\"prev\">上一页</li>\n" +
"\n" +
" <!-- first -->\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--first', {'paging-item--disabled' : index == 1}]\"\n" +
" @click=\"first\">首页</li>\n" +
"\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--more']\"\n" +
" v-if=\"showPrevMore\">...</li>\n" +
"\n" +
" <li\n" +
" :class=\"['paging-item', {'paging-item--current' : index == pager}]\"\n" +
" v-for=\"pager in pagers\"\n" +
" @click=\"go(pager)\">{ {pager} }</li>\n" +
"\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--more']\"\n" +
" v-if=\"showNextMore\">...</li>\n" +
"\n" +
" <!-- last -->\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--last', {'paging-item--disabled' : index == pages}]\"\n" +
" @click=\"last\">尾页</li>\n" +
"\n" +
" <!-- next -->\n" +
" <li\n" +
" :class=\"['paging-item', 'paging-item--next', {'paging-item--disabled' : index == pages}]\"\n" +
" @click=\"next\">下一页</li>\n" +
" <li v-if='isGo' :class=\"['paging-item', 'paging-item--next']\"><input class='pageinput' type='text' v-model='index'></li>\n" +
" <li v-if='isTotal' :class=\"['paging-item', 'paging-item--next']\">共{ {total} }条</li>\n" +
" </ul></div>",
methods: {
toPage() {
this.go(this.index)
},
prev() {
if (this.index > 1) {
this.go(this.index - 1)
}
},
next() {
if (this.index < this.pages) {
this.go(this.index + 1)
}
},
first() {
if (this.index !== 1) {
this.go(1)
}
},
last() {
if (this.index != this.pages) {
this.go(this.pages)
}
},
go(page) {
if (this.index !== page) {
this.index = page
//父组件通过change方法来接受当前的页码
this.$emit('change', this.index)
}
}
},
computed: {
pages() {
return Math.ceil(this.size / this.limit)
},
pagers() {
const array = []
const perPages = this.perPages * 1
const pageCount = this.pages * 1
let current = this.index * 1
const _offset = (perPages - 1) / 2
const offset = {
start: current - _offset,
end: current + _offset
}
if (offset.start < 1) {
offset.end = offset.end + (1 - offset.start)
offset.start = 1
}
if (offset.end > pageCount) {
offset.start = offset.start - (offset.end - pageCount)
offset.end = pageCount
}
if (offset.start < 1) offset.start = 1
this.showPrevMore = (offset.start > 1)
this.showNextMore = (offset.end < pageCount)
for (let i = offset.start; i <= offset.end; i++) {
array.push(i)
}
return array
}
},
data() {
return {
index: this.pageIndex,
limit: this.pageSize,
size: this.total || 1,
showPrevMore: false,
showNextMore: false
}
},
watch: {
pageIndex(val) {
this.index = val || 1
},
pageSize(val) {
this.limit = val || 10
},
total(val) {
this.size = val || 1
}
}
});
app.mount("#app");
</script>
</body>
</html>
封装组件 - ksd-loading
<!DOloadingType html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ksd-loading</title>
<link rel="stylesheet" href="css/ksdui.css">
<style>
.loadingbox { text-align: center; }
</style>
</head>
<body>
<div id="app">
<ksd-loading :load-type="0" :ctype="2" :load-text="loadText"></ksd-loading>
<ksd-loading :load-type="0" :ctype="1" :bgclor="'red'" :load-text="loadText"></ksd-loading>
<ksd-loading :load-type="0" :ctype="2" :bgclor="'red'" :load-text="loadText"></ksd-loading>
</div>
<script src="js/vue.global.js"></script>
<script>
var app = Vue.createApp({
data() {
return {
loadText: ["正在来的路上...", "亲已经没有了...", "亲,家底被你掏空了..."]
}
}
});
app.component("KsdLoading", {
props: {
ctype: {
type: Number,
default: 1
},
bgclor: {
type: String,
default: "#171915"
},
loadType: Number,
loadText: {
type: Array,
default: ["数据加载中...", "数据加载完毕...", "亲,我是有底线的..."]
}
},
template: `
<div class="loadingbox">
<div v-if="loadType == 2">{ {loadText[2]} }</div>
<div v-if="loadType == 1">{ {loadText[1]} }</div>
<div v-if="loadType == 0">{ {loadText[0]} }</div>
<div v-if="loadType == 0">
<div class="spinner2" :style='{"background": bgclor}' v-if="ctype == 2"></div>
<div class="spinner" v-if="ctype == 1">
<div class="spinner-container container1">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
<div class="spinner-container container2">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
<div class="spinner-container container3">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
</div>
</div>
</div>
`
});
app.mount("#app");
</script>
</body>
</html>
Vue-cli 中的使用
1:搭建 vue-cli 脚手架。
npm install -g @vue/cli
# OR
yarn global add @vue/cli
# OR
npm install -g cnpm --registry=https://registry.npmmirror.com
cnpm install -g @vue/cli
vue ui # 图形化界面中创建项目,安装router和vuex依赖,运行启动
2:component 目录下定义 KsdLoading 加载组件。
<template>
<div class="loadingbox" v-if="closeFlag" @click="closeLoading">
<div v-if="loadType == 2">{ { loadText[2] } }</div>
<div v-if="loadType == 1">{ { loadText[1] } }</div>
<div v-if="loadType == 0">{ { loadText[0] } }</div>
<div v-if="loadType == 0">
<div class="spinner2" :style='{"background": bgclor}' v-if="ctype == 2"></div>
<div class="spinner" v-if="ctype == 1">
<div class="spinner-container container1">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
<div class="spinner-container container2">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
<div class="spinner-container container3">
<div class="circle1" :style='{"background": bgclor}'></div>
<div class="circle2" :style='{"background": bgclor}'></div>
<div class="circle3" :style='{"background": bgclor}'></div>
<div class="circle4" :style='{"background": bgclor}'></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "KsdLoading",
props: {
ctype: {
type: Number,
default: 1
},
bgclor: {
type: String,
default: "#171915"
},
loadType: {
type: Number,
default: 0
},
loadText: {
type: Array,
default() {
return ["数据加载中...", "数据加载完毕...", "亲,我是有底线的..."];
}
}
},
data() {
return {
closeFlag: true
}
},
methods: {
closeLoading() {
this.closeFlag = false;
this.$emit("message");
}
}
}
</script>
<style scoped>
/* loading样式 */
.loadingbox {
text-align: center;
}
.spinner {
margin: 20px auto;
width: 20px;
height: 20px;
position: relative;
}
.container1 > div, .container2 > div, .container3 > div {
width: 6px;
height: 6px;
background-color: #333;
border-radius: 100%;
position: absolute;
-webkit-animation: bouncedelay 1.2s infinite ease-in-out;
animation: bouncedelay 1.2s infinite ease-in-out;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.spinner .spinner-container {
position: absolute;
width: 100%;
height: 100%;
}
.container2 {
-webkit-transform: rotateZ(45deg);
transform: rotateZ(45deg);
}
.container3 {
-webkit-transform: rotateZ(90deg);
transform: rotateZ(90deg);
}
.circle1 { top: 0; left: 0; }
.circle2 { top: 0; right: 0; }
.circle3 { right: 0; bottom: 0; }
.circle4 { left: 0; bottom: 0; }
.container2 .circle1 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.container3 .circle1 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
.container1 .circle2 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.container2 .circle2 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.container3 .circle2 {
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s;
}
.container1 .circle3 {
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s;
}
.container2 .circle3 {
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s;
}
.container3 .circle3 {
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s;
}
.container1 .circle4 {
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s;
}
.container2 .circle4 {
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s;
}
.container3 .circle4 {
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s;
}
@-webkit-keyframes bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0.0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes bouncedelay {
0%, 80%, 100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
} 40% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}
.spinner2 {
width: 24px;
height: 24px;
background-color: #67CF22;
margin: 20px auto;
-webkit-animation: rotateplane 1.2s infinite ease-in-out;
animation: rotateplane 1.2s infinite ease-in-out;
}
@-webkit-keyframes rotateplane {
0% { -webkit-transform: perspective(120px) }
50% { -webkit-transform: perspective(120px) rotateY(180deg) }
100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
}
@keyframes rotateplane {
0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
} 50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
} 100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}
</style>
3:定义 KsdPage 分页组件。
<template>
<div>
<ul class="ksd-paging">
<!-- prev -->
<li
:class="[
'paging-item',
'paging-item--prev',
{ 'paging-item--disabled': index == 1 },
]"
@click="prev"
>
上一页
</li>
<!-- first -->
<li
:class="[
'paging-item',
'paging-item--first',
{ 'paging-item--disabled': index == 1 },
]"
@click="first"
>
首页
</li>
<li :class="['paging-item', 'paging-item--more']" v-if="showPrevMore">
...
</li>
<li
:class="['paging-item', index == pager ? 'paging-item--current' : '']"
v-for="pager in pagers"
@click="go(pager)"
:key="pager">
{ { pager } }
</li>
<li :class="['paging-item', 'paging-item--more']" v-if="showNextMore">
...
</li>
<!-- last -->
<li
:class="[
'paging-item',
'paging-item--last',
{ 'paging-item--disabled': index == pages },
]"
@click="last"
>
尾页
</li>
<!-- next -->
<li
:class="[
'paging-item',
'paging-item--next',
{ 'paging-item--disabled': index == pages },
]"
@click="next"
>
下一页
</li>
<li v-if="isGo" :class="['paging-item', 'paging-item--next']">
<input class="pageinput" type="text" v-model="index"/>
</li>
<li v-if="isTotal" :class="['paging-item', 'paging-item--next']">
共{ { total } }条
</li>
</ul>
</div>
</template>
<script>
export default {
name: "KsdPage",
props: {
perPages: {
type: Number,
default: 5
},
pageIndex: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
total: {
type: Number,
default: 1
},
isGo: {
type: Boolean,
default: false
},
isTotal: {
type: Boolean,
default: false
}
},
methods: {
toPage() {
this.go(this.index)
},
prev() {
if (this.index > 1) {
this.go(this.index - 1)
}
},
next() {
if (this.index < this.pages) {
this.go(this.index + 1)
}
},
first() {
if (this.index !== 1) {
this.go(1)
}
},
last() {
if (this.index != this.pages) {
this.go(this.pages)
}
},
go(page) {
if (this.index !== page) {
this.index = page
this.$emit('change', this.index)
}
}
},
computed: {
pages() {
return Math.ceil(this.size / this.limit)
},
pagers() {
const array = []
const perPages = this.perPages * 1
const pageCount = this.pages * 1
let current = this.index * 1
const _offset = (perPages - 1) / 2
const offset = {
start: current - _offset,
end: current + _offset
}
if (offset.start < 1) {
offset.end = offset.end + (1 - offset.start)
offset.start = 1
}
if (offset.end > pageCount) {
offset.start = offset.start - (offset.end - pageCount)
offset.end = pageCount
}
if (offset.start < 1) offset.start = 1
this.showPrevMore = (offset.start > 1)
this.showNextMore = (offset.end < pageCount)
for (let i = offset.start; i <= offset.end; i++) {
array.push(i)
}
return array
}
},
data() {
return {
index: this.pageIndex,
limit: this.pageSize,
size: this.total || 1,
showPrevMore: false,
showNextMore: false
}
},
watch: {
pageIndex(val) {
this.index = val || 1
},
pageSize(val) {
this.limit = val || 10
},
total(val) {
this.size = val || 1
}
}
};
</script>
<style scoped>
/* 分页样式*/
.ksd-paging {
display: inline-block;
padding: 0;
margin: 1rem 0;
font-size: 0;
list-style: none;
user-select: none;
}
.ksd-paging .paging-item {
display: inline;
font-size: 14px;
position: relative;
padding: 6px 12px;
line-height: 1.42857143;
text-decoration: none;
border: 1px solid #ccc;
background-color: #fff;
margin-left: -1px;
cursor: pointer;
color: #0275d8;
}
.ksd-paging .paging-item:first-child {
margin-left: 0;
}
.ksd-paging .paging-item:hover {
background-color: #f0f0f0;
color: #0275d8;
}
.ksd-paging .paging-item.paging-item--disabled,
.ksd-paging .paging-item.paging-item--more {
background-color: #fff;
color: #505050;
}
.ksd-paging .paging-item.paging-item--disabled {
cursor: not-allowed;
opacity: .75;
}
.ksd-paging .paging-item.paging-item--more,
.ksd-paging .paging-item.paging-item--current {
cursor: default;
}
.ksd-paging .paging-item.paging-item--current {
background-color: #0275d8;
color: #fff;
position: relative;
z-index: 1;
border-color: #0275d8;
}
.ksd-paging .pageinput {
width: 40px;
text-align: center;
outline: none;
border: 1px solid #eee;
color: #0275d8;
}
</style>
4:router/index.js 中定义路由。
{
path: '/userList',
name: 'UserList',
component: () => import('../views/UserList.vue')
}
5:App.vue 中定义路由导航。
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/userList">用户列表</router-link>
</div>
<router-view/>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
6:相应页面中注册和使用组件。
<template>
<div class="userList">
<h1>我是用户列表</h1>
<p v-for="(user, index) in users" :key="user.id">
<span>{ {index} }:{ {user.id} } == { {user.name} }</span>
</p>
<ksd-loading @message="loadingChange"></ksd-loading>
<ksd-page :total="total" @change="pageChange"></ksd-page>
</div>
</template>
<script>
import KsdLoading from "@/components/KsdLoading";
import KsdPage from "@/components/KsdPage";
export default {
name: "UserList",
components: {
KsdLoading, KsdPage
},
data() {
return {
users: [],
total: 110
}
},
created() {
this.users = [
{ id: 1, name: "zhangsan1" },
{ id: 2, name: "zhangsan2" },
{ id: 3, name: "zhangsan3" }
]
},
methods: {
loadingChange() {
alert("关闭了...");
},
pageChange(pageNo) {
alert(pageNo);
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
<ksd-loading></ksd-loading>
</template>
<script>
import KsdLoading from "@/components/KsdLoading";
export default {
name: "About",
components: {
KsdLoading
},
data() {
return {
users: []
}
},
created() {
this.users = [
{ id: 1, name: "zhangsan1" },
{ id: 2, name: "zhangsan2" },
{ id: 3, name: "zhangsan3" }
]
}
}
</script>
<style scoped>
</style>