一个BS框架工程搭建笔记



一、 目录结构

根目录下设置两个目录,分别承载后端和前端两个工程。

(一)       后端

后端工程目录结构如下。


后端工程源自默认生成的express工程,目录结构如下:

【bin】

bin目录下保存的www文件是执行入口。启动方式是:node ./www

【node_modules】

【public】

程序将此目录挂载为’/’,前端工程中生成的文件均放置于此。

【routes】

存放路由文件。

【app.js】

app.js是主入口文件,这个文件中主要修改了路由。

【package.json】

 

(二)       前端

前端使用webpack打包,将结果输出为文件,使用Vue.js和Element UI组件。目录结构如下。

 

【dst目录】

存放各模块生成的目标js文件。

目录下设置index.html文件,用于测试,其余文件均为程序生成文件。

【node_modules目录】

【src目录】

存放源码,以子目录方式分割各模块。

模块子目录下有app.js、app.vue以及其他所需文件。

【package.json】

【webpack.common.js】

【webpack.dev.js】

【webpack.prod.js】

 

二、 源码实现

 

(一)       配置文件

 

【前端package.json】

修改内容如下:

修改scripts节,编写webpack打包脚本,增加babel-loader的配置。

{
  "name": "xxx-frontend",
  "version": "1.0.0",
  "description": "xxx",
  "scripts": {
    "dev": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
  "author": "wangxq",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "clean-webpack-plugin": "^0.1.17",
    "css-loader": "^0.28.7",
    "file-loader": "^0.11.2",
    "style-loader": "^0.18.2",
    "uglifyjs-webpack-plugin": "^0.4.6",
    "vue-loader": "^13.0.5",
    "vue-template-compiler": "^2.4.4",
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.8.2",
    "webpack-merge": "^4.1.0"
  },
  "dependencies": {
    "element-ui": "^1.4.6",
    "vue": "^2.4.4"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  }
}


【webpack.common.js】

webpack配置。

const path = require('path');


module.exports = {
    entry: {
        // 模块:desktop
        desktop: './src/desktop/app.js',
        // 模块:login
        login: './src/login/app.js',
    },
    output: {
        // 输出文件名:模块名.dst.js
        filename: '[name].dst.js',
        // 输出路径:当前路径下的dst目录
        path: path.resolve(__dirname, 'dst')
    },
    module: {
        rules: [
            {
                // vue-loader,处理vue文件
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                // vue-loader默认使用babel-loader处理js
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: path.join(__dirname, './node_modules')
            },
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
                loader: 'file-loader',
                query: {
                    // 目标路径是/ttfs,生成文件时会在dst目录下建立ttfs目录,将文件拷贝至后端public/ttfs目录下即可
                    name: '/ttfs/[name].[ext]?[hash]'
                }
            },
            {
                test: /\.(png|jpe?g|gif|svg)(\?\S*)?$/,
                loader: 'file-loader',
                query: {
                    // 目标路径是/images,生成文件时会在dst目录下建立images目录,将文件拷贝至后端public/images目录下即可
                    name: '/images/[name].[ext]?[hash]'
                }
            }
        ]
    }
};


【webpack.dev.js】

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  devServer: {
    contentBase: './dst'
  }
});


【webpack.prod.js】

const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
var webpack = require('webpack');

module.exports = merge(common, {
    plugins: [
        new UglifyJSPlugin(),
		new webpack.DefinePlugin({
			'process.env': {NODE_ENV:"'production'"}
		}),
    ],
})


(二)       后端模块源码示例

【路由示例】

login.js:

var express = require('express');
var path = require('path');
var router = express.Router();

/* 登录. */
router.get('/', function(req, res, next) {
    res.sendfile(path.join(__dirname, '../public/htmls/login.html'));
});

module.exports = router;


 

(三)       前端模块源码示例

以login模块为例,

在测试文件index.html中,加入以下代码。

<div id="login">
  <script type="text/javascript" src="login.dst.js"></script>
</div>
login.dst.js由webpack生成。
【app.js】
import Vue from 'vue'
import elementui from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
import app from './app.vue'

Vue.use(elementui);

new Vue({
    el:'#login',
    render: h => h(app)
});

