浏览代码

Merge branch 'master' into dev

就是那个锅 4 年之前
父节点
当前提交
f6e6414e6a

+ 0 - 54
_web/src/api/manage.js

@ -1,54 +0,0 @@
/**
 * 此文件已与正式环境无关,上线前需删除,及相关vue
 */
import { axios } from '@/utils/request'
const api = {
  user: '/user',
  role: '/role',
  service: '/service'
}
export default api
export function getRoleList (parameter) {
  return axios({
    url: api.role,
    method: 'get',
    params: parameter
  })
}
export function getServiceList (parameter) {
  return axios({
    url: api.service,
    method: 'get',
    params: parameter
  })
}
export function getPermissions (parameter) {
  return axios({
    url: api.permissionNoPager,
    method: 'get',
    data: parameter
  })
}
export function getOrgTree (parameter) {
  return axios({
    url: api.orgTree,
    method: 'get',
    params: parameter
  })
}
// id == 0 add     post
// id != 0 update  put
export function saveService (parameter) {
  return axios({
    url: api.service,
    method: parameter.id === 0 ? 'post' : 'put',
    data: parameter
  })
}

+ 14 - 0
_web/src/api/modular/system/dictManage.js

@ -69,3 +69,17 @@ export function sysDictTypeDropDown (parameter) {
    params: parameter
  })
}
/**
 * 获取所有字典,启动时加入缓存使用
 *
 * @author yubaoshan
 * @date 2020/6/10 00:10
 */
export function sysDictTypeTree (parameter) {
  return axios({
    url: '/sysDictType/tree',
    method: 'get',
    params: parameter
  })
}

+ 0 - 28
_web/src/api/modular/system/loginManage.js

@ -62,20 +62,6 @@ export function getTenantOpen (parameter) {
  })
}
/**
 * 获取用户头像
 *
 * @author yubaoshan
 * @date 2020/5/26 19:08
 */
export function previewAvatar (parameter) {
  return axios({
    url: '/system/previewAvatar',
    method: 'get',
    params: parameter
  })
}
/**
 * 获取短信验证码
 *
@ -89,17 +75,3 @@ export function getSmsCaptcha (parameter) {
    params: parameter
  })
}
/**
 *
 *
 * @author yubaoshan
 * @date 2020/5/26 19:29
 */
export function twoStepCode (parameter) {
  return axios({
    url: '/auth/2step-code',
    method: 'get',
    params: parameter
  })
}

+ 28 - 0
_web/src/api/modular/system/userManage.js

@ -181,3 +181,31 @@ export function sysUserSelector (parameter) {
    params: parameter
  })
}
/**
 * 修改头像
 *
 * @author yubaoshan
 * @date 2020/9/20 2:21
 */
export function sysUserUpdateAvatar (parameter) {
  return axios({
    url: '/sysUser/updateAvatar',
    method: 'post',
    data: parameter
  })
}
/**
 * 更新基本信息
 *
 * @author yubaoshan
 * @date 2020/9/20 03:12
 */
export function sysUserUpdateInfo (parameter) {
  return axios({
    url: '/sysUser/updateInfo',
    method: 'post',
    data: parameter
  })
}

+ 23 - 1
_web/src/components/tools/Logo.vue

@ -2,19 +2,26 @@
  <div class="logo">
    <router-link :to="{name:'Console'}">
      <LogoSvg alt="logo" />
      <h1 v-if="showTitle">{{ title }}</h1>
      <h1 v-if="showTitle">{{ this.titles }}</h1>
    </router-link>
  </div>
