来源于:阿贤博客javascript
下一代web开发框架
npm install easywebpack-cli -g
npm install
npm run dev
npm run build
npm start
├── app │ ├── controller //控制器(模式:C层) │ │ ├── test │ │ └── test.js │ ├── models //数据模型(模式:M层) │ │ ├── api //接口api │ │ ├── mocks //数据处理 │ │ ├── app │ │ └── home │ ├── extend │ ├── lib │ ├── middleware │ ├── mocks │ ├── proxy │ ├── router.js //服务器端路由 │ ├── view //视图(模式:V层,工具生成) │ │ ├── about // 服务器编译的jsbundle文件 │ │ │ └── about.js │ │ ├── home │ │ │ └── home.js // 服务器编译的jsbundle文件 │ │ └── layout // 用于根据指定的layout生成对应的html页面, 用于服务器渲染失败时,采用客户端渲染 │ │ └── layout.html │ └── web //视图(前端工程目录开发-->生成模式:V层) │ ├── asset // 存放公共js,css资源 │ ├── framework // 前端公共库和第三方库 │ │ ├── fastclick │ │ │ └── fastclick.js │ │ ├── sdk │ │ │ ├── sdk.js │ │ ├── storage │ │ │ └── storage.js │ │ └── vue // 与vue相关的公开代码 │ │ ├── app.js // 先后端调用入口, 默认引入componet/directive/filter │ │ ├── component.js // 组件入口, 能够增长component目录,相似下面的directive │ │ ├── directive // directive 目录,存放各类directive组件 │ │ ├── directive.js // directive引用入口 │ │ └── filter.js // filter引用入口 │ ├── page // 前端页面和webpack构建目录, 也就是webpack打包配置entryDir │ │ ├── home // 每一个页面遵循目录名, js文件名, scss文件名, vue文件名相同 │ │ │ ├── home.scss │ │ │ ├── home.vue │ │ │ ├── images // 页面自有图片,公共图片和css放到asset下面 │ │ │ │ └── icon_more.png │ │ │ └── w-week // 页面自有组件,公共组件放到widget下面 │ │ │ ├── w-week.scss │ │ │ └── w-week.vue │ │ └── test // 每一个页面遵循目录名, js文件名, scss文件名, vue文件名相同 │ │ └── test.vue │ ├── store // 引入vuex 的基本规范, 能够分模块 │ │ ├── app │ │ │ ├── actions.js │ │ │ ├── getters.js │ │ │ ├── index.js │ │ │ ├── mutation-type.js │ │ │ └── mutations.js │ │ └── store.js │ └── component // 公共业务组件, 好比loading, toast等, 遵循目录名, js文件名, scss文件名, vue文件名相同 │ ├── loading │ │ ├── loading.scss │ │ └── loading.vue │ ├── test │ │ ├── test.vue │ │ └── test.scss │ └── toast │ ├── toast.scss │ └── toast.vue ├── build // webpack 自定义配置入口, 会与默认配置进行合并(看似这么多,其实这里只是占个位说明一下) │ ├── base │ │ └── index.js // 公共配置 │ ├── client // 客户端webpack编译配置 │ │ ├── dev.js │ │ ├── prod.js │ │ └── index.js │ ├── server // 服务端webpack编译配置 │ │ ├── dev.js │ │ ├── prod.js │ │ └── index.js │ └── index.js ├── config │ ├── config.default.js │ ├── config.local.js │ ├── config.prod.js │ ├── config.test.js │ └── plugin.js ├── doc ├── index.js ├── public // webpack编译目录结构, render文件查找目录 │ ├── manifest.json // 资源依赖表 │ ├── static │ │ ├── css │ │ │ ├── home │ │ │ │ ├── home.07012d33.css │ │ │ └── test │ │ │ ├── test.4bbb32ce.css │ │ ├── img │ │ │ ├── change_top.4735c57.png │ │ │ └── intro.0e66266.png │ ├── test │ │ └── test.js │ └── vendor.js // 生成的公共打包库
/*文件目录:project/app/router.js*/ module.exports = app => { app.get('/', app.controller.home.home.index); app.get('/app/api/article/list', app.controller.app.app.list); app.get('/app/api/article/:id', app.controller.app.app.detail); app.get('/app(/.+)?', app.controller.app.app.index); };
/*文件目录:project/app/controller/home/home.js*/ const Controller = require('egg').Controller; const Mocks = require('../../models/mocks/home/init'); class HomeController extends Controller { mocks(){ const { ctx } = this; return new Mocks({ctx: ctx}); } async index() {//页面 const { ctx } = this; const mocks = this.mocks(); await ctx.render('index/index.js', { title: 'egg vue ssr', list: await mocks.index() //发接口获取的数据 }); } async pager() {//接口 const { ctx } = this; const mocks = this.mocks(); const pageIndex = ctx.query.pageIndex; const pageSize = ctx.query.pageSize; ctx.body = await mocks.index();//发接口获取的数据 } }; module.exports = HomeController;
//获取(46),好比:http://localhost:7001/app/detail/46 const id = this.ctx.params.id;
//获取(20),好比:http://localhost:7001/?page=20 const page = this.ctx.query.page;
//获取(20),好比:http://localhost:7001/?page=20 const protocol = this.ctx.protocol;
await ctx.render('app/app.js', {});
this.ctx.body = {title: '接口'};
了解更多php
/* 文件目录:project/app/models/api/api.js */ const axios = require('axios'); const Config = require('../../config/config'); class Api { constructor(opts) { console.log(88777); } fetch(_opt) { var param = '', opt = Object.assign({ baseHost: Config.apiHost, protocol: Config.apiProtocol, urlMap: '', url: '', method: 'GET', type: 'json', cookies: true, //Boolean timeout: 10000, param: null, //{id: 123} paramType: 0 // 0表示参数以字符串形式提交好比“wen=12&xx=333”; 1表示参数以对象形式提交好比“{wen:12, xx:33}” }, _opt); //考虑redis缓存处理 if (opt.urlMap !== '') { opt.url = `${opt.baseHost}${opt.urlMap}`; } if (opt.protocol === 'https') { opt.url = (opt.url).replace(/^http:(\/\/[\w])/, 'https:$1'); } console.log('opt.url', opt.url); if (opt.param !== null) { for (var key in opt.param) { if (typeof opt.param[key] !== 'function') { param += '&'+key+'='+ encodeURIComponent(opt.param[key]); } } param = param.substring(1); } return axios(opt.url, { method: opt.method, timeout: opt.timeout, data: (opt.paramType === 0) ? param : opt.param, withCredentials: opt.cookies }).then(function(response) { return response.data; }).catch(function(ex) { return { error: true, url: ex }; }); } } //export default Api; module.exports = new Api();
/*目录文件:project/app/models/mocks/app/init.js*/ const { fetch } = require('../../api/api'); class Mocks { constructor(suppor) { this.ctx = suppor.ctx; } index(){ let urlMap = '/menus', protocol = this.ctx.protocol; return fetch({ url: 'http://m.aipai.com/mobile/apps/apps.php?module=gameIndex&func=newAsset&sort=click&appId=11616&page=3&pageSize=12', //urlMap: urlMap, protocol: protocol,//ctx.protocol, method: 'get', type: "jsonp", //param: param }).then(ret=>{ //访问超时or资源地址出错 if(typeof ret.error !== 'undefined' && ret.error){ //msg = '网络错误'; }else{ } return ret; }); } }; module.exports = Mocks;
//使用计算属性 computed: { title(){ return this.serverData.title; }, lists(){ return this.serverData.list; } },
/*目录文件:project/app/web/page/app/views/index.vue*/ preFetch ({ state, dispatch, commit }) {//只在服务器端执行; preFetch比created执行快 return Promise.all([ dispatch('FETCH_ARTICLE_LIST_PRE') ]) }, beforeMount() {//只在客户端执行; created比beforeMount执行快 let serverData = this.$store.state.serverData; if(serverData.articleList && serverData.articleList.length >0){ this.$store.commit('SET_ARTICLE_LIST', serverData.articleList); }else{ return Promise.all([ this.$store.dispatch('FETCH_ARTICLE_LIST') ]); } }
<layout :layoutData="layoutData"> <router-view></router-view> <!-- <transition name="fade" mode="out-in"> </transition> --> </layout>
/*目录文件:project/app/web/page/app/app.vue*/ computed: { layoutData() { let state = this.$store.state; return { title: state.serverData.title+": 2018世界杯大数据报告", keywords: "keywords", description: "description", pluginCss: ['//www.xxx.com/static/index.min.css'], pluginJs: ["//www.xxx.com/static/libs/zepto.min.1.2.0.js"], pluginFooterJs: ["//www.xxx.com/static/libs/zepto.min.1.2.0.js"], }; } },
备注: 服务端与客户端数据经过渲染window.__INITIAL_STATE__来桥接的
来源于:阿贤博客css