项目实战【vue,react,微信小程序】(1706C)

mac2025-02-12  10

目录

一、接口封装

二、vue图片懒加载

三、书城后端接口

四、登录、注册、修改密码后端接口

五、时间戳转日期

六、文件上传单个文件

 七、上传多个文件

八、设置淘宝镜像

九、yarn和npm清缓存

十、react图片懒加载

十一、yarn

十二、react路由懒加载

十三、让所有接口延时返回

十四、前端使用代理解决跨域问题

十五、mock很多数据、分页查找

十六、如何上线

十七、小程序轮播图

十八、父子组件通讯

十九、路由

二十、tabBar

二十一、组件生命周期

二十二、计算属性

二十三、小程序购车

二十四、使用 Component 构造器构造页面

二十五、使用组件observers做数据监听器

github源码:


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

一、接口封装

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; width: 112px;height: 150px;background: #dddddd;} .m-list-item-img{width: 100%;} .m-list-item-img[lazy=loading]{margin: auto; width: 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; width: 112px;height: 150px;background: #dddddd;} .m-list-item-img-wrap::before{content: '';margin: auto;width: 38px;height: 38px;background-image: url(./images/loading.png);animation: loading 0.5s linear infinite;} .m-list-item-img{position: absolute; width: 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; width: 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/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

github源码:

https://github.com/baweireact/m-app-1706C

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

最新回复(0)