</template>
<script>
import LogoSvg from '@/assets/logo.svg?inline'
import { mixin, mixinDevice } from '@/utils/mixin'
export default {
  name: 'Logo',
  components: {
    LogoSvg
  },
  mixins: [mixin, mixinDevice],
  data () {
    return {
      titles: ''
    }
  },
  props: {
    title: {
      type: String,
@ -26,6 +33,21 @@ export default {
      default: true,
      required: false
    }
  },
  created () {
    if (this.layoutMode === 'topmenu') {
      if (this.title.length > 8) {
        this.titles = this.title.substring(0, 7) + '...'
      } else {
        this.titles = this.title
      }
    } else {
      if (this.title.length > 10) {
        this.titles = this.title.substring(0, 9) + '...'
      } else {
        this.titles = this.title
      }
    }
  }
}
</script>

+ 1 - 108
_web/src/components/tools/UserMenu.vue

@ -21,13 +21,6 @@
              <span>切换应用</span>
            </a>
          </a-menu-item>
          <a-menu-item key="5" v-if="hasPerm('sysUser:updatePwd')" >
            <a @click="updatePwd()" >
              <a-icon type="tool"/>
              <span>修改密码</span>
            </a>
          </a-menu-item>
          <a-menu-item key="0">
            <router-link :to="{ name: 'center' }">
              <a-icon type="user"/>
@ -50,7 +43,6 @@
        </a-menu>
      </a-dropdown>
    </div>
    <a-modal
      title="切换应用"
      :visible="visible"
@ -58,7 +50,6 @@
      :confirm-loading="confirmLoading"
      @cancel="handleCancel"
    >
      <a-form :form="form1" >
        <a-form-item
          :labelCol="labelCol"
@ -77,58 +68,7 @@
        </a-form-item>
      </a-form>
    </a-modal>
    <a-modal
      title="修改密码"
      :visible="visible_updPwd"
      :confirm-loading="confirmLoading"
      @ok="handleOkUpdPwd"
      @cancel="handleCancel"
    >
      <a-form :form="form2">
        <a-form-item
          label="原密码"
          :labelCol="labelCol"
          :wrapperCol="wrapperCol"
          has-feedback
        >
          <a-input placeholder="请输入原密码" type="password" v-decorator="['password', {rules: [{required: true, message: '请输入原密码!'}]}]" />
        </a-form-item>
        <a-form-item
          label="新密码"
          :labelCol="labelCol"
          :wrapperCol="wrapperCol"
          has-feedback
        >
          <a-input
            placeholder="请输入新密码"
            type="password"
            v-decorator="['newPassword', {rules: [{required: true, message: '请输入新密码!'},{
              validator: validateToNextPassword,
            },]}]" />
        </a-form-item>
        <a-form-item
          label="重复新密码"
          :labelCol="labelCol"
          :wrapperCol="wrapperCol"
          has-feedback
        >
          <a-input
            placeholder="请再次输入新密码"
            type="password"
            v-decorator="['confirm', {rules: [{required: true, message: '请再次输入新密码!'},
                                              {
                                                validator: compareToFirstPassword,
                                              }]}]" />
        </a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>
<script>
@ -161,20 +101,17 @@ export default {
        sm: { span: 16 }
      },
      visible: false,
      visible_updPwd: false,
      confirmLoading: false,
      form1: this.$form.createForm(this),
      form2: this.$form.createForm(this),
      defApp: []
    }
  },
  computed: {
    ...mapGetters(['nickname', 'avatar', 'userInfo'])
  },
  methods: {
    ...mapActions(['Logout', 'MenuChange', 'UpdatePwd']),
    ...mapActions(['Logout', 'MenuChange']),
    handleLogout () {
      this.$confirm({
@ -205,30 +142,6 @@ export default {
      this.defApp.push(Vue.ls.get(ALL_APPS_MENU)[0].code)
    },
    /**
     * 打开修改密码框
     */
    updatePwd () {
      this.visible_updPwd = true
    },
    compareToFirstPassword (rule, value, callback) {
      const form2 = this.form2
      if (value && value !== form2.getFieldValue('newPassword')) {
        // eslint-disable-next-line standard/no-callback-literal
        callback('请确认两次输入密码的一致性!')
      } else {
        callback()
      }
    },
    validateToNextPassword (rule, value, callback) {
      const form2 = this.form2
      if (value && this.confirmDirty) {
        form2.validateFields(['confirm'], { force: true })
      }
      callback()
    },
    switchApp (appCode) {
      this.visible = false
      this.defApp = []
@ -241,29 +154,9 @@ export default {
        message.error('应用切换异常')
      })
    },
    handleOkUpdPwd () {
      const { form2: { validateFields } } = this
      validateFields((errors, values) => {
        if (!errors) {
          values.id = this.userInfo.id
          this.UpdatePwd(values).then((res) => {
            if (res.success) {
              this.$message.success('修改成功')
              this.handleCancel()
            } else {
              this.$message.error('修改失败:' + res.message)
            }
          })
        }
      })
    },
    handleCancel () {
      this.form1.resetFields()
      this.form2.resetFields()
      this.visible = false
      this.visible_updPwd = false
    }
  }
}

+ 34 - 22
_web/src/store/modules/user.js

@ -1,8 +1,8 @@
import Vue from 'vue'
import { login, getLoginUser, logout } from '@/api/modular/system/loginManage'
import { sysDictTypeTree } from '@/api/modular/system/dictManage'
import { sysMenuChange } from '@/api/modular/system/menuManage'
import { sysUserUpdatePwd } from '@/api/modular/system/userManage'
import { ACCESS_TOKEN, ALL_APPS_MENU } from '@/store/mutation-types'
import { ACCESS_TOKEN, ALL_APPS_MENU, DICT_TYPE_TREE_DATA } from '@/store/mutation-types'
import { welcome } from '@/utils/util'
import store from '../index'
@ -58,8 +58,10 @@ const user = {
          Vue.ls.set(ACCESS_TOKEN, result, 7 * 24 * 60 * 60 * 1000)
          commit('SET_TOKEN', result)
          resolve()
        // eslint-disable-next-line handle-callback-err
        }).catch(error => {
          reject(error)
          // eslint-disable-next-line prefer-promise-reject-errors
          reject('后端未启动或代理错误')
        })
      })
    },
