update 实现修改密码. 并对一些用户填写的格式进行了验证
This commit is contained in:
54
Web/src/pages/system/account/index.vue
Normal file
54
Web/src/pages/system/account/index.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<container>
|
||||
<a-anchor
|
||||
:get-container="()=> $el.parentNode"
|
||||
:offset-top="16"
|
||||
@click.prevent
|
||||
class="yo-account--anchor"
|
||||
>
|
||||
<a-anchor-link
|
||||
:href="`#account-${key}`"
|
||||
:key="key"
|
||||
:title="nav.title"
|
||||
v-for="(nav, key) in navs"
|
||||
/>
|
||||
</a-anchor>
|
||||
<br />
|
||||
<section :id="`account-${key}`" :key="key" v-for="(nav, key) in navs">
|
||||
<component :is="nav.component" v-if="nav.component" />
|
||||
</section>
|
||||
</container>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@import (reference) '~@/assets/style/extend.less';
|
||||
.yo-account--anchor {
|
||||
position: absolute;
|
||||
|
||||
width: 200px;
|
||||
/deep/.ant-anchor-wrapper {
|
||||
background-color: transparent;
|
||||
}
|
||||
/deep/.ant-anchor-ink {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
navs: {
|
||||
0: {
|
||||
title: '我的信息',
|
||||
component: () => import('./setting/info'),
|
||||
},
|
||||
1: {
|
||||
title: '安全设置',
|
||||
component: () => import('./setting/safety'),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
255
Web/src/pages/system/account/setting/info.vue
Normal file
255
Web/src/pages/system/account/setting/info.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<container mode="container-xxs">
|
||||
<a-form-model class="yo-form">
|
||||
<h4 class="h4">我的信息</h4>
|
||||
<div class="yo-avatar-info">
|
||||
<yo-image :id="form.avatar" :size="128" icon="user" type="avatar" />
|
||||
<div @click="avatar.cropper = true" class="yo-avatar-info--cover">
|
||||
<a-icon type="cloud-upload-o" />
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<a-card :key="avatar.key" v-if="avatar.cropper">
|
||||
<a-alert type="error">
|
||||
<template slot="message">上传完成时无法被销毁</template>
|
||||
</a-alert>
|
||||
<br />
|
||||
<a-row :gutter="16" align="middle" type="flex">
|
||||
<a-col :span="12">
|
||||
<div class="yo-avatar-cropper">
|
||||
<vue-cropper
|
||||
:auto-crop-height="avatar.autoWidth"
|
||||
:auto-crop-width="avatar.autoHeight"
|
||||
:img="avatar.img"
|
||||
@real-time="(data) => avatar.preview = data"
|
||||
auto-crop
|
||||
fixed-box
|
||||
info
|
||||
ref="cropper"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="yo-avatar-preview" v-html="avatar.preview.html"></div>
|
||||
</a-col>
|
||||
<a-col :span="12" class="mt-md text-center">
|
||||
<a-upload
|
||||
:before-upload="onBeforeUpload"
|
||||
:show-upload-list="false"
|
||||
class="mr-xs"
|
||||
name="file"
|
||||
>
|
||||
<a-button icon="picture" type="primary">选择图片</a-button>
|
||||
</a-upload>
|
||||
<a-button-group>
|
||||
<a-button @click="$refs.cropper.changeScale(1)" icon="zoom-in" />
|
||||
<a-button @click="$refs.cropper.changeScale(-1)" icon="zoom-out" />
|
||||
<a-button @click="$refs.cropper.rotateLeft()" icon="undo" />
|
||||
<a-button @click="$refs.cropper.rotateRight()" icon="redo" />
|
||||
</a-button-group>
|
||||
</a-col>
|
||||
<a-col :span="12" class="mt-md text-center">
|
||||
<a-button
|
||||
:loading="avatar.uploading"
|
||||
@click="onAvatarOk"
|
||||
class="mr-xs"
|
||||
icon="check"
|
||||
type="primary"
|
||||
>确认</a-button>
|
||||
<a-button @click="avatar.cropper = false" icon="close">取消</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<div class="yo-form-group yo-form--short">
|
||||
<a-form-model-item label="昵称">
|
||||
<a-input placeholder="请输入昵称" v-model="form.nickName" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="用户名">
|
||||
<span>{{ form.name }}</span>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="生日">
|
||||
<a-date-picker
|
||||
@change="(date) => form.birthday = date ? $moment(date).format('YYYY-MM-DD') : null"
|
||||
class="w-100-p"
|
||||
placeholder="请选择生日"
|
||||
v-model="form.birthday"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="性别" prop="sex">
|
||||
<a-radio-group v-model="form.sex">
|
||||
<a-radio-button :value="0">
|
||||
<a-icon type="stop" />
|
||||
<span>保密</span>
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="1">
|
||||
<a-icon :style="{ color: '#1890ff' }" type="man" />
|
||||
<span>男</span>
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="2">
|
||||
<a-icon :style="{ color: '#eb2f96' }" type="woman" />
|
||||
<span>女</span>
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="电话" prop="tel">
|
||||
<a-input placeholder="请输入电话" v-model="form.tel" />
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
|
||||
<a-button :loading="saving" @click="onSaveInfo" block>更新个人信息</a-button>
|
||||
</a-form-model>
|
||||
<br />
|
||||
</container>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@import (reference) '~@/assets/style/extend.less';
|
||||
.yo-avatar-info {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 128px;
|
||||
margin: 0 auto;
|
||||
|
||||
border-radius: 50%;
|
||||
&--cover {
|
||||
font-size: @font-size-lg * 2;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
transition: @animation-duration-slow;
|
||||
|
||||
opacity: 0;
|
||||
color: @white;
|
||||
background-color: fade(@black, 50%);
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.yo-avatar-cropper {
|
||||
min-width: 240px;
|
||||
height: 240px;
|
||||
|
||||
widows: 100%;
|
||||
}
|
||||
.yo-avatar-preview {
|
||||
overflow: hidden;
|
||||
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 0 auto;
|
||||
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import { BlobToFile } from '@/util/file';
|
||||
import { setGlobal } from '@/util/global';
|
||||
|
||||
import { VueCropper } from 'vue-cropper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueCropper,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
|
||||
avatar: {
|
||||
key: Date.now(),
|
||||
cropper: false,
|
||||
img: '',
|
||||
autoWidth: 200,
|
||||
autoHeight: 200,
|
||||
|
||||
uploading: false,
|
||||
|
||||
preview: {},
|
||||
file: null,
|
||||
},
|
||||
|
||||
saving: false,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
'avatar.cropper'(value) {
|
||||
if (value) {
|
||||
this.avatar.key = Date.now();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.form = this.$_.cloneDeep(this.$root.global.info);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onRefreshInfo() {
|
||||
return this.$api.getLoginUser().then(async ({ data }) => {
|
||||
// 去除应用和菜单信息,存储基本信息
|
||||
const info = this.$_.cloneDeep(data);
|
||||
delete info.apps;
|
||||
delete info.menus;
|
||||
setGlobal(info);
|
||||
|
||||
this.form = this.$_.cloneDeep(this.$root.global.info);
|
||||
});
|
||||
},
|
||||
|
||||
onBeforeUpload(file) {
|
||||
this.avatar.file = file;
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
this.avatar.img = reader.result;
|
||||
};
|
||||
return false;
|
||||
},
|
||||
|
||||
onAvatarOk() {
|
||||
this.avatar.uploading = true;
|
||||
this.$refs.cropper.getCropBlob(async (data) => {
|
||||
try {
|
||||
const file = BlobToFile(data, this.avatar.file.name, this.avatar.file.type);
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
const { data: fileId } = await this.$api.sysFileInfoUpload(fd);
|
||||
this.form.avatar = fileId;
|
||||
|
||||
this.avatar.cropper = false;
|
||||
} finally {
|
||||
this.avatar.uploading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async onSaveInfo() {
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$api.sysUserUpdateInfo(this.form);
|
||||
await this.onRefreshInfo();
|
||||
this.$message.success('更新个人信息成功');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
143
Web/src/pages/system/account/setting/safety/index.vue
Normal file
143
Web/src/pages/system/account/setting/safety/index.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<container mode="container-xxs">
|
||||
<div class="yo-form">
|
||||
<h4 class="h4">安全设置</h4>
|
||||
<a-progress
|
||||
:percent="15"
|
||||
:stroke-color="{
|
||||
from: '#108ee9',
|
||||
to: '#87d068',
|
||||
}"
|
||||
:stroke-width="15"
|
||||
class="mb-md"
|
||||
status="active"
|
||||
stroke-linecap="square"
|
||||
>
|
||||
<span slot="format">
|
||||
帐号安全级别:
|
||||
<span v-html="safetyLevel"></span>
|
||||
</span>
|
||||
</a-progress>
|
||||
<a-list :data-source="data" bordered item-layout="vertical">
|
||||
<a-list-item slot="renderItem" slot-scope="item">
|
||||
<template v-if="item.done">
|
||||
<span class="text-success" slot="actions">
|
||||
<a-icon class="mr-xxs" type="check-circle" />已设置
|
||||
</span>
|
||||
<a @click="item.action" slot="actions">修改</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-warning" slot="actions">
|
||||
<a-icon class="mr-xxs" type="exclamation-circle" />未设置
|
||||
</span>
|
||||
<a @click="item.action" slot="actions">设置</a>
|
||||
</template>
|
||||
<a-list-item-meta :description="item.description" :title="item.title" />
|
||||
<span>{{ item.content }}</span>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<yo-modal-form
|
||||
:action="$api.sysUserUpdatePwd"
|
||||
@ok="onSetPasswordSuccess"
|
||||
ref="password-form"
|
||||
success-message="密码修改成功"
|
||||
title="修改密码"
|
||||
>
|
||||
<password />
|
||||
</yo-modal-form>
|
||||
</container>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@import (reference) '~@/assets/style/app.less';
|
||||
.ant-progress {
|
||||
width: 400px;
|
||||
/deep/.ant-progress-inner {
|
||||
border-radius: @border-radius-base;
|
||||
background-color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import { doLogout } from '@/common/login';
|
||||
|
||||
import Password from './password';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Password,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
safetyLevel() {
|
||||
// 计算帐号安全级别
|
||||
return '<span class="text-error">弱</span>';
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
const info = this.$root.global.info;
|
||||
|
||||
// 登录密码
|
||||
this.data.push({
|
||||
title: '登录密码',
|
||||
description:
|
||||
'安全性高的密码可以使帐号更安全。建议您定期更换密码,设置一个包含字母,符号或数字中至少两项且长度超过6位的密码。',
|
||||
content: '当前密码强度:弱(不保存密码明文,需要在数据库以字段形式存储)',
|
||||
done: true,
|
||||
action: () => {
|
||||
this.$refs['password-form'].onOpen({});
|
||||
},
|
||||
});
|
||||
|
||||
// 手机绑定
|
||||
this.data.push({
|
||||
title: '手机绑定(发送验证码到手机,未实现)',
|
||||
description: (
|
||||
<div>
|
||||
手机号可以直接用于登录、找回密码等。
|
||||
{info.phone && (
|
||||
<span>
|
||||
您已绑定了手机<b>{info.phone}</b>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
done: !!info.phone,
|
||||
action: () => {},
|
||||
});
|
||||
|
||||
// 邮箱绑定
|
||||
this.data.push({
|
||||
title: '安全邮箱(发送验证码到邮箱,未实现)',
|
||||
description: (
|
||||
<div>
|
||||
安全邮箱可以直接用于登录、找回密码等。
|
||||
{info.email && (
|
||||
<span>
|
||||
您已绑定了邮箱<b>{info.email}</b>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
done: !!info.email,
|
||||
action: () => {},
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSetPasswordSuccess() {
|
||||
doLogout();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
126
Web/src/pages/system/account/setting/safety/password.vue
Normal file
126
Web/src/pages/system/account/setting/safety/password.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<!--
|
||||
普通编辑窗体
|
||||
v 1.2
|
||||
2021-04-30
|
||||
Lufthafen
|
||||
-->
|
||||
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
|
||||
<a-spin :spinning="loading">
|
||||
<a-icon slot="indicator" spin type="loading" />
|
||||
<div class="yo-form-group">
|
||||
<!-- 表单控件 -->
|
||||
<!-- ... -->
|
||||
<a-form-model-item label="旧密码" prop="password">
|
||||
<a-input-password placeholder="请输入旧密码" v-model="form.password" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="新密码" prop="newPassword">
|
||||
<a-input-password placeholder="请输入新密码" v-model="form.newPassword" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="确认新密码" prop="confirm">
|
||||
<a-input-password placeholder="请确认新密码" v-model="form.confirm" />
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-form-model>
|
||||
</template>
|
||||
<script>
|
||||
/* 表单内容默认值 */
|
||||
const defaultForm = {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
/** 表单数据 */
|
||||
form: {},
|
||||
/** 验证格式 */
|
||||
rules: {
|
||||
/* ... */
|
||||
password: [{ required: true, message: '请输入旧密码' }],
|
||||
newPassword: [{ required: true, message: '请输入新密码' }],
|
||||
confirm: [{ required: true, message: '请确认新密码' }],
|
||||
},
|
||||
|
||||
/** 加载异步数据状态 */
|
||||
loading: false,
|
||||
|
||||
/** 其他成员属性 */
|
||||
/* ... */
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 必要的方法
|
||||
* 在打开编辑页时允许填充数据
|
||||
*/
|
||||
onFillData(params) {
|
||||
/** 将默认数据覆盖到form */
|
||||
this.form = this.$_.cloneDeep({
|
||||
...defaultForm,
|
||||
...params.record,
|
||||
/** 在此处添加其他默认数据转换 */
|
||||
/* ... */
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 必要方法
|
||||
* 验证表单并获取表单数据
|
||||
*/
|
||||
onGetData() {
|
||||
return new Promise((reslove, reject) => {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
const record = this.$_.cloneDeep(this.form);
|
||||
|
||||
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
|
||||
/* ... */
|
||||
|
||||
reslove(record);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 必要的方法
|
||||
* 在外部窗口进行保存时调用表单验证
|
||||
*/
|
||||
onValidate(callback) {
|
||||
this.$refs.form.validate(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 必要的方法
|
||||
* 在外部窗口关闭或重置时对表单验证进行初始化
|
||||
*/
|
||||
onResetFields() {
|
||||
setTimeout(() => {
|
||||
this.$refs.form.resetFields();
|
||||
|
||||
/** 在这里可以初始化当前组件中其他属性 */
|
||||
/* ... */
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 必要方法
|
||||
* 加载当前表单中所需要的异步数据
|
||||
*/
|
||||
async onInit(params) {
|
||||
this.loading = true;
|
||||
/** 可以在这里await获取一些异步数据 */
|
||||
/* ... */
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
/** 当前组件的其他方法 */
|
||||
/* ... */
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -79,6 +79,7 @@
|
||||
:get-container="()=> $el.parentNode"
|
||||
:offset-top="24"
|
||||
:wrapper-style="{ backgroundColor: 'transparent' }"
|
||||
@click.prevent
|
||||
>
|
||||
<a-anchor-link
|
||||
:href="`#doc-${index}`"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-icon slot="indicator" spin type="loading" />
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="10">
|
||||
<h3>基本信息</h3>
|
||||
<h3 class="h3">基本信息</h3>
|
||||
<div class="yo-form-group">
|
||||
<a-form-model-item label="账号" prop="account">
|
||||
<a-input placeholder="请输入账号" v-model="form.account" />
|
||||
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="14">
|
||||
<h3>员工信息</h3>
|
||||
<h3 class="h3">员工信息</h3>
|
||||
<div class="yo-form-group">
|
||||
<a-form-model-item label="所属组织机构" prop="sysEmpParam.orgId">
|
||||
<a-tree-select
|
||||
@@ -77,7 +77,7 @@
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
<h4>附加信息</h4>
|
||||
<h4 class="h4">附加信息</h4>
|
||||
<a-table
|
||||
:columns="extColumns"
|
||||
:data-source="form.sysEmpParam.extIds"
|
||||
@@ -136,15 +136,27 @@ export default {
|
||||
sysEmpParam: {},
|
||||
},
|
||||
rules: {
|
||||
account: [{ required: true, min: 5, message: '请输入至少五个字符的账号' }],
|
||||
name: [{ required: true, message: '请输入姓名' }],
|
||||
account: [{ required: true, min: 5, message: '请输入至少五个字符的账号', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, min: 5, message: '请输入至少五个字符的密码' },
|
||||
{ validator: validateToNextPassword },
|
||||
{ required: true, min: 5, message: '请输入至少五个字符的密码', trigger: 'blur' },
|
||||
{ validator: validateToNextPassword, trigger: 'blur' },
|
||||
],
|
||||
confirm: [
|
||||
{ required: true, message: '请确认密码', trigger: 'blur' },
|
||||
{ validator: compareToFirstPassword, trigger: 'blur' },
|
||||
],
|
||||
confirm: [{ required: true, message: '请确认密码' }, { validator: compareToFirstPassword }],
|
||||
sex: [{ required: true, message: '请选择性别' }],
|
||||
phone: [{ required: true, message: '请输入手机号' }],
|
||||
phone: [
|
||||
{
|
||||
pattern: /^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|(18[0-9])|(19[8,9])|(166))[0-9]{8}$/,
|
||||
message: '手机号格式不正确',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{ pattern: /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/, message: '邮箱格式不正确', trigger: 'blur' },
|
||||
],
|
||||
'sysEmpParam.orgId': [{ required: true, message: '请选择所属组织机构' }],
|
||||
'sysEmpParam.posIdList': [{ required: true, message: '请选择职位信息' }],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user