@@ -0,0 +1,148 @@ | |||||
# Store(参考小米商城) | |||||
## 前言 | |||||
2020年寒假尤其特殊,因为新型冠状病毒肺炎疫情,学校至今没有开学。想起上学期利用课余时间学习了`Vue.js`、`Node.js`,一直想做个完整的项目实战一下,但之前在学校并没有那么多的时间。现在恰好有时间,就想着做一个项目巩固之前学到的东西。 | |||||
思来想去,最后决定模仿 [小米商城 ](www.mi.com)做一个电商项目。可能时间隔得有点久了,之前学的东西很多都差不多忘记了,做这个项目基本上都是看着官方的文档一点一点做的。在家里也免不了有各种各样的事情耽误了项目的进度。现在终于差不多做好了,分享出来,新手上路,如有错误,请多多指教。 | |||||
## 说明 | |||||
> 本项目前后端分离,前端参考 [小米商城](www.mi.com) 实现,但与小米官方没有关系,纯属个人瞎搞,若需要购买小米产品请到小米官方商城。 | |||||
> 这是本项目的前端,后端请移步到[store-server](https://github.com/hai-27/store-server) 。 | |||||
> 前端已经部署在阿里云,欢迎访问 [http://101.132.181.9/](http://101.132.181.9/) (没有兼容移动端,请使用电脑访问)。 | |||||
> 后端也已经部署在阿里云,接口地址已经修改为线上地址,本地运行前端也可以正常访问后端。 | |||||
> 本人在读本科大三,今年暑假即将开始实习,后面的时间可能没有那么的自由,但会不定期的更新完善该项目,如有问题请直接在 Issues 中提。 | |||||
> 如果觉得这个项目还不错,您可以点右上角 `Star`支持一下, 谢谢! ^_^ | |||||
## 项目简介 | |||||
本项目前后端分离,前端基于`Vue`+`Vue-router`+`Vuex`+`Element-ui`+`Axios`,参考小米商城实现。后端基于`Node.js(Koa框架)`+`Mysql`实现。 | |||||
前端包含了11个页面:首页、登录、注册、全部商品、商品详情页、关于我们、我的收藏、购物车、订单结算页面、我的订单以及错误处理页面。 | |||||
实现了商品的展示、商品分类查询、关键字搜索商品、商品详细信息展示、登录、注册、用户购物车、订单结算、用户订单、用户收藏列表以及错误处理功能。 | |||||
后端采取了MVC模式,根据前端需要的数据分模块设计了相应的接口、控制层、数据持久层。后端传送地址[store-server](https://github.com/hai-27/store-server) 。 | |||||
## 技术栈 | |||||
- **前端:**`Vue`+`Vue-router`+`Vuex`+`Element-ui`+`Axios` | |||||
- **后端:**`Node.js`、`Koa框架` | |||||
- **数据库:**`Mysql` | |||||
## 功能模块 | |||||
### 登录 | |||||
页面使用了element-ui的`Dialog`实现弹出蒙版对话框的效果,`登录`按钮设置在App.vue根组件,通过`vuex`中的`showLogin`状态控制登录框是否显示。 | |||||
这样设计是为了既可以通过点击页面中的按钮登录,也可以是用户访问需要登录验证的页面或后端返回需要验证登录的提示后自动弹出登录框,减少了页面的跳转,简化用户操作。 | |||||
用户输入的数据往往是不可靠的,所以本项目前后端都对登录信息进行了校验,前端基于element-ui的表单校验方式,自定义了校验规则进行校验。 | |||||
### 注册 | |||||
页面同样使用了element-ui的`Dialog`实现弹出蒙版对话框的效果,`注册`按钮设置在App.vue根组件,通过父子组件传值控制注册框是否显示。 | |||||
用户输入的数据往往是不可靠的,所以本项目前后端同样都对注册信息进行了校验,前端基于element-ui的表单校验方式,自定义了校验规则进行校验。 | |||||
### 首页 | |||||
首页主要是对商品的展示,有轮播图展示推荐的商品,分类别对热门商品进行展示。 | |||||
### 全部商品 | |||||
全部商品页面集成了全部商品展示、商品分类查询,以及根据关键字搜索商品结果展示。 | |||||
### 商品详情页 | |||||
商品详情页主要是对某个商品的详细信息进行展示,用户可以在这里把喜欢的商品加入购物车或收藏列表。 | |||||
### 我的购物车 | |||||
购物车采用vuex实现,页面效果参考了小米商城的购物车。 | |||||
详细实现过程请看:[基于Vuex实现小米商城购物车](https://juejin.im/post/5e660ef9518825490276748a) | |||||
### 订单结算 | |||||
用户在购物车选择了准备购买的商品后,点击“去结算”按钮,会来到该页面。 | |||||
用户在这里选择收货地址,确认订单的相关信息,然后确认购买。 | |||||
### 我的收藏 | |||||
用户在商品的详情页,可以通过点击加入 喜欢 按钮,把喜欢的商品加入到收藏列表。 | |||||
### 我的订单 | |||||
对用户的所有订单进行展示。 | |||||
## 运行项目 | |||||
**注意:** | |||||
- 后端接口地址已经修改为线上的地址,本地运行会直接分为我部署在服务器的后端。 | |||||
- 为了方便测试,数据库数据没有加密,注册时切记**不要使用自己的常用密码**。 | |||||
- 如果需要自己运行后端,请移步到[store-server](https://github.com/hai-27/store-server) clone后端项目,并修改前端的接口地址为您的服务器地址。 | |||||
``` | |||||
1. Clone project | |||||
git clone https://github.com/hai-27/vue-store.git | |||||
2. Project setup | |||||
cd vue-store | |||||
npm install | |||||
3. Compiles and hot-reloads for development | |||||
npm run serve | |||||
4. Compiles and minifies for production | |||||
npm run build | |||||
``` | |||||
## 页面截图 | |||||
**首页** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/home.png) | |||||
**全部商品** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/goods.png) | |||||
**购物车** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/shoppingCart.png) | |||||
**我的收藏** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/collect.png) | |||||
**我的订单** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/order.png) | |||||
**登录** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/login.png) | |||||
**注册** | |||||
![](https://raw.githubusercontent.com/hai-27/vue-store/master/public/screenshots/register.png) | |||||
**作者** [hai-27](https://github.com/hai-27) | |||||
2020年3月8日 |
@@ -0,0 +1,5 @@ | |||||
module.exports = { | |||||
presets: [ | |||||
'@vue/cli-plugin-babel/preset' | |||||
] | |||||
} |
@@ -0,0 +1,30 @@ | |||||
{ | |||||
"name": "store", | |||||
"version": "0.1.0", | |||||
"private": true, | |||||
"scripts": { | |||||
"serve": "vue-cli-service serve", | |||||
"build": "vue-cli-service build", | |||||
"lint": "vue-cli-service lint" | |||||
}, | |||||
"dependencies": { | |||||
"axios": "^0.19.0", | |||||
"core-js": "^3.4.3", | |||||
"element-ui": "^2.13.0", | |||||
"vue": "^2.6.10", | |||||
"vue-markdown": "^2.2.4", | |||||
"vue-router": "^3.1.3", | |||||
"vuex": "^3.1.2" | |||||
}, | |||||
"devDependencies": { | |||||
"@vue/cli-plugin-babel": "^4.1.0", | |||||
"@vue/cli-plugin-eslint": "^4.1.0", | |||||
"@vue/cli-plugin-router": "^4.1.0", | |||||
"@vue/cli-plugin-vuex": "^4.1.0", | |||||
"@vue/cli-service": "^4.1.0", | |||||
"babel-eslint": "^10.0.3", | |||||
"eslint": "^5.16.0", | |||||
"eslint-plugin-vue": "^5.0.0", | |||||
"vue-template-compiler": "^2.6.10" | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<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"> | |||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |||||
<title>store</title> | |||||
</head> | |||||
<body> | |||||
<noscript> | |||||
<strong>We're sorry but store doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |||||
</noscript> | |||||
<div id="app"></div> | |||||
<!-- built files will be auto injected --> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,375 @@ | |||||
<!-- | |||||
* @Description: 项目根组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-04-05 13:14:48 | |||||
--> | |||||
<template> | |||||
<div id="app" name="app"> | |||||
<el-container> | |||||
<!-- 顶部导航栏 --> | |||||
<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" slot="reference">{{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> | |||||
<!-- 顶部导航栏END --> | |||||
<!-- 顶栏容器 --> | |||||
<el-header> | |||||
<el-menu | |||||
:default-active="activeIndex" | |||||
class="el-menu-demo" | |||||
mode="horizontal" | |||||
active-text-color="#409eff" | |||||
router | |||||
> | |||||
<div class="logo"> | |||||
<router-link to="/"> | |||||
<img src="./assets/imgs/logo.png" alt /> | |||||
</router-link> | |||||
</div> | |||||
<el-menu-item index="/">首页</el-menu-item> | |||||
<el-menu-item index="/goods">全部商品</el-menu-item> | |||||
<el-menu-item index="/about">关于我们</el-menu-item> | |||||
<div class="so"> | |||||
<el-input placeholder="请输入搜索内容" v-model="search"> | |||||
<el-button slot="append" icon="el-icon-search" @click="searchClick"></el-button> | |||||
</el-input> | |||||
</div> | |||||
</el-menu> | |||||
</el-header> | |||||
<!-- 顶栏容器END --> | |||||
<!-- 登录模块 --> | |||||
<MyLogin></MyLogin> | |||||
<!-- 注册模块 --> | |||||
<MyRegister :register="register" @fromChild="isRegister"></MyRegister> | |||||
<!-- 主要区域容器 --> | |||||
<el-main> | |||||
<keep-alive> | |||||
<router-view></router-view> | |||||
</keep-alive> | |||||
</el-main> | |||||
<!-- 主要区域容器END --> | |||||
<!-- 底栏容器 --> | |||||
<el-footer> | |||||
<div class="footer"> | |||||
<div class="ng-promise-box"> | |||||
<div class="ng-promise"> | |||||
<p class="text"> | |||||
<a class="icon1" href="javascript:;">7天无理由退换货</a> | |||||
<a class="icon2" href="javascript:;">满99元全场免邮</a> | |||||
<a class="icon3" style="margin-right: 0" href="javascript:;">100%品质保证</a> | |||||
</p> | |||||
</div> | |||||
</div> | |||||
<div class="github"> | |||||
<a href="https://github.com/hai-27/vue-store" target="_blank"> | |||||
<div class="github-but"></div> | |||||
</a> | |||||
</div> | |||||
<div class="mod_help"> | |||||
<p> | |||||
<router-link to="/">首页</router-link> | |||||
<span>|</span> | |||||
<router-link to="/goods">全部商品</router-link> | |||||
<span>|</span> | |||||
<router-link to="/about">关于我们</router-link> | |||||
</p> | |||||
<p class="coty">商城版权所有 © 2012-2021</p> | |||||
</div> | |||||
</div> | |||||
</el-footer> | |||||
<!-- 底栏容器END --> | |||||
</el-container> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapActions } from "vuex"; | |||||
import { mapGetters } from "vuex"; | |||||
export default { | |||||
beforeUpdate() { | |||||
this.activeIndex = this.$route.path; | |||||
}, | |||||
data() { | |||||
return { | |||||
activeIndex: "", // 头部导航栏选中的标签 | |||||
search: "", // 搜索条件 | |||||
register: false, // 是否显示注册组件 | |||||
visible: false // 是否退出登录 | |||||
}; | |||||
}, | |||||
created() { | |||||
// 获取浏览器localStorage,判断用户是否已经登录 | |||||
if (localStorage.getItem("user")) { | |||||
// 如果已经登录,设置vuex登录状态 | |||||
this.setUser(JSON.parse(localStorage.getItem("user"))); | |||||
} | |||||
/* window.setTimeout(() => { | |||||
this.$message({ | |||||
duration: 0, | |||||
showClose: true, | |||||
message: ` | |||||
<p>如果觉得这个项目还不错,</p> | |||||
<p style="padding:10px 0">您可以给项目源代码仓库点Star支持一下,谢谢!</p> | |||||
<p><a href="https://github.com/hai-27/vue-store" target="_blank">Github传送门</a></p>`, | |||||
dangerouslyUseHTMLString: true, | |||||
type: "success" | |||||
}); | |||||
}, 1000 * 60); */ | |||||
}, | |||||
computed: { | |||||
...mapGetters(["getUser", "getNum"]) | |||||
}, | |||||
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; | |||||
}, | |||||
// 点击搜索按钮 | |||||
searchClick() { | |||||
if (this.search != "") { | |||||
// 跳转到全部商品页面,并传递搜索条件 | |||||
this.$router.push({ path: "/goods", query: { search: this.search } }); | |||||
this.search = ""; | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
/* 全局CSS */ | |||||
* { | |||||
padding: 0; | |||||
margin: 0; | |||||
border: 0; | |||||
list-style: none; | |||||
} | |||||
#app .el-header { | |||||
padding: 0; | |||||
} | |||||
#app .el-main { | |||||
min-height: 300px; | |||||
padding: 20px 0; | |||||
} | |||||
#app .el-footer { | |||||
padding: 0; | |||||
} | |||||
a, | |||||
a:hover { | |||||
text-decoration: none; | |||||
} | |||||
/* 全局CSS END */ | |||||
/* 顶部导航栏CSS */ | |||||
.topbar { | |||||
height: 40px; | |||||
background-color: #3d3d3d; | |||||
margin-bottom: 20px; | |||||
} | |||||
.topbar .nav { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.topbar .nav ul { | |||||
float: right; | |||||
} | |||||
.topbar .nav li { | |||||
float: left; | |||||
height: 40px; | |||||
color: #b0b0b0; | |||||
font-size: 14px; | |||||
text-align: center; | |||||
line-height: 40px; | |||||
margin-left: 20px; | |||||
} | |||||
.topbar .nav .sep { | |||||
color: #b0b0b0; | |||||
font-size: 12px; | |||||
margin: 0 5px; | |||||
} | |||||
.topbar .nav li .el-button { | |||||
color: #b0b0b0; | |||||
} | |||||
.topbar .nav .el-button:hover { | |||||
color: #fff; | |||||
} | |||||
.topbar .nav li a { | |||||
color: #b0b0b0; | |||||
} | |||||
.topbar .nav a:hover { | |||||
color: #fff; | |||||
} | |||||
.topbar .nav .shopCart { | |||||
width: 120px; | |||||
background: #424242; | |||||
} | |||||
.topbar .nav .shopCart:hover { | |||||
background: #fff; | |||||
} | |||||
.topbar .nav .shopCart:hover a { | |||||
color: #ff6700; | |||||
} | |||||
.topbar .nav .shopCart-full { | |||||
width: 120px; | |||||
background: #ff6700; | |||||
} | |||||
.topbar .nav .shopCart-full a { | |||||
color: white; | |||||
} | |||||
/* 顶部导航栏CSS END */ | |||||
/* 顶栏容器CSS */ | |||||
.el-header .el-menu { | |||||
max-width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.el-header .logo { | |||||
height: 60px; | |||||
width: 189px; | |||||
float: left; | |||||
margin-right: 100px; | |||||
} | |||||
.el-header .so { | |||||
margin-top: 10px; | |||||
width: 300px; | |||||
float: right; | |||||
} | |||||
/* 顶栏容器CSS END */ | |||||
/* 底栏容器CSS */ | |||||
.footer { | |||||
width: 100%; | |||||
text-align: center; | |||||
background: #2f2f2f; | |||||
padding-bottom: 20px; | |||||
} | |||||
.footer .ng-promise-box { | |||||
border-bottom: 1px solid #3d3d3d; | |||||
line-height: 145px; | |||||
} | |||||
.footer .ng-promise-box { | |||||
margin: 0 auto; | |||||
border-bottom: 1px solid #3d3d3d; | |||||
line-height: 145px; | |||||
} | |||||
.footer .ng-promise-box .ng-promise p a { | |||||
color: #fff; | |||||
font-size: 20px; | |||||
margin-right: 210px; | |||||
padding-left: 44px; | |||||
height: 40px; | |||||
display: inline-block; | |||||
line-height: 40px; | |||||
text-decoration: none; | |||||
background: url("./assets/imgs/us-icon.png") no-repeat left 0; | |||||
} | |||||
.footer .github { | |||||
height: 50px; | |||||
line-height: 50px; | |||||
margin-top: 20px; | |||||
} | |||||
.footer .github .github-but { | |||||
width: 50px; | |||||
height: 50px; | |||||
margin: 0 auto; | |||||
background: url("./assets/imgs/github.png") no-repeat; | |||||
} | |||||
.footer .mod_help { | |||||
text-align: center; | |||||
color: #888888; | |||||
} | |||||
.footer .mod_help p { | |||||
margin: 20px 0 16px 0; | |||||
} | |||||
.footer .mod_help p a { | |||||
color: #888888; | |||||
text-decoration: none; | |||||
} | |||||
.footer .mod_help p a:hover { | |||||
color: #fff; | |||||
} | |||||
.footer .mod_help p span { | |||||
padding: 0 22px; | |||||
} | |||||
/* 底栏容器CSS END */ | |||||
</style> |
@@ -0,0 +1,28 @@ | |||||
/* | |||||
* @Description: 全局变量 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-23 13:40:18 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2021-02-27 04:29:16 | |||||
*/ | |||||
exports.install = function (Vue) { | |||||
// Vue.prototype.$target = "http://101.132.181.9:3000/"; // 线上后端地址 | |||||
Vue.prototype.$target = "http://localhost:3000/"; // 本地后端地址 | |||||
// 封装提示成功的弹出框 | |||||
Vue.prototype.notifySucceed = function (msg) { | |||||
this.$notify({ | |||||
title: "成功", | |||||
message: msg, | |||||
type: "success", | |||||
offset: 100 | |||||
}); | |||||
}; | |||||
// 封装提示失败的弹出框 | |||||
Vue.prototype.notifyError = function (msg) { | |||||
this.$notify.error({ | |||||
title: "错误", | |||||
message: msg, | |||||
offset: 100 | |||||
}); | |||||
}; | |||||
} |
@@ -0,0 +1,985 @@ | |||||
.markdown-body .octicon { | |||||
display: inline-block; | |||||
fill: currentColor; | |||||
vertical-align: text-bottom; | |||||
} | |||||
.markdown-body .anchor { | |||||
float: left; | |||||
line-height: 1; | |||||
margin-left: -20px; | |||||
padding-right: 4px; | |||||
} | |||||
.markdown-body .anchor:focus { | |||||
outline: none; | |||||
} | |||||
.markdown-body h1 .octicon-link, | |||||
.markdown-body h2 .octicon-link, | |||||
.markdown-body h3 .octicon-link, | |||||
.markdown-body h4 .octicon-link, | |||||
.markdown-body h5 .octicon-link, | |||||
.markdown-body h6 .octicon-link { | |||||
color: #1b1f23; | |||||
vertical-align: middle; | |||||
visibility: hidden; | |||||
} | |||||
.markdown-body h1:hover .anchor, | |||||
.markdown-body h2:hover .anchor, | |||||
.markdown-body h3:hover .anchor, | |||||
.markdown-body h4:hover .anchor, | |||||
.markdown-body h5:hover .anchor, | |||||
.markdown-body h6:hover .anchor { | |||||
text-decoration: none; | |||||
} | |||||
.markdown-body h1:hover .anchor .octicon-link, | |||||
.markdown-body h2:hover .anchor .octicon-link, | |||||
.markdown-body h3:hover .anchor .octicon-link, | |||||
.markdown-body h4:hover .anchor .octicon-link, | |||||
.markdown-body h5:hover .anchor .octicon-link, | |||||
.markdown-body h6:hover .anchor .octicon-link { | |||||
visibility: visible; | |||||
} | |||||
.markdown-body h1:hover .anchor .octicon-link:before, | |||||
.markdown-body h2:hover .anchor .octicon-link:before, | |||||
.markdown-body h3:hover .anchor .octicon-link:before, | |||||
.markdown-body h4:hover .anchor .octicon-link:before, | |||||
.markdown-body h5:hover .anchor .octicon-link:before, | |||||
.markdown-body h6:hover .anchor .octicon-link:before { | |||||
width: 16px; | |||||
height: 16px; | |||||
content: ' '; | |||||
display: inline-block; | |||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E"); | |||||
}.markdown-body { | |||||
-ms-text-size-adjust: 100%; | |||||
-webkit-text-size-adjust: 100%; | |||||
line-height: 1.5; | |||||
color: #24292e; | |||||
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; | |||||
font-size: 16px; | |||||
line-height: 1.5; | |||||
word-wrap: break-word; | |||||
} | |||||
.markdown-body details { | |||||
display: block; | |||||
} | |||||
.markdown-body summary { | |||||
display: list-item; | |||||
} | |||||
.markdown-body a { | |||||
background-color: initial; | |||||
} | |||||
.markdown-body a:active, | |||||
.markdown-body a:hover { | |||||
outline-width: 0; | |||||
} | |||||
.markdown-body strong { | |||||
font-weight: inherit; | |||||
font-weight: bolder; | |||||
} | |||||
.markdown-body h1 { | |||||
font-size: 2em; | |||||
margin: .67em 0; | |||||
} | |||||
.markdown-body img { | |||||
border-style: none; | |||||
} | |||||
.markdown-body code, | |||||
.markdown-body kbd, | |||||
.markdown-body pre { | |||||
font-family: monospace,monospace; | |||||
font-size: 1em; | |||||
} | |||||
.markdown-body hr { | |||||
box-sizing: initial; | |||||
height: 0; | |||||
overflow: visible; | |||||
} | |||||
.markdown-body input { | |||||
font: inherit; | |||||
margin: 0; | |||||
} | |||||
.markdown-body input { | |||||
overflow: visible; | |||||
} | |||||
.markdown-body [type=checkbox] { | |||||
box-sizing: border-box; | |||||
padding: 0; | |||||
} | |||||
.markdown-body * { | |||||
box-sizing: border-box; | |||||
} | |||||
.markdown-body input { | |||||
font-family: inherit; | |||||
font-size: inherit; | |||||
line-height: inherit; | |||||
} | |||||
.markdown-body a { | |||||
color: #0366d6; | |||||
text-decoration: none; | |||||
} | |||||
.markdown-body a:hover { | |||||
text-decoration: underline; | |||||
} | |||||
.markdown-body strong { | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body hr { | |||||
height: 0; | |||||
margin: 15px 0; | |||||
overflow: hidden; | |||||
background: transparent; | |||||
border: 0; | |||||
border-bottom: 1px solid #dfe2e5; | |||||
} | |||||
.markdown-body hr:after, | |||||
.markdown-body hr:before { | |||||
display: table; | |||||
content: ""; | |||||
} | |||||
.markdown-body hr:after { | |||||
clear: both; | |||||
} | |||||
.markdown-body table { | |||||
border-spacing: 0; | |||||
border-collapse: collapse; | |||||
} | |||||
.markdown-body td, | |||||
.markdown-body th { | |||||
padding: 0; | |||||
} | |||||
.markdown-body details summary { | |||||
cursor: pointer; | |||||
} | |||||
.markdown-body kbd { | |||||
display: inline-block; | |||||
padding: 3px 5px; | |||||
font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
line-height: 10px; | |||||
color: #444d56; | |||||
vertical-align: middle; | |||||
background-color: #fafbfc; | |||||
border: 1px solid #d1d5da; | |||||
border-radius: 3px; | |||||
box-shadow: inset 0 -1px 0 #d1d5da; | |||||
} | |||||
.markdown-body h1, | |||||
.markdown-body h2, | |||||
.markdown-body h3, | |||||
.markdown-body h4, | |||||
.markdown-body h5, | |||||
.markdown-body h6 { | |||||
margin-top: 0; | |||||
margin-bottom: 0; | |||||
} | |||||
.markdown-body h1 { | |||||
font-size: 32px; | |||||
} | |||||
.markdown-body h1, | |||||
.markdown-body h2 { | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body h2 { | |||||
font-size: 24px; | |||||
} | |||||
.markdown-body h3 { | |||||
font-size: 20px; | |||||
} | |||||
.markdown-body h3, | |||||
.markdown-body h4 { | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body h4 { | |||||
font-size: 16px; | |||||
} | |||||
.markdown-body h5 { | |||||
font-size: 14px; | |||||
} | |||||
.markdown-body h5, | |||||
.markdown-body h6 { | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body h6 { | |||||
font-size: 12px; | |||||
} | |||||
.markdown-body p { | |||||
margin-top: 0; | |||||
margin-bottom: 10px; | |||||
} | |||||
.markdown-body blockquote { | |||||
margin: 0; | |||||
} | |||||
.markdown-body ol, | |||||
.markdown-body ul { | |||||
padding-left: 0; | |||||
margin-top: 0; | |||||
margin-bottom: 0; | |||||
} | |||||
.markdown-body ol ol, | |||||
.markdown-body ul ol { | |||||
list-style-type: lower-roman; | |||||
} | |||||
.markdown-body ol ol ol, | |||||
.markdown-body ol ul ol, | |||||
.markdown-body ul ol ol, | |||||
.markdown-body ul ul ol { | |||||
list-style-type: lower-alpha; | |||||
} | |||||
.markdown-body dd { | |||||
margin-left: 0; | |||||
} | |||||
.markdown-body code, | |||||
.markdown-body pre { | |||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
font-size: 12px; | |||||
} | |||||
.markdown-body pre { | |||||
margin-top: 0; | |||||
margin-bottom: 0; | |||||
} | |||||
.markdown-body input::-webkit-inner-spin-button, | |||||
.markdown-body input::-webkit-outer-spin-button { | |||||
margin: 0; | |||||
-webkit-appearance: none; | |||||
appearance: none; | |||||
} | |||||
.markdown-body :checked+.radio-label { | |||||
position: relative; | |||||
z-index: 1; | |||||
border-color: #0366d6; | |||||
} | |||||
.markdown-body .border { | |||||
border: 1px solid #e1e4e8!important; | |||||
} | |||||
.markdown-body .border-0 { | |||||
border: 0!important; | |||||
} | |||||
.markdown-body .border-bottom { | |||||
border-bottom: 1px solid #e1e4e8!important; | |||||
} | |||||
.markdown-body .rounded-1 { | |||||
border-radius: 3px!important; | |||||
} | |||||
.markdown-body .bg-white { | |||||
background-color: #fff!important; | |||||
} | |||||
.markdown-body .bg-gray-light { | |||||
background-color: #fafbfc!important; | |||||
} | |||||
.markdown-body .text-gray-light { | |||||
color: #6a737d!important; | |||||
} | |||||
.markdown-body .mb-0 { | |||||
margin-bottom: 0!important; | |||||
} | |||||
.markdown-body .my-2 { | |||||
margin-top: 8px!important; | |||||
margin-bottom: 8px!important; | |||||
} | |||||
.markdown-body .pl-0 { | |||||
padding-left: 0!important; | |||||
} | |||||
.markdown-body .py-0 { | |||||
padding-top: 0!important; | |||||
padding-bottom: 0!important; | |||||
} | |||||
.markdown-body .pl-1 { | |||||
padding-left: 4px!important; | |||||
} | |||||
.markdown-body .pl-2 { | |||||
padding-left: 8px!important; | |||||
} | |||||
.markdown-body .py-2 { | |||||
padding-top: 8px!important; | |||||
padding-bottom: 8px!important; | |||||
} | |||||
.markdown-body .pl-3, | |||||
.markdown-body .px-3 { | |||||
padding-left: 16px!important; | |||||
} | |||||
.markdown-body .px-3 { | |||||
padding-right: 16px!important; | |||||
} | |||||
.markdown-body .pl-4 { | |||||
padding-left: 24px!important; | |||||
} | |||||
.markdown-body .pl-5 { | |||||
padding-left: 32px!important; | |||||
} | |||||
.markdown-body .pl-6 { | |||||
padding-left: 40px!important; | |||||
} | |||||
.markdown-body .f6 { | |||||
font-size: 12px!important; | |||||
} | |||||
.markdown-body .lh-condensed { | |||||
line-height: 1.25!important; | |||||
} | |||||
.markdown-body .text-bold { | |||||
font-weight: 600!important; | |||||
} | |||||
.markdown-body .pl-c { | |||||
color: #6a737d; | |||||
} | |||||
.markdown-body .pl-c1, | |||||
.markdown-body .pl-s .pl-v { | |||||
color: #005cc5; | |||||
} | |||||
.markdown-body .pl-e, | |||||
.markdown-body .pl-en { | |||||
color: #6f42c1; | |||||
} | |||||
.markdown-body .pl-s .pl-s1, | |||||
.markdown-body .pl-smi { | |||||
color: #24292e; | |||||
} | |||||
.markdown-body .pl-ent { | |||||
color: #22863a; | |||||
} | |||||
.markdown-body .pl-k { | |||||
color: #d73a49; | |||||
} | |||||
.markdown-body .pl-pds, | |||||
.markdown-body .pl-s, | |||||
.markdown-body .pl-s .pl-pse .pl-s1, | |||||
.markdown-body .pl-sr, | |||||
.markdown-body .pl-sr .pl-cce, | |||||
.markdown-body .pl-sr .pl-sra, | |||||
.markdown-body .pl-sr .pl-sre { | |||||
color: #032f62; | |||||
} | |||||
.markdown-body .pl-smw, | |||||
.markdown-body .pl-v { | |||||
color: #e36209; | |||||
} | |||||
.markdown-body .pl-bu { | |||||
color: #b31d28; | |||||
} | |||||
.markdown-body .pl-ii { | |||||
color: #fafbfc; | |||||
background-color: #b31d28; | |||||
} | |||||
.markdown-body .pl-c2 { | |||||
color: #fafbfc; | |||||
background-color: #d73a49; | |||||
} | |||||
.markdown-body .pl-c2:before { | |||||
content: "^M"; | |||||
} | |||||
.markdown-body .pl-sr .pl-cce { | |||||
font-weight: 700; | |||||
color: #22863a; | |||||
} | |||||
.markdown-body .pl-ml { | |||||
color: #735c0f; | |||||
} | |||||
.markdown-body .pl-mh, | |||||
.markdown-body .pl-mh .pl-en, | |||||
.markdown-body .pl-ms { | |||||
font-weight: 700; | |||||
color: #005cc5; | |||||
} | |||||
.markdown-body .pl-mi { | |||||
font-style: italic; | |||||
color: #24292e; | |||||
} | |||||
.markdown-body .pl-mb { | |||||
font-weight: 700; | |||||
color: #24292e; | |||||
} | |||||
.markdown-body .pl-md { | |||||
color: #b31d28; | |||||
background-color: #ffeef0; | |||||
} | |||||
.markdown-body .pl-mi1 { | |||||
color: #22863a; | |||||
background-color: #f0fff4; | |||||
} | |||||
.markdown-body .pl-mc { | |||||
color: #e36209; | |||||
background-color: #ffebda; | |||||
} | |||||
.markdown-body .pl-mi2 { | |||||
color: #f6f8fa; | |||||
background-color: #005cc5; | |||||
} | |||||
.markdown-body .pl-mdr { | |||||
font-weight: 700; | |||||
color: #6f42c1; | |||||
} | |||||
.markdown-body .pl-ba { | |||||
color: #586069; | |||||
} | |||||
.markdown-body .pl-sg { | |||||
color: #959da5; | |||||
} | |||||
.markdown-body .pl-corl { | |||||
text-decoration: underline; | |||||
color: #032f62; | |||||
} | |||||
.markdown-body .mb-0 { | |||||
margin-bottom: 0!important; | |||||
} | |||||
.markdown-body .my-2 { | |||||
margin-bottom: 8px!important; | |||||
} | |||||
.markdown-body .my-2 { | |||||
margin-top: 8px!important; | |||||
} | |||||
.markdown-body .pl-0 { | |||||
padding-left: 0!important; | |||||
} | |||||
.markdown-body .py-0 { | |||||
padding-top: 0!important; | |||||
padding-bottom: 0!important; | |||||
} | |||||
.markdown-body .pl-1 { | |||||
padding-left: 4px!important; | |||||
} | |||||
.markdown-body .pl-2 { | |||||
padding-left: 8px!important; | |||||
} | |||||
.markdown-body .py-2 { | |||||
padding-top: 8px!important; | |||||
padding-bottom: 8px!important; | |||||
} | |||||
.markdown-body .pl-3 { | |||||
padding-left: 16px!important; | |||||
} | |||||
.markdown-body .pl-4 { | |||||
padding-left: 24px!important; | |||||
} | |||||
.markdown-body .pl-5 { | |||||
padding-left: 32px!important; | |||||
} | |||||
.markdown-body .pl-6 { | |||||
padding-left: 40px!important; | |||||
} | |||||
.markdown-body .pl-7 { | |||||
padding-left: 48px!important; | |||||
} | |||||
.markdown-body .pl-8 { | |||||
padding-left: 64px!important; | |||||
} | |||||
.markdown-body .pl-9 { | |||||
padding-left: 80px!important; | |||||
} | |||||
.markdown-body .pl-10 { | |||||
padding-left: 96px!important; | |||||
} | |||||
.markdown-body .pl-11 { | |||||
padding-left: 112px!important; | |||||
} | |||||
.markdown-body .pl-12 { | |||||
padding-left: 128px!important; | |||||
} | |||||
.markdown-body hr { | |||||
border-bottom-color: #eee; | |||||
} | |||||
.markdown-body kbd { | |||||
display: inline-block; | |||||
padding: 3px 5px; | |||||
font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
line-height: 10px; | |||||
color: #444d56; | |||||
vertical-align: middle; | |||||
background-color: #fafbfc; | |||||
border: 1px solid #d1d5da; | |||||
border-radius: 3px; | |||||
box-shadow: inset 0 -1px 0 #d1d5da; | |||||
} | |||||
.markdown-body:after, | |||||
.markdown-body:before { | |||||
display: table; | |||||
content: ""; | |||||
} | |||||
.markdown-body:after { | |||||
clear: both; | |||||
} | |||||
.markdown-body>:first-child { | |||||
margin-top: 0!important; | |||||
} | |||||
.markdown-body>:last-child { | |||||
margin-bottom: 0!important; | |||||
} | |||||
.markdown-body a:not([href]) { | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
.markdown-body blockquote, | |||||
.markdown-body details, | |||||
.markdown-body dl, | |||||
.markdown-body ol, | |||||
.markdown-body p, | |||||
.markdown-body pre, | |||||
.markdown-body table, | |||||
.markdown-body ul { | |||||
margin-top: 0; | |||||
margin-bottom: 16px; | |||||
} | |||||
.markdown-body hr { | |||||
height: .25em; | |||||
padding: 0; | |||||
margin: 24px 0; | |||||
background-color: #e1e4e8; | |||||
border: 0; | |||||
} | |||||
.markdown-body blockquote { | |||||
padding: 0 1em; | |||||
color: #6a737d; | |||||
border-left: .25em solid #dfe2e5; | |||||
} | |||||
.markdown-body blockquote>:first-child { | |||||
margin-top: 0; | |||||
} | |||||
.markdown-body blockquote>:last-child { | |||||
margin-bottom: 0; | |||||
} | |||||
.markdown-body h1, | |||||
.markdown-body h2, | |||||
.markdown-body h3, | |||||
.markdown-body h4, | |||||
.markdown-body h5, | |||||
.markdown-body h6 { | |||||
margin-top: 24px; | |||||
margin-bottom: 16px; | |||||
font-weight: 600; | |||||
line-height: 1.25; | |||||
} | |||||
.markdown-body h1 { | |||||
font-size: 2em; | |||||
} | |||||
.markdown-body h1, | |||||
.markdown-body h2 { | |||||
padding-bottom: .3em; | |||||
border-bottom: 1px solid #eaecef; | |||||
} | |||||
.markdown-body h2 { | |||||
font-size: 1.5em; | |||||
} | |||||
.markdown-body h3 { | |||||
font-size: 1.25em; | |||||
} | |||||
.markdown-body h4 { | |||||
font-size: 1em; | |||||
} | |||||
.markdown-body h5 { | |||||
font-size: .875em; | |||||
} | |||||
.markdown-body h6 { | |||||
font-size: .85em; | |||||
color: #6a737d; | |||||
} | |||||
.markdown-body ol, | |||||
.markdown-body ul { | |||||
padding-left: 2em; | |||||
} | |||||
.markdown-body ol ol, | |||||
.markdown-body ol ul, | |||||
.markdown-body ul ol, | |||||
.markdown-body ul ul { | |||||
margin-top: 0; | |||||
margin-bottom: 0; | |||||
} | |||||
.markdown-body li { | |||||
word-wrap: break-all; | |||||
} | |||||
.markdown-body li>p { | |||||
margin-top: 16px; | |||||
} | |||||
.markdown-body li+li { | |||||
margin-top: .25em; | |||||
} | |||||
.markdown-body dl { | |||||
padding: 0; | |||||
} | |||||
.markdown-body dl dt { | |||||
padding: 0; | |||||
margin-top: 16px; | |||||
font-size: 1em; | |||||
font-style: italic; | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body dl dd { | |||||
padding: 0 16px; | |||||
margin-bottom: 16px; | |||||
} | |||||
.markdown-body table { | |||||
display: block; | |||||
width: 100%; | |||||
overflow: auto; | |||||
} | |||||
.markdown-body table th { | |||||
font-weight: 600; | |||||
} | |||||
.markdown-body table td, | |||||
.markdown-body table th { | |||||
padding: 6px 13px; | |||||
border: 1px solid #dfe2e5; | |||||
} | |||||
.markdown-body table tr { | |||||
background-color: #fff; | |||||
border-top: 1px solid #c6cbd1; | |||||
} | |||||
.markdown-body table tr:nth-child(2n) { | |||||
background-color: #f6f8fa; | |||||
} | |||||
.markdown-body img { | |||||
max-width: 100%; | |||||
box-sizing: initial; | |||||
background-color: #fff; | |||||
} | |||||
.markdown-body img[align=right] { | |||||
padding-left: 20px; | |||||
} | |||||
.markdown-body img[align=left] { | |||||
padding-right: 20px; | |||||
} | |||||
.markdown-body code { | |||||
padding: .2em .4em; | |||||
margin: 0; | |||||
font-size: 85%; | |||||
background-color: rgba(27,31,35,.05); | |||||
border-radius: 3px; | |||||
} | |||||
.markdown-body pre { | |||||
word-wrap: normal; | |||||
} | |||||
.markdown-body pre>code { | |||||
padding: 0; | |||||
margin: 0; | |||||
font-size: 100%; | |||||
word-break: normal; | |||||
white-space: pre; | |||||
background: transparent; | |||||
border: 0; | |||||
} | |||||
.markdown-body .highlight { | |||||
margin-bottom: 16px; | |||||
} | |||||
.markdown-body .highlight pre { | |||||
margin-bottom: 0; | |||||
word-break: normal; | |||||
} | |||||
.markdown-body .highlight pre, | |||||
.markdown-body pre { | |||||
padding: 16px; | |||||
overflow: auto; | |||||
font-size: 85%; | |||||
line-height: 1.45; | |||||
background-color: #f6f8fa; | |||||
border-radius: 3px; | |||||
} | |||||
.markdown-body pre code { | |||||
display: inline; | |||||
max-width: auto; | |||||
padding: 0; | |||||
margin: 0; | |||||
overflow: visible; | |||||
line-height: inherit; | |||||
word-wrap: normal; | |||||
background-color: initial; | |||||
border: 0; | |||||
} | |||||
.markdown-body .commit-tease-sha { | |||||
display: inline-block; | |||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
font-size: 90%; | |||||
color: #444d56; | |||||
} | |||||
.markdown-body .full-commit .btn-outline:not(:disabled):hover { | |||||
color: #005cc5; | |||||
border-color: #005cc5; | |||||
} | |||||
.markdown-body .blob-wrapper { | |||||
overflow-x: auto; | |||||
overflow-y: hidden; | |||||
} | |||||
.markdown-body .blob-wrapper-embedded { | |||||
max-height: 240px; | |||||
overflow-y: auto; | |||||
} | |||||
.markdown-body .blob-num { | |||||
width: 1%; | |||||
min-width: 50px; | |||||
padding-right: 10px; | |||||
padding-left: 10px; | |||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
font-size: 12px; | |||||
line-height: 20px; | |||||
color: rgba(27,31,35,.3); | |||||
text-align: right; | |||||
white-space: nowrap; | |||||
vertical-align: top; | |||||
cursor: pointer; | |||||
-webkit-user-select: none; | |||||
-moz-user-select: none; | |||||
-ms-user-select: none; | |||||
user-select: none; | |||||
} | |||||
.markdown-body .blob-num:hover { | |||||
color: rgba(27,31,35,.6); | |||||
} | |||||
.markdown-body .blob-num:before { | |||||
content: attr(data-line-number); | |||||
} | |||||
.markdown-body .blob-code { | |||||
position: relative; | |||||
padding-right: 10px; | |||||
padding-left: 10px; | |||||
line-height: 20px; | |||||
vertical-align: top; | |||||
} | |||||
.markdown-body .blob-code-inner { | |||||
overflow: visible; | |||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; | |||||
font-size: 12px; | |||||
color: #24292e; | |||||
word-wrap: normal; | |||||
white-space: pre; | |||||
} | |||||
.markdown-body .pl-token.active, | |||||
.markdown-body .pl-token:hover { | |||||
cursor: pointer; | |||||
background: #ffea7f; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="1"] { | |||||
-moz-tab-size: 1; | |||||
tab-size: 1; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="2"] { | |||||
-moz-tab-size: 2; | |||||
tab-size: 2; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="3"] { | |||||
-moz-tab-size: 3; | |||||
tab-size: 3; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="4"] { | |||||
-moz-tab-size: 4; | |||||
tab-size: 4; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="5"] { | |||||
-moz-tab-size: 5; | |||||
tab-size: 5; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="6"] { | |||||
-moz-tab-size: 6; | |||||
tab-size: 6; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="7"] { | |||||
-moz-tab-size: 7; | |||||
tab-size: 7; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="8"] { | |||||
-moz-tab-size: 8; | |||||
tab-size: 8; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="9"] { | |||||
-moz-tab-size: 9; | |||||
tab-size: 9; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="10"] { | |||||
-moz-tab-size: 10; | |||||
tab-size: 10; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="11"] { | |||||
-moz-tab-size: 11; | |||||
tab-size: 11; | |||||
} | |||||
.markdown-body .tab-size[data-tab-size="12"] { | |||||
-moz-tab-size: 12; | |||||
tab-size: 12; | |||||
} | |||||
.markdown-body .task-list-item { | |||||
list-style-type: none; | |||||
} | |||||
.markdown-body .task-list-item+.task-list-item { | |||||
margin-top: 3px; | |||||
} | |||||
.markdown-body .task-list-item input { | |||||
margin: 0 .2em .25em -1.6em; | |||||
vertical-align: middle; | |||||
} |
@@ -0,0 +1,102 @@ | |||||
/* | |||||
* @Description: 首页css样式 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 00:46:58 | |||||
*/ | |||||
.main-box { | |||||
background-color: #f5f5f5; | |||||
padding-bottom: 20px; | |||||
} | |||||
.main { | |||||
margin: 0 auto; | |||||
max-width: 1225px; | |||||
} | |||||
/* 轮播图CSS */ | |||||
.block { | |||||
margin: 0 auto; | |||||
max-width: 1225px; | |||||
} | |||||
.el-carousel__item:nth-child(2n) { | |||||
background-color: #99a9bf; | |||||
} | |||||
.el-carousel__item:nth-child(2n + 1) { | |||||
background-color: #d3dce6; | |||||
} | |||||
/* 轮播图CSS END */ | |||||
.box-hd { | |||||
height: 58px; | |||||
margin: 20px 0 0 0; | |||||
} | |||||
.box-hd .title { | |||||
float: left; | |||||
font-size: 22px; | |||||
font-weight: 200; | |||||
line-height: 58px; | |||||
color: #333; | |||||
} | |||||
.box-hd .more { | |||||
float: right; | |||||
} | |||||
.box-hd .more a { | |||||
font-size: 16px; | |||||
line-height: 58px; | |||||
color: #424242; | |||||
} | |||||
.box-hd .more a:hover { | |||||
color: #ff6700; | |||||
} | |||||
.box-bd { | |||||
height: 615px; | |||||
} | |||||
.box-bd .promo-list { | |||||
float: left; | |||||
height: 615px; | |||||
width: 234px; | |||||
} | |||||
.box-bd .promo-list li { | |||||
z-index: 1; | |||||
width: 234px; | |||||
height: 300px; | |||||
margin-bottom: 14.5px; | |||||
-webkit-transition: all 0.2s linear; | |||||
transition: all 0.2s linear; | |||||
} | |||||
.box-bd .promo-list li:hover { | |||||
z-index: 2; | |||||
-webkit-box-shadow: 0 15px 30px rgba(0, 0, 0, .1); | |||||
box-shadow: 0 15px 30px rgba(0, 0, 0, .1); | |||||
-webkit-transform: translate3d(0, -2px, 0); | |||||
transform: translate3d(0, -2px, 0); | |||||
} | |||||
.box-bd .promo-list li img { | |||||
width: 234px; | |||||
height: 300px; | |||||
} | |||||
.box-bd .promo-list img { | |||||
width: 234px; | |||||
} | |||||
.box-bd .list { | |||||
float: left; | |||||
height: 615px; | |||||
width: 991px; | |||||
} |
@@ -0,0 +1,24 @@ | |||||
<!-- | |||||
* @Description: | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-27 13:57:14 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 14:11:31 | |||||
--> | |||||
<template> | |||||
<div class="error"> | |||||
<div class="main"></div> | |||||
</div> | |||||
</template> | |||||
<style> | |||||
.error { | |||||
height: 500px; | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.error .main { | |||||
margin: 0 362.5px; | |||||
height: 500px; | |||||
background: url(../assets/imgs/error.png) no-repeat; | |||||
} | |||||
</style> |
@@ -0,0 +1,179 @@ | |||||
<!-- | |||||
* @Description: 列表组件,用于首页、全部商品页面的商品列表 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-04-05 13:22:22 | |||||
--> | |||||
<template> | |||||
<div id="myList" class="myList"> | |||||
<ul> | |||||
<li v-for="item in list" :key="item.product_id"> | |||||
<el-popover placement="top"> | |||||
<p>确定删除吗?</p> | |||||
<div style="text-align: right; margin: 10px 0 0"> | |||||
<el-button type="primary" size="mini" @click="deleteCollect(item.product_id)">确定</el-button> | |||||
</div> | |||||
<i class="el-icon-close delete" slot="reference" v-show="isDelete"></i> | |||||
</el-popover> | |||||
<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> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: "MyList", | |||||
// list为父组件传过来的商品列表 | |||||
// isMore为是否显示“浏览更多” | |||||
props: ["list", "isMore", "isDelete"], | |||||
data() { | |||||
return {}; | |||||
}, | |||||
computed: { | |||||
// 通过list获取当前显示的商品的分类ID,用于“浏览更多”链接的参数 | |||||
categoryID: function() { | |||||
let categoryID = []; | |||||
if (this.list != "") { | |||||
for (let i = 0; i < this.list.length; i++) { | |||||
const id = this.list[i].category_id; | |||||
if (!categoryID.includes(id)) { | |||||
categoryID.push(id); | |||||
} | |||||
} | |||||
} | |||||
return categoryID; | |||||
} | |||||
}, | |||||
methods: { | |||||
deleteCollect(product_id) { | |||||
this.$axios | |||||
.post("/api/user/collect/deleteCollect", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
product_id: product_id | |||||
}) | |||||
.then(res => { | |||||
switch (res.data.code) { | |||||
case "001": | |||||
// 删除成功 | |||||
// 删除删除列表中的该商品信息 | |||||
for (let i = 0; i < this.list.length; i++) { | |||||
const temp = this.list[i]; | |||||
if (temp.product_id == product_id) { | |||||
this.list.splice(i, 1); | |||||
} | |||||
} | |||||
// 提示删除成功信息 | |||||
this.notifySucceed(res.data.msg); | |||||
break; | |||||
default: | |||||
// 提示删除失败信息 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.myList ul li { | |||||
z-index: 1; | |||||
float: left; | |||||
width: 234px; | |||||
height: 280px; | |||||
padding: 10px 0; | |||||
margin: 0 0 14.5px 13.7px; | |||||
background-color: white; | |||||
-webkit-transition: all 0.2s linear; | |||||
transition: all 0.2s linear; | |||||
position: relative; | |||||
} | |||||
.myList ul li:hover { | |||||
z-index: 2; | |||||
-webkit-box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||||
-webkit-transform: translate3d(0, -2px, 0); | |||||
transform: translate3d(0, -2px, 0); | |||||
} | |||||
.myList ul li img { | |||||
display: block; | |||||
width: 160px; | |||||
height: 160px; | |||||
background: url(../assets/imgs/placeholder.png) no-repeat 50%; | |||||
margin: 0 auto; | |||||
} | |||||
.myList ul li h2 { | |||||
margin: 25px 10px 0; | |||||
font-size: 14px; | |||||
font-weight: 400; | |||||
color: #333; | |||||
text-align: center; | |||||
text-overflow: ellipsis; | |||||
white-space: nowrap; | |||||
overflow: hidden; | |||||
} | |||||
.myList ul li h3 { | |||||
margin: 5px 10px; | |||||
height: 18px; | |||||
font-size: 12px; | |||||
font-weight: 400; | |||||
color: #b0b0b0; | |||||
text-align: center; | |||||
text-overflow: ellipsis; | |||||
white-space: nowrap; | |||||
overflow: hidden; | |||||
} | |||||
.myList ul li p { | |||||
margin: 10px 10px 10px; | |||||
text-align: center; | |||||
color: #ff6700; | |||||
} | |||||
.myList ul li p .del { | |||||
margin-left: 0.5em; | |||||
color: #b0b0b0; | |||||
text-decoration: line-through; | |||||
} | |||||
.myList #more { | |||||
text-align: center; | |||||
line-height: 280px; | |||||
} | |||||
.myList #more a { | |||||
font-size: 18px; | |||||
color: #333; | |||||
} | |||||
.myList #more a:hover { | |||||
color: #ff6700; | |||||
} | |||||
.myList ul li .delete { | |||||
position: absolute; | |||||
top: 10px; | |||||
right: 10px; | |||||
display: none; | |||||
} | |||||
.myList ul li:hover .delete { | |||||
display: block | |||||
} | |||||
.myList ul li .delete:hover { | |||||
color: #ff6700; | |||||
} | |||||
</style> |
@@ -0,0 +1,133 @@ | |||||
<!-- | |||||
* @Description: 登录组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-19 20:55:17 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-01 15:34:08 | |||||
--> | |||||
<template> | |||||
<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> | |||||
</template> | |||||
<script> | |||||
import { mapActions } from "vuex"; | |||||
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: "" | |||||
}, | |||||
// 用户信息校验规则,validator(校验方法),trigger(触发方式),blur为在组件 Input 失去焦点时触发 | |||||
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() { | |||||
// 通过element自定义表单校验规则,校验用户输入的用户信息 | |||||
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); | |||||
// 登录信息存到vuex | |||||
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> | |||||
<style> | |||||
</style> |
@@ -0,0 +1,45 @@ | |||||
<!-- | |||||
* @Description: 渲染markdown文件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-03-12 17:30:46 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-12 18:23:58 | |||||
--> | |||||
<template> | |||||
<div id="my-markdown" class="markdown-body"> | |||||
<vue-markdown :source="md"></vue-markdown> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import VueMarkdown from "vue-markdown"; | |||||
export default { | |||||
name: "MyMarkdown", | |||||
components: { | |||||
VueMarkdown | |||||
}, | |||||
data() { | |||||
return { | |||||
md: "" | |||||
}; | |||||
}, | |||||
created() { | |||||
// 从后端请求README.md | |||||
this.$axios | |||||
.get("/api/public/docs/README.md", {}) | |||||
.then(res => { | |||||
this.md = res.data; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
@import "../assets/css/github-markdown.css"; | |||||
.markdown-body { | |||||
box-sizing: border-box; | |||||
margin: 0 auto; | |||||
padding: 0 40px; | |||||
} | |||||
</style> |
@@ -0,0 +1,62 @@ | |||||
<!-- | |||||
* @Description: 菜单组件,用于首页商品展示模块的右上角菜单 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-26 22:52:35 | |||||
--> | |||||
<template> | |||||
<div class="myMenu" id="myMenu"> | |||||
<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> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
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); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
#myMenu li { | |||||
float: left; | |||||
margin-left: 30px; | |||||
} | |||||
#myMenu a:hover { | |||||
color: #ff6700; | |||||
border-bottom: 2px solid #ff6700; | |||||
} | |||||
#myMenu .active a { | |||||
color: #ff6700; | |||||
border-bottom: 2px solid #ff6700; | |||||
} | |||||
</style> |
@@ -0,0 +1,176 @@ | |||||
<!-- | |||||
* @Description: 用户注册组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-19 22:20:35 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-01 15:34:34 | |||||
--> | |||||
<template> | |||||
<div id="register"> | |||||
<el-dialog title="注册" width="300px" center :visible.sync="isRegister"> | |||||
<el-form | |||||
:model="RegisterUser" | |||||
: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="RegisterUser.name" | |||||
></el-input> | |||||
</el-form-item> | |||||
<el-form-item prop="pass"> | |||||
<el-input | |||||
prefix-icon="el-icon-view" | |||||
type="password" | |||||
placeholder="请输入密码" | |||||
v-model="RegisterUser.pass" | |||||
></el-input> | |||||
</el-form-item> | |||||
<el-form-item prop="confirmPass"> | |||||
<el-input | |||||
prefix-icon="el-icon-view" | |||||
type="password" | |||||
placeholder="请再次输入密码" | |||||
v-model="RegisterUser.confirmPass" | |||||
></el-input> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button size="medium" type="primary" @click="Register" style="width:100%;">注册</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: "MyRegister", | |||||
props: ["register"], | |||||
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.$axios | |||||
.post("/api/users/findUserName", { | |||||
userName: this.RegisterUser.name | |||||
}) | |||||
.then(res => { | |||||
// “001”代表用户名不存在,可以注册 | |||||
if (res.data.code == "001") { | |||||
this.$refs.ruleForm.validateField("checkPass"); | |||||
return callback(); | |||||
} else { | |||||
return callback(new Error(res.data.msg)); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} 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之间,允许字母数字和下划线") | |||||
); | |||||
} | |||||
}; | |||||
// 确认密码的校验方法 | |||||
let validateConfirmPass = (rule, value, callback) => { | |||||
if (value === "") { | |||||
return callback(new Error("请输入确认密码")); | |||||
} | |||||
// 校验是否以密码一致 | |||||
if (this.RegisterUser.pass != "" && value === this.RegisterUser.pass) { | |||||
this.$refs.ruleForm.validateField("checkPass"); | |||||
return callback(); | |||||
} else { | |||||
return callback(new Error("两次输入的密码不一致")); | |||||
} | |||||
}; | |||||
return { | |||||
isRegister: false, // 控制注册组件是否显示 | |||||
RegisterUser: { | |||||
name: "", | |||||
pass: "", | |||||
confirmPass: "" | |||||
}, | |||||
// 用户信息校验规则,validator(校验方法),trigger(触发方式),blur为在组件 Input 失去焦点时触发 | |||||
rules: { | |||||
name: [{ validator: validateName, trigger: "blur" }], | |||||
pass: [{ validator: validatePass, trigger: "blur" }], | |||||
confirmPass: [{ validator: validateConfirmPass, trigger: "blur" }] | |||||
} | |||||
}; | |||||
}, | |||||
watch: { | |||||
// 监听父组件传过来的register变量,设置this.isRegister的值 | |||||
register: function(val) { | |||||
if (val) { | |||||
this.isRegister = val; | |||||
} | |||||
}, | |||||
// 监听this.isRegister变量的值,更新父组件register变量的值 | |||||
isRegister: function(val) { | |||||
if (!val) { | |||||
this.$refs["ruleForm"].resetFields(); | |||||
this.$emit("fromChild", val); | |||||
} | |||||
} | |||||
}, | |||||
methods: { | |||||
Register() { | |||||
// 通过element自定义表单校验规则,校验用户输入的用户信息 | |||||
this.$refs["ruleForm"].validate(valid => { | |||||
//如果通过校验开始注册 | |||||
if (valid) { | |||||
this.$axios | |||||
.post("/api/users/register", { | |||||
userName: this.RegisterUser.name, | |||||
password: this.RegisterUser.pass | |||||
}) | |||||
.then(res => { | |||||
// “001”代表注册成功,其他的均为失败 | |||||
if (res.data.code === "001") { | |||||
// 隐藏注册组件 | |||||
this.isRegister = false; | |||||
// 弹出通知框提示注册成功信息 | |||||
this.notifySucceed(res.data.msg); | |||||
} else { | |||||
// 弹出通知框提示注册失败信息 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} else { | |||||
return false; | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
</style> |
@@ -0,0 +1,114 @@ | |||||
/* | |||||
* 入口文件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-04 23:38:41 | |||||
*/ | |||||
import Vue from 'vue' | |||||
import App from './App.vue' | |||||
import router from './router' | |||||
import store from './store' | |||||
import ElementUI from 'element-ui'; | |||||
import 'element-ui/lib/theme-chalk/index.css'; | |||||
Vue.use(ElementUI); | |||||
// 全局函数及变量 | |||||
import Global from './Global'; | |||||
Vue.use(Global); | |||||
import Axios from 'axios'; | |||||
Vue.prototype.$axios = Axios; | |||||
// 全局请求拦截器 | |||||
Axios.interceptors.request.use( | |||||
config => { | |||||
return config; | |||||
}, | |||||
error => { | |||||
// 跳转error页面 | |||||
router.push({ path: "/error" }); | |||||
return Promise.reject(error); | |||||
} | |||||
); | |||||
// 全局响应拦截器 | |||||
Axios.interceptors.response.use( | |||||
res => { | |||||
if (res.data.code === "401") { | |||||
// 401表示没有登录 | |||||
// 提示没有登录 | |||||
Vue.prototype.notifyError(res.data.msg); | |||||
// 修改vuex的showLogin状态,显示登录组件 | |||||
store.dispatch("setShowLogin", true); | |||||
} | |||||
if (res.data.code === "500") { | |||||
// 500表示服务器异常 | |||||
// 跳转error页面 | |||||
router.push({ path: "/error" }); | |||||
} | |||||
return res; | |||||
}, | |||||
error => { | |||||
// 跳转error页面 | |||||
router.push({ path: "/error" }); | |||||
return Promise.reject(error); | |||||
} | |||||
); | |||||
// 全局拦截器,在进入需要用户权限的页面前校验是否已经登录 | |||||
router.beforeResolve((to, from, next) => { | |||||
const loginUser = store.state.user.user; | |||||
// 判断路由是否设置相应校验用户权限 | |||||
if (to.meta.requireAuth) { | |||||
if (!loginUser) { | |||||
// 没有登录,显示登录组件 | |||||
store.dispatch("setShowLogin", true); | |||||
if (from.name == null) { | |||||
//此时,是在页面没有加载,直接在地址栏输入链接,进入需要登录验证的页面 | |||||
next("/"); | |||||
return; | |||||
} | |||||
// 终止导航 | |||||
next(false); | |||||
return; | |||||
} | |||||
} | |||||
next(); | |||||
}); | |||||
// 相对时间过滤器,把时间戳转换成时间 | |||||
// 格式: 2020-02-25 21:43:23 | |||||
Vue.filter('dateFormat', (dataStr) => { | |||||
var time = new Date(dataStr); | |||||
function timeAdd0 (str) { | |||||
if (str < 10) { | |||||
str = '0' + str; | |||||
} | |||||
return str; | |||||
} | |||||
var y = time.getFullYear(); | |||||
var m = time.getMonth() + 1; | |||||
var d = time.getDate(); | |||||
var h = time.getHours(); | |||||
var mm = time.getMinutes(); | |||||
var s = time.getSeconds(); | |||||
return y + '-' + timeAdd0(m) + '-' + timeAdd0(d) + ' ' + timeAdd0(h) + ':' + timeAdd0(mm) + ':' + timeAdd0(s); | |||||
}); | |||||
//全局组件 | |||||
import MyMenu from './components/MyMenu'; | |||||
Vue.component(MyMenu.name, MyMenu); | |||||
import MyList from './components/MyList'; | |||||
Vue.component(MyList.name, MyList); | |||||
import MyLogin from './components/MyLogin'; | |||||
Vue.component(MyLogin.name, MyLogin); | |||||
import MyRegister from './components/MyRegister'; | |||||
Vue.component(MyRegister.name, MyRegister); | |||||
Vue.config.productionTip = false; | |||||
new Vue({ | |||||
router, | |||||
store, | |||||
render: h => h(App) | |||||
}).$mount('#app') |
@@ -0,0 +1,90 @@ | |||||
/* | |||||
* @Description: 路由配置 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 13:58:48 | |||||
*/ | |||||
import Vue from 'vue' | |||||
import Router from 'vue-router' | |||||
Vue.use(Router) | |||||
const routes = [ | |||||
{ | |||||
path: '/', | |||||
name: 'Home', | |||||
component: () => import('../views/Home.vue') | |||||
}, | |||||
{ | |||||
path: '/error', | |||||
name: 'Error', | |||||
component: () => import('../components/Error.vue') | |||||
}, | |||||
{ | |||||
path: '/goods', | |||||
name: 'Goods', | |||||
component: () => import('../views/Goods.vue') | |||||
}, | |||||
{ | |||||
path: '/about', | |||||
name: 'About', | |||||
component: () => import('../views/About.vue') | |||||
}, | |||||
{ | |||||
path: '/goods/details', | |||||
name: 'Details', | |||||
component: () => import('../views/Details.vue') | |||||
}, | |||||
{ | |||||
path: '/shoppingCart', | |||||
name: 'ShoppingCart', | |||||
component: () => import('../views/ShoppingCart.vue'), | |||||
meta: { | |||||
requireAuth: true // 需要验证登录状态 | |||||
} | |||||
}, | |||||
{ | |||||
path: '/collect', | |||||
name: 'Collect', | |||||
component: () => import('../views/Collect.vue'), | |||||
meta: { | |||||
requireAuth: true // 需要验证登录状态 | |||||
} | |||||
}, | |||||
{ | |||||
path: '/order', | |||||
name: 'Order', | |||||
component: () => import('../views/Order.vue'), | |||||
meta: { | |||||
requireAuth: true // 需要验证登录状态 | |||||
} | |||||
}, | |||||
{ | |||||
path: '/confirmOrder', | |||||
name: 'ConfirmOrder', | |||||
component: () => import('../views/ConfirmOrder.vue'), | |||||
meta: { | |||||
requireAuth: true // 需要验证登录状态 | |||||
} | |||||
} | |||||
] | |||||
const router = new Router({ | |||||
// base: '/dist', | |||||
// mode: 'history', | |||||
routes | |||||
}) | |||||
/* 由于Vue-router在3.1之后把$router.push()方法改为了Promise。所以假如没有回调函数,错误信息就会交给全局的路由错误处理。 | |||||
vue-router先报了一个Uncaught(in promise)的错误(因为push没加回调) ,然后再点击路由的时候才会触发NavigationDuplicated的错误(路由出现的错误,全局错误处理打印了出来)。*/ | |||||
// 禁止全局路由错误处理打印 | |||||
const originalPush = Router.prototype.push; | |||||
Router.prototype.push = function push (location, onResolve, onReject) { | |||||
if (onResolve || onReject) | |||||
return originalPush.call(this, location, onResolve, onReject) | |||||
return originalPush.call(this, location).catch(err => err) | |||||
} | |||||
export default router |
@@ -0,0 +1,22 @@ | |||||
/* | |||||
* @Description: Vuex入口 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-25 22:51:50 | |||||
*/ | |||||
import Vue from 'vue' | |||||
import Vuex from 'vuex' | |||||
import user from './modules/user' | |||||
import shoppingCart from './modules/shoppingCart' | |||||
Vue.use(Vuex) | |||||
export default new Vuex.Store({ | |||||
strict: true, | |||||
modules: { | |||||
user, | |||||
shoppingCart | |||||
} | |||||
}) |
@@ -0,0 +1,160 @@ | |||||
/* | |||||
* @Description: 购物车状态模块 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-21 18:40:41 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-07 20:38:55 | |||||
*/ | |||||
export default { | |||||
state: { | |||||
shoppingCart: [] | |||||
// shoppingCart结构 | |||||
/* | |||||
shoppingCart = { | |||||
id: "", // 购物车id | |||||
productID: "", // 商品id | |||||
productName: "", // 商品名称 | |||||
productImg: "", // 商品图片 | |||||
price: "", // 商品价格 | |||||
num: "", // 商品数量 | |||||
maxNum: "", // 商品限购数量 | |||||
check: false // 是否勾选 | |||||
} */ | |||||
}, | |||||
getters: { | |||||
getShoppingCart (state) { | |||||
// 获取购物车状态 | |||||
return state.shoppingCart; | |||||
}, | |||||
getNum (state) { | |||||
// 购物车商品总数量 | |||||
let totalNum = 0; | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
totalNum += temp.num; | |||||
} | |||||
return totalNum; | |||||
}, | |||||
getIsAllCheck (state) { | |||||
// 判断是否全选 | |||||
let isAllCheck = true; | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
// 只要有一个商品没有勾选立即return false; | |||||
if (!temp.check) { | |||||
isAllCheck = false; | |||||
return isAllCheck; | |||||
} | |||||
} | |||||
return isAllCheck; | |||||
}, | |||||
getCheckGoods (state) { | |||||
// 获取勾选的商品信息 | |||||
// 用于确认订单页面 | |||||
let checkGoods = []; | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
if (temp.check) { | |||||
checkGoods.push(temp); | |||||
} | |||||
} | |||||
return checkGoods; | |||||
}, | |||||
getCheckNum (state) { | |||||
// 获取购物车勾选的商品数量 | |||||
let totalNum = 0; | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
if (temp.check) { | |||||
totalNum += temp.num; | |||||
} | |||||
} | |||||
return totalNum; | |||||
}, | |||||
getTotalPrice (state) { | |||||
// 购物车勾选的商品总价格 | |||||
let totalPrice = 0; | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
if (temp.check) { | |||||
totalPrice += temp.price * temp.num; | |||||
} | |||||
} | |||||
return totalPrice; | |||||
} | |||||
}, | |||||
mutations: { | |||||
setShoppingCart (state, data) { | |||||
// 设置购物车状态 | |||||
state.shoppingCart = data; | |||||
}, | |||||
unshiftShoppingCart (state, data) { | |||||
// 添加购物车 | |||||
// 用于在商品详情页点击添加购物车,后台添加成功后,更新vuex状态 | |||||
state.shoppingCart.unshift(data); | |||||
}, | |||||
updateShoppingCart (state, payload) { | |||||
// 更新购物车 | |||||
// 可更新商品数量和是否勾选 | |||||
// 用于购物车点击勾选及加减商品数量 | |||||
if (payload.prop == "num") { | |||||
// 判断效果的商品数量是否大于限购数量或小于1 | |||||
if (state.shoppingCart[payload.key].maxNum < payload.val) { | |||||
return; | |||||
} | |||||
if (payload.val < 1) { | |||||
return; | |||||
} | |||||
} | |||||
// 根据商品在购物车的数组的索引和属性更改 | |||||
state.shoppingCart[payload.key][payload.prop] = payload.val; | |||||
}, | |||||
addShoppingCartNum (state, productID) { | |||||
// 增加购物车商品数量 | |||||
// 用于在商品详情页点击添加购物车,后台返回002,“该商品已在购物车,数量 +1”,更新vuex的商品数量 | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
if (temp.productID == productID) { | |||||
if (temp.num < temp.maxNum) { | |||||
temp.num++; | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
deleteShoppingCart (state, id) { | |||||
// 根据购物车id删除购物车商品 | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
const temp = state.shoppingCart[i]; | |||||
if (temp.id == id) { | |||||
state.shoppingCart.splice(i, 1); | |||||
} | |||||
} | |||||
}, | |||||
checkAll (state, data) { | |||||
// 点击全选按钮,更改每个商品的勾选状态 | |||||
for (let i = 0; i < state.shoppingCart.length; i++) { | |||||
state.shoppingCart[i].check = data; | |||||
} | |||||
} | |||||
}, | |||||
actions: { | |||||
setShoppingCart ({ commit }, data) { | |||||
commit('setShoppingCart', data); | |||||
}, | |||||
unshiftShoppingCart ({ commit }, data) { | |||||
commit('unshiftShoppingCart', data); | |||||
}, | |||||
updateShoppingCart ({ commit }, payload) { | |||||
commit('updateShoppingCart', payload); | |||||
}, | |||||
addShoppingCartNum ({ commit }, productID) { | |||||
commit('addShoppingCartNum', productID); | |||||
}, | |||||
deleteShoppingCart ({ commit }, id) { | |||||
commit('deleteShoppingCart', id); | |||||
}, | |||||
checkAll ({ commit }, data) { | |||||
commit('checkAll', data); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
/* | |||||
* @Description: 用户登录状态模块 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-19 17:42:11 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-26 23:14:32 | |||||
*/ | |||||
export default { | |||||
state: { | |||||
user: "", // 登录的用户 | |||||
showLogin: false // 用于控制是否显示登录组件 | |||||
}, | |||||
getters: { | |||||
getUser (state) { | |||||
return state.user | |||||
}, | |||||
getShowLogin (state) { | |||||
return state.showLogin | |||||
} | |||||
}, | |||||
mutations: { | |||||
setUser (state, data) { | |||||
state.user = data; | |||||
}, | |||||
setShowLogin (state, data) { | |||||
state.showLogin = data; | |||||
} | |||||
}, | |||||
actions: { | |||||
setUser ({ commit }, data) { | |||||
commit('setUser', data); | |||||
}, | |||||
setShowLogin ({ commit }, data) { | |||||
commit('setShowLogin', data); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,59 @@ | |||||
<!-- | |||||
* @Description: 关于我们页面组件,未完成 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-12 19:36:46 | |||||
--> | |||||
<template> | |||||
<div class="about" id="about" name="about"> | |||||
<div class="about-header"> | |||||
<div class="about-title"> | |||||
<i class="el-icon-tickets" style="color: #ff6700;"></i> | |||||
关于我们 | |||||
</div> | |||||
</div> | |||||
<div class="about-content"> | |||||
<MyMarkdown></MyMarkdown> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import MyMarkdown from "../components/MyMarkdown"; | |||||
export default { | |||||
components: { | |||||
MyMarkdown | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.about { | |||||
background-color: #f5f5f5; | |||||
} | |||||
.about .about-header { | |||||
height: 64px; | |||||
background-color: #fff; | |||||
border-bottom: 2px solid #ff6700; | |||||
} | |||||
.about .about-header .about-title { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
height: 64px; | |||||
line-height: 64px; | |||||
font-size: 28px; | |||||
} | |||||
.about .content { | |||||
padding: 20px 0; | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.about .content .goods-list { | |||||
margin-left: -13.7px; | |||||
overflow: hidden; | |||||
} | |||||
.about .about-content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
background-color: #fff; | |||||
} | |||||
</style> |
@@ -0,0 +1,103 @@ | |||||
<!-- | |||||
* @Description: 我的收藏页面组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-20 17:22:56 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-12 19:34:00 | |||||
--> | |||||
<template> | |||||
<div class="collect"> | |||||
<!-- Add a static page for my favorite module --> | |||||
<div class="collect-header"> | |||||
<div class="collect-title"> | |||||
<i class="el-icon-collection-tag" style="color: #ff6700;"></i> | |||||
我的收藏 | |||||
</div> | |||||
</div> | |||||
<div class="content"> | |||||
<div class="goods-list" v-if="collectList.length>0"> | |||||
<MyList :list="collectList" :isDelete="true"></MyList> | |||||
</div> | |||||
<!-- 收藏列表为空的时候显示的内容 --> | |||||
<div v-else class="collect-empty"> | |||||
<div class="empty"> | |||||
<h2>您的收藏还是空的!</h2> | |||||
<p>快去购物吧!</p> | |||||
</div> | |||||
</div> | |||||
<!-- 收藏列表为空的时候显示的内容END --> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
collectList: [] | |||||
}; | |||||
}, | |||||
activated() { | |||||
// 获取收藏数据 | |||||
this.$axios | |||||
.post("/api/user/collect/getCollect", { | |||||
user_id: this.$store.getters.getUser.user_id | |||||
}) | |||||
.then(res => { | |||||
if (res.data.code === "001") { | |||||
this.collectList = res.data.collectList; | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
.collect { | |||||
background-color: #f5f5f5; | |||||
} | |||||
.collect .collect-header { | |||||
height: 64px; | |||||
background-color: #fff; | |||||
border-bottom: 2px solid #ff6700; | |||||
} | |||||
.collect .collect-header .collect-title { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
height: 64px; | |||||
line-height: 58px; | |||||
font-size: 28px; | |||||
} | |||||
.collect .content { | |||||
padding: 20px 0; | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.collect .content .goods-list { | |||||
margin-left: -13.7px; | |||||
overflow: hidden; | |||||
} | |||||
/* 收藏列表为空的时候显示的内容CSS */ | |||||
.collect .collect-empty { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.collect .collect-empty .empty { | |||||
height: 300px; | |||||
padding: 0 0 130px 558px; | |||||
margin: 65px 0 0; | |||||
background: url(../assets/imgs/cart-empty.png) no-repeat 124px 0; | |||||
color: #b0b0b0; | |||||
overflow: hidden; | |||||
} | |||||
.collect .collect-empty .empty h2 { | |||||
margin: 70px 0 15px; | |||||
font-size: 36px; | |||||
} | |||||
.collect .collect-empty .empty p { | |||||
margin: 0 0 20px; | |||||
font-size: 20px; | |||||
} | |||||
/* 收藏列表为空的时候显示的内容CSS END */ | |||||
</style> |
@@ -0,0 +1,454 @@ | |||||
<!-- | |||||
* @Description: 确认订单页面组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-23 23:46:39 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-29 13:10:21 | |||||
--> | |||||
<template> | |||||
<div class="confirmOrder"> | |||||
<!-- 头部 --> | |||||
<div class="confirmOrder-header"> | |||||
<div class="header-content"> | |||||
<p> | |||||
<i class="el-icon-s-order"></i> | |||||
</p> | |||||
<p>确认订单</p> | |||||
<router-link to></router-link> | |||||
</div> | |||||
</div> | |||||
<!-- 头部END --> | |||||
<!-- 主要内容容器 --> | |||||
<div class="content"> | |||||
<!-- 选择地址 --> | |||||
<div class="section-address"> | |||||
<p class="title">收货地址</p> | |||||
<div class="address-body"> | |||||
<ul> | |||||
<li | |||||
:class="item.id == confirmAddress ? 'in-section' : ''" | |||||
v-for="item in address" | |||||
:key="item.id" | |||||
> | |||||
<h2>{{item.name}}</h2> | |||||
<p class="phone">{{item.phone}}</p> | |||||
<p class="address">{{item.address}}</p> | |||||
</li> | |||||
<li class="add-address"> | |||||
<i class="el-icon-circle-plus-outline"></i> | |||||
<p>添加新地址</p> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
<!-- 选择地址END --> | |||||
<!-- 商品及优惠券 --> | |||||
<div class="section-goods"> | |||||
<p class="title">商品及优惠券</p> | |||||
<div class="goods-list"> | |||||
<ul> | |||||
<li v-for="item in getCheckGoods" :key="item.id"> | |||||
<img :src="$target + item.productImg" /> | |||||
<span class="pro-name">{{item.productName}}</span> | |||||
<span class="pro-price">{{item.price}}元 x {{item.num}}</span> | |||||
<span class="pro-status"></span> | |||||
<span class="pro-total">{{item.price * item.num}}元</span> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
<!-- 商品及优惠券END --> | |||||
<!-- 配送方式 --> | |||||
<div class="section-shipment"> | |||||
<p class="title">配送方式</p> | |||||
<p class="shipment">包邮</p> | |||||
</div> | |||||
<!-- 配送方式END --> | |||||
<!-- 发票 --> | |||||
<div class="section-invoice"> | |||||
<p class="title">发票</p> | |||||
<p class="invoice">电子发票</p> | |||||
<p class="invoice">个人</p> | |||||
<p class="invoice">商品明细</p> | |||||
</div> | |||||
<!-- 发票END --> | |||||
<!-- 结算列表 --> | |||||
<div class="section-count"> | |||||
<div class="money-box"> | |||||
<ul> | |||||
<li> | |||||
<span class="title">商品件数:</span> | |||||
<span class="value">{{getCheckNum}}件</span> | |||||
</li> | |||||
<li> | |||||
<span class="title">商品总价:</span> | |||||
<span class="value">{{getTotalPrice}}元</span> | |||||
</li> | |||||
<li> | |||||
<span class="title">活动优惠:</span> | |||||
<span class="value">-0元</span> | |||||
</li> | |||||
<li> | |||||
<span class="title">优惠券抵扣:</span> | |||||
<span class="value">-0元</span> | |||||
</li> | |||||
<li> | |||||
<span class="title">运费:</span> | |||||
<span class="value">0元</span> | |||||
</li> | |||||
<li class="total"> | |||||
<span class="title">应付总额:</span> | |||||
<span class="value"> | |||||
<span class="total-price">{{getTotalPrice}}</span>元 | |||||
</span> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
<!-- 结算列表END --> | |||||
<!-- 结算导航 --> | |||||
<div class="section-bar"> | |||||
<div class="btn"> | |||||
<router-link to="/shoppingCart" class="btn-base btn-return">返回购物车</router-link> | |||||
<a href="javascript:void(0);" @click="addOrder" class="btn-base btn-primary">结算</a> | |||||
</div> | |||||
</div> | |||||
<!-- 结算导航END --> | |||||
</div> | |||||
<!-- 主要内容容器END --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapGetters } from "vuex"; | |||||
import { mapActions } from "vuex"; | |||||
export default { | |||||
name: "", | |||||
data() { | |||||
return { | |||||
// 虚拟数据 | |||||
confirmAddress: 1, // 选择的地址id | |||||
// 地址列表 | |||||
address: [ | |||||
{ | |||||
id: 1, | |||||
name: "陈同学", | |||||
phone: "100861001010000", | |||||
address: "广东 广州市 白云区 ***" | |||||
}, | |||||
{ | |||||
id: 2, | |||||
name: "陈同学", | |||||
phone: "100861001010000", | |||||
address: "广东 广州市 白云区 ***" | |||||
} | |||||
] | |||||
}; | |||||
}, | |||||
created() { | |||||
// 如果没有勾选购物车商品直接进入确认订单页面,提示信息并返回购物车 | |||||
if (this.getCheckNum < 1) { | |||||
this.notifyError("请勾选商品后再结算"); | |||||
this.$router.push({ path: "/shoppingCart" }); | |||||
} | |||||
}, | |||||
computed: { | |||||
// 结算的商品数量; 结算商品总计; 结算商品信息 | |||||
...mapGetters(["getCheckNum", "getTotalPrice", "getCheckGoods"]) | |||||
}, | |||||
methods: { | |||||
...mapActions(["deleteShoppingCart"]), | |||||
addOrder() { | |||||
this.$axios | |||||
.post("/api/user/order/addOrder", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
products: this.getCheckGoods | |||||
}) | |||||
.then(res => { | |||||
let products = this.getCheckGoods; | |||||
switch (res.data.code) { | |||||
// “001”代表结算成功 | |||||
case "001": | |||||
for (let i = 0; i < products.length; i++) { | |||||
const temp = products[i]; | |||||
// 删除已经结算的购物车商品 | |||||
this.deleteShoppingCart(temp.id); | |||||
} | |||||
// 提示结算结果 | |||||
this.notifySucceed(res.data.msg); | |||||
// 跳转我的订单页面 | |||||
this.$router.push({ path: "/order" }); | |||||
break; | |||||
default: | |||||
// 提示失败信息 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.confirmOrder { | |||||
background-color: #f5f5f5; | |||||
padding-bottom: 20px; | |||||
} | |||||
/* 头部CSS */ | |||||
.confirmOrder .confirmOrder-header { | |||||
background-color: #fff; | |||||
border-bottom: 2px solid #ff6700; | |||||
margin-bottom: 20px; | |||||
} | |||||
.confirmOrder .confirmOrder-header .header-content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
height: 80px; | |||||
} | |||||
.confirmOrder .confirmOrder-header .header-content p { | |||||
float: left; | |||||
font-size: 28px; | |||||
line-height: 80px; | |||||
color: #424242; | |||||
margin-right: 20px; | |||||
} | |||||
.confirmOrder .confirmOrder-header .header-content p i { | |||||
font-size: 45px; | |||||
color: #ff6700; | |||||
line-height: 80px; | |||||
} | |||||
/* 头部CSS END */ | |||||
/* 主要内容容器CSS */ | |||||
.confirmOrder .content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
padding: 48px 0 0; | |||||
background-color: #fff; | |||||
} | |||||
/* 选择地址CSS */ | |||||
.confirmOrder .content .section-address { | |||||
margin: 0 48px; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-address .title { | |||||
color: #333; | |||||
font-size: 18px; | |||||
line-height: 20px; | |||||
margin-bottom: 20px; | |||||
} | |||||
.confirmOrder .content .address-body li { | |||||
float: left; | |||||
color: #333; | |||||
width: 220px; | |||||
height: 178px; | |||||
border: 1px solid #e0e0e0; | |||||
padding: 15px 24px 0; | |||||
margin-right: 17px; | |||||
margin-bottom: 24px; | |||||
} | |||||
.confirmOrder .content .address-body .in-section { | |||||
border: 1px solid #ff6700; | |||||
} | |||||
.confirmOrder .content .address-body li h2 { | |||||
font-size: 18px; | |||||
font-weight: normal; | |||||
line-height: 30px; | |||||
margin-bottom: 10px; | |||||
} | |||||
.confirmOrder .content .address-body li p { | |||||
font-size: 14px; | |||||
color: #757575; | |||||
} | |||||
.confirmOrder .content .address-body li .address { | |||||
padding: 10px 0; | |||||
max-width: 180px; | |||||
max-height: 88px; | |||||
line-height: 22px; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .address-body .add-address { | |||||
text-align: center; | |||||
line-height: 30px; | |||||
} | |||||
.confirmOrder .content .address-body .add-address i { | |||||
font-size: 30px; | |||||
padding-top: 50px; | |||||
text-align: center; | |||||
} | |||||
/* 选择地址CSS END */ | |||||
/* 商品及优惠券CSS */ | |||||
.confirmOrder .content .section-goods { | |||||
margin: 0 48px; | |||||
} | |||||
.confirmOrder .content .section-goods p.title { | |||||
color: #333; | |||||
font-size: 18px; | |||||
line-height: 40px; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list { | |||||
padding: 5px 0; | |||||
border-top: 1px solid #e0e0e0; | |||||
border-bottom: 1px solid #e0e0e0; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li { | |||||
padding: 10px 0; | |||||
color: #424242; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li img { | |||||
float: left; | |||||
width: 30px; | |||||
height: 30px; | |||||
margin-right: 10px; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li .pro-name { | |||||
float: left; | |||||
width: 650px; | |||||
line-height: 30px; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li .pro-price { | |||||
float: left; | |||||
width: 150px; | |||||
text-align: center; | |||||
line-height: 30px; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li .pro-status { | |||||
float: left; | |||||
width: 99px; | |||||
height: 30px; | |||||
text-align: center; | |||||
line-height: 30px; | |||||
} | |||||
.confirmOrder .content .section-goods .goods-list li .pro-total { | |||||
float: left; | |||||
width: 190px; | |||||
text-align: center; | |||||
color: #ff6700; | |||||
line-height: 30px; | |||||
} | |||||
/* 商品及优惠券CSS END */ | |||||
/* 配送方式CSS */ | |||||
.confirmOrder .content .section-shipment { | |||||
margin: 0 48px; | |||||
padding: 25px 0; | |||||
border-bottom: 1px solid #e0e0e0; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-shipment .title { | |||||
float: left; | |||||
width: 150px; | |||||
color: #333; | |||||
font-size: 18px; | |||||
line-height: 38px; | |||||
} | |||||
.confirmOrder .content .section-shipment .shipment { | |||||
float: left; | |||||
line-height: 38px; | |||||
font-size: 14px; | |||||
color: #ff6700; | |||||
} | |||||
/* 配送方式CSS END */ | |||||
/* 发票CSS */ | |||||
.confirmOrder .content .section-invoice { | |||||
margin: 0 48px; | |||||
padding: 25px 0; | |||||
border-bottom: 1px solid #e0e0e0; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-invoice .title { | |||||
float: left; | |||||
width: 150px; | |||||
color: #333; | |||||
font-size: 18px; | |||||
line-height: 38px; | |||||
} | |||||
.confirmOrder .content .section-invoice .invoice { | |||||
float: left; | |||||
line-height: 38px; | |||||
font-size: 14px; | |||||
margin-right: 20px; | |||||
color: #ff6700; | |||||
} | |||||
/* 发票CSS END */ | |||||
/* 结算列表CSS */ | |||||
.confirmOrder .content .section-count { | |||||
margin: 0 48px; | |||||
padding: 20px 0; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-count .money-box { | |||||
float: right; | |||||
text-align: right; | |||||
} | |||||
.confirmOrder .content .section-count .money-box .title { | |||||
float: left; | |||||
width: 126px; | |||||
height: 30px; | |||||
display: block; | |||||
line-height: 30px; | |||||
color: #757575; | |||||
} | |||||
.confirmOrder .content .section-count .money-box .value { | |||||
float: left; | |||||
min-width: 105px; | |||||
height: 30px; | |||||
display: block; | |||||
line-height: 30px; | |||||
color: #ff6700; | |||||
} | |||||
.confirmOrder .content .section-count .money-box .total .title { | |||||
padding-top: 15px; | |||||
} | |||||
.confirmOrder .content .section-count .money-box .total .value { | |||||
padding-top: 10px; | |||||
} | |||||
.confirmOrder .content .section-count .money-box .total-price { | |||||
font-size: 30px; | |||||
} | |||||
/* 结算列表CSS END */ | |||||
/* 结算导航CSS */ | |||||
.confirmOrder .content .section-bar { | |||||
padding: 20px 48px; | |||||
border-top: 2px solid #f5f5f5; | |||||
overflow: hidden; | |||||
} | |||||
.confirmOrder .content .section-bar .btn { | |||||
float: right; | |||||
} | |||||
.confirmOrder .content .section-bar .btn .btn-base { | |||||
float: left; | |||||
margin-left: 30px; | |||||
width: 158px; | |||||
height: 38px; | |||||
border: 1px solid #b0b0b0; | |||||
font-size: 14px; | |||||
line-height: 38px; | |||||
text-align: center; | |||||
} | |||||
.confirmOrder .content .section-bar .btn .btn-return { | |||||
color: rgba(0, 0, 0, 0.27); | |||||
border-color: rgba(0, 0, 0, 0.27); | |||||
} | |||||
.confirmOrder .content .section-bar .btn .btn-primary { | |||||
background: #ff6700; | |||||
border-color: #ff6700; | |||||
color: #fff; | |||||
} | |||||
/* 结算导航CSS */ | |||||
/* 主要内容容器CSS END */ | |||||
</style> |
@@ -0,0 +1,361 @@ | |||||
<!-- | |||||
* @Description: 商品详情页面组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-16 20:20:26 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-07 21:59:26 | |||||
--> | |||||
<template> | |||||
<div id="details"> | |||||
<!-- 头部 --> | |||||
<div class="page-header"> | |||||
<div class="title"> | |||||
<p>{{productDetails.product_name}}</p> | |||||
<div class="list"> | |||||
<ul> | |||||
<li> | |||||
<router-link to>概述</router-link> | |||||
</li> | |||||
<li> | |||||
<router-link to>参数</router-link> | |||||
</li> | |||||
<li> | |||||
<router-link to>用户评价</router-link> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 头部END --> | |||||
<!-- 主要内容 --> | |||||
<div class="main"> | |||||
<!-- 左侧商品轮播图 --> | |||||
<div class="block"> | |||||
<el-carousel height="560px" v-if="productPicture.length>1"> | |||||
<el-carousel-item v-for="item in productPicture" :key="item.id"> | |||||
<img style="height:560px;" :src="$target + item.product_picture" :alt="item.intro" /> | |||||
</el-carousel-item> | |||||
</el-carousel> | |||||
<div v-if="productPicture.length==1"> | |||||
<img | |||||
style="height:560px;" | |||||
:src="$target + productPicture[0].product_picture" | |||||
:alt="productPicture[0].intro" | |||||
/> | |||||
</div> | |||||
</div> | |||||
<!-- 左侧商品轮播图END --> | |||||
<!-- 右侧内容区 --> | |||||
<div class="content"> | |||||
<h1 class="name">{{productDetails.product_name}}</h1> | |||||
<p class="intro">{{productDetails.product_intro}}</p> | |||||
<p class="store">小米自营</p> | |||||
<div class="price"> | |||||
<span>{{productDetails.product_selling_price}}元</span> | |||||
<span | |||||
v-show="productDetails.product_price != productDetails.product_selling_price" | |||||
class="del" | |||||
>{{productDetails.product_price}}元</span> | |||||
</div> | |||||
<div class="pro-list"> | |||||
<span class="pro-name">{{productDetails.product_name}}</span> | |||||
<span class="pro-price"> | |||||
<span>{{productDetails.product_selling_price}}元</span> | |||||
<span | |||||
v-show="productDetails.product_price != productDetails.product_selling_price" | |||||
class="pro-del" | |||||
>{{productDetails.product_price}}元</span> | |||||
</span> | |||||
<p class="price-sum">总计 : {{productDetails.product_selling_price}}元</p> | |||||
</div> | |||||
<!-- 内容区底部按钮 --> | |||||
<div class="button"> | |||||
<el-button class="shop-cart" :disabled="dis" @click="addShoppingCart">加入购物车</el-button> | |||||
<el-button class="like" @click="addCollect">喜欢</el-button> | |||||
</div> | |||||
<!-- 内容区底部按钮END --> | |||||
<div class="pro-policy"> | |||||
<ul> | |||||
<li> | |||||
<i class="el-icon-circle-check"></i> 小米自营 | |||||
</li> | |||||
<li> | |||||
<i class="el-icon-circle-check"></i> 小米发货 | |||||
</li> | |||||
<li> | |||||
<i class="el-icon-circle-check"></i> 7天无理由退货 | |||||
</li> | |||||
<li> | |||||
<i class="el-icon-circle-check"></i> 7天价格保护 | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
<!-- 右侧内容区END --> | |||||
</div> | |||||
<!-- 主要内容END --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapActions } from "vuex"; | |||||
export default { | |||||
data() { | |||||
return { | |||||
dis: false, // 控制“加入购物车按钮是否可用” | |||||
productID: "", // 商品id | |||||
productDetails: "", // 商品详细信息 | |||||
productPicture: "" // 商品图片 | |||||
}; | |||||
}, | |||||
// 通过路由获取商品id | |||||
activated() { | |||||
if (this.$route.query.productID != undefined) { | |||||
this.productID = this.$route.query.productID; | |||||
} | |||||
}, | |||||
watch: { | |||||
// 监听商品id的变化,请求后端获取商品数据 | |||||
productID: function(val) { | |||||
this.getDetails(val); | |||||
this.getDetailsPicture(val); | |||||
} | |||||
}, | |||||
methods: { | |||||
...mapActions(["unshiftShoppingCart", "addShoppingCartNum"]), | |||||
// 获取商品详细信息 | |||||
getDetails(val) { | |||||
this.$axios | |||||
.post("/api/product/getDetails", { | |||||
productID: val | |||||
}) | |||||
.then(res => { | |||||
this.productDetails = res.data.Product[0]; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
// 获取商品图片 | |||||
getDetailsPicture(val) { | |||||
this.$axios | |||||
.post("/api/product/getDetailsPicture", { | |||||
productID: val | |||||
}) | |||||
.then(res => { | |||||
this.productPicture = res.data.ProductPicture; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
// 加入购物车 | |||||
addShoppingCart() { | |||||
// 判断是否登录,没有登录则显示登录组件 | |||||
if (!this.$store.getters.getUser) { | |||||
this.$store.dispatch("setShowLogin", true); | |||||
return; | |||||
} | |||||
this.$axios | |||||
.post("/api/user/shoppingCart/addShoppingCart", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
product_id: this.productID | |||||
}) | |||||
.then(res => { | |||||
switch (res.data.code) { | |||||
case "001": | |||||
// 新加入购物车成功 | |||||
this.unshiftShoppingCart(res.data.shoppingCartData[0]); | |||||
this.notifySucceed(res.data.msg); | |||||
break; | |||||
case "002": | |||||
// 该商品已经在购物车,数量+1 | |||||
this.addShoppingCartNum(this.productID); | |||||
this.notifySucceed(res.data.msg); | |||||
break; | |||||
case "003": | |||||
// 商品数量达到限购数量 | |||||
this.dis = true; | |||||
this.notifyError(res.data.msg); | |||||
break; | |||||
default: | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
addCollect() { | |||||
// 判断是否登录,没有登录则显示登录组件 | |||||
if (!this.$store.getters.getUser) { | |||||
this.$store.dispatch("setShowLogin", true); | |||||
return; | |||||
} | |||||
this.$axios | |||||
.post("/api/user/collect/addCollect", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
product_id: this.productID | |||||
}) | |||||
.then(res => { | |||||
if (res.data.code == "001") { | |||||
// 添加收藏成功 | |||||
this.notifySucceed(res.data.msg); | |||||
} else { | |||||
// 添加收藏失败 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
/* 头部CSS */ | |||||
#details .page-header { | |||||
height: 64px; | |||||
margin-top: -20px; | |||||
z-index: 4; | |||||
background: #fff; | |||||
border-bottom: 1px solid #e0e0e0; | |||||
-webkit-box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.07); | |||||
box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.07); | |||||
} | |||||
#details .page-header .title { | |||||
width: 1225px; | |||||
height: 64px; | |||||
line-height: 64px; | |||||
font-size: 18px; | |||||
font-weight: 400; | |||||
color: #212121; | |||||
margin: 0 auto; | |||||
} | |||||
#details .page-header .title p { | |||||
float: left; | |||||
} | |||||
#details .page-header .title .list { | |||||
height: 64px; | |||||
float: right; | |||||
} | |||||
#details .page-header .title .list li { | |||||
float: left; | |||||
margin-left: 20px; | |||||
} | |||||
#details .page-header .title .list li a { | |||||
font-size: 14px; | |||||
color: #616161; | |||||
} | |||||
#details .page-header .title .list li a:hover { | |||||
font-size: 14px; | |||||
color: #ff6700; | |||||
} | |||||
/* 头部CSS END */ | |||||
/* 主要内容CSS */ | |||||
#details .main { | |||||
width: 1225px; | |||||
height: 560px; | |||||
padding-top: 30px; | |||||
margin: 0 auto; | |||||
} | |||||
#details .main .block { | |||||
float: left; | |||||
width: 560px; | |||||
height: 560px; | |||||
} | |||||
#details .el-carousel .el-carousel__indicator .el-carousel__button { | |||||
background-color: rgba(163, 163, 163, 0.8); | |||||
} | |||||
#details .main .content { | |||||
float: left; | |||||
margin-left: 25px; | |||||
width: 640px; | |||||
} | |||||
#details .main .content .name { | |||||
height: 30px; | |||||
line-height: 30px; | |||||
font-size: 24px; | |||||
font-weight: normal; | |||||
color: #212121; | |||||
} | |||||
#details .main .content .intro { | |||||
color: #b0b0b0; | |||||
padding-top: 10px; | |||||
} | |||||
#details .main .content .store { | |||||
color: #ff6700; | |||||
padding-top: 10px; | |||||
} | |||||
#details .main .content .price { | |||||
display: block; | |||||
font-size: 18px; | |||||
color: #ff6700; | |||||
border-bottom: 1px solid #e0e0e0; | |||||
padding: 25px 0 25px; | |||||
} | |||||
#details .main .content .price .del { | |||||
font-size: 14px; | |||||
margin-left: 10px; | |||||
color: #b0b0b0; | |||||
text-decoration: line-through; | |||||
} | |||||
#details .main .content .pro-list { | |||||
background: #f9f9fa; | |||||
padding: 30px 60px; | |||||
margin: 50px 0 50px; | |||||
} | |||||
#details .main .content .pro-list span { | |||||
line-height: 30px; | |||||
color: #616161; | |||||
} | |||||
#details .main .content .pro-list .pro-price { | |||||
float: right; | |||||
} | |||||
#details .main .content .pro-list .pro-price .pro-del { | |||||
margin-left: 10px; | |||||
text-decoration: line-through; | |||||
} | |||||
#details .main .content .pro-list .price-sum { | |||||
color: #ff6700; | |||||
font-size: 24px; | |||||
padding-top: 20px; | |||||
} | |||||
#details .main .content .button { | |||||
height: 55px; | |||||
margin: 10px 0 20px 0; | |||||
} | |||||
#details .main .content .button .el-button { | |||||
float: left; | |||||
height: 55px; | |||||
font-size: 16px; | |||||
color: #fff; | |||||
border: none; | |||||
text-align: center; | |||||
} | |||||
#details .main .content .button .shop-cart { | |||||
width: 340px; | |||||
background-color: #ff6700; | |||||
} | |||||
#details .main .content .button .shop-cart:hover { | |||||
background-color: #f25807; | |||||
} | |||||
#details .main .content .button .like { | |||||
width: 260px; | |||||
margin-left: 40px; | |||||
background-color: #b0b0b0; | |||||
} | |||||
#details .main .content .button .like:hover { | |||||
background-color: #757575; | |||||
} | |||||
#details .main .content .pro-policy li { | |||||
float: left; | |||||
margin-right: 20px; | |||||
color: #b0b0b0; | |||||
} | |||||
/* 主要内容CSS END */ | |||||
</style> |
@@ -0,0 +1,282 @@ | |||||
<!-- | |||||
* @Description: 全部商品页面组件(包括全部商品,商品分类,商品搜索) | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-03-08 12:11:13 | |||||
--> | |||||
<template> | |||||
<div class="goods" id="goods" name="goods"> | |||||
<!-- 面包屑 --> | |||||
<div class="breadcrumb"> | |||||
<el-breadcrumb separator-class="el-icon-arrow-right"> | |||||
<el-breadcrumb-item to="/">首页</el-breadcrumb-item> | |||||
<el-breadcrumb-item>全部商品</el-breadcrumb-item> | |||||
<el-breadcrumb-item v-if="search">搜索</el-breadcrumb-item> | |||||
<el-breadcrumb-item v-else>分类</el-breadcrumb-item> | |||||
<el-breadcrumb-item v-if="search">{{search}}</el-breadcrumb-item> | |||||
</el-breadcrumb> | |||||
</div> | |||||
<!-- 面包屑END --> | |||||
<!-- 分类标签 --> | |||||
<div class="nav"> | |||||
<div class="product-nav"> | |||||
<div class="title">分类</div> | |||||
<el-tabs v-model="activeName" type="card"> | |||||
<el-tab-pane | |||||
v-for="item in categoryList" | |||||
:key="item.category_id" | |||||
:label="item.category_name" | |||||
:name="''+item.category_id" | |||||
/> | |||||
</el-tabs> | |||||
</div> | |||||
</div> | |||||
<!-- 分类标签END --> | |||||
<!-- 主要内容区 --> | |||||
<div class="main"> | |||||
<div class="list"> | |||||
<MyList :list="product" v-if="product.length>0"></MyList> | |||||
<div v-else class="none-product">抱歉没有找到相关的商品,请看看其他的商品</div> | |||||
</div> | |||||
<!-- 分页 --> | |||||
<div class="pagination"> | |||||
<el-pagination | |||||
background | |||||
layout="prev, pager, next" | |||||
:page-size="pageSize" | |||||
:total="total" | |||||
@current-change="currentChange" | |||||
></el-pagination> | |||||
</div> | |||||
<!-- 分页END --> | |||||
</div> | |||||
<!-- 主要内容区END --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
categoryList: "", //分类列表 | |||||
categoryID: [], // 分类id | |||||
product: "", // 商品列表 | |||||
productList: "", | |||||
total: 0, // 商品总量 | |||||
pageSize: 15, // 每页显示的商品数量 | |||||
currentPage: 1, //当前页码 | |||||
activeName: "-1", // 分类列表当前选中的id | |||||
search: "" // 搜索条件 | |||||
}; | |||||
}, | |||||
created() { | |||||
// 获取分类列表 | |||||
this.getCategory(); | |||||
}, | |||||
activated() { | |||||
this.activeName = "-1"; // 初始化分类列表当前选中的id为-1 | |||||
this.total = 0; // 初始化商品总量为0 | |||||
this.currentPage = 1; //初始化当前页码为1 | |||||
// 如果路由没有传递参数,默认为显示全部商品 | |||||
if (Object.keys(this.$route.query).length == 0) { | |||||
this.categoryID = []; | |||||
this.activeName = "0"; | |||||
return; | |||||
} | |||||
// 如果路由传递了categoryID,则显示对应的分类商品 | |||||
if (this.$route.query.categoryID != undefined) { | |||||
this.categoryID = this.$route.query.categoryID; | |||||
if (this.categoryID.length == 1) { | |||||
this.activeName = "" + this.categoryID[0]; | |||||
} | |||||
return; | |||||
} | |||||
// 如果路由传递了search,则为搜索,显示对应的分类商品 | |||||
if (this.$route.query.search != undefined) { | |||||
this.search = this.$route.query.search; | |||||
} | |||||
}, | |||||
watch: { | |||||
// 监听点击了哪个分类标签,通过修改分类id,响应相应的商品 | |||||
activeName: function(val) { | |||||
if (val == 0) { | |||||
this.categoryID = []; | |||||
} | |||||
if (val > 0) { | |||||
this.categoryID = [Number(val)]; | |||||
} | |||||
// 初始化商品总量和当前页码 | |||||
this.total = 0; | |||||
this.currentPage = 1; | |||||
// 更新地址栏链接,方便刷新页面可以回到原来的页面 | |||||
this.$router.push({ | |||||
path: "/goods", | |||||
query: { categoryID: this.categoryID } | |||||
}); | |||||
}, | |||||
// 监听搜索条件,响应相应的商品 | |||||
search: function(val) { | |||||
if (val != "") { | |||||
this.getProductBySearch(val); | |||||
} | |||||
}, | |||||
// 监听分类id,响应相应的商品 | |||||
categoryID: function() { | |||||
this.getData(); | |||||
this.search = ""; | |||||
}, | |||||
// 监听路由变化,更新路由传递了搜索条件 | |||||
$route: function(val) { | |||||
if (val.path == "/goods") { | |||||
if (val.query.search != undefined) { | |||||
this.activeName = "-1"; | |||||
this.currentPage = 1; | |||||
this.total = 0; | |||||
this.search = val.query.search; | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
methods: { | |||||
// 返回顶部 | |||||
backtop() { | |||||
const timer = setInterval(function() { | |||||
const top = document.documentElement.scrollTop || document.body.scrollTop; | |||||
const speed = Math.floor(-top / 5); | |||||
document.documentElement.scrollTop = document.body.scrollTop = | |||||
top + speed; | |||||
if (top === 0) { | |||||
clearInterval(timer); | |||||
} | |||||
}, 20); | |||||
}, | |||||
// 页码变化调用currentChange方法 | |||||
currentChange(currentPage) { | |||||
this.currentPage = currentPage; | |||||
if (this.search != "") { | |||||
this.getProductBySearch(); | |||||
} else { | |||||
this.getData(); | |||||
} | |||||
this.backtop(); | |||||
}, | |||||
// 向后端请求分类列表数据 | |||||
getCategory() { | |||||
this.$axios | |||||
.post("/api/product/getCategory", {}) | |||||
.then(res => { | |||||
const val = { | |||||
category_id: 0, | |||||
category_name: "全部" | |||||
}; | |||||
const cate = res.data.category; | |||||
cate.unshift(val); | |||||
this.categoryList = cate; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
// 向后端请求全部商品或分类商品数据 | |||||
getData() { | |||||
// 如果分类列表为空则请求全部商品数据,否则请求分类商品数据 | |||||
const api = | |||||
this.categoryID.length == 0 | |||||
? "/api/product/getAllProduct" | |||||
: "/api/product/getProductByCategory"; | |||||
this.$axios | |||||
.post(api, { | |||||
categoryID: this.categoryID, | |||||
currentPage: this.currentPage, | |||||
pageSize: this.pageSize | |||||
}) | |||||
.then(res => { | |||||
this.product = res.data.Product; | |||||
this.total = res.data.total; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
// 通过搜索条件向后端请求商品数据 | |||||
getProductBySearch() { | |||||
this.$axios | |||||
.post("/api/product/getProductBySearch", { | |||||
search: this.search, | |||||
currentPage: this.currentPage, | |||||
pageSize: this.pageSize | |||||
}) | |||||
.then(res => { | |||||
this.product = res.data.Product; | |||||
this.total = res.data.total; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.goods { | |||||
background-color: #f5f5f5; | |||||
} | |||||
/* 面包屑CSS */ | |||||
.el-tabs--card .el-tabs__header { | |||||
border-bottom: none; | |||||
} | |||||
.goods .breadcrumb { | |||||
height: 50px; | |||||
background-color: white; | |||||
} | |||||
.goods .breadcrumb .el-breadcrumb { | |||||
width: 1225px; | |||||
line-height: 30px; | |||||
font-size: 16px; | |||||
margin: 0 auto; | |||||
} | |||||
/* 面包屑CSS END */ | |||||
/* 分类标签CSS */ | |||||
.goods .nav { | |||||
background-color: white; | |||||
} | |||||
.goods .nav .product-nav { | |||||
width: 1225px; | |||||
height: 40px; | |||||
line-height: 40px; | |||||
margin: 0 auto; | |||||
} | |||||
.nav .product-nav .title { | |||||
width: 50px; | |||||
font-size: 16px; | |||||
font-weight: 700; | |||||
float: left; | |||||
} | |||||
/* 分类标签CSS END */ | |||||
/* 主要内容区CSS */ | |||||
.goods .main { | |||||
margin: 0 auto; | |||||
max-width: 1225px; | |||||
} | |||||
.goods .main .list { | |||||
min-height: 650px; | |||||
padding-top: 14.5px; | |||||
margin-left: -13.7px; | |||||
overflow: auto; | |||||
} | |||||
.goods .main .pagination { | |||||
height: 50px; | |||||
text-align: center; | |||||
} | |||||
.goods .main .none-product { | |||||
color: #333; | |||||
margin-left: 13.7px; | |||||
} | |||||
/* 主要内容区CSS END */ | |||||
</style> |
@@ -0,0 +1,214 @@ | |||||
<!-- | |||||
* @Description: 首页组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 13:36:12 | |||||
--> | |||||
<template> | |||||
<div class="home" id="home" name="home"> | |||||
<!-- 轮播图 --> | |||||
<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> | |||||
<!-- 轮播图END --> | |||||
<div class="main-box"> | |||||
<div class="main"> | |||||
<!-- 手机商品展示区域 --> | |||||
<div class="phone"> | |||||
<div class="box-hd"> | |||||
<div class="title">手机</div> | |||||
</div> | |||||
<div class="box-bd"> | |||||
<div class="promo-list"> | |||||
<router-link to> | |||||
<img :src="$target +'public/imgs/phone/phone.png'" /> | |||||
</router-link> | |||||
</div> | |||||
<div class="list"> | |||||
<MyList :list="phoneList" :isMore="true"></MyList> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 手机商品展示区域END --> | |||||
<!-- 家电商品展示区域 --> | |||||
<div class="appliance" id="promo-menu"> | |||||
<div class="box-hd"> | |||||
<div class="title">家电</div> | |||||
<div class="more" id="more"> | |||||
<MyMenu :val="2" @fromChild="getChildMsg"> | |||||
<span slot="1">热门</span> | |||||
<span slot="2">电视影音</span> | |||||
</MyMenu> | |||||
</div> | |||||
</div> | |||||
<div class="box-bd"> | |||||
<div class="promo-list"> | |||||
<ul> | |||||
<li> | |||||
<img :src="$target +'public/imgs/appliance/appliance-promo1.png'" /> | |||||
</li> | |||||
<li> | |||||
<img :src="$target +'public/imgs/appliance/appliance-promo2.png'" /> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
<div class="list"> | |||||
<MyList :list="applianceList" :isMore="true"></MyList> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 家电商品展示区域END --> | |||||
<!-- 配件商品展示区域 --> | |||||
<div class="accessory" id="promo-menu"> | |||||
<div class="box-hd"> | |||||
<div class="title">配件</div> | |||||
<div class="more" id="more"> | |||||
<MyMenu :val="3" @fromChild="getChildMsg2"> | |||||
<span slot="1">热门</span> | |||||
<span slot="2">保护套</span> | |||||
<span slot="3">充电器</span> | |||||
</MyMenu> | |||||
</div> | |||||
</div> | |||||
<div class="box-bd"> | |||||
<div class="promo-list"> | |||||
<ul> | |||||
<li> | |||||
<img :src="$target +'public/imgs/accessory/accessory-promo1.png'" alt /> | |||||
</li> | |||||
<li> | |||||
<img :src="$target +'public/imgs/accessory/accessory-promo2.png'" alt /> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
<div class="list"> | |||||
<MyList :list="accessoryList" :isMore="true"></MyList> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 配件商品展示区域END --> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
carousel: "", // 轮播图数据 | |||||
phoneList: "", // 手机商品列表 | |||||
miTvList: "", // 小米电视商品列表 | |||||
applianceList: "", // 家电商品列表 | |||||
applianceHotList: "", //热门家电商品列表 | |||||
accessoryList: "", //配件商品列表 | |||||
accessoryHotList: "", //热门配件商品列表 | |||||
protectingShellList: "", // 保护套商品列表 | |||||
chargerList: "", //充电器商品列表 | |||||
applianceActive: 1, // 家电当前选中的商品分类 | |||||
accessoryActive: 1 // 配件当前选中的商品分类 | |||||
}; | |||||
}, | |||||
watch: { | |||||
// 家电当前选中的商品分类,响应不同的商品数据 | |||||
applianceActive: function(val) { | |||||
// 页面初始化的时候把applianceHotList(热门家电商品列表)直接赋值给applianceList(家电商品列表) | |||||
// 所以在切换商品列表时判断applianceHotList是否为空,为空则是第一次切换,把applianceList赋值给applianceHotList | |||||
if (this.applianceHotList == "") { | |||||
this.applianceHotList = this.applianceList; | |||||
} | |||||
if (val == 1) { | |||||
// 1为热门商品 | |||||
this.applianceList = this.applianceHotList; | |||||
return; | |||||
} | |||||
if (val == 2) { | |||||
// 2为电视商品 | |||||
this.applianceList = this.miTvList; | |||||
return; | |||||
} | |||||
}, | |||||
accessoryActive: function(val) { | |||||
// 页面初始化的时候把accessoryHotList(热门配件商品列表)直接赋值给accessoryList(配件商品列表) | |||||
// 所以在切换商品列表时判断accessoryHotList是否为空,为空则是第一次切换,把accessoryList赋值给accessoryHotList | |||||
if (this.accessoryHotList == "") { | |||||
this.accessoryHotList = this.accessoryList; | |||||
} | |||||
if (val == 1) { | |||||
// 1为热门商品 | |||||
this.accessoryList = this.accessoryHotList; | |||||
return; | |||||
} | |||||
if (val == 2) { | |||||
// 2为保护套商品 | |||||
this.accessoryList = this.protectingShellList; | |||||
return; | |||||
} | |||||
if (val == 3) { | |||||
//3 为充电器商品 | |||||
this.accessoryList = this.chargerList; | |||||
return; | |||||
} | |||||
} | |||||
}, | |||||
created() { | |||||
// 获取轮播图数据 | |||||
this.$axios | |||||
.post("/api/resources/carousel", {}) | |||||
.then(res => { | |||||
this.carousel = res.data.carousel; | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
// 获取各类商品数据 | |||||
this.getPromo("手机", "phoneList"); | |||||
this.getPromo("电视机", "miTvList"); | |||||
this.getPromo("保护套", "protectingShellList"); | |||||
this.getPromo("充电器", "chargerList"); | |||||
this.getPromo( | |||||
["电视机", "空调", "洗衣机"], | |||||
"applianceList", | |||||
"/api/product/getHotProduct" | |||||
); | |||||
this.getPromo( | |||||
["保护套", "保护膜", "充电器", "充电宝"], | |||||
"accessoryList", | |||||
"/api/product/getHotProduct" | |||||
); | |||||
}, | |||||
methods: { | |||||
// 获取家电模块子组件传过来的数据 | |||||
getChildMsg(val) { | |||||
this.applianceActive = val; | |||||
}, | |||||
// 获取配件模块子组件传过来的数据 | |||||
getChildMsg2(val) { | |||||
this.accessoryActive = val; | |||||
}, | |||||
// 获取各类商品数据方法封装 | |||||
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); | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
@import "../assets/css/index.css"; | |||||
</style> |
@@ -0,0 +1,290 @@ | |||||
<!-- | |||||
* @Description: 我的订单页面组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-20 17:21:54 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 13:36:27 | |||||
--> | |||||
<template> | |||||
<div class="order"> | |||||
<!-- 我的订单头部 --> | |||||
<div class="order-header"> | |||||
<div class="order-header-content"> | |||||
<p> | |||||
<i class="el-icon-s-order" style="font-size: 30px;color: #ff6700;"></i> | |||||
我的订单 | |||||
</p> | |||||
</div> | |||||
</div> | |||||
<!-- 我的订单头部END --> | |||||
<!-- 我的订单主要内容 --> | |||||
<div class="order-content" v-if="orders.length>0"> | |||||
<div class="content" v-for="(item,index) in orders" :key="index"> | |||||
<ul> | |||||
<!-- 我的订单表头 --> | |||||
<li class="order-info"> | |||||
<div class="order-id">订单编号: {{item[0].order_id}}</div> | |||||
<div class="order-time">订单时间: {{item[0].order_time | dateFormat}}</div> | |||||
</li> | |||||
<li class="header"> | |||||
<div class="pro-img"></div> | |||||
<div class="pro-name">商品名称</div> | |||||
<div class="pro-price">单价</div> | |||||
<div class="pro-num">数量</div> | |||||
<div class="pro-total">小计</div> | |||||
</li> | |||||
<!-- 我的订单表头END --> | |||||
<!-- 订单列表 --> | |||||
<li class="product-list" v-for="(product,i) in item" :key="i"> | |||||
<div class="pro-img"> | |||||
<router-link :to="{ path: '/goods/details', query: {productID:product.product_id} }"> | |||||
<img :src="$target + product.product_picture" /> | |||||
</router-link> | |||||
</div> | |||||
<div class="pro-name"> | |||||
<router-link | |||||
:to="{ path: '/goods/details', query: {productID:product.product_id} }" | |||||
>{{product.product_name}}</router-link> | |||||
</div> | |||||
<div class="pro-price">{{product.product_price}}元</div> | |||||
<div class="pro-num">{{product.product_num}}</div> | |||||
<div class="pro-total pro-total-in">{{product.product_price*product.product_num}}元</div> | |||||
</li> | |||||
</ul> | |||||
<div class="order-bar"> | |||||
<div class="order-bar-left"> | |||||
<span class="order-total"> | |||||
共 | |||||
<span class="order-total-num">{{total[index].totalNum}}</span> 件商品 | |||||
</span> | |||||
</div> | |||||
<div class="order-bar-right"> | |||||
<span> | |||||
<span class="total-price-title">合计:</span> | |||||
<span class="total-price">{{total[index].totalPrice}}元</span> | |||||
</span> | |||||
</div> | |||||
<!-- 订单列表END --> | |||||
</div> | |||||
</div> | |||||
<div style="margin-top:-40px;"></div> | |||||
</div> | |||||
<!-- 我的订单主要内容END --> | |||||
<!-- 订单为空的时候显示的内容 --> | |||||
<div v-else class="order-empty"> | |||||
<div class="empty"> | |||||
<h2>您的订单还是空的!</h2> | |||||
<p>快去购物吧!</p> | |||||
</div> | |||||
</div> | |||||
<!-- 订单为空的时候显示的内容END --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
orders: [], // 订单列表 | |||||
total: [] // 每个订单的商品数量及总价列表 | |||||
}; | |||||
}, | |||||
activated() { | |||||
// 获取订单数据 | |||||
this.$axios | |||||
.post("/api/user/order/getOrder", { | |||||
user_id: this.$store.getters.getUser.user_id | |||||
}) | |||||
.then(res => { | |||||
if (res.data.code === "001") { | |||||
this.orders = res.data.orders; | |||||
} else { | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
watch: { | |||||
// 通过订单信息,计算出每个订单的商品数量及总价 | |||||
orders: function(val) { | |||||
let total = []; | |||||
for (let i = 0; i < val.length; i++) { | |||||
const element = val[i]; | |||||
let totalNum = 0; | |||||
let totalPrice = 0; | |||||
for (let j = 0; j < element.length; j++) { | |||||
const temp = element[j]; | |||||
totalNum += temp.product_num; | |||||
totalPrice += temp.product_price * temp.product_num; | |||||
} | |||||
total.push({ totalNum, totalPrice }); | |||||
} | |||||
this.total = total; | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.order { | |||||
background-color: #f5f5f5; | |||||
padding-bottom: 20px; | |||||
} | |||||
/* 我的订单头部CSS */ | |||||
.order .order-header { | |||||
height: 64px; | |||||
border-bottom: 2px solid #ff6700; | |||||
background-color: #fff; | |||||
margin-bottom: 20px; | |||||
} | |||||
.order .order-header .order-header-content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.order .order-header p { | |||||
font-size: 28px; | |||||
line-height: 58px; | |||||
float: left; | |||||
font-weight: normal; | |||||
color: #424242; | |||||
} | |||||
/* 我的订单头部CSS END */ | |||||
.order .content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
background-color: #fff; | |||||
margin-bottom: 50px; | |||||
} | |||||
.order .content ul { | |||||
background-color: #fff; | |||||
color: #424242; | |||||
line-height: 85px; | |||||
} | |||||
/* 我的订单表头CSS */ | |||||
.order .content ul .order-info { | |||||
height: 60px; | |||||
line-height: 60px; | |||||
padding: 0 26px; | |||||
color: #424242; | |||||
border-bottom: 1px solid #ff6700; | |||||
} | |||||
.order .content ul .order-info .order-id { | |||||
float: left; | |||||
color: #ff6700; | |||||
} | |||||
.order .content ul .order-info .order-time { | |||||
float: right; | |||||
} | |||||
.order .content ul .header { | |||||
height: 85px; | |||||
padding-right: 26px; | |||||
color: #424242; | |||||
} | |||||
/* 我的订单表头CSS END */ | |||||
/* 订单列表CSS */ | |||||
.order .content ul .product-list { | |||||
height: 85px; | |||||
padding: 15px 26px 15px 0; | |||||
border-top: 1px solid #e0e0e0; | |||||
} | |||||
.order .content ul .pro-img { | |||||
float: left; | |||||
height: 85px; | |||||
width: 120px; | |||||
padding-left: 80px; | |||||
} | |||||
.order .content ul .pro-img img { | |||||
height: 80px; | |||||
width: 80px; | |||||
} | |||||
.order .content ul .pro-name { | |||||
float: left; | |||||
width: 380px; | |||||
} | |||||
.order .content ul .pro-name a { | |||||
color: #424242; | |||||
} | |||||
.order .content ul .pro-name a:hover { | |||||
color: #ff6700; | |||||
} | |||||
.order .content ul .pro-price { | |||||
float: left; | |||||
width: 160px; | |||||
padding-right: 18px; | |||||
text-align: center; | |||||
} | |||||
.order .content ul .pro-num { | |||||
float: left; | |||||
width: 190px; | |||||
text-align: center; | |||||
} | |||||
.order .content ul .pro-total { | |||||
float: left; | |||||
width: 160px; | |||||
padding-right: 81px; | |||||
text-align: right; | |||||
} | |||||
.order .content ul .pro-total-in { | |||||
color: #ff6700; | |||||
} | |||||
.order .order-bar { | |||||
width: 1185px; | |||||
padding: 0 20px; | |||||
border-top: 1px solid #ff6700; | |||||
height: 50px; | |||||
line-height: 50px; | |||||
background-color: #fff; | |||||
} | |||||
.order .order-bar .order-bar-left { | |||||
float: left; | |||||
} | |||||
.order .order-bar .order-bar-left .order-total { | |||||
color: #757575; | |||||
} | |||||
.order .order-bar .order-bar-left .order-total-num { | |||||
color: #ff6700; | |||||
} | |||||
.order .order-bar .order-bar-right { | |||||
float: right; | |||||
} | |||||
.order .order-bar .order-bar-right .total-price-title { | |||||
color: #ff6700; | |||||
font-size: 14px; | |||||
} | |||||
.order .order-bar .order-bar-right .total-price { | |||||
color: #ff6700; | |||||
font-size: 30px; | |||||
} | |||||
/* 订单列表CSS END */ | |||||
/* 订单为空的时候显示的内容CSS */ | |||||
.order .order-empty { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.order .order-empty .empty { | |||||
height: 300px; | |||||
padding: 0 0 130px 558px; | |||||
margin: 65px 0 0; | |||||
background: url(../assets/imgs/cart-empty.png) no-repeat 124px 0; | |||||
color: #b0b0b0; | |||||
overflow: hidden; | |||||
} | |||||
.order .order-empty .empty h2 { | |||||
margin: 70px 0 15px; | |||||
font-size: 36px; | |||||
} | |||||
.order .order-empty .empty p { | |||||
margin: 0 0 20px; | |||||
font-size: 20px; | |||||
} | |||||
/* 订单为空的时候显示的内容CSS END */ | |||||
</style> |
@@ -0,0 +1,411 @@ | |||||
<!-- | |||||
* @Description: 我的购物车页面组件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-20 01:55:47 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2020-02-27 13:36:42 | |||||
--> | |||||
<template> | |||||
<div class="shoppingCart"> | |||||
<!-- 购物车头部 --> | |||||
<div class="cart-header"> | |||||
<div class="cart-header-content"> | |||||
<p> | |||||
<i class="el-icon-shopping-cart-full" style="color:#ff6700; font-weight: 600;"></i> | |||||
我的购物车 | |||||
</p> | |||||
<span>温馨提示:产品是否购买成功,以最终下单为准哦,请尽快结算</span> | |||||
</div> | |||||
</div> | |||||
<!-- 购物车头部END --> | |||||
<!-- 购物车主要内容区 --> | |||||
<div class="content" v-if="getShoppingCart.length>0"> | |||||
<ul> | |||||
<!-- 购物车表头 --> | |||||
<li class="header"> | |||||
<div class="pro-check"> | |||||
<el-checkbox v-model="isAllCheck">全选</el-checkbox> | |||||
</div> | |||||
<div class="pro-img"></div> | |||||
<div class="pro-name">商品名称</div> | |||||
<div class="pro-price">单价</div> | |||||
<div class="pro-num">数量</div> | |||||
<div class="pro-total">小计</div> | |||||
<div class="pro-action">操作</div> | |||||
</li> | |||||
<!-- 购物车表头END --> | |||||
<!-- 购物车列表 --> | |||||
<li class="product-list" v-for="(item,index) in getShoppingCart" :key="item.id"> | |||||
<div class="pro-check"> | |||||
<el-checkbox :value="item.check" @change="checkChange($event,index)"></el-checkbox> | |||||
</div> | |||||
<div class="pro-img"> | |||||
<router-link :to="{ path: '/goods/details', query: {productID:item.productID} }"> | |||||
<img :src="$target + item.productImg" /> | |||||
</router-link> | |||||
</div> | |||||
<div class="pro-name"> | |||||
<router-link | |||||
:to="{ path: '/goods/details', query: {productID:item.productID} }" | |||||
>{{item.productName}}</router-link> | |||||
</div> | |||||
<div class="pro-price">{{item.price}}元</div> | |||||
<div class="pro-num"> | |||||
<el-input-number | |||||
size="small" | |||||
:value="item.num" | |||||
@change="handleChange($event,index,item.productID)" | |||||
:min="1" | |||||
:max="item.maxNum" | |||||
></el-input-number> | |||||
</div> | |||||
<div class="pro-total pro-total-in">{{item.price*item.num}}元</div> | |||||
<div class="pro-action"> | |||||
<el-popover placement="right"> | |||||
<p>确定删除吗?</p> | |||||
<div style="text-align: right; margin: 10px 0 0"> | |||||
<el-button | |||||
type="primary" | |||||
size="mini" | |||||
@click="deleteItem($event,item.id,item.productID)" | |||||
>确定</el-button> | |||||
</div> | |||||
<i class="el-icon-error" slot="reference" style="font-size: 18px;"></i> | |||||
</el-popover> | |||||
</div> | |||||
</li> | |||||
<!-- 购物车列表END --> | |||||
</ul> | |||||
<div style="height:20px;background-color: #f5f5f5"></div> | |||||
<!-- 购物车底部导航条 --> | |||||
<div class="cart-bar"> | |||||
<div class="cart-bar-left"> | |||||
<span> | |||||
<router-link to="/goods">继续购物</router-link> | |||||
</span> | |||||
<span class="sep">|</span> | |||||
<span class="cart-total"> | |||||
共 | |||||
<span class="cart-total-num">{{getNum}}</span> 件商品,已选择 | |||||
<span class="cart-total-num">{{getCheckNum}}</span> 件 | |||||
</span> | |||||
</div> | |||||
<div class="cart-bar-right"> | |||||
<span> | |||||
<span class="total-price-title">合计:</span> | |||||
<span class="total-price">{{getTotalPrice}}元</span> | |||||
</span> | |||||
<router-link :to="getCheckNum > 0 ? '/confirmOrder' : ''"> | |||||
<div :class="getCheckNum > 0 ? 'btn-primary' : 'btn-primary-disabled'">去结算</div> | |||||
</router-link> | |||||
</div> | |||||
</div> | |||||
<!-- 购物车底部导航条END --> | |||||
</div> | |||||
<!-- 购物车主要内容区END --> | |||||
<!-- 购物车为空的时候显示的内容 --> | |||||
<div v-else class="cart-empty"> | |||||
<div class="empty"> | |||||
<h2>您的购物车还是空的!</h2> | |||||
<p>快去购物吧!</p> | |||||
</div> | |||||
</div> | |||||
<!-- 购物车为空的时候显示的内容END --> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapActions } from "vuex"; | |||||
import { mapGetters } from "vuex"; | |||||
export default { | |||||
data() { | |||||
return {}; | |||||
}, | |||||
methods: { | |||||
...mapActions(["updateShoppingCart", "deleteShoppingCart", "checkAll"]), | |||||
// 修改商品数量的时候调用该函数 | |||||
handleChange(currentValue, key, productID) { | |||||
// 当修改数量时,默认勾选 | |||||
this.updateShoppingCart({ key: key, prop: "check", val: true }); | |||||
// 向后端发起更新购物车的数据库信息请求 | |||||
this.$axios | |||||
.post("/api/user/shoppingCart/updateShoppingCart", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
product_id: productID, | |||||
num: currentValue | |||||
}) | |||||
.then(res => { | |||||
switch (res.data.code) { | |||||
case "001": | |||||
// “001”代表更新成功 | |||||
// 更新vuex状态 | |||||
this.updateShoppingCart({ | |||||
key: key, | |||||
prop: "num", | |||||
val: currentValue | |||||
}); | |||||
// 提示更新成功信息 | |||||
this.notifySucceed(res.data.msg); | |||||
break; | |||||
default: | |||||
// 提示更新失败信息 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
}, | |||||
checkChange(val, key) { | |||||
// 更新vuex中购物车商品是否勾选的状态 | |||||
this.updateShoppingCart({ key: key, prop: "check", val: val }); | |||||
}, | |||||
// 向后端发起删除购物车的数据库信息请求 | |||||
deleteItem(e, id, productID) { | |||||
this.$axios | |||||
.post("/api/user/shoppingCart/deleteShoppingCart", { | |||||
user_id: this.$store.getters.getUser.user_id, | |||||
product_id: productID | |||||
}) | |||||
.then(res => { | |||||
switch (res.data.code) { | |||||
case "001": | |||||
// “001” 删除成功 | |||||
// 更新vuex状态 | |||||
this.deleteShoppingCart(id); | |||||
// 提示删除成功信息 | |||||
this.notifySucceed(res.data.msg); | |||||
break; | |||||
default: | |||||
// 提示删除失败信息 | |||||
this.notifyError(res.data.msg); | |||||
} | |||||
}) | |||||
.catch(err => { | |||||
return Promise.reject(err); | |||||
}); | |||||
} | |||||
}, | |||||
computed: { | |||||
...mapGetters([ | |||||
"getShoppingCart", | |||||
"getCheckNum", | |||||
"getTotalPrice", | |||||
"getNum" | |||||
]), | |||||
isAllCheck: { | |||||
get() { | |||||
return this.$store.getters.getIsAllCheck; | |||||
}, | |||||
set(val) { | |||||
this.checkAll(val); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
.shoppingCart { | |||||
background-color: #f5f5f5; | |||||
padding-bottom: 20px; | |||||
} | |||||
/* 购物车头部CSS */ | |||||
.shoppingCart .cart-header { | |||||
height: 64px; | |||||
border-bottom: 2px solid #ff6700; | |||||
background-color: #fff; | |||||
margin-bottom: 20px; | |||||
} | |||||
.shoppingCart .cart-header .cart-header-content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.shoppingCart .cart-header p { | |||||
font-size: 28px; | |||||
line-height: 58px; | |||||
float: left; | |||||
font-weight: normal; | |||||
color: #424242; | |||||
} | |||||
.shoppingCart .cart-header span { | |||||
color: #757575; | |||||
font-size: 12px; | |||||
float: left; | |||||
height: 20px; | |||||
line-height: 20px; | |||||
margin-top: 30px; | |||||
margin-left: 15px; | |||||
} | |||||
/* 购物车头部CSS END */ | |||||
/* 购物车主要内容区CSS */ | |||||
.shoppingCart .content { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
background-color: #fff; | |||||
} | |||||
.shoppingCart .content ul { | |||||
background-color: #fff; | |||||
color: #424242; | |||||
line-height: 85px; | |||||
} | |||||
/* 购物车表头及CSS */ | |||||
.shoppingCart .content ul .header { | |||||
height: 85px; | |||||
padding-right: 26px; | |||||
color: #424242; | |||||
} | |||||
.shoppingCart .content ul .product-list { | |||||
height: 85px; | |||||
padding: 15px 26px 15px 0; | |||||
border-top: 1px solid #e0e0e0; | |||||
} | |||||
.shoppingCart .content ul .pro-check { | |||||
float: left; | |||||
height: 85px; | |||||
width: 110px; | |||||
} | |||||
.shoppingCart .content ul .pro-check .el-checkbox { | |||||
font-size: 16px; | |||||
margin-left: 24px; | |||||
} | |||||
.shoppingCart .content ul .pro-img { | |||||
float: left; | |||||
height: 85px; | |||||
width: 120px; | |||||
} | |||||
.shoppingCart .content ul .pro-img img { | |||||
height: 80px; | |||||
width: 80px; | |||||
} | |||||
.shoppingCart .content ul .pro-name { | |||||
float: left; | |||||
width: 380px; | |||||
} | |||||
.shoppingCart .content ul .pro-name a { | |||||
color: #424242; | |||||
} | |||||
.shoppingCart .content ul .pro-name a:hover { | |||||
color: #ff6700; | |||||
} | |||||
.shoppingCart .content ul .pro-price { | |||||
float: left; | |||||
width: 140px; | |||||
padding-right: 18px; | |||||
text-align: center; | |||||
} | |||||
.shoppingCart .content ul .pro-num { | |||||
float: left; | |||||
width: 150px; | |||||
text-align: center; | |||||
} | |||||
.shoppingCart .content ul .pro-total { | |||||
float: left; | |||||
width: 120px; | |||||
padding-right: 81px; | |||||
text-align: right; | |||||
} | |||||
.shoppingCart .content ul .pro-total-in { | |||||
color: #ff6700; | |||||
} | |||||
.shoppingCart .content ul .pro-action { | |||||
float: left; | |||||
width: 80px; | |||||
text-align: center; | |||||
} | |||||
.shoppingCart .content ul .pro-action i:hover { | |||||
color: #ff6700; | |||||
} | |||||
/* 购物车表头及CSS END */ | |||||
/* 购物车底部导航条CSS */ | |||||
.shoppingCart .cart-bar { | |||||
width: 1225px; | |||||
height: 50px; | |||||
line-height: 50px; | |||||
background-color: #fff; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left { | |||||
float: left; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left a { | |||||
line-height: 50px; | |||||
margin-left: 32px; | |||||
color: #757575; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left a:hover { | |||||
color: #ff6700; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left .sep { | |||||
color: #eee; | |||||
margin: 0 20px; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left .cart-total { | |||||
color: #757575; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-left .cart-total-num { | |||||
color: #ff6700; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right { | |||||
float: right; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right .total-price-title { | |||||
color: #ff6700; | |||||
font-size: 14px; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right .total-price { | |||||
color: #ff6700; | |||||
font-size: 30px; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right .btn-primary { | |||||
float: right; | |||||
width: 200px; | |||||
text-align: center; | |||||
font-size: 18px; | |||||
margin-left: 50px; | |||||
background: #ff6700; | |||||
color: #fff; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right .btn-primary-disabled { | |||||
float: right; | |||||
width: 200px; | |||||
text-align: center; | |||||
font-size: 18px; | |||||
margin-left: 50px; | |||||
background: #e0e0e0; | |||||
color: #b0b0b0; | |||||
} | |||||
.shoppingCart .cart-bar .cart-bar-right .btn-primary:hover { | |||||
background-color: #f25807; | |||||
} | |||||
/* 购物车底部导航条CSS END */ | |||||
/* 购物车主要内容区CSS END */ | |||||
/* 购物车为空的时候显示的内容CSS */ | |||||
.shoppingCart .cart-empty { | |||||
width: 1225px; | |||||
margin: 0 auto; | |||||
} | |||||
.shoppingCart .cart-empty .empty { | |||||
height: 300px; | |||||
padding: 0 0 130px 558px; | |||||
margin: 65px 0 0; | |||||
background: url(../assets/imgs/cart-empty.png) no-repeat 124px 0; | |||||
color: #b0b0b0; | |||||
overflow: hidden; | |||||
} | |||||
.shoppingCart .cart-empty .empty h2 { | |||||
margin: 70px 0 15px; | |||||
font-size: 36px; | |||||
} | |||||
.shoppingCart .cart-empty .empty p { | |||||
margin: 0 0 20px; | |||||
font-size: 20px; | |||||
} | |||||
/* 购物车为空的时候显示的内容CSS END */ | |||||
</style> |
@@ -0,0 +1,23 @@ | |||||
/* | |||||
* @Description: 配置文件 | |||||
* @Author: hai-27 | |||||
* @Date: 2020-02-07 16:23:00 | |||||
* @LastEditors: hai-27 | |||||
* @LastEditTime: 2021-03-03 22:32:57 | |||||
*/ | |||||
module.exports = { | |||||
publicPath: './', | |||||
devServer: { | |||||
open: true, | |||||
proxy: { | |||||
'/api': { | |||||
target: 'http://localhost:3000/', // 本地后端地址 | |||||
// target: 'http://101.132.181.9:3000/', // 线上后端地址 | |||||
changeOrigin: true, //允许跨域 | |||||
pathRewrite: { | |||||
'^/api': '' | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |