소스 검색

add first

xgwangman 5 년 전
부모
커밋
ca5d7ceb7e
41개의 변경된 파일3260개의 추가작업 그리고 0개의 파일을 삭제
  1. 7 0
      .env
  2. 21 0
      .gitignore
  3. 8 0
      LICENSE
  4. 192 0
      README.md
  5. 5 0
      babel.config.js
  6. 62 0
      package.json
  7. BIN
      public/favicon.ico
  8. 93 0
      public/index.html
  9. 165 0
      src/App.vue
  10. 34 0
      src/components/authCheck.vue
  11. 45 0
      src/components/fieldMap.vue
  12. 35 0
      src/components/pageBar.vue
  13. 113 0
      src/components/tableLists.vue
  14. 132 0
      src/components/uploadFile.vue
  15. 26 0
      src/components/uploadImg.vue
  16. 35 0
      src/components/username.vue
  17. 55 0
      src/helper.js
  18. 30 0
      src/main.js
  19. 162 0
      src/plugins/request.js
  20. 68 0
      src/router.js
  21. 14 0
      src/store.js
  22. 100 0
      src/store/admin.js
  23. 142 0
      src/views/example.vue
  24. 18 0
      src/views/index.vue
  25. 167 0
      src/views/system/auth.vue
  26. 77 0
      src/views/system/components/AssignMenu.vue
  27. 87 0
      src/views/system/components/AssignRequest.vue
  28. 83 0
      src/views/system/components/AssignUser.vue
  29. 82 0
      src/views/system/components/AssignUserGroup.vue
  30. 154 0
      src/views/system/components/LeftMenu.vue
  31. 84 0
      src/views/system/components/Login.vue
  32. 0 0
      src/views/system/components/icon.js
  33. 71 0
      src/views/system/components/iconSelect.vue
  34. 60 0
      src/views/system/components/userSetting.vue
  35. 4 0
      src/views/system/listsConst.js
  36. 273 0
      src/views/system/menu.vue
  37. 50 0
      src/views/system/order/dataExport.vue
  38. 181 0
      src/views/system/request.vue
  39. 153 0
      src/views/system/user.vue
  40. 152 0
      src/views/system/userGroup.vue
  41. 20 0
      vue.config.js

+ 7 - 0
.env

@@ -0,0 +1,7 @@
+VUE_APP_ADMIN_TOKEN_NAME = 'admin_token'
+VUE_APP_API_URL_PREFIX = 'http://192.168.20.241:8001/api/admin/'
+VUE_APP_API_ACTION_NAME = '_action'
+VUE_APP_API_TOKEN_NAME = '_token'
+VUE_APP_INDEX_URL = '/'
+VUE_APP_SITE_NAME = '后台管理'
+VUE_APP_SITE_TITLE_TPL = '{title} - 后台管理'

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+package-lock.json

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright © 2019 baiy.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 192 - 0
README.md

@@ -0,0 +1,192 @@
+Cadmin的客户端实现 vue+iview
+
+### 依赖
+* [vue](https://cn.vuejs.org)
+* [iview](http://v3.iviewui.com/)
+* localStorage
+* [axios](https://github.com/axios/axios)
+* [lodash](https://lodash.com/)
+* `vue-router` 
+* `vuex`
+
+> 注意:当前使用的是`iview 3` 
+
+### 安装
+
+```shell
+// 安装
+npm install
+// 开发
+npm run serve
+// 编译
+npm run build
+```
+
+### 配置
+配置文件地址:`./env`
+
+> 使用vue-cli的[环境变量](https://cli.vuejs.org/zh/guide/mode-and-env.html)实现
+
+| 配置变量名 | 说明| 
+| --- | --- |
+|VUE_APP_ADMIN_TOKEN_NAME|前端localStorage存储 `token` 名称|
+|VUE_APP_API_URL_PREFIX|服务端数据请求入口地址|
+|VUE_APP_API_ACTION_NAME|服务端数据请求地址中 `action` 变量名|
+|VUE_APP_API_TOKEN_NAME|服务端数据请求地址中 `token` 变量名|
+|VUE_APP_INDEX_URL|登录后首页(`/`)跳转地址|
+|VUE_APP_SITE_NAME|站点名称 页面左上角|
+|VUE_APP_SITE_TITLE_TPL|页面标题模板|
+
+```js
+import {config} from '/src/helper'
+// 获取配置
+let name = config('SITE_NAME')
+```
+> 获取配置时`VUE_APP_`无需填写
+
+### 路由与菜单
+
+##### 路由
+系统中页面路由已经实现动态自动加载,加载规则如下:
+1. 加载目录`/src/views`
+1. 加载文件后缀名'.vue'
+1. 会自动过滤`components`文件夹中的文件
+
+例如:
+
+| 文件路径 | 路由地址| 
+| --- | --- |
+|/src/views/system/user.vue|/system/user|
+|/src/views/a/b/c/d.vue|/a/b/c/d|
+|/src/views/a/components/c.vue|不自动加载路由|
+|/src/views/components/a/c.vue|不自动加载路由|
+
+##### 菜单
+菜单分为两种类型:
+1. 目录型: 点击该菜单会展示下级菜单,没有真实页面与之对应
+1. 页面型: 这种菜单有前端页面对应,点击进入对应的前端页面
+1. 链接型: http/https链接
+
+页面型菜单: 后台配置菜单链接时对应这里 `路由地址`
+
+
+### store
+> `/src/store/admin.js`
+
+| - | 数据信息| 
+| --- | --- |
+|adminUser|当前用户信息|
+|adminMenu|用户已授权菜单列表|
+|adminAllUser|后台所有用户列表|
+|adminRequest|用户已授权请求列表|
+|adminAuth|用户关联权限|
+|adminUserGroup|用户关联用户组|
+|currentMenu|当前页面菜单信息|
+
+### 服务端请求
+
+#### vue 组件内部
+```js
+// get 请求
+this.$request('/system/request/remove').
+    data({id:1}).
+    showSuccessTip().
+    success(r=>{
+         console.log(r)
+    }).get();       
+// post 请求
+this.$request('/system/request/remove').
+    data({id:1}).
+    showSuccessTip().
+    success(r=>{
+         console.log(r)
+    }).get();
+```
+`this.$request()` 方法参数为服务端请求`action`
+
+`this.$request()` 方法返回值为 `/src/plugins/request.js` 中 `ActionRequest` 对象
+
+发送请求时无需关心`token`,程序会自动附加
+
+##### ActionRequest对象
+| 方法 | 说明| 默认值|
+| --- | --- |---|
+|dataType(string)|响应格式|json|
+|contentType(string)|请求头`Content-Type`|application/x-www-form-urlencoded|
+|data(object)|请求数据对象|{}|
+|showSuccessTip()|显示业务执行成功页面提示 不调用默认`不提示`||
+|hideErrorTip()|隐藏业务执行异常页面提示 不调用默认`提示`||
+|success(function)|业务执行成功回调函数|null|
+|error(function)|业务执行异常回调函数|null|
+|complete(function)|请求完成回调函数|null|
+|get()|发起GET请求||
+|post()|发起POST请求||
+
+> 只有调用`get()`/`post()`方法才会发起请求
+
+
+#### 任意位置
+```js
+import {request} from '/src/plugins/request'
+import {actionUrl} from '/src/helper'
+
+// 生成标准服务端action请求地址 已自动处理action/token参数
+let url = actionUrl('action')
+
+request({ 
+    type:"get", // 请求方法
+    data:{}, // 请求数据
+    dataType:"json", // 响应格式
+    contentType:"application/x-www-form-urlencoded",
+    url:"", // 请求地址
+    success, // 成功回调
+    error, // 异常回调
+    complete  // 完成回调
+})
+```
+> 当然你也可以自己导入`axios` 按照`axios`方式发起请求
+
+### 内置组件
+
+开发过程除了可以使用[iviewui](http://v3.iviewui.com/)的组件外, 系统还内置一些与后台开发常用的组件
+
+#### 显示后台指定用户名称
+```html
+<username :id="1" default="未知用户" />
+```
+> `id`:用户ID 
+>
+>`default`:用户不存在显示文字
+
+#### 权限检查
+```html
+<auth-check action="/system/auth/assignMenu">
+    有权限是展示
+    <span slot="without">无权限时展示</span>
+</auth-check>
+```
+> `without` 插槽为可选
+>
+> 常用于根据用户指定请求的权限判断结果展示不同的内容
+
+#### 输入框式文件上传
+```html
+<upload-file v-model="url" action="/upload"></upload-file>
+```
+
+#### 列表页组件
+```html
+<table-lists></table-lists>
+```
+> 该组件集成筛选框/分页等功能
+
+#### 字段映射
+```js
+let map = [{v: 1, n: '启用'},{v: 2, n: '禁用'}]
+```
+```html
+<field-map value="2" :map="map" valueField="v" descField="n" />
+```
+> 以上代码输出:`禁用`
+>
+> 该组件常与映射字段的页面输出  `valueField`/`descField` 为可选

+ 5 - 0
babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/app'
+  ]
+}

+ 62 - 0
package.json

@@ -0,0 +1,62 @@
+{
+    "name": "admin",
+    "version": "0.1.0",
+    "private": true,
+    "scripts": {
+        "serve": "vue-cli-service serve",
+        "build": "vue-cli-service build",
+        "lint": "vue-cli-service lint"
+    },
+    "dependencies": {
+        "axios": "^0.19.0",
+        "iview": "^3.5.1",
+        "localStorage": "^1.0.4",
+        "lodash": "^4.17.15",
+        "marked": "^0.7.0",
+        "qs": "^6.9.0",
+        "vue": "^2.6.10",
+        "vue-router": "^3.1.3",
+        "vuex": "^3.1.1"
+    },
+    "devDependencies": {
+        "@vue/cli-plugin-babel": "^3.12.0",
+        "@vue/cli-plugin-eslint": "^3.12.0",
+        "@vue/cli-service": "^3.12.0",
+        "babel-eslint": "^10.0.3",
+        "eslint": "^5.16.0",
+        "eslint-plugin-vue": "^5.2.3",
+        "vue-template-compiler": "^2.6.10"
+    },
+    "eslintConfig": {
+        "root": true,
+        "env": {
+            "node": true
+        },
+        "extends": [
+            "plugin:vue/essential",
+            "eslint:recommended"
+        ],
+        "rules": {
+            "no-console": "off",
+            "vue/no-parsing-error": [
+                2,
+                {
+                    "x-invalid-end-tag": false
+                }
+            ]
+        },
+        "parserOptions": {
+            "parser": "babel-eslint"
+        }
+    },
+    "postcss": {
+        "plugins": {
+            "autoprefixer": {}
+        }
+    },
+    "browserslist": [
+        "> 1%",
+        "last 2 versions",
+        "not ie <= 8"
+    ]
+}

BIN
public/favicon.ico


+ 93 - 0
public/index.html

@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title>管理后台</title>
+    <style>
+        #loading {
+            width: 100%;
+            height: 100%;
+            background-color: #FFF;
+            position: absolute;
+            top: 0;
+            left: 0;
+            z-index: 99999;
+        }
+
+        #loading .spinner {
+            margin: 200px auto 0;
+            width: 50px;
+            height: 60px;
+            text-align: center;
+            font-size: 10px;
+        }
+
+        #loading .spinner > div {
+            background-color: #67CF22;
+            height: 100%;
+            width: 6px;
+            display: inline-block;
+            -webkit-animation: stretchdelay 1.2s infinite ease-in-out;
+            animation: stretchdelay 1.2s infinite ease-in-out;
+        }
+
+        #loading .spinner .rect2 {
+            -webkit-animation-delay: -1.1s;
+            animation-delay: -1.1s;
+        }
+
+        #loading .spinner .rect3 {
+            -webkit-animation-delay: -1.0s;
+            animation-delay: -1.0s;
+        }
+
+        #loading .spinner .rect4 {
+            -webkit-animation-delay: -0.9s;
+            animation-delay: -0.9s;
+        }
+
+        #loading .spinner .rect5 {
+            -webkit-animation-delay: -0.8s;
+            animation-delay: -0.8s;
+        }
+
+        @-webkit-keyframes stretchdelay {
+            0%, 40%, 100% {
+                -webkit-transform: scaleY(0.4)
+            }
+            20% {
+                -webkit-transform: scaleY(1.0)
+            }
+        }
+
+        @keyframes stretchdelay {
+            0%, 40%, 100% {
+                transform: scaleY(0.4);
+                -webkit-transform: scaleY(0.4);
+            }
+            20% {
+                transform: scaleY(1.0);
+                -webkit-transform: scaleY(1.0);
+            }
+        }
+    </style>
+</head>
+<body>
+<noscript>
+    <strong>We're sorry but doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+</noscript>
+<div id="loading">
+    <div class="spinner">
+        <div class="rect1"></div>
+        <div class="rect2"></div>
+        <div class="rect3"></div>
+        <div class="rect4"></div>
+        <div class="rect5"></div>
+    </div>
+</div>
+<div id="app"></div>
+</body>
+</html>

+ 165 - 0
src/App.vue

@@ -0,0 +1,165 @@
+<template>
+    <div id="app">
+        <Login v-if="!isLogin"></Login>
+        <div class="main layout" v-if="isLogin">
+            <Layout>
+                <Sider class="left-sider" ref="side1" hide-trigger collapsible :collapsed-width="80"
+                       v-model="isCollapsed">
+                    <LeftMenu :menuClass="menuitemClasses" :isCollapsed="isCollapsed"></LeftMenu>
+                </Sider>
+                <Layout :style="{position: 'relative',marginLeft: '200px'}">
+                    <Header style="padding: 0;height:50px;line-height: 50px" class="layout-header-bar">
+                        <!-- <Icon @click.native="collapsedSider" :class="rotateIcon" :style="{margin: '0 10px 0 20px'}"
+                              type="md-menu" size="28"></Icon> -->
+                        <Breadcrumb class="nav">
+                            <BreadcrumbItem v-for="item in nav" :key="item.id">
+                                <Icon :type="item.icon"></Icon>
+                                {{item.name}}
+                            </BreadcrumbItem>
+                        </Breadcrumb>
+                        <Dropdown @on-click="selectDropdown" style="float: right;margin-right: 20px">
+                            <a href="javascript:void(0)">
+                                <Avatar src="" icon="ios-person" size="small" style="marginRight:5px" />
+                                {{user.username}}
+                                <Icon type="ios-arrow-down"></Icon>
+                            </a>
+                            <DropdownMenu slot="list">
+                                <DropdownItem name="userSetting">用户设置</DropdownItem>
+                                <DropdownItem name="logout">退出</DropdownItem>
+                            </DropdownMenu>
+                        </Dropdown>
+                    </Header>
+                    <Content style="margin:10px;background: #fff;padding: 10px">
+                        <router-view/>
+                    </Content>
+                </Layout>
+            </Layout>
+            <userSetting ref="userSetting"></userSetting>
+        </div>
+    </div>
+</template>
+
+<script>
+    import Login from './views/system/components/Login.vue'
+    import LeftMenu from './views/system/components/LeftMenu.vue'
+    import userSetting from './views/system/components/userSetting.vue'
+    import {setTitle} from './router'
+    import {token} from './helper'
+    import _ from 'lodash'
+
+    export default {
+        components: {
+            Login,
+            LeftMenu,
+            userSetting
+        },
+        data() {
+            return {
+                isCollapsed: false,
+            }
+        },
+        computed: {
+            isLogin() {
+                return this.$store.getters.getAdminUser.hasOwnProperty('id');
+            },
+            nav() {
+                let menus = [];
+                let ids = this.$store.getters.getCurrentMenuIds;
+                this.$store.getters.getAdminMenu.forEach((item) => {
+                    if (_.indexOf(ids, item.id) !== -1) {
+                        menus.push(item)
+                    }
+                });
+
+                return menus
+            },
+            user() {
+                return this.$store.getters.getAdminUser;
+            },
+            rotateIcon() {
+                return [
+                    'menu-icon',
+                    this.isCollapsed ? 'rotate-icon' : ''
+                ];
+            },
+            menuitemClasses() {
+                return [
+                    'menu-item',
+                    this.isCollapsed ? 'collapsed-menu' : ''
+                ]
+            }
+        },
+        created() {
+        },
+        mounted() {
+            this.$router.onReady(() => {
+                this.initialize();
+            });
+        },
+        methods: {
+            // 初始化系统
+            initialize() {
+                if (token()){
+                    this.$request("/load").success((r) => {
+                        this.$store.dispatch('initialize', r.data)
+                        setTitle(this.$route)
+                    }).error(() => {
+                        this.$store.dispatch('logout');
+                    }).complete(() => {
+                        document.querySelector("#loading").remove();
+                    }).get();
+                }
+                else{
+                    document.querySelector("#loading").remove();
+                }
+            },
+            selectDropdown(name) {
+                this[name]();
+            },
+            logout() {
+                this.$request("/logout").success(() => {
+                    this.$store.dispatch('logout');
+                    setTitle(this.$route)
+                }).get();
+            },
+            userSetting() {
+                this.$refs.userSetting.show();
+            },
+            collapsedSider() {
+                this.$refs.side1.toggleCollapse();
+            }
+        },
+    }
+</script>
+<style scoped>
+    .layout {
+        background: #f5f7f9;
+        position: relative;
+        overflow: hidden;
+    }
+    .left-sider {
+        height: 100vh;
+        position: fixed;
+        left: 0;
+        overflow: auto;
+    }
+    .layout-header-bar {
+        background: #fff;
+        box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
+    }
+
+    .menu-icon {
+        transition: all .3s;
+    }
+
+    .rotate-icon {
+        transform: rotate(-90deg);
+    }
+
+    .nav {
+        display: inline-block;
+        width: 500px;
+        vertical-align: middle;
+        padding-left: 20px;
+    }
+</style>

+ 34 - 0
src/components/authCheck.vue

@@ -0,0 +1,34 @@
+<template>
+  <div style="display: inline">
+    <template v-if="is">
+        <slot></slot>
+    </template>
+    <template v-else>
+      <slot name="without"></slot>
+    </template>
+  </div>
+</template>
+<script>
+    export default {
+        name: 'authCheck',
+        props: {
+            action: {
+                type: String,
+                default: function () {
+                    return ''
+                }
+            }
+        },
+        computed: {
+            is () {
+                let is = false
+                this.$store.getters.getAdminRequest.forEach(item => {
+                    if (item.action === this.action) {
+                        is = true
+                    }
+                })
+                return is
+            }
+        }
+    }
+</script>

+ 45 - 0
src/components/fieldMap.vue

@@ -0,0 +1,45 @@
+<template>
+  <span>{{desc}}</span>
+</template>
+<script>
+    export default {
+        name: 'fieldMap',
+        props: {
+            value: {
+                type: [String, Number],
+                default: function () {
+                    return ''
+                }
+            },
+            map: {
+                type: Array,
+                default: function () {
+                    return []
+                }
+            },
+            valueField: {
+                type: String,
+                default: function () { return 'v'}
+            },
+            descField: {
+                type: String,
+                default: function () { return 'n'}
+            },
+            default: {
+                type: String,
+                default: function () { return ''}
+            }
+        },
+        computed: {
+            desc () {
+                let desc = this.default
+                this.map.forEach(item => {
+                    if (String(this.value) === String(item[this.valueField])) {
+                        desc = String(item[this.descField])
+                    }
+                })
+                return desc
+            },
+        }
+    }
+</script>

+ 35 - 0
src/components/pageBar.vue

@@ -0,0 +1,35 @@
+<template>
+    <div class="lists-filter">
+        <Layout :style="{background:'none'}">
+            <Content>
+                <Form inline>
+                    <slot></slot>
+                </Form>
+            </Content>
+            <Sider hide-trigger :style="{background:'none'}" :width="rightWidth" style="text-align: right">
+                <slot name="right"></slot>
+            </Sider>
+        </Layout>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "pageBar",
+        props: {
+            rightWidth: {
+                type: [Number, String],
+                default: 100
+            },
+        },
+    }
+</script>
+
+<style>
+    .lists-filter .ivu-layout.ivu-layout-has-sider>.ivu-layout, .lists-filter .ivu-layout.ivu-layout-has-sider>.ivu-layout-content{
+        overflow-x: visible;
+    }
+    .lists-filter .ivu-form-item{
+        margin-bottom: 10px;
+    }
+</style>

+ 113 - 0
src/components/tableLists.vue

@@ -0,0 +1,113 @@
+<template>
+    <div class="table-lists">
+        <page-bar v-if="!hiddenFilter">
+            <slot name="filterContent"></slot>
+            <slot name="filterButton" v-if="filterType === 1">
+                <Button type="primary" @click="reload()">查 询</Button>
+            </slot>
+            <div slot="right">
+                <slot name="filterRight"></slot>
+            </div>
+        </page-bar>
+        <slot></slot>
+        <page-bar style="margin-top: 10px" :rightWidth="750">
+            <slot name="options"></slot>
+            <div slot="right">
+                <page @on-change="changePage" :current="page" :page-size="pageSizeData" :total="total" show-total
+                      show-elevator show-sizer @on-page-size-change="pageSizeChange"
+                      :page-size-opts="[10,20,50,100,200]"></page>
+            </div>
+        </page-bar>
+    </div>
+</template>
+
+<script>
+    import _ from 'lodash'
+
+    export default {
+        name: "tableLists",
+        data() {
+            return {
+                page: 1,
+                total: 0,
+                pageSizeData: this.pageSize,
+            };
+        },
+        props: {
+            value: {
+                type: [Array,Object],
+                default: function () {
+                    return []
+                }
+            },
+            filter: {
+                type: Object,
+                default: function () {
+                    return {}
+                }
+            },
+            pageSize: {
+                type: Number,
+                default: 20
+            },
+            requestApi: {
+                type: String,
+                required: true
+            },
+            autoLoad: {
+                type: Boolean,
+                default: true
+            },
+            hiddenFilter: {
+                type: Boolean,
+                default: false
+            },
+            // 0 不显示按钮 1 搜索按钮触发 2 筛选条件发生变化触发筛选
+            filterType: {
+                type: Number,
+                default: 1
+            }
+        },
+        watch: {
+            filter: {
+                handler: function () {
+                    if (this.filterType === 2) {
+                        this.reload()
+                    }
+                },
+                deep: true
+            }
+        },
+        created() {
+            if (this.autoLoad) {
+                this.load();
+            }
+        },
+        methods: {
+            changePage: function (page) {
+                this.page = page;
+                this.load();
+            },
+            pageSizeChange: function (size) {
+                this.pageSizeData = size;
+                this.reload();
+            },
+            load() {
+                this.$request(this.requestApi).data({
+                    offset: (this.page - 1) * this.pageSizeData,
+                    pageSize: this.pageSizeData,
+                    ..._.cloneDeep(this.filter)
+                }).success((r) => {
+                    this.total = r.data['total'];
+                    this.$emit('input', r.data);
+                }).get()
+            },
+            reload(isPage) {
+                if (!isPage) {
+                    this.page = 1;
+                }
+                this.load();
+            },
+        },
+    }
+</script>

