一、项目介绍
本项目是书城项目,该项目共分为以下功能模块:
- 首页
- banner图
- 获取热门图书
- 列表页
- 展示列表
- 删除和收藏
- 点击图书进入详情页
- 收藏
- 列表
- 删除收藏
- 新增
- 新增图书
二、技术方案
- 前端:vue + vue-router + less + vue-cli + axios + vue-awesome-swiper
- 后端:express
三、项目结构以及git仓库
四、入口文件及功能组件
1. main.js
import Vue from 'vue'import App from './App.vue'import router from './router'import store from './store'import VueAwesomeSwiper from 'vue-awesome-swiper'import 'swiper/dist/css/swiper.css'Vue.config.productionTip = falseVue.use(VueAwesomeSwiper)new Vue({router,store,render: h => h(App)}).$mount('#app')
2. App.vue
<template><div id="app"><Tab></Tab><router-view/></div></template><script>// App.vue 这个组件不会销毁import Tab from '@/components/base/Tab.vue'export default {name: 'App',components: {Tab}}</script><style>/*如果是公共样式,最好写在 App.vue 中*/* {margin: 0;padding: 0;}ul li {list-style: none;}a {text-decoration: none;}.content {position: absolute;top: 40px;width: 100%;bottom: 50px;}</style>
3. router.js
import Vue from 'vue'import Router from 'vue-router'import Home from './views/Home.vue'import Collect from './views/Collect.vue'import Add from './views/Add.vue'Vue.use(Router)export default new Router({routes: [{path: '/',name: 'home',component: Home,alias: '/home'},{path: '/list',component: () => import('./views/List.vue')},{name: 'detail',path: '/detail/:id',component: () => import('./views/Detail.vue')},{path: '/collect',component: Collect},{name: 'add',path: '/add',component: Add}]})
4. Tab.vue 组件
<template><div class="footer"><router-link to="/home"><i class="iconfont icon-home"></i><span>首页</span></router-link><router-link to="/list"><i class="iconfont icon-chazhaobiaodanliebiao"></i><span>列表</span></router-link><router-link to="/collect"><i class="iconfont icon-shoucang"></i><span>收藏</span></router-link><router-link to="/add"><i class="iconfont icon-tianjia-xue"></i><span>添加</span></router-link></div></template><style scoped lang="less">.footer {position: fixed;width: 100%;bottom: 0;display: flex;border-top: 1px solid #ccc;background: #fff;z-index: 100;a {display: flex;flex: 1; /* 每个a标签占相同的比例 */flex-direction: column;font-size: 18px;align-items: center;color: yellowgreen;}.router-link-active {color: red;}}</style>
5. MyHeader.vue 组件
<template><div class="header"><slot></slot><i class="iconfont icon-fanhui" v-if="back" @click="goBack"></i></div></template><script>export default {data () {return {}},props: ['back'],methods: {goBack () {this.$router.go(-1)}}}</script><style scoped>.header {position: fixed;width: 100%;background: #afd9ee;text-align: center;height: 40px;line-height: 40px;font-size: 20px;z-index: 100;}.icon-fanhui {position: absolute;left: 10px;}</style>
5. Swiper.vue 组件
<template><swiper :options="swiperOption"><!----><swiper-slide v-for="(item, index) in sliders" :key="index"><img :src="item" alt=""></swiper-slide><div class="swiper-pagination" slot="pagination"></div></swiper></template><script>import { swiper, swiperSlide } from 'vue-awesome-swiper'export default {data () {return {swiperOption: {autoplay: 3000,pagination: '.swiper-pagination',loop: true}}},props: ['sliders'],components: {swiper,swiperSlide}}</script><style scoped>img {width: 100%;}</style>
五、页面组件
- 首页截图
1. Home.vue
<template><div><MyHeader>首页</MyHeader><div class="content"><!--内容区域--><Swiper :sliders="sliders"></Swiper><div class="container"><h2>热门图书</h2><ul><li v-for="(item, index) in hotBooks" :key="index"><img :src="item.bookCover" alt=""><b>{{item.bookName}}</b></li></ul></div></div></div></template><script>// @ is an alias to /srcimport MyHeader from '@/components/base/MyHeader.vue'import Swiper from '@/components/base/Swiper.vue'import { getSlider, getHot } from '../api/home'export default {name: 'home',data () {return {hotBooks: [],sliders: []}},async created () {this.sliders = await getSlider()this.hotBooks = await getHot()// this.slide()// this.getHotBook()},methods: {async slide () {this.sliders = await getSlider()},async getHotBook () {this.hotBooks = await getHot()}},components: {MyHeader,Swiper}}</script><style scoped lang="less">.container {box-sizing: border-box;overflow-x: hidden;h2 {padding-left: 30px;}ul li {float: left;margin: 20px 0;width: 50%;img {display: block;}b {display: block;padding-left: 20px;}}}</style>
2. List.vue 列表页

<template><div class="wrapper"><MyHeader :back="true">列表页</MyHeader><div class="content"><ul class="container"><router-link v-for="(book, index) in allBooks":key="index":to="{name: 'detail', params: {id: book.bookId}}" tag="li"><img :src="book.bookCover" alt=""><div class="right"><h3>{{book.bookName}}</h3><p>{{book.bookInfo}}</p><p class="rice">{{book.bookPrice}}</p><button class="btn" @click.stop="remove(book.bookId)">删除</button><button class="btn" @click.stop="collect(book)">收藏</button></div></router-link></ul></div></div></template><script>import MyHeader from '@/components/base/MyHeader.vue'import { getAll, deleteBook, collectBook } from '../api/list'export default {name: 'List',data () {return {allBooks: []}},created () {this.getAllBooks()},methods: {async getAllBooks () {this.allBooks = await getAll()},async remove (id) {await deleteBook(id)this.getAllBooks()},async collect (data) {await collectBook(data)}},components: {MyHeader}}</script><style scoped lang="less">.container {margin-bottom: 50px;li {padding: 10px;font-size: 16px;img {width: 160px;}.right {padding-top: 30px;width: 180px;float: right;}.price {color: red;font-size: 30px;}.btn {width: 60px;height: 30px;background: red;color: #fff;font-size: 18px;border: none;border-radius: 5px;&:nth-of-type(1) {margin-right: 5px;}}}}</style>
3. 详情页 Detail.vue

<template><div><MyHeader :back="true"></MyHeader><div class="content container"><ul><li><label>书名</label><input type="text" v-model="book.bookName"></li><li><label>信息</label><input type="text" v-model="book.bookInfo"></li><li><label>价格</label><input type="text" v-model="book.bookPrice"></li></ul><button @click="updateBook">确认修改</button></div></div></template><script>import MyHeader from '../components/base/MyHeader.vue'import { getOne, update } from '../api/detail'export default {components: {MyHeader},name: 'Detail',data () {return {book: {}}},created () {let { id } = this.$route.paramsthis.getBook(id)},methods: {async getBook (id) {this.book = await getOne(id)this.book.bookId = id},async updateBook () {await update(this.book)this.$router.go(-1)}}}</script><style scoped lang="less">.container {width: 100%;padding: 20px;position: fixed;top: 40px;left: 0;right: 0;bottom: 0;height: 100%;background: #fff;z-index: 101;li {height: 100px;label {display: block;font-size: 25px;font-weight: bold;margin-bottom: 10px;}input {display: block;width: 300px;height: 40px;padding-left: 10px;margin-left: 5px;}}button {display: block;width: 100px;height: 40px;text-align: center;line-height: 40px;background: red;color: #fff;font-size: 20px;border-radius: 4px;border: none;}}</style>
4. Collect.vue 收藏夹

<template><div><MyHeader :back="true">收藏页</MyHeader><div class="content"><ul class="container"><li v-for="(book, index) in allBooks" :key="index"><img :src="book.bookCover" alt=""><div class="right"><h3>{{book.bookName}}</h3><p>{{book.bookInfo}}</p><p class="price">{{book.bookPrice}}</p><button class="btn" @click="remove(book.bookId)">删除</button></div></li></ul></div></div></template><script>import MyHeader from '../components/base/MyHeader.vue'import { getCollect, rmCollect } from '../api/collect'export default {name: 'Collect',data () {return {allBooks: []}},created () {this.getCollect()},methods: {async getCollect () {this.allBooks = await getCollect()},async remove (id) {await rmCollect(id)this.getCollect()}},components: {MyHeader}}</script><style scoped lang="less">.container {margin-bottom: 50px;li {padding: 10px;font-size: 16px;img {width: 160px;}.right {float: right;width: 180px;}.price {color: red;font-size: 30px;}.btn {width: 60px;height: 30px;background: red;color: #fff;border: none;border-radius: 5px;}}}</style>
5. Add.vue 新增页面

<template><div><MyHeader>添加页</MyHeader><div class="content container"><ul><li><label>书名</label><input type="text" v-model="book.bookName"></li><li><label>信息</label><input type="text" v-model="book.bookInfo"></li><li><label>价格</label><input type="text" v-model="book.bookPrice"></li><li><label>封面</label><input type="text" v-model="book.bookCover"></li></ul><button @click="add" class="btn">新增</button></div></div></template><script>import MyHeader from '../components/base/MyHeader.vue'import { addBook } from '../api/add.js'export default {name: 'Add',data () {return {book: {}}},methods: {async add () {await addBook(this.book)this.$router.push('/list')}},components: {MyHeader}}</script><style scoped lang="less">.container {width: 100%;padding: 20px;position: fixed;top: 40px;left: 0;right: 0;bottom: 50px;height: 100%;z-index: 10;li {height: 100px;label {display: block;font-size: 25px;font-weight: bold;margin-bottom: 10px;}input {display: block;width: 300px;height: 40px;padding-left: 10px;margin-left: 5px;}}button {width: 100px;height: 40px;display: block;text-align: center;line-height: 40px;background: red;color: #fff;font-size: 20px;border: none;border-radius: 5px;}}</style>
六、服务端代码
let express = require('express');let bodyParser = require('body-parser');let fs = require('fs');let slides = require('./database/sliders');let bookData = './database/book.json';let collectData = './database/collect.json';let jdb = (dir) => JSON.parse(fs.readFileSync(dir, 'utf8'));let app = express();app.use(express.static(__dirname + '/static'));app.use(bodyParser.json());// 首页app.get('/api/sliders', (req, res) => {res.send(slides);});// 获取热门图书app.get('/api/hot', (req, res) => {// 从数组中最后四个let con = jdb(bookData, 'utf8');let data = con.slice(-4);res.send(data);});// 获取所有图书app.get('/api/books', (req, res) => {let con = jdb(bookData);res.send(con);});app.get('/api/collect', (req, res) => {let con = jdb(collectData);res.send(con)});// 删除书app.get('/api/delete', (req, res) => {let { id } = req.query;let con = jdb(bookData);con = con.filter(item => +item.bookId !== +id);fs.writeFileSync(bookData, JSON.stringify(con), 'utf8');res.send({code: 0,data: null,msg: 'ok'});});// 获取指定的id的图书app.get('/api/getOne', (req, res) => {let { id } = req.query;let con = jdb(bookData);let byId = con.find(item => +item.bookId === +id);if (byId) {res.send(byId)} else {res.send({code: 1,data: null,msg: 'id不存在'})}});// 修改图书信息app.post('/api/update', (req, res) => {let { bookId }= req.body;let con = jdb(bookData);let index = con.findIndex(item => +item.bookId === +bookId);con[index] = req.body;console.log(con[index]);fs.writeFileSync(bookData, JSON.stringify(con), 'utf8');res.send({code: 0,data: null,msg: 'ok'});});// 新增app.post('/api/add', (req, res) => {let con = jdb(bookData);let data = req.body;data.bookId = con.length ? +con[con.length - 1].bookId + 1 : 1;con.push(data);fs.writeFileSync(bookData, JSON.stringify(con), 'utf8');res.send({code: 0,data: null,msg: 'ok'})});// 收藏app.post('/api/collect', (req, res) => {let con = jdb(collectData);let data = req.body;con.push(data);fs.writeFileSync(collectData, JSON.stringify(con), 'utf8');res.send({code: 0,data: null,msg: 'ok'})});app.get('/api/rmCollect', (req, res) => {let con = jdb(collectData);let { id } = req.query;con = con.filter(item => +item.bookId !== +id);fs.writeFileSync(collectData, JSON.stringify(con), 'utf8');res.send({code: 0,data: null,msg: 'ok'})});app.listen(8090, () => console.log('port 8000 is on'));
七、vue.config.js
module.exports = {outputDir: '../book-server/static',devServer: {open: true,proxy: {'/api': {target: 'http://localhost:8090',changeOrigin: true,secure: false}}}};
