项目介绍
《叩丁狼积分商城》是一个使用vue全家桶构建的PC端SPA商城,主要用于给学员将子级在叩丁狼的积分兑换成商品。
一、项目参考地址【熟悉】
真实项目参考地址:
实战项目参考地址:
二、项目UI设计稿及图片资源【熟悉】
1、UI设计稿
链接: 提取码:yyds
2、静态图片资源
链接: 提取码:9yos
3、iconfont链接
1、在项目中全局的css引入以下链接:
2、具体图标名称:
icon-yduifuxuankuangxuanzhong
3、引用方式
复制 <i class="iconfont icon-loading"></i>
三、PS安装【选装】
这里为电脑没有预装ps的同学提供ps安装包
下载打开压缩包, 解压密码为:123456
四、蓝湖【重点】
蓝湖产品设计协作平台是一个服务于产品经理、设计师、工程师的在线协作平台,无缝连接产品、设计、研发流程,旨在降低沟通成本,缩短开发周期,提高工作效率,帮助企业建立科学的工作环境,提升企业的开发效率。
1、注册登录与插件
下载完成即可安装,安装后重启ps,如果看到:
就代表你已成功安装蓝湖插件。此时,顺便登录账号。
2、上传UI设计稿
首先,打开一张psd设计图,然后选择刚刚创建的团队与项目,选择 1倍像素 的web端设计稿:
点击 上传全部画板
即可。
后续每开发一个页面,可自行按照上述流程将需要用到的设计稿上传,无需一次性上传所有设计图。
五、项目创建【熟悉】
执行 vue create 项目名称
:
复制 # 这里 store-pc 是我的项目名
vue create store-pc
按照没有eslint的配置,选择 vue 2
、less
、vuex
和 router
:
创建完成后,cd到项目中。
六、仓库创建【熟悉】
点击 创建
即可。
由于我们已经有本地项目,直接在本地项目的命令行中执行:
复制 git add .
git commit -m '项目创建'
# origin后面要改成你的仓库地址
git remote add origin git@gitee.com:codesohigh/store-pc.git
git push -u origin master
完成提交。刷新仓库页面,如果看到仓库已有文件:
表示你已提交成功。
七、默认样式【熟悉】
1、清空默认样式
复制 npm install reset-css
# 或者:
yarn add reset-css
然后在项目入口文件 main.js
中引入:
2、定义默认样式
网页中有很多固定的颜色,我们可以抽离出来作为less公共变量。在 src
下,新建 total.less
:
复制 // 公共样式变量
@blue: #0A328E;
@orange: #FF5E0F;
@black: #333333;
// 公有版心
.banxin{
width: 1200px;
margin-left: auto;
margin-right: auto;
}
然后在需要使用这些变量的组件中的css最顶部引入:
复制 @import "../total.less";
八、配置@指向src【了解】
1、方案一
打开设置 - 首选项 - 搜索 Path Intellisense
- 打开 settings.json
,添加:
复制 "path-intellisense.mappings": {
"@": "${workspaceRoot}/src"
}
在项目 package.json
所在同级目录下创建文件 jsconfig.json
:
复制 {
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": [
"node_modules"
]
}
2、方案二
安装 path
:
复制 yarn add path
# 或者npm
npm i path -S
创建 vue.config.js
:
复制 const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set("@", resolve("src"))
.set("assets", resolve("src/assets"))
.set("components", resolve("src/components"))
.set("base", resolve("baseConfig"))
.set("public", resolve("public"));
},
}
九、项目静态资源【了解】
将老师提供的 assets
文件夹放到项目的 src
下。
十、登录滑动拼图验证【了解】
1、安装插件
复制 npm install --save vue-monoplasty-slide-verify
# yarn安装方式
yarn add vue-monoplasty-slide-verify
2、入口文件引入
复制 import SlideVerify from 'vue-monoplasty-slide-verify' // 拼图验证码
Vue.use(SlideVerify)
3、组件引用
复制 <template>
<slide-verify :l="42" :r="20" :w="362" :h="140" @success="onSuccess" @fail="onFail" @refresh="onRefresh" :style="{ width: '100%' }" class="slide-box" ref="slideBlock" :slider-text="msg"></slide-verify>
</template>
<script>
export default {
data() {
return {
msg: "向右滑动"
};
},
methods: {
// 拼图成功
onSuccess(times) {
let ms = (times / 1000).toFixed(1);
this.msg = "login success, 耗时 " + ms + "s";
},
// 拼图失败
onFail() {
this.onRefresh(); // 重新刷新拼图
},
// 拼图刷新
onRefresh() {
this.msg = "再试一次";
},
// 点击登录按钮
submitFn(formName) {
if (this.msg == "再试一次" || this.msg == "向右滑动") {
console.log("请滑动拼图");
} else {
console.log("开始登录");
}
},
},
};
</script>
<style lang="less" scoped>
/deep/.slide-box {
width: 100%;
position: relative;
box-sizing: border-box;
canvas {
position: absolute;
left: 0;
top: -140px;
display: none;
width: 100%;
box-sizing: border-box;
}
.slide-verify-block{
width: 85px;
height: 136px;
}
.slide-verify-refresh-icon {
top: -140px;
display: none;
}
&:hover {
canvas {
display: block;
}
.slide-verify-refresh-icon {
display: block;
}
}
}
</style>
十一、配置axios拦截器【重点】
1、安装 axios
与 qs
:
2、在 src
下创建 request>request.js+api.js
复制 // request.js
import axios from "axios";
let instance = axios.create({
baseURL: "http://192.168.113.249:8081/cms",
timeout: 5000
});
// http request 拦截器
instance.interceptors.request.use(
(config) => {
if (config.url === "/wechatUsers/PCLogin") {
config.headers["Content-Type"] = "application/x-www-form-urlencoded";
}
const token = sessionStorage.getItem("token");
if (token) {
// 判断是否存在token,如果存在的话,则每个http header都加上token
config.headers["x-auth-token"] = token; //请求头加上token
}
return config;
},
(err) => {
return Promise.reject(err);
}
);
// http response 拦截器
instance.interceptors.response.use(
(response) => {
return response.data;
},
//接口错误状态处理,也就是说无响应时的处理
(error) => {
return Promise.reject(error.response.status); // 返回接口返回的错误信息
}
);
export default instance
api.js
复制 import request from './request'
import qs from 'qs'
// 首页精品推荐数据请求
export const JingpinApi = () => request.get('/products/recommend')
// 微信登录(这个接口必须用qs对数据进行格式化)
export const WeixinLoginApi = (params) => request.post(`/wechatUsers/PCLogin`, qs.stringify(params))
十二、Vuex与提示【重点】
vuex中可以定义三个状态:
复制 import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 提示的内容
toastMsg: "",
// 提示的状态
toastStatus: false,
// 提示的类型(success,danger,info)
toastType: "success"
},
mutations: {
// 打开提示
openToast(state, payload){
state.toastStatus = true;
state.toastMsg = payload.msg;
state.toastType = payload.type;
},
// 关闭提示
closeToast(state){
state.toastStatus = false;
}
},
actions: {
// 2秒后关闭提示
closeToastFn(context){
setTimeout(()=>{
context.commit('closeToast');
}, 2000)
}
}
})
创建一个 Toast.vue
组件:
复制 <template>
<transition name="slide">
<div class="toast" v-if="$store.state.toastStatus">
<i
:class=" $store.state.toastType === 'success' ? 'iconfont icon-toast_chenggong' : $store.state.toastType === 'danger' ? 'iconfont icon-toast-shibai_huaban' : 'iconfont icon-toast-jinggao'"
:style="{ color: $store.state.toastType === 'success' ? 'green' : $store.state.toastType === 'danger' ? 'red' : 'orange'}"
></i>
<span>{{ $store.state.toastMsg }}</span>
</div>
</transition>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style lang="less" scoped>
@import "https://at.alicdn.com/t/font_2730880_lc6xeulwi7o.css";
.toast {
position: absolute;
left: 50%;
z-index: 10;
transform: translateX(-50%);
padding: 10px 20px;
max-width: 400px;
display: flex;
background: #fff;
box-shadow: 0 0 10px #000;
border-radius: 10px;
font-size: 18px;
box-sizing: border-box;
.iconfont {
margin-right: 10px;
font-size: 18px;
}
span {
flex: 1;
line-height: 1.2;
}
}
.slide-enter, .slide-leave-to{
top: -500px;
}
.slide-enter-active, .slide-leave-active{
transition: top 1s linear;
}
.slide-enter-to, .slide-leave{
top: 10px;
}
</style>
在任何一个组件想要触发这个Toast,需要:
复制 this.$store.commit("openToast", { msg: "你好世界", type: "success" });
setTimeout(() => {
this.$store.dispatch("closeToastFn");
}, 1500)
但是每次都这么触发太麻烦,我们用一个 toastFn.js
文件封装一个函数:
复制 // 打开与关闭toast
export function toastFn(_this, msg, type) {
_this.$store.commit("openToast", { msg, type });
setTimeout(() => {
_this.$store.dispatch("closeToastFn");
}, 1500)
}
如此,在需要调用的地方直接传参即可:
复制 toastFn(this, "你好世界", "success");
十三、PC微信登录【重点】
1、微信扫码布局与配置
想要实现在登录框中可以扫码登录:
在 public/index.html
的 head
中:
复制 <script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
在登录框中代码中添加一个div,这个div是用来存放微信二维码所在的iframe的:
复制 <div id="weixin"></div>
在 api.js
中:
在切换到 微信扫码登录
的事件中:
复制 // 点击了微信扫码登录
weixinClickFn() {
...
let _this = this;
new WxLogin({
id: "weixin",
appid: "wx67cfaf9e3ad31a0d", // 这个appid要填死
scope: "snsapi_login",
// 扫码成功后重定向的接口
redirect_uri: "https://sc.wolfcode.cn/cms/wechatUsers/shop/PC",
// state填写编码后的url
state: encodeURIComponent(window.btoa(process.env.VUE_APP_STATE_URL + _this.$route.path)),
// 调用样式文件
href: "",
});
},
* 环境变量
上面的process.env.VUE_APP_STATE_URL是环境变量。书写方式:
在项目根目录新建 .env.prod
与 .env.dev
:
复制 # .env.dev
NODE_ENV=development
VUE_APP_BASE_URL=/
VUE_APP_STATE_URL=http://127.0.0.1:8080
# .env.prod
NODE_ENV = production
VUE_APP_BASE_URL = 'http://codesohigh.com/store-pc/#'
VUE_APP_STATE_URL = 'http://codesohigh.com/store-pc/#'
然后修改 package.json
中:
复制 {
"scripts": {
"serve": "vue-cli-service serve --mode dev",
"build": "vue-cli-service build --mode prod"
},
}
重跑项目即可。
当你还没写href的时候,会发现iframe样式是无法改变的,因此我们需要借助 node+css
来实现css转base64:
在 src
下新建 utils
文件夹,并且在其中新建: data-url.js
与 wxlogin.css
:
复制 /* wxlogin.css */
.impowerBox .title, .impowerBox .info{
display: none;
}
.impowerBox .qrcode{
margin-top: 20px;
}
复制 // data-url.js
var fs = require('fs');
// function to encode file data to base64 encoded string
function base64_encode(file) {
// read binary data
var bitmap = fs.readFileSync(file);
// convert binary data to base64 encoded string
return 'data:text/css;base64,'+new Buffer(bitmap).toString('base64');
}
console.log(base64_encode('./wxlogin.css'))
然后控制台运行:
复制 cd src/utils/
node data-url.js
得到一段base64转码字符串:
复制 data:text/css;base64,LmltcG93ZXJCb3ggLnRpdGxlLCAuaW1wb3dlckJveCAuaW5mb3sNCiAgICBkaXNwbGF5OiBub25lOw0KfQ0KDQouaW1wb3dlckJveCAucXJjb2Rlew0KICAgIG1hcmdpbi10b3A6IDIwcHg7DQp9DQoNCg==
然后粘贴到 href
中。最终效果:
2、扫码得到code做登录
扫码跳转页面后,我们来到 Header.vue
组件。在 Header.vue
组件的 created
生命周期里,由于在地址栏上可以得到code:
此时要做判断,如果有code,则做请求获取token:
复制 created() {
let _this = this;
if (this.$route.query.code) {
// 存在code,说明扫码登录过,直接做请求
WeixinLoginApi({
code: this.$route.query.code,
})
.then((res) => {
if (res.code === 0) {
toastFn(_this, res.message, "success");
localStorage.setItem("x-auth-token", res["x-auth-token"]); // 存储token
} else {
toastFn(_this, res.message, "danger");
}
this.$router.push(this.$route.path); // 清除参数code
setTimeout(() => {
this.$router.go(0); // 刷新当前页
}, 2000);
})
.catch((err) => {
console.log(err);
});
} else {
// 没有code,说明可能未登录,也可能登录过已存好token,所以此时要判断token是否存在
...
}
},
但这样写有个问题,Header组件只会加载一次,如果用户退出登录,在别的页面登录,就无法正常登录了。
3、强制组件更新
由于具有以上描述的情况,我们希望只要路由的 query参数
发生变化,就强制更新一次 Header.vue
组件,这样就能重复触发它的 created
,方法如下:
复制 <!-- App.vue中 -->
<Header :key="componentKey" />
<script>
export default {
data() {
return {
componentKey: 0
};
},
watch: {
"$route.query": {
handler(newVal, oldVal) {
let _this = this;
if (_this.$route.query.code) {
_this.componentKey++;
}
},
deep: true,
},
},
}
</script>
十三、获取用户信息【重点】
在 Header.vue
的 created
中:
复制 created() {
let _this = this;
if (this.$route.query.code) {
...
} else {
// 没有code,说明可能未登录,也可能登录过已存好token,所以此时要判断token是否存在
let token = localStorage.getItem("x-auth-token");
if (token) {
UserInfoApi().then((res) => {
console.log(res); // 获得用户信息
});
}
}
},
十四、手机号登录【重点】
手机号登录之前,需要判断三点:
1、手机号校验
新建一个 validate.js
文件:
复制 // 正则校验手机号
export function validateTelephone(value) {
let reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
return reg.test(value.trim());
}
在提交的事件中直接判断:
复制 // 点击登录按钮
submitFn() {
let result = validateTelephone(this.phoneNum);
if (!result) {
// 手机号错误
toastFn(this, "请填写正确手机号", "danger");
this.phoneRight = false; // 控制手机输入框是否有红色边框提示,false表示有提示
return;
}
},
2、验证码校验
验证码无法校验填写对了没有,只能判断是否有填写:
复制 // 点击登录按钮
submitFn() {
let result = validateTelephone(this.phoneNum);
// ...校验手机号的代码(省略)
if (this.code.trim() === "") {
// 验证码有误
toastFn(this, "验证码有误", "danger");
this.codeRight = false; // 控制验证码输入框是否有红色边框提示,false表示有提示
return;
}
},
3、检验是否完成拼图
复制 submitFn() {
// ...手机与验证码校验代码(省略)
this.phoneRight = true; // 重新复原手机号输入框状态
this.codeRight = true; // 重新复原验证码输入框状态
if (this.msg == "再试一次" || this.msg == "向右滑动") {
// 拼图未完成
toastFn(this, "请完成拼图", "info");
} else {
// 拼图完成
console.log("开始登录");
}
},
4、登录请求
完成了以上的所有校验,就可以点击登录,先配置 api.js
:
复制 // 手机号登录
export const PhoneReginApi = (params) => request.post('/phoneRegin', qs.stringify(params))
在判断拼图完成之后:
复制 PhoneReginApi({
phone: _this.phoneNum,
verifyCode: _this.code,
})
.then((res) => {
if (res.code === 0) {
localStorage.setItem("x-auth-token", res["x-auth-token"]); // 存储token
toastFn(_this, res.message, "success");
} else {
toastFn(_this, res.message, "danger");
}
setTimeout(()=>{
this.$router.go(0); // 刷新当前页
}, 2000)
})
.catch((err) => {
console.log(err);
});
十五、商品页滚动加载【熟悉】
十六、用户中心与购物车界面套用
个人中心的界面:
1、用户中心
新建 views/person/Person.vue
:
复制 <template>
<div class="person_page">
<div class="person banxin">
<bread-crumb title="个人中心">首页</bread-crumb>
<main>
<aside>
<div
class="avatar"
:style="{ backgroundImage: `url(${userInfo.headImg})` }"
></div>
<div class="name">{{ userInfo.nickName }} <span @click="loginOutFn">[退出]</span></div>
<div class="title">
<img
src="../../assets/images/person/transaction.png"
width="20"
alt="交易管理"
/>
交易管理
</div>
<ul class="list">
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">
个人中心
</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">
我的订单
</li>
<li :class="/\/cart/g.test($route.path) ? 'active' : ''">购物车</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">
消息通知
</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">
积分明细
</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">
积分攻略
</li>
</ul>
<div class="title">
<img
src="../../assets/images/person/transaction.png"
width="20"
alt="交易管理"
/>
个人信息管理
</div>
<ul class="list">
<li>地址管理</li>
<li>账号安全</li>
</ul>
</aside>
<article><router-view></router-view></article>
</main>
</div>
</div>
</template>
<script>
import Breadcrumb from "@/components/products/Breadcrumb.vue";
import {toastFn} from '@/utils/toastFn'
export default {
data() {
return {
userInfo: JSON.parse(sessionStorage.getItem("userInfo")),
};
},
components: {
"bread-crumb": Breadcrumb,
},
methods: {
loginOutFn(){
localStorage.removeItem("x-auth-token");
sessionStorage.clear();
toastFn(this, "您已退出登录,即将返回首页", "success");
setTimeout(()=>{
this.$router.push('/home');
}, 2000)
}
}
};
</script>
<style lang="less" scoped>
@import "../../total.less";
.person_page {
background: #fff;
main {
border-top: 1px solid #e1e1e1;
padding: 28px 0 48px;
display: flex;
justify-content: space-between;
background: #fff;
aside {
width: 200px;
height: 740px;
background: #e7e7e7;
margin-right: 62px;
box-sizing: border-box;
padding: 30px 18px 0;
.avatar {
width: 100px;
height: 100px;
margin: auto;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.name {
text-align: center;
margin-top: 19px;
margin-bottom: 43px;
span {
text-decoration: underline;
color: #2a5df1;
}
}
.title {
font-size: 16px;
color: #333333;
display: flex;
align-items: center;
margin-bottom: 14px;
img {
margin-right: 6px;
}
}
.list {
li {
margin-bottom: 17px;
font-weight: 300;
color: #666666;
cursor: pointer;
&.active {
color: @blue;
font-weight: bold;
&::before {
width: 2px;
height: 14px;
background: @blue;
display: inline-block;
content: "";
margin-right: 10px;
}
}
}
}
}
article {
flex: 1;
padding: 20px 0 0 20px;
box-sizing: border-box;
background: #fff;
}
}
}
</style>
2、购物车
新建 views/person/Cart.vue
:
复制 <template>
<div class="cart_page">
<table>
<thead>
<tr>
<th style="width: 8%">
<i
:class="
totalSelect
? 'iconfont icon-yduifuxuankuangxuanzhong'
: 'iconfont icon-yduifuxuankuang'
"
></i>
</th>
<th style="width: 30%">礼品信息</th>
<th>兑换分数</th>
<th>数量</th>
<th>小计 (鸡腿)</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i
:class="
oneSelect
? 'iconfont icon-yduifuxuankuangxuanzhong'
: 'iconfont icon-yduifuxuankuang'
"
></i>
</td>
<td>
<section>
<img
width="84"
src="http://sc.wolfcode.cn/upload/images/product_images/20200615/41ddc8c8-bd4b-4f5c-ae68-474f9ed18eb7.png"
alt="列表图片"
/>
<div class="info">
<h5>叩丁狼定制T恤</h5>
<p>颜色、版本:XL</p>
</div>
</section>
</td>
<td>5000鸡腿</td>
<td>
<div class="step">
<span>-</span>
<input type="text" disabled v-model="stepNum" />
<span>+</span>
</div>
</td>
<td>5000鸡腿</td>
<td>
<span class="del">删除</span>
</td>
</tr>
</tbody>
</table>
<div class="total">总计:<span>0鸡腿</span></div>
<div class="submit">提交</div>
</div>
</template>
<script>
export default {
data() {
return {
stepNum: 1,
// 全选
totalSelect: false,
// 单选
oneSelect: true,
};
},
};
</script>
<style lang="less" scoped>
.cart_page {
background: #fff;
table {
width: 100%;
border: 1px solid #e6e6e6;
box-sizing: border-box;
color: #666;
border-collapse: collapse;
font-size: 14px;
thead {
background: #f2f2f2;
th {
padding: 19px 0;
.iconfont {
cursor: pointer;
}
.icon-yduifuxuankuangxuanzhong {
color: #0a328e;
}
}
}
tbody {
tr {
td {
vertical-align: middle;
text-align: center;
padding: 19px 0;
table-layout: fixed; // td的宽度固定,不随内容变化
.iconfont {
cursor: pointer;
}
.icon-yduifuxuankuangxuanzhong {
color: #0a328e;
}
section {
padding-left: 20px;
display: flex;
box-sizing: border-box;
img {
margin-right: 12px;
}
.info {
padding-top: 20px;
flex: 1;
overflow: hidden;
box-sizing: border-box;
text-align: left;
h5 {
overflow: hidden;
color: #333;
font-size: 18px;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 20px;
}
p {
color: #666;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.step {
width: 106px;
height: 32px;
margin: auto;
span {
float: left;
width: 30px;
height: 32px;
display: block;
border: solid 1px #d1d1d1;
font-size: 20px;
box-sizing: border-box;
font-weight: normal;
font-stretch: normal;
line-height: 30px;
letter-spacing: 0px;
color: #999999;
text-align: center;
cursor: pointer;
background: #fff;
}
input {
box-sizing: border-box;
width: 46px;
height: 32px;
float: left;
text-align: center;
font-size: 14px;
line-height: 23px;
letter-spacing: 0px;
color: #666666;
border: 0;
border-top: 1px solid #d1d1d1;
border-bottom: 1px solid #d1d1d1;
background: #fff;
}
}
.del {
border: 1px solid #ececec;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
&:hover {
color: #fff;
background: #0a328e;
}
}
}
}
}
}
.total {
padding: 30px 0;
text-align: right;
font-size: 22px;
span {
font-weight: bold;
color: #fd604d;
}
}
.submit {
width: 175px;
height: 40px;
text-align: center;
line-height: 40px;
font-family: SourceHanSansSC-Light;
font-size: 18px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 0px;
color: #ffffff;
cursor: pointer;
background-color: #0a328e;
float: right;
}
}
</style>
3、路由配置
复制 {
path: "/person",
name: "Person",
component: () => import(/* webpackChunkName: "person" */ "../views/person/Person.vue"),
children: [
{
path: "cart",
name: "Cart",
component: () => import(/* webpackChunkName: "cart" */ "../views/person/Cart.vue"),
},
],
},
4、导航修改
找到 Nav.vue
组件,修改个人中心的当前项判断:
复制 <div class="link">
<router-link :class="$route.path=='/home' ? 'active' : ''" to="/home">首页</router-link>
<router-link :class="$route.path=='/products' ? 'active' : ''" to="/products">全部商品</router-link>
<router-link :class="/\/person/g.test($route.path) ? 'active' : ''" to="/user">个人中心</router-link>
...
</div>
十七、开发小技巧
1、iconfont无法旋转?
iconfont是行内元素,无法添加 transform:rotate()
属性,需要把它转行内块才能旋转。
2、社交平台分享
使用方式:
在组件中引入:
复制 // ES6
import vshare from 'vshare'
//or require
var vshare = require('vshare')
components: {
vshare
}
然后使用:
复制 <vshare :vshareConfig="vshareConfig"></vshare>
data中定义 vshareConfig
:
复制 data () {
return {
// 分享功能的配置
vshareConfig: {
// 此处放分享列表(ID)
shareList: ["weixin", "qzone"],
//此处放置分享按钮设置
share: [{ bdSize: 24 }],
//此处放置浮窗分享设置
slide: false
},
}
}
3、路由监听
当你路由更新,当页面没刷新时,需要监听路由:
复制 watch: {
"$route.query.id": {
handler(newVal, oldVal){
if(newVal !== oldVal){
this.$router.go(0); // 刷新页面
}
}
}
},
4、正则表达式替换文本
假设你得到的字符串为str,其中包含html标签,标签中有img,想把img里的 upload
字符串替换为 https://sc.wolfcode.cn/upload
,可以如下操作:
复制 let newStr = str.replace(/upload/g, 'http://sc.wolfcode.cn/upload');
5、个人中心最基本界面
复制 <template>
<div class="person_page banxin">
<bread-crumb title="个人中心">首页</bread-crumb>
<main>
<aside>
<div class="avatar" :style="{ backgroundImage: `url(${userInfo.headImg})` }"></div>
<div class="name">{{ userInfo.nickName }} <span>[退出]</span></div>
<div class="title">
<img src="../../assets/images/person/transaction.png" width="20" alt="交易管理" />
交易管理
</div>
<ul class="list">
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">个人中心</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">我的订单</li>
<li :class="/\/cart/g.test($route.path) ? 'active' : ''">购物车</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">消息通知</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">积分明细</li>
<li :class="/\/person1/g.test($route.path) ? 'active' : ''">积分攻略</li>
</ul>
<div class="title">
<img src="../../assets/images/person/transaction.png" width="20" alt="交易管理" />
个人信息管理
</div>
<ul class="list">
<li>地址管理</li>
<li>账号安全</li>
</ul>
</aside>
<article><router-view></router-view></article>
</main>
</div>
</template>
<script>
import Breadcrumb from "@/components/products/Breadcrumb.vue";
export default {
data() {
return {
userInfo: JSON.parse(sessionStorage.getItem("userInfo")),
};
},
components: {
"bread-crumb": Breadcrumb,
},
};
</script>
<style lang="less" scoped>
@import "../../total.less";
main {
border-top: 1px solid #e1e1e1;
padding: 28px 0 48px;
display: flex;
justify-content: space-between;
aside {
width: 200px;
height: 740px;
background: #e7e7e7;
margin-right: 62px;
box-sizing: border-box;
padding: 30px 18px 0;
.avatar {
width: 100px;
height: 100px;
margin: auto;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.name {
text-align: center;
margin-top: 19px;
margin-bottom: 43px;
span {
text-decoration: underline;
color: #2a5df1;
}
}
.title {
font-size: 16px;
color: #333333;
display: flex;
align-items: center;
margin-bottom: 14px;
img {
margin-right: 6px;
}
}
.list {
li {
margin-bottom: 17px;
font-weight: 300;
color: #666666;
&.active {
color: @blue;
font-weight: bold;
&::before {
width: 2px;
height: 14px;
background: @blue;
display: inline-block;
content: "";
margin-right: 10px;
}
}
}
}
}
article {
flex: 1;
}
}
</style>
6、重复点击相同路由报错
当我们重复点击相同的路由,就会有以下报错:
这是路由升级导致的vue版本过低报错。解决方案:
在路由文件中加这一段代码:
复制 const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
十八、项目打包
1、将路由中的 mode: history
注释
2、在 vue.config.js
中:
复制 module.exports = {
publicPath: "./"
}
3、如果使用了懒加载,记得将img_loading.gif图片放到相应的位置
十九、图片懒加载
1、安装
复制 yarn add vue-lazyload
# 或者
npm i vue-lazyload -S
2、全局引入与配置
复制 import VueLazyload from 'vue-lazyload'
// 配置项
Vue.use(VueLazyload, {
preLoad: 1.3,
// error: 'dist/error.png',
// 这里注意,不能写相对路径,因此打包上线也需要修改这个地址
loading: 'http://codesohigh.com/img_loading.gif',
attempt: 1
})
3、设定loading大小
在 App.vue
中:
复制 img[lazy="loading"] {
display: block;
width: 30% !important;
height: 30% !important;
margin: 0 auto;
}
4、:src --> v-lazy
二十、设置title与favicon.ico
当我们完成项目后,想要在webpack修改 index.html
的title标签,可以在 vue.config.js
中:
复制 module.exports = {
chainWebpack: (config) => {
config.plugin("html").tap((args) => {
args[0].title = "叩丁狼积分商城";
return args;
});
},
publicPath: "./",
};
如果想改favicon.ico,可以在网上扒一个你想要的,然后替换 public
下的favicon.ico即可。
二十一、作业
【Level-1】完成可达到做网页设计师的水平
day01-day02:仓库新建一个dev分支,首页、全部商品页、个人中心页搭建
【Level-2】完成可达到项目逻辑度最高的水平
day03-day04:独立完成微信扫码登录及手机号登录
【Level-3】完成可达到中级前端工程师的水平
day05-day06:独立完成购物车全选与步进器功能、商品详情页社交平台分享功能
【Level-4】完成可达到高级前端工程师的水平
在day07之前完成项目,项目完成过程中请教同学或老师的次数小于3次,并能在老师讲授每个模块前已自主完成该模块,那么,你就是前端大佬了!
二十二、项目总结
1、项目介绍
《叩丁严选》是一个由vue-cli搭建的PC端SPA商城,该商城主要涉及登录注册、商品列表、商品详情、个人中心、购物车及商品检索等主体功能。该项目主要用于平台用户参与积分兑换商品,是一个中大型的PC端商城项目。
2、项目技术点
使用vue-cli搭建项目,并结合蓝湖+PS进行页面切图,实现对设计稿的高保真还原;
使用axios进行数据请求,并对其进行request拦截封装;
登录采用手机+验证码、手机+密码及微信扫码登录,其中微信扫码登录结合环境变量,调用后端接口实现平台切换验证;
使用localStorage对用户信息和token进行存储;
使用vshare实现将项目分享到第三方社交平台(如:微信、QQ空间等);
使用原生JS在组件mounted中监听滚动,并实现向下滚动加载更多;
使用导航守卫对每个进入个人中心页的路由进行拦截,正则匹配路径后保证有token方能进入该路由;
给组件绑定key属性,通过修改key值来强制更新组件;