前端技术栈为vue全家桶(vuex+vue-router+vuelidate(简洁的校验库))+webpack+fetch+bootstrap(初次接触vue并没有找类似element之类的ui库)等,后端技术栈spring-boot+mybatis+spring-mvc等,完成了一个10个页面的简单订单系统, 前端模板来源于vue官方提供的webpack模板(热部署,生产打包,单元测试等),主要完成了注册,登录,细化到按钮级别的权限控制,excel上传解析, 表格查看展示等功能
A Vue.js+webpack project vue-simple
springboot-simple project
# install dependencies
npm install
# serve with hot reload at localhost:3333
npm run dev
# build for production with minification
npm run build
├─/build #webpack构建配置文件
├─/node_modules #项目依赖
├─/dist #项目生成物
├─/config #配置文件
├─/src #源码
│ ├─common #通用方法
│ ├─components #全局简单组件封装
│ ├─filters #全局过滤器
│ ├─pages #各个应用代码
│ │ ├─accounts
│ │ ├─cost
│ │ ├─customers
│ │ ├─fundsApp
│ │ ├─fundsSum
│ │ ├─home
│ │ ├─login
│ │ ├─orders
│ │ ├─permissions
│ │ ├─roles
│ │ └─users
│ └─store #vuex相关
└─static #静态文件
├─css
├─img
└─js
{ "name": "vue-webpack2.0", "version": "1.0.0", "description": "", "private": true, "scripts": { "dev": "node build/dev-server.js",//dev调试脚本 npm run dev "build": "node build/build.js" //生成生存生成物,包括压缩js,抽取样式等 }, "dependencies": { "vue": "^2.1.6", "vue-router": "^2.1.1", "vuelidate": "^0.2.0", "vuex": "^2.1.1" }, "devDependencies": { "alertifyjs": "^1.8.0", //消息提醒 "autoprefixer": "^6.4.0",//css兼容性 "babel-core": "^6.0.0", "babel-loader": "^6.0.0", "babel-plugin-istanbul": "^3.0.0", "babel-plugin-transform-object-assign": "^6.8.0", "babel-plugin-transform-runtime": "^6.0.0", "babel-preset-es2015": "^6.18.0", "babel-preset-stage-0": "^6.16.0", "babel-register": "^6.0.0", "bootstrap": "^3.3.7", "bootstrap-table": "^1.11.0", "chai": "^3.5.0", "chalk": "^1.1.3", "connect-history-api-fallback": "^1.1.0", "cross-env": "^3.1.3", "css-loader": "^0.25.0", "date-fns": "^1.27.1",//时间日期库 "eventsource-polyfill": "^0.9.6", "express": "^4.13.3",#node "extract-text-webpack-plugin": "^1.0.1",//用于抽取vue组件中的样式 "file-loader": "^0.9.0", "friendly-errors-webpack-plugin": "^1.1.2", "function-bind": "^1.0.2",//es7方法绑定插件 "html-webpack-plugin": "^2.8.1",//html模板插件 "http-proxy-middleware": "^0.17.2", "inject-loader": "^2.0.1", "jquery": "^3.1.1", "js-cookie": "^2.1.3",#cookie库 "json-loader": "^0.5.4", "lodash": "^4.17.2", "opn": "^4.0.2", "ora": "^0.3.0", "semver": "^5.3.0", "shelljs": "^0.7.4", "url-loader": "^0.5.7", "vue-loader": "^10.0.0", "vue-style-loader": "^1.0.0", "vue-template-compiler": "^2.1.0", "webpack": "^1.13.2", "webpack-dev-middleware": "^1.8.3", "webpack-dev-server": "^1.16.2", "webpack-merge": "^0.14.1" }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" } }
var path = require('path') var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') var webpack = require('webpack') var env = process.env.NODE_ENV // check env & config/index.js to decide whether to enable CSS source maps for the // various preprocessor loaders added to vue-loader at the end of this file var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) var useCssSourceMap = cssSourceMapDev || cssSourceMapProd module.exports = { entry: [ './src/main.js' #入口 ], output: {#生成物 path: config.build.assetsRoot, publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, filename: '[name].js' }, resolve: { extensions: ['', '.js', '.vue', '.json'],//后缀名 fallback: [path.join(__dirname, '../node_modules')],//寻找文件失败后回调 alias: {//设置别名 'vue$': 'vue/dist/vue.common.js', 'src': path.resolve(__dirname, '../src'), 'filters': path.resolve(__dirname, '../src/filters'), 'components': path.resolve(__dirname, '../src/components') } }, resolveLoader: { fallback: [path.join(__dirname, '../node_modules')] }, module: { loaders: [#设置各种文件loader { test: /\.vue$/, loader: 'vue' }, { test: /\.js$/, loader: 'babel', include: [ path.join(projectRoot, 'src'),//只编译src下源码 ], exclude: /node_modules/ }, { test: /\.json$/, loader: 'json' }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url', query: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url', query: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, vue: { loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), postcss: [ require('autoprefixer')({ browsers: ['last 2 versions'] //兼容浏览器前2个版本 }) ] }, plugins:[ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery" }) //将jquery设为全局变量 ] }
let format = require('date-fns/format'); Date.prototype.format = function (fmt="YYYY-MM-DD HH:mm:ss") { return format(this,fmt); }//时间格式化 window.post=(url,params,headers)=>{ return fetch(url, {method:'POST',credentials: 'include',headers: Object.assign({'Content-Type': 'application/json' },headers || {}),body:JSON.stringify(params || '')} ).then(v=>v.json()).catch(()=>{throw new Error(commonErrorMessage);}) }//封装fetch的post方法 window.formPost=(url,params)=>{ let body=''; if(params) body=Object.keys(params).map(key=>key+"="+params[key]).join("&"); return fetch(url, {method:'POST',credentials: 'include',headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' },body:body} ).then(v=>v.json()).catch(()=>{throw new Error(commonErrorMessage);}) }//封装post表单提交 window.get=(url,headers)=>{ return fetch(url,{ headers:headers || {} }) .then(v=>v.text() ).catch(()=>{throw new Error(commonErrorMessage);}) }//封装get方法 window.upload=(url,formData)=>{ if(!(formData instanceof FormData)) return new Promise(()=>{throw new Error('正文内容必须为FormData')}); return fetch(url,{ method: 'POST', body:formData, credentials: 'include' }).then(r=>{ return r.json(); }).catch(()=>{throw new Error(commonErrorMessage);}) }//封装上传 window.timeout=(duration = 0)=> { return new Promise(resolve=> setTimeout(resolve, duration) ) }//封装超时
项目入口为src/main.js
Vue.use(Vuelidate);//表单校验组件 Vue.use(VueRouter);//路由组件 Object.keys(components).forEach((key) => { var name = key.replace(/(\w)/, (v) => v.toUpperCase()) //首字母大写 Vue.component(`v${name}`, components[key]) }) //加载全局组件,v开头 Object.keys(filters).forEach((key) => { Vue.filter(key, filters[key]) }) //加载全局过滤器 const routes = [//配置路由 { path:'/', component:App, children: [ { path: 'main/home', component: home }, { path: 'main/users', component: users, }, { path: 'main/roles', component: roles }, { path: 'main/permissions', component: permissions }, { path: 'main/customers/sendInfo', component: sendInfo }, { path: 'main/customers/deliveryManInfo', component: deliveryManInfo }, { path: 'main/customers/personalInfo', component: personalInfo }, { path: 'main/orders', component: orders }, { path: 'main/orders/:orderNum', component: orders }, { path: 'main/accounts', component: accounts }, { path: 'main/accounts/:orderNum', component: accounts }, { path: 'main/fundsApp/fundsApplication', component: fundsApplication }, { path: 'main/fundsApp/fundsCheck', component: fundsCheck }, { path: 'main/fundsSum/fundsStatistics', component: fundsStatistics }, { path: 'main/cost/costMaintain/:orderNum', component: costMaintain }, { path: 'main/cost/costMaintain', component: costMaintain }, ] }, { path:'/goHome', component:goHome }, { path:'/#', meta: { auth: false }, component:login } ]; const router = new VueRouter({ routes }); //路由导航钩子,用于检查登录跳转 router.beforeEach(({meta, path}, from, next) => { var { auth = true } = meta; var isLogin = Boolean(store.state.user.name) //true用户已登录, false用户未登录 var loginReg=/^\/#/; if (auth && !isLogin && !loginReg.test(path)) { return next({ path: '/#' }) }else if(loginReg.test(path) && isLogin ){ return next({ path: '/main/home' }) } next() }); //挂载vue const vue=new Vue({ store, router, mounted(){ } }).$mount('#app');
For detailed explanation on how things work, checkout the guide,template and docs for vue-loader.