V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
1340641314
V2EX  ›  分享发现

从零构建 vue2 + vue-router + vuex 开发环境到入门,实现基本的登录退出功能(二)

  •  
  •   1340641314 · 2016-12-03 14:25:45 +08:00 · 13725 次点击
    这是一个创建于 2914 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    国庆在回家的路上,得知了 vue2 发布了正式版,
    国庆回来后,在公司内两个项目便直接应用上了 vue2 ,
    一个是 PC 端的商户后台,一个是微信端商城,
    都是基于 Vue2 、 vue-router 、 vuex ......
    在开发的过程中,遇到了一系列的问题,
    比如页面后退数据还原,滚动条还原,
    登录超时,获取列表数据,表单提交,
    多台服务器自动化部署,最终后一个个解决了,
    能够平稳的从 react 切换到 vue2 开发, vue 的文档功不可没。
    

    github:https://github.com/lzxb/vue2-demo

    源码说明

    项目目录说明

    .
    |-- config                           // 项目开发环境配置
    |   |-- index.js                     // 项目打包部署配置
    |-- src                              // 源码目录
    |   |-- components                   // 公共组件
    |       |-- header.vue               // 页面头部公共组件
    |       |-- index.js                 // 加载各种公共组件
    |   |-- config                       // 路由配置和程序的基本信息配置
    |       |-- routes.js                // 配置页面路由
    |   |-- css                          // 各种 css 文件
    |       |-- common.css               // 全局通用 css 文件
    |   |-- iconfont                     // 各种字体图标
    |   |-- images                       // 公共图片
    |   |-- less                         // 各种 less 文件
    |       |-- common.less              // 全局通用 less 文件
    |   |-- pages                        // 页面组件
    |       |-- home                     // 个人中心
    |       |-- index                    // 网站首页
    |       |-- login                    // 登录
    |       |-- signout                  // 退出
    |   |-- store                        // vuex 的状态管理
    |       |-- index.js                 // 加载各种 store 模块
    |       |-- user.js                  // 用户 store
    |   |-- template                     // 各种 html 文件
    |       |-- index.html               // 程序入口 html 文件
    |   |-- util                         // 公共的 js 方法, vue 的 mixin 混合
    |   |-- app.vue                      // 页面入口文件
    |   |-- main.js                      // 程序入口文件,加载各种公共组件
    |-- .babelrc                         // ES6 语法编译配置
    |-- gulpfile.js                      // 启动,打包,部署,自动化构建
    |-- webpack.config.js                // 程序打包配置
    |-- server.js                        // 代理服务器配置
    |-- README.md                        // 项目说明
    |-- package.json                     // 配置项目相关信息,通过执行 npm init 命令创建
    .
    

    1.html 入口文件,源文件路径: src/template/index.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui">
    	<title>vue2-demo</title>
    </head>
    
    <body>
    	<div id="app">
    		<router-view></router-view>
    	</div>
    </body>
    
    </html>
    

    2.js 入口文件,源文件路径: src/main.js

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    import routes from './config/routes'
    import store from './store/'
    import components from './components/' //加载公共组件
    
    import './css/common.css'
    import './less/common.less'
    
    Object.keys(components).forEach((key) => {
        var name = key.replace(/(\w)/, (v) => v.toUpperCase()) //首字母大写
        Vue.component(`v${name}`, components[key])
    })
    
    Vue.use(VueRouter)
    
    const router = new VueRouter({
        routes
    })
    router.beforeEach(({meta, path}, from, next) => {
        var { auth = true } = meta
        var isLogin = Boolean(store.state.user.id) //true 用户已登录, false 用户未登录
    
        if (auth && !isLogin && path !== '/login') {
            return next({ path: '/login' })
        }
        next()
    })
    
    new Vue({ store, router }).$mount('#app')
    

    3.页面路由,权限配置,源文件路径: src/config/routes.js

    import App from '../app'
    
    export default [
        {
            path: '/',
            component: App,
            children: [
                {
                    path: '/login', //登录
                    meta: { auth: false },
                    component: resolve => require(['../pages/login/'], resolve)
                },
                {
                    path: '/signout', //退出
                    component: resolve => require(['../pages/signout/'], resolve)
                },
                {
                    path: '/home', //个人主页
                    component: resolve => require(['../pages/home/'], resolve)
                },
                {
                    path: '/', //首页
                    meta: { auth: false },
                    component: resolve => require(['../pages/index/'], resolve)
                },
                {
                    path: '*', //其他页面,强制跳转到登录页面
                    redirect: '/login'
                }
            ]
        }
    ]
    

    4.页面入口组件,源文件路径: src/app.vue

    <style lang="less" scoped>
    
    </style>
    <template>
        <router-view></router-view>
    </template>
    <script>
        export default {
            
        }
    </script>
    

    5.store 实例化,导入各种 modules ,源文件路径: src/store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    import user from './user'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
        strict: process.env.NODE_ENV !== 'production', //在非生产环境下,使用严格模式
        modules: {
            user
        }
    })
    

    6.定义 store user 模块,源文件路径: src/store/user.js

    import Vue from 'vue'
    
    export const USER_SIGNIN = 'USER_SIGNIN' //登录成功
    export const USER_SIGNOUT = 'USER_SIGNOUT' //退出登录
    
    export default {
        state: JSON.parse(sessionStorage.getItem('user')) || {},
        mutations: {
            [USER_SIGNIN](state, user) {
                sessionStorage.setItem('user', JSON.stringify(user))
                Object.assign(state, user)
            },
            [USER_SIGNOUT](state) {
                sessionStorage.removeItem('user')
                Object.keys(state).forEach(k => Vue.delete(state, k))
            }
        },
        actions: {
            [USER_SIGNIN]({commit}, user) {
                commit(USER_SIGNIN, user)
            },
            [USER_SIGNOUT]({commit}) {
                commit(USER_SIGNOUT)
            }
        }
    }
    

    7.加载各种公共组件,源文件路径: src/components/index.js

    import header from './header'
    export default { header }
    

    8.封装页面公共头部组件,源文件路径: src/components/header.js

    <style lang="less" scoped>
    	.header {
    		position: relative;
    		line-height: 38px;
    		color: #fff;
    		text-align: center;
    		background: #222;
    		.item {
    			position: absolute;
    			top: 0;
    			bottom: 0;
    			z-index: 1;
    			a {
    				color: #fff;
    			}
    		}
    		.left {
    			left: 10px;
    		}
    		.right {
    			right: 10px;
    		}
    	}
    </style>
    <template>
    	<header class="header">
    		<div class="item left">
    			<slot name="left"></slot>
    		</div>
    		<div class="title">{{title}}</div>
    		<div class="item right">
    			<slot name="right"></slot>
    		</div>
    	</header>
    </template>
    <script>
        export default {
            props: {
                title: {
                    type: String,
                    default: ''
                }
            }
        }
    </script>
    

    9.引入全局公共 css ,源文件路径: src/css/common.css

    input::-webkit-outer-spin-button,  
    input::-webkit-inner-spin-button{  
        -webkit-appearance: none !important;  
        margin: 0;  
    }  
    

    10.引入全局公共 less ,源文件路径: src/less/common.less

    * {
        padding: 0;
        margin: 0;
    }
    

    11.创建首页,,源文件路径: src/pages/index.vue

    <style lang="less" scoped>
    	.login-msg {
    		padding: 50px;
    		text-align: center;
    	}
    	.msg {
    		padding: 50px;
    		text-align: center;
    		font-size: 20px;
    		color: red;
    	}
    </style>
    <template>
    	<div>
    		<v-header title="首页">
    			<router-link slot="right" v-if="user.id" to="/home">{{user.name}}</router-link>
    		</v-header>
    		<div class="login-msg" v-if="!user.id">
    			<router-link to="/login">你还未登录,请先登录</router-link>
    		</div>
    		<div class="msg" v-if="user.id">
    			<img width="50" :src="logo" alt=""> <br>
    			哈哈,恭喜你已经入坑 Vue2
    		</div>
    	</div>
    </template>
    <script>
        import { mapState } from 'vuex'
    	import logo from './logo.png'
        export default {
    		data() {
    			return {
    				logo
    			}
    		},
            computed: mapState({ user: state => state.user }),
        }
    </script>
    

    12.创建登录页,,源文件路径: src/pages/login.vue

    <style lang="less" scoped>
    	.login {
    		padding: 50px;
    		text-align: center;
    		.line {
    			padding: 5px;
    			input {
    				padding: 0 10px;
    				line-height: 28px;
    			}
    		}
    		button {
    			padding: 0 20px;
    			margin-top: 20px;
    			line-height: 28px;
    		}
    	}
    </style>
    <template>
    	<div>
    		<v-header title="登录">
    			<router-link slot="left" to="/">返回</router-link>
    		</v-header>
    		<form class="login" v-on:submit.prevent="submit">
    			<div class="line">	
    				<div v-show="btn && !form.id">id 不能为空</div>
    				<input type="number" placeholder="输入你的 id" v-model="form.id">
    			</div>
    			<div class="line">
    				<div v-show="btn && !form.name">用户名不能为空</div>
    				<input type="text" placeholder="输入你的用户名" v-model="form.name">
    			</div>
    			<button>登录</button>
    		</form>
    	</div>
    </template>
    <script>
        import { mapActions } from 'vuex'
        import { USER_SIGNIN } from 'store/user'
    
        export default {
            data() {
    			return {
    				btn: false, //true 已经提交过, false 没有提交过
    				form: {
    					id: '',
    					name: ''
    				}
    			}
    		},
    		methods: {
                ...mapActions([USER_SIGNIN]),
    			submit() {
    				this.btn = true
    				if(!this.form.id || !this.form.name) return
    				this.USER_SIGNIN(this.form)
    				this.$router.replace({ path: '/home' })
    			}
    		}
        }
    </script>
    

    13.创建个人主页,,源文件路径: src/pages/home.vue

    <style lang="less" scoped>
    
    </style>
    <template>
    	<div>
    		<v-header title="首页">
    			<router-link slot="left" to="/">首页</router-link>
    			<router-link slot="right" to="/signout">退出</router-link>
    		</v-header>
    		<div style="padding: 50px;">{{user.name}}欢迎回家</div>
    	</div>
    </template>
    <script>
        import { mapState } from 'vuex'
        export default {
            computed: mapState({ user: state => state.user }),
        }
    </script>
    

    14.创建退出页,,源文件路径: src/pages/signout.vue

    <style lang="less" scoped>
    	.btn {
    		padding: 50px;
    		text-align: center;
    		button {
    			padding: 5px 10px;
    		}
    	}
    </style>
    <template>
    	<div>
    		<v-header title="退出">
    			<router-link slot="left" to="/home">返回</router-link>
    		</v-header>
    		<div class="btn">
    			<button v-on:click="submit">确认退出</button>
    		</div>
    	</div>
    </template>
    <script>
        import { mapActions } from 'vuex'
        import { USER_SIGNOUT } from 'store/user'
        export default {
            methods: {
                ...mapActions([USER_SIGNOUT]),
                submit() {
                    this.USER_SIGNOUT()
    				this.$router.replace({ path: '/login' })
                }
            }
        }
    </script>
    

    进击 vue2

    vue2 重构 cnode 社区,将会近日完成,
    一个从 0 构建的 vue2 的完整项目,
    可以使用最简单的方式实现页面后台时状态还原,
    局部滚动条还原等。
    

    原文地址:https://gold.xitu.io/post/5842554761ff4b006b90c399

    5 条回复    2018-03-13 08:56:02 +08:00
    Cbdy
        1
    Cbdy  
       2016-12-03 14:38:08 +08:00
    V2EX :有门的话不需要把内容转过来了。
    1340641314
        2
    1340641314  
    OP
       2016-12-03 14:49:26 +08:00
    ...找不到删除的地方
    linmi
        3
    linmi  
       2016-12-03 15:23:19 +08:00   ❤️ 1
    你得去掘金,开发者头条,极客头条发。
    1340641314
        4
    1340641314  
    OP
       2016-12-03 16:55:49 +08:00
    下次会注意的。无意冒犯了社区的精神
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5587 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 08:16 · PVG 16:16 · LAX 00:16 · JFK 03:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.