从app.vue中导入默认模块并命名为app,使用app模块将html中id为login的节点进行渲染。

【app.vue】

将大部分的前端代码封入app.vue文件中,通过vue-loader进行预编译。

 

(四)       使用静态资源

【css资源】

可在app.js中使用语句导入。如:

import 'element-ui/lib/theme-default/index.css'

也可以在app.vue中通过style节配置。如:
<style scope>
  .image {
    width: 100%;
    display: block;
  }
  a{text-decoration:none;}
</style>

也可以在app.vue中引入。如:
<style src="./app.css">
</style>

或者
<style scope>
  @import "./app.css";
</style>

【文件资源】

文件资源,如图片。

可将文件放置在模块目录中,在app.vue的script节中用以下代码引用。

import TaskManager from "./TaskManager.jpg";

在webpack配置中,修改file-loader的配置:
{
    test: /\.(png|jpe?g|gif|svg)(\?\S*)?$/,
    loader: 'file-loader',
    query: {
        // 目标路径是/images,生成文件时会在dst目录下建立images目录,将文件拷贝至后端public/images目录下即可
        name: '/images/[name].[ext]?[hash]'
    }
}


 

(五)       前后端通讯

Vue官方建议使用axios,搜了一下网上大部分都是对官网样例的翻译,从头开始教学的几乎没有,学习之后感觉可以分为以下几个步骤。

【配置】

唯一必须配置的是baseURL项,标明请求的资源地址。更改默认配置代码:

axios.defaults.baseURL = 'https://xxxx';

【使用】

可以实例化,也可以直接应用全局实例。

实例化代码:

var instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});


全局应用:

axios.get('/tasks')
    .then(function (response) {
        console.log(response);
    })
    .catch(function (error) {
        console.log(error);
    });


或者:

Vue.prototype.$http = axios;

可直接在method定义的函数中使用。

methods: {
  postData () {
    this.$http({
      method: 'post',
      url: '/task',
      data: {
        name: 'task001',
        id: '001'
      }
   })
}

(六)      用户session管理

使用sessionStorage进行session管理,sessionStorage通过setItem( key, value)、getItem(key)、removeItem进行操作。

【登录记录session】

设置登录状态为用户ID。

// 验证用户...
sessionStorage.setItem('userID', userID);
this.$router.push({path:'/content'});

【登出时清除session】

sessionStorage.removeItem('userID');

【路由跳转时判断登录状态】

在app.js中,添加以下代码:

// 递归查找路由表routesArray中path为'routePath'的路由条目
var routePathInRoutes=function (routesArray, routePath) {
    for(var i in routesArray){
        if(routesArray[i].path == routePath){
            return routesArray[i];
        }
        if(routesArray[i].children != null){
            return routePathInRoutes(routesArray[i].children, routePath);
        }
    }
    return null;
}
router.beforeEach(function (to, from, next) {
    if(to.path == '/login') {
        sessionStorage.removeItem('userID');
    }
    else{
        var userID = sessionStorage.getItem('userID');
        if((userID == null) || (userID == '')){
            // 尚未登录,检查跳转路由是否需要认证
            var route = routePathInRoutes(routes, to.path);
            if (route !=null){
                if(route.auth){
                    next({path:'/login'});
                }
            }
        }
    }
    next();
})


(七)      使用vuex在前端保存状态信息

使用vuex在前端进行状态信息的管理。在前端设置目录store,其下设置文件index.js和session.js。

在app.js中引入vuex组件和index.js。

import Vuex from 'vuex'
import store from './store/index'
Vue.use(Vuex);
new Vue({
    el:'#home',
    router,
    store,
    render: h => h(app)
});

在index.js中定义各个组件,如session(登录状态的详细信息)、auth(认证相关信息)。

import Vue from 'vue'
import Vuex from 'vuex'

import session from "./session"

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
    modules: {
        session
    },
    strict: debug,
})

在session.js中定义具体的信息

/*
登录的用户信息
 */

const state={
    // 当前登录的用户信息
    user:{},
    userID:'',
    userName:'',
    userPwd:'',
}

const getters={
    userID: state => state.user.userID,
    userName: state => state.user.userName,
    userPwd: state => state.user.userPwd,
}

const mutations={
    removeUser(state){
        state.user={};
    },
    setUser(state, user) {
        state.user=user;
    },
}

export default{
    state,
    getters,
    mutations
}
在登录时,更新信息。
this.$store.commit('setUser', {
    userID: guid,
    userName: name,
    userPwd: pwd,
    });
在注销时,清除信息。
this.$store.commit('removeUser');





三、 问题

(一)       webpack run build报uglifyjs unexpected token错误

需要使用babel选项:"presets":["es2015"],可以在.babelrc文件中添加,也可以在package.json中添加。

(二)       vue-router嵌套路由的展现问题

使用vue-router嵌套路由时,需要逐级展现。

【routers.js】

import demos from "./demos.vue"
import datagrid from"./datagrid.vue"
import demosabout from"./demosabout.vue"
   {
      path: '/demos',
      component:demos,
      name: 'demos',
      icon: 'el-icon-more',
      children:[
   {
      path:'/demos/demosabout',
      component:demosabout,
      name:'about',
      icon: 'el-icon-share'
   },
   {
      path:'/demos/datagrid',
      component:datagrid,
      name:'数据表格',
      icon: 'el-icon-share'
   }
      ] 
export default routes;


首先在指向/home路由的页面中使用<router-view></router-view>标签进行渲染,此时将渲染出demons.vue中定义的组件,这时需要在demons.vue中再次使用<router-view></router-view>进行渲染,才可渲染出/demons/xxxx等组件。
【demons.vue】

<template>
    <div>
      <h1>{{title}}</h1>
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    data() {
        return {
            title: 'demos home page'
        };
    },
}
</script>

(三)       跨域访问问题

出现Access-Control-Allow-Origin错误,原因是默认情况下服务器都是不允许跨域访问的,如网站http://a.com访问http://b.com/records/,是不允许的,解决方法是在服务器后端设置允许跨域访问,即:在路由中设置。

router.get('/', function(req, res, next) {
    // 允许跨域访问
    res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.json(mgr.allUsrs());
});

也可以通过app.use设定全局开放。

app.use('/api', function (req,res,next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    next();
})

注意:app.use()和app.all()的区别是use()将匹配以/api开头的所有路由,all()则仅匹配/api路由。

(四)       axios异步访问无法访问vue实例的问题

axio是异步调用的,在其.then函数作用域中是无法访问vue实例的this引用的,解决方法很简单,只需在then的函数体上绑定this即可。代码如下:

axios.get('/tasks')
    .then(function (response) {
        for(var i in response.data)
        {
            var t = new Object();
            t['guid'] = response.data[i]['guid'];
            t['name'] = response.data[i]['name'];
            t['remark'] = response.data[i]['remark'];
            this.tasks.push(t);
        }
    }.bind(this))
    .catch(function (error) {
        console.log(error);
    });


(五)       vue报template compiler is not available的问题

在vue实例中应用template模板语法,报以下错误。

debug.dst.js:6[Vue warn]: You are using the runtime-only build of Vue where the templatecompiler is not available. Either pre-compile the templates into renderfunctions, or use the compiler-included build.

这个在vue/dist/README.md中描述的比较清楚了,Compiler版本、Runtime版本,简单说一下区别,看例子。

new Vue({
    el:'#debug',
    template:'<div><h1>{{a}}</h1></div>',
    data:{
        a:'aaaaaa',
    }
});


像上述例子中需要对template字符串进行模板渲染(或者将绑定的html对象作为模板进行渲染),则必须使用Compiler版本。

而使用vue-loader加载.vue文件的情况,webppack在打包时已经对模板进行了渲染,因此仅需Runtime版本即可。

使用Compiler版本的方法在README.md中也已给出,就是在打包配置文件中加入精确匹配解析项,以webpack为例。

module.exports = {
  // ...
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
    }
  }
}


Vue推荐使用runtime版本。


四、 引用

 

https://vue-loader.vuejs.org/zh-cn/

https://doc.webpack-china.org

http://www.cnblogs.com/tugenhua0707/p/4793265.html

https://github.com/jantimon/html-webpack-plugin

https://www.npmjs.com/package/axios

http://json.is/


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值