+ 132 - 0
src/components/uploadFile.vue

@@ -0,0 +1,132 @@
+<template>
+    <div style="padding-bottom: 10px">
+        <Input :value="value" disabled>
+            <div slot="append">
+                <Upload
+                        :name="name"
+                        :data="{key:name}"
+                        :format="format"
+                        :max-size="maxSize"
+                        :show-upload-list="false"
+                        :action="url"
+                        :on-progress="onUploadProgress"
+                        :on-success="onUploadSuccess"
+                        :on-error="onUploadError"
+                        :on-remove="onUploadRemove"
+                        :on-preview="onUploadPreview"
+                        :on-exceeded-size="onUploadExceededSize"
+                        :on-format-error="onUploadFormatError"
+                        style="display: inline-block;"
+                >
+                    <Button icon="md-cloud-upload">选择文件</Button>
+                </Upload>
+                <Button icon="md-search" @click="view"></Button>
+                <Button icon="md-backspace" @click="remove"></Button>
+            </div>
+        </Input>
+    </div>
+</template>
+<script>
+    import {actionUrl} from "../helper";
+    import {requestSuccessHandle} from "../plugins/request";
+
+    export default {
+        name: "uploadFile",
+        data() {
+            return {}
+        },
+        props: {
+            value: {
+                type: String,
+                default: function () {
+                    return "";
+                }
+            },
+            action: {
+                type: String,
+                default: function () {
+                    return "";
+                }
+            },
+            name: {
+                type: String,
+                default: function () {
+                    return "file";
+                }
+            },
+            format: {
+                type: Array,
+                default: function () {
+                    return ['jpeg', 'jpg', 'png', 'gif']
+                }
+            },
+            domain: {
+                type: String,
+                default: function () {
+                    return "";
+                }
+            },
+            maxSize: {
+                type: Number, // 大小 单位kb
+            },
+            onSuccess: {
+                type: Function,
+                default() {
+                    return {};
+                }
+            },
+            onError: {
+                type: Function,
+                default() {
+                    return {};
+                }
+            },
+            onRemove: {
+                type: Function,
+                default() {
+                    return {};
+                }
+            }
+        },
+        computed: {
+            url () {
+                return actionUrl(this.action)
+            }
+        },
+        methods: {
+            remove() {
+                this.$emit('input', "");
+                this.$emit('remove')
+            },
+            view() {
+                window.open(this.domain + "" + this.value)
+            },
+            onUploadProgress() {
+            },
+            onUploadSuccess(response, file, fileList) {
+                requestSuccessHandle(this, response, true, true, () => {
+                    this.onSuccess(response.data, file, fileList);
+                    this.$emit('input', response.data.url);
+                })
+            },
+            onUploadError(error, file) {
+                this.$Notice.error({
+                    title: '错误提示',
+                    desc: error,
+                    duration: 10
+                });
+                this.onError(error, file)
+            },
+            onUploadRemove() {
+            },
+            onUploadPreview() {
+            },
+            onUploadExceededSize(file) {
+                this.onUploadError("文件过大,最大文件大小为:" + this.maxSize + 'kb', file)
+            },
+            onUploadFormatError(file) {
+                this.onUploadError("文件格式错误,允许上传的文件格式为:" + this.format.join(','), file)
+            },
+        },
+    }
+</script>

+ 26 - 0
src/components/uploadImg.vue

@@ -0,0 +1,26 @@
+<template>
+    <div class="upload-img">
+        <Upload
+            :before-upload="handleUpload"
+            action="//jsonplaceholder.typicode.com/posts/">
+            <Button icon="ios-cloud-upload-outline">Select the file to upload</Button>
+        </Upload>
+    </div>
+</template>
+
+<script>
+export default {
+    data () {
+        return {
+            file: ''
+        }
+    },
+    methods:{
+        handleUpload (file) {
+            this.file = file;
+            console.log(file)
+            return false;
+        }  
+    },
+}
+</script>

+ 35 - 0
src/components/username.vue

@@ -0,0 +1,35 @@
+<template>
+    <span>{{username}}</span>
+</template>
+<script>
+    export default {
+        name: 'username',
+        props: {
+            id: {
+                type: [String, Number],
+                default: function () {
+                    return 0
+                }
+            },
+            default: {
+                type: String,
+                default: function () {
+                    return "未知用户"
+                }
+            },
+        },
+        computed: {
+            username () {
+                let id = parseInt(this.id)
+                let allUser = this.$store.getters.getAdminAllUser
+                let username = this.default
+                allUser.forEach(user => {
+                    if (user.id === id) {
+                        username = user.username
+                    }
+                })
+                return username
+            },
+        }
+    }
+</script>

+ 55 - 0
src/helper.js

@@ -0,0 +1,55 @@
+import localStorage from 'localStorage'
+import _ from "lodash";
+
+export const trim = function (str, char, type) {
+    if (char) {
+        if (type === 'left') {
+            return str.replace(new RegExp('^\\' + char + '+', 'g'), '')
+        } else if (type === 'right') {
+            return str.replace(new RegExp('\\' + char + '+$', 'g'), '')
+        }
+        return str.replace(new RegExp('^\\' + char + '+|\\' + char + '+$', 'g'), '')
+    }
+    return str.replace(/^\s+|\s+$/g, '')
+}
+
+// 配置获取
+export const config = function (key) {
+    return process.env['VUE_APP_' + key]
+}
+
+export const token = function () {
+    return localStorage.getItem(config('ADMIN_TOKEN_NAME'))
+}
+
+export const serverUrl = function (data = {}) {
+    let query = []
+    Object.keys(data).forEach((item) => {
+        query.push(item + '=' + encodeURIComponent(data[item]))
+    })
+    return config('API_URL_PREFIX') + (query.length > 0 ? ('?' + query.join('&')) : '')
+}
+
+export const actionUrl = function (action) {
+    let data = {}
+    data[config('API_ACTION_NAME')] = action
+    if (token()) {
+        data[config('API_TOKEN_NAME')] = token()
+    }
+    return serverUrl(data)
+}
+
+// 菜单排序
+export const menuSort = function (menus) {
+    let m = _.cloneDeep(menus)
+    m.sort((item1, item2) => {
+        if (item1.sort < item2.sort) {
+            return -1
+        }
+        if (item1.sort === item2.sort) {
+            return item1.id < item2.id ? -1 : 1
+        }
+        return 1
+    })
+    return m;
+}

+ 30 - 0
src/main.js

@@ -0,0 +1,30 @@
+import Vue from 'vue'
+import iView from 'iview'
+import 'iview/dist/styles/iview.css'
+import {vueRequest} from './plugins/request'
+import store from './store'
+import router from './router'
+import pageBar from './components/pageBar'
+import tableLists from './components/tableLists'
+import username from './components/username'
+import authCheck from './components/authCheck'
+import uploadFile from './components/uploadFile'
+import fieldMap from './components/fieldMap'
+import App from './App.vue'
+
+Vue.config.productionTip = false;
+Vue.use(iView);
+Vue.use(vueRequest);
+
+Vue.component('page-bar',pageBar);
+Vue.component('username',username);
+Vue.component('auth-check',authCheck);
+Vue.component('table-lists',tableLists);
+Vue.component('upload-file',uploadFile);
+Vue.component('field-map',fieldMap);
+
+new Vue({
+    router,
+    store,
+    render: h => h(App)
+}).$mount('#app');

+ 162 - 0
src/plugins/request.js

@@ -0,0 +1,162 @@
+import axios from 'axios'
+import qs from 'qs'
+
+import { actionUrl } from '../helper'
+import _ from 'lodash'
+
+export const request = function ({ type, data, dataType,contentType, url, success, error, complete }) {
+    type = (type || 'get').toUpperCase()
+    let config = {
+        method: type || 'get',
+        url: url,
+        headers: { 'content-type': contentType || "application/x-www-form-urlencoded" },
+        responseType: dataType || 'json'
+    }
+    if (_.indexOf(['POST', 'PUT', 'PATCH'], type) === -1) {
+        config.params = data
+    } else {
+        config.data = qs.stringify(data)
+    }
+    axios(config).then((response) => {
+        complete && complete()
+        success && success(response.data)
+    }).catch((e) => {
+        complete && complete()
+        error && error(e)
+    })
+}
+
+export const requestSuccessHandle = function (vue, response, tipSuccess, tipError, success, error) {
+    if (response.status === 'success') {
+        if (tipSuccess) {
+            vue.$Notice.success({
+                title: '操作提示',
+                desc: response.info,
+                duration: 2
+            })
+        }
+        success && success(response)
+    } else {
+        if (tipError) {
+            vue.$Notice.error({
+                title: '错误提示',
+                desc: response.info,
+                duration: 5
+            })
+            // if (response.info == '暂无权限') {
+            //     this.$request("/load").success((r) => {
+                    
+            //     })
+            // }
+        }
+        error && error(response)
+    }
+}
+
+class ActionRequest {
+    _type = 'get'
+    _action = ''
+    _data = {}
+    _tipSuccess = false
+    _tipError = true
+    _dataType = 'json'
+    _contentType = 'application/x-www-form-urlencoded'
+    _success = null
+    _error = null
+    _complete = null
+
+    constructor (vue) {
+        this.vue = vue
+    }
+
+    dataType (dataType) {
+        this._dataType = dataType
+        return this
+    }
+
+    contentType (contentType) {
+        this._contentType = contentType
+        return this
+    }
+
+    action (action) {
+        this._action = action
+        return this
+    }
+
+    data (data) {
+        this._data = data
+        return this
+    }
+
+    showSuccessTip () {
+        this._tipSuccess = true
+        return this
+    }
+
+    hideErrorTip () {
+        this._tipError = false
+        return this
+    }
+
+    success (success) {
+        this._success = success
+        return this
+    }
+
+    error (error) {
+        this._error = error
+        return this
+    }
+
+    complete (complete) {
+        this._complete = complete
+        return this
+    }
+
+    get () {
+        return this.execute('get')
+    }
+
+    post () {
+        return this.execute('post')
+    }
+
+    execute (type) {
+        this._type = type
+        request({
+            type: this._type,
+            dataType: this._dataType,
+            contentType: this._contentType,
+            data: this._data,
+            url: actionUrl(this._action),
+            success: (response) => {
+                requestSuccessHandle(
+                    this.vue,
+                    response,
+                    this._tipSuccess,
+                    this._tipError,
+                    this['_success'],
+                    this['_error']
+                )
+            },
+            error: () => {
+                this.vue.$Notice.error({ title: '对不起您请求的数据不存在或者返回异常', duration: 5 })
+            },
+            complete: () => {
+                this.vue.$Loading.finish()
+                this['_complete'] && this['_complete']()
+            }
+        })
+    }
+}
+
+export const vueRequest = {
+    install: function (Vue) {
+        Vue.prototype.$request = function (action) {
+            let actionRequest = new ActionRequest(this)
+            actionRequest.action(action)
+            return actionRequest
+        }
+    }
+}