@ -69,14 +71,16 @@ const user = {
      return new Promise((resolve, reject) => {
        getLoginUser().then(response => {
          if (response.success) {
              const data = response.data
              commit('SET_ADMINTYPE', data.adminType)
              commit('SET_ROLES', 1)
              commit('SET_BUTTONS', data.permissions)
              commit('SET_INFO', data)
              commit('SET_NAME', { name: data.name, welcome: welcome() })
              commit('SET_AVATAR', data.avatar)
              resolve(data)
            const data = response.data
            commit('SET_ADMINTYPE', data.adminType)
            commit('SET_ROLES', 1)
            commit('SET_BUTTONS', data.permissions)
            commit('SET_INFO', data)
            commit('SET_NAME', { name: data.name, welcome: welcome() })
            if (data.avatar != null) {
              commit('SET_AVATAR', process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + data.avatar)
            }
            resolve(data)
          } else {
            // eslint-disable-next-line no-undef
            reject(new Error(data.message))
@ -101,6 +105,25 @@ const user = {
          commit('SET_ADMINTYPE', '')
          Vue.ls.remove(ACCESS_TOKEN)
          Vue.ls.remove(ALL_APPS_MENU)
          Vue.ls.remove(DICT_TYPE_TREE_DATA)
        })
      })
    },
    // 加载所有字典数据
    dictTypeData () {
      return new Promise((resolve, reject) => {
        sysDictTypeTree().then((data) => {
          if (data.success) {
            const result = data.data
            Vue.ls.set(DICT_TYPE_TREE_DATA, result)
            resolve()
          } else {
            // eslint-disable-next-line no-undef
            reject(new Error(data.message))
          }
        }).catch(error => {
          reject(error)
        })
      })
    },
@ -141,17 +164,6 @@ const user = {
          resolve()
        })
      })
    },
    // 修改密码
    UpdatePwd ({ commit }, passwords) {
      return new Promise((resolve, reject) => {
        sysUserUpdatePwd(passwords).then((res) => {
          resolve(res)
        }).catch(() => {
          resolve()
        })
      })
    }
  }

+ 2 - 1
_web/src/store/mutation-types.js

@ -1,6 +1,6 @@
export const ACCESS_TOKEN = 'Access-Token'
export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
export const ALL_APPS_MENU = []
export const ALL_APPS_MENU = 'ALL_APPS_MENU'
export const DEFAULT_THEME = 'DEFAULT_THEME'
export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
export const DEFAULT_COLOR = 'DEFAULT_COLOR'
@ -10,6 +10,7 @@ export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB'
export const DICT_TYPE_TREE_DATA = 'DICT_TYPE_TREE_DATA'
export const CONTENT_WIDTH_TYPE = {
  Fluid: 'Fluid',

+ 51 - 0
_web/src/utils/filter.js

@ -1,4 +1,5 @@
import Vue from 'vue'
import { DICT_TYPE_TREE_DATA } from '@/store/mutation-types'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
@ -18,6 +19,13 @@ Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
  return moment(dataStr).format(pattern)
})
/**
 * 金额格式化 ,使用方法:{{ val | Fmoney }}
 *
 * @author yubaoshan
 * @date 2020-9-15 15:02:20
 */
