进入项目文件夹
- 全局安装 Vue-cli
npm install -g vue-cli
- 使用 Vue-cli 创建 基于webpack模板的新项目
vue init webpack vue-cloud-note
-
在
src/components
下创建Logon.vue
文件,并输入以下代码<template> <div id="login"> <h1>{{ msg }}</h1> </div> </template> <script> export default { name: 'Login', data() { return {msg: '这是登录页面'} } } </script> <style scoped> h1{ color: #f60; } </style>
-
在
router/index.js
文件中,添加如下代码import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '../components/HelloWorld' + import Login from '../components/Login' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, + { + path: '/login', + name: 'Login', + component: Login + } ] })
经过上面的例子,我相信你已经大概知道路由是怎么用的,因此我们还需要在添加几个页面
- 在
src/components
中依次添加NotebookList.vue
、NoteDetail.vue
、TashDetail.vue
文件
-
在
router/index.js
中添加路由import Vue from 'vue' import Router from 'vue-router' import Login from '../components/Login' - import HelloWorld from '../components/HelloWorld' + import NotebookList from '../components/NotebookList.vue' + import NoteDetail from '../components/NoteDetail.vue' + import TashDetail from '../components/TashDetail.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/login', name: 'Login', component: Login + }, { + path: '/notebooks', + name: 'NotebookList', + component: NotebookList + }, { + path: '/note/:noteId', + name: 'NoteDetail', + component: NoteDetail + }, { + path: '/trash/:noteId', + name: 'TashDetail', + component: TashDetail + } + ] +})
新增的三个界面代码可点击此链接查看
在页面输入不同的路由测试无误,说明路由配置都正确啦
代码记录
npm install --save-dev less-loader@4
将 Sidebar.vue
的 style
改写成 less
语法
挑重点记录一下
...
<div class="button" @click="onRegister">创建账号</div>
...
<div class="button" @click="onLogin">登录</div>
...
<script>
export default{
//点击注册按钮触发事件
onRegister() {
let result1 = this.validUsername(this.register.username)
if (!result1.isValid) {
this.register.isError = true
this.register.notice = result1.notice
return
}
let result2 = this.validPassword(this.register.password)
if (!result2.isValid) {
this.register.isError = true
this.register.notice = result2.notice
return
}
this.register.isError = false
this.register.notice = ''
console.log('注册',
'用户名是:', this.register.username,
'密码是:', this.register.password
)
},
//点击登录按钮触发事件
onLogin() {
let result1 = this.validUsername(this.login.username)
if (!result1.isValid) {
this.login.isError = true
this.login.notice = result1.notice
return
}
let result2 = this.validPassword(this.login.password)
if (!result2.isValid) {
this.login.isError = true
this.login.notice = result2.notice
return
}
this.login.isError = false
this.login.notice = ''
console.log('登录',
'用户名是:', this.login.username,
'密码是:', this.login.password
)
},
//设计校验用户名、密码函数
validUsername(username) {
return {
isValid: /^[a-zA-Z_0-9]{3,15}$/.test(username),
notice: '用户名必须是3-15位数,且仅限字母、数字、下划线'
}
},
validPassword(password) {
return {
isValid: /^.{6,16}$/.test(password),
notice: '密码长度在6-16位以内'
}
}
}
</script>
在component
目录下新建helper\request.js
安装axios
npm install --save axios
在requset.js
引入axios
定义请求头和根路径
axios.defaults.headers.post['Content-Type'] = 'application/x-www-from-urlencoded'
axios.defaults.baseURL = 'https://note-server.hunger-valley.com/'
export default function request(url, type = 'GET', date = {}) {
return new Promise((resolve, reject) => {
let option = {
url,
method: type,
validateStatus(status) {
return (status >= 200 && status < 300) || status === 400
},
}
if (type.toLowerCase() === 'get') {
option.params = date
} else {
option.data = date
}
axios(option).then(res => {
if (res.status === 200) {
resolve(res.data)
} else {
console.log('11111')
console.log(res.data)
reject(res.data)
}
}).catch(err => {
console.log({msg: '网络异常'})
reject({msg: '网络异常'})
})
}
)
}
由于当前访问域名是http://localhost:...
而后端接口提供的域名是https://note-server.hunger-valley.com/
存在跨域,此时由于后端接口已经允许跨域请求
因此前端需要在request.js
再添加允许跨域,才能在登录请求的时候带上cookies
axios.defaults.withCredentials = true
新建src/apis/auth.js
import request from '../helpers/request'
const URL = {
REGISTER: '/auth/register',
LOGIN: '/auth/login',
LOGOUT: '/auth/logout',
GET_INFO: '/auth'
}
export default {
register({username, password}) {
return request(URL.REGISTER, 'POST', {username, password})
},
login({username, password}) {
return request(URL.LOGIN, 'POST', {username, password})
},
logout() {
return request(URL.LOGOUT)
},
getInfo() {
return request(URL.GET_INFO)
}
}
当需要调用接口时
Api.login({
username: this.login.username,
password: this.login.password
}).then(data => {
console.log(data)
})
在实际开发中,通常后端接口时没有写好的,这时候就需要用一些测试的url来进行对接,因此测试环境和生产环境的url不一致,经常需要手动切换很不方便。
新建build/mock.config.js
const fs = require('fs')
const path = require('path')
const mockBaseURL = 'http://localhost:3000'
const realBaseURL = 'https://note-server.hunger-valley.com/'
exports.config = function ({isDev = true} = {isDev: true}) {
let fileTxt = `
module.exports={
baseURL:'${isDev ? mockBaseURL : realBaseURL}'
}`
fs.writeFileSync(path.join(__dirname, '../src/helpers/config-baseURL.js'), fileTxt)
}
如何使用:
在build/webpack.dev.conf.js
添加
require('./mock.config').config({isDev: true})
在build/webpack.prod.conf.js
添加
require('./mock.config').config({isDev: false})
在helper/request.js
中引用
import baseURLConfig from './config-baseURL'
axios.defaults.baseURL = baseURLConfig
Api.register({
username: this.register.username,
password: this.register.password
}).then(data => {
this.register.isError = false
this.register.notice = ''
this.$router.push({path: '/notebooks'})
}).catch(data => {
this.register.isError = true
this.register.notice = data.msg
})
Api.login({
username: this.login.username,
password: this.login.password
}).then(data => {
this.login.isError = false
this.login.notice = ''
this.$router.push({path: '/notebooks'})
}).catch(data => {
console.log(data)
this.login.isError = true
this.login.notice = data.msg
})
新建src/helper/bus.js
import Vue from 'vue'
export default new Vue()
/* 使用方法:
import Bus from '../helper/bus'
绑定事件:
Bus.$on('test',msg=>{
console.log(msg)
})
触发事件:
Bus.$emit('test','hello')
*/
Login.vue
组件,绑定emit事件(onRegister 注册函数同理)
Api.login({
username: this.login.username,
password: this.login.password
}).then(data => {
this.login.isError = false
this.login.notice = ''
+ Bus.$emit('userInfo', {username: this.login.username})
this.$router.push({path: '/notebooks'})
}).catch(data => {
console.log(data)
this.login.isError = true
this.login.notice = data.msg
})
Avatar.vue
组件,触发on事件
export default {
data() {
return {
username: '未登录',
}
},
created() {
Bus.$on('userInfo', user => {
this.username = user.username
})
Auth.getInfo()
.then(res => {
if (res.isLogin) {
this.username = res.data.username
}
})
},
computed: {
slug() {
return this.username.charAt(0)
}
}
};
</script>
在components
的NotebookList.vue
、NoteDetail.vue
和TashDetail.vue
中,添加登录验证,未登录跳转至登录界面
<script>
import Auth from '../apis/auth'
export default {
name: 'Login',
data() {
return {
msg: '笔记本列表'
}
},
created() {
+ Auth.getInfo()
+ .then(res => {
+ if (!res.isLogin) {
+ this.$router.push({path: '/login'})
+ }
+ })
+ }
}
</script>
在src/components/NotebookList.vue
<template>
<div class="detail" id="notebook-list">
<header>
<a href="#" class="btn">
<i class="iconfont icon-plus"/>
新建笔记本
</a>
</header>
<main>
<div class="layout">
<h3>笔记本列表(10)</h3>
<div class="book-list">
<router-link to="/note/1" class="notebook">
<div>
<span class="iconfont icon-notebook"></span>文章标题
<span>3</span>
<span class="action">编辑</span>
<span class="action">删除</span>
<span class="date">3天前</span>
</div>
</router-link>
</div>
</div>
</main>
</div>
</template>
<style scoped lang="less">
#notebook-list {
flex: 1;
background: #eeedef;
.btn {
font-size: 12px;
color: #666;
cursor: pointer;
margin-left: 10px;
}
.btn .iconfont {
font-size: 12px;
}
input {
width: 300px;
height: 30px;
line-height: 30px;
border: 1px solid #ccc;
border-radius: 3px;
padding: 3px 5px;
outline: none;
}
header {
padding: 12px;
border-bottom: 1px solid #ccc;
}
main {
padding: 30px 40px;
}
.layout {
max-width: 966px;
margin: 0 auto;
}
main h3 {
font-size: 12px;
color: #000;
}
main .book-list {
margin-top: 10px;
font-size: 14px;
color: #666;
background-color: #fff;
border-radius: 4px;
font-weight: bold;
}
main .book-list span {
font-size: 12px;
font-weight: bold;
color: #b3c0c8;
}
main .date,
main .action {
float: right;
margin-left: 10px;
}
main .iconfont {
color: #1687ea;
margin-right: 4px;
}
main .notebook {
display: block;
cursor: pointer;
}
main a.notebook:hover {
background-color: #f7fafd;
}
main a.notebook div {
border-bottom: 1px solid #ebebeb;
padding: 12px 14px;
}
main .error-msg {
font-size: 12px;
color: red;
}
}
</style>
新建src/apis/notebooks.js
import request from '../helpers/request'
const URL = {
GET: '/notebooks',
ADD: '/notebooks',
UPDATE: '/notebooks/:id',
DELETE: '/notebooks/:id'
}
export default {
getAll() {
return new Promise((resolve, reject) => {
request(URL.GET)
.then(res => {
res.data = res.data.sort((notebook1, notebook2) => notebook1.createdAt < notebook2.createdAt)
res.data.forEach(notebook=>{
// notebook.friendlyCreatedAt = friendlyDate(notebook.createdAt)
})
resolve(res)
}).catch(err => {
reject(err)
})
})
},
updateNotebook(notebookId, { title = '' } = { title: '' }) {
return request(URL.UPDATE.replace(':id', notebookId), 'PATCH', { title })
},
deleteNotebook(notebookId) {
return request(URL.DELETE.replace(':id', notebookId), 'DELETE')
},
addNotebook({ title = ''} = { title: ''}) {
return request(URL.ADD, 'POST', { title })
}
}
在Notebooks.vue
中
<template>
<div class="detail" id="notebook-list">
<header>
<a href="#" class="btn" @click="onCreate">
<i class="iconfont icon-plus"/>
新建笔记本
</a>
</header>
<main>
<div class="layout">
<h3>笔记本列表({{notebooks.length}})</h3>
<div class="book-list">
<router-link to="/note/1" v-for="notebook in notebooks" :key="notebook.id" class="notebook">
<div>
<span class="iconfont icon-notebook"></span>{{notebook.title}}
<span>{{notebook.noteCounts}}</span>
<span class="action" @click="onEdit(notebook)" >编辑</span>
<span class="action" @click="onDelete(notebook)">删除</span>
<span class="date">3天前</span>
</div>
</router-link>
</div>
</div>
</main>
</div>
</template>
<script>
import Auth from '../apis/auth'
import Notebooks from '../apis/notebooks'
export default {
data() {
return {
notebooks: [],
msg: '笔记本列表'
}
},
created() {
Auth.getInfo()
.then(res => {
if (!res.isLogin) {
this.$router.push({path: '/login'})
}
})
+ Notebooks.getAll()
+ .then(res => {
+ console.log(res)
+ this.notebooks = res.data
+ })
},
methods: {
onCreate() {
console.log('1')
},
onEdit(notebook) {
console.log('2')
},
onDelete(notebook) {
console.log('3')
}
}
}
</script>
由于<a>
标签包裹了<span>
,因此在点击<span>
时会产生默认冒泡,需要阻止
<span class="action" @click.stop.prevent="onEdit(notebook)" >编辑</span>
<span class="action" @click.stop.prevent="onDelete(notebook)">删除</span>
新建时间优化函数src/hrlper/util.js
export function friendlyDate(dateStr) {
let dateObj = typeof dateStr === 'object' ? dateStr : new Date(dateStr)
let time = dateObj.getTime()
let now = Date.now()
let space = now - time
let str = ''
switch (true) {
case space < 1000 * 60 :
str = '刚刚'
break
case space < 1000 * 60 * 60 :
str = Math.floor((space) / (1000 * 60)) + '分钟前'
break
case space < 1000 * 60 * 60 * 24 :
str = Math.floor((space) / (1000 * 3600)) + '小时前'
break
default:
str = Math.floor((space) / (1000 * 3600 * 24)) + '天前'
break
}
return str
}
src/component/NotebookList.vue
methods: {
onCreate() {
let title = window.prompt('创建笔记本:')
if (title.trim() === '') {
alert('标题不能为空!')
return
}
Notebooks.addNotebook({title})
.then(res => {
this.notebooks.unshift(res.data)
res.data.friendlyCreatedAt = friendlyDate(res.data.createdAt)
alert(res.msg)
})
},
onEdit(notebook) {
let title = window.prompt('修改标题', notebook.title)
Notebooks.updateNotebook(notebook.id, {title})
.then(res => {
notebook.title = title
alert(res.msg)
})
},
onDelete(notebook) {
let isConfirm = window.confirm('你确定要删除吗?')
if (isConfirm) {
Notebooks.deleteNotebook(notebook.id)
.then(res => {
this.notebooks.splice(this.notebooks.indexOf(notebook), 1)
alert(res.msg)
})
}
}
}
src/apis/notebook.js
...
return new Promise((resolve, reject) => {
request(URL.GET)
.then(res => {
res.data = res.data.sort((notebook1, notebook2) => notebook1.createdAt > notebook2.createdAt ? -1 : 1)
res.data.forEach(notebook=>{
+ notebook.friendlyCreatedAt = friendlyDate(notebook.createdAt)
})
resolve(res)
}).catch(err => {
reject(err)
})
})
...
实际效果
-
安装element-ui
npm install element-ui --save
-
在
main.js
引入element-uiimport Vue from 'vue' + import ElementUI from 'element-ui' + import 'element-ui/lib/theme-chalk/index.css'; import App from './App' import router from './router' + Vue.use(ElementUI); Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: {App}, template: '<App/>' })
<h1>noteId : {{ $route.params.noteId }}</h1>
不知道你发现没有,项目做到这里,我们经常需要再不同的组件中都发请求,有时候是需要父子组件进行通讯,有时候是兄弟组件进行通讯,层级少的情况下还尚可,一旦层级变多,整个数据之间的流转就会变得非常复杂,所以引入vuex就变得十分必要。
vuex主要做的就是处理数据之间的分发
vuex的安装
npm install vuex@next --save
/或/
yarn add vuex@next --save
配置 vuex,使其工作起来:在src路径下创建store文件夹,然后创建index.js文件,文件内容如下:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 定义一个name,以供全局使用
name: '张三',
// 定义一个number,以供全局使用
number: 0,
// 定义一个list,以供全局使用
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
});
export default store;
修改main.js:
可以通过store.state
来获取状态对象,并通过 store.commit
方法触发状态变更:
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store'; // 引入我们前面导出的store对象
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store, // 把store对象添加到vue实例上
components: { App },
template: '<App/>'
});
最后修改App.vue
:
<template>
<div></div>
</template>
<script>
export default {
mounted() {
// 使用this.$store.state.XXX可以直接访问到仓库中的状态
console.log(this.$store.state.name); //打印出张三
}
}
</script>