目录
一、接口封装
index.js:
import axios from 'axios'
import urls from './urls'
console.log(process)
if (process.env.NODE_ENV === 'development') {
console.log('env', process.env.NODE_ENV)
axios.defaults.baseURL = 'http://localhost'
}
axios.interceptors.request.use((config) => {
return config
})
axios.interceptors.response.use((res) => {
if (res.data.code === 400) {
alert(res.data.message)
}
return res
})
const common = async (config) => {
let response = await axios(config)
return response.data
}
export default {
login: (data) => common({ url: urls.login, data, method: 'post' }),
getNav: () => common({ url: urls.getNav }),
getList: (url) => common({ url: urls.getList + url }),
updateMyBook: (data) => common({ url: urls.updateMyBook, data, method: 'post' }),
add:(data) => common({ url: urls.add, data, method: 'post' }),
getMyBook: () => common({ url: urls.getMyBook }),
getDetail: (url) => common({ url: urls.getDetail + url })
}
urls.js:
const urls = {
login: '/api/login',
getNav: '/api/nav',
getList: '/api/list',
updateMyBook: '/api/update_my_book',
add: '/api/add',
getMyBook: '/api/get_my_book',
getDetail: '/api/detail'
}
export default urls
拦截器参考链接:http://www.axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8
二、vue图片懒加载
参考链接:https://www.npmjs.com/package/vue-lazyload
注册:
import Vue from 'vue'
import VueLazyload from 'vue-lazyload'
import loadingImg from './images/loading.gif'
Vue.use(VueLazyload, {
preLoad: 1.3,
error: loadingImg,
loading: loadingImg,
attempt: 1
})
new Vue({
render: h => h(App)
}).$mount('#app')
v-lazy:
<div class="m-list-item-img-wrap">
<img v-lazy="item.avatar" alt="" class="m-list-item-img">
</div>
样式:
.m-list-item-img-wrap{display: flex; 112px;height: 150px;background: #dddddd;}
.m-list-item-img{ 100%;}
.m-list-item-img[lazy=loading]{margin: auto; 40px;height: 40px;}
三、书城后端接口
const express = require('express')
const { bookNavData, bookMallData, bookMallDetailData } = require('./data')
const bodyParser = require('body-parser')
const cors = require('cors')
const history = require('connect-history-api-fallback')
const app = express()
//用户列表
let userList = [{
id: '001',
username: 'admin',
password: '123456'
}, {
id: '002',
username: 'xu',
password: '123'
}, {
id: '003',
username: 'a',
password: '123456'
}]
//书包
let myBook = []
//跨域
app.use(cors())
//解析post请求
// parse application/json
app.use(bodyParser.json())
app.use(history())
// parse application/x-www-form-urlencoded
//app.use(bodyParser.urlencoded({ extended: false }))
//静态web服务器
app.use(express.static( __dirname + '/public'))
//登录
app.post('/api/login', (req, res) => {
let { username, password } = req.body
console.log(JSON.stringify(req.body))
console.log(username, password)
let user = userList.find(item => item.username === username)
if (user) {
if (user.password === password) {
res.send({
code: 200,
data: {
username
},
message: '登录成功'
})
} else {
res.send({
code: 400,
message: '密码错误'
})
}
} else {
res.send({
code: 400,
message: '用户名不存在'
})
}
})
//导航
app.get('/api/nav', (req, res) => {
res.send({
code: 200,
data: bookNavData,
message: '导航'
})
})
//列表
app.get('/api/list', (req, res) => {
let { id } = req.query
let list = bookMallData.find(item => item.id == id).list
list.forEach(item => {
item.is_in_my_book = myBook.findIndex(book => book.id === item.id) >= 0
})
res.send({
code: 200,
data: list,
message: '列表'
})
})
//详情
app.get('/api/detail', (req, res) => {
let { id } = req.query
let detail
bookMallDetailData.forEach(item => {
item.list.forEach(book => {
if (book.id == id) {
book.is_in_my_book = myBook.findIndex(item => item.id === book.id) >= 0
detail = book
}
})
})
res.send({
code: 200,
data: detail,
message: '详情'
})
})
//更新书包
app.post('/api/update_my_book', (req, res) => {
let { myBookNew } = req.body
myBook = myBookNew
res.send({
code: 200,
data: myBook,
message: '更新成功'
})
})
//添加
app.post('/api/add', (req, res) => {
let { book } = req.body
myBook.push(book)
res.send({
code: 200,
data: myBook,
message: '添加成功'
})
})
//获取书包
app.get('/api/get_my_book', (req, res) => {
res.send({
code: 200,
data: myBook,
message: '书包'
})
})
app.listen(80)
console.log(80)
四、登录、注册、修改密码后端接口
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const history = require('connect-history-api-fallback')
const uuidv1 = require('uuid/v1')
const jwt = require('jwt-simple')
const redis = require('redis')
//用户列表
let userList = [{
id: '001',
username: 'admin',
password: '123456'
}, {
id: '002',
username: 'xu',
password: '123'
}, {
id: '003',
username: 'a',
password: '123456'
}]
//token加密密码
let secret = 'xxx'
const app = express()
const client = redis.createClient()
client.on('error', err => {
console.log('redis错误:' + err)
})
//跨域
app.use(cors())
//解析post请求
// parse application/json
app.use(bodyParser.json())
//let jsonParser = bodyParser.json()
//处理react前端路由(BrowserRoute),vue前端路由(mode:history),注意:开启后无法用postman和浏览器地址栏调试get接口
//app.use(history())
//静态web服务器
app.use(express.static(__dirname + '/public'))
//通过中间件检查登录是否过期,并自动续期
const checkTokenByMiddleware = (req, res, next) => {
let token = req.headers.token
client.get(token, (err, response) => {
if (response) {
client.set(token, token, 'EX', 60)
next()
} else {
res.send({
code: 403,
message: '登录过期'
})
}
})
console.log(2)
}
//登录
app.post('/api/login', (req, res) => {
let { username, password } = req.body
let user = userList.find(item => item.username === username)
if (user) {
if (user.password === password) {
let token = jwt.encode(user.id, secret)
client.set(token, token, 'EX', 60)
res.send({
code: 200,
data: {
username,
token
},
message: '登录成功'
})
} else {
res.send({
code: 400,
message: '密码错误'
})
}
} else {
res.send({
code: 400,
message: '用户名不存在'
})
}
})
//注册
app.post('/api/register', (req, res) => {
let { username, password } = req.body
console.log(username, password)
let user = userList.find(item => item.username === username)
if (user) {
res.send({
code: 400,
message: '用户名已存在'
})
} else {
let id = uuidv1()
userList.push({
id,
username,
password,
})
let token = jwt.encode(id, secret)
client.set(token, token, 'EX', 60)
res.send({
code: 200,
data: {
userList,
token
},
message: '注册成功'
})
}
})
//修改密码
app.post('/api/modify_password', checkTokenByMiddleware, async (req, res) => {
let token = req.headers.token
let { password } = req.body
let id = jwt.decode(token, secret)
console.log(id)
let index = userList.findIndex(item => item.id === id)
userList[index].password = password
res.send({
code: 200,
data: userList,
message: '修改成功'
})
})
//动态路由
app.get('/api/test/:id', (req, res) => {
let { id } = req.params
res.send({
code: 200,
data: id,
message: '动态路由测试'
})
})
app.listen(82)
console.log(82)
五、时间戳转日期
参考链接:http://momentjs.cn/
import moment from "moment"
moment.locale('zh-cn')
console.log(moment(1573042782076).format('YYYY年MM月DD日 hh:mm:ss'))
console.log(moment(1573042782076).fromNow())
六、文件上传单个文件
controller/upload.js:
const multer = require('multer')
const storage = multer.diskStorage({
destination: (req ,file, cb) => {
cb(null, 'upload')
},
filename: (req, file, cb) => {
cb(null, `${Date.now()} - ${file.originalname}` )
}
})
const upload = multer({ storage })
const uploadImg = (req, res) => {
res.send({
code: 200,
data: req.file,
message: '上传成功'
})
}
module.exports = {
upload,
uploadImg
}
router/upload.js:
const express = require('express')
const router = express.Router()
const { upload, uploadImg } = require('../controller/upload')
router.post('/upload', upload.single('img'), uploadImg)
module.exports = router
app.js:
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const history = require('connect-history-api-fallback')
const upload = require('./router/upload')
const app = express()
//跨域
app.use(cors())
//解析post请求
// parse application/json
app.use(bodyParser.json())
//let jsonParser = bodyParser.json()
//处理react前端路由(BrowserRoute),vue前端路由(mode:history),注意:开启后无法用postman和浏览器地址栏调试get接口
app.use(history())
//静态web服务器
app.use(express.static(__dirname + '/public'))
app.use(express.static(__dirname + '/upload'))
//上传文件
app.use('/api/', upload)
app.listen(82)
console.log(82)
使用postman测试上传接口:
前端:
<input type="file" @change="handleUpload" >
handleUpload(e) {
const data = new FormData()
data.append('img', e.target.files[0])
axios({
url: '/api/upload',
data,
method: 'post',
timeout: 1000 * 60
}).then(res => {
if (res.code === 200) {
}
})
}
七、上传多个文件
controller/upload.js:
const multer = require('multer')
const storage = multer.diskStorage({
destination: (req ,file, cb) => {
cb(null, 'upload')
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}` )
}
})
const upload = multer({ storage })
const uploadImg = (req, res) => {
res.send({
code: 200,
data: req.files, //单个文件:req.file 多个文件:req.files
message: '上传成功'
})
}
module.exports = {
upload,
uploadImg
}
router/upload.js:
const express = require('express')
const router = express.Router()
const { upload, uploadImg } = require('../controller/upload')
//上传单个文件
//router.post('/upload', upload.single('img'), uploadImg)
router.post('/upload', upload.array('img', 9), uploadImg)
module.exports = router
app.js和上传单个文件相同
使用postman测试上传多个文件:
前端代码:
<input type="file" multiple="multiple" @change="handleUpload" >
handleUpload(e) {
const data = new FormData()
let files = e.target.files
files.forEach(item => {
data.append('img', item)
})
axios({
url: '/api/upload',
data,
method: 'post',
timeout: 1000 * 60
}).then(res => {
if (res.code === 200) {
}
})
}
},
八、设置淘宝镜像
yarn config set registry http://registry.npm.taobao.org/
yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
九、yarn和npm清缓存
npm cache clean --force
yarn cache clean
十、react图片懒加载
参考链接:
https://www.npmjs.com/package/react-lazy-load
import LazyLoad from 'react-lazy-load'
<div key={item.id} className="m-list-item">
<LazyLoad className="m-list-item-img-wrap">
<img src={item.avatar} className="m-list-item-img"></img>
</LazyLoad>
<div className="m-list-item-info">
{item.title}
</div>
</div>
.m-list-item{display: flex;margin: 5px;}
.m-list-item-img-wrap{display: flex; 112px;height: 150px;background: #dddddd;}
.m-list-item-img-wrap::before{content: '';margin: auto; 38px;height: 38px;background-image: url(./images/loading.png);animation: loading 0.5s linear infinite;}
.m-list-item-img{position: absolute; 112px;height: 150px;}
.m-list-item-info{flex:1}
@keyframes loading {
from {transform: rotate(0deg);}
to{transform: rotate(360deg);}
}
十一、yarn
yarn是什么? 能干什么?
yarn是facebook发布的一种包管理工具,作用同npm 一样,是一个包管理用具
优点:
快速: 1.会缓存它下载的每个包, 无需重复下载;能并行化操作以最大资源利用率
可靠: 使用格式详尽而又简洁的 lockfile文件 和确定性算法来安装依赖,能够保证在一个系统上的运行的安装过程也会以同样的方式运行在其他系统上。
安全: 安装包被执行前校验其完整性
十二、react路由懒加载
参考链接:
https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy
import React, { Component, Suspense, lazy } from 'react'
import { NavLink, Switch, Route } from 'react-router-dom'
import Home from './Home'
//import MyBook from './MyBook'
import Loading from '../components/Loading'
const MyBook = lazy(async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(import('./MyBook'))
}, 2000)
})
})
export default class Index extends Component {
render() {
return (
<div>
<div>
<NavLink to="/index/home" className="m-nav-item">首页</NavLink>
<NavLink to="/index/my_book" className="m-nav-item">书包</NavLink>
</div>
<Suspense fallback={<Loading lazyLoading={true}></Loading>}>
<Switch>
<Route path="/index/home" component={Home}></Route>
<Route path="/index/my_book" component={MyBook}></Route>
</Switch>
</Suspense>
</div>
)
}
}
Loading.js:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Loading extends Component {
render() {
let { loading, lazyLoading } = this.props
return (
<div className={"m-loading-wrap " + (loading ? 'active ' : '') + (lazyLoading ? 'active' : '')}>
<span className="m-loading-img"></span>
</div>
)
}
}
const mapStateToProps = (state) => {
state = state.toJS()
return {
loading: state.loading
}
}
export default connect(mapStateToProps)(Loading)
loading样式文件:
.m-loading-wrap{position: fixed;display: none; top: 0;left: 0;right: 0;bottom: 0; background: rgba(0, 0, 0, 0.5);z-index: 999;}
.m-loading-wrap.active{display: flex;}
.m-loading-img{ display: inline-block;margin: auto; 38px;height: 38px;background-image: url(./images/loading.png);background-size: 100% 100%; animation: loading 0.5s linear infinite; }
@keyframes loading {
from {transform: rotate(0deg);}
to{transform: rotate(360deg);}
}
路由懒加载时,需要懒加载的路由组件特别小,这时很难观察到加载的loading效果,怎么办呢?
可以把使用Promise模拟延时:
const MyBook = lazy(async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(import('./MyBook'))
}, 2000)
})
})
十三、让所有接口延时返回
目的是测试前端loading效果
//让所有接口延时返回
app.use((req, res, next) => {
setTimeout(() => {
next()
}, 500)
})
十四、前端使用代理解决跨域问题
To tell the development server to proxy any unknown requests to your API server in development, add a proxy
field to your package.json
, for example:
"proxy": "http://localhost:83"
也可以增加一个文件:
装包:
yarn add http-proxy-middleware
在src目录下面建一个文件,文件名是setupProxy.js 即 src/setupProxy.js:
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
proxy({
target: 'http://localhost:83',
changeOrigin: true,
})
);
};
参考链接:
https://create-react-app.dev/docs/proxying-api-requests-in-development/
十五、mock很多数据、分页查找
装包:
yarn add mockjs
const Mock = require('mockjs')
const mockDataList = Mock.mock({
'list|100': [{
'id|+1': 1,
'name': '@cname',
'title': '@ctitle',
'image': '@image(300x300)',
'address': '@county(true)'
}]
}).list
//分页
app.get('/api/mock_data', (req, res) => {
let { page, size } = req.query
let start = (page - 1) * size
let end = start + Number(size)
console.log(start, end)
let data = mockDataList.slice(start, end)
res.send({
code: 200,
data: data,
message: '列表'
})
})
前端分页,滚动到底加载更多:
import React, { Component } from 'react'
import axios from 'axios'
let updateDone = true
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
list: [],
end: '',
page: 1
}
}
handleScroll(e) {
let { list, end, page} = this.state
if (
e.target.clientHeight + e.target.scrollTop + 200 >
e.target.scrollHeight &&
end === "" && updateDone
) {
updateDone = false
page = page + 1;
axios({
url: `/api/mock_data?page=${page}&size=20`
}).then(res => {
if (res.data.code === 200) {
this.setState({
list: list.concat(res.data.data),
page
})
if (res.data.data.length < 20) {
console.log("到底了");
this.setState({
end: "到底了"
})
}
}
});
}
}
componentDidUpdate() {
updateDone = true
}
componentDidMount() {
axios({
url: '/api/mock_data?page=1&size=20'
}).then(res => {
if (res.data.code === 200) {
this.setState({
list: res.data.data
})
}
})
}
render() {
let { list, end } = this.state
let listDom = list.map(item => (
<div key={item.id} className="m-list-item">{item.name}</div>
))
return (
<div className="m-warp" onScroll={this.handleScroll.bind(this)}>
{listDom}
<div className="m-end">{end}</div>
</div>
)
}
}
十六、如何上线
打包:
yarn build
利用 Express 托管静态文件:
//静态web服务器
app.use(express.static(__dirname + '/public'))
app.use(express.static('public'))
参考链接:
http://www.expressjs.com.cn/starter/static-files.html
利用 connect-history-api-fallback 解决前端history路由刷新报错:
const history = require('connect-history-api-fallback')
//处理react前端路由(BrowserRoute),vue前端路由(mode:history),注意:开启后无法用postman和浏览器地址栏调试get接口
app.use(history())
参考链接:
https://www.npmjs.com/package/connect-history-api-fallback
十七、小程序轮播图
//图片的宽高比等于手机屏幕的宽度和swiper的高度比
handleImageLoad(e) {
let { windowWidth } = wx.getSystemInfoSync()
let { height, width } = e.detail
height = height / width * windowWidth
this.setData({
height
})
},
<swiper
indicator-dots="{{true}}"
autoplay="{{true}}"
interval="{{5000}}"
circular="{{true}}"
style="height:{{height}}px">
<swiper-item wx:for="{{banner}}" wx:key="{{index}}">
<image src="{{item}}" mode="widthFix" class="m-item-img" bindload="handleImageLoad"></image>
</swiper-item>
</swiper>
十八、父子组件通讯
父组件:
<book-nav navList="{{navList}}" currentId="{{currentId}}" bind:onNav="handleNav"></book-nav>
handleNav(e) {
let { id } = e.detail
this.setData({
currentId: id
})
wx.request({
url: `${host}/api/list?id=${id}`,
success: (res) => {
if (res.data.code === 200) {
this.setData({
currentList: res.data.data
})
}
}
})
},
子组件:
// components/book-nav/book-nav.js
Component({
/**
* 组件的属性列表
*/
properties: {
navList: Array,
currentId: Number
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
handleNav(e) {
let { id } = e.mark
this.triggerEvent('onNav', { id })
}
}
})
十九、路由
wx.navigateTo({
url: `/pages/detail/detail?id=${id}`,
})
onLoad: function (options) {
let { id } = options
wx.request({
url: `${host}/api/detail?id=${id}`,
success: (res) => {
if (res.data.code === 200) {
this.setData({
detail: res.data.data
})
}
}
})
},
二十、tabBar
"tabBar": {
"color": "#333333",
"selectedColor": "#ff0000",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/static/images/home.png",
"selectedIconPath": "/static/images/home-active.png"
},
{
"pagePath": "pages/my-book/my-book",
"text": "书包",
"iconPath": "/static/images/cart.png",
"selectedIconPath": "/static/images/cart-active.png"
},
{
"pagePath": "pages/index/index",
"text": "我的",
"iconPath": "/static/images/me.png",
"selectedIconPath": "/static/images/me-active.png"
}
]
}
二十一、组件生命周期
lifetimes: {
ready() {
wx.request({
url: `${host}/api/my_book`,
})
}
}
pageLifetimes: {
show() {
wx.request({
url: `${host}/api/my_book`,
success: (res) => {
if (res.data.code === 200) {
this.setData({
myBook: res.data.data
})
}
}
})
}
}
二十二、计算属性
在终端进入小程序目录,使用下面的命令省事package.json文件
npm init -y
装包:
yarn add miniprogram-computed
构建npm:
构建后生成miniprogram_npm:
使用计算属性计算总价,总数:
// pages/my-book/my-book.js
const computedBehavior = require('miniprogram-computed')
const { host } = getApp().globalData
Component({
behaviors: [computedBehavior],
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
myBook: []
},
computed: {
total(data) {
let totalPrice = 0, totalCount = 0
data.myBook.forEach(item => {
totalCount += item.count
totalPrice += item.count * item.price
})
return {
totalCount,
totalPrice
}
}
},
/**
* 组件的方法列表
*/
methods: {
},
pageLifetimes: {
show() {
wx.request({
url: `${host}/api/my_book`,
success: (res) => {
if (res.data.code === 200) {
this.setData({
myBook: res.data.data
})
}
}
})
}
}
})
参考链接:
https://github.com/wechat-miniprogram/computed
https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html
二十三、小程序购车
二十四、使用 Component 构造器构造页面
页面的生命周期方法(即 on
开头的方法),应写在 methods
定义段中。
Component({
properties: {
paramA: Number,
paramB: String,
},
methods: {
onLoad: function(options) {
let { id } = options
this.data.paramA // 页面参数 paramA 的值
this.data.paramB // 页面参数 paramB 的值
},
onShow() {
}
}
})
也可以写在pageLifetimes字段里(去掉on):
Component({
properties: {
},
data: {
detail: {}
},
pageLifetimes: {
load(options) {
let { id } = options
},
show() {
}
}
})
二十五、使用组件observers做数据监听器
可以代替计算属性
observers: {
'myBook': function (myBook) {
this.update(myBook)
let totalPrice = 0, totalCount = 0
myBook.filter(item => item.checked).forEach(item => {
totalCount += item.count
totalPrice += item.count * item.price
})
let total = {
totalCount,
totalPrice,
selectedAll: myBook.length === myBook.filter(item => item.checked).length && myBook.length > 0
}
this.setData({
total
})
}
},
二十六、antd移动版
https://mobile.ant.design/components/carousel-cn/