官网
https://cn.vuejs.org/v2/guide/
一、单页面Tab导航栏切换
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.js"></script>
<style>
.tab{
position: relative;
}
ul{
width: 300px;
display: flex;
justify-content: space-between;
}
ul li{
border: 1px solid #000;
padding: 10px;
list-style: none;
}
ul li.active{
background-color: #ccc;
}
.tab div{
display: none;
width: 300px;
height: 300px;
}
.tab div.current{
display: block;
}
</style>
</head>
<body>
<div id="app">
<div class="tab">
<ul>
<!-- 初始化序号与当前点击元素序号相等,添加类名 ,等价于 :style="{backgroundColor: currentIndex == index ? '#ccc': 'initial' }"-->
<li :class="currentIndex == index ? 'active':''" v-for="(item,index) in list" :key="item.id" @click="click(index)">{{ item.name }}</li>
</ul>
<div :class="currentIndex == index ? 'current':''" v-for="(item,index) in bg" :key="item.id" :style="'backgroundColor:' + item.color"></div>
</div>
</div>
</body>
<script>
new Vue ({
el: "#app",
data: {
currentIndex: 0,
// 初始化序号
list: [
{id:1,name:'张三'},
{id:2,name:'李四'},
{id:3,name:'王五'},
],
bg: [
{id:1,color:'red'},
{id:2,color:'blue'},
{id:3,color:'pink'},
]
},
methods:{
// 点击当前导航栏元素并将当前点击元素序号给初始化序号
click(i){
this.currentIndex = i;
}
}
})
</script>
</html>
补充案例:底部导航栏路由和图片切换,同时使用router-link和@click点击图标2次才显示,可使用props和$router.push解决
参考文献:https://www.jb51.net/article/160601.htm
// 子组件
<template>
<ul class="tabbar-container">
<li class="tabbar-item" v-for="(item,index) in tabs" :key="index" @click="$router.push(item.path)">
<i class="tabbar-icon" :style="{backgroundImage: index == currentIndex ? 'url(' + item.reicon + ')' : 'url(' + item.icon + ')',backgroundRepeat: 'no-repeat',backgroundSize: '100%'}"></i>
{{ item.name }}
</li>
</ul>
</template>
<script>
export default {
name: "TabBar",
props:{
index: Number
},
data() {
return {
currentIndex: this.index,
tabs: [
{id:1,name:"首页",active:true,icon:require("../../assets/images/icon/dibu-shouye.png"),reicon:require("../../assets/images/icon/dibu-shouye2.png"),path:"/home"},
{id:2,name:"分类",active:false,icon:require("../../assets/images/icon/dibu-fenlei.png"),reicon:require("../../assets/images/icon/dibu-fenlei2.png"),path:"/type"},
{id:3,name:"购物车",active:false,icon:require("../../assets/images/icon/dibu-gouwuche.png"),reicon:require("../../assets/images/icon/dibu-gouwuche2.png"),path:"/shopcar"},
{id:4,name:"消息",active:false,icon:require("../../assets/images/icon/dibu-xiaoxi.png"),reicon:require("../../assets/images/icon/dibu-xiaoxi2.png"),path:"/message"},
{id:5,name:"个人中心",active:false,icon:require("../../assets/images/icon/dibu-gerenzhongxin.png"),reicon:require("../../assets/images/icon/dibu-gerenzhongxin2.png"),path:"/my"}
]
}
}
}
</script>
// 父组件
<TabBar class="tabbar" :index='0'></TabBar>
二、模拟后端接口,验证用户名是否可用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.js"></script>
<style>
</style>
</head>
<body>
<div id="app">
<!-- lazy:失去焦点后触发 -->
用户名:<input type="text" v-model.lazy="username">{{ tip }}
</div>
</body>
<script>
/*步骤:
1、采用侦听器监听用户名的变化
2、调用后台接口进行验证
3、根据验证的结果调整提示信息
*/
new Vue ({
el: "#app",
data: {
currentIndex: 0,
username: "",
tip: ""
},
methods:{
checkName(username){
// 模拟接口调用
setTimeout(() => {
if (username == "admin") {
this.tip = "用户名已存在,请更换一个"
}else{
this.tip = "用户名可以使用"
}
},2000)
}
},
watch: {
// 属性名与方法名必须一致
username(val){
// 调用后台接口验证用户名的合法性
this.checkName(val);
// 修改提示信息
this.tip = "正在验证……"
}
}
})
</script>
</html>
三、购物车(父子组件传值)
插槽:https://cn.vuejs.org/v2/guide/components-slots.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.js"></script>
<style>
.cart-title,.cart-total{
width: 50%;
border: 1px solid #000;
padding: 10px;
box-sizing: border-box;
}
.cart-title{
margin: 0;
border-bottom: 0;
text-align: center;
}
.cart-total{
border-top: 0;
display: flex;
justify-content: space-between;
}
.cart-list{
width: 50%;
text-align: center;
border-collapse: collapse;
}
.cart-list .totalSelect{
margin-top: -10px;
}
.cart-list .btn-group .btn-num{
width: 20px;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<h3 class="cart-title">我的商品</h3>
<!-- 绑定属性或方法,前后名称可以不一致 -->
<car-list :list="cart_list" @del-good="delGood" @change-num="changeNum"></car-list>
<car-total :list="cart_list"></car-total>
</div>
<template id="carList">
<table class="cart-list" border="1" cellpadding="10" cellspacing="10">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>数量</th>
<th>操作</th>
</tr>
<tr v-for="(item,index) in list" :key="index">
<td>{{ item.goods_name }}</td>
<td>{{ item.goods_price }}</td>
<td class="btn-group">
<span class="btn btn-reduce" @click="reduce(index)">-</span>
<input class="btn-num" type="text" v-model="item.num" @blur="change(index,$event)"/>
<span class="btn btn-add" @click="add(index)">+</span>
</td>
<td>
<button class="delete" @click="del(index)">删除</button>
</td>
</tr>
</table>
</template>
<template id="carTotal">
<div class="cart-total">
<div class="total-pay">
总计:共 {{ list.length }} 件商品,
合计:{{ totalPrice }} 元
</div>
<button class="total-pay-success">去结算</button>
</div>
</template>
</body>
<script>
new Vue ({
el: "#app",
data: {
cart_list: [{
goods_name: '小米6',
goods_price: '1699',
num: '2',
}, {
goods_name: '红米3',
goods_price: '699',
num: '1',
}, {
goods_name: '小米8',
goods_price: '2899',
num: '1',
}],
},
methods:{
// 根据子组件传过来的值中的type,改变数量输入框的值
changeNum(val){
// console.log(val)
if (val.type == 'change') {
this.cart_list[val.id].num = val.num;
}else if (val.type == 'reduce') {
if (this.cart_list[val.id].num <= 0) {
this.cart_list[val.id].num = 0;
}else{
this.cart_list[val.id].num--;
}
}else{
this.cart_list[val.id].num++;
}
},
// 接收子组件传过来的当前元素的id,删除当前点击元素
delGood(id){
this.cart_list.splice(id,1);
},
},
components: {
carList: {
template: '#carList',
data(){
return{
}
},
methods: {
// 将当前已减少的输入框数量传给父组件
reduce(id){
this.emit('change-num',id,'reduce')
},
// 将当前已增加的输入框数量传给父组件
add(id){
this.emit('change-num',id,'add')
},
// 将当前已改变的输入框数量传给父组件
change(id,event){
this.emit('change-num',id,'change',event.target.value)
},
// 将当前点击元素id传给父组件
del(id){
this.emit('del-good',id)
},
// 封装子组件传值父组件
emit(){
// console.log(arguments)
if (arguments.length > 2) {
this.$emit(arguments[0],{
id: arguments[1],
type: arguments[2],
num: arguments[3]
})
}else{
this.$emit(arguments[0],arguments[1])
}
}
},
// 接收父组件传给子组件的值
props: ['list']
},
carTotal: {
template: '#carTotal',
computed: {
// 计算总价
totalPrice(){
let total_price = 0;
this.list.forEach(item => {
total_price += Number(item.goods_price) * Number(item.num);
})
return total_price;
}
},
props: ['list']
}
}
})
</script>
</html>
补充:兄弟组件传值
https://www.cnblogs.com/rich23/p/7110409.html
五、异步操作
promise和ajax使用
query(url){
let p = new Promise((resolve,reject) => {
Ajax('get',url,null,success,failed);
// 返回成功数据
function success(jsondata){
let json = JSON.parse(jsondata);
resolve(json);
}
// 返回失败数据
function failed(){
reject('服务器错误');
}
});
return p;
};
query('http://localhost:3000/data')
.then(res => {
// 返回成功数据
console.log(res);
// 返回下一次异步调用数据
return query('http://localhost:3000/data2');
},rej => {
// 返回失败数据
console.log(rej);
})
.then(res => {
console.log(res);
return query('http://localhost:3000/data3');
},rej => {
console.log(rej);
})
.then(res => {
console.log(res);
},rej => {
console.log(rej);
})
// Ajax封装
function Ajax(type, url, data, success, failed){
// 创建ajax对象
let xhr = null;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
//转化为大写
type = type.toUpperCase();
// 用于清除缓存,防止浏览器缓存
let random = Math.random();
// 字符串拼接传过来的数据,如'name=zhangsan&'
if(typeof data == 'object'){
var str = '';
for(var key in data){
str += key+'='+data[key]+'&';
}
data = str.replace(/&$/, '');
}
// 判断使用方法
if(type == 'GET'){
// 根据是否传入数据处理参数显示
if(data){
xhr.open('GET', url + '?' + data, true);
} else {
xhr.open('GET', url + '?t=' + random, true);
}
xhr.send(null);
} else if(type == 'POST'){
xhr.open('POST', url, true);
// 如果需要像html表单那样POST数据,请使用setRequestHeader() 来添加http头
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(data);
}
// 检测状态,处理返回数据
xhr.onreadystatechange = function(){
//检测是否已经准备好
if(xhr.readyState == 4){
//表示响应准备就绪
if((xhr.status >= 200&&xhr.status<300)||xhr.status==304){
//请求成功之后的处理
success(xhr.responseText);
} else {
//处理ajax返回异常的情形
if(failed){
failed(xhr.status);
}
}
}
}
}
fetch使用
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
axios使用
post报错400:https://www.cnblogs.com/chenlw/p/9994891.html
传参:https://blog.csdn.net/zhaofuqiangmycomm/article/details/89479904
官方文档:https://www.kancloud.cn/yunye/axios/234846
vue:https://hacpai.com/article/1567922774522
封装:https://www.cnblogs.com/panax/p/10942889.html
https://www.cnblogs.com/chaoyuehedy/p/9931146.html
async/await使用
https://segmentfault.com/a/1190000007535316
四、后台管理路由
vue-router使用
https://router.vuejs.org/zh/installation.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.js"></script>
<script src="vue-router.js"></script>
<style>
*{
padding: 0;
margin: 0;
}
html,body,#app,.wrapper{
height: 100%;
}
ul{
list-style: none;
}
a{
text-decoration: none;
color: #fff;
}
.wrapper{
display: flex;
flex-direction: column;
justify-content: space-between;
}
.header,.footer,.main .left{
background-color: #666;
color: #fff;
}
.header,.footer,.main .left ul li{
text-align: center;
padding: 10px 0;
}
.main{
height: 100%;
display: flex;
justify-content: space-between;
}
.main .left{
width: 20%;
}
.main .left ul li{
border-bottom: 1px solid #fff;
background-color: #ccc;
}
.main .right{
flex: 1;
text-align: center;
}
.main .right h3{
padding: 10px 0;
}
.main .right table{
border-collapse: collapse;
width: 60%;
margin: 0 auto;
}
.main .right table td,.main .right table th{
padding: 10px 0;
}
.main .right table a{
color: #000;
}
</style>
</head>
<body>
<!-- 要被vue实例控制的区域 -->
<div id="app">
<router-view></router-view>
</div>
</body>
<script>
// 定义app根组件
const App = {
template: `
<div class="wrapper">
<header class="header">后台管理系统</header>
<div class="main">
<div class="left">
<ul>
<li>
<router-link to="/users">用户管理</router-link>
</li>
<li>
<router-link to="/rights">权限管理</router-link>
</li>
<li>
<router-link to="/goods">商品管理</router-link>
</li>
<li>
<router-link to="/orders">订单管理</router-link>
</li>
<li>
<router-link to="/settings">用户管理</router-link>
</li>
</ul>
</div>
<div class="right">
<router-view></router-view>
</div>
</div>
<footer class="footer">版权信息</footer>
</div>
`
}
const Users = {
data(){
return{
list: [
{id:1,name:"张三",age: 10},
{id:2,name:"李四",age: 20},
{id:3,name:"王五",age: 30},
]
}
},
methods: {
goDetail(id){
this.$router.push('/userinfo/' + id)
}
},
template: `
<div>
<h3>用户管理区域</h3>
<table border="1">
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in list" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>
`
}
const UserInfo = {
// 接收参数
props: ['id'],
methods: {
// 返回
goBack(){
this.$router.go(-1)
}
},
template: `
<div>
<h5>用户详情页-{{ id }}</h5>
<button @click="goBack()">后退</button>
</div>
`
}
const Rights = {
template: `
<div>
<h3>权限管理区域</h3>
</div>
`
}
const Goods = {
template: `
<div>
<h3>商品管理区域</h3>
</div>
`
}
const Orders = {
template: `
<div>
<h3>订单管理区域</h3>
</div>
`
}
const Settings = {
template: `
<div>
<h3>系统管理区域</h3>
</div>
`
}
// 创建路由对象
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/users',
component: App,
children: [
{
path: '/users',
component: Users
},
{
// 传参id
path: '/userinfo/:id',
component: UserInfo,
props: true
},
{
path: '/rights',
component: Rights
},
{
path: '/goods',
component: Goods
},
{
path: '/orders',
component: Orders
},
{
path: '/settings',
component: Settings
}
]
}
]
})
new Vue({
el: "#app",
router
})
</script>
</html>
五、登录
token:https://blog.csdn.net/c880420/article/details/80346127
完整案例:https://www.cnblogs.com/web-record/p/9876916.html
// 根目录下vue.config.js 解决跨域
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000/api',
ws: true,
changeOrigin: true,
pathRewrite:{
'^/api': ''
}
},
}
}
}
// main.js
import axios from 'axios'
Vue.prototype.$http = axios
// 添加请求头
axios.interceptors.request.use(config => {
config.headers.Authorization = window.sessionStorage.getItem('token');
return config;
})
router.beforeEach((to,from,next) => {
// 登录页无需权限
if (to.path == '/login') {
next()
}else{
// 获取token
const token = window.sessionStorage.getItem('token');
// token不存在直接跳转登录页
if(!token) return next('/login');
// 存在放行
next();
}
})
// login.vue
this.$http.post('/api/login',data).then(res => {
if(res.status !== 200){
// element-ui弹窗
this.$message.error('登录失败');
}
this.$message.success('登录成功');
// 缓存token
window.sessionStorage.setItem('token',res.data.token);
this.$router.push('/home')
})
// 退出,清缓存
window.sessionStorage.clear();
this.$router.push('/login');
六、增删改查
调用后端接口实现图书管理增删改查
https://github.com/mycummity/book
实现功能
调用后端接口,实现数据库增删改查并将结果返回给前端
filter过滤器进行关键字搜索和格式化日期
watch监听数据变化
created初始化数据
directives自定义获取焦点
computed计算数据
keydown设置快捷键
响应式布局
https://www.jianshu.com/p/6e77c838ab71
https://www.cnblogs.com/wgl0126/p/9468804.html
https://www.cnblogs.com/baiyygynui/p/5903749.html锚链接
navArray: [
{
href: "#navTitle1",
name: "应用简介"
}
]
<div
class="navItem"
:class="{'selected':currentIndex==index}"
v-for="(item,index) in navArray"
:key="index"
@click="goAnchor(item.href)"
>
<span>{{item.name}}</span>
</div>
<div class="appSketch" id="navTitle1">
</div>
goAnchor(type) {
var anchor = this.$el.querySelector(type);
// chrome
document.body.scrollTop = anchor.offsetTop - 50;
// firefox
document.documentElement.scrollTop = anchor.offsetTop - 50;
}