+ 68 - 0
src/router.js

@@ -0,0 +1,68 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import {config, trim} from './helper'
+import _ from "lodash";
+
+// 默认title
+document.title = config('SITE_NAME');
+// title 模板
+const siteTitleTpl = config('SITE_TITLE_TPL');
+
+// 路由配置
+const routes = [
+    {path: '/', name: 'index', component: () => import('./views/index.vue')},
+];
+
+// 路由自动加载
+const routeComponent = require.context(
+    './views',
+    true,
+    /\.vue$/
+);
+
+routeComponent.keys().forEach(fileName => {
+    // 过滤组件
+    if (fileName.indexOf('components/') !== -1 || fileName === "index") {
+        return;
+    }
+    const path = '/' + trim(fileName.replace(/^\.\/(.*)\.\w+$/, '$1'), '/', 'left');
+    routes.push({
+        path: path,
+        name: path,
+        component: routeComponent(fileName).default
+    })
+});
+
+Vue.use(Router);
+
+const router = new Router({routes});
+
+const originalPush = Router.prototype.push
+Router.prototype.push = function push(location) {// 解决路由重复报错的问题
+  return originalPush.call(this, location).catch(err => err)
+}
+
+// title 设置
+export const setTitle = function (to) {
+    let title = config('SITE_NAME');
+    let currentMenu = {};
+    if (router.app.$store.getters.getAdminMenu.length) {
+        router.app.$store.getters.getAdminMenu.forEach(item => {
+            if (item.url === to.path) {
+                title = siteTitleTpl.replace(/{title}/g, item.name);
+                currentMenu = _.cloneDeep(item);
+            }
+        });
+    }
+    router.app.$store.dispatch('updateCurrentMenu', currentMenu);
+    document.title = title;
+};
+
+router.afterEach(to => {
+    if (router.app.$store.getters.getAdminMenu.length !== 0) {
+        setTitle(to)
+    }
+    window.scrollTo(0, 0);
+});
+
+export default router

+ 14 - 0
src/store.js

@@ -0,0 +1,14 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import admin from './store/admin'
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+    state: {},
+    mutations: {},
+    actions: {},
+    modules: {
+        admin
+    }
+})

+ 100 - 0
src/store/admin.js

@@ -0,0 +1,100 @@
+import { config } from '../helper'
+import localStorage from 'localStorage'
+
+export default {
+    state: {
+        adminUser: {},
+        adminMenu: [],
+        adminAllUser: [],
+        adminRequest: [],
+        adminAuth: [],
+        adminUserGroup: [],
+        currentMenu: {},
+    },
+    getters: {
+        getAdminUser (state) {
+            return state.adminUser
+        },
+        getAdminMenu (state) {
+            return state.adminMenu
+        },
+        getAdminAllUser (state) {
+            return state.adminAllUser
+        },
+        getAdminRequest (state) {
+            return state.adminRequest
+        },
+        getAdminAuth (state) {
+            return state.adminAuth
+        },
+        getAdminUserGroup (state) {
+            return state.adminUserGroup
+        },
+        getCurrentMenu (state) {
+            return state.currentMenu
+        },
+        getCurrentMenuIds (state) {
+            if (Object.keys(state.currentMenu).length !== 0) {
+                let ids = [state.currentMenu.id]
+                let current = state.currentMenu
+                const allMenu = state.adminMenu
+                while (current.parent_id) {
+                    for (let i = 0; i < allMenu.length; i++) {
+                        if (current.parent_id === allMenu[i].id) {
+                            ids.push(allMenu[i].id)
+                            current = allMenu[i]
+                            break
+                        }
+                    }
+                }
+                return ids.reverse()
+            }
+            return []
+        },
+    },
+    mutations: {
+        setAdminUser (state, user) {
+            state.adminUser = user ? user : {}
+        },
+        setAdminAllUser (state, user) {
+            state.adminAllUser = user ? user : []
+        },
+        setAdminMenu (state, menu) {
+            state.adminMenu = menu ? menu : []
+        },
+        setAdminRequest (state, request) {
+            state.adminRequest = request ? request : []
+        },
+        setAdminAuth (state, auth) {
+            state.adminAuth = auth ? auth : []
+        },
+        setAdminUserGroup (state, userGroup) {
+            state.adminUserGroup = userGroup ? userGroup : []
+        },
+
+        setCurrentMenu (state, menu) {
+            state.currentMenu = menu ? menu : {}
+        }
+    },
+    actions: {
+        initialize ({ commit }, { user, menu, allUser, request, auth, userGroup }) {
+            commit('setAdminUser', user)
+            commit('setAdminMenu', menu)
+            commit('setAdminAllUser', allUser)
+            commit('setAdminRequest', request)
+            commit('setAdminAuth', auth)
+            commit('setAdminUserGroup', userGroup)
+        },
+        logout ({ commit }) {
+            localStorage.removeItem(config('ADMIN_TOKEN_NAME'))
+            commit('setAdminUser', {})
+            commit('setAdminMenu', [])
+        },
+        login (context, { token }) {
+            localStorage.setItem(config('ADMIN_TOKEN_NAME'), token)
+        },
+        updateCurrentMenu ({ commit }, menu) {
+            commit('setCurrentMenu', menu)
+        },
+    }
+}

+ 142 - 0
src/views/example.vue

@@ -0,0 +1,142 @@
+<template>
+    <div>
+        <page-bar>
+            <FormItem>
+                <Input type="text" placeholder="文本框" clearable/>
+            </FormItem>
+            <FormItem>
+                <AutoComplete v-model="autoComplete.value" :data="autoComplete.data"
+                              @on-search="handleSearch1"
+                              placeholder="输入框搜索"
+                              style="width:200px"></AutoComplete>
+            </FormItem>
+            <FormItem>
+                <Cascader :data="cascader.data" v-model="cascader.value" placeholder="级联选择"></Cascader>
+            </FormItem>
+            <FormItem>
+                <i-switch v-model="isSwitch">
+                    <span slot="open">开</span>
+                    <span slot="close">关</span>
+                </i-switch>
+            </FormItem>
+            <FormItem>
+                <Checkbox>多选框</Checkbox>
+                <Checkbox>多选框</Checkbox>
+            </FormItem>
+            <FormItem>
+                <Radio>单选按钮</Radio>
+                <Radio>单选按钮</Radio>
+                <Radio>单选按钮</Radio>
+                <Radio>单选按钮</Radio>
+            </FormItem>
+            <FormItem>
+                <Select v-model="datepPicker" style="width:200px" placeholder="下拉菜单">
+                    <Option v-for="(item,k) in select" :value="k" :key="k">{{ item }}
+                    </Option>
+                </Select>
+            </FormItem>
+            <FormItem>
+                <DatePicker :type="datepPicker || 'date'" :placeholder="select[datepPicker || 'date']" style="width: 200px"></DatePicker>
+            </FormItem>
+            <FormItem>
+                <Button>查询</Button>
+            </FormItem>
+            <div slot="right">
+                <Button type="success" icon="md-add">添加</Button>
+            </div>
+        </page-bar>
+        <Card title="文件上传">
+            <upload-file v-model="uploadFileUrl"></upload-file>
+        </Card>
+    </div>
+</template>
+<script>
+    export default {
+        data() {
+            return {
+                uploadFileUrl:"",
+                isSwitch: true,
+                select: {
+                    date: "日期",
+                    daterange: "日期范围",
+                    datetime: "日期时间",
+                    datetimerange: "日期时间范围",
+                    year: "年",
+                    month: "月",
+                },
+                datepPicker: "",
+                autoComplete: {
+                    value: '',
+                    data: []
+                },
+                cascader:{
+                    value: [],
+                    data: [{
+                        value: 'beijing',
+                        label: '北京',
+                        children: [
+                            {
+                                value: 'gugong',
+                                label: '故宫'
+                            },
+                            {
+                                value: 'tiantan',
+                                label: '天坛'
+                            },
+                            {
+                                value: 'wangfujing',
+                                label: '王府井'
+                            }
+                        ]
+                    }, {
+                        value: 'jiangsu',
+                        label: '江苏',
+                        children: [
+                            {
+                                value: 'nanjing',
+                                label: '南京',
+                                children: [
+                                    {
+                                        value: 'fuzimiao',
+                                        label: '夫子庙',
+                                    }
+                                ]
+                            },
+                            {
+                                value: 'suzhou',
+                                label: '苏州',
+                                children: [
+                                    {
+                                        value: 'zhuozhengyuan',
+                                        label: '拙政园',
+                                    },
+                                    {
+                                        value: 'shizilin',
+                                        label: '狮子林',
+                                    }
+                                ]
+                            }
+                        ],
+                    }]
+                }
+            }
+        },
+        computed: {},
+        created() {
+        },
+        mounted() {
+        },
+        methods: {
+            handleSearch1(value) {
+                this.autoComplete.data = !value ? [] : [
+                    value,
+                    value + value,
+                    value + value + value
+                ];
+            }
+        },
+    }
+</script>
+<style scoped>
+
+</style>

+ 18 - 0
src/views/index.vue

@@ -0,0 +1,18 @@
+<template>
+    <div>
+        <Alert type="success">默认后台首页地址 可以在 <code>.env</code> 中配置 <code>INDEX_URL</code></Alert>
+        <Button type="primary" @click="$router.push('/example')">例子页面</Button>
+    </div>
+</template>
+
+<script>
+    import { config } from '../helper'
+
+    export default {
+        created() {
+            if (config('INDEX_URL') !== "/") {
+                this.$router.replace(config('INDEX_URL'))
+            }
+        },
+    }
+</script>

+ 167 - 0
src/views/system/auth.vue

