Parcourir la source

Wesmiler 校企小程序源代码部署

wesmiler il y a 3 ans
Parent
commit
471397b705

+ 32 - 0
addons/admin/src/api/client/h5/setting.js

@@ -0,0 +1,32 @@
+import { axios } from '@/utils/request'
+
+/**
+ * api接口列表
+ */
+const api = {
+  detail: '/client.h5.setting/detail',
+  update: '/client.h5.setting/update'
+}
+
+/**
+ * 获取设置
+ */
+export function detail (key) {
+  return axios({
+    url: api.detail,
+    method: 'get',
+    params: { key }
+  })
+}
+
+/**
+ * 更新设置
+ * @param {*} data
+ */
+export function update (key, data) {
+  return axios({
+    url: api.update,
+    method: 'post',
+    data: { key, ...data }
+  })
+}

+ 0 - 62
addons/admin/src/api/store.js

@@ -1,62 +0,0 @@
-import api from './api.config'
-import { axios } from '@/utils/request'
-
-/**
- * 获取列表
- * @param {*} params
- */
-export function list (params) {
-  return axios({
-    url: api.store.list,
-    method: 'get',
-    params
-  })
-}
-
-/**
- * 回收站列表
- * @param {*} params
- */
-export function recycle (params) {
-  return axios({
-    url: api.store.recycle,
-    method: 'get',
-    params
-  })
-}
-
-/**
- * 新增记录
- * @param {*} data
- */
-export function add (data) {
-  return axios({
-    url: api.store.add,
-    method: 'post',
-    data: data
-  })
-}
-
-/**
- * 移入回收站
- * @param {*} data
- */
-export function recovery (data) {
-  return axios({
-    url: api.store.recovery,
-    method: 'post',
-    data: data
-  })
-}
-
-/**
- * 移出回收站
- * @param {*} data
- */
-export function move (data) {
-  return axios({
-    url: api.store.move,
-    method: 'post',
-    data: data
-  })
-}

+ 0 - 50
addons/admin/src/api/store/api.js

@@ -1,50 +0,0 @@
-import api from '../api.config'
-import { axios } from '@/utils/request'
-
-/**
- * 获取列表
- * @param {*} params
- */
-export function list (params) {
-  return axios({
-    url: api.store.api.list,
-    method: 'get',
-    params
-  })
-}
-
-/**
- * 新增记录
- * @param {*} data
- */
-export function add (data) {
-  return axios({
-    url: api.store.api.add,
-    method: 'post',
-    data: data
-  })
-}
-
-/**
- * 编辑记录
- * @param {*} data
- */
-export function edit (data) {
-  return axios({
-    url: api.store.api.edit,
-    method: 'post',
-    data: data
-  })
-}
-
-/**
- * 删除记录
- * @param {*} data
- */
-export function deleted (data) {
-  return axios({
-    url: api.store.api.delete,
-    method: 'post',
-    data: data
-  })
-}

+ 82 - 0
addons/admin/src/api/store/shop/index.js

