├── app ├── view │ ├── .gitkeep │ └── README.md ├── typings │ ├── global.d.ts │ ├── module.d.ts │ └── ves.d.ts ├── web │ ├── index.d.ts │ ├── page │ │ ├── store │ │ │ ├── state.ts │ │ │ ├── modules │ │ │ │ └── admin │ │ │ │ │ ├── state.ts │ │ │ │ │ ├── type.ts │ │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── admin │ │ │ ├── home │ │ │ ├── view │ │ │ │ ├── home │ │ │ │ │ ├── index.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── notfound │ │ │ │ │ └── index.vue │ │ │ │ ├── dashboard │ │ │ │ │ └── index.vue │ │ │ │ ├── detail │ │ │ │ │ ├── index.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── write │ │ │ │ │ ├── index.vue │ │ │ │ │ └── index.ts │ │ │ │ └── list │ │ │ │ │ ├── index.ts │ │ │ │ │ └── index.vue │ │ │ ├── index.ts │ │ │ ├── component │ │ │ │ ├── panel.ts │ │ │ │ └── panel.vue │ │ │ └── router │ │ │ │ └── index.ts │ │ │ └── login │ │ │ ├── login.ts │ │ │ ├── login.css │ │ │ └── login.vue │ ├── typings │ │ ├── vue-shims.d.ts │ │ └── global.d.ts │ ├── asset │ │ ├── images │ │ │ ├── logo.png │ │ │ ├── favicon.ico │ │ │ └── loading.gif │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ ├── glyphicons-halflings-regular.woff2 │ │ │ └── glyphicons-halflings-regular.svg │ ├── component │ │ ├── layout │ │ │ ├── admin │ │ │ │ ├── content │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── index.vue │ │ │ │ │ └── index.css │ │ │ │ ├── main │ │ │ │ │ ├── index.ts │ │ │ │ │ └── index.vue │ │ │ │ ├── header │ │ │ │ │ ├── header.ts │ │ │ │ │ ├── header.css │ │ │ │ │ └── header.vue │ │ │ │ ├── index.css │ │ │ │ ├── footer │ │ │ │ │ ├── footer.vue │ │ │ │ │ └── footer.css │ │ │ │ ├── index.ts │ │ │ │ ├── menu │ │ │ │ │ ├── menu.ts │ │ │ │ │ └── menu.vue │ │ │ │ └── index.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ └── MarkdownEditor │ │ │ ├── index.vue │ │ │ └── index.ts │ ├── view │ │ └── layout.html │ ├── tsconfig.json │ └── framework │ │ └── app.ts ├── lib │ ├── db │ │ ├── mongo.ts │ │ ├── mysql.ts │ │ ├── factory.ts │ │ ├── base.ts │ │ ├── collection.ts │ │ └── file.ts │ └── condition.ts ├── extend │ ├── context.ts │ └── application.ts ├── middleware │ ├── global.ts │ └── access.ts ├── router.ts ├── model │ └── article.ts ├── controller │ └── admin.ts └── service │ └── article.ts ├── History.md ├── config ├── plugin.ts ├── config.test.ts ├── plugin.local.ts ├── config.prod.ts ├── config.local.ts ├── config.default.ts └── tsconfig.json ├── .gitattributes ├── docs ├── images │ ├── vue-node.png │ ├── vue-admin-ui.png │ └── vue-front-end.png └── issue_template.md ├── typings ├── app │ ├── index.d.ts │ ├── controller │ │ └── index.d.ts │ ├── model │ │ └── index.d.ts │ ├── extend │ │ ├── context.d.ts │ │ └── application.d.ts │ ├── middleware │ │ └── index.d.ts │ └── service │ │ └── index.d.ts └── config │ └── plugin.d.ts ├── .gitignore ├── test └── lowdb.test.js ├── .vscode ├── settings.json └── launch.json ├── CHANGELOG.md ├── tslint.json ├── tsconfig.json ├── webpack.config.js ├── LICENSE ├── README.md ├── package.json └── blog.json /app/view/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/typings/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lodash-id'; -------------------------------------------------------------------------------- /app/view/README.md: -------------------------------------------------------------------------------- 1 | ## egg规范view目录, 保证view文件夹存在, 否则app.config.view.root为空, 编译服务器文件会存放到该目录. -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0 / 2018-01-17 4 | ================== 5 | 6 | * feat:init 7 | -------------------------------------------------------------------------------- /config/plugin.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | vuessr: { 3 | package: 'egg-view-vue-ssr' 4 | } 5 | }; -------------------------------------------------------------------------------- /app/web/index.d.ts: -------------------------------------------------------------------------------- 1 | declare var EASY_ENV_IS_NODE: boolean; 2 | type PlainObject = { [key: string]: T }; -------------------------------------------------------------------------------- /app/web/page/store/state.ts: -------------------------------------------------------------------------------- 1 | export default interface RootState { 2 | origin: string; 3 | csrf: string; 4 | } -------------------------------------------------------------------------------- /app/web/typings/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=javascript 2 | *.css linguist-language=javascript 3 | *.html linguist-language=javascript -------------------------------------------------------------------------------- /docs/images/vue-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/docs/images/vue-node.png -------------------------------------------------------------------------------- /docs/images/vue-admin-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/docs/images/vue-admin-ui.png -------------------------------------------------------------------------------- /app/web/asset/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/images/logo.png -------------------------------------------------------------------------------- /docs/images/vue-front-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/docs/images/vue-front-end.png -------------------------------------------------------------------------------- /app/web/asset/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/images/favicon.ico -------------------------------------------------------------------------------- /app/web/asset/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/images/loading.gif -------------------------------------------------------------------------------- /app/web/component/layout/admin/content/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | export default class Content extends Vue {}; -------------------------------------------------------------------------------- /app/web/asset/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/web/asset/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/web/asset/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/web/asset/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/web/asset/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/web/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare var window: Window; 2 | declare var EASY_ENV_IS_NODE: boolean; 3 | 4 | interface Window { 5 | __INITIAL_STATE__: any; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /app/web/asset/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/web/asset/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/web/asset/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/lib/db/mongo.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import BaseDB from './base'; 3 | export default class MongoDB extends BaseDB { 4 | constructor(name: string) { 5 | super(name); 6 | } 7 | } -------------------------------------------------------------------------------- /app/lib/db/mysql.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import BaseDB from './base'; 3 | export default class MySQLDB extends BaseDB { 4 | constructor(name?: string) { 5 | super(name); 6 | } 7 | } -------------------------------------------------------------------------------- /app/web/asset/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easy-team/egg-vue-typescript-boilerplate/HEAD/app/web/asset/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /typings/app/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | export * from 'egg'; 6 | export as namespace Egg; 7 | -------------------------------------------------------------------------------- /config/config.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Application, EggAppConfig } from 'egg'; 3 | 4 | export default (appInfo: EggAppConfig) => { 5 | const exports: any = {}; 6 | 7 | return exports; 8 | }; 9 | -------------------------------------------------------------------------------- /app/extend/context.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { Context } from 'egg'; 3 | import DB from '../lib/db/base'; 4 | export default { 5 | get db(): DB { 6 | return (this as Context).app.db; 7 | } 8 | }; -------------------------------------------------------------------------------- /app/typings/ves.d.ts: -------------------------------------------------------------------------------- 1 | import DB from '../lib/db/base'; 2 | declare module 'egg' { 3 | interface Application { 4 | db: DB; 5 | } 6 | 7 | interface Context { 8 | db: DB; 9 | } 10 | } -------------------------------------------------------------------------------- /app/web/page/store/modules/admin/state.ts: -------------------------------------------------------------------------------- 1 | import Article from '../../../../../model/article'; 2 | 3 | export default interface AdminState { 4 | articleTotal: number; 5 | articleList: Article[]; 6 | article?: Article; 7 | } -------------------------------------------------------------------------------- /config/plugin.local.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | cors: { 3 | package: 'egg-cors' 4 | }, 5 | webpack: { 6 | package: 'egg-webpack' 7 | }, 8 | webpackvue : { 9 | package: 'egg-webpack-vue' 10 | } 11 | }; -------------------------------------------------------------------------------- /app/web/page/admin/home/view/home/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /app/web/page/store/modules/admin/type.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | export const SET_ARTICLE_LIST = 'SET_ARTICLE_LIST'; 3 | export const SET_ARTICLE_DETAIL = 'SET_ARTICLE_DETAIL'; 4 | export const SET_SAVE_ARTICLE = 'SET_SAVE_ARTICLE'; 5 | export const DELETE_ARTICLE = 'DELETE_ARTICLE'; -------------------------------------------------------------------------------- /app/middleware/global.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | export default () => { 3 | return async function global(ctx: Context, next: any) { 4 | ctx.locals.locale = ctx.query.locale || 'cn'; 5 | ctx.locals.origin = ctx.request.origin; 6 | await next(); 7 | }; 8 | }; -------------------------------------------------------------------------------- /app/web/page/admin/home/view/home/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Emit } from 'vue-property-decorator'; 2 | import Layout from 'component/layout/admin/index.vue'; 3 | @Component({ 4 | components: { 5 | Layout 6 | } 7 | }) 8 | 9 | export default class Home extends Vue {} -------------------------------------------------------------------------------- /config/config.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * production 3 | * 4 | * prod + default(override) 5 | */ 6 | 7 | import { Application, EggAppConfig } from 'egg'; 8 | 9 | export default (appInfo: EggAppConfig) => { 10 | const exports: any = {}; 11 | 12 | return exports; 13 | }; 14 | -------------------------------------------------------------------------------- /app/web/page/admin/home/view/notfound/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/web/page/admin/home/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import App from '@framework/app'; 3 | import createStore from '@store/index'; 4 | import createRouter from '@router/index'; 5 | import entry from '@view/home/index.vue'; 6 | export default new App({ entry, createStore, createRouter }).bootstrap(); -------------------------------------------------------------------------------- /docs/issue_template.md: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /typings/app/controller/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportAdmin from '../../../app/controller/admin'; 6 | 7 | declare module 'egg' { 8 | interface IController { 9 | admin: ExportAdmin; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/content/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/web/page/admin/home/component/panel.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { Vue, Component, Emit } from 'vue-property-decorator'; 3 | 4 | @Component 5 | export default class Panel extends Vue { 6 | @Emit('handleSetLineChartData') 7 | private handleSetLineChartData(type: string) { 8 | console.log(type); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /typings/app/model/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportArticle from '../../../app/model/article'; 6 | 7 | declare module 'egg' { 8 | interface IModel { 9 | Article: ReturnType; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /typings/app/extend/context.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExtendContext from '../../../app/extend/context'; 6 | type ExtendContextType = typeof ExtendContext; 7 | declare module 'egg' { 8 | interface Context extends ExtendContextType { } 9 | } -------------------------------------------------------------------------------- /typings/app/extend/application.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExtendApplication from '../../../app/extend/application'; 6 | type ExtendApplicationType = typeof ExtendApplication; 7 | declare module 'egg' { 8 | interface Application extends ExtendApplicationType { } 9 | } -------------------------------------------------------------------------------- /app/web/component/layout/admin/main/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Prop } from 'vue-property-decorator'; 2 | import LayoutHeader from '../header/header.vue'; 3 | import LayoutContent from '../content/index.vue'; 4 | 5 | @Component({ 6 | components: { 7 | LayoutHeader, 8 | LayoutContent 9 | } 10 | }) 11 | export default class Main extends Vue {} -------------------------------------------------------------------------------- /app/lib/db/factory.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import FileDB from './file'; 3 | import MongoDB from './mongo'; 4 | import MySQLDB from './mysql'; 5 | export default function(type?: string) { 6 | switch (type) { 7 | case 'mysql': 8 | return new MySQLDB(); 9 | case 'mongo': 10 | return new MySQLDB(); 11 | default: 12 | return new FileDB(); 13 | } 14 | } -------------------------------------------------------------------------------- /app/extend/application.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import DB from '../lib/db/base'; 3 | import DBFactory from '../lib/db/factory'; 4 | const DBSymbol = Symbol('Application#db'); 5 | export default { 6 | get db(): DB { 7 | // if (!this[DBSymbol]) { 8 | // this[DBSymbol] = DBFactory(); 9 | // } 10 | // return this[DBSymbol]; 11 | return DBFactory(); 12 | } 13 | }; -------------------------------------------------------------------------------- /app/web/page/admin/home/view/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | 17 | -------------------------------------------------------------------------------- /typings/app/middleware/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import ExportAccess from '../../../app/middleware/access'; 6 | import ExportGlobal from '../../../app/middleware/global'; 7 | 8 | declare module 'egg' { 9 | interface IMiddleware { 10 | access: typeof ExportAccess; 11 | global: typeof ExportGlobal; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/header/header.ts: -------------------------------------------------------------------------------- 1 | import './header.css'; 2 | import LeftMenu from '../menu/menu.vue'; 3 | import { Vue, Component, Prop } from 'vue-property-decorator'; 4 | 5 | @Component({ 6 | components: { 7 | LeftMenu 8 | } 9 | }) 10 | export default class Header extends Vue { 11 | collapse: boolean = false; 12 | site: any = { name: 'Egg + Vue + TypeScript' }; 13 | logout() { 14 | window.location.replace('/login'); 15 | } 16 | } -------------------------------------------------------------------------------- /app/web/page/admin/login/login.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Prop } from 'vue-property-decorator'; 2 | import { Action } from 'vuex-class'; 3 | // @ts-ignore 4 | import Layout from '../../../component/layout/index.vue'; 5 | import './login.css'; 6 | @Component({ 7 | components: { 8 | Layout 9 | } 10 | }) 11 | export default class Login extends Vue { 12 | username: string = 'admin'; 13 | password: string = 'admin'; 14 | remenber: boolean = false; 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .happypack/ 3 | node_modules/ 4 | npm-debug.log 5 | .idea/ 6 | dist 7 | static 8 | public 9 | private 10 | run 11 | *.iml 12 | artifacts.json 13 | *tmp 14 | _site 15 | logs 16 | app/**/*.js 17 | config/plugin.local.js 18 | config/plugin.js 19 | config/config.*.js 20 | index.js 21 | config/manifest.json 22 | app/view/* 23 | !app/view/layout.html 24 | !app/view/README.md 25 | !app/view/.gitkeep 26 | package-lock.json 27 | yarn.lock 28 | *.log 29 | coverage -------------------------------------------------------------------------------- /test/lowdb.test.js: -------------------------------------------------------------------------------- 1 | const lowdb = require('lowdb'); 2 | const FileSync = require('lowdb/adapters/FileSync'); 3 | const file = new FileSync('blog.json'); 4 | const db = lowdb(file); 5 | db._.mixin({ 6 | like(array, predicate){ 7 | Object.keys(predicate).forEach(item => { 8 | 9 | }); 10 | } 11 | }) 12 | const result = db.get('article') 13 | .filter(item => { 14 | return item.title && item.title.indexOf('webpack')>-1; 15 | }) 16 | .value(); 17 | 18 | console.log(result); -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "USE_GITIGNORE": true, 4 | "**/*.js": { 5 | "when": "$(basename).ts" 6 | } 7 | }, 8 | "path-intellisense.mappings": { 9 | "lib": "${workspaceRoot}/app/web/lib", 10 | "asset": "${workspaceRoot}/app/web/asset", 11 | "component": "${workspaceRoot}/app/web/component", 12 | "page": "${workspaceRoot}/app/web/page", 13 | "store": "${workspaceRoot}/app/web/store", 14 | }, 15 | "typescript.tsdk": "node_modules/typescript/lib" 16 | } -------------------------------------------------------------------------------- /app/web/component/layout/admin/index.css: -------------------------------------------------------------------------------- 1 | .admin .search { 2 | margin-top: 8px; 3 | margin-bottom: 16px; 4 | } 5 | .admin label { 6 | padding-left: 8px; 7 | padding-right: 8px; 8 | color: #878d99 9 | } 10 | 11 | .admin .search-input{ 12 | max-width: 200px; 13 | } 14 | .admin .search-button{ 15 | margin-left: 16px; 16 | } 17 | .admin .add-button{ 18 | float:right; 19 | margin-right: 16px; 20 | } 21 | 22 | .admin .long-input { 23 | max-width: 75%; 24 | } 25 | 26 | .admin .top16 { 27 | margin-top: 16px; 28 | } -------------------------------------------------------------------------------- /app/web/page/store/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import RootState from './state'; 5 | import Admin from './modules/admin'; 6 | 7 | Vue.use(Vuex); 8 | 9 | export default function createStore(initState: any = {}) { 10 | const { title, url, origin, locale, csrf, admin } = initState; 11 | const state = { title, url, origin, locale, csrf }; 12 | return new Vuex.Store({ 13 | state, 14 | modules: { 15 | admin: new Admin(admin) 16 | } 17 | }); 18 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [4.0.0](https://github.com/easy-team/egg-vue-typescript-boilerplate/compare/3.0.0...4.0.0) (2018-11-07) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * npm run dev https://github.com/easy-team/egg-vue-typescript-boilerplate/issues/6 ([8dec42b](https://github.com/easy-team/egg-vue-typescript-boilerplate/commit/8dec42b)) 8 | 9 | 10 | ### Features 11 | 12 | * font-end typescript support ([beeaead](https://github.com/easy-team/egg-vue-typescript-boilerplate/commit/beeaead)) 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Egg Vue", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "npm", 10 | "windows": { "runtimeExecutable": "npm.cmd" }, 11 | "runtimeArgs": [ "run", "debug" ], 12 | "console": "integratedTerminal", 13 | "protocol": "auto", 14 | "restart": true, 15 | "port": 9229, 16 | "autoAttachChildProcesses": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /app/web/page/admin/home/view/detail/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/footer/footer.vue: -------------------------------------------------------------------------------- 1 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "member-access": false, 9 | "ordered-imports": false, 10 | "trailing-comma": false, 11 | "quotemark": [true, "single", "jsx-double"], 12 | "eofline": false, 13 | "object-literal-sort-keys": false, 14 | "interface-name": false, 15 | "arrow-parens": false, 16 | "no-console": false, 17 | "max-line-length": false, 18 | "only-arrow-functions": false 19 | }, 20 | "rulesDirectory": ["app"] 21 | } -------------------------------------------------------------------------------- /app/router.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Application } from 'egg'; 3 | 4 | export default (application: Application) => { 5 | const { router, controller } = application; 6 | router.post('/admin/api/article/list', controller.admin.list); 7 | router.post('/admin/api/article/add', controller.admin.add); 8 | router.post('/admin/api/article/del', controller.admin.del); 9 | router.get('/admin/api/article/:id', controller.admin.detail); 10 | router.get('/', controller.admin.login); 11 | router.get('/admin', controller.admin.home); 12 | router.get('/admin/*', controller.admin.home); 13 | }; -------------------------------------------------------------------------------- /app/web/page/admin/home/view/detail/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component } from 'vue-property-decorator'; 2 | import Article from '../../../../../../model/article'; 3 | 4 | import { 5 | Getter, 6 | Action 7 | } from 'vuex-class'; 8 | 9 | @Component 10 | export default class Detail extends Vue { 11 | @Getter('article') article?: Article; 12 | fetchApi(options) { 13 | const { store, route } = options; 14 | const { id } = route.params; 15 | return store.dispatch('getArticle', { id }); 16 | } 17 | destroyed() { 18 | this.$store.state.admin.article = null; 19 | } 20 | } -------------------------------------------------------------------------------- /app/web/view/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Egg + Vue + TypeScript 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /typings/app/service/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | type AnyClass = new (...args: any[]) => any; 6 | type AnyFunc = (...args: any[]) => T; 7 | type CanExportFunc = AnyFunc> | AnyFunc>; 8 | type AutoInstanceType : T> = U extends AnyClass ? InstanceType : U; 9 | import ExportArticle from '../../../app/service/article'; 10 | 11 | declare module 'egg' { 12 | interface IService { 13 | article: AutoInstanceType; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/web/component/layout/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Prop } from 'vue-property-decorator'; 2 | import Index from './index.vue'; 3 | @Component({ 4 | name: 'Layout', 5 | components: { 6 | Index 7 | } 8 | }) 9 | export default class Layout extends Vue { 10 | @Prop({ type: String, default: 'egg' }) title?: string; 11 | @Prop({ type: String, default: 'Vue TypeScript Framework, Server Side Render' }) description?: string; 12 | @Prop({ type: String, default: 'Vue,TypeScript,Isomorphic' }) keywords?: string; 13 | 14 | isNode: boolean = EASY_ENV_IS_NODE; 15 | 16 | created() { 17 | console.log('>>EASY_ENV_IS_NODE create', EASY_ENV_IS_NODE); 18 | } 19 | } -------------------------------------------------------------------------------- /app/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../config/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "sourceMap": true, 7 | "lib": [ 8 | "es6", 9 | "dom", 10 | "es2017", 11 | "esnext" 12 | ], 13 | "baseUrl": ".", 14 | "paths": { 15 | "@asset/*": ["asset/*"], 16 | "@component/*": ["component/*"], 17 | "@framework/*": ["framework/*"], 18 | "@store/*": ["page/store/*"], 19 | "@router/*": ["page/admin/home/router/*"], 20 | "@view/*": ["page/admin/home/view/*"] 21 | } 22 | }, 23 | "include": [ 24 | "./**/*.ts", 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "**/*.spec.ts" 29 | ] 30 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./config/tsconfig.json", 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | "target": "es2017", 6 | "module": "commonjs", 7 | /* Experimental Options */ 8 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 9 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 10 | "strictNullChecks": false 11 | }, 12 | "include": [ 13 | "index.ts", 14 | "app/**/*.ts", 15 | "config/**/*.ts", 16 | "mock/**/*.ts", 17 | "test/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "public", 21 | "app/web", 22 | "app/public", 23 | "app/view", 24 | "node_modules" 25 | ] 26 | } -------------------------------------------------------------------------------- /app/web/component/layout/admin/main/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 32 | 33 | -------------------------------------------------------------------------------- /app/web/component/layout/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 20 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Prop } from 'vue-property-decorator'; 2 | import ElementUI from 'element-ui'; 3 | import 'element-ui/lib/theme-chalk/index.css'; 4 | import './index.css'; 5 | 6 | import MainLayout from './main/index.vue'; 7 | Vue.use(ElementUI); 8 | 9 | @Component({ 10 | name: 'Layout', 11 | components: { 12 | MainLayout 13 | } 14 | }) 15 | export default class Layout extends Vue { 16 | @Prop({ type: String, default: 'egg' }) title?: string; 17 | @Prop({ type: String, default: 'Vue TypeScript Framework, Server Side Render' }) description?: string; 18 | @Prop({ type: String, default: 'Vue,TypeScript,Isomorphic' }) keywords?: string; 19 | 20 | isNode: boolean = EASY_ENV_IS_NODE; 21 | 22 | created() { 23 | console.log('>>EASY_ENV_IS_NODE create', EASY_ENV_IS_NODE); 24 | } 25 | } -------------------------------------------------------------------------------- /config/config.local.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig } from 'egg'; 2 | import * as path from 'path'; 3 | import { getWebpackConfig } from '@easy-team/easywebpack-vue'; 4 | 5 | export default (appInfo: EggAppConfig) => { 6 | const exports: any = {}; 7 | 8 | exports.static = { 9 | maxAge: 0 // maxAge 缓存,默认 1 年 10 | }; 11 | 12 | exports.development = { 13 | // watchDirs: ['app/controller'], // 指定监视的目录(包括子目录),当目录下的文件变化的时候自动重载应用,路径从项目根目录开始写 14 | ignoreDirs: ['app/web', 'public', 'config/manifest.json'] // 指定过滤的目录(包括子目录) 15 | }; 16 | 17 | exports.logview = { 18 | dir: path.join(appInfo.baseDir, 'logs') 19 | }; 20 | 21 | exports.vuessr = { 22 | injectCss: false 23 | }; 24 | 25 | exports.webpack = { 26 | browser: false, 27 | webpackConfigList: getWebpackConfig() 28 | } 29 | 30 | return exports; 31 | }; 32 | -------------------------------------------------------------------------------- /app/web/page/admin/login/login.css: -------------------------------------------------------------------------------- 1 | .login { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | position: absolute; 6 | height: 100%; 7 | width: 100%; 8 | background-color: #e4e5e6; 9 | } 10 | 11 | .login .login-info input { 12 | width: 100%; 13 | } 14 | 15 | .login .login-form { 16 | width: 375px; 17 | height: 400px; 18 | padding: 30px; 19 | background-color: white; 20 | text-align: left; 21 | border-radius: 4px; 22 | position: relative; 23 | margin-left: 0; 24 | margin-right: 0; 25 | zoom: 1; 26 | display: block; 27 | } 28 | 29 | .login .login-header { 30 | text-align: center; 31 | font-size: 16px; 32 | font-weight: bold; 33 | margin-bottom: 20px; 34 | } 35 | 36 | .login .el-checkbox__label { 37 | font-size: 14px; 38 | font-weight: normal; 39 | padding-left: 4px; 40 | } 41 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/menu/menu.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Prop } from 'vue-property-decorator'; 2 | 3 | @Component 4 | export default class Menu extends Vue { 5 | @Prop() private collapse: boolean = false; 6 | private menu: any = { 7 | home: { 8 | name: '首页', 9 | path: '/', 10 | icon: 'el-icon-menu', 11 | }, 12 | content: { 13 | name: '内容管理', 14 | icon: 'el-icon-document', 15 | children: { 16 | list: { 17 | name: '文章管理', 18 | path: '/article/list' 19 | }, 20 | add: { 21 | name: '添加文章', 22 | path: '/article/add' 23 | } 24 | } 25 | }, 26 | }; 27 | private handleOpen(key: string, keyPath: string) { 28 | console.log(key, keyPath); 29 | } 30 | private handleClose(key: string, keyPath: string) { 31 | console.log(key, keyPath); 32 | } 33 | } -------------------------------------------------------------------------------- /app/lib/condition.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { JsonProperty } from '@hubcarl/json-typescript-mapper'; 3 | export default class Condition { 4 | @JsonProperty('title') 5 | public title?: string; 6 | @JsonProperty('categoryId') 7 | public categoryId?: number; 8 | @JsonProperty('status') 9 | public status?: number; 10 | @JsonProperty('tag') 11 | public tag?: string; 12 | @JsonProperty('pageIndex') 13 | public pageIndex: number; 14 | @JsonProperty('pageSize') 15 | public pageSize: number; 16 | public where: any = {}; 17 | public like: any = {}; 18 | public orderByField: string = 'createTime'; 19 | public orderBy: string = 'desc'; 20 | 21 | constructor() { 22 | this.title = undefined; 23 | this.categoryId = undefined; 24 | this.status = undefined; 25 | this.tag = undefined; 26 | this.pageIndex = 1; 27 | this.pageSize = 10; 28 | this.where = {}; 29 | this.like = {}; 30 | } 31 | } -------------------------------------------------------------------------------- /app/web/component/layout/admin/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 20 | -------------------------------------------------------------------------------- /app/lib/db/base.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Condition from '../condition'; 3 | import * as shortid from 'shortid'; 4 | import { PlainObject } from 'egg'; 5 | 6 | export default class DB { 7 | public instance: any; 8 | public name: string; 9 | constructor(name: string = 'blog.json') { 10 | this.name = name; 11 | } 12 | 13 | public getUniqueId() { 14 | return shortid.generate(); 15 | } 16 | 17 | public get(collectionName: string) { 18 | return null; 19 | } 20 | 21 | public query(collectionName: string, json: PlainObject) { 22 | return null; 23 | } 24 | 25 | public add(collectionName: string, json: PlainObject) { 26 | return null; 27 | } 28 | 29 | public update(collectionName: string, where: PlainObject, json: PlainObject) { 30 | return null; 31 | } 32 | 33 | public delete(collectionName: string, field: any) { 34 | return null; 35 | } 36 | 37 | public getPager(collectionName: string, condition: Condition): any { 38 | return null; 39 | } 40 | } -------------------------------------------------------------------------------- /config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig } from 'egg'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | export default (appInfo: EggAppConfig) => { 6 | const config: any = {}; 7 | 8 | config.siteFile = { 9 | '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/web/asset/images/favicon.ico')) 10 | }; 11 | 12 | config.view = { 13 | cache: false 14 | }; 15 | 16 | config.vuessr = { 17 | layout: path.resolve(appInfo.baseDir, 'app/web/view/layout.html'), 18 | renderOptions: { 19 | basedir: path.join(appInfo.baseDir, 'app/view'), 20 | }, 21 | }; 22 | 23 | config.logger = { 24 | consoleLevel: 'DEBUG', 25 | dir: path.join(appInfo.baseDir, 'logs') 26 | }; 27 | 28 | config.static = { 29 | prefix: '/public/', 30 | dir: path.join(appInfo.baseDir, 'public') 31 | }; 32 | 33 | config.keys = '123456'; 34 | 35 | config.middleware = [ 36 | 'access', 37 | 'global' 38 | ]; 39 | 40 | return config; 41 | }; 42 | -------------------------------------------------------------------------------- /app/web/page/admin/home/view/write/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const resolve = filepath => path.resolve(__dirname, filepath); 4 | module.exports = { 5 | entry: { 6 | 'admin/login': 'app/web/page/admin/login/login.vue', 7 | 'admin/home': 'app/web/page/admin/home/index.ts' 8 | }, 9 | resolve: { 10 | alias:{ 11 | '@asset': resolve('app/web/asset'), 12 | '@framework': resolve('app/web/framework'), 13 | '@component': resolve('app/web/component'), 14 | '@store': resolve('app/web/page/store'), 15 | '@router': resolve('app/web/page/admin/home/router'), 16 | '@view': resolve('app/web/page/admin/home/view') 17 | } 18 | }, 19 | module:{ 20 | rules:[ 21 | { babel: false }, 22 | { 23 | ts: { 24 | exclude: [] 25 | } 26 | } 27 | ] 28 | }, 29 | plugins: [ 30 | { imagemini: false }, 31 | { 32 | copy: [{ 33 | from: 'app/web/asset', 34 | to: 'asset' 35 | }] 36 | } 37 | ] 38 | }; -------------------------------------------------------------------------------- /app/web/page/admin/home/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import VueRouter from 'vue-router'; 4 | import Dashboard from '../view/dashboard/index.vue'; 5 | import ArticleList from '../view/list/index.vue'; 6 | 7 | Vue.use(VueRouter); 8 | 9 | export default function createRouter() { 10 | return new VueRouter({ 11 | mode: 'history', 12 | base: '/admin/', 13 | routes: [ 14 | { 15 | path: '/', 16 | component: Dashboard 17 | }, 18 | { 19 | path: '/article/list', 20 | component: ArticleList 21 | }, 22 | { 23 | path: '/article/add', 24 | component: () => import('../view/write/index.vue') 25 | }, 26 | { 27 | path: '/article/edit/:id', 28 | component: () => import('../view/write/index.vue') 29 | }, 30 | { 31 | path: '/article/detail/:id', 32 | component: () => import('../view/detail/index.vue') 33 | }, 34 | { 35 | path: '*', component: () => import('../view/notfound/index.vue') 36 | } 37 | ] 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 easy-team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/web/component/MarkdownEditor/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 45 | -------------------------------------------------------------------------------- /app/lib/db/collection.ts: -------------------------------------------------------------------------------- 1 | import { PlainObject } from 'egg'; 2 | import DB from './base'; 3 | import Condition from '../condition'; 4 | export default class Collection { 5 | private db: DB; 6 | private name: string; 7 | constructor(db: DB, name: string) { 8 | this.db = db; 9 | this.name = name; 10 | } 11 | 12 | public get(): any { 13 | return this.db.get(this.name); 14 | } 15 | 16 | public query(json: any) { 17 | return this.db.query(this.name, json); 18 | } 19 | 20 | public add(json: PlainObject) { 21 | return this.db.add(this.name, json); 22 | } 23 | 24 | public update(where: PlainObject, json: PlainObject) { 25 | return this.db.update(this.name, where, json); 26 | } 27 | 28 | public delete(field: PlainObject) { 29 | return this.db.delete(this.name, field); 30 | } 31 | 32 | public getPager(condition: Condition) { 33 | return this.db.getPager(this.name, condition); 34 | } 35 | 36 | public getOrderAscByField(field: string) { 37 | return this.get().orderBy(field, 'asc').value(); 38 | } 39 | 40 | public getOrderDescByField(field: string) { 41 | return this.get().orderBy(field, 'desc').value(); 42 | } 43 | } -------------------------------------------------------------------------------- /app/model/article.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { JsonProperty } from '@hubcarl/json-typescript-mapper'; 4 | 5 | export default class Article { 6 | @JsonProperty('id') 7 | public id?: string; 8 | @JsonProperty('title') 9 | public title?: string; 10 | @JsonProperty('summary') 11 | public summary?: string; 12 | @JsonProperty('categoryId') 13 | public categoryId?: number; 14 | @JsonProperty('tag') 15 | public tag?: string ; 16 | @JsonProperty('categoryId') 17 | public authorId?: number; 18 | @JsonProperty('createTime') 19 | public createTime?: number; 20 | @JsonProperty('hits') 21 | public hits?: number; 22 | @JsonProperty('url') 23 | public url?: string; 24 | @JsonProperty('status') 25 | public status?: number; 26 | @JsonProperty('content') 27 | public content?: string; 28 | 29 | // constructor must be init everyone JsonProperty 30 | constructor() { 31 | this.id = ''; 32 | this.title = ''; 33 | this.summary = ''; 34 | this.categoryId = -1; 35 | this.tag = ''; 36 | this.authorId = -1; 37 | this.url = ''; 38 | this.status = 0; 39 | this.hits = 0; 40 | this.content = ''; 41 | this.createTime = Date.now(); 42 | } 43 | } -------------------------------------------------------------------------------- /typings/config/plugin.d.ts: -------------------------------------------------------------------------------- 1 | // This file is created by egg-ts-helper@1.25.8 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; 5 | import 'egg-onerror'; 6 | import 'egg-session'; 7 | import 'egg-i18n'; 8 | import 'egg-watcher'; 9 | import 'egg-multipart'; 10 | import 'egg-security'; 11 | import 'egg-development'; 12 | import 'egg-logrotator'; 13 | import 'egg-schedule'; 14 | import 'egg-static'; 15 | import 'egg-jsonp'; 16 | import 'egg-view'; 17 | import 'egg-view-vue-ssr'; 18 | import 'egg-cors'; 19 | import 'egg-webpack'; 20 | import 'egg-webpack-vue'; 21 | import { EggPluginItem } from 'egg'; 22 | declare module 'egg' { 23 | interface EggPlugin { 24 | onerror?: EggPluginItem; 25 | session?: EggPluginItem; 26 | i18n?: EggPluginItem; 27 | watcher?: EggPluginItem; 28 | multipart?: EggPluginItem; 29 | security?: EggPluginItem; 30 | development?: EggPluginItem; 31 | logrotator?: EggPluginItem; 32 | schedule?: EggPluginItem; 33 | static?: EggPluginItem; 34 | jsonp?: EggPluginItem; 35 | view?: EggPluginItem; 36 | vuessr?: EggPluginItem; 37 | cors?: EggPluginItem; 38 | webpack?: EggPluginItem; 39 | webpackvue?: EggPluginItem; 40 | } 41 | } -------------------------------------------------------------------------------- /app/web/page/admin/login/login.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/header/header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | position: fixed; 4 | display: flex; 5 | height: 50px; 6 | background-color: #3c8dbc; 7 | z-index: 10; 8 | } 9 | .header .top-left-bar { 10 | width: 230px; 11 | height: 50px; 12 | text-align: center; 13 | line-height: 50px; 14 | color: #fff; 15 | background-color: #367fa9; 16 | -webkit-transition: width 0.35s; 17 | transition: width 0.35s; 18 | } 19 | .header .top-left-bar-show { 20 | width: 230px !important; 21 | } 22 | .header .top-left-bar-hidden { 23 | width: 60px !important; 24 | } 25 | 26 | .header .right { 27 | position: absolute; 28 | right: 0; 29 | } 30 | 31 | .header .header-btn { 32 | overflow: hidden; 33 | height: 50px; 34 | display: inline-block; 35 | text-align: center; 36 | line-height: 50px; 37 | cursor: pointer; 38 | padding: 0 14px; 39 | color: #fff; 40 | } 41 | 42 | .header .header-btn:hover { 43 | background-color: #367fa9 44 | } 45 | 46 | .header .header-btn .el-badge__content { 47 | top: 14px; 48 | right: 7px; 49 | text-align: center; 50 | font-size: 9px; 51 | padding: 0 3px; 52 | background-color: #00a65a; 53 | color: #fff; 54 | border: none; 55 | white-space: nowrap; 56 | vertical-align: baseline; 57 | border-radius: .25em; 58 | } 59 | 60 | .menu { 61 | border-right: none; 62 | } -------------------------------------------------------------------------------- /app/middleware/access.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as util from 'util'; 3 | import { Context } from 'egg'; 4 | export default () => { 5 | const skipExt = [ '.png', '.jpeg', '.jpg', '.ico', '.gif' ]; 6 | return async function access(ctx: Context, next: any) { 7 | const start = new Date().getTime(); 8 | 9 | await next(); 10 | 11 | const rs: number = Math.ceil(new Date().getTime() - start); 12 | 13 | ctx.set('X-Response-Time', String(rs)); 14 | 15 | const ext = path.extname(ctx.url).toLocaleLowerCase(); 16 | const isSkip = skipExt.indexOf(ext) !== -1 && ctx.status < 400; 17 | 18 | if (!isSkip) { 19 | const ip = ctx.get('X-Real-IP') || ctx.ip; 20 | const port = ctx.get('X-Real-Port'); 21 | const protocol = ctx.protocol.toUpperCase(); 22 | const method = ctx.method; 23 | const url = ctx.url; 24 | const status = ctx.status; 25 | const length = ctx.length || '-'; 26 | const referrer = ctx.get('referrer') || '-'; 27 | const ua = ctx.get('user-agent') || '-'; 28 | const serverTime = ctx.response.get('X-Server-Response-Time') || '-'; 29 | const message = util.format('[access] %s:%s - %s %s %s/%s %s %s %s %s %s', 30 | ip, port, method, url, protocol, status, length, referrer, rs, serverTime, ua); 31 | ctx.logger.info(message); 32 | } 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/header/header.vue: -------------------------------------------------------------------------------- 1 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /app/controller/admin.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Context } from 'egg'; 2 | import { deserialize } from '@hubcarl/json-typescript-mapper'; 3 | import Article from '../model/article'; 4 | import Condition from '../lib/condition'; 5 | 6 | export default class AdminController extends Controller { 7 | 8 | public async login(ctx: Context) { 9 | await ctx.render('admin/login.js', {}); 10 | } 11 | 12 | public async home(ctx: Context) { 13 | if (ctx.query.mode === 'csr') { 14 | await ctx.renderClient('admin/home.js', { url: ctx.url.replace(/\/admin/, '') }, { viewEngine: null }); 15 | } else { 16 | await ctx.render('admin/home.js', { url: ctx.url.replace(/\/admin/, '') }); 17 | } 18 | } 19 | 20 | public async list(ctx: Context) { 21 | const condition = deserialize(Condition, ctx.request.body); 22 | ctx.body = await ctx.service.article.getArtilceList(condition); 23 | } 24 | 25 | public async add(ctx: Context) { 26 | const article = deserialize(Article, ctx.request.body); 27 | ctx.body = await ctx.service.article.saveArticle(article); 28 | } 29 | 30 | public async del(ctx: Context) { 31 | const { id } = ctx.request.body; 32 | ctx.body = await ctx.service.article.deleteArticle(id); 33 | } 34 | 35 | public async detail(ctx: Context) { 36 | const { id } = ctx.params; 37 | ctx.body = await ctx.service.article.query({ id: Number(id) }); 38 | } 39 | } -------------------------------------------------------------------------------- /app/service/article.ts: -------------------------------------------------------------------------------- 1 | import { Context, Service } from 'egg'; 2 | import { deserialize } from '@hubcarl/json-typescript-mapper'; 3 | import Colllection from '../lib/db/collection'; 4 | import Article from '../model/article'; 5 | import Condition from '../lib/condition'; 6 | 7 | export default class ArticeService extends Service { 8 | private context: Context; 9 | private colllection: Colllection; 10 | constructor(ctx: Context) { 11 | super(ctx); 12 | this.context = ctx; 13 | this.colllection = new Colllection(ctx.db, 'article'); 14 | } 15 | 16 | public async getArtilceList(condition: Condition) { 17 | if (condition.categoryId) { 18 | condition.where.categoryId = condition.categoryId; 19 | } 20 | if (condition.status) { 21 | condition.where.status = condition.status; 22 | } 23 | if (condition.title) { 24 | condition.like.title = condition.title; 25 | } 26 | return this.colllection.getPager(condition); 27 | } 28 | 29 | public async saveArticle(data: object) { 30 | const article: Article = deserialize(Article, data); 31 | if (article.id) { 32 | return this.colllection.update({ id: article.id }, article); 33 | } 34 | article.id = this.context.db.getUniqueId(); 35 | this.colllection.add(article); 36 | return article; 37 | } 38 | 39 | public async query(json: object) { 40 | return this.colllection.query(json); 41 | } 42 | 43 | public async deleteArticle(id: string) { 44 | return this.colllection.delete({ id }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/menu/menu.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/web/page/admin/home/view/write/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Getter, Action } from 'vuex-class'; 3 | import { Component, Prop, Watch } from 'vue-property-decorator'; 4 | import Article from '../../../../../../model/article'; 5 | 6 | @Component({ 7 | components: { 8 | MarkdownEditor: () => import('component/MarkdownEditor/index.vue') 9 | } 10 | }) 11 | export default class Write extends Vue { 12 | @Getter('article') article?: Article; 13 | @Action('saveArticle') saveArticle; 14 | @Action('getArticle') getArticle; 15 | text: string = ''; 16 | 17 | isShowEditor: boolean = false; 18 | 19 | async submit(status) { 20 | const showdown = await import('showdown'); 21 | const converter = new showdown.Converter(); 22 | const article = { 23 | ...this.article, 24 | status, 25 | content : converter.makeHtml(this.text), 26 | }; 27 | const result = await this.saveArticle(article); 28 | if (result.status === 200 ) { 29 | this.$message(`添加成功`); 30 | this.article = {}; 31 | } else { 32 | this.$message(`添加失败: ${JSON.stringify(result)}`); 33 | } 34 | } 35 | 36 | @Watch('article') 37 | onArticleChanged(val: string, oldVal: string) { 38 | if (this.article) { 39 | // need html to markdown 40 | this.text = this.article.content || ''; 41 | } 42 | } 43 | 44 | mounted() { 45 | this.isShowEditor = true; 46 | const { id } = this.$route.params; 47 | if (id) { 48 | this.getArticle({ id }); 49 | } 50 | } 51 | 52 | destroyed() { 53 | this.$store.state.admin.article = {}; 54 | } 55 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-vue-typescript-boilerplate 2 | 3 | 基于 Egg + Vue + Webpack SSR 服务端渲染和 CSR 前端渲染工程骨架项目。 4 | 5 | Single Page Application Isomorphic Example for Egg + Vue, Front-End and Node of The Application are Written in TypeScript. 6 | 7 | ## Document 8 | 9 | - https://www.yuque.com/easy-team/egg-vue 10 | - https://www.yuque.com/easy-team/easywebpack 11 | - https://easyjs.cn 12 | 13 | 14 | ## QuickStart 15 | 16 | - Development 17 | 18 | ```bash 19 | $ npm install -g easywebpack-cli 20 | $ easy init 21 | $ npm install 22 | $ npm run dev 23 | $ open http://localhost:7001 24 | ``` 25 | 26 | - Publish 27 | 28 | ```bash 29 | npm run tsc 30 | npm run build 31 | npm start 32 | ``` 33 | 34 | ## Features 35 | 36 | - ✔︎ Single Page Application, Support Vue Server Side Render and Client Side Render Modes, Rendering Cache, Automatic Downgrade 37 | - ✔︎ Front-End and Node of The Application are Written in TypeScript, Use `vue-property-decorator` and `vuex-class` 38 | - ✔︎ Build with Webpack + TypeScript, Auto Building, Hot Reload, Code Splitting, High Speed, Performance Optimization 39 | 40 | ## Rendering 41 | 42 | - Front-End TypeScript 43 | 44 | ![Front-End TypeScript](https://github.com/easy-team/egg-vue-typescript-boilerplate/blob/master/docs/images/vue-front-end.png?raw=true) 45 | 46 | - Node TypeScript 47 | 48 | ![Node TypeScript](https://github.com/easy-team/egg-vue-typescript-boilerplate/blob/master/docs/images/vue-node.png?raw=true) 49 | 50 | - UI ScreenShot 51 | 52 | ![UI ScreenShot](https://github.com/easy-team/egg-vue-typescript-boilerplate/blob/master/docs/images/vue-admin-ui.png?raw=true) 53 | 54 | ## TypeScript 55 | 56 | - https://github.com/kaorun343/vue-property-decorator 57 | - https://github.com/ktsn/vuex-class 58 | 59 | 60 | ## License 61 | 62 | [MIT](LICENSE) 63 | -------------------------------------------------------------------------------- /app/web/component/MarkdownEditor/index.ts: -------------------------------------------------------------------------------- 1 | import 'font-awesome/css/font-awesome.min.css'; 2 | import 'simplemde/dist/simplemde.min.css'; 3 | import SimpleMDE from 'simplemde'; 4 | import Vue from 'vue'; 5 | import { Component, Prop, Watch } from 'vue-property-decorator'; 6 | 7 | @Component({ 8 | name: 'simplemde-md' 9 | }) 10 | export default class MarkDownEditor extends Vue { 11 | @Prop(String) id; 12 | @Prop({ type: Boolean, default: false }) autofocus; 13 | @Prop({ type: String, default: '' }) placeholder; 14 | @Prop({ type: Number, default: 400 }) height; 15 | @Prop({ type: Number, default: 10 }) zIndex; 16 | @Prop({ type: Array }) toolbar; 17 | 18 | simplemde: SimpleMDE = null; 19 | hasChange: boolean = false; 20 | 21 | // @Watch 22 | // value(val) { 23 | // if (val === this.simplemde.value() && !this.hasChange) return 24 | // this.simplemde.value(val); 25 | // } 26 | mounted() { 27 | this.simplemde = new SimpleMDE({ 28 | element: document.getElementById(this.id || 'markdown-editor-' + +new Date()), 29 | autoDownloadFontAwesome: false, 30 | autofocus: this.autofocus, 31 | toolbar: this.toolbar, 32 | spellChecker: false, 33 | insertTexts: { 34 | link: ['[', ']( )'] 35 | }, 36 | // hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'], 37 | placeholder: this.placeholder 38 | }); 39 | // if (this.value) { 40 | // this.simplemde.value(this.value) 41 | // } 42 | this.simplemde.codemirror.on('change', () => { 43 | if (this.hasChange) { 44 | this.hasChange = true; 45 | } 46 | this.$emit('input', this.simplemde.value()); 47 | }); 48 | } 49 | destroyed() { 50 | this.simplemde.toTextArea(); 51 | this.simplemde = null; 52 | } 53 | } -------------------------------------------------------------------------------- /app/web/framework/app.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { sync } from 'vuex-router-sync'; 3 | 4 | export default class App { 5 | config: any; 6 | constructor(config) { 7 | this.config = config; 8 | } 9 | 10 | bootstrap() { 11 | if (EASY_ENV_IS_NODE) { 12 | return this.server(); 13 | } 14 | return this.client(); 15 | } 16 | 17 | create(initState) { 18 | const { entry, createStore, createRouter } = this.config; 19 | const store = createStore(initState); 20 | const router = createRouter(); 21 | sync(store, router); 22 | return { 23 | router, 24 | store, 25 | render: h => { // not use ...entry, why ? 26 | return h(entry); 27 | }, 28 | }; 29 | } 30 | 31 | fetch(vm): Promise { 32 | const { store, router } = vm; 33 | const matchedComponents = router.getMatchedComponents(); 34 | if (!matchedComponents) { 35 | return Promise.reject('No Match Component'); 36 | } 37 | return Promise.all( 38 | matchedComponents.map((component: any) => { 39 | const options = component.options; 40 | if (options && options.methods && options.methods.fetchApi) { 41 | return options.methods.fetchApi.call(component, { store, router, route: router.currentRoute }); 42 | } 43 | return null; 44 | }) 45 | ); 46 | } 47 | 48 | client() { 49 | Vue.prototype.$http = require('axios'); 50 | const vm = this.create(window.__INITIAL_STATE__); 51 | vm.router.afterEach(() => { 52 | this.fetch(vm); 53 | }); 54 | const app = new Vue(vm); 55 | app.$mount('#app'); 56 | return app; 57 | } 58 | 59 | server() { 60 | return context => { 61 | const vm = this.create(context.state); 62 | const { store, router } = vm; 63 | router.push(context.state.url); 64 | return new Promise((resolve, reject) => { 65 | router.onReady(() => { 66 | this.fetch(vm).then(() => { 67 | context.state = store.state; 68 | return resolve(new Vue(vm)); 69 | }); 70 | }); 71 | }); 72 | }; 73 | } 74 | } -------------------------------------------------------------------------------- /app/lib/db/file.ts: -------------------------------------------------------------------------------- 1 | import * as lowdb from 'lowdb'; 2 | import * as lodashid from 'lodash-id'; 3 | import * as FileSync from 'lowdb/adapters/FileSync'; 4 | import { PlainObject } from 'egg'; 5 | import BaseDB from './base'; 6 | import Condition from '../condition'; 7 | export default class FileDB extends BaseDB { 8 | public instance: any; 9 | constructor(name?: string) { 10 | super(name); 11 | const file = new FileSync(this.name); 12 | this.instance = lowdb(file); 13 | this.instance._.mixin(lodashid); 14 | this.create(); 15 | } 16 | 17 | public create() { 18 | this.instance.defaults({ article: [], user: {} }).write(); 19 | } 20 | 21 | public get(collectionName: string) { 22 | return this.instance.get(collectionName); 23 | } 24 | 25 | public query(collectionName: string, json: PlainObject) { 26 | return this.get(collectionName) 27 | .find(json) 28 | .write(); 29 | } 30 | 31 | public add(collectionName: string, json: PlainObject) { 32 | return this.get(collectionName) 33 | .push(json) 34 | .write(); 35 | } 36 | 37 | public update(collectionName: string, where: PlainObject, json: PlainObject) { 38 | return this.get(collectionName).find(where).assign(json).write(); 39 | } 40 | 41 | public delete(collectionName: string, json: PlainObject) { 42 | return this.get(collectionName).remove(json).write(); 43 | } 44 | 45 | public getPager(collectionName: string, condition: Condition) { 46 | const { 47 | where, 48 | like, 49 | pageIndex, 50 | pageSize, 51 | orderByField, 52 | orderBy 53 | } = condition; 54 | const start = (pageIndex - 1) * pageSize; 55 | const end = pageIndex * pageSize; 56 | const result = this.get(collectionName) 57 | .filter(where) 58 | .filter(item => { 59 | return Object.keys(like).reduce((isLike, key) => { 60 | return isLike && item[key] && item[key].indexOf(like[key]) > -1; 61 | }, true); 62 | }) 63 | .orderBy(orderByField, orderBy); 64 | const total = result.size().value(); 65 | const list = result.slice(start, end).value(); 66 | return { total, list }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/web/page/admin/home/view/list/index.ts: -------------------------------------------------------------------------------- 1 | import { Vue, Component, Emit } from 'vue-property-decorator'; 2 | import { 3 | Getter, 4 | Action 5 | } from 'vuex-class'; 6 | 7 | @Component 8 | export default class List extends Vue { 9 | @Getter('total') total?: number; 10 | @Getter('articleList') articleList: any; 11 | @Action('deleteArticle') deleteArticle: any; 12 | 13 | loading: boolean = false; 14 | batchSelectArray: number[] = []; 15 | 16 | q = { 17 | title: undefined, 18 | categoryId: undefined, 19 | statusId: undefined, 20 | pageIndex: 1, 21 | pageSize: 10 22 | }; 23 | 24 | status = [ 25 | { status: undefined, name: '--请选择--' }, 26 | { status: 1, name: '已发布' }, 27 | { status: 2, name: '草稿' } 28 | ]; 29 | 30 | categories = [ 31 | { categoryId: 0, name: '--请选择--' }, 32 | { categoryId: 1, name: 'Nodejs' }, 33 | { categoryId: 2, name: 'Webpack' }, 34 | { categoryId: 3, name: 'Egg' } 35 | ]; 36 | 37 | fetchApi(options, q) { 38 | const { store } = options; 39 | return store.dispatch('getArticleList', q); 40 | } 41 | 42 | query() { 43 | this.fetchApi({ store: this.$store }, this.q); 44 | } 45 | 46 | refresh() { 47 | this.fetchApi({ store: this.$store }, this.q); 48 | } 49 | 50 | write() { 51 | this.$router.push(`/article/add`); 52 | } 53 | 54 | edit(id) { 55 | this.$router.push(`/article/edit/${id}`); 56 | } 57 | 58 | handleSelectionChange(val: number) { 59 | console.log('handleSelectionChange', val); 60 | } 61 | 62 | handleSizeChange(val: number) { 63 | console.log(`每页 ${val} 条`); 64 | this.q.pageSize = val; 65 | this.refresh(); 66 | } 67 | 68 | handleCurrentChange(val: number) { 69 | console.log(`当前页: ${val}`); 70 | this.q.pageIndex = val; 71 | this.refresh(); 72 | } 73 | 74 | handleEdit(index: number, row: any) { 75 | console.log(row); 76 | this.edit(row.id); 77 | } 78 | 79 | handleDelete(index: number, row: any) { 80 | this.deleteArticle({ id: row.id }); 81 | this.$message(`删除[${row.title}]成功!`); 82 | } 83 | 84 | // 批量选择 85 | batchSelect(val: number[]) { 86 | this.batchSelectArray = val; 87 | } 88 | 89 | // 批量删除 90 | batchDel() { 91 | this.$confirm('将批量删除选择文章, 是否继续?', '提示', { 92 | confirmButtonText: '确定', 93 | cancelButtonText: '取消', 94 | type: 'warning' 95 | }).then(() => { 96 | this.loading = true; 97 | this.$message.success('DELETE'); 98 | this.loading = false; 99 | }); 100 | } 101 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-vue-typescript-boilerplate", 3 | "version": "4.0.4", 4 | "description": "Egg + Vue + TypeScript Server Side Render(SSR) 服务端渲染骨架项目", 5 | "scripts": { 6 | "start": "egg-scripts start --port 7001 --workers 4", 7 | "backend": "nohup egg-scripts start --port 7001 --workers 4 &", 8 | "dev": "egg-bin dev -r egg-ts-helper/register", 9 | "debug": "egg-bin debug -r egg-ts-helper/register", 10 | "build": "npm run tsc && easy build", 11 | "tsc": "ets && tsc -p tsconfig.json", 12 | "clean": "ets clean", 13 | "kill": "easy kill", 14 | "lint": "tslint --project . -c tslint.json", 15 | "fix": "tslint --fix --project . -c tslint.json", 16 | "ii": "npm install --registry https://registry.npm.taobao.org", 17 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" 18 | }, 19 | "dependencies": { 20 | "@hubcarl/json-typescript-mapper": "^2.0.0", 21 | "axios": "^0.21.1", 22 | "egg": "^2.3.0", 23 | "egg-cors": "^2.1.1", 24 | "egg-scripts": "^2.10.0", 25 | "egg-view-vue-ssr": "^3.0.5", 26 | "egg-webpack": "^4.4.7", 27 | "egg-webpack-vue": "^2.0.0", 28 | "element-ui": "^2.0.8", 29 | "extend": "~3.0.0", 30 | "font-awesome": "^4.7.0", 31 | "lodash": "^4.17.4", 32 | "lodash-id": "^0.14.0", 33 | "lowdb": "^1.0.0", 34 | "mockjs": "^1.0.1-beta3", 35 | "moment": "^2.17.1", 36 | "shortid": "^2.2.8", 37 | "showdown": "^1.8.6", 38 | "simplemde": "^1.11.2", 39 | "vue": "^2.5.0", 40 | "vue-property-decorator": "^7.2.0", 41 | "vue-router": "^3.0.1", 42 | "vuex": "^3.0.1", 43 | "vuex-class": "^0.3.1", 44 | "vuex-router-sync": "^5.0.0" 45 | }, 46 | "devDependencies": { 47 | "@types/lodash": "^4.14.117", 48 | "@types/lowdb": "^1.0.6", 49 | "@types/node": "^10.12.0", 50 | "@types/shortid": "^0.0.29", 51 | "cz-conventional-changelog": "^2.1.0", 52 | "@easy-team/easywebpack-cli": "^4.0.0", 53 | "@easy-team/easywebpack-vue": "^4.0.0", 54 | "egg-bin": "^4.9.0", 55 | "egg-scripts": "^2.10.0", 56 | "egg-ts-helper": "^1.13.0", 57 | "node-tool-utils": "^1.1.1", 58 | "ts-loader": "^5.3.0", 59 | "ts-node": "^7.0.1", 60 | "tslint": "^5.9.1", 61 | "tslint-loader": "^3.5.3", 62 | "typescript": "^3.9.2" 63 | }, 64 | "egg": { 65 | "typescript": true 66 | }, 67 | "engines": { 68 | "node": ">=8.0.0" 69 | }, 70 | "ci": { 71 | "version": "8, 10" 72 | }, 73 | "repository": { 74 | "type": "git", 75 | "url": "git+https://github.com/easy-team/egg-vue-typescript-boilerplate.git" 76 | }, 77 | "author": "hubcarl@126.com", 78 | "license": "MIT", 79 | "homepage": "https://github.com/easy-team/egg-vue-typescript-boilerplate.git", 80 | "config": { 81 | "commitizen": { 82 | "path": "./node_modules/cz-conventional-changelog" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/web/component/layout/admin/footer/footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | height: 120px; 3 | background-color: #324057; 4 | color: #a4aebd; 5 | width: 100%; 6 | } 7 | 8 | .footer * { 9 | word-spacing: 0 10 | } 11 | 12 | .footer .container { 13 | height: 100%; 14 | box-sizing: border-box 15 | } 16 | 17 | .footer .footer-main { 18 | font-size: 0; 19 | padding-top: 40px; 20 | display: inline-block 21 | } 22 | 23 | .footer .footer-main .footer-main-title { 24 | line-height: 1; 25 | font-size: 22px; 26 | margin: 0 27 | } 28 | 29 | .footer .footer-main .footer-main-link { 30 | display: inline-block; 31 | margin: 12px 18px 0 0; 32 | line-height: 1; 33 | font-size: 12px; 34 | color: #768193 35 | } 36 | 37 | .footer .footer-main .footer-main-link a { 38 | color: #768193; 39 | text-decoration: none 40 | } 41 | 42 | .footer .footer-social { 43 | float: right; 44 | line-height: 120px 45 | } 46 | 47 | .footer .footer-social .elementdoc { 48 | transition: .3s; 49 | display: inline-block; 50 | line-height: 32px; 51 | text-align: center; 52 | color: #8d99ab; 53 | background-color: transparent; 54 | width: 32px; 55 | height: 32px; 56 | font-size: 32px; 57 | vertical-align: middle 58 | } 59 | 60 | .footer .footer-social .elementdoc:hover { 61 | transform: scale(1.2) 62 | } 63 | 64 | .footer .footer-social .doc-icon-weixin { 65 | margin-right: 36px 66 | } 67 | 68 | .footer .footer-social .doc-icon-weixin:hover { 69 | color: #fff 70 | } 71 | 72 | .footer .footer-social .doc-icon-github { 73 | margin-right: 0 74 | } 75 | 76 | .footer .footer-social .doc-icon-github:hover { 77 | color: #fff 78 | } 79 | 80 | .footer-popover { 81 | padding: 0; 82 | min-width: 120px; 83 | line-height: normal; 84 | box-shadow: 0 0 11px 0 rgba(174, 187, 211, .24) 85 | } 86 | 87 | .footer-popover .footer-popover-title { 88 | border-bottom: 1px solid #eaeefb; 89 | height: 30px; 90 | line-height: 30px; 91 | text-align: center; 92 | color: #99a9bf; 93 | background-color: #f8f9fe 94 | } 95 | 96 | .footer-popover img { 97 | width: 100px; 98 | height: 100px; 99 | margin: 10px 100 | } 101 | 102 | @media (max-width: 768px) { 103 | .footer .footer-social { 104 | display: none 105 | } 106 | } 107 | 108 | .footer-nav { 109 | padding: 24px 0; 110 | color: #99a9bf; 111 | font-size: 14px 112 | } 113 | 114 | .footer-nav:after { 115 | content: ""; 116 | display: block; 117 | clear: both 118 | } 119 | 120 | .footer-nav i { 121 | transition: .3s; 122 | color: #d9def1; 123 | vertical-align: baseline 124 | } 125 | 126 | .footer-nav-link { 127 | cursor: pointer; 128 | transition: .3s 129 | } 130 | 131 | .footer-nav-link:hover, .footer-nav-link:hover i { 132 | color: #20a0ff 133 | } 134 | 135 | .footer-nav-left { 136 | float: left; 137 | margin-left: -4px 138 | } 139 | 140 | .footer-nav-right { 141 | float: right; 142 | margin-right: -4px 143 | } -------------------------------------------------------------------------------- /app/web/page/store/modules/admin/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Module, GetterTree, ActionTree, MutationTree } from 'vuex'; 3 | import { 4 | SET_ARTICLE_LIST, 5 | SET_ARTICLE_DETAIL, 6 | SET_SAVE_ARTICLE, 7 | DELETE_ARTICLE 8 | } from './type'; 9 | 10 | import RootState from '../../state'; 11 | import AdminState from './state'; 12 | import Article from '../../../../../model/article'; 13 | 14 | axios.defaults.baseURL = 'http://127.0.0.1:7001'; 15 | axios.defaults.timeout = 15000; 16 | axios.defaults.xsrfHeaderName = 'x-csrf-token'; 17 | axios.defaults.xsrfCookieName = 'csrfToken'; 18 | 19 | export default class AdminModule implements Module { 20 | state: AdminState; 21 | 22 | getters: GetterTree = { 23 | total(state): number { 24 | return state.articleTotal; 25 | }, 26 | article(state): Article { 27 | return state.article || {}; 28 | }, 29 | articleList(state): Article[] { 30 | return state.articleList; 31 | }, 32 | }; 33 | 34 | actions: ActionTree = { 35 | async getArticleList({ commit, dispatch, state, rootState }, condition) { 36 | // fetch no token headers 37 | const headers = EASY_ENV_IS_NODE ? { 38 | 'x-csrf-token': rootState.csrf, 39 | 'Cookie': `csrfToken=${rootState.csrf}` 40 | } : {}; 41 | const res = await axios.post(`${rootState.origin}/admin/api/article/list`, condition, { headers }); 42 | commit(SET_ARTICLE_LIST, res.data); 43 | }, 44 | async getArticle({ commit, dispatch, state , rootState}, { id }) { 45 | const res = await axios.get(`${rootState.origin}/admin/api/article/${id}`); 46 | commit(SET_ARTICLE_DETAIL, res.data); 47 | }, 48 | async saveArticle({ commit, dispatch, state, rootState }, data) { 49 | // node need auth 50 | const res = await axios.post(`${rootState.origin}/admin/api/article/add`, data); 51 | commit(SET_ARTICLE_LIST, res.data); 52 | return res; 53 | }, 54 | async deleteArticle({ commit, dispatch, state, rootState }, { id }) { 55 | // node need auth 56 | await axios.post(`${rootState.origin}/admin/api/article/del`, { id }); 57 | commit(DELETE_ARTICLE, { id }); 58 | } 59 | }; 60 | 61 | mutations: MutationTree = { 62 | [SET_ARTICLE_LIST](state, { list, total }) { 63 | state.articleTotal = total; 64 | state.articleList = list; 65 | }, 66 | [SET_ARTICLE_DETAIL](state, data) { 67 | state.article = data; 68 | }, 69 | [SET_SAVE_ARTICLE](state, data) { 70 | state.articleTotal += 1; 71 | state.articleList = [data].concat(state.articleList); 72 | }, 73 | [DELETE_ARTICLE](state, { id }) { 74 | state.articleTotal -= 1; 75 | state.articleList = state.articleList.filter((item: any) => { 76 | return item.id !== id; 77 | }); 78 | } 79 | }; 80 | 81 | constructor(initState: AdminState) { 82 | this.state = { 83 | articleTotal: 0, 84 | articleList: [], 85 | article: undefined, 86 | ...initState 87 | }; 88 | } 89 | } -------------------------------------------------------------------------------- /app/web/component/layout/admin/content/index.css: -------------------------------------------------------------------------------- 1 | .main{ 2 | margin-top: -80px; 3 | padding: 80px 0 120px; 4 | box-sizing: border-box; 5 | } 6 | .page-container{ 7 | width: 1140px; 8 | padding: 0 30px; 9 | margin: 36 auto; 10 | } 11 | .page-component { 12 | padding-bottom: 95px; 13 | box-sizing: border-box 14 | } 15 | 16 | .page-component .content { 17 | margin-left: -1px 18 | } 19 | 20 | .page-component .content > h3 { 21 | margin: 45px 0 15px 22 | } 23 | 24 | .page-component .content > table { 25 | border-collapse: collapse; 26 | width: 100%; 27 | background-color: #fff; 28 | color: #5e6d82; 29 | font-size: 14px; 30 | margin-bottom: 45px 31 | } 32 | 33 | .page-component .content > table strong { 34 | font-weight: 400 35 | } 36 | 37 | .page-component .content > table th { 38 | text-align: left; 39 | border-top: 1px solid #eaeefb; 40 | background-color: #eff2f7; 41 | white-space: nowrap 42 | } 43 | 44 | .page-component .content > table td, .page-component .content > table th { 45 | border-bottom: 1px solid #eaeefb; 46 | padding: 10px; 47 | max-width: 250px 48 | } 49 | 50 | .page-component .content > table td:first-child, .page-component .content > table th:first-child { 51 | padding-left: 10px 52 | } 53 | 54 | .page-component .page-component-up { 55 | background-color: #58b7ff; 56 | position: fixed; 57 | right: 100px; 58 | bottom: 150px; 59 | width: 50px; 60 | height: 50px; 61 | border-radius: 25px; 62 | cursor: pointer; 63 | opacity: .4; 64 | transition: .3s 65 | } 66 | 67 | .page-component .page-component-up i { 68 | color: #fff; 69 | display: block; 70 | line-height: 50px; 71 | text-align: center; 72 | font-size: 22px 73 | } 74 | 75 | .page-component .page-component-up.hover { 76 | opacity: 1 77 | } 78 | 79 | .page-component .back-top-fade-enter, .page-component .back-top-fade-leave-active { 80 | transform: translateY(-30px); 81 | opacity: 0 82 | } 83 | 84 | .page-component { 85 | padding-bottom: 95px; 86 | box-sizing: border-box 87 | } 88 | 89 | .page-component .content { 90 | margin-left: -1px 91 | } 92 | 93 | .page-component .content > h3 { 94 | margin: 45px 0 15px 95 | } 96 | 97 | .page-component .content > table { 98 | border-collapse: collapse; 99 | width: 100%; 100 | background-color: #fff; 101 | color: #5e6d82; 102 | font-size: 14px; 103 | margin-bottom: 45px 104 | } 105 | 106 | .page-component .content > table strong { 107 | font-weight: 400 108 | } 109 | 110 | .page-component .content > table th { 111 | text-align: left; 112 | border-top: 1px solid #eaeefb; 113 | background-color: #eff2f7; 114 | white-space: nowrap 115 | } 116 | 117 | .page-component .content > table td, .page-component .content > table th { 118 | border-bottom: 1px solid #eaeefb; 119 | padding: 10px; 120 | max-width: 250px 121 | } 122 | 123 | .page-component .content > table td:first-child, .page-component .content > table th:first-child { 124 | padding-left: 10px 125 | } 126 | 127 | .page-component .page-component-up { 128 | background-color: #58b7ff; 129 | position: fixed; 130 | right: 100px; 131 | bottom: 150px; 132 | width: 50px; 133 | height: 50px; 134 | border-radius: 25px; 135 | cursor: pointer; 136 | opacity: .4; 137 | transition: .3s 138 | } 139 | 140 | .page-component .page-component-up i { 141 | color: #fff; 142 | display: block; 143 | line-height: 50px; 144 | text-align: center; 145 | font-size: 22px 146 | } 147 | 148 | .page-component .page-component-up.hover { 149 | opacity: 1 150 | } 151 | 152 | .page-component .back-top-fade-enter, .page-component .back-top-fade-leave-active { 153 | transform: translateY(-30px); 154 | opacity: 0 155 | } -------------------------------------------------------------------------------- /app/web/page/admin/home/view/list/index.vue: -------------------------------------------------------------------------------- 1 | 92 | 94 | -------------------------------------------------------------------------------- /app/web/page/admin/home/component/panel.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | 52 | 140 | -------------------------------------------------------------------------------- /config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 5 | // "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation: */ 7 | "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | // "outDir": "./", /* Redirect output structure to the directory. */ 14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | // "strictNullChecks": true, /* Enable strict null checks. */ 25 | "strictFunctionTypes": false, /* Enable strict checking of function types. */ 26 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 27 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 28 | 29 | /* Additional Checks */ 30 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 31 | "noUnusedParameters": false, /* Report errors on unused parameters. */ 32 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 33 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 34 | 35 | /* Module Resolution Options */ 36 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 37 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 38 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 39 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 40 | // "typeRoots": [], /* List of folders to include type definitions from. */ 41 | // "types": [], /* Type declaration files to be included in compilation. */ 42 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 43 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 44 | 45 | /* Source Map Options */ 46 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 47 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 48 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 49 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 50 | "experimentalDecorators": true, 51 | "emitDecoratorMetadata": true 52 | } 53 | } -------------------------------------------------------------------------------- /blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": {}, 3 | "article": [ 4 | { 5 | "id": 946940, 6 | "space": "egg-vue", 7 | "slug": "learn", 8 | "url": "https://easyjs.cn/egg-vue/learn", 9 | "title": "Egg + Vue 工程化方案- 工程介绍", 10 | "summary": "Egg Vue 工程解决方案特性支持Node 端基于 Egg 开发,遵循 Egg 开发规范和 Egg 生态,支持 Egg 所有特性,比如插件机制,多进程机制。使用 TypeScript 或 JavaScript 编写前端和Node端代码,支持 ts-node 无编译 和 Webpack 编译开...", 11 | "coverImage": null, 12 | "createTime": "2019-11-30T06:48:54.000Z", 13 | "updateTime": "2019-11-30T06:48:54.000Z", 14 | "wordCount": 2152 15 | }, 16 | { 17 | "id": 685667, 18 | "space": "egg-vue", 19 | "slug": "init", 20 | "url": "https://easyjs.cn/egg-vue/init", 21 | "title": "Egg + Vue 工程化方案 - 快速开始", 22 | "summary": "基于 Egg + Vue + Webpack 服务端渲染开发指南1. 项目初始化1.1 easywebpack-cli 脚手架初始化项目安装脚手架 npm install easywebpack-cli -g 命令行,然后就可以使用 easywebpack 或 easy 命令命令行运行 eas...", 23 | "coverImage": null, 24 | "createTime": "2019-03-01T08:48:05.000Z", 25 | "updateTime": "2019-10-18T02:12:11.000Z", 26 | "wordCount": 2712 27 | }, 28 | { 29 | "id": 685668, 30 | "space": "egg-vue", 31 | "slug": "start", 32 | "url": "https://easyjs.cn/egg-vue/start", 33 | "title": "Egg + Vue 工程化方案 - 从零开始", 34 | "summary": "从零开始搭建 Egg + Vue + Webpack 服务端渲染项目1. 初始化环境安装 Node LST (8.x.x) 环境: https://nodejs.org/zh-cn2. 初始化 egg 项目https://github.com/eggjs/egg-init/blob/maste...", 35 | "coverImage": null, 36 | "createTime": "2019-07-12T15:44:47.000Z", 37 | "updateTime": "2019-08-06T07:56:28.000Z", 38 | "wordCount": 1752 39 | }, 40 | { 41 | "id": 810030, 42 | "space": "egg-vue", 43 | "slug": "node", 44 | "url": "/egg-vue/node", 45 | "title": "Egg + Vue 工程化方案 - 服务端渲染SSR模式", 46 | "summary": "Egg + Vue 服务端 Node 渲染模式目前 egg-view-vue-ssr 支持 服务端渲染模式 和 前端渲染模式 两种渲染模式这里服务端渲染指的是编写的 Vue 组件在 Node 服务端直接编译成完整的HTML, 然后直接输出给浏览器。MVVM 服务端渲染相比前端渲染,支持SEO,...", 47 | "coverImage": null, 48 | "createTime": "2019-07-27T05:49:44.000Z", 49 | "updateTime": "2019-08-19T14:06:57.000Z", 50 | "wordCount": 816 51 | }, 52 | { 53 | "id": 809509, 54 | "space": "egg-vue", 55 | "slug": "web", 56 | "url": "https://easyjs.cn/egg-vue/web", 57 | "title": "Egg + Vue 工程化方案 - 前端渲染模式", 58 | "summary": "浏览器渲染模式指的是Node 端只会根据包含html, head, body节点信息的 layout 文件输出骨架内容, 页面的实际内容交给浏览器去渲染。调用 egg-view-vue-ssr 的 renderClient 方法实现客户端浏览器渲染在使用上面, 客户端浏览器渲染模式只需要把 render 改成 renderClient。 正常情况下, 能进行 render 运行的, renderClient 方式也能正常运行。Webpack 配置优化,提高构建速度在 ${root}/webpack...", 59 | "coverImage": null, 60 | "createTime": "2018-09-06T07:09:43.000Z", 61 | "updateTime": "2019-07-24T10:05:33.000Z", 62 | "wordCount": 646 63 | }, 64 | { 65 | "id": 2787947, 66 | "space": "egg-vue", 67 | "slug": "html", 68 | "url": "/egg-vue/html", 69 | "title": "Egg + Vue 工程化方案 - HTML前端渲染", 70 | "summary": "背景在 前端渲染模式 和 asset 渲染模式 章节讲到了基于 React 的前端渲染模式,但都依赖 egg-view-react-ssr 插件,那如何基于已有 egg 模板引擎 (egg-view-nunjucks 或 egg-view-ejs) + Webpack 完全自定义前端方案呢?...", 71 | "coverImage": null, 72 | "createTime": "2019-10-08T07:10:41.000Z", 73 | "updateTime": "2019-10-08T07:10:42.000Z", 74 | "wordCount": 652 75 | }, 76 | { 77 | "id": 1050061, 78 | "space": "egg-vue", 79 | "slug": "asset", 80 | "url": "https://easyjs.cn/egg-vue/asset", 81 | "title": "Egg + Vue 工程化方案 - asset 渲染模式", 82 | "summary": "背景在 前端渲染模式 章节讲到了基于 Vue 的一体化的前端渲染模式,好处是不需要借助第三方模板引擎且无需关注静态资源注入问题,但有两个小的功能限制:layout 模板数据绑定能力较弱资源注入不能自己定义,比如 async, crossorigin 等配置针对上面问题 egg-view-vue...", 83 | "coverImage": null, 84 | "createTime": "2019-07-26T12:18:09.000Z", 85 | "updateTime": "2019-07-26T12:18:09.000Z", 86 | "wordCount": 737 87 | }, 88 | { 89 | "id": 2787931, 90 | "space": "egg-vue", 91 | "slug": "fls9r6", 92 | "url": "https://easyjs.cn/egg-vue/fls9r6", 93 | "title": "Egg + Vue 工程化方案 - 自定义前端渲染", 94 | "summary": "背景在 前端渲染模式 和 asset 渲染模式 章节讲到了基于 React 的前端渲染模式,但都依赖 egg-view-react-ssr 插件,那如何基于已有 egg 模板引擎 (egg-view-nunjucks 或 egg-view-ejs) + Webpack 完全自定义前端方案呢?...", 95 | "coverImage": null, 96 | "createTime": "2019-10-08T07:06:39.000Z", 97 | "updateTime": "2019-10-08T07:06:39.000Z", 98 | "wordCount": 652 99 | }, 100 | { 101 | "id": 685673, 102 | "space": "egg-vue", 103 | "slug": "online", 104 | "url": "/egg-vue/online", 105 | "title": "Egg + Vue 工程化方案 -部署流程", 106 | "summary": "项目开发在 egg-vue-webpack-boilerplate 骨架项目中, 提供了一些demo, 如果要进行新项目开发,可以删除部分文件:app/web/page 是页面目录。下面的每个目录都是一个单独的页面,其中 app 目录是一个单页面服务端渲染例子,其他是简单的 Vue 服务端渲染...", 107 | "coverImage": null, 108 | "createTime": "2019-08-25T03:49:02.000Z", 109 | "updateTime": "2019-09-03T06:17:39.000Z", 110 | "wordCount": 1198 111 | }, 112 | { 113 | "id": 880830, 114 | "space": "egg-vue", 115 | "slug": "config", 116 | "url": "https://easyjs.cn/egg-vue/config", 117 | "title": "Egg + Vue 工程化方案 -配置说明", 118 | "summary": "webpack.config.jseasywebpack@4.8.0 开始支持,因为有了默认配置,所以最新的骨架项目中,webpack.config.js 文件为非必须配置。使用 node-glob 遍历文件。下面配置会自动遍历 app/web/page 目录的所有 .vue 文件作为 en...", 119 | "coverImage": null, 120 | "createTime": "2019-02-11T02:55:39.000Z", 121 | "updateTime": "2019-06-21T09:19:15.000Z", 122 | "wordCount": 264 123 | }, 124 | { 125 | "id": 2, 126 | "title": "Webpack 配置官方文档", 127 | "summary": "webpack is a module bundler for modern JavaScript applications.", 128 | "hits": 550, 129 | "url": "https://webpack.js.org/configuration/", 130 | "status": 1, 131 | "tag": "egg,vue,webpack", 132 | "categoryId": 1, 133 | "authorId": 1, 134 | "createTime": 1515671348290 135 | }, 136 | { 137 | "id": 3, 138 | "title": "egg-为企业级框架和应用而生", 139 | "summary": "Born to buildbetter enterprise frameworks and apps with Node.js & Koa", 140 | "hits": 278, 141 | "url": "https://eggjs.org/", 142 | "tag": "egg,vue,webpack", 143 | "categoryId": 1, 144 | "authorId": 1, 145 | "createTime": 1515671348290 146 | }, 147 | { 148 | "id": 5, 149 | "title": "Centralized State Management for Vue.js", 150 | "summary": "Vuex 是一个专为Vue.js 应用程序开发的状态管理模式", 151 | "hits": 232, 152 | "url": "https://github.com/vuejs/vuex", 153 | "tag": "egg,vue,webpack", 154 | "categoryId": 1, 155 | "authorId": 1, 156 | "createTime": 1515671348293 157 | }, 158 | { 159 | "id": 6, 160 | "title": "vue服务器渲染", 161 | "summary": "服务器渲染可以加快首屏速度,利于SEO", 162 | "hits": 565, 163 | "url": "http://csbun.github.io/blog/2016/08/vue-2-0-server-side-rendering/", 164 | "tag": "egg,vue,webpack", 165 | "categoryId": 1, 166 | "authorId": 1, 167 | "createTime": 1515671348293 168 | }, 169 | { 170 | "id": 7, 171 | "title": "webpack服务器构建", 172 | "summary": "Webpack is an amazing tool.", 173 | "hits": 988, 174 | "url": "http://jlongster.com/Backend-Apps-with-Webpack--Part-I", 175 | "tag": "egg,vue,webpack", 176 | "categoryId": 1, 177 | "authorId": 1, 178 | "createTime": 1515671348294 179 | }, 180 | { 181 | "id": 8, 182 | "title": "vue component loader for Webpack", 183 | "summary": "Webpack loader for Vue.js components", 184 | "hits": 322, 185 | "url": "https://github.com/vuejs/vue-loader", 186 | "tag": "egg,vue,webpack", 187 | "categoryId": 1, 188 | "authorId": 1, 189 | "createTime": 1515671348295 190 | }, 191 | { 192 | "id": 9, 193 | "title": "vue-router--The official router for Vue.js", 194 | "summary": "It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze", 195 | "hits": 566, 196 | "url": "https://github.com/vuejs/vue-router", 197 | "tag": "egg,vue,webpack", 198 | "categoryId": 1, 199 | "authorId": 1, 200 | "createTime": 1515671348295 201 | }, 202 | { 203 | "id": 10, 204 | "title": "vue生命周期", 205 | "summary": "Vue.js 生命周期和route的生命周期讲解", 206 | "hits": 434, 207 | "url": "http://www.jianshu.com/p/e9f884b6ba6c", 208 | "tag": "egg,vue,webpack", 209 | "categoryId": 1, 210 | "authorId": 1, 211 | "createTime": 1515671348296 212 | }, 213 | { 214 | "id": 11, 215 | "title": "babel到底将代码转换成什么鸟样", 216 | "summary": "将babel捧作前端一个划时代的工具一定也不为过,它的出现让许多程序员幸福地用上了es6新语法", 217 | "hits": 432, 218 | "url": "https://github.com/lcxfs1991/blog/issues/9", 219 | "tag": "egg,vue,webpack", 220 | "categoryId": 1, 221 | "authorId": 1, 222 | "createTime": 1515671348296 223 | }, 224 | { 225 | "id": 12, 226 | "title": "HTTP2 的真正性能到底如何", 227 | "summary": "HTTP2 的真正性能到底如何", 228 | "hits": 565, 229 | "url": "https://segmentfault.com/a/1190000007219256?utm_source=weekly&utm_medium=email&utm_campaign=email_weekly", 230 | "tag": "egg,vue,webpack", 231 | "categoryId": 1, 232 | "authorId": 1, 233 | "createTime": 1515671348296 234 | }, 235 | { 236 | "id": 13, 237 | "title": "HTTP,HTTP2.0,SPDY,HTTPS讲解", 238 | "summary": "使用SPDY加快你的网站速度", 239 | "hits": 787, 240 | "url": "http://www.alloyteam.com/2016/07/httphttp2-0spdyhttps-reading-this-is-enough/", 241 | "tag": "egg,vue,webpack", 242 | "categoryId": 1, 243 | "authorId": 1, 244 | "createTime": 1515671348297 245 | }, 246 | { 247 | "id": 14, 248 | "title": "git - 简明指南", 249 | "summary": "助你入门 git 的简明指南", 250 | "hits": 121, 251 | "url": "http://rogerdudler.github.io/git-guide/index.zh.html", 252 | "tag": "egg,vue,webpack", 253 | "categoryId": 1, 254 | "authorId": 1, 255 | "createTime": 1515671348297 256 | }, 257 | { 258 | "id": 15, 259 | "title": "vue从1升级到2", 260 | "summary": "Migrating from v1 to v2", 261 | "hits": 555, 262 | "url": "https://webpack.js.org/guides/migrating/", 263 | "tag": "egg,vue,webpack", 264 | "categoryId": 1, 265 | "authorId": 1, 266 | "createTime": 1515671348298 267 | }, 268 | { 269 | "id": 16, 270 | "title": "vue-渐进式JavaScript 框架", 271 | "summary": "简单小巧的核心,渐进式技术栈,足以应付任何规模的应用", 272 | "hits": 200, 273 | "url": "https://cn.vuejs.org", 274 | "status": 1, 275 | "tag": "egg,vue,webpack", 276 | "categoryId": 1, 277 | "authorId": 1, 278 | "createTime": 1515671348299 279 | }, 280 | { 281 | "id": 17, 282 | "title": "webpack配置官方文档", 283 | "summary": "webpack is a module bundler for modern JavaScript applications.", 284 | "hits": 550, 285 | "url": "https://webpack.js.org/configuration/", 286 | "status": 1, 287 | "tag": "egg,vue,webpack", 288 | "categoryId": 1, 289 | "authorId": 1, 290 | "createTime": 1515671348321 291 | }, 292 | { 293 | "id": 18, 294 | "title": "egg-为企业级框架和应用而生", 295 | "summary": "Born to buildbetter enterprise frameworks and apps with Node.js & Koa", 296 | "hits": 278, 297 | "url": "https://eggjs.org/", 298 | "tag": "egg,vue,webpack", 299 | "categoryId": 1, 300 | "authorId": 1, 301 | "createTime": 1515671348322 302 | }, 303 | { 304 | "id": 20, 305 | "title": "Centralized State Management for Vue.js", 306 | "summary": "Vuex 是一个专为Vue.js 应用程序开发的状态管理模式", 307 | "hits": 232, 308 | "url": "https://github.com/vuejs/vuex", 309 | "tag": "egg,vue,webpack", 310 | "categoryId": 1, 311 | "authorId": 1, 312 | "createTime": 1515671348326 313 | }, 314 | { 315 | "id": 21, 316 | "title": "vue服务器渲染", 317 | "summary": "服务器渲染可以加快首屏速度,利于SEO", 318 | "hits": 565, 319 | "url": "http://csbun.github.io/blog/2016/08/vue-2-0-server-side-rendering/", 320 | "tag": "egg,vue,webpack", 321 | "categoryId": 1, 322 | "authorId": 1, 323 | "createTime": 1515671348327 324 | }, 325 | { 326 | "id": 22, 327 | "title": "webpack服务器构建", 328 | "summary": "Webpack is an amazing tool.", 329 | "hits": 988, 330 | "url": "http://jlongster.com/Backend-Apps-with-Webpack--Part-I", 331 | "tag": "egg,vue,webpack", 332 | "categoryId": 1, 333 | "authorId": 1, 334 | "createTime": 1515671348329 335 | }, 336 | { 337 | "id": 23, 338 | "title": "vue component loader for Webpack", 339 | "summary": "Webpack loader for Vue.js components", 340 | "hits": 322, 341 | "url": "https://github.com/vuejs/vue-loader", 342 | "tag": "egg,vue,webpack", 343 | "categoryId": 1, 344 | "authorId": 1, 345 | "createTime": 1515671348330 346 | }, 347 | { 348 | "id": 24, 349 | "title": "vue-router--The official router for Vue.js", 350 | "summary": "It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze", 351 | "hits": 566, 352 | "url": "https://github.com/vuejs/vue-router", 353 | "tag": "egg,vue,webpack", 354 | "categoryId": 1, 355 | "authorId": 1, 356 | "createTime": 1515671348330 357 | }, 358 | { 359 | "id": 25, 360 | "title": "vue生命周期", 361 | "summary": "Vue.js 生命周期和route的生命周期讲解", 362 | "hits": 434, 363 | "url": "http://www.jianshu.com/p/e9f884b6ba6c", 364 | "tag": "egg,vue,webpack", 365 | "categoryId": 1, 366 | "authorId": 1, 367 | "createTime": 1515671348331 368 | }, 369 | { 370 | "id": 26, 371 | "title": "babel到底将代码转换成什么鸟样", 372 | "summary": "将babel捧作前端一个划时代的工具一定也不为过,它的出现让许多程序员幸福地用上了es6新语法", 373 | "hits": 432, 374 | "url": "https://github.com/lcxfs1991/blog/issues/9", 375 | "tag": "egg,vue,webpack", 376 | "categoryId": 1, 377 | "authorId": 1, 378 | "createTime": 1515671348333 379 | }, 380 | { 381 | "id": 27, 382 | "title": "HTTP2 的真正性能到底如何", 383 | "summary": "HTTP2 的真正性能到底如何", 384 | "hits": 565, 385 | "url": "https://segmentfault.com/a/1190000007219256?utm_source=weekly&utm_medium=email&utm_campaign=email_weekly", 386 | "tag": "egg,vue,webpack", 387 | "categoryId": 1, 388 | "authorId": 1, 389 | "createTime": 1515671348333 390 | }, 391 | { 392 | "id": 28, 393 | "title": "HTTP,HTTP2.0,SPDY,HTTPS讲解", 394 | "summary": "使用SPDY加快你的网站速度", 395 | "hits": 787, 396 | "url": "http://www.alloyteam.com/2016/07/httphttp2-0spdyhttps-reading-this-is-enough/", 397 | "tag": "egg,vue,webpack", 398 | "categoryId": 1, 399 | "authorId": 1, 400 | "createTime": 1515671348334 401 | }, 402 | { 403 | "id": 29, 404 | "title": "git - 简明指南", 405 | "summary": "助你入门 git 的简明指南", 406 | "hits": 121, 407 | "url": "http://rogerdudler.github.io/git-guide/index.zh.html", 408 | "tag": "egg,vue,webpack", 409 | "categoryId": 1, 410 | "authorId": 1, 411 | "createTime": 1515671348335 412 | }, 413 | { 414 | "id": 30, 415 | "title": "vue从1升级到2", 416 | "summary": "Migrating from v1 to v2", 417 | "hits": 555, 418 | "url": "https://webpack.js.org/guides/migrating/", 419 | "tag": "egg,vue,webpack", 420 | "categoryId": 1, 421 | "authorId": 1, 422 | "createTime": 1515671348335 423 | }, 424 | { 425 | "id": 31, 426 | "title": "vue-渐进式JavaScript 框架", 427 | "summary": "简单小巧的核心,渐进式技术栈,足以应付任何规模的应用", 428 | "hits": 200, 429 | "url": "https://cn.vuejs.org", 430 | "status": 1, 431 | "tag": "egg,vue,webpack", 432 | "categoryId": 1, 433 | "authorId": 1, 434 | "createTime": 1515671348336 435 | }, 436 | { 437 | "id": 32, 438 | "title": "webpack配置官方文档", 439 | "summary": "webpack is a module bundler for modern JavaScript applications.", 440 | "hits": 550, 441 | "url": "https://webpack.js.org/configuration/", 442 | "status": 1, 443 | "tag": "egg,vue,webpack", 444 | "categoryId": 1, 445 | "authorId": 1, 446 | "createTime": 1515671348336 447 | }, 448 | { 449 | "id": 33, 450 | "title": "egg-为企业级框架和应用而生", 451 | "summary": "Born to buildbetter enterprise frameworks and apps with Node.js & Koa", 452 | "hits": 278, 453 | "url": "https://eggjs.org/", 454 | "tag": "egg,vue,webpack", 455 | "categoryId": 1, 456 | "authorId": 1, 457 | "createTime": 1515671348338 458 | }, 459 | { 460 | "id": 35, 461 | "title": "Centralized State Management for Vue.js", 462 | "summary": "Vuex 是一个专为Vue.js 应用程序开发的状态管理模式", 463 | "hits": 232, 464 | "url": "https://github.com/vuejs/vuex", 465 | "tag": "egg,vue,webpack", 466 | "categoryId": 1, 467 | "authorId": 1, 468 | "createTime": 1515671348339 469 | }, 470 | { 471 | "id": 36, 472 | "title": "vue服务器渲染", 473 | "summary": "服务器渲染可以加快首屏速度,利于SEO", 474 | "hits": 565, 475 | "url": "http://csbun.github.io/blog/2016/08/vue-2-0-server-side-rendering/", 476 | "tag": "egg,vue,webpack", 477 | "categoryId": 1, 478 | "authorId": 1, 479 | "createTime": 1515671348340 480 | }, 481 | { 482 | "id": 37, 483 | "title": "webpack服务器构建", 484 | "summary": "Webpack is an amazing tool.", 485 | "hits": 988, 486 | "url": "http://jlongster.com/Backend-Apps-with-Webpack--Part-I", 487 | "tag": "egg,vue,webpack", 488 | "categoryId": 1, 489 | "authorId": 1, 490 | "createTime": 1515671348340 491 | }, 492 | { 493 | "id": 38, 494 | "title": "vue component loader for Webpack", 495 | "summary": "Webpack loader for Vue.js components", 496 | "hits": 322, 497 | "url": "https://github.com/vuejs/vue-loader", 498 | "tag": "egg,vue,webpack", 499 | "categoryId": 1, 500 | "authorId": 1, 501 | "createTime": 1515671348341 502 | }, 503 | { 504 | "id": 39, 505 | "title": "vue-router--The official router for Vue.js", 506 | "summary": "It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze", 507 | "hits": 566, 508 | "url": "https://github.com/vuejs/vue-router", 509 | "tag": "egg,vue,webpack", 510 | "categoryId": 1, 511 | "authorId": 1, 512 | "createTime": 1515671348341 513 | }, 514 | { 515 | "id": 40, 516 | "title": "vue生命周期", 517 | "summary": "Vue.js 生命周期和route的生命周期讲解", 518 | "hits": 434, 519 | "url": "http://www.jianshu.com/p/e9f884b6ba6c", 520 | "tag": "egg,vue,webpack", 521 | "categoryId": 1, 522 | "authorId": 1, 523 | "createTime": 1515671348342 524 | }, 525 | { 526 | "id": 41, 527 | "title": "babel到底将代码转换成什么鸟样", 528 | "summary": "将babel捧作前端一个划时代的工具一定也不为过,它的出现让许多程序员幸福地用上了es6新语法", 529 | "hits": 432, 530 | "url": "https://github.com/lcxfs1991/blog/issues/9", 531 | "tag": "egg,vue,webpack", 532 | "categoryId": 1, 533 | "authorId": 1, 534 | "createTime": 1515671348342 535 | }, 536 | { 537 | "id": 42, 538 | "title": "HTTP2 的真正性能到底如何", 539 | "summary": "HTTP2 的真正性能到底如何", 540 | "hits": 565, 541 | "url": "https://segmentfault.com/a/1190000007219256?utm_source=weekly&utm_medium=email&utm_campaign=email_weekly", 542 | "tag": "egg,vue,webpack", 543 | "categoryId": 1, 544 | "authorId": 1, 545 | "createTime": 1515671348343 546 | }, 547 | { 548 | "id": 43, 549 | "title": "HTTP,HTTP2.0,SPDY,HTTPS讲解", 550 | "summary": "使用SPDY加快你的网站速度", 551 | "hits": 787, 552 | "url": "http://www.alloyteam.com/2016/07/httphttp2-0spdyhttps-reading-this-is-enough/", 553 | "tag": "egg,vue,webpack", 554 | "categoryId": 1, 555 | "authorId": 1, 556 | "createTime": 1515671348343 557 | }, 558 | { 559 | "id": 44, 560 | "title": "git - 简明指南", 561 | "summary": "助你入门 git 的简明指南", 562 | "hits": 121, 563 | "url": "http://rogerdudler.github.io/git-guide/index.zh.html", 564 | "tag": "egg,vue,webpack", 565 | "categoryId": 1, 566 | "authorId": 1, 567 | "createTime": 1515671348344 568 | }, 569 | { 570 | "id": 45, 571 | "title": "vue从1升级到2", 572 | "summary": "Migrating from v1 to v2", 573 | "hits": 555, 574 | "url": "https://webpack.js.org/guides/migrating/", 575 | "tag": "egg,vue,webpack", 576 | "categoryId": 1, 577 | "authorId": 1, 578 | "createTime": 1515671348345 579 | }, 580 | { 581 | "id": 46, 582 | "title": "vue-渐进式JavaScript 框架", 583 | "summary": "简单小巧的核心,渐进式技术栈,足以应付任何规模的应用", 584 | "hits": 200, 585 | "url": "https://cn.vuejs.org", 586 | "status": 1, 587 | "tag": "egg,vue,webpack", 588 | "categoryId": 1, 589 | "authorId": 1, 590 | "createTime": 1515671348345 591 | }, 592 | { 593 | "id": 47, 594 | "title": "webpack配置官方文档", 595 | "summary": "webpack is a module bundler for modern JavaScript applications.", 596 | "hits": 550, 597 | "url": "https://webpack.js.org/configuration/", 598 | "status": 1, 599 | "tag": "egg,vue,webpack", 600 | "categoryId": 1, 601 | "authorId": 1, 602 | "createTime": 1515671348346 603 | }, 604 | { 605 | "id": 48, 606 | "title": "egg-为企业级框架和应用而生", 607 | "summary": "Born to buildbetter enterprise frameworks and apps with Node.js & Koa", 608 | "hits": 278, 609 | "url": "https://eggjs.org/", 610 | "tag": "egg,vue,webpack", 611 | "categoryId": 1, 612 | "authorId": 1, 613 | "createTime": 1515671348346 614 | }, 615 | { 616 | "id": 50, 617 | "title": "Centralized State Management for Vue.js", 618 | "summary": "Vuex 是一个专为Vue.js 应用程序开发的状态管理模式", 619 | "hits": 232, 620 | "url": "https://github.com/vuejs/vuex", 621 | "tag": "egg,vue,webpack", 622 | "categoryId": 1, 623 | "authorId": 1, 624 | "createTime": 1515671348347 625 | } 626 | ] 627 | } -------------------------------------------------------------------------------- /app/web/asset/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | --------------------------------------------------------------------------------