@@ -0,0 +1,167 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="list" :filter="filter" :filterType="2" requestApi="/system/auth/lists">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词"/>
+                </FormItem>
+            </template>
+            <template slot="filterRight">
+                <Button type="success" icon="md-add" @click="add()">添加</Button>
+            </template>
+            <Table :columns="columns" :data="list.lists" stripe>
+                <template slot-scope="{ row }" slot="_request">
+                    <Button size="small" @click="showAssign(row,'request')">
+                        关联请求({{row.request.length}})
+                    </Button>
+                </template>
+                <template slot-scope="{ row }" slot="_userGroup">
+                    <Button size="small" @click="showAssign(row,'userGroup')">
+                        关联用户组({{row.userGroup.length}})
+                    </Button>
+                </template>
+                <template slot-scope="{ row }" slot="_menu">
+                    <Button size="small" @click="showAssign(row,'menu')">
+                        关联菜单({{row.menu.length}})
+                    </Button>
+                </template>
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="primary" @click="edit(row)" style="margin-right: 5px">编辑
+                    </Button>
+                    <Button size="small" type="error" @click="remove(row)">删除</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Drawer :title="assign.data.name+' 菜单关联'" v-model="assign.show.menu" width="300" :mask-closable="false">
+            <AssignMenu v-if="assign.show.menu" :id="assign.data.id" @reload="reload"></AssignMenu>
+        </Drawer>
+        <Drawer :title="assign.data.name+' 请求关联'" v-model="assign.show.request" width="900" :mask-closable="false">
+            <AssignRequest v-if="assign.show.request" :id="assign.data.id" @reload="reload"></AssignRequest>
+        </Drawer>
+        <Drawer :title="assign.data.name+' 用户组关联'" v-model="assign.show.userGroup" width="900" :mask-closable="false">
+            <AssignUserGroup v-if="assign.show.userGroup" :id="assign.data.id" @reload="reload"></AssignUserGroup>
+        </Drawer>
+        <Modal v-model="current.show" :title="current.data['id'] ? '编辑' : '添加'" :width="400" @on-ok="save">
+            <Form :label-width="50">
+                <FormItem label="名称">
+                    <Input v-model="current.data.name" type="text"></Input>
+                </FormItem>
+                <FormItem label="描述">
+                    <Input v-model="current.data.description" type="textarea"></Input>
+                </FormItem>
+            </Form>
+            <template slot="footer">
+                <Button type="primary" size="large" @click="save">保存</Button>
+            </template>
+        </Modal>
+    </div>
+</template>
+<script>
+    import AssignRequest from './components/AssignRequest'
+    import AssignUserGroup from './components/AssignUserGroup'
+    import AssignMenu from './components/AssignMenu'
+    import _ from "lodash";
+
+    export default {
+        components: {
+            AssignRequest,
+            AssignUserGroup,
+            AssignMenu
+        },
+        methods: {
+            add() {
+                this.current.data = {};
+                this.current.show = true;
+            },
+            edit(row) {
+                this.current.data = _.cloneDeep(row);
+                console.log(_.cloneDeep(row))
+                this.current.show = true;
+            },
+            remove(row) {
+                this.$Modal.confirm({
+                    title: "确认要删除[" + row.name + "]?",
+                    onOk: () => {
+                        this.$request('/system/auth/remove').data({id: row.id}).showSuccessTip().success(() => {
+                            this.reload()
+                        }).get();
+                    }
+                });
+            },
+            save() {
+                this.$request('/system/auth/save').data(this.current.data).showSuccessTip().success(() => {
+                    this.reload()
+                    this.current.show = false;
+                }).post();
+            },
+            showAssign(row,type){
+                this.assign.data = _.cloneDeep(row);
+                this.assign.show[type] = true;
+            },
+            reload(){
+                this.$refs.tableLists.reload(true);
+            }
+        },
+        data() {
+            return {
+                assign:{
+                    data:{},
+                    show:{
+                        request:false,
+                        userGroup:false,
+                        menu:false,
+                    },
+                },
+                filter: {
+                    keyword: "",
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'name',
+                        width: 200
+                    },
+                    {
+                        title: '创建时间',
+                        key: 'create_time',
+                        align: 'center',
+                        width: 200
+                    },
+                    {
+                        title: '请求',
+                        slot: '_request',
+                        width: 140,
+                        align: 'center',
+                    },
+                    {
+                        title: '用户',
+                        slot: '_userGroup',
+                        width: 140,
+                        align: 'center',
+                    },
+                    {
+                        title: '菜单',
+                        slot: '_menu',
+                        width: 140,
+                        align: 'center',
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                    },
+                ],
+                list: [],
+                current:{
+                    show:false,
+                    data:{},
+                },
+            }
+        }
+    }
+</script>

+ 77 - 0
src/views/system/components/AssignMenu.vue

@@ -0,0 +1,77 @@
+<template>
+    <Tree ref="tree" :data="menu" show-checkbox multiple @on-check-change="change"></Tree>
+</template>
+
+<script>
+    import { menuSort } from '../../../helper'
+
+    export default {
+        name: 'AssignMenu',
+        data() {
+            return {
+                menu: [],
+            }
+        },
+        props: {
+            id: Number,
+        },
+        created() {
+            this.load();
+        },
+        methods: {
+            tree(menus, pid, level) {
+                let menu = [];
+                menus.forEach(item => {
+                    if (item['parent_id'] === pid) {
+                        let data = {
+                            menu: item,
+                            title: item.name,
+                            level: level,
+                            expand: true,
+                            children: item['url'] ? [] : this.tree(menus, item['id'], level + 1),
+                        };
+                        if (item['url'] && item.checked) {
+                            data.checked = true;
+                        }
+                        menu.push(data);
+                    }
+                });
+                return menu;
+            },
+            load() {
+                this.$request('/system/auth/getMenu').data({id: this.id}).success((r) => {
+                    this.menu = this.tree(menuSort(r.data), 0, 1);
+                }).get();
+            },
+            change() {
+                let menu = this.$refs.tree.getCheckedAndIndeterminateNodes();
+                let request = this.$request('/system/auth/assignMenu').data({
+                    menuIds: menu.map(item => {
+                        return item.menu.id;
+                    }),
+                    id:this.id
+                }).success(() => {
+                    this.$emit('reload')
+                });
+                if (menu.length < 1) {
+                    return this.$Modal.confirm({
+                        title: "确定要清空当前的所有菜单?",
+                        onOk: () => {
+                            request.post();
+                        },
+                        onCancel:() => {
+                            this.load();
+                        },
+                    });
+                }
+                request.post();
+            },
+            reload() {
+                this.$emit('reload')
+            }
+        }
+    }
+</script>
+<style scoped>
+
+</style>

+ 87 - 0
src/views/system/components/AssignRequest.vue

