缘由
去年寒假里学习了Vue.js,开学后想做一个完整的练手项目实战一下,最后决定模仿小米手机官网做一个网站项目,具体参考了Github上一位作者的项目。
现在已经基本完成了,分享在CSDN作为学习记录。
简介
- 技术支持 :该项目采用前后端分离的设计结构,使用Vue2+Vue-Router+Vuex+Axios+ElementUI作为前端技术框架,后端采用了Koa框架,使用MySQL数据库作为数据存储支持。
- 项目功能:项目包含登录、注册、商品浏览、商品检索、收藏、购物车、订单生成与结算等功能
- 项目页面:项目整体页面分为首页、全部商品、商品详情页、登录注册、我的购物车、我的订单、订单结算等
- 后端模式:使用MVC模式,设计对应的模型层、控制层。
- 数据支持:使用Github项目中的小米商城数据
首页实现
首页主要分为4个部分:头部信息栏、导航栏、商品列表、底部信息栏。
1. 头部信息栏
头部信息栏主要功能为:登录注册按钮、登录成功后显示用户信息、我的订单、我的收藏、购物车商品数目信息。
效果图
效果实现
- 源码
<template>
<div class="topbar">
<div class="nav">
<ul>
<li v-if="!this.$store.getters.getUser">
<el-button type="text" @click="login">登录</el-button>
<span class="sep">|</span>
<el-button type="text" @click="register = true">注册</el-button>
</li>
<li v-else>
欢迎
<el-popover placement="top" width="180" v-model="visible">
<p>确定退出登录吗?</p>
<div style="text-align: right; margin: 10px 0 0">
<el-button size="mini" type="text" @click="visible = false">取消</el-button>
<el-button type="primary" size="mini" @click="logout">确定</el-button>
</div>
<el-button type="text">{{this.$store.getters.getUser.userName}}</el-button>
</el-popover>
</li>
<li>
<router-link to="/order">我的订单</router-link>
</li>
<li>
<router-link to="/collect">我的收藏</router-link>
</li>
<li :class="getNum > 0 ? 'shopCart-full' : 'shopCart'">
<router-link to="/shoppingCart">
<i class="el-icon-shopping-cart-full"></i> 购物车
<span class="num">({{getNum}})</span>
</router-link>
</li>
</ul>
</div>
</div>
</template>
- 逻辑实现
- 进入首页之后,判断localStorage内是否存在登录用户,若不存在则设置登录列表初始状态为隐藏。
- 点击触发 login 事件后更改vuex中的setShowLogin显示登录组件。
- 登录成功后将用户信息存入vuex中,通过watch监听vuex的登录状态。
- 若已经登录,从后台查询该用户的购物车信息,并通过vuex中的setShoppingCart方法更新购物车信息,从而获取购物车内的商品数量并显示在头部栏中。
- 为我的订单和我的收藏添加路由信息,实现点击跳转对应页面。
<script>
export default {
data() {
return {
activeIndex: "", // 头部导航栏选中的标签
search: "", // 搜索条件
register: false, // 是否显示注册组件
visible: false // 是否退出登录
};
},
created() {
if (localStorage.getItem("user")) {
this.setUser(JSON.parse(localStorage.getItem("user")));
}
},
watch: {
// 获取vuex的登录状态
getUser: function(val) {
if (val === "") {
// 用户没有登录
this.setShoppingCart([]);
} else {
// 用户已经登录,获取该用户的购物车信息
this.$axios.post("/api/user/shoppingCart/getShoppingCart", {
user_id: val.user_id
}).then(res => {
if (res.data.code === "001") {
// 001 为成功, 更新vuex购物车状态
this.setShoppingCart(res.data.shoppingCartData);
} else {
// 提示失败信息
this.notifyError(res.data.msg);
}
}).catch(err => {
return Promise.reject(err);
});
}
}
},
methods: {
...mapActions(["setUser", "setShowLogin", "setShoppingCart"]),
login() {
// 点击登录按钮, 通过更改vuex的showLogin值显示登录组件
this.setShowLogin(true);
},
// 退出登录
logout() {
this.visible = false;
// 清空本地登录信息
localStorage.setItem("user", "");
// 清空vuex登录信息
this.setUser("");
this.notifySucceed("成功退出登录");
},
// 接收注册子组件传过来的数据
isRegister(val) {
this.register = val;
}
},
computed: {
...mapGetters(["getUser", "getNum"]);
}
};
</script>
- 登录组件
<div id="myLogin">
<el-dialog title="登录" width="300px" center :visible.sync="isLogin">
<el-form :model="LoginUser" :rules="rules" status-icon ref="ruleForm" class="demo-ruleForm">
<el-form-item prop="name">
<el-input prefix-icon="el-icon-user-solid" placeholder="请输入账号" v-model="LoginUser.name"></el-input>
</el-form-item>
<el-form-item prop="pass">
<el-input
prefix-icon="el-icon-view"
type="password"
placeholder="请输入密码"
v-model="LoginUser.pass"
></el-input>
</el-form-item>
<el-form-item>
<el-button size="medium" type="primary" @click="Login" style="width:100%;">登录</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
<script>
export default {
name: "MyLogin",
data() {
// 用户名的校验方法
let validateName = (rule, value, callback) => {
if (!value) {
return callback(new Error("请输入用户名"));
}
// 用户名以字母开头,长度在5-16之间,允许字母数字下划线
const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/;
if (userNameRule.test(value)) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {
return callback(new Error("字母开头,长度5-16之间,允许字母数字下划线"));
}
};
// 密码的校验方法
let validatePass = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入密码"));
}
// 密码以字母开头,长度在6-18之间,允许字母数字和下划线
const passwordRule = /^[a-zA-Z]\w{5,17}$/;
if (passwordRule.test(value)) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {
return callback(
new Error("字母开头,长度6-18之间,允许字母数字和下划线")
);
}
};
return {
LoginUser: {
name: "",
pass: ""
},
rules: {
name: [{ validator: validateName, trigger: "blur" }],
pass: [{ validator: validatePass, trigger: "blur" }]
}
};
},
computed: {
// 获取vuex中的showLogin,控制登录组件是否显示
isLogin: {
get() {
return this.$store.getters.getShowLogin;
},
set(val) {
this.$refs["ruleForm"].resetFields();
this.setShowLogin(val);
}
}
},
methods: {
...mapActions(["setUser", "setShowLogin"]),
Login() {
this.$refs["ruleForm"].validate(valid => {
if (valid) {
this.$axios.post("/api/users/login", {
userName: this.LoginUser.name,
password: this.LoginUser.pass
}).then(res => {
// “001”代表登录成功,其他的均为失败
if (res.data.code === "001") {
this.isLogin = false;
let user = JSON.stringify(res.data.user);
localStorage.setItem("user", user);
this.setUser(res.data.user);
this.notifySucceed(res.data.msg);
} else {
this.$refs["ruleForm"].resetFields();
this.notifyError(res.data.msg);
}
}).catch(err => {
return Promise.reject(err);
});
} else {
return false;
}
});
}
}
};
</script>
2. 导航栏及轮播图
导航栏主要功能为首页、全部商品路由跳转,条件搜索、轮播图显示
效果图
效果实现
- 路由分配
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/goods">全部商品</el-menu-item>
<el-menu-item index="/about">关于我们</el-menu-item>
- 搜索功能
<el-input placeholder="请输入搜索内容" v-model="search">
<el-button slot="append" icon="el-icon-search" @click="searchClick"></el-button>
</el-input>
searchClick() {
if (this.search != "") {
// 跳转到全部商品页面,并传递搜索条件
this.$router.push({ path: "/goods", query: { search: this.search } });
this.search = "";
}
}
- 轮播图
<div class="block">
<el-carousel height="460px">
<el-carousel-item v-for="item in carousel" :key="item.carousel_id">
<img style="height:460px;" :src="$target + item.imgPath" :alt="item.describes" />
</el-carousel-item>
</el-carousel>
</div>
// 获取轮播图数据
this.$axios.post("/api/resources/carousel", {})
.then(res => {
this.carousel = res.data.carousel;
}).catch(err => {
return Promise.reject(err);
});
3. 商品列表及切换
商品列表主要显示从后台获取到的商品信息
效果图
效果实现
- 商品列表
<ul>
<li v-for="item in list" :key="item.product_id">
<router-link :to="{ path: '/goods/details', query: {productID:item.product_id} }">
<img :src="$target +item.product_picture" alt />
<h2>{{item.product_name}}</h2>
<h3>{{item.product_title}}</h3>
<p>
<span>{{item.product_selling_price}}元</span>
<span
v-show="item.product_price != item.product_selling_price"
class="del"
>{{item.product_price}}元</span>
</p>
</router-link>
</li>
<li v-show="isMore && list.length>=1" id="more">
<router-link :to="{ path: '/goods', query: {categoryID:categoryID} }">
浏览更多
<i class="el-icon-d-arrow-right"></i>
</router-link>
</li>
</ul>
getPromo(categoryName, val, api) {
api = api != undefined ? api : "/api/product/getPromoProduct";
this.$axios
.post(api, {
categoryName
})
.then(res => {
this[val] = res.data.Product;
})
.catch(err => {
return Promise.reject(err);
});
}
- 类别菜单切换
<ul>
<li
v-for="item in val"
:key="item"
:class="activeClass == item ? 'active':''"
@mouseover="mouseover($event,item)"
>
<router-link to>
<slot :name="item"></slot>
</router-link>
</li>
</ul>
props: ["val"],
name: "MyMenu",
data() {
return {
activeClass: 1
};
},
methods: {
// 通过mouseover事件控制当前显示的商品分类,1为该类别的热门商品
mouseover(e, val) {
this.activeClass = val;
}
},
watch: {
// 向父组件传过去当前要显示的商品分类,从而更新商品列表
activeClass: function(val) {
this.$emit("fromChild", val);
}
}
4. 底部信息栏
底部信息栏仅几个链接及样式设计,不作详细介绍。
效果图
文章来源:https://www.toymoban.com/news/detail-487160.html
往期内容
Vue2实现仿小米商城练手项目前端篇(1-项目介绍)文章来源地址https://www.toymoban.com/news/detail-487160.html
到了这里,关于Vue2实现仿小米商城练手项目前端篇(2-首页实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!