Vue.filter('Fmoney', function (val) {
  // eslint-disable-next-line no-useless-escape
  val = val.toString().replace(/\$|\,/g, '')
@ -38,3 +46,46 @@ Vue.filter('Fmoney', function (val) {
  }
  return (((sign) ? '' : '-') + val + '.' + cents)
})
/**
 * 翻译使用方法,直接返回翻译后的name {{ code | dictType(value) }}
 *
 * @author yubaoshan
 * @date 2020-9-15 15:02:20
 */
Vue.filter('dictType', function (code, value) {
  const dictTypeTree = Vue.ls.get(DICT_TYPE_TREE_DATA)
  if (dictTypeTree === undefined) {
    return '需重新登录'
  }
  // eslint-disable-next-line eqeqeq
  const tree = dictTypeTree.filter(item => item.code == code)[0].children
  if (tree === undefined || tree.length === 0) {
    return '无此字典'
  }
  // eslint-disable-next-line eqeqeq
  const values = tree.filter(item => item.code == value)
  if (values.length === undefined || values.length === 0) {
    return '无此字典'
  }
  return values[0].name
})
/**
 * 获取某个code下字典的列表,多用于字典下拉框,使用方法:{{ code | dictData }}
 *
 * @author yubaoshan
 * @date 2020-9-19 22:40:22
 */
Vue.filter('dictData', function (code) {
  const dictTypeTree = Vue.ls.get(DICT_TYPE_TREE_DATA)
  if (dictTypeTree === undefined) {
    return []
  }
  // eslint-disable-next-line eqeqeq
  const tree = dictTypeTree.filter(item => item.code == code)[0].children
  if (tree === undefined) {
    return []
  }
  return tree
})

+ 38 - 39
_web/src/views/system/account/settings/AvatarModal.vue

@ -48,14 +48,17 @@
        <a-button icon="redo" @click="rotateRight"/>
      </a-col>
      <a-col :lg="{span: 2, offset: 6}" :md="2">
        <a-button type="primary" @click="finish('blob')">保存</a-button>
        <a-button type="primary" @click="finish('blob')" :loading="uploading">保存</a-button>
      </a-col>
    </a-row>
  </a-modal>
</template>
<script>
export default {
  import { sysFileInfoUpload } from '@/api/modular/system/fileManage'
  import { sysUserUpdateAvatar } from '@/api/modular/system/userManage'
  export default {
  data () {
    return {
      visible: false,
@ -64,7 +67,6 @@ export default {
      fileList: [],
      uploading: false,
      options: {
        // img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        img: '',
        autoCrop: true,
        autoCropWidth: 200,
@ -98,6 +100,7 @@ export default {
      this.$refs.cropper.rotateRight()
    },
    beforeUpload (file) {
      this.fileList = file
      const reader = new FileReader()
      // 把Array Buffer转化为blob 如果是base64不需要
      // 转化为base64
@ -107,56 +110,52 @@ export default {
      }
      // 转化为blob
      // reader.readAsArrayBuffer(file)
      return false
    },
    // 上传图片(点击上传按钮)
    finish (type) {
      console.log('finish')
      const _this = this
      const formData = new FormData()
      // 输出
      if (type === 'blob') {
        this.uploading = true
        this.$refs.cropper.getCropBlob((data) => {
          const img = window.URL.createObjectURL(data)
          this.model = true
          this.modelSrc = img
          formData.append('file', data, this.fileName)
          this.$http.post('https://www.mocky.io/v2/5cc8019d300000980a055e76', formData, { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
            .then((response) => {
              console.log('upload response:', response)
              // var res = response.data
              // if (response.status === 'done') {
              //   _this.imgFile = ''
              //   _this.headImg = res.realPathList[0] // 完整路径
              //   _this.uploadImgRelaPath = res.relaPathList[0] // 非完整路径
              //   _this.$message.success('上传成功')
              //   this.visible = false
              // }
              _this.$message.success('上传成功')
              _this.$emit('ok', response.url)
              _this.visible = false
            })
          const files = new window.File(
            [data],
            this.fileList.name,
            { type: this.fileList.type }
          )
          const formData = new FormData()
          formData.append('file', files)
          sysFileInfoUpload(formData).then((res) => {
            if (res.success) {
              this.updateAvatar(res.data)
              this.$emit('ok', res.data)
            } else {
              this.uploading = false
              this.$message.error(res.message)
            }
          })
        })
      } else {
        this.$refs.cropper.getCropData((data) => {
          this.model = true
          this.modelSrc = data
          console.log(data)
        })
      }
    },
    okHandel () {
      const vm = this
      vm.confirmLoading = true
      setTimeout(() => {
        vm.confirmLoading = false
        vm.close()
        vm.$message.success('上传头像成功')
      }, 2000)
    updateAvatar (data) {
      const params = {
        id: this.id,
        avatar: data
      }
      sysUserUpdateAvatar(params).then((res) => {
        this.uploading = false
        if (res.success) {
          this.visible = false
          this.$message.success('头像上传修改成功')
        } else {
          this.$message.error(res.message)
        }
      })
    },
    realTime (data) {
      this.previews = data
    }

+ 82 - 28
_web/src/views/system/account/settings/BaseSetting.vue

@ -3,56 +3,48 @@
    <a-row :gutter="16">
      <a-col :md="24" :lg="16">
        <a-form layout="vertical">
        <a-form layout="vertical" :form="form">
          <a-form-item
            label="昵称"
          >
            <a-input placeholder="给自己起个名字" />
            <a-input placeholder="给自己起个昵称吧" v-decorator="['nickName']"/>
          </a-form-item>
          <a-form-item
            label="Bio"
            label="生日"
          >
            <a-textarea rows="4" placeholder="You are not alone."/>
            <a-date-picker placeholder="请选择生日" @change="onChange" style="width: 100%" v-decorator="['birthday', {rules: [{required: true, message: '请选择生日!'}]}]" />
          </a-form-item>
          <a-form-item
            label="电子邮件"
            :required="false"
            label="性别"
          >
            <a-input placeholder="exp@admin.com"/>
            <a-radio-group v-decorator="['sex',{rules: [{ required: true, message: '请选择性别!' }]}]" >
              <a-radio v-for="(item,index) in sexData" :key="index" :value="item.code">{{ item.name }}</a-radio>
            </a-radio-group>
          </a-form-item>
          <a-form-item
            label="加密方式"
            :required="false"
            label="手机"
          >
            <a-select defaultValue="aes-256-cfb">
              <a-select-option value="aes-256-cfb">aes-256-cfb</a-select-option>
              <a-select-option value="aes-128-cfb">aes-128-cfb</a-select-option>
              <a-select-option value="chacha20">chacha20</a-select-option>
            </a-select>
            <a-input placeholder="请输入手机号" v-decorator="['phone', {rules: [{required: true, message: '请输入手机号!'}]}]"/>
          </a-form-item>
          <a-form-item
            label="连接密码"
            :required="false"
            label="电话"
          >
            <a-input placeholder="h3gSbecd"/>
            <a-input placeholder="请输入电话" v-decorator="['tel', {rules: [{required: true, message: '请输入电话!'}]}]"/>
          </a-form-item>
          <a-form-item
            label="登录密码"
            label="电子邮件"
            :required="false"
          >
            <a-input placeholder="密码"/>
            <a-input placeholder="请输入电子邮件地址" v-decorator="['email', {type: 'email',message: '请输入正确的邮箱号',rules: [{required: true, message: '请输入正确的邮箱号!'}]}]"/>
          </a-form-item>
          <a-form-item>
            <a-button type="primary">提交</a-button>
            <a-button style="margin-left: 8px">保存</a-button>
            <a-button type="primary" @click="submitUserInfo">更新基本信息</a-button>
          </a-form-item>
        </a-form>
      </a-col>
      <a-col :md="24" :lg="8" :style="{ minHeight: '180px' }">
        <div class="ant-upload-preview" @click="$refs.modal.edit(1)" >
        <div class="ant-upload-preview" @click="$refs.modal.edit(userInfo.id)" >
          <a-icon type="cloud-upload-o" class="upload-icon"/>
          <div class="mask">
            <a-icon type="plus" />
@ -69,8 +61,12 @@
</template>
<script>
import AvatarModal from './AvatarModal'
  import store from '@/store'
  import AvatarModal from './AvatarModal'
  import { mapGetters } from 'vuex'
  import moment from 'moment'
  import { sysUserUpdateInfo } from '@/api/modular/system/userManage'
// mapActions
export default {
  components: {
    AvatarModal
@ -79,6 +75,8 @@ export default {
    return {
      // cropper
      preview: {},
      form: this.$form.createForm(this),
      sexData: [],
      option: {
        img: '/avatar2.jpg',
        info: true,
@ -92,13 +90,69 @@ export default {
        fixedBox: true,
        // 开启宽度和高度比例
        fixed: true,
        fixedNumber: [1, 1]
        fixedNumber: [1, 1],
        // userInfo
        birthdayString: ''
      }
    }
  },
  computed: {
    ...mapGetters(['userInfo'])
  },
  mounted () {
    this.initUserInfo()
  },
  methods: {
    // ...mapActions(['GetInfo']),
    /**
     * 初始化用户信息
     */
    initUserInfo () {
      setTimeout(() => {
        this.form.setFieldsValue(
          {
            birthday: moment(this.userInfo.birthday, 'YYYY-MM-DD'),
            nickName: this.userInfo.nickName,
            sex: this.userInfo.sex.toString(),
            email: this.userInfo.email,
            phone: this.userInfo.phone,
            tel: this.userInfo.tel
          }
        )
        this.birthdayString = moment(this.userInfo.birthday).format('YYYY-MM-DD')
        this.option.img = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + this.userInfo.avatar
        this.getSexData()
      }, 100)
    },
    /**
     * 日期需单独转换
     */
    onChange (date, dateString) {
      this.birthdayString = dateString
    },
    submitUserInfo () {
      const { form: { validateFields } } = this
      validateFields((err, values) => {
        if (!err) {
          values.birthday = this.birthdayString
          values.id = this.userInfo.id
          sysUserUpdateInfo(values).then((res) => {
            if (res.success) {
              this.$message.success('个人信息更新成功')
              store.dispatch('GetInfo').then(() => {})
            } else {
              this.$message.error(res.message)
            }
          })
        }
      })
    },
    getSexData () {
      this.sexData = this.$options.filters['dictData']('sex')
    },
    setavatar (url) {
      this.option.img = url
      this.option.img = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + url
      store.dispatch('GetInfo').then(() => {})
    }
  }
}

+ 59 - 30
_web/src/views/system/account/settings/Security.vue

@ -1,39 +1,68 @@
<template>
  <a-list
    itemLayout="horizontal"
    :dataSource="data"
  >
    <a-list-item slot="renderItem" slot-scope="item, MenuIndex" :key="MenuIndex">
      <a-list-item-meta>
        <a slot="title">{{ item.title }}</a>
        <span slot="description">
          <span class="security-list-description">{{ item.description }}</span>
          <span v-if="item.value"> : </span>
          <span class="security-list-value">{{ item.value }}</span>
        </span>
      </a-list-item-meta>
      <template v-if="item.actions">
        <a slot="actions" @click="item.actions.callback">{{ item.actions.title }}</a>
      </template>
    </a-list-item>
  </a-list>
  <div>
    <a-list
      itemLayout="horizontal"
      :dataSource="data"
    >
      <a-list-item slot="renderItem" slot-scope="item, MenuIndex" :key="MenuIndex">
        <a-list-item-meta>
          <a slot="title">{{ item.title }}</a>
          <span slot="description">
            <span class="security-list-description">{{ item.description }}</span>
            <span v-if="item.value"> : </span>
            <span class="security-list-value">{{ item.value }}</span>
          </span>
        </a-list-item-meta>
        <template v-if="item.actions">
          <a slot="actions" @click="item.actions.callback">{{ item.actions.title }}</a>
        </template>
      </a-list-item>
    </a-list>
    <upd-pwd ref="updPwd"/>
  </div>
</template>
<script>
export default {
  data () {
    return {
      data: [
        { title: '账户密码', description: '当前密码强度', value: '强', actions: { title: '修改', callback: () => { this.$message.info('This is a normal message') } } },
        { title: '密保手机', description: '已绑定手机', value: '138****8293', actions: { title: '修改', callback: () => { this.$message.success('This is a message of success') } } },
        { title: '密保问题', description: '未设置密保问题,密保问题可有效保护账户安全', value: '', actions: { title: '设置', callback: () => { this.$message.error('This is a message of error') } } },
        { title: '备用邮箱', description: '已绑定邮箱', value: 'ant***sign.com', actions: { title: '修改', callback: () => { this.$message.warning('This is message of warning') } } },
        { title: 'MFA 设备', description: '未绑定 MFA 设备,绑定后,可以进行二次确认', value: '', actions: { title: '绑定', callback: () => { this.$message.info('This is a normal message') } } }
      ]
  import { mapGetters } from 'vuex'
  import UpdPwd from './securityItem/updPwd'
  export default {
    components: {
      UpdPwd
    },
    data () {
      return {
        data: []
      }
    },
    created () {
      if (this.hasPerm('sysUser:updatePwd')) {
        const updPwdMenu = {
          title: '账户密码',
          description: '当前密码强度',
          value: '强',
          actions: { title: '修改',
            callback: () => {
              this.$refs.updPwd.open(this.userInfo.id)
            }
          }
        }
        this.data.push(updPwdMenu)
      }
      const encryptedPhone = { title: '密保手机', description: '已绑定手机', value: '138****8293', actions: { title: '修改', callback: () => { this.$message.success('This is a message of success') } } }
      const encryptedProblem = { title: '密保问题', description: '未设置密保问题,密保问题可有效保护账户安全', value: '', actions: { title: '设置', callback: () => { this.$message.error('This is a message of error') } } }
      const encryptedEmail = { title: '备用邮箱', description: '已绑定邮箱', value: 'ant***sign.com', actions: { title: '修改', callback: () => { this.$message.warning('This is message of warning') } } }
      const encryptedMfa = { title: 'MFA 设备', description: '未绑定 MFA 设备,绑定后,可以进行二次确认', value: '', actions: { title: '绑定', callback: () => { this.$message.info('This is a normal message') } } }
      this.data.push(encryptedPhone)
      this.data.push(encryptedProblem)
      this.data.push(encryptedEmail)
      this.data.push(encryptedMfa)
    },
    computed: {
      ...mapGetters(['userInfo'])
    },
    methods: {
    }
  }
}
</script>
<style scoped>

+ 115 - 0
_web/src/views/system/account/settings/securityItem/updPwd.vue

@ -0,0 +1,115 @@
<template>
  <a-modal
    title="修改密码"
    :visible="visible_updPwd"
    :confirm-loading="confirmLoading"
    @ok="handleOkUpdPwd"
    @cancel="handleCancel"
  >
    <a-form :form="formUpdPwd">
      <a-form-item
        label="原密码"
        :labelCol="labelCol"
        :wrapperCol="wrapperCol"
        has-feedback
      >
        <a-input placeholder="请输入原密码" type="password" v-decorator="['password', {rules: [{required: true, message: '请输入原密码!'}]}]" />
      </a-form-item>
      <a-form-item
        label="新密码"
        :labelCol="labelCol"
        :wrapperCol="wrapperCol"
        has-feedback
      >
        <a-input
          placeholder="请输入新密码"
          type="password"
          v-decorator="['newPassword', {rules: [{required: true, message: '请输入新密码!'},{
            validator: validateToNextPassword,
          },]}]" />
      </a-form-item>
      <a-form-item
        label="重复新密码"
        :labelCol="labelCol"
        :wrapperCol="wrapperCol"
        has-feedback
      >
        <a-input
          placeholder="请再次输入新密码"
          type="password"
          v-decorator="['confirm', {rules: [{required: true, message: '请再次输入新密码!'},
                                            {
                                              validator: compareToFirstPassword,
                                            }]}]" />
      </a-form-item>
    </a-form>
  </a-modal>
</template>
<script>
  import { sysUserUpdatePwd } from '@/api/modular/system/userManage'
  export default {
    data () {
      return {
        labelCol: {
          xs: { span: 24 },
          sm: { span: 5 }
        },
        wrapperCol: {
          xs: { span: 24 },
          sm: { span: 16 }
        },
        confirmLoading: false,
        visible_updPwd: false,
        userId: '',
        formUpdPwd: this.$form.createForm(this)
      }
    },
    methods: {
      open (id) {
        this.userId = id
        this.visible_updPwd = true
      },
      handleOkUpdPwd () {
        const { formUpdPwd: { validateFields } } = this
        validateFields((errors, values) => {
          if (!errors) {
            this.confirmLoading = true
            values.id = this.userId
            sysUserUpdatePwd(values).then((res) => {
              if (res.success) {
                this.$message.success('修改成功')
                this.handleCancel()
              } else {
                this.$message.error('修改失败:' + res.message)
              }
            // eslint-disable-next-line handle-callback-err
            }).finally((err) => {
              this.confirmLoading = false
            })
          }
        })
      },
      handleCancel () {
        this.visible_updPwd = false
      },
      compareToFirstPassword (rule, value, callback) {
        const formUpdPwd = this.formUpdPwd
        if (value && value !== formUpdPwd.getFieldValue('newPassword')) {
          // eslint-disable-next-line standard/no-callback-literal
          callback('请确认两次输入密码的一致性!')
        } else {
          callback()
        }
      },
      validateToNextPassword (rule, value, callback) {
        const formUpdPwd = this.formUpdPwd
        if (value && this.confirmDirty) {
          formUpdPwd.validateFields(['confirm'], { force: true })
        }
        callback()
      }
    }
  }
</script>

+ 1 - 13
_web/src/views/system/dashboard/Workplace.vue

@ -112,13 +112,9 @@
<script>
  import { timeFix } from '@/utils/util'
  import { mapState } from 'vuex'
  import { PageView } from '@/layouts'
  import HeadInfo from '@/components/tools/HeadInfo'
  import { Radar } from '@/components'
  import { getRoleList, getServiceList } from '@/api/manage'
  const DataSet = require('@antv/data-set')
  export default {
@ -190,15 +186,7 @@
    },
    created () {
      this.user = this.userInfo
      this.avatar = this.userInfo.avatar
      getRoleList().then(res => {
        // console.log('workplace -> call getRoleList()', res)
      })
      getServiceList().then(res => {
        // console.log('workplace -> call getServiceList()', res)
      })
      this.avatar = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + this.userInfo.avatar
    },
    mounted () {
      this.getProjects()

+ 3 - 5
_web/src/views/system/timers/index.vue

@ -15,11 +15,9 @@
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :md="!advanced && 8 || 24" :sm="24">
            <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} ">
              <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
              <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
            </span>
          <a-col :md="8" :sm="24">
            <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
            <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
          </a-col>
        </a-row>
      </a-form>

+ 4 - 9
_web/src/views/userLoginReg/Login.vue

@ -139,7 +139,7 @@
<script>
import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha'
import { mapActions } from 'vuex'
import { getSmsCaptcha, twoStepCode } from '@/api/modular/system/loginManage'
import { getSmsCaptcha } from '@/api/modular/system/loginManage'
export default {
  components: {
@ -168,17 +168,10 @@ export default {
    }
  },
  created () {
    twoStepCode({ })
      .then(res => {
        this.requiredTwoStepCaptcha = res.result.stepCode
      })
      .catch(() => {
        this.requiredTwoStepCaptcha = false
      })
    // this.requiredTwoStepCaptcha = true
  },
  methods: {
    ...mapActions(['Login', 'Logout']),
    ...mapActions(['Login', 'Logout', 'dictTypeData']),
    // handler
    handleUsernameOrEmail (rule, value, callback) {
      const { state } = this
@ -278,6 +271,8 @@ export default {
    loginSuccess (res) {
      this.$router.push({ path: '/' })
      this.isLoginError = false
      // 加载字典所有字典到缓存中
      this.dictTypeData().then((res) => { })
    },
    requestFailed (err) {
      this.accountLoginErrMsg = err

+ 9 - 0
guns-base-support/guns-core/src/main/java/cn/stylefeng/guns/core/context/login/LoginContext.java

@ -156,4 +156,13 @@ public interface LoginContext {
     * @date 2020/4/20 16:04
     */
    List<String> getLoginUserRoleIds();
    /**
     * 获取最新的用户信息,用于修改之后前端获取
     *
     * @return 最新的用户信息
     * @author xuyuxiang
     * @date 2020/9/20 15:18
     **/
    SysLoginUser getSysLoginUserUpToDate();
}

+ 14 - 0
guns-base-support/guns-system/src/main/java/cn/stylefeng/guns/sys/modular/auth/context/LoginContextSpringSecurityImpl.java

@ -36,6 +36,8 @@ import cn.stylefeng.guns.core.pojo.login.LoginEmpInfo;
import cn.stylefeng.guns.core.pojo.login.SysLoginUser;
import cn.stylefeng.guns.sys.core.enums.AdminTypeEnum;
import cn.stylefeng.guns.sys.modular.auth.service.AuthService;
import cn.stylefeng.guns.sys.modular.user.entity.SysUser;
import cn.stylefeng.guns.sys.modular.user.service.SysUserService;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@ -56,6 +58,9 @@ public class LoginContextSpringSecurityImpl implements LoginContext {
    @Resource
    private AuthService authService;
    @Resource
    private SysUserService sysUserService;
    private LoginContextSpringSecurityImpl() {
    }
@ -263,6 +268,15 @@ public class LoginContextSpringSecurityImpl implements LoginContext {
        return resultList;
    }
    @Override
    public SysLoginUser getSysLoginUserUpToDate() {
        SysLoginUser sysLoginUser = this.getSysLoginUser();
        Long loginUserId = sysLoginUser.getId();
        SysUser sysUser = sysUserService.getById(loginUserId);
        //构造SysLoginUser
        return authService.genSysLoginUser(sysUser);
    }
    /**
     * 获取当前用户的角色编码集合
     *

+ 1 - 1
guns-base-support/guns-system/src/main/java/cn/stylefeng/guns/sys/modular/auth/controller/SysLoginController.java

@ -89,7 +89,7 @@ public class SysLoginController {
     */
    @GetMapping("/getLoginUser")
    public ResponseData getLoginUser() {
        return new SuccessResponseData(LoginContextHolder.me().getSysLoginUser());
        return new SuccessResponseData(LoginContextHolder.me().getSysLoginUserUpToDate());
    }
}

+ 11 - 0
guns-base-support/guns-system/src/main/java/cn/stylefeng/guns/sys/modular/auth/service/AuthService.java

@ -25,6 +25,7 @@ Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意
package cn.stylefeng.guns.sys.modular.auth.service;
import cn.stylefeng.guns.core.pojo.login.SysLoginUser;
import cn.stylefeng.guns.sys.modular.user.entity.SysUser;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
@ -111,4 +112,14 @@ public interface AuthService {
     * @date 2020/9/3 21:22
     */
    void cacheTenantInfo(String tenantCode);
    /**
     * 根据系统用户构造用户登陆信息
     *
     * @param sysUser 系统用户
     * @return 用户信息
     * @author xuyuxiang
     * @date 2020/9/20 15:21
     **/
    SysLoginUser genSysLoginUser(SysUser sysUser);
}

+ 2 - 1
guns-base-support/guns-system/src/main/java/cn/stylefeng/guns/sys/modular/auth/service/impl/AuthServiceImpl.java

@ -345,7 +345,8 @@ public class AuthServiceImpl implements AuthService, UserDetailsService {
     * @author xuyuxiang
     * @date 2020/3/12 17:32
     */
    private SysLoginUser genSysLoginUser(SysUser sysUser) {
    @Override
    public SysLoginUser genSysLoginUser(SysUser sysUser) {
        SysLoginUser sysLoginUser = new SysLoginUser();
        BeanUtil.copyProperties(sysUser, sysLoginUser);
        LoginUserFactory.fillLoginUserInfo(sysLoginUser);