+ Hello {{ world }} {{ text }}
+
+
+
diff --git a/src/web/router/admin.js b/src/web/router/admin.js
new file mode 100644
index 0000000..f3a80ac
--- /dev/null
+++ b/src/web/router/admin.js
@@ -0,0 +1,28 @@
+import AuthGuard from '@/router/authGuard'
+
+import adminLayout from '@/layout/admin'
+
+import AdminHome from '@/page/admin/home'
+import Login from '@/page/admin/login'
+
+export default {
+ path: '/admin',
+ component: adminLayout,
+ beforeEnter: AuthGuard,
+ children: [
+ {
+ path: '',
+ redirect: '/admin/home'
+ },
+ {
+ path: 'home',
+ name: 'AdminHome',
+ component: AdminHome
+ },
+ {
+ path: 'login',
+ name: 'Login',
+ component: Login
+ }
+ ]
+}
diff --git a/src/web/router/index.js b/src/web/router/index.js
new file mode 100644
index 0000000..b29c6e9
--- /dev/null
+++ b/src/web/router/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+import home from '@/page/home'
+
+Vue.use(Router)
+
+export function createRouter () {
+ return new Router({
+ mode: 'history',
+ fallback: false,
+ scrollBehavior: () => ({ y: 0 }),
+ routes: [
+ {
+ path: '/',
+ name: 'Home',
+ component: home
+ }
+ ]
+ })
+}
diff --git a/src/web/store/index.js b/src/web/store/index.js
new file mode 100644
index 0000000..edf6627
--- /dev/null
+++ b/src/web/store/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import modules from './modules'
+import axios from 'axios'
+import conf from '../../../config/app'
+
+Vuex.Store.prototype.$request = axios.create({
+ baseURL: 'http://' + conf.app.devHost + ':' + conf.app.port,
+ timeout: 1000
+})
+
+Vue.use(Vuex)
+
+const debug = process.env.NODE_ENV !== 'production'
+
+export function createStore () {
+ return new Vuex.Store({
+ modules,
+ strict: debug
+ })
+}
diff --git a/src/web/store/modules/api.js b/src/web/store/modules/api.js
new file mode 100644
index 0000000..b0de5bb
--- /dev/null
+++ b/src/web/store/modules/api.js
@@ -0,0 +1,32 @@
+// initial state
+const state = {
+ text: ''
+}
+
+// getters
+const getters = {
+
+}
+
+// actions
+const actions = {
+ async fetchVal ({ commit }) {
+ const { data } = await this.$request.get('/api')
+ commit('setVal', data)
+ }
+}
+
+// mutations
+const mutations = {
+ setVal (state, val) {
+ state.text = val
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ getters,
+ actions,
+ mutations
+}
diff --git a/src/web/store/modules/index.js b/src/web/store/modules/index.js
new file mode 100644
index 0000000..428c6be
--- /dev/null
+++ b/src/web/store/modules/index.js
@@ -0,0 +1,14 @@
+/**
+ * The file enables `@/store/index.js` to import all vuex modules
+ * in a one-shot manner. There should not be any reason to edit this file.
+ */
+
+const files = require.context('.', false, /\.js$/)
+const modules = {}
+
+files.keys().forEach(key => {
+ if (key === './index.js') return
+ modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
+})
+
+export default modules
diff --git a/src/web/store/util.js b/src/web/store/util.js
new file mode 100644
index 0000000..fb12032
--- /dev/null
+++ b/src/web/store/util.js
@@ -0,0 +1,66 @@
+/**
+ * Get the first item that pass the test
+ * by second argument function
+ *
+ * @param {Array} list
+ * @param {Function} f
+ * @return {*}
+ */
+export function find (list, f) {
+ return list.filter(f)[0]
+}
+
+/**
+ * Deep copy the given object considering circular structure.
+ * This function caches all nested objects and its copies.
+ * If it detects circular structure, use cached copy to avoid infinite loop.
+ *
+ * @param {*} obj
+ * @param {Array