fanghuisheng 4 år sedan
förälder
incheckning
94206cef48

+ 2 - 2
README.md

@@ -1,4 +1,4 @@
-# vue-webtopo-svgeditor
+<!-- # vue-webtopo-svgeditor
 
 > 纯vue3实现的svg可视化web组态编辑器。可无需修改代码动态添加组件
 
@@ -95,4 +95,4 @@ ps:目前中心辅助线的坐标取决于svg组件的中心坐标,请自行
 
 ## License
 
-[MIT](http://opensource.org/licenses/MIT)
+[MIT](http://opensource.org/licenses/MIT) -->

+ 24 - 0
src/api/login/user.js

@@ -0,0 +1,24 @@
+import request from '@/utils/request'
+
+export function login(data) {
+    return request({
+      url: ``,
+      method: 'post',
+      data
+    })
+  }
+  
+  export function getInfo(data) {
+    return request({
+      url: '',
+      method: 'post',
+      data
+    })
+  }
+  
+  export function logout() {
+    return request({
+      url: '',
+      method: 'post',
+    })
+  }

+ 46 - 0
src/layout/components/AppMain.vue

@@ -0,0 +1,46 @@
+<template>
+  <section class="app-main">
+    <transition name="fade-transform" mode="out-in">
+      <router-view :key="key" />
+    </transition>
+  </section>
+</template>
+
+<script>
+export default {
+  name: 'AppMain',
+  computed: {
+    key() {
+      return this.$route.path
+    }
+  }
+}
+</script>
+<style >
+.app-container{
+  background:#fff;
+  position: relative;
+}
+</style>
+<style scoped>
+.app-main {
+  /*50 = navbar  */
+  min-height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+  background:#F4F4F4
+}
+.fixed-header+.app-main {
+  padding-top: 50px;
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 15px;
+  }
+}
+</style>

+ 88 - 0
src/layout/components/Navbar.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="navbar">
+    <hamburger
+      :is-active="sidebar.opened"
+      class="hamburger-container"
+      @toggleClick="toggleSideBar"
+    />
+    <!-- <breadcrumb class="breadcrumb-container" /> -->
+
+    <div class="title-top">管理平台</div>
+    <time-menu></time-menu>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import Breadcrumb from "@/components/Breadcrumb";
+import Hamburger from "@/components/Hamburger";
+import TimeMenu from "@/components/TimeMenu";
+
+export default {
+  data() {
+    return {
+     
+    };
+  },
+
+  filters: {},
+
+  components: {
+    Breadcrumb,
+    Hamburger,
+    TimeMenu
+  },
+  mounted(){
+    // console.log(Hamburger)
+    // Hamburger.props.isActive.default = true
+  },
+  computed: {
+    ...mapGetters(["sidebar", "avatar",'addRoutes']),
+  },
+  methods: {
+    toggleSideBar() {
+      this.$store.dispatch("app/toggleSideBar");
+    },
+    async logout() {
+      await this.$store.dispatch("user/logout");
+      this.$router.push(`/login?redirect=${this.$route.fullPath}`);
+    },
+    
+  },
+
+};
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 50px;
+  overflow: hidden;
+  position: relative;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+
+  .hamburger-container {
+    line-height: 46px;
+    height: 100%;
+    float: left;
+    cursor: pointer;
+    transition: background 0.3s;
+    -webkit-tap-highlight-color: transparent;
+
+    &:hover {
+      background: rgba(0, 0, 0, 0.025);
+    }
+  }
+
+
+  .title-top {
+    line-height: 50px;
+    font-size: 24px;
+    width: 50%;
+    float: left;
+  }
+
+  
+
+}
+</style>

+ 26 - 0
src/layout/components/Sidebar/FixiOSBug.js

@@ -0,0 +1,26 @@
+export default {
+  computed: {
+    device() {
+      return this.$store.state.app.device
+    }
+  },
+  mounted() {
+    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+    this.fixBugIniOS()
+  },
+  methods: {
+    fixBugIniOS() {
+      const $subMenu = this.$refs.subMenu
+      if ($subMenu) {
+        const handleMouseleave = $subMenu.handleMouseleave
+        $subMenu.handleMouseleave = (e) => {
+          if (this.device === 'mobile') {
+            return
+          }
+          handleMouseleave(e)
+        }
+      }
+    }
+  }
+}

