A simple Vue.js project
# 初始化webpack-simple 模板
# 注意webpack-simple 后面可以写项目名称 不写代表在当前目录生成项目
vue init webpack-simple
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
# 注意打包编译前原文件请求的静态资源路径需要修改
npm run build
npm install bootstrap -S
在main.js 中 导入bootstrap
import 'bootstrap'
在 webpack.config.js 中纠正默认导入的bootstrap文件
resolve:{
alias:{
'bootstrap$':'bootstrap/dist/css/bootstrap.css'
}
}
处理文件加载 (默认导出的是js)
css style loader 加载规则
npm i style-loader css-loader -D
改变loader规则
module: {
rules: [
{
test:/\.css$/,
loader:'style-loader!css-loader'
}
]
}
配置字体图标
module: {
rules: [
{
test: /\.(png|jpg|gif|svg|woff|ttf|eot|woff2)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
]
}
在src下建立一个components 文件夹 放置组件
在components 下创建
navbar.vue 图书导航组件
list.vue 图书列表组件
detail.vue 图书详情组件
add.vue 添加图书组件
在components 下创建index.js
// 导入全部组件 并批量导出
import Navbar from './navbar.vue'
import List from './list.vue'
import Detail from './detail.vue'
import Add from './add.vue'
export {Navbar,List,Detail,Add}
导入bootstrap 导航组件 http://v3.bootcss.com/components/#nav
组件只能有一个根元素
<nav id="navbar" class="navbar navbar-default navbar-fixed-center">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Simple Bookstore</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active">
<a href="javascript:;">Book list</a>
</li>
<li>
<a href="javascript:;">Add book</a>
</li>
</ul>
</div>
</div>
</nav>
在App.vue 中导入
# 导入Navbar 模块
import {Navbar} from './components'
# 作为组件
export default {
components: {
Navbar
}
}
# 输出到页面
<template>
<div id="app">
<Navbar></Navbar>
</div>
</template>
基于vue-router 开发单页应用
# 下载vue-router
npm install vue-router -S
在src下建立一个router 文件夹 放置路由表
在router 下创建router.js 文件配置路由
-
配置路由导航
-
导入路由组件
-
配置路由表 (注册路由表)
-
生成和导出VueRouter 实例
-
在Vue根实例中注入路由
在navbar.vue中
<li class="active">
<router-link to="/list">Book list</router-link>
</li>
<li>
<router-link to="/add">Add book</router-link>
</li>
在router.js中
# 导入路由组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import {List, Add, Detail} from '../components'
# 启用VueRouter
Vue.use(VueRouter);
# 创建路由路径规则
let routes = [
{path: '/list', component: List},
{path: '/detail/:id', name: 'detail', component: Detail},
{path: '/add', component: Add},
{path: '*', redirect: '/list'} // redirect 重定向
];
# 生成VueRouter 实例 并导出
export default new VueRouter({routes});
在main.js 中
# 导入VueRouter 实例
import router from './router/router'
new Vue({
el:'#app',
render: h => h(App),
router // 将VueRouter 实例注入到vue 根实例中
})
在App.vue 中 指定视图位置
# router-view 视图 路由出现位置
<template>
<div id="app">
<Navbar></Navbar>
<div class="container">
<router-view></router-view>
</div>
</div>
</template>
在list.vue 中
# 先写静态页面
<template>
<div id="list">
<h3>Book list page</h3>
<div class="col-md-3">
<div class="panel panel-warning">
<div class="panel-heading">
Title:<span>VueJs</span>
</div>
<div class="panel-body">
<img src="./logo.png" alt="">
</div>
<div class="panel-footer">
Price:<span>9.9 $</span>
<a href="javascript:;">Detail</a>
</div>
</div>
</div>
</div>
</template>
<style>
a {
text-decoration: none;
}
h3 {
text-align: center;
}
img {
width: 100%;
}
</style>
-
通过vue-resource 进行前后端交互
-
webpack => devServer.proxy(解决跨域问题)
-
搭建node server (express)
-
接口restful风格
get 获取一个或多个资源
post 向后台添加资源
put 更新修改资源
delete 删除资源
下载vue-resource 模块
npm i vue-resource -S
在main.js 中
import VueResource from 'vue-resource'
Vue.use(VueResource);
在src下建立一个api文件夹 放置接口和数据
创建books.json 数据文件
[
{"id": 1, "bookname": "VueJs1", "bookcover": "dist/logo.png", "price": 10},
{"id": 2, "bookname": "VueJs2", "bookcover": "dist/logo.png", "price": 10},
{"id": 3, "bookname": "VueJs3", "bookcover": "dist/logo.png", "price": 10},
{"id": 4, "bookname": "VueJs4", "bookcover": "dist/logo.png", "price": 10}
...
]
创建server.js 服务文件(基于express框架)
let express = require('express');
let app = express();
// 监听get请求并处理
app.get('/books', (req, res) => {
res.send('hello List!');
});
app.listen(6061, () => {
console.log('监听6061端口');
});
在webpack.config.js 中配置代理
devServer: {
historyApiFallback: true,
noInfo: true,
proxy:{ // 设置代理 解决跨域问题
'/books':'http://127.0.0.1:6061'
}
}
在list.vue 中测试接口
<script>
export default {
beforeMount(){
this.$http.get('/books').then(res => {
// 从后台得到数据放在body属性中
console.log(res.body)
}, err => {
console.log(err)
});
}
}
</script>
在 server.js 中
let fs = require('fs');
// 封装异步读取数据
let readBooks = callback => {
fs.readFile('./books.json', 'utf-8', (err, data) => {
if (err || data.json.length === 0) data = '[]';
callback(JSON.parse(data));
})
};
// 监听get 请求并处理
app.get('/books', (req, res) => {
readBooks((books) => { // books 是读取到的数据 异步
res.send(books);
})
});
在list.vue 发出请求得到后台返回的数据并渲染页面
<script>
export default {
data() {
return {books: null};
},
beforeMount(){
this.$http.get('/books').then(res => {
// 从后台得到数据放在body属性中
this.books = res.body;
}, err => {
console.log(err)
});
}
}
</script>
# 循环得到的数据,有多少条数据渲染多少次
<div class="col-md-3" v-for="book in books">
# 动态绑定每条数据的参数
<span>{{book.bookname}}</span>
<img :src="book.bookcover" alt="">
<span>{{book.price}}</span>
<router-link :to="{name:'detail',params:{id:book.id}}">详情</router-link>
在detail.vue中
<script>
export default {
data(){
return {id: null, book: (id: "", bookname: "", bookcover: "", price: ""};
},
beforeMount(){
// 获取当前页面的路由参数 id
this.id=this.$route.params.id;
// 把当前页对应的id 传到后台 渲染从后台返回的数据
this.$http.get('/books?id='+this.id).then(res => {
this.book = res.body;
}, err => {
console.log(err);
})
}
</script>
# 把从后台得到数据渲染到页面
<span>{{book.bookname}}</span>
<img :src="book.bookcover" alt="">
<span>{{book.price}}</span>
在server.js 中
// 监听get请求并处理
app.get('/books', (req, res) => {
console.log(req.query.id); // 得到前端传递过来的id
let id = Number(req.query.id);
if (id) { // 如果前台传过来id 返回指定id 的图书数据 详情页
readBooks(books => {
// 返回id 相同的那一项
let book = books.find(item => item.id === id);
// 把得到id 相同那一项传给前台
res.send(book);
});
} else { // 如果前台没有传过来id 返回所有的数据 列表页
readBooks(books=> {
res.send(books);
})
}
});
在detail.vue中
<script>
export default {
name: 'detail',
data(){
// flag 默认不显示 当点击修改按钮 原本显示元素隐藏 隐藏的元素显示
return {id: null, book: {bookname: '', bookcover: '', price: ''}, flag: false};
},
methods:{
// 把当前页的id 传到后台 后台删除后返回列表页
remove(){
this.$http.delete('/books?id='+this.book.id).then(res=>{
this.$router.push('/list');
},err=>{
console.log(err);
})
},
// 把修改后的book 传到后台 后台更新后返回列表页
update(){
this.$http.put('/books',this.book).then(res=>{
this.$router.push('/list');
},err=>{
console.log(err);
})
}
}
}
</script>
# 给按钮绑定事件
<button type="button" class="btn btn-danger" v-show="!flag" @click="remove">Delete</button>
<button type="button" class="btn btn-warning" v-show="!flag" @click="flag=true">Modify</button>
<button type="button" class="btn btn-primary" v-show="flag" @click="update">Confirm change</button>
// 添加可编辑的文本框 默认隐藏
<input type="text" v-show="flag" v-model="book.bookname">
<input type="text" v-show="flag" v-model="book.price">
body-parser 解析请求体的中间键
npm install body-parser -D
在server.js 中
let bodyParser = require('body-parser');
// 封装异步写入方法
let writeBooks = (data, callback) => {
fs.writeFile('./books.json', JSON.stringify(data), callback);
};
app.use(bodyParser.json()); // 解析请求体json内容
// 监听put请求并处理
app.put('/books', (req, res) => {
console.log("需要修改的内容: "+req.body); // req.body 获取的请求体里的内容 json对象
readBooks(books => {
let id = Number(req.body.id); // 获取需要更新的对象id
books = books.map(item => {
// 如果id 相同 则替换这条数据
if (item.id === id) {
return req.body;
}
// 不相同则返回原数据
return item;
});
// 把新的数组写入json 文件
writeBooks(books, err => {
res.send(books);
});
});
});
// 监听delete请求并处理
app.delete('/books', (req, res) => {
// 得到前台传过来的 需要删除的id
let id = Number(req.query.id);
readBooks(books => {
books = books.filter(item => {
// 过滤id 不符合项
return item.id != id;
});
// 把过滤后的新数组写入json 文件
writeBooks(books, err => {
res.send(books);
});
});
});
在add.vue中
<form>
<div class="form-group">
<label for="name">Title</label>
<input type="text" class="form-control" id="name" v-model="book.bookname">
</div>
...
<button class="btn btn-primary" @click="add">Confirm change</button>
</form>
<script>
export default {
data(){
return {book: {bookname: null, bookcover: null, price: null}}
},
methods: {
add(){
if (this.book.bookname && this.book.bookcover && this.book.price) {
// 如果增加的book 各个值都存在 就传到后台 然后返回列表页
this.$http.post('/books', this.book).then(res => {
this.$router.push('/list');
}, err => {
console.log(err);
})
} else {
alert('请把信息填写完整!');
}
}
}
}
</script>
在server.js 中
// 监听post请求并处理
app.post('/books', (req, res) => {
readBooks(books => {
// 如果之前json 文件里有数据
if (books.length >= 1) {
// 收到前台的请求 并在后台生成一个id id是json文件里最后一条数据的id+1
let oId = books.length - 1;
// 把id添加到要增加的那条数据里
req.body.id = books[oId].id + 1;
} else {
req.body.id = 1;
}
// 把数据添加到数组末尾
books.push(req.body);
// 把数组重新写入json 文件
writeBooks(books, function () {
res.send(books);
})
})
});
在生产环境运行代码
# 打包代码
npm run build
拷贝一份 server.js 和 books.json 到根目录
在根目录server.js 中添加
// 返回静态文件中间件
app.use(express.static('public'));
// 如果访问 / 就展示主页
app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html', ';charset=utf-8');
let resHtml = fs.readFile('./index.html', 'utf-8', (err, resHtml) => {
res.send(resHtml);
});
});
在根目录创建public文件夹
把生产后的代码dist文件夹 放入public
把静态资源assets文件夹 拷贝一份到public
# 修改根目录books.js 里的的图片路径
[{"bookname":"VueJs1","bookcover":"assets/logo.png","price":"10","id":1}]
启动根目录的server 打开localhost:127.0.0.1:6061/
注意index.html 里的引入路径是不需要修改的
<script src="/dist/build.js"></script>
生产后项目中除了以下几个文件其余不再依赖
books.json index.html node_modules public server.js
如有问题或补充欢迎联系我
项目预览地址: https://bookstore.gggoe.cn