@@ -0,0 +1,82 @@
+import { axios } from '@/utils/request'
+
+/**
+ * api接口列表
+ */
+const api = {
+  list: '/shop/list',
+  all: '/shop/all',
+  detail: '/shop/detail',
+  add: '/shop/add',
+  edit: '/shop/edit',
+  delete: '/shop/delete'
+}
+
+/**
+ * 列表记录
+ */
+export function list (params) {
+  return axios({
+    url: api.list,
+    method: 'get',
+    params
+  })
+}
+
+/**
+ * 全部记录
+ */
+export function all (params) {
+  return axios({
+    url: api.all,
+    method: 'get',
+    params
+  })
+}
+
+/**
+ * 详情记录
+ */
+export function detail (params) {
+  return axios({
+    url: api.detail,
+    method: 'get',
+    params
+  })
+}
+
+/**
+ * 新增记录
+ * @param {*} data
+ */
+export function add (data) {
+  return axios({
+    url: api.add,
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 编辑记录
+ * @param {*} data
+ */
+export function edit (data) {
+  return axios({
+    url: api.edit,
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 删除记录
+ * @param {*} data
+ */
+export function deleted (data) {
+  return axios({
+    url: api.delete,
+    method: 'post',
+    data: data
+  })
+}

+ 571 - 0
addons/admin/src/components/Modal/FilesModal/FilesModal.vue

@@ -0,0 +1,571 @@
+<template>
+  <a-modal
+    :title="title"
+    :width="840"
+    :visible="visible"
+    :isLoading="isLoading"
+    :maskClosable="false"
+    :destroyOnClose="false"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <a-spin :spinning="isLoading">
+      <div class="library-box clearfix">
+        <!-- 分组列表 -->
+        <div class="file-group">
+          <div class="group-tree">
+            <a-directory-tree
+              v-if="groupListTreeSelect.length"
+              :treeData="groupListTreeSelect"
+              :blockNode="true"
+              :showIcon="false"
+              @select="onSelectGroup"
+            ></a-directory-tree>
+          </div>
+          <a class="group-add" href="javascript:void(0);" @click="handleAddGroup">新增分组</a>
+        </div>
+        <!-- 文件列表 -->
+        <div class="file-list">
+          <!-- 头部操作栏 -->
+          <div class="top-operate clearfix">
+            <!-- 搜索框 -->
+            <a-input-search
+              class="fl-l"
+              style="width: 200px"
+              placeholder="搜索文件名称"
+              v-model="queryParam.fileName"
+              @search="onSearch"
+            />
+            <!-- 上传按钮 -->
+            <div class="file-upload fl-r">
+              <span
+                class="upload-desc"
+              >{{ fileType === FileTypeEnum.VIDEO.value ? '视频' : '图片' }}大小不能超过{{ uploadSizeLimit }}M</span>
+              <a-upload
+                name="iFile"
+                :accept="accept"
+                :beforeUpload="beforeUpload"
+                :customRequest="onUpload"
+                :multiple="true"
+                :showUploadList="false"
+              >
+                <a-button icon="cloud-upload">上传</a-button>
+              </a-upload>
+            </div>
+          </div>
+          <div class="file-list-body">
+            <!-- 文件列表 -->
+            <ul v-if="fileList.data && fileList.data.length" class="file-list-ul clearfix">
+              <li
+                class="file-item"
+                :class="{ active: selectedIndexs.indexOf(index) > -1 }"
+                v-for="(item, index) in fileList.data"
+                :key="index"
+                @click="onSelectItem(index)"
+              >
+                <div
+                  class="img-cover"
+                  :style="{ backgroundImage: `url('${item.preview_url}')`, width: fileType === FileTypeEnum.VIDEO.value ? '55px' : '95px' }"
+                ></div>
+                <p class="file-name oneline-hide">{{ item.file_name }}</p>
+                <div class="select-mask">
+                  <a-icon class="selected-icon" type="check" />
+                </div>
+              </li>
+            </ul>
+            <!-- 无数据时显示 -->
+            <a-empty v-else-if="!isLoading" />
+            <!-- 底部操作栏 -->
+            <div class="footer-operate clearfix">
+              <div class="fl-l" v-if="selectedIndexs.length">
+                <span class="footer-desc">已选择{{ selectedIndexs.length }}项</span>
+                <a-config-provider :auto-insert-space-in-button="false">
+                  <a-button-group>
+                    <a-button class="btn-mini" size="small" @click="handleDelete()">删除</a-button>
+                    <a-button class="btn-mini" size="small" @click="handleBatchMove()">移动</a-button>
+                  </a-button-group>
+                </a-config-provider>
+              </div>
+              <!-- 分页组件 -->
+              <a-pagination
+                class="fl-r"
+                size="small"
+                v-model="fileList.current_page"
+                :total="fileList.total"
+                :defaultPageSize="15"
+                hideOnSinglePage
+                @change="handleNextPage"
+              />
+            </div>
+          </div>
+        </div>
+      </div>
+    </a-spin>
+    <!-- 新增分组 -->
+    <AddGroupForm ref="AddGroupForm" :groupList="groupList" @handleSubmit="getGroupList" />
+    <!-- 移动分组 -->
+    <MoveGroupForm ref="MoveGroupForm" :groupList="groupListTree" @handleSubmit="handleRefresh" />
+  </a-modal>
+</template>
+
+<script>
+import PropTypes from 'ant-design-vue/es/_util/vue-types'
+import store from '@/store'
+import { debounce } from '@/utils/util'
+import * as FileApi from '@/api/files'
+import * as GroupApi from '@/api/files/group'
+import * as UploadApi from '@/api/upload'
+import FileTypeEnum from '@/common/enum/file/FileType'
+import ChannelEnum from '@/common/enum/file/Channel'
+import AddGroupForm from './AddGroupForm'
+import MoveGroupForm from './MoveGroupForm'
+
+export default {
+  name: 'FilesModal',
+  components: {
+    AddGroupForm,
+    MoveGroupForm
+  },
+  props: {
+    // 多选模式, 如果false为单选
+    multiple: PropTypes.bool.def(false),
+    // 最大选择的数量限制, multiple模式下有效
+    maxNum: PropTypes.integer.def(100),
+    // 已选择的数量
+    selectedNum: PropTypes.integer.def(0),
+    // 文件类型 (10图片 30视频)
+    fileType: PropTypes.integer.def(FileTypeEnum.IMAGE.value),
+  },
+  data () {
+    return {
+      // 对话框标题
+      title: '图片库',
+      // modal(对话框)是否可见
+      visible: false,
+      // 枚举类
+      FileTypeEnum,
+      // 后端上传api
+      uploadUrl: UploadApi.image,
+      // 上传文件大小限制
+      uploadSizeLimit: 2,
+      // 文件上传的格式限制
+      accept: '',
+      // 查询参数
+      queryParam: {
+        // 文件类型: 图片
+        fileType: FileTypeEnum.IMAGE.value,
+        // 上传来源: 商户后台
+        channel: ChannelEnum.STORE.value,
+        // 当前页码
+        page: 1,
+        // 文件名称
+        fileName: '',
+        // 文件分组
+        groupId: 0,
+      },
+      // modal(对话框)确定按钮 loading
+      isLoading: true,
+      // 分组列表
+      groupList: [],
+      // 文件列表
+      fileList: [],
+      // 文件分组列表(树状结构)
+      groupListTree: [],
+      // 文件分组列表(用于筛选组件)
+      groupListTreeSelect: [],
+      // 选中的文件
+      selectedIndexs: [],
+      // 上传中的文件
+      uploading: []
+    }
+  },
+  created () {
+  },
+  methods: {
+
+    // 显示对话框
+    show () {
+      // 显示窗口
+      this.visible = true
+      // 初始化文件类型
+      this.initFileType()
+      // 获取分组列表
+      this.getGroupList()
+      // 获取文件列表
+      this.getFileList()
+    },
+
+    // 初始化文件类型
+    initFileType () {
+      const publicConfig = store.getters.publicConfig
+      if (this.fileType === FileTypeEnum.IMAGE.value) {
+        this.title = '图片库'
+        this.accept = 'image/jpeg,image/png,image/gif,image/webp'
+        this.uploadUrl = UploadApi.image
+        this.uploadSizeLimit = publicConfig.uploadImageSize || 2
+      }
+      if (this.fileType === FileTypeEnum.VIDEO.value) {
+        this.title = '视频库'
+        this.accept = '.mp4'
+        this.uploadUrl = UploadApi.video
+        this.uploadSizeLimit = publicConfig.uploadVideoSize || 10
+      }
+      this.queryParam.fileType = this.fileType
+    },
+
+    // 获取文件分组列表
+    getGroupList () {
+      // this.isLoading = true
+      GroupApi.list({}).then(result => {
+        // 记录列表数据
+        const groupList = result.data.list
+        this.groupList = groupList
+        // 格式化分组列表
+        const groupListTree = this.formatTreeData(groupList)
+        // 记录 groupListTree
+        this.groupListTree = groupListTree
+        // 记录 groupListTreeSelect
+        this.groupListTreeSelect = [{
+          title: '全部',
+          key: -1,
+          value: -1
+        }, {
+          title: '未分组',
+          key: 0,
+          value: 0
+        }].concat(groupListTree)
+        // this.isLoading = false
+      })
+    },
+
+    // 获取文件列表
+    getFileList () {
+      this.isLoading = true
+      FileApi.list(this.queryParam)
+        .then(result => {
+          this.fileList = result.data.list
+          this.isLoading = false
+        })
+    },
+
+    /**
+     * 格式化分组列表
+     */
+    formatTreeData (list, disabled = false) {
+      const data = []
+      list.forEach(item => {
+        // 新的元素
+        const netItem = {
+          title: item.name,
+          key: item.group_id,
+          value: item.group_id
+        }
+        // 递归整理子集
+        if (item.children && item.children.length) {
+          netItem['children'] = this.formatTreeData(item['children'], netItem.disabled)
+        } else {
+          netItem['isLeaf'] = true
+          netItem['scopedSlots'] = { icon: 'meh' }
+        }
+        data.push(netItem)
+      })
+      return data
+    },
+
+    // 记录选中的分组
+    onSelectGroup (selectedKeys) {
+      this.queryParam.groupId = selectedKeys[0]
+      this.handleRefresh(true)
+    },
+
+    // 记录选中的文件
+    onSelectItem (index) {
+      const { multiple, maxNum, selectedIndexs } = this
+      // 记录选中状态
+      if (!multiple) {
+        this.selectedIndexs = [index]
+        return
+      }
+      const key = selectedIndexs.indexOf(index)
+      const selected = key > -1
+      // 验证数量限制
+      if (!selected && (selectedIndexs.length + this.selectedNum) >= maxNum) {
+        this.$message.warning(`最多可选${maxNum}个文件`, 1)
+        return
+      }
+      !selected ? this.selectedIndexs.push(index) : this.selectedIndexs.splice(key, 1)
+    },
+
+    // 新增分组
+    handleAddGroup () {
+      this.$refs.AddGroupForm.add()
+    },
+
+    // 搜索文件名称
+    onSearch () {
+      this.handleRefresh(true)
+    },
+
+    // 事件: 上传文件之前
+    beforeUpload (file, fileList) {
+      // 显示错误提示(防抖处理)
+      const showErrorMsg = debounce(this.$message.error, 20)
+      // 验证文件大小
+      const fileSizeMb = file.size / 1024 / 1024
+      if (fileSizeMb > this.uploadSizeLimit) {
+        showErrorMsg(`上传的文件大小不能超出${this.uploadSizeLimit}MB`)
+        return false
+      }
+      // 验证文件上传数量
+      if (fileList.length > 10) {
+        showErrorMsg('一次上传的文件数量不能超出10个')
+        return false
+      }
+      return true
+    },
+
+    // 事件: 自定义上传
+    onUpload (info) {
+      this.isLoading = true
+      // 记录上传状态
+      this.uploading.push(true)
+      // 构建上传参数
+      const formData = new FormData()
+      formData.append('iFile', info.file)
+      formData.append('groupId', this.queryParam.groupId)
+      // 开始上传
+      this.uploadUrl(formData)
+        .finally(() => {
+          this.uploading.pop()
+          if (this.uploading.length === 0) {
+            this.isLoading = false
+            this.handleRefresh(true)
+          }
+        })
+    },
+
+    // 列表分页事件
+    handleNextPage (page, pageSize) {
+      this.queryParam.page = page
+      this.handleRefresh()
+    },
+
+    // 关闭对话框事件
+    handleCancel () {
+      this.visible = false
+      this.selectedIndexs = []
+    },
+
+    /**
+     * 刷新文件列表
+     * @param Boolean bool 强制刷新到第一页
+     */
+    handleRefresh (bool = false) {
+      bool && (this.queryParam.page = 1)
+      // 清空选中
+      this.selectedIndexs = []
+      // 获取文件列表
+      this.getFileList()
+    },
+
+    // 删除文件
+    handleDelete (item) {
+      const that = this
+      const fileIds = this.getSelectedItemIds()
+      const modal = this.$confirm({
+        title: '您确定要删除该文件吗?',
+        content: '删除后不可恢复,请谨慎操作',
+        onOk () {
+          return FileApi.deleted({ fileIds })
+            .then(result => {
+              that.$message.success(result.message, 1.5)
+              that.handleRefresh()
+            })
+            .catch(() => true)
+            .finally(result => modal.destroy())
+        }
+      })
+    },
+
+    /**
+     * 批量移动文件
+     */
+    handleBatchMove () {
+      const fileIds = this.getSelectedItemIds()
+      this.$refs.MoveGroupForm.show(fileIds)
+    },
+
+    // 获取选中的文件id集
+    getSelectedItemIds () {
+      const selectedItems = this.getSelectedItems()
+      return selectedItems.map(item => item.file_id)
+    },
+
+    // 获取选中的文件
+    getSelectedItems () {
+      const selectedItems = []
+      for (const key in this.selectedIndexs) {
+        const index = this.selectedIndexs[key]
+        selectedItems.push(this.fileList.data[index])
+      }
+      return selectedItems
+    },
+
+    // 确认按钮
+    handleSubmit (e) {
+      e.preventDefault()
+      // 获取选中的文件
+      const selectedItems = this.getSelectedItems()
+      // 通知父端组件提交完成了
+      this.$emit('handleSubmit', selectedItems)
+      // 关闭对话框
+      this.handleCancel()
+    }
+
+  }
+}
+</script>
+
+<style lang="less" scoped>
+/deep/.ant-modal-header,
+/deep/.ant-modal-footer {
+  border: none;
+}
+/deep/.ant-modal-body {
+  padding: 6px;
+}
+
+/deep/.ant-empty {
+  padding: 120px 0;
+}
+
+/* 文件库 */
+.library-box {
+  user-select: none;
+
+  // 文件分组
+  .file-group {
+    float: left;
+    border-right: 1px solid #e6e6e6;
+
+    // 分组列表
+    .group-tree {
+      width: 150px;
+      height: 440px;
+      overflow-y: auto;
+      overflow-x: auto;
+
+      /deep/.ant-tree {
+        display: inline-block;
+        min-width: 100%;
+        max-height: 380px;
+        width: auto;
+      }
+    }
+
+    // 新增分组
+    .group-add {
+      display: block;
+      margin-top: 20px;
+      font-size: @font-size-base;
+      padding: 0 30px;
+    }
+  }
+
+  // 文件列表
+  .file-list {
+    float: left;
+    width: 630px;
+    margin-left: 20px;
+
+    // 头部操作区
+    .top-operate {
+      margin-bottom: 10px;
+      .file-upload {
+        .upload-desc {
+          font-size: 12px;
+          padding-right: 10px;
+          color: #999;
+        }
+      }
+    }
+
+    // 文件列表
+    .file-list-body {
+      height: 455px;
+      .file-list-ul {
+        margin: 0;
+        padding: 0;
+        height: 417px;
+      }
+      .file-item {
+        width: 110px;
+        position: relative;
+        cursor: pointer;
+        border-radius: 2px;
+        padding: 4px;
+        border: 1px solid rgba(0, 0, 0, 0.05);
+        float: left;
+        margin: 8px;
+        -webkit-transition: All 0.2s ease-in-out;
+        -moz-transition: All 0.2s ease-in-out;
+        -o-transition: All 0.2s ease-in-out;
+        transition: All 0.2s ease-in-out;
+        &:hover {
+          border: 1px solid #16bce2;
+        }
+      }
+      .file-item {
+        // 文件名称
+        .file-name {
+          font-size: 12px;
+          text-align: center;
+        }
+        // 预览图
+        .img-cover {
+          margin: 0 auto;
+          width: 95px;
+          height: 95px;
+          background: no-repeat center center / 100%;
+        }
+        // 遮罩层(选中时)
+        &.active .select-mask {
+          display: block;
+        }
+        .select-mask {
+          display: none;
+          position: absolute;
+          top: 0;
+          bottom: 0;
+          left: 0;
+          right: 0;
+          background: rgba(0, 0, 0, 0.41);
+          text-align: center;
+          border-radius: 2px;
+          .selected-icon {
+            font-size: 26px;
+            color: #fff;
+            line-height: 122px;
+            text-align: center;
+          }
+        }
+      }
+
+      // 底部操作栏
+      .footer-operate {
+        height: 28px;
+        margin-top: 10px;
+        .footer-desc {
+          color: #999;
+          margin-right: 10px;
+        }
+        .btn-mini {
+          font-size: @font-size-base;
+          padding: 0 15px;
+          height: 28px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 0 - 15
addons/admin/src/components/tree/index.jsx

@@ -1,15 +0,0 @@
-import Tree from './Tree'
-import DirectoryTree from './DirectoryTree'
-import Base from 'ant-design-vue/es/base'
-
-Tree.TreeNode.name = 'ATreeNode'
-Tree.DirectoryTree = DirectoryTree
-/* istanbul ignore next */
-Tree.install = function (Vue) {
-  Vue.use(Base)
-  Vue.component(Tree.name, Tree)
-  Vue.component(Tree.TreeNode.name, Tree.TreeNode)
-  Vue.component(DirectoryTree.name, DirectoryTree)
-}
-
-export default Tree

+ 1 - 1
addons/admin/src/components/tree/style/index.less

@@ -92,7 +92,7 @@
               width    : 24px;
               height   : @tree-title-height;
               color    : @primary-color;
-              font-size: 14px;
+              font-size: @font-size-base;
               transform: none;
 
               svg {

+ 59 - 50
addons/admin/src/views/menu/modules/AddForm.vue

@@ -1,6 +1,6 @@
 <template>
   <a-modal
-    title="新增菜单"
+    title="编辑文件分组"
     :width="720"
     :visible="visible"
     :confirmLoading="confirmLoading"
@@ -10,22 +10,19 @@
   >
     <a-spin :spinning="confirmLoading">
       <a-form :form="form">
-        <a-form-item label="菜单名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
+        <a-form-item label="分组名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
           <a-input
             v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
           />
         </a-form-item>
-        <a-form-item label="上级菜单" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="默认为顶级菜单">
+        <a-form-item label="上级分组" :labelCol="labelCol" :wrapperCol="wrapperCol">
           <a-tree-select
-            :tree-data="menuListTreeData"
-            :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
-            allow-clear
+            :treeData="groupListTree"
+            :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+            allowClear
             v-decorator="['parent_id']"
           ></a-tree-select>
         </a-form-item>
-        <a-form-item label="菜单path" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="指向的页面path">
-          <a-input v-decorator="['path', {rules: [{required: true, message: '请输入菜单path'}]}]" />
-        </a-form-item>
         <a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
           <a-input-number
             :min="0"
@@ -38,50 +35,54 @@
 </template>
 
 <script>
-import * as Api from '@/api/store/menu'
+import * as Api from '@/api/files/group'
+import _ from 'lodash'
 
 export default {
   props: {
-    menuList: {
+    // 分组列表
+    groupList: {
       type: Array,
       required: true
     }
   },
   data () {
     return {
+      // 对话框标题
+      title: '',
       // 标签布局属性
       labelCol: {
-        xs: { span: 24 },
-        sm: { span: 7 }
+        span: 7
       },
       // 输入框布局属性
       wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 13 }
+        span: 13
       },
       // modal(对话框)是否可见
       visible: false,
       // modal(对话框)确定按钮 loading
       confirmLoading: false,
-
-      // 当前表单
+      // 当前表单元素
       form: this.$form.createForm(this),
 
-      // 菜单列表 树状结构
-      menuListTreeData: []
+      // 当前记录
+      record: {},
+      // 上级分组列表
+      groupListTree: []
     }
   },
-
   methods: {
 
     /**
      * 显示对话框
      */
-    show () {
+    edit (record) {
       // 显示窗口
       this.visible = true
-      // 获取菜单列表
-      this.getMenuList()
+      // 当前分组记录
+      this.record = record
+      // 获取分组列表
+      this.getGroupList()
       // 设置默认值
       this.setFieldsValue()
     },
@@ -90,41 +91,50 @@ export default {
      * 设置默认值
      */
     setFieldsValue () {
-      this.$nextTick(() => {
-        this.form.resetFields()
-        this.form.setFieldsValue({ parent_id: 0 })
+      const { $nextTick, form: { setFieldsValue } } = this
+      $nextTick(() => {
+        setFieldsValue(_.pick(this.record, ['name', 'parent_id', 'sort']))
       })
     },
 
     /**
-     * 获取菜单列表
+     * 获取分组列表
      */
-    getMenuList () {
-      const { menuList } = this
-      // 格式化菜单列表
-      const selectList = this.formatTreeData(menuList)
-      // 顶级菜单
+    getGroupList () {
+      const { groupList } = this
+      // 格式化分组列表
+      const selectList = this.formatTreeData(groupList)
+      // 顶级分组
       selectList.unshift({
-        title: '顶级菜单',
+        title: '顶级分组',
         key: 0,
         value: 0
       })
-      this.menuListTreeData = selectList
+      this.groupListTree = selectList
     },
 
     /**
-     * 格式化菜单列表
+     * 格式化分组列表
      */
-    formatTreeData (list) {
+    formatTreeData (list, disabled = false) {
       const data = []
       list.forEach(item => {
+        // 新的元素
         const netItem = {
           title: item.name,
-          key: item.menu_id,
-          value: item.menu_id
+          key: item.group_id,
+          value: item.group_id
+        }
+        // 禁用的分组
+        if (
+          [item.group_id, item.parent_id].includes(this.record.group_id) ||
+          disabled === true
+        ) {
+          netItem.disabled = true
         }
-        if (item.hasOwnProperty('children')) {
-          netItem['children'] = this.formatTreeData(item['children'])
+        // 递归整理子集
+        if (item.children && item.children.length) {
+          netItem['children'] = this.formatTreeData(item['children'], netItem.disabled)
         }
         data.push(netItem)
       })
@@ -134,24 +144,23 @@ export default {
     /**
      * 确认按钮
      */
-    handleSubmit () {
+    handleSubmit (e) {
+      e.preventDefault()
+      // 表单验证
       const { form: { validateFields } } = this
-      // 表单的验证
       validateFields((errors, values) => {
+        // 提交到后端api
         if (!errors) {
-          // 提交到后端api
           this.onFormSubmit(values)
         }
       })
     },
 
     /**
-     * 取消按钮
+     * 关闭对话框事件
      */
     handleCancel () {
-      // 关闭窗口
       this.visible = false
-      // 重置表单内容
       this.form.resetFields()
     },
 
@@ -160,11 +169,11 @@ export default {
     */
     onFormSubmit (values) {
       this.confirmLoading = true
-      Api.add({ form: { module: 10, ...values } })
+      Api.edit({ groupId: this.record['group_id'], form: values })
         .then((result) => {
-          // 显示成功
-          this.$message.success(result.message)
-          // 关闭对话框
+           // 显示成功
+          this.$message.success(result.message, 1.5)
+          // 关闭对话框事件
           this.handleCancel()
           // 通知父端组件提交完成了
           this.$emit('handleSubmit', values)

+ 144 - 0
addons/admin/src/views/goods/service/modules/EditForm.vue

@@ -0,0 +1,144 @@
+<template>
+  <a-modal
+    :title="title"
+    :width="720"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    :maskClosable="false"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <a-spin :spinning="confirmLoading">
+      <a-form :form="form">
+        <a-form-item label="服务名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
+          <a-input
+            placeholder="请输入服务与承诺的名称"
+            v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
+          />
+        </a-form-item>
+        <a-form-item label="概述" :labelCol="labelCol" :wrapperCol="wrapperCol">
+          <a-textarea
+            placeholder="请输入概述内容 (300个字符以内)"
+            :autoSize="{ minRows: 4 }"
+            v-decorator="['summary']"
+          />
+        </a-form-item>
+        <a-form-item label="是否默认" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="新增商品时是否默认勾选">
+          <a-radio-group v-decorator="['is_default', {initialValue: 1, rules: [{required: true}]}]">
+            <a-radio :value="1">是</a-radio>
+            <a-radio :value="0">否</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
+          <a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
+            <a-radio :value="1">显示</a-radio>
+            <a-radio :value="0">隐藏</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
+          <a-input-number
+            :min="0"
+            v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
+          />
+        </a-form-item>
+      </a-form>
+    </a-spin>
+  </a-modal>
+</template>
+
+<script>
+import pick from 'lodash.pick'
+import * as Api from '@/api/goods/service'
+
+export default {
+  data () {
+    return {
+      // 对话框标题
+      title: '',
+      // 标签布局属性
+      labelCol: {
+        span: 7
+      },
+      // 输入框布局属性
+      wrapperCol: {
+        span: 13
+      },
+      // modal(对话框)是否可见
+      visible: false,
+      // modal(对话框)确定按钮 loading
+      confirmLoading: false,
+      // 当前表单元素
+      form: this.$form.createForm(this),
+
+      // 当前记录
+      record: {}
+    }
+  },
+  methods: {
+
+    /**
+     * 显示对话框
+     */
+    edit (record) {
+      // 显示窗口
+      this.title = '编辑记录'
+      this.visible = true
+      // 当前管理员记录
+      this.record = record
+      // 设置默认值
+      this.setFieldsValue()
+    },
+
+    /**
+     * 设置默认值
+     */
+    setFieldsValue () {
+      const { form: { setFieldsValue } } = this
+      this.$nextTick(() => {
+        setFieldsValue(pick(this.record, ['name', 'summary', 'is_default', 'status', 'sort']))
+      })
+    },
+
+    /**
+     * 确认按钮
+     */
+    handleSubmit (e) {
+      e.preventDefault()
+      // 表单验证
+      const { form: { validateFields } } = this
+      validateFields((errors, values) => {
+        // 提交到后端api
+        !errors && this.onFormSubmit(values)
+      })
+    },
+
+    /**
+     * 关闭对话框事件
+     */
+    handleCancel () {
+      this.visible = false
+      this.form.resetFields()
+    },
+
+    /**
+    * 提交到后端api
+    */
+    onFormSubmit (values) {
+      this.confirmLoading = true
+      Api.edit({ serviceId: this.record['service_id'], form: values })
+        .then((result) => {
+          // 显示成功
+          this.$message.success(result.message, 1.5)
+          // 关闭对话框事件
+          this.handleCancel()
+          // 通知父端组件提交完成了
+          this.$emit('handleSubmit', values)
+        })
+        .finally((result) => {
+          this.confirmLoading = false
+        })
+    }
+
+  }
+}
+</script>

+ 0 - 184
addons/admin/src/views/menu/access/modules/AddForm.vue

@@ -1,184 +0,0 @@
-<template>
-  <a-modal
-    title="新增权限"
-    :width="720"
-    :visible="visible"
-    :confirmLoading="confirmLoading"
-    :maskClosable="false"
-    @ok="handleSubmit"
-    @cancel="handleCancel"
-  >
-    <a-spin :spinning="confirmLoading">
-      <a-form :form="form">
-        <a-form-item label="权限名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
-          <a-input
-            v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
-          />
-        </a-form-item>
-        <a-form-item label="上级权限" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="默认为顶级权限">
-          <a-tree-select
-            :tree-data="apiListTreeData"
-            :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
-            allow-clear
-            v-decorator="['parent_id']"
-          ></a-tree-select>
-        </a-form-item>
-        <a-form-item
-          label="权限url"
-          :labelCol="labelCol"
-          :wrapperCol="wrapperCol"
-          extra="例如:index/index"
-        >
-          <a-input v-decorator="['url', {rules: [{required: true, message: '请输入权限url'}]}]" />
-        </a-form-item>
-        <a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
-          <a-input-number
-            :min="0"
-            v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
-          />
-        </a-form-item>
-      </a-form>
-    </a-spin>
-  </a-modal>
-</template>
-
-<script>
-import * as Api from '@/api/store/api'
-
-export default {
-  props: {
-    apiList: {
-      type: Array,
-      required: true
-    }
-  },
-  data () {
-    return {
-      // 标签布局属性
-      labelCol: {
-        xs: { span: 24 },
-        sm: { span: 7 }
-      },
-      // 输入框布局属性
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 13 }
-      },
-      // modal(对话框)是否可见
-      visible: false,
-      // modal(对话框)确定按钮 loading
-      confirmLoading: false,
-
-      form: this.$form.createForm(this),
-
-      // 权限列表 树状结构
-      apiListTreeData: []
-    }
-  },
-
-  methods: {
-
-    /**
-     * 显示对话框
-     */
-    show () {
-      // 显示窗口
-      this.visible = true
-      // 获取权限列表
-      this.getAccessList()
-      // 设置默认值
-      this.setFieldsValue()
-    },
-
-    /**
-     * 设置默认值
-     */
-    setFieldsValue () {
-      this.$nextTick(() => {
-        this.form.resetFields()
-        this.form.setFieldsValue({ parent_id: 0 })
-      })
-    },
-
-    /**
-     * 获取权限列表
-     */
-    getAccessList () {
-      const { apiList } = this
-      // 格式化权限列表
-      const selectList = this.formatTreeData(apiList)
-      // 顶级权限
-      selectList.unshift({
-        title: '顶级权限',
-        key: 0,
-        value: 0
-      })
-      this.apiListTreeData = selectList
-    },
-
-    /**
-     * 格式化权限列表
-     */
-    formatTreeData (list) {
-      const data = []
-      list.forEach(item => {
-        const netItem = {
-          title: item.name,
-          key: item.api_id,
-          value: item.api_id
-        }
-        if (item.children && item.children.length) {
-          netItem['children'] = this.formatTreeData(item['children'])
-        }
-        data.push(netItem)
-      })
-      return data
-    },
-
-    /**
-     * 确认按钮
-     */
-    handleSubmit () {
-      const { form: { validateFields } } = this
-      this.confirmLoading = true
-      // 表单的验证
-      validateFields((errors, values) => {
-        if (!errors) {
-          // 提交到后端api
-          this.onFormSubmit(values)
-        } else {
-          this.confirmLoading = false
-        }
-      })
-    },
-
-    /**
-     * 取消按钮
-     */
-    handleCancel () {
-      this.visible = false
-    },
-
-    /**
-    * 提交到后端api
-    */
-    onFormSubmit (values) {
-      Api.add({ form: values })
-        .then((result) => {
-          // 显示成功
-          this.$message.success(result.message)
-          // 关闭窗口
-          this.visible = false
-          // 重置表单内容
-          this.form.resetFields()
-          // 通知父端组件提交完成了
-          this.$emit('handleSubmit', values)
-        })
-        .finally((result) => {
-          this.confirmLoading = false
-        })
-    }
-
-  }
-}
-</script>

+ 253 - 0
addons/admin/src/views/setting/Sms.vue

@@ -0,0 +1,253 @@
+<template>
+  <a-card :bordered="false">
+    <div class="card-title">{{ $route.meta.title }}</div>
+    <a-spin :spinning="isLoading">
+      <a-form :form="form" @submit="handleSubmit">
+        <a-form-item class="mb-20" label="短信平台" :labelCol="labelCol" :wrapperCol="wrapperCol">
+          <a-radio-group
+            v-decorator="['default', { rules: [{ required: true }] }]"
+            @change="onChangeEngine"
+          >
+            <a-radio
+              v-for="(engine, index) in record.engine"
+              :key="index"
+              :value="index"
+            >{{ engine.name }}</a-radio>
+          </a-radio-group>
+
+          <div v-if="form.getFieldValue('default')" class="form-item-help">
+            <small>短信平台管理地址:</small>
+            <a
+              :href="record.engine[form.getFieldValue('default')].website"
+              target="_blank"
+            >{{ record.engine[form.getFieldValue('default')].website }}</a>
+          </div>
+        </a-form-item>
+        <!-- 阿里云配置 -->
+        <div v-show="form.getFieldValue('default') === 'aliyun'">
+          <a-form-item label="AccessKeyId" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.aliyun.AccessKeyId`]" />
+          </a-form-item>
+          <a-form-item
+            label="AccessKeySecret"
+            :labelCol="labelCol"
+            :wrapperCol="wrapperCol"
+            required
+          >
+            <a-input v-decorator="[`engine.aliyun.AccessKeySecret`]" />
+          </a-form-item>
+          <a-form-item label="短信签名 Sign" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.aliyun.sign`]" />
+          </a-form-item>
+        </div>
+        <!-- 腾讯云配置 -->
+        <div v-show="form.getFieldValue('default') === 'qcloud'">
+          <a-form-item label="SdkAppID" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.qcloud.SdkAppID`]" />
+          </a-form-item>
+          <a-form-item label="AccessKeyId" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.qcloud.AccessKeyId`]" />
+          </a-form-item>
+          <a-form-item
+            label="AccessKeySecret"
+            :labelCol="labelCol"
+            :wrapperCol="wrapperCol"
+            required
+          >
+            <a-input v-decorator="[`engine.qcloud.AccessKeySecret`]" />
+          </a-form-item>
+          <a-form-item label="短信签名 Sign" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.qcloud.sign`]" />
+          </a-form-item>
+        </div>
+        <!-- 七牛云配置 -->
+        <div v-show="form.getFieldValue('default') === 'qiniu'">
+          <a-form-item label="AccessKey" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.qiniu.AccessKey`]" />
+          </a-form-item>
+          <a-form-item label="SecretKey" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`engine.qiniu.SecretKey`]" />
+          </a-form-item>
+        </div>
+
+        <!-- 短信场景配置 -->
+        <div v-for="(item, index) in record['scene']" :key="index">
+          <a-divider orientation="left">{{ item.name }}</a-divider>
+          <a-form-item label="是否开启" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-radio-group
+              v-decorator="[`scene.${index}.isEnable`, { rules: [{ required: true }] }]"
+            >
+              <a-radio :value="true">开启</a-radio>
+              <a-radio :value="false">关闭</a-radio>
+            </a-radio-group>
+          </a-form-item>
+          <a-form-item label="模板内容" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <span>{{ record.scene[index].contentPractical }}</span>
+          </a-form-item>
+          <a-form-item label="模板ID/Code" :labelCol="labelCol" :wrapperCol="wrapperCol" required>
+            <a-input v-decorator="[`scene.${index}.templateCode`]" />
+            <div class="form-item-help">
+              <small>例如:SMS_139800030</small>
+            </div>
+          </a-form-item>
+          <a-form-item
+            v-if="record.scene[index].acceptPhone !== undefined"
+            label="接收手机号"
+            :labelCol="labelCol"
+            :wrapperCol="wrapperCol"
+            required
+          >
+            <a-input v-decorator="[`scene.${index}.acceptPhone`]" />
+            <div class="form-item-help">
+              <small>
+                注:如需填写多个手机号,请用英文逗号
+                <a-tag>,</a-tag>隔开
+              </small>
+            </div>
+          </a-form-item>
+        </div>
+        <a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
+          <a-button type="primary" html-type="submit">提交</a-button>
+        </a-form-item>
+      </a-form>
+    </a-spin>
+  </a-card>
+</template>
+
+<script>
+import { pick, omit } from 'lodash'
+import { isEmpty } from '@/utils/util'
+import * as Api from '@/api/setting/store'
+import SettingSmsSceneEnum from '@/common/enum/setting/sms/Scene'
+
+export default {
+  data () {
+    return {
+      SettingSmsSceneEnum,
+      // 当前设置项的key
+      key: 'sms',
+      // 标签布局属性
+      labelCol: { span: 3 },
+      // 输入框布局属性
+      wrapperCol: { span: 10 },
+      // loading状态
+      isLoading: false,
+      // 当前表单元素
+      form: this.$form.createForm(this),
+      // 当前记录详情
+      record: {}
+    }
+  },
+  // 初始化数据
+  created () {
+    // 获取当前详情记录
+    this.getDetail()
+  },
+  methods: {
+
+    // 获取当前详情记录
+    getDetail () {
+      this.isLoading = true
+      Api.detail(this.key)
+        .then(result => {
+          // 当前记录
+          this.record = result.data.values
+          // 设置默认值
+          this.setFieldsValue()
+        })
+        .finally(result => {
+          this.isLoading = false
+        })
+    },
+
+    // 切换短信平台事件
+    onChangeEngine (e) {
+      const app = this
+      const engine = e.target.value
+      for (const index in app.record.scene) {
+        const item = app.record.scene[index]
+        item.contentPractical = app.onVsprintf(item.content, item.variables[engine])
+      }
+    },
+
+    // 解析短信内容变量, 生成完整的模板内容
+    onVsprintf (str, variables) {
+      const reg = new RegExp('%s')
+      for (var i = 0; i < variables.length; i++) {
+        str = str.replace(reg, variables[i])
+      }
+      return str
+    },
+
+    /**
+     * 设置默认值
+     */
+    setFieldsValue () {
+      const app = this
+      const { record, $nextTick, form } = app
+      !isEmpty(form.getFieldsValue()) && $nextTick(() => {
+        const scene = {}
+        for (const index in record.scene) {
+          const item = record.scene[index]
+          const contentPractical = app.onVsprintf(item.content, item.variables[record.default])
+          app.$set(item, 'contentPractical', contentPractical)
+          scene[index] = pick(item, ['isEnable', 'templateCode', 'acceptPhone'])
+        }
+        const engine = {}
+        for (const index in record.engine) {
+          engine[index] = omit(record.engine[index], ['name', 'website'])
+        }
+        form.setFieldsValue({
+          default: record.default,
+          engine,
+          scene
+        })
+      })
+    },
+
+    /**
+     * 确认按钮
+     */
+    handleSubmit (e) {
+      e.preventDefault()
+      // 表单验证
+      const { form: { validateFields } } = this
+      validateFields((errors, values) => {
+        // 提交到后端api
+        !errors && this.onFormSubmit(values)
+      })
+    },
+
+    /**
+    * 提交到后端api
+    */
+    onFormSubmit (values) {
+      this.isLoading = true
+      Api.update(this.key, { form: values })
+        .then((result) => {
+          // 显示提示信息
+          this.$message.success(result.message, 1.5)
+        })
+        .finally((result) => {
+          this.isLoading = false
+        })
+    }
+
+  }
+}
+</script>
+<style lang="less" scoped>
+.ant-form-item {
+  margin-bottom: 10px;
+}
+/deep/.ant-form-item-control {
+  padding-left: 10px;
+
+  .ant-form-item-control {
+    padding-left: 0;
+  }
+}
+.ant-divider {
+  margin-top: 50px !important;
+}
+</style>

+ 0 - 137
addons/admin/src/views/store/modules/CreateForm.vue

@@ -1,137 +0,0 @@
-<template>
-  <a-modal
-    title="新增商城"
-    :width="720"
-    :visible="visible"
-    :confirmLoading="confirmLoading"
-    :maskClosable="false"
-    @ok="handleSubmit"
-    @cancel="handleCancel"
-  >
-    <a-spin :spinning="confirmLoading">
-      <a-form :form="form">
-        <a-form-item label="商城名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
-          <a-input
-            v-decorator="['store_name', {rules: [{required: true, min: 3, message: '请输入至少3个字符'}]}]"
-          />
-        </a-form-item>
-        <a-form-item label="商家用户名" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="商家后台登录用户名">
-          <a-input
-            v-decorator="['user_name', {rules: [{required: true, min: 4, message: '请输入至少4个字符'}]}]"
-          />
-        </a-form-item>
-        <a-form-item label="用户密码" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="商家后台登录密码">
-          <a-input
-            type="password"
-            v-decorator="['password', {rules: [
-              {required: true, min: 6, message: '请输入至少6个字符'}
-            ]}]"
-          />
-        </a-form-item>
-        <a-form-item label="确认密码" :labelCol="labelCol" :wrapperCol="wrapperCol">
-          <a-input
-            type="password"
-            v-decorator="['password_confirm', {rules: [
-              {required: true, message: '请输入确认密码'},
-              {validator: compareToFirstPassword}
-            ]}]"
-          />
-        </a-form-item>
-        <a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
-          <a-input-number
-            :min="0"
-            v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
-          />
-        </a-form-item>
-      </a-form>
-    </a-spin>
-  </a-modal>
-</template>
-
-<script>
-
-import { add as addApi } from '@/api/store'
-
-export default {
-  data () {
-    return {
-      // 标签布局属性
-      labelCol: {
-        span: 7
-      },
-      // 输入框布局属性
-      wrapperCol: {
-        span: 13
-      },
-      // modal(对话框)是否可见
-      visible: false,
-      // modal(对话框)确定按钮 loading
-      confirmLoading: false,
-      // 当前表单元素
-      form: this.$form.createForm(this)
-    }
-  },
-  methods: {
-
-    /**
-     * 显示对话框
-     */
-    show () {
-      this.visible = true
-      this.form.resetFields()
-    },
-
-    /**
-     * 确认按钮
-     */
-    handleSubmit () {
-      const { form: { validateFields } } = this
-      // 表单的验证
-      validateFields((errors, values) => {
-        // 提交到后端api
-        !errors && this.onFormSubmit(values)
-      })
-    },
-
-    /**
-     * 关闭对话框
-     */
-    handleCancel () {
-      this.visible = false
-      // 重置表单内容
-      this.form.resetFields()
-    },
-
-    /**
-     * 验证确认密码是否一致
-     */
-    compareToFirstPassword (rule, value, callback) {
-      const { form } = this
-      if (value && value !== form.getFieldValue('password')) {
-        return new Error('您输入的确认密码不一致')
-      }
-      return true
-    },
-
-    /**
-    * 提交到后端api
-    */
-    onFormSubmit (values) {
-      this.confirmLoading = true
-      addApi({ form: values })
-        .then(result => {
-          // 显示成功提示
-          this.$message.success(result.message, 1.2)
-          // 关闭对话框
-          this.handleCancel()
-          // 通知父端组件提交完成了
-          this.$emit('handleSubmit', values)
-        })
-        .finally((result) => {
-          this.confirmLoading = false
-        })
-    }
-
-  }
-}
-</script>

+ 0 - 188
addons/admin/src/views/user/Login.vue

@@ -1,188 +0,0 @@
-<template>
-  <div class="main">
-    <div class="header">
-      <!-- <img src="~@/assets/logo.svg" class="logo" alt="logo" /> -->
-      <span class="title">管理后台登录</span>
-    </div>
-    <a-form
-      id="formLogin"
-      class="user-layout-login"
-      ref="formLogin"
-      :form="form"
-      @submit="handleSubmit"
-    >
-      <!-- 错误提示 -->
-      <a-alert
-        v-if="isLoginError"
-        type="error"
-        showIcon
-        style="margin-bottom: 24px;"
-        :message="loginErrorMsg"
-      />
-      <a-form-item>
-        <a-input
-          size="large"
-          type="text"
-          placeholder="请输入用户名"
-          v-decorator="[
-            'username',
-            {rules: [{ required: true, message: '您还没有输入用户名' }], validateTrigger: 'change'}
-          ]"
-        >
-          <a-icon slot="prefix" type="user" :style="{ color: 'rgba(0,0,0,.25)' }" />
-        </a-input>
-      </a-form-item>
-
-      <a-form-item>
-        <a-input
-          size="large"
-          type="password"
-          autocomplete="false"
-          placeholder="请输入用户密码"
-          v-decorator="[
-            'password',
-            {rules: [{ required: true, message: '您还没有输入用户密码' }], validateTrigger: 'blur'}
-          ]"
-        >
-          <a-icon slot="prefix" type="lock" :style="{ color: 'rgba(0,0,0,.25)' }" />
-        </a-input>
-      </a-form-item>
-
-      <a-form-item style="margin-top:24px">
-        <a-button
-          size="large"
-          type="primary"
-          htmlType="submit"
-          class="login-button"
-          :loading="state.loginBtn"
-          :disabled="state.loginBtn"
-        >确定</a-button>
-      </a-form-item>
-    </a-form>
-  </div>
-</template>
-
-<script>
-import { mapActions } from 'vuex'
-import { timeFix } from '@/utils/util'
-
-export default {
-  data () {
-    return {
-      // 是否登录错误
-      isLoginError: false,
-      // 登录错误的信息
-      loginErrorMsg: '登录失败',
-      // 表单组件
-      form: this.$form.createForm(this),
-      // 页面状态
-      state: { loginBtn: false }
-    }
-  },
-  created () { },
-  methods: {
-    ...mapActions(['Login', 'Logout']),
-    /**
-     * 表单提交: 确定登录
-     */
-    handleSubmit (e) {
-      e.preventDefault()
-      const {
-        form: { validateFields },
-        state,
-        Login
-      } = this
-
-      state.loginBtn = true
-
-      // 表单验证
-      validateFields(['username', 'password'], { force: true }, (err, values) => {
-        if (!err) {
-          Login(values)
-            .then(res => this.loginSuccess(res))
-            .catch(err => this.loginFailed(err))
-            .finally(() => {
-              state.loginBtn = false
-            })
-        } else {
-          setTimeout(() => {
-            state.loginBtn = false
-          }, 100)
-        }
-      })
-    },
-
-    /**
-     * 登录成功
-     */
-    loginSuccess (res) {
-      this.$router.push({ path: '/' })
-      // 显示欢迎信息
-      setTimeout(() => {
-        this.$notification.success({
-          message: '欢迎',
-          description: `${timeFix()},欢迎回来`
-        })
-      }, 1000)
-      this.isLoginError = false
-    },
-
-    /**
-     * 登录请求失败
-     */
-    loginFailed (response) {
-      this.isLoginError = true
-      this.loginErrorMsg = response.message
-    }
-  }
-}
-</script>
-
-<style lang="less" scoped>
-.header {
-  height: 44px;
-  line-height: 44px;
-  margin-bottom: 40px;
-  text-align: center;
-
-  .badge {
-    position: absolute;
-    display: inline-block;
-    line-height: 1;
-    vertical-align: middle;
-    margin-left: -12px;
-    margin-top: -10px;
-    opacity: 0.8;
-  }
-
-  .logo {
-    height: 44px;
-    vertical-align: top;
-    margin-right: 10px;
-    margin-left: -12px;
-    border-style: none;
-  }
-
-  .title {
-    font-size: 30px;
-    color: rgba(0, 0, 0, 0.85);
-    font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
-    font-weight: 600;
-    position: relative;
-    top: 2px;
-  }
-}
-
-.user-layout-login {
-  label {
-    font-size: 14px;
-  }
-
-  button.login-button {
-    padding: 0 15px;
-    font-size: 16px;
-    height: 40px;
-    width: 100%;
-  }
-}
-</style>