+ 41 - 0
src/layout/components/Sidebar/Item.vue

@@ -0,0 +1,41 @@
+<script>
+export default {
+  name: 'MenuItem',
+  functional: true,
+  props: {
+    icon: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  render(h, context) {
+    const { icon, title } = context.props
+    const vnodes = []
+
+    if (icon) {
+      if (icon.includes('el-icon')) {
+        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
+      } else {
+        vnodes.push(<svg-icon icon-class={icon}/>)
+      }
+    }
+
+    if (title) {
+      vnodes.push(<span slot='title'>{(title)}</span>)
+    }
+    return vnodes
+  }
+}
+</script>
+
+<style scoped>
+.sub-el-icon {
+  color: currentColor;
+  width: 1em;
+  height: 1em;
+}
+</style>

+ 43 - 0
src/layout/components/Sidebar/Link.vue

@@ -0,0 +1,43 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  props: {
+    to: {
+      type: String,
+      required: true
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
+  methods: {
+    linkProps(to) {
+      if (this.isExternal) {
+        return {
+          href: to,
+          target: '_blank',
+          rel: 'noopener'
+        }
+      }
+      return {
+        to: to
+      }
+    }
+  }
+}
+</script>

+ 82 - 0
src/layout/components/Sidebar/Logo.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
+    <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 v-else class="sidebar-title">{{ title }} </h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 class="sidebar-title">{{ title }} </h1>
+      </router-link>
+    </transition>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SidebarLogo',
+  props: {
+    collapse: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data() {
+    return {
+      title: '',
+      logo: require('@/assets/logo.png')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 89px;
+  line-height: 89px;
+  background: #021233;
+  text-align: center;
+  overflow: hidden;
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      // width: 32px;
+      // height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>

+ 142 - 0
src/layout/components/Sidebar/SidebarItem.vue

@@ -0,0 +1,142 @@
+<template>
+
+  <div v-if="!item.hidden">
+
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
+      <template slot="title">
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-submenu>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
+
+export default {
+  name: 'SidebarItem',
+  components: { Item, AppLink },
+  mixins: [FixiOSBug],
+  props: {
+    // route object
+    item: {
+      type: Object,
+      required: true
+    },
+    isNest: {
+      type: Boolean,
+      default: false
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
+    // TODO: refactor with render function
+    this.onlyOneChild = null
+    return {}
+  },
+  methods: {
+    hasOneShowingChild(children = [], parent) {
+      const showingChildren = children.filter(item => {
+        if (item.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          this.onlyOneChild = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    },
+    resolvePath(routePath) {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      if (isExternal(this.basePath)) {
+        return this.basePath
+      }
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
+</script>
+
+
+<style lang="scss" scoped>
+  @import "~@/styles/mixin.scss";
+  @import "~@/styles/variables.scss";
+
+  // .app-wrapper {
+  //   @include clearfix;
+  //   position: relative;
+  //   height: 100%;
+  //   width: 100%;
+
+  //   &.mobile.openSidebar {
+  //     position: fixed;
+  //     top: 0;
+  //   }
+  // }
+
+  // .drawer-bg {
+  //   background: #000;
+  //   opacity: 0.3;
+  //   width: 100%;
+  //   top: 0;
+  //   height: 100%;
+  //   position: absolute;
+  //   z-index: 999;
+  // }
+
+  // .fixed-header {
+  //   position: fixed;
+  //   top: 0;
+  //   right: 0;
+  //   z-index: 9;
+  //   width: calc(100% - #{$sideBarWidth});
+  //   transition: width 0.28s;
+  // }
+
+  // .hideSidebar .fixed-header {
+  //   width: calc(100% - 54px)
+  // }
+
+  // .mobile .fixed-header {
+  //   width: 100%;
+  // }
+</style>

+ 59 - 0
src/layout/components/Sidebar/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <div :class="{'has-logo':showLogo}">
+    <logo v-if="showLogo" :collapse="isCollapse" />
+  
+    <el-scrollbar wrap-class="scrollbar-wrapper">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="isCollapse"
+        :background-color="variables.menuBg"
+        :text-color="variables.menuText"
+        :unique-opened="false"
+        :active-text-color="variables.menuActiveText"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Logo from './Logo'
+import SidebarItem from './SidebarItem'
+import variables from '@/styles/variables.scss'
+
+export default {
+  components: { SidebarItem, Logo },
+  computed: {
+    ...mapGetters([
+      'sidebar',
+      'addRoutes'
+    ]),
+    routes() {
+      return this.$router.options.routes
+    },
+    activeMenu() {
+      const route = this.$route
+      const { meta, path } = route
+      // if set path, the sidebar will highlight the path you set
+      if (meta.activeMenu) {
+        return meta.activeMenu
+      }
+      return path
+    },
+    showLogo() {
+      return this.$store.state.settings.sidebarLogo
+    },
+    variables() {
+      return variables
+    },
+    isCollapse() {
+      return !this.sidebar.opened
+    }
+  }
+}
+</script>
+

+ 3 - 0
src/layout/components/index.js

@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'

+ 94 - 0
src/layout/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <div :class="classObj" class="app-wrapper">
+    
+    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
+    <sidebar class="sidebar-container" />
+    <div class="main-container">
+      <div :class="{'fixed-header':fixedHeader}">
+        <navbar />
+      </div>
+      <app-main />
+    </div>
+  </div>
+</template>
+
+<script>
+import { Navbar, Sidebar, AppMain } from './components'
+import ResizeMixin from './mixin/ResizeHandler'
+
+export default {
+  name: 'Layout',
+  components: {
+    Navbar,
+    Sidebar,
+    AppMain
+  },
+  mixins: [ResizeMixin],
+  computed: {
+    sidebar() {
+      return this.$store.state.app.sidebar
+    },
+    device() {
+      return this.$store.state.app.device
+    },
+    fixedHeader() {
+      return this.$store.state.settings.fixedHeader
+    },
+    classObj() {
+      return {
+        hideSidebar: !this.sidebar.opened,
+        openSidebar: this.sidebar.opened,
+        withoutAnimation: this.sidebar.withoutAnimation,
+        mobile: this.device === 'mobile'
+      }
+    }
+  },
+  methods: {
+    handleClickOutside() {
+      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "~@/styles/mixin.scss";
+  @import "~@/styles/variables.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+    &.mobile.openSidebar{
+      position: fixed;
+      top: 0;
+    }
+  }
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index: 9;
+    width: calc(100% - #{$sideBarWidth});
+    transition: width 0.28s;
+  }
+
+  .hideSidebar .fixed-header {
+    width: calc(100% - 54px)
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>

+ 45 - 0
src/layout/mixin/ResizeHandler.js

@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}

+ 1 - 1
src/main.js

@@ -2,7 +2,7 @@ import { createApp } from 'vue/dist/vue.esm-bundler.js'
 import Antd from 'ant-design-vue';
 import App from './App.vue'
 import 'ant-design-vue/dist/antd.css';
-import UCore from './util/UCore.js'
+import UCore from './utils/UCore.js'
 import './assets/css/index.css';
 import router from './router';
 import axios from 'axios';

+ 11 - 0
src/store/getters.js

@@ -0,0 +1,11 @@
+import store from "."
+
+const getters = {
+  sidebar: state => state.app.sidebar,
+  device: state => state.app.device,
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name,
+  routes:state => state.app.routes
+}
+export default getters

+ 189 - 0
src/store/index.js

@@ -0,0 +1,189 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+import settings from './modules/settings'
+import user from './modules/user'
+import { login, logout, getInfo } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import router from '../router/index'
+/* Layout */
+import Layout from '@/layout'
+
+Vue.use(Vuex)
+
+
+const getDefaultState = () => {
+    return {
+        token: getToken(),
+        name: '',
+        avatar: '',
+        routes: []
+    }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+    RESET_STATE: (state) => {
+        Object.assign(state, getDefaultState())
+    },
+    SET_TOKEN: (state, token) => {
+        state.token = token
+    },
+    SET_NAME: (state, name) => {
+        state.name = name
+    },
+    SET_AVATAR: (state, avatar) => {
+        state.avatar = avatar
+    },
+    // 移除用户
+    REMOVE: (state) => {
+        state.name = ''
+        state.addRoutes = []
+            //退出后,改变还原静态路由
+        removeArrayData(constantRoutes)
+    },
+    ADD_ROUTES: (state, routes) => {
+        state.addRoutes = routes
+        routes.forEach((i, index) => {
+            // 从第3+index个位置插入
+            constantRoutes.splice(3 + index, 0, i)
+        })
+    },
+    SET_MENULIST: (state, menuList) => {
+        console.log(state.menuList = menuList)
+        state.menuList = menuList
+    }
+}
+
+const actions = {
+    // user login
+    login({ commit }, userInfo) {
+        const { username, password } = userInfo
+        return new Promise((resolve, reject) => {
+            login({ username: username.trim(), password: password }).then(response => {
+                var time = new Date().getTime()
+                if (response.flag == true) {
+                    var data = {
+                        token: time
+                    }
+                    commit('SET_TOKEN', data.token)
+                    setToken(data.token)
+                    resolve()
+                } else {
+                    reject("输入的账号密码有误!")
+                }
+            }).catch(error => {
+                reject(error)
+            })
+        })
+    },
+    // get user info
+    getInfo({ commit, state }, data) {
+        const { parentId, userId } = data
+        return new Promise((resolve, reject) => {
+            getInfo({ parentId: parentId, userId: userId }).then(response => {
+                // console.log(response)
+                var arrayList = [];
+                for (let i = 0; i < response.length; i++) {
+                    arrayList.push({
+                        path: response[i].path,
+                        component: Layout,
+                        redirect: response[i].url,
+                        name: response[i].name,
+                        meta: {
+                            title: response[i].title,
+                            icon: response[i].icon
+                        },
+                        children: []
+                    })
+                    for (let j = 0; j < response[i].children.length; j++) {
+                        var new_response = response[i].children[j]
+                        var url = new_response.url
+                        arrayList[i].children.push({
+                            path: new_response.path,
+                            name: new_response.name,
+                            component: (resolve) => require([`@/views` + `${url}`], resolve),
+                            meta: {
+                                title: new_response.title,
+                                icon: new_response.icon
+                            }
+                        })
+                    }
+                }
+                // console.log(arrayList)
+
+                var List = [
+                    {
+                        path: '/index',
+                        component: () =>
+                            import ('@/views/index/index'),
+                        hidden: true,
+                    },
+                    {
+                        path: '/',
+                        component: Layout,
+                        redirect: '/index',
+                        children: [{
+                            path: 'index',
+                            name: 'index',
+                            component: () =>
+                                import ('@/views/index/index'),
+                            meta: { title: '首页', icon: 'home' }
+                        }]
+                    },
+                    {
+                        path: '*',
+                        redirect: '/404',
+                        hidden: true
+                    }
+                ]
+                router.options.routes = arrayList
+                state.app.routes = router.options.routes
+                router.addRoutes(List)
+
+            }).catch(error => {
+                reject(error)
+            })
+        })
+    },
+
+    // user logout
+    logout({ commit, state }) {
+        return new Promise((resolve, reject) => {
+            logout().then((response) => {
+                removeToken() // must remove  token  first
+                    // resetRouter()
+                commit('RESET_STATE')
+                resolve()
+            }).catch(error => {
+                reject(error)
+            })
+        })
+    },
+
+    // remove token
+    resetToken({ commit }) {
+        return new Promise(resolve => {
+            removeToken() // must remove  token  first
+            commit('RESET_STATE')
+            resolve()
+        })
+    }
+}
+
+const store = new Vuex.Store({
+    modules: {
+        app,
+        settings,
+        user
+    },
+    getters,
+    actions,
+    state,
+    mutations,
+    router
+})
+
+export default store

+ 48 - 0
src/store/modules/app.js

@@ -0,0 +1,48 @@
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false
+  },
+  device: 'desktop',
+  routes:[]
+}
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 32 - 0
src/store/modules/settings.js

@@ -0,0 +1,32 @@
+import defaultSettings from '@/settings'
+
+const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
+
+const state = {
+  showSettings: showSettings,
+  fixedHeader: fixedHeader,
+  sidebarLogo: sidebarLogo
+}
+
+const mutations = {
+  CHANGE_SETTING: (state, { key, value }) => {
+    // eslint-disable-next-line no-prototype-builtins
+    if (state.hasOwnProperty(key)) {
+      state[key] = value
+    }
+  }
+}
+
+const actions = {
+  changeSetting({ commit }, data) {
+    commit('CHANGE_SETTING', data)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 4 - 0
src/store/modules/user.js

@@ -0,0 +1,4 @@
+export default {
+  
+}
+

+ 0 - 0
src/util/UCore.js → src/utils/UCore.js


+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'vue_admin_template_token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 10 - 0
src/utils/get-page-title.js

@@ -0,0 +1,10 @@
+import defaultSettings from '@/settings'
+
+const title = defaultSettings.title || 'Vue Admin Template'
+
+export default function getPageTitle(pageTitle) {
+  if (pageTitle) {
+    return `${pageTitle} - ${title}`
+  }
+  return `${title}`
+}

+ 174 - 0
src/utils/index.js

@@ -0,0 +1,174 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseTime(time, cFormat) {
+    if (arguments.length === 0 || !time) {
+        return null
+    }
+    const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+    let date
+    if (typeof time === 'object') {
+        date = time
+    } else {
+        if ((typeof time === 'string')) {
+            if ((/^[0-9]+$/.test(time))) {
+                // support "1548221490638"
+                time = parseInt(time)
+            } else {
+                // support safari
+                // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
+                time = time.replace(new RegExp(/-/gm), '/')
+            }
+        }
+
+        if ((typeof time === 'number') && (time.toString().length === 10)) {
+            time = time * 1000
+        }
+        date = new Date(time)
+    }
+    const formatObj = {
+        y: date.getFullYear(),
+        m: date.getMonth() + 1,
+        d: date.getDate(),
+        h: date.getHours(),
+        i: date.getMinutes(),
+        s: date.getSeconds(),
+        a: date.getDay()
+    }
+    const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+        const value = formatObj[key]
+            // Note: getDay() returns 0 on Sunday
+        if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+        return value.toString().padStart(2, '0')
+    })
+    return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+    if (('' + time).length === 10) {
+        time = parseInt(time) * 1000
+    } else {
+        time = +time
+    }
+    const d = new Date(time)
+    const now = Date.now()
+
+    const diff = (now - d) / 1000
+
+    if (diff < 30) {
+        return '刚刚'
+    } else if (diff < 3600) {
+        // less 1 hour
+        return Math.ceil(diff / 60) + '分钟前'
+    } else if (diff < 3600 * 24) {
+        return Math.ceil(diff / 3600) + '小时前'
+    } else if (diff < 3600 * 24 * 2) {
+        return '1天前'
+    }
+    if (option) {
+        return parseTime(time, option)
+    } else {
+        return (
+            d.getMonth() +
+            1 +
+            '月' +
+            d.getDate() +
+            '日' +
+            d.getHours() +
+            '时' +
+            d.getMinutes() +
+            '分'
+        )
+    }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+    const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+    if (!search) {
+        return {}
+    }
+    const obj = {}
+    const searchArr = search.split('&')
+    searchArr.forEach(v => {
+        const index = v.indexOf('=')
+        if (index !== -1) {
+            const name = v.substring(0, index)
+            const val = v.substring(index + 1, v.length)
+            obj[name] = val
+        }
+    })
+    return obj
+}
+
+
+
+
+
+/**
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {*}
+ */
+export function debounce(func, wait, immediate) {
+    let timeout, args, context, timestamp, result
+
+    const later = function() {
+        // 据上一次触发时间间隔
+        const last = +new Date() - timestamp
+
+        // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
+        if (last < wait && last > 0) {
+            timeout = setTimeout(later, wait - last)
+        } else {
+            timeout = null
+                // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
+            if (!immediate) {
+                result = func.apply(context, args)
+                if (!timeout) context = args = null
+            }
+        }
+    }
+
+    return function(...args) {
+        context = this
+        timestamp = +new Date()
+        const callNow = immediate && !timeout
+            // 如果延时不存在,重新设定延时
+        if (!timeout) timeout = setTimeout(later, wait)
+        if (callNow) {
+            result = func.apply(context, args)
+            context = args = null
+        }
+
+        return result
+    }
+}
+
+
+//时间格式化 2021-01-21T05:53:00.000+0000
+export function renderTime(date) {
+
+    var dateee = new Date(date).toJSON();
+    return new Date(+new Date(dateee) + 8 * 3600 * 1000)
+        .toISOString()
+        .replace(/T/g, " ")
+        .replace(/\.[\d]{3}Z/, "");
+
+}

+ 110 - 0
src/utils/request.js

@@ -0,0 +1,110 @@
+import axios from 'axios'
+import { MessageBox, Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+import qs from 'qs'
+
+// create an axios instance
+let baseURL = window.PLATFROM_CONFIG.baseUrl
+const service = axios.create({
+    baseURL: baseURL, // url = base url + request url
+    // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+    // withCredentials: true, // send cookies when cross-domain requests
+    timeout: 5000, // request timeout
+    headers: {
+        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
+    }
+})
+
+// request interceptor
+service.interceptors.request.use(
+    config => {
+
+        config.data = qs.stringify(config.data);
+        config.headers.token = getToken()
+        // do something before request is sent
+
+        if (store.getters.token) {
+            // let each request carry token
+            // ['X-Token'] is a custom headers key
+            // please modify it according to the actual situation
+            config.headers['X-Token'] = getToken()
+        }
+
+        if (config.method == 'get') {
+            let ext_str = config.url.indexOf('?') == -1 ? '?' : '&';
+            let obj_val = '';
+            for (let k in config.data) {
+                obj_val += k + "=" + config.data[k] + "&";
+            }
+            config.url += ext_str + obj_val;
+        }
+        // return config;
+
+        return config
+    },
+    error => {
+        // do something with request error
+        console.log(error) // for debug
+        return Promise.reject(error)
+    }
+)
+
+// response interceptor
+service.interceptors.response.use(
+    /**
+     * If you want to get http information such as headers or status
+     * Please return  response => response
+     */
+
+    /**
+     * Determine the request status by custom code
+     * Here is just an example
+     * You can also judge the status by HTTP Status Code
+     */
+    response => {
+        const res = response.data
+
+        // console.log(res)
+
+
+        return res
+
+        // if the custom code is not 20000, it is judged as an error.
+        // if (res.code !== 200) {
+        //     Message({
+        //         message: res.message || 'Error',
+        //         type: 'error',
+        //         duration: 5 * 1000
+        //     })
+
+        //     // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+        //     if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+        //         // to re-login
+        //         MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
+        //             confirmButtonText: 'Re-Login',
+        //             cancelButtonText: 'Cancel',
+        //             type: 'warning'
+        //         }).then(() => {
+        //             store.dispatch('user/resetToken').then(() => {
+        //                 location.reload()
+        //             })
+        //         })
+        //     }
+        //     return Promise.reject(new Error(res.message || 'Error'))
+        // } else {
+        //     return res
+        // }
+    },
+    error => {
+        console.log('err' + error) // for debug
+        Message({
+            message: error.message,
+            type: 'error',
+            duration: 5 * 1000
+        })
+        return Promise.reject(error)
+    }
+)
+
+export default service

+ 20 - 0
src/utils/validate.js

@@ -0,0 +1,20 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+  const valid_map = ['admin', 'editor']
+  return valid_map.indexOf(str.trim()) >= 0
+}