@@ -0,0 +1,87 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="data" :filter="filter" requestApi="/system/auth/getRequest">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词"/>
+                </FormItem>
+            </template>
+            <Table :columns="columns" :data="data.lists['noAssign']" stripe height="270">
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="success" @click="add(row)">加入</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Table style="margin-top: 10px" :columns="columns" :data="data.lists['assign']" stripe height="270">
+            <template slot-scope="{row,index}" slot="op">
+                <Button size="small" type="error" @click="remove(row,index)">移出</Button>
+            </template>
+        </Table>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: 'AssignRequest',
+        data() {
+            return {
+                data: {
+                    lists:[]
+                },
+                filter: {
+                    keyword: "",
+                    id: 0,
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'name',
+                    },
+                    {
+                        title: 'ACTION',
+                        key: 'action',
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                        width: 140,
+                    },
+                ]
+            }
+        },
+        props: {
+            id: Number,
+        },
+        created() {
+            this.filter.id = this.id;
+        },
+        methods: {
+            remove(row) {
+                this.$request('/system/auth/removeRequest').data({
+                    id: this.id,
+                    requestId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            add(row) {
+                this.$request('/system/auth/assignRequest').data({
+                    id: this.id,
+                    requestId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            reload() {
+                this.$refs.tableLists.reload(true);
+                this.$emit('reload')
+            }
+        }
+    }
+</script>

+ 83 - 0
src/views/system/components/AssignUser.vue

@@ -0,0 +1,83 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="data" :filter="filter" requestApi="/system/userGroup/getUser">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词"/>
+                </FormItem>
+            </template>
+            <Table :columns="columns" :data="data.lists['noAssign']" stripe height="270">
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="success" @click="add(row)">加入</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Table style="margin-top: 10px" :columns="columns" :data="data.lists['assign']" stripe height="270">
+            <template slot-scope="{row,index}" slot="op">
+                <Button size="small" type="error" @click="remove(row,index)">移出</Button>
+            </template>
+        </Table>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: 'AssignUser',
+        data() {
+            return {
+                data: {
+                    lists: []
+                },
+                filter: {
+                    keyword: "",
+                    id: 0,
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'username',
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                        width: 140,
+                    },
+                ]
+            }
+        },
+        props: {
+            id: Number,
+        },
+        created() {
+            this.filter.id = this.id;
+        },
+        methods: {
+            remove(row) {
+                this.$request('/system/userGroup/removeUser').data({
+                    id: this.id,
+                    userId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            add(row) {
+                this.$request('/system/userGroup/assignUser').data({
+                    id: this.id,
+                    userId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            reload() {
+                this.$refs.tableLists.reload(true);
+                this.$emit('reload')
+            }
+        }
+    }
+</script>

+ 82 - 0
src/views/system/components/AssignUserGroup.vue

@@ -0,0 +1,82 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="data" :filter="filter" requestApi="/system/auth/getUserGroup">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词"/>
+                </FormItem>
+            </template>
+            <Table :columns="columns" :data="data.lists['noAssign']" stripe height="270">
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="success" @click="add(row)">加入</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Table style="margin-top: 10px" :columns="columns" :data="data.lists['assign']" stripe height="270">
+            <template slot-scope="{row,index}" slot="op">
+                <Button size="small" type="error" @click="remove(row,index)">移出</Button>
+            </template>
+        </Table>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'AssignUserGroup',
+        data() {
+            return {
+                data: {
+                    lists: []
+                },
+                filter: {
+                    keyword: "",
+                    id: 0,
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'name',
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                        width: 140,
+                    },
+                ]
+            }
+        },
+        props: {
+            id: Number,
+        },
+        created() {
+            this.filter.id = this.id;
+        },
+        methods: {
+            remove(row) {
+                this.$request('/system/auth/removeUserGroup').data({
+                    id: this.id,
+                    userGroupId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            add(row) {
+                this.$request('/system/auth/assignUserGroup').data({
+                    id: this.id,
+                    userGroupId: row.id
+                }).success(() => {
+                    this.reload();
+                }).get();
+            },
+            reload() {
+                this.$refs.tableLists.reload(true);
+                this.$emit('reload')
+            }
+        }
+    }
+</script>

+ 154 - 0
src/views/system/components/LeftMenu.vue

@@ -0,0 +1,154 @@
+<template>
+    <Menu :active-name="active" :open-names="openNames" theme="dark" width="auto" :class="menuClass"
+          @on-select="goto" :accordion="true">
+        <MenuItem name="site-name">
+            <Icon type="ios-home"/>
+            <span>{{siteName}}</span>
+        </MenuItem>
+        <div v-for="menu in menus" :key="menu.id">
+            <MenuItem :name="menu.id" v-if="menu.url">
+                <Icon type="menu.icon"></Icon>
+                <span>{{menu.name}}</span>
+            </MenuItem>
+            <Submenu :name="menu.id" v-else>
+                <template slot="title">
+                    <Icon :type="menu.icon" v-if="menu.icon"/>
+                    <span>{{menu.name}}</span>
+                </template>
+                <div v-for="second in menu['sub_menu']" :key="second.id">
+                    <MenuItem :name="second.id" v-if="second.url">
+                        <Icon :type="second.icon" v-if="second.icon"></Icon>
+                        <span>{{second.name}}</span>
+                    </MenuItem>
+                    <Submenu :name="second.id" v-else>
+                        <template slot="title">
+                            <Icon :type="second.icon" v-if="second.icon"/>
+                            <span>{{second.name}}</span>
+                        </template>
+                        <div v-for="third in second['sub_menu']" :key="third.id">
+                            <MenuItem :name="third.id">
+                                <Icon :type="third.icon" v-if="third.icon"></Icon>
+                                <span>{{third.name}}</span>
+                            </MenuItem>
+                        </div>
+                    </Submenu>
+                </div>
+            </Submenu>
+        </div>
+    </Menu>
+</template>
+
+<script>
+    import _ from "lodash";
+    import { config } from '../../../helper'
+
+    let tree = function (menus, pid) {
+        let menu = [];
+        menus.forEach(function (item) {
+            if (item['parent_id'] === pid) {
+                item['sub_menu'] = item['url'] ? [] : tree(menus, item['id']);
+                menu.push(item)
+            }
+        });
+        return menu;
+    };
+
+    export default {
+        name: 'LeftMenu',
+        data() {
+            return {}
+        },
+        props: {
+            menuClass: Array,
+            isCollapsed: Boolean,
+        },
+        computed: {
+            siteName() {
+                return config('SITE_NAME');
+            },
+            menus() {
+                return tree(_.cloneDeep(this.$store.getters.getAdminMenu), 0);
+            },
+            pageMenus() {
+                let menu = [];
+                this.$store.getters.getAdminMenu.forEach(function (v) {
+                    if (v['url']) {
+                        menu.push(v);
+                    }
+                });
+                return menu;
+            },
+            openNames() {
+                return !this.isCollapsed ? this.$store.getters.getCurrentMenuIds : [];
+            },
+            active() {
+                let menu = "";
+                let path = this.$route.path;
+                this.pageMenus.forEach(function ({url, id}) {
+                    if (url === path) {
+                        menu = id;
+                    }
+                });
+                return menu;
+            }
+        },
+        methods: {
+            goto(menuId) {
+                if (menuId === "site-name") {
+                    return this.$router.push('/');
+                }
+                this.pageMenus.forEach(({url, id}) => {
+                    if (id === menuId) {
+                        if (url.indexOf('://') !== -1) {
+                            // 外部链接
+                            return window.open(url);
+                        } else {
+                            return this.$router.push(url);
+                        }
+                    }
+                });
+            },
+        }
+    }
+</script>
+<style>
+    .collapsed-menu .ivu-menu-submenu-title .ivu-menu-submenu-title-icon {
+        display: none;
+        transition: display .2s ease;
+    }
+
+    .collapsed-menu .ivu-menu {
+        display: none;
+        transition: display .2s ease;
+    }
+</style>
+<style scoped>
+    .menu-item span {
+        display: inline-block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        vertical-align: middle;
+        transition: width .2s ease .2s;
+    }
+
+    .menu-item i {
+        transform: translateX(0px);
+        transition: font-size .2s ease,
+        transform .2s ease;
+        vertical-align: middle;
+        font-size: 16px;
+    }
+
+    .collapsed-menu span {
+        width: 0;
+        transition: width .2s ease;
+    }
+
+    .collapsed-menu i {
+        transform: translateX(5px);
+        transition: font-size .2s ease .2s, transform .2s ease .2s;
+        vertical-align: middle;
+        font-size: 22px;
+    }
+</style>

+ 84 - 0
src/views/system/components/Login.vue

@@ -0,0 +1,84 @@
+<template>
+    <div class="login">
+        <Row style="margin-top: 150px">
+            <Col span="6" offset="9">
+                <Card>
+                    <p slot="title">用户登录</p>
+                    <Form ref="form" :model="form" :rules="rule">
+                        <FormItem prop="username">
+                            <Input size="large" type="text" v-model="form.username" placeholder="用户名">
+                                <Icon type="ios-person-outline" slot="prepend"></Icon>
+                            </Input>
+                        </FormItem>
+                        <FormItem prop="password">
+                            <Input size="large" type="password" v-model="form.password" placeholder="密码">
+                                <Icon type="ios-lock-outline" slot="prepend"></Icon>
+                            </Input>
+                        </FormItem>
+                        <FormItem style="margin-bottom: 5px">
+                            <Button size="large" type="primary" long @click="handleSubmit('form')">登 录</Button>
+                        </FormItem>
+                    </Form>
+                </Card>
+            </Col>
+        </Row>
+    </div>
+</template>
+
+<script>
+    import {setTitle} from '../../../router'
+
+    export default {
+        name: 'Login',
+        data() {
+            return {
+                form: {
+                    username: '',
+                    password: ''
+                },
+                rule: {
+                    username: [
+                        {required: true, message: '请填写用户名', trigger: 'blur'}
+                    ],
+                    password: [
+                        {required: true, message: '请填写密码', trigger: 'blur'}
+                    ]
+                }
+            }
+        },
+        methods: {
+            handleSubmit(name) {
+                let $this = this;
+                this.$refs[name].validate((valid) => {
+                    if (!valid) {
+                        return;
+                    }
+                    this.$request("/login").data($this.form).success((r) => {
+                        // 设置登录
+                        this.$store.dispatch('login', r.data);
+                        // 重新初始化系统
+                        this.$request("/load").success((r) => {
+                            this.$store.dispatch('initialize', r.data);
+                            setTitle(this.$route)
+                        }).error(() => {
+                            this.$Notice.error({title: '系统初始化发送异常', desc: r.info, duration: 5});
+                            this.$store.dispatch('logout');
+                        }).get();
+                    }).post()
+
+                })
+            }
+        }
+    }
+</script>
+<style scoped>
+    .login {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        top: 0;
+        left: 0;
+        background: url(//gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg) #f0f2f5 no-repeat center 110px;
+        background-size: 100%;
+    }
+</style>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/views/system/components/icon.js


+ 71 - 0
src/views/system/components/iconSelect.vue

@@ -0,0 +1,71 @@
+<template>
+    <Modal v-model="show" fullscreen title="选择图标" footer-hide>
+        <div style="text-align: center;line-height: 40px">
+            <Input style="width: 500px" v-model="keyword" placeholder="输入英文关键词搜索,比如 success"></Input>
+        </div>
+        <Row class="icon-lists">
+            <Col span="3" v-for="icon in icons" :key="icon">
+                <a @click="select(icon)">
+                    <Icon :type="icon" :size="38"/>
+                    <p style="text-align: center">{{icon}}</p>
+                </a>
+            </Col>
+        </Row>
+    </Modal>
+</template>
+
+<script>
+    import icon from './icon.js'
+
+    export default {
+        name: "iconSelect",
+        data() {
+            return {
+                keyword: "",
+                show: false
+            };
+        },
+        props: {
+            value: {
+                type: String,
+                default: ""
+            },
+        },
+        computed: {
+            icons() {
+                let lists = [];
+                if (!this.keyword) {
+                    lists = icon;
+                } else {
+                    icon.forEach((v) => {
+                        if (v.indexOf(this.keyword) !== -1) {
+                            lists.push(v);
+                        }
+                    });
+                }
+                return lists;
+            }
+        },
+        methods: {
+            select(icon) {
+                this.$emit('input', icon);
+                this.show = false
+            },
+            open() {
+                this.show = true
+            }
+        }
+    }
+</script>
+
+<style scoped>
+    .icon-lists{
+        margin-top: 10px;
+    }
+    .icon-lists a {
+        text-align: center;
+        display: block;
+        color: #5c6b77;
+        padding: 10px;
+    }
+</style>

+ 60 - 0
src/views/system/components/userSetting.vue

@@ -0,0 +1,60 @@
+<template>
+  <Modal v-model="modelShow" title="用户设置" :width="500">
+    <Form :label-width="80">
+      <FormItem label="头像">
+        <img-load></img-load>
+      </FormItem>
+      <FormItem label="用户名">
+        <Input v-model="current.username" type="text"></Input>
+      </FormItem>
+      <FormItem label="密码">
+        <Input v-model="current.password" type="password"></Input>
+      </FormItem>
+      <FormItem label="重复密码">
+        <Input v-model="current.repeatPassword" type="password"></Input>
+      </FormItem>
+    </Form>
+    <div slot="footer">
+      <Button type="primary" size="large" @click="save">提交</Button>
+    </div>
+  </Modal>
+</template>
+
+<script>
+import imgLoad from '../../../components/uploadImg';
+export default {
+    name: 'userSetting',
+    components: {
+      imgLoad
+    },
+    data () {
+        return {
+            modelShow: false,
+            current: {
+                username: '',
+                password: '',
+                repeatPassword: '',
+            },
+        }
+    },
+    methods: {
+        save () {
+            this.$request('/current/user/setting').data(this.current).showSuccessTip().success(() => {
+                this.modelShow = false
+                this.$request("/load").success((r) => {
+                    this.$store.dispatch('initialize', r.data)
+                }).get()
+            }).post()
+        },
+        show () {
+            this.current = {
+                username: '',
+                password: '',
+                repeatPassword: '',
+            }
+            this.current.username = this.$store.getters.getAdminUser.username
+            this.modelShow = true
+        }
+    }
+}
+</script>

+ 4 - 0
src/views/system/listsConst.js

@@ -0,0 +1,4 @@
+export const userStatus = [
+    {v: 1, n: '启用'},
+    {v: 2, n: '禁用'}
+];

+ 273 - 0
src/views/system/menu.vue

@@ -0,0 +1,273 @@
+<template>
+    <div>
+        <Row :gutter="16">
+            <Col span="12">
+                <Card>
+                    <p slot="title">菜单层级</p>
+                    <Button size="small" type="primary" slot="extra" @click="add(0)">
+                        <Icon type="md-add"/>
+                        添加一级菜单
+                    </Button>
+                    <Tree :data="menu" :render="renderContent"></Tree>
+                </Card>
+            </Col>
+            <Col span="12">
+                <Card v-if="ieEditIng">
+                    <p slot="title">{{current.id ? '编辑' : '添加'}}</p>
+                    <div slot="extra">
+                        <Button size="small" type="error" style="margin-right: 10px" @click="cancelSetCurrent()">放弃
+                        </Button>
+                        <Button size="small" type="primary" @click="saveSetCurrent()">保存</Button>
+                    </div>
+                    <Form :label-width="80">
+                        <FormItem label="名称">
+                            <Input v-model="current.name" placeholder="菜单名称"></Input>
+                        </FormItem>
+                        <FormItem label="父菜单ID">
+                            <Input v-model="current.parent_id" placeholder="父菜单ID 0为一级菜单"></Input>
+                        </FormItem>
+                        <FormItem label="链接">
+                            <Input v-model="current.url" placeholder="页面型菜单链接不能为空,目录型菜单链接要置空"></Input>
+                        </FormItem>
+                        <FormItem label="图标">
+                            <Icon v-if="current.icon" :type="current.icon" size="24" style="margin-right: 10px">
+                                {{current.icon}}
+                            </Icon>
+                            <span v-if="current.icon" style="margin-right: 10px">{{current.icon}}</span>
+                            <Button @click="iconSelect()">选择</Button>
+                        </FormItem>
+                        <FormItem label="描述">
+                            <Input v-model="current.description" placeholder=""></Input>
+                        </FormItem>
+                    </Form>
+                </Card>
+            </Col>
+        </Row>
+        <icon-select v-model="current.icon" ref="iconSelect"></icon-select>
+    </div>
+</template>
+<script>
+    import iconSelect from './components/iconSelect'
+    import _ from "lodash";
+    import { menuSort } from '../../helper'
+
+    export default {
+        data() {
+            return {
+                lists: [],
+                current: {},
+                ieEditIng: false
+            }
+        },
+        components: {
+            'icon-select': iconSelect
+        },
+        computed: {
+            menu() {
+                return this.tree(this.lists, 0, 1);
+            }
+        },
+        created() {
+            this.load();
+        },
+        methods: {
+            renderContent(h, {data, node, root}) {
+                // 获取同级节点
+                let brother = [];
+                // 一级节点
+                if (data.level === 1) {
+                    root.forEach(function (value) {
+                        if (value.node.level === data.level) {
+                            brother.push(value.node.menu)
+                        }
+                    });
+                } else {
+                    root[node.parent].node.children.forEach(function (value) {
+                        brother.push(value.menu)
+                    })
+                }
+                let currentIndex = 0;
+                brother.forEach(function (menu, k) {
+                    if (menu.id === data.menu.id) {
+                        currentIndex = k;
+                    }
+                });
+                return h('span', {
+                    style: {
+                        display: 'inline-block',
+                        width: '100%',
+                    }
+                }, [
+                    h('span', [
+                        h('Icon', {
+                            props: {
+                                type: data.menu['icon'] ? data.menu['icon'] : 'ios-paper-outline'
+                            },
+                            style: {
+                                marginRight: '8px'
+                            }
+                        }),
+                        h('span', '[' + data.menu.id + ']' + data.title)
+                    ]),
+                    h('span', {
+                        style: {
+                            display: 'inline-block',
+                            float: 'right',
+                            marginRight: '32px'
+                        }
+                    }, [
+                        h('Button', {
+                            props: {
+                                icon: 'md-arrow-round-down',
+                                size: 'small',
+                                disabled: brother.length === currentIndex + 1
+                            },
+                            style: {
+                                marginRight: '8px'
+                            },
+                            on: {
+                                click: () => {
+                                    this.down(brother, currentIndex)
+                                }
+                            }
+                        }),
+                        h('Button', {
+                            props: {
+                                icon: 'md-arrow-round-up',
+                                size: 'small',
+                                disabled: currentIndex === 0
+                            },
+                            style: {
+                                marginRight: '8px'
+                            },
+                            on: {
+                                click: () => {
+                                    this.up(brother, currentIndex)
+                                }
+                            }
+                        }),
+                        h('Button', {
+                            props: {icon: 'md-create', type: 'success', size: 'small'},
+                            style: {
+                                marginRight: '8px'
+                            },
+                            on: {
+                                click: () => {
+                                    this.edit(data)
+                                }
+                            }
+                        }),
+                        h('Button', {
+                            props: {icon: 'md-add', type: 'primary', size: 'small', disabled: data.level > 2},
+                            style: {
+                                marginRight: '8px'
+                            },
+                            on: {
+                                click: () => {
+                                    this.add(data.menu.id)
+                                }
+                            }
+                        }),
+                        h('Button', {
+                            props: {
+                                icon: 'md-remove',
+                                type: 'warning',
+                                size: 'small',
+                                disabled: node.children.length > 0
+                            },
+                            on: {
+                                click: () => {
+                                    this.remove(data)
+                                }
+                            }
+                        })
+                    ])
+                ]);
+            },
+            load() {
+                this.$request('/system/menu/lists').success((r)=>{
+                    this.lists = menuSort(r.data)
+                }).get()
+            },
+            tree(menus, pid, level) {
+                let menu = [];
+                menus.forEach(item => {
+                    if (item['parent_id'] === pid) {
+                        menu.push({
+                            menu: item,
+                            title: item.name,
+                            level: level,
+                            expand: true,
+                            children: item['url'] ? [] : this.tree(menus, item['id'], level + 1),
+                        });
+                    }
+                });
+                return menu;
+            },
+            add(parent_id) {
+                this.setCurrent({
+                    parent_id: parent_id,
+                });
+            },
+            edit(data) {
+                this.setCurrent(data.menu);
+            },
+            remove(data) {
+                this.$Modal.confirm({
+                    title: "确认要移除当前菜单[" + data.title + "]?",
+                    onOk: () => {
+                        this.$request('/system/menu/remove').data({id: data.menu.id}).showSuccessTip().success(() => {
+                            this.load();
+                        }).get();
+                    }
+                });
+            },
+            setCurrent(data) {
+                if (this.ieEditIng) {
+                    return this.$Notice.error({title: "请先保存或放弃当前编辑/添加的菜单"});
+                }
+                this.current = _.cloneDeep(data);
+                this.ieEditIng = true;
+            },
+            cancelSetCurrent() {
+                this.$Modal.confirm({
+                    title: "确认要放弃当前编辑/添加的菜单?",
+                    onOk: () => {
+                        this.current = {};
+                        this.ieEditIng = false;
+                    }
+                });
+            },
+            saveSetCurrent() {
+                this.$request('/system/menu/save').data(this.current).showSuccessTip().success(() => {
+                    this.current = {};
+                    this.load();
+                    this.ieEditIng = false;
+                }).post()
+            },
+            down(brother, currentIndex) {
+                let current = brother[currentIndex];
+                let next = brother[currentIndex + 1];
+                brother.splice(currentIndex, 1, next);
+                brother.splice(currentIndex + 1, 1, current);
+                let menus = brother.map(function ({id}, sort) {
+                    return {id, sort}
+                });
+                this.$request('/system/menu/sort').data({menus}).showSuccessTip().success(() => this.load()).post()
+            },
+            up(brother, currentIndex) {
+                let current = brother[currentIndex];
+                let before = brother[currentIndex - 1];
+                brother.splice(currentIndex, 1, before);
+                brother.splice(currentIndex - 1, 1, current);
+                let menus = brother.map(function ({id}, sort) {
+                    return {id, sort}
+                });
+                this.$request('/system/menu/sort').data({menus}).showSuccessTip().success(() => this.load()).post()
+            },
+            iconSelect() {
+                this.$refs.iconSelect.open();
+            }
+        },
+    }
+</script>

+ 50 - 0
src/views/system/order/dataExport.vue

@@ -0,0 +1,50 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="list" :filter="filter" :filterType="2" requestApi="/system/order/dataExport">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词"/>
+                </FormItem>
+            </template>
+            <Table :columns="columns" :data="list.lists" stripe></Table>
+        </table-lists>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            filter: {
+                keyword: "",
+            },
+            columns: [
+                {
+                    type: 'ID',
+                    key: 'id',
+                    width: 70,
+                    align: 'center'
+                },
+                {
+                    title: '订单编号',
+                    key: 'order_code',
+                    align: 'center'
+                },
+                {
+                    title: '创建时间',
+                    key: 'create_time',
+                    align: 'center'
+                },
+                {
+                    title: '金额',
+                    key: 'order_money',
+                    align: 'center',
+                    render: (h, {row}) => {
+                        return h('div',(row.order_money%10)?row.order_money/100:(row.order_money/100+'.00'))
+                    }
+                }
+            ],
+            list: []
+        }
+    }
+}
+</script>

+ 181 - 0
src/views/system/request.vue

@@ -0,0 +1,181 @@
+<template>
+  <div>
+    <table-lists ref="tableLists" v-model="list" :filter="filter" :filterType="2" requestApi="/system/request/lists">
+      <template slot="filterContent">
+        <FormItem>
+          <Input type="text" v-model="filter.keyword" placeholder="搜索关键词" clearable/>
+        </FormItem>
+        <FormItem>
+          <Select placeholder="搜索类型" clearable v-model="filter.type" style="width:162px">
+            <Option v-for="item in type" :value="item.v" :key="item.v">{{ item.n }}</Option>
+          </Select>
+        </FormItem>
+      </template>
+      <template slot="filterRight">
+        <Button type="success" icon="md-add" @click="add()">添加</Button>
+      </template>
+      <Table ref="selection" :columns="columns" :data="list.lists" stripe>
+        <template slot-scope="{ row }" slot="type">
+          <field-map :value="row.type" :map="type"></field-map>
+        </template>
+        <template slot-scope="{ row }" slot="_action">
+          <Tooltip :content="row.call" :max-width="500">
+            {{row.action}}
+          </Tooltip>
+        </template>
+        <template slot-scope="{ row }" slot="_auth">
+          <Poptip trigger="click" word-wrap transfer>
+            <Button size="small">权限({{row.auth.length}})</Button>
+            <template slot="content">
+              <div v-if="row.auth.length < 1">暂无</div>
+              <div v-for="auth in row.auth" :key="auth.id">{{auth.id}}:{{auth.name}}</div>
+            </template>
+          </Poptip>
+        </template>
+        <template slot-scope="{ row}" slot="op">
+          <Button size="small" type="primary" @click="edit(row)" style="margin-right: 5px">编辑</Button>
+          <Button size="small" type="error" @click="remove(row)" style="margin-right: 5px">删除</Button>
+          <Button size="small" @click="copy(row)">复制</Button>
+        </template>
+      </Table>
+    </table-lists>
+    <Modal v-model="current.show" :title="current.data['id'] ? '编辑' : '添加'" :width="800">
+      <Form :label-width="80">
+        <FormItem label="名称">
+          <Input v-model="current.data.name"></Input>
+        </FormItem>
+        <FormItem label="ACTION">
+          <Input v-model="current.data.action"></Input>
+        </FormItem>
+        <FormItem label="类型">
+          <Select v-model="current.data.type">
+            <Option v-for="item in type" :value="item.v" :key="item.v">{{ item.n }}</Option>
+          </Select>
+        </FormItem>
+        <FormItem label="类型配置">
+          <Input v-model="current.data['call']" type="textarea"></Input>
+        </FormItem>
+        <FormItem label="类型说明" v-if="currentTypeDescription">
+            <div v-html="currentTypeDescription"></div>
+        </FormItem>
+      </Form>
+      <div slot="footer">
+        <Button type="primary" size="large" @click="save">提交</Button>
+      </div>
+    </Modal>
+  </div>
+</template>
+<script>
+    import _ from 'lodash'
+    import marked from 'marked'
+
+    export default {
+        computed: {
+            type () {
+                return this.requestType.map((item) => {
+                    return { v: item.type, n: item.name }
+                })
+            },
+            currentTypeDescription () {
+                let description = "";
+                this.requestType.forEach((item)=>{
+                    if (item.type === this.current.data.type){
+                        description = item.description
+                    }
+                })
+                return marked(description)
+            }
+        },
+        created () {
+            this.$request('/system/request/type').success((r) => {
+                this.requestType = r.data
+            }).get()
+        },
+        methods: {
+            add () {
+                this.current.data = { type: 'default' }
+                this.current.show = true
+            },
+            copy ({ type, name, action, call }) {
+                this.current.data = { type, name, action, call }
+                this.current.show = true
+            },
+            edit (row) {
+                this.current.data = _.cloneDeep(row)
+                this.current.show = true
+            },
+            remove (row) {
+                this.$Modal.confirm({
+                    title: '确认要删除[' + row.name + ']?',
+                    onOk: () => {
+                        this.$request('/system/request/remove').data({ id: row.id }).showSuccessTip().success(() => {
+                            this.reload()
+                        }).get()
+                    }
+                })
+            },
+            save () {
+                this.$request('/system/request/save').data(this.current.data).showSuccessTip().success(() => {
+                    this.reload()
+                    this.current.show = false
+                }).post()
+            },
+            reload () {
+                this.$refs.tableLists.reload(true)
+            }
+        },
+        data () {
+            return {
+                requestType: [],
+                filter: {
+                    keyword: '',
+                    type: ''
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'name',
+                        width: 260
+                    },
+                    {
+                        title: '类型',
+                        slot: 'type',
+                        width: 150
+                    },
+                    {
+                        title: 'action',
+                        slot: '_action',
+                    },
+                    {
+                        title: '创建时间',
+                        key: 'create_time',
+                        align: 'center',
+                        width: 200
+                    },
+                    {
+                        title: '权限',
+                        slot: '_auth',
+                        width: 80
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                        width: 200
+                    },
+                ],
+                list: [],
+                current: {
+                    show: false,
+                    data: {},
+                    tips: ''
+                }
+            }
+        },
+    }
+</script>

+ 153 - 0
src/views/system/user.vue

@@ -0,0 +1,153 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="list" :filter="filter" :filterType="2" requestApi="/system/user/lists">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词" clearable/>
+                </FormItem>
+            </template>
+            <template slot="filterRight">
+                <Button type="success" icon="md-add" @click="add()">添加</Button>
+            </template>
+            <Table :columns="columns" :data="list.lists" stripe>
+                <template slot-scope="{ row }" slot="_status">
+                    <field-map :value="row.status" :map="map.status"></field-map>
+                </template>
+                <template slot-scope="{ row }" slot="_group">
+                    <Poptip trigger="click" word-wrap transfer>
+                        <Button size="small">用户组({{row.userGroup.length}})</Button>
+                        <template slot="content">
+                            <div v-if="row.userGroup.length < 1">暂无</div>
+                            <div v-for="group in row.userGroup" :key="group.id">{{group.id}}:{{group.name}}</div>
+                        </template>
+                    </Poptip>
+                </template>
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="primary" @click="edit(row)" style="margin-right: 5px" v-if="list.updateAuth">编辑</Button>
+                    <!-- <Button v-if="row.id" size="small" type="error" @click="remove(row)">删除</Button> -->
+                    <Button size="small" type="error" @click="remove(row)" v-if="list.delAuth">删除</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Modal v-model="current.show" :title="current.data['id'] ? '编辑' : '添加'" :width="500">
+            <Form :label-width="80">
+                <FormItem label="用户名">
+                    <Input v-model="current.data.username" type="text"></Input>
+                </FormItem>
+                <FormItem label="密码">
+                    <Input v-model="current.data.password" type="text"></Input>
+                </FormItem>
+                <FormItem label="状态">
+                    <Select v-model="current.data.status">
+                        <Option v-for="item in map.status" :value="item.v" :key="item.v">{{ item.n }}</Option>
+                    </Select>
+                </FormItem>
+                <FormItem label="描述">
+                    <Input v-model="current.data.description" type="textarea"></Input>
+                </FormItem>
+            </Form>
+            <div slot="footer">
+                <Button type="primary" size="large" @click="save">提交</Button>
+            </div>
+        </Modal>
+    </div>
+</template>
+<script>
+    import {userStatus} from './listsConst.js'
+    import _ from "lodash";
+    export default {
+        methods: {
+            add() {
+                this.current.data = {status: 1};
+                this.current.show = true;
+            },
+            edit(row) {
+                this.current.data = _.cloneDeep(row);
+                this.current.data.password = "";
+                this.current.show = true;
+            },
+            remove(row) {
+                this.$Modal.confirm({
+                    title: "确认要删除当前[" + row.username + "]?",
+                    onOk: () => {
+                        this.$request("/system/user/remove").data({id: row.id}).showSuccessTip().success(() => {
+                            this.reload();
+                        }).get()
+                    }
+                });
+            },
+            save() {
+                console.log(this.current.data)
+                this.$request("/system/user/save").data(this.current.data).showSuccessTip().success(() => {
+                    this.current.show = false;
+                    this.reload();
+                }).post();
+            },
+            reload(){
+                this.$refs.tableLists.reload(true);
+            }
+        },
+        data() {
+            return {
+                map: {
+                    status: userStatus
+                },
+                filter: {
+                    keyword: ""
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '用户名',
+                        key: 'username',
+                        width: 150
+                    },
+                    {
+                        title: '最后登录IP',
+                        key: 'last_login_ip',
+                        align: 'center',
+                        width: 150
+                    },
+                    {
+                        title: '最后登录时间',
+                        key: 'last_login_time',
+                        align: 'center',
+                        width: 150
+                    },
+                    {
+                        title: '创建时间',
+                        key: 'create_time',
+                        align: 'center',
+                        width: 150
+                    },
+                    {
+                        title: '状态',
+                        slot: '_status',
+                        width: 100
+                    },
+                    {
+                        title: '用户组',
+                        slot: '_group',
+                        align: 'center',
+                        width: 120
+                    },
+                    {
+                        title: '操作',
+                        key: 'updateAuth',
+                        slot: 'op',
+                    },
+                ],
+                list: [],
+                current: {
+                    show: false,
+                    data: {},
+                },
+            }
+        },
+    }
+</script>

+ 152 - 0
src/views/system/userGroup.vue

@@ -0,0 +1,152 @@
+<template>
+    <div>
+        <table-lists ref="tableLists" v-model="list" :filter="filter" :filterType="2" requestApi="/system/userGroup/lists">
+            <template slot="filterContent">
+                <FormItem>
+                    <Input type="text" v-model="filter.keyword" placeholder="搜索关键词" clearable/>
+                </FormItem>
+            </template>
+            <template slot="filterRight">
+                <Button type="success" icon="md-add" @click="add()">添加</Button>
+            </template>
+            <Table :columns="columns" :data="list.lists" stripe>
+                <template slot-scope="{ row }" slot="_user">
+                    <Button size="small" @click="showAssign(row,'user')">
+                        用户({{row.user.length}})
+                    </Button>
+                </template>
+                <template slot-scope="{ row }" slot="_auth">
+                    <Poptip trigger="click" word-wrap transfer>
+                        <Button size="small">权限({{row.auth.length}})</Button>
+                        <template slot="content">
+                            <div v-if="row.auth.length < 1">暂无</div>
+                            <div v-for="auth in row.auth" :key="auth.id">{{auth.id}}:{{auth.name}}</div>
+                        </template>
+                    </Poptip>
+                </template>
+                <template slot-scope="{ row }" slot="op">
+                    <Button size="small" type="primary" @click="edit(row)" style="margin-right: 5px" v-if="list.updateAuth">编辑</Button>
+                    <!-- <Button v-if="row.id" size="small" type="error" @click="remove(row)">删除</Button> -->
+                    <Button v-if="list.delAuth" size="small" type="error" @click="remove(row)">删除</Button>
+                </template>
+            </Table>
+        </table-lists>
+        <Modal v-model="current.show" :title="current.data['id'] ? '编辑' : '添加'" :width="500">
+            <Form :label-width="80">
+                <FormItem label="名称">
+                    <Input v-model="current.data.name" type="text"></Input>
+                </FormItem>
+                <FormItem label="描述">
+                    <Input v-model="current.data.description" type="textarea"></Input>
+                </FormItem>
+            </Form>
+            <div slot="footer">
+                <Button type="primary" size="large" @click="save">提交</Button>
+            </div>
+        </Modal>
+        <Drawer :title="assign.data.name+' 用户关联'" v-model="assign.show.user" width="900" :mask-closable="false">
+            <AssignUser v-if="assign.show.user" :id="assign.data.id" @reload="reload"></AssignUser>
+        </Drawer>
+    </div>
+</template>
+<script>
+    import _ from "lodash";
+    import AssignUser from './components/AssignUser'
+    export default {
+        components: {
+            AssignUser
+        },
+        methods: {
+            add() {
+                this.current.data = {};
+                this.current.show = true;
+            },
+            edit(row) {
+                this.current.data = _.cloneDeep(row);
+                this.current.show = true;
+            },
+            remove(row) {
+                this.$Modal.confirm({
+                    title: "确认要删除当前[ " + row.name + " ]?",
+                    onOk: () => {
+                        this.$request("/system/userGroup/remove").data({id: row.id}).showSuccessTip().success(() => {
+                            this.reload();
+                        }).get()
+                    }
+                });
+            },
+            save() {
+                this.$request("/system/userGroup/save").data(this.current.data).showSuccessTip().success(() => {
+                    this.current.show = false;
+                    this.reload();
+                }).post();
+            },
+            reload(){
+                this.$refs.tableLists.reload(true);
+            },
+            showAssign(row,type){
+                this.assign.data = _.cloneDeep(row);
+                this.assign.show[type] = true;
+            },
+        },
+        data() {
+            return {
+                assign:{
+                    data:{},
+                    show:{
+                        user:false
+                    },
+                },
+                filter: {
+                    keyword: ""
+                },
+                columns: [
+                    {
+                        type: 'ID',
+                        key: 'id',
+                        width: 70,
+                        align: 'center'
+                    },
+                    {
+                        title: '名称',
+                        key: 'name',
+                        width: 150
+                    },
+                    {
+                        title: '更新时间',
+                        key: 'create_time',
+                        align: 'center',
+                        width: 150
+                    },
+                    {
+                        title: '创建时间',
+                        key: 'update_time',
+                        align: 'center',
+                        width: 150
+                    },
+                    {
+                        title: '权限',
+                        slot: '_auth',
+                        align: 'center',
+                        width: 120
+                    },
+                    {
+                        title: '用户',
+                        slot: '_user',
+                        align: 'center',
+                        width: 120
+                    },
+                    {
+                        title: '操作',
+                        slot: 'op',
+                    },
+                ],
+                list: [],
+                current: {
+                    show: false,
+                    data: {},
+                },
+            }
+        },
+    }
+</script>

+ 20 - 0
vue.config.js

@@ -0,0 +1,20 @@
+module.exports = {
+  publicPath: './',
+  // outputDir: "wxlist",
+//   productionSourceMap: false,
+//   devServer: {
+//     port: '9090',
+//     open: true,
+//     disableHostCheck: true,
+//     proxy: {
+//       '/api': {
+//         target: 'https://cadmin-service-demo.baiy.org',
+//         changeOrigin: true,
+//         ws: true,
+//         pathRewrite: {
+//           '^/api': ''
+//         }
+//       }
+//     }
+//   }
+};

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.