完成个人信息设置页面

This commit is contained in:
jmal 2020-03-21 14:38:47 +08:00
parent 892f7df54e
commit aed0856ceb
14 changed files with 515 additions and 246 deletions

View File

@ -8,11 +8,11 @@ export function login(data) {
})
}
export function getInfo(token) {
export function getInfo(params) {
return request({
url: 'user/userInfo',
method: 'get',
params: { token }
params
})
}
@ -22,3 +22,11 @@ export function logout() {
method: 'get'
})
}
export function userUpdate(data) {
return request({
url: 'user/update',
method: 'post',
data
})
}

View File

@ -0,0 +1,190 @@
<template>
<div>
<div class="container">
<!--<img :src="sourceImg" style="width: 50px; height: 50px;">-->
<div class="img-container" v-loading="loading">
<div class="img-region">
<img :src="sourceImg" ref="image" style="max-width: 100%;" alt="">
</div>
<div class="button-region">
<el-button type="primary" @click="sureSava">保存头像</el-button>
<el-button @click="selectImg">重新选择</el-button>
<!--<el-upload-->
<!--action="https://jsonplaceholder.typicode.com/posts/"-->
<!--:auto-upload="false">-->
<!--<el-button ref="selectImg">选择</el-button>-->
<!--</el-upload>-->
<input ref="selectImg" type="file" style="display: none;" accept="image/*" @change="changImg"></input>
</div>
</div>
<el-divider direction="vertical"></el-divider>
<div class="avatar-preview">
<h3>头像预览</h3>
<div class="before big"></div>
<span>大头像100*100</span>
<div class="before small margin-top"></div>
<span>小头像55*55</span>
</div>
</div>
</div>
</template>
<script>
import "@/assets/css/cropper.css"
import Cropper from 'cropperjs'
export default {
name: 'CropperDialog',
props: {
dialogVisible: {
type: Boolean,
default: false
},
src: {
type: String,
default: '~@/assets/img/widget-img.jpeg'
}
},
data () {
return {
loading: true,
cropper: null,
sourceImg: this.src,
fileImg: '',
}
},
mounted() {
this.$nextTick(()=>{
this.initCropper()
this.loading = false
})
},
methods: {
initCropper(){
this.cropper = new Cropper(this.$refs.image, {
responsive: true,
restore: true,
checkCrossOrigin: true,
checkOrientation: true,
modal: true,
guides: true,
center: true,
highlight: true,
autoCrop: true,
movable: true,
rotatable: true,
scalable: true,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: true,
preview: '.before',
aspectRatio: 1,
viewMode: 1,
// dragMode: 'none',
// initialAspectRatio: 1,
// background: false,
// autoCropArea: 0.6,
});
},
selectImg() {
this.$refs.selectImg.click()
},
changImg(e) {
if(e.target.files[0].size/1024/1024 > 2){
this.$message({
message: '选择的图片不能超过2M',
type: 'warning'
});
return
}
this.loading = true
const that = this
let reader = new FileReader();
this.fileImg = e.target.files[0]
reader.readAsDataURL( this.fileImg);
reader.onload = function()
{
that.cropper.replace(reader.result)
that.loading = false
};
},
sureSava(){
let cro = this.cropper.getCroppedCanvas({
maxWidth: 1204 ,
maxHeight: 1024,
fillColor: '#FFF',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high'
})
let dataURL = cro.toDataURL('image/jpeg');
cro.toBlob((blob)=>{
this.$emit('croppedCanvas', dataURL, blob)
})
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.container{
display: flex;
}
.big{
width: 100px;
height: 100px;
overflow: hidden;
}
.small{
width: 55px;
height: 55px;
overflow: hidden;
}
.img-container{
height: 450px;
line-height: 450px;
text-align: center;
width: 712px;
overflow: hidden;
.img-region {
height: 350px;
line-height: 350px;
text-align: center;
width: 623px;
overflow: hidden;
}
.button-region {
height: 100px;
line-height: 100px;
}
}
.afterCropper{
flex: 1;
margin-left: 20px;
border: 1px solid salmon;
text-align: center;
}
.afterCropper img{
width: 150px;
margin-top: 30px;
}
.avatar-preview {
span{
font-size: 12px;
}
.margin-top{
margin-top: 15px;
}
}
/deep/.el-divider--vertical {
height: 450px;
margin: 0 20px;
}
/deep/.el-loading-spinner {
top: 0;
margin-top: 0;
}
</style>

View File

@ -11,9 +11,9 @@
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/userinfo">
<router-link to="/setting">
<el-dropdown-item>
个人信息
<i class="el-icon-setting" />设置
</el-dropdown-item>
</router-link>
<!--<router-link to="/">-->

View File

@ -84,7 +84,7 @@ new Vue({
el: '#app',
router,
store,
render: h => h(App)
render: h => h(App),
})
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {

View File

@ -33,7 +33,6 @@ router.beforeEach(async(to, from, next) => {
try {
// get user info
await store.dispatch('user/getInfo')
next()
} catch (error) {
// remove token and go to login page to re-login

View File

@ -87,13 +87,13 @@ export const constantRoutes = [
},
{
path: '/userinfo',
path: '/setting',
component: Layout,
children: [
{
path: '',
name: 'userInfo',
component: () => import('@/views/userInfo/index'),
name: 'setting',
component: () => import('@/views/setting/index'),
}
]
},

View File

@ -1,5 +1,5 @@
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { getToken, setToken, getConsumerId, setConsumerId, removeToken, removeConsumerId } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
@ -7,7 +7,7 @@ const getDefaultState = () => {
token: getToken(),
name: '',
avatar: '',
userId: ''
userId: getConsumerId(),
}
}
@ -40,6 +40,8 @@ const actions = {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
commit('SET_USERID', data.userId)
setConsumerId(data.userId)
resolve()
}).catch(error => {
reject(error)
@ -50,9 +52,11 @@ const actions = {
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
getInfo({
id: state.userId,
token: state.token
}).then(response => {
const { data } = response
if (!data) {
// reject('Verification failed, please Login again.')
}
@ -62,6 +66,7 @@ const actions = {
commit('SET_NAME', username)
commit('SET_AVATAR', avatar)
commit('SET_USERID', id)
sessionStorage.setItem('store', JSON.stringify(state))
resolve(data)
}).catch(error => {
reject(error)
@ -74,6 +79,7 @@ const actions = {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
removeConsumerId()
resetRouter()
commit('RESET_STATE')
resolve()
@ -87,6 +93,7 @@ const actions = {
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
removeConsumerId()
commit('RESET_STATE')
resolve()
})

45
src/styles/setting.scss Normal file
View File

@ -0,0 +1,45 @@
.img-dialog {
}
.form-item-avatar {
.avatar-value{
position: absolute;
z-index: 1;
}
.avatar-value:hover {
cursor: pointer;
z-index: 0;
}
.avatar-overlay{
position: absolute;
}
.avatar-overlay:hover{
cursor: pointer;
z-index: 1;
background: #2f2f2fa8;
}
}
.form-item-avatar /deep/ .el-form-item__label{
padding: 30px 12px;
}
.dialog-avatar {
/deep/.el-dialog {
min-width: 800px;
margin-top: 5vh!important;
}
}
/deep/.el-input {
width: unset;
}
/deep/.el-textarea {
max-width: 500px;
}
.form-slogan {
width: 100%;
max-width: 500px;
}
.quota-space {
margin-top: 10px;
max-width: 300px;
}

View File

@ -1,6 +1,7 @@
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token'
const TokenKey = 'jmal_token'
const consumerId = 'consumerId'
export function getToken() {
return Cookies.get(TokenKey)
@ -13,3 +14,15 @@ export function setToken(token) {
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function getConsumerId() {
return Cookies.get(consumerId)
}
export function setConsumerId(userId) {
return Cookies.set(consumerId, userId)
}
export function removeConsumerId() {
return Cookies.remove(consumerId)
}

View File

@ -46,6 +46,10 @@ service.interceptors.response.use(
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 0) {
if(res.code === 5) {
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
return
}
if(res.code === -2){
Message({
message: res.message || '服务器开小差了...',

View File

@ -1,47 +1,78 @@
<template>
<div>
<mavon-editor
v-model="content"
ref="md"
@change="change"
style="min-height: 600px"
/>
<button @click="submit">提交</button>
<div class="hello">
<p>预览</p>
<div class="before"></div>
<button style="margin: 30px auto;" type="error" @click="sureSava">裁剪</button>
<div class="container">
<div class="img-container">
<img src="~@/assets/img/back.jpeg" ref="image" alt="">
</div>
<div class="afterCropper">
<img :src="afterImg" alt="">
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import Cropper from 'cropperjs'
export default {
// 注册
components: {
mavonEditor,
},
data() {
name: 'HelloWorld',
data () {
return {
content:'', // 输入的markdown
html:'', // 及时转的html
}
},
methods: {
// 所有操作都会被解析重新渲染
change(value, render){
console.log(value)
// render 为 markdown 解析后的结果[html]
this.html = render;
},
// 提交
submit(){
console.log(this.content);
console.log(this.html);
cropper: null,
afterImg: ''
}
},
mounted() {
}
this.init()
},
methods: {
init(){
this.cropper = new Cropper(this.$refs.image, {
viewMode: 1,
dragMode: 'none',
initialAspectRatio: 1,
aspectRatio: 1,
preview: '.before',
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
})
},
sureSava(){
this.afterImg = this.cropper.getCroppedCanvas({
imageSmoothingQuality: 'high'
}).toDataURL('image/jpeg')
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.container{
display: flex;
}
.before{
width: 100px;
height: 100px;
overflow: hidden;
/* 这个属性可以得到想要的效果 */
}
.img-container{
height: 400px;
overflow: hidden;
}
.afterCropper{
flex: 1;
margin-left: 20px;
border: 1px solid salmon;
text-align: center;
}
.afterCropper img{
width: 150px;
margin-top: 30px;
}
</style>

View File

@ -74,8 +74,8 @@ export default {
}
return {
loginForm: {
username: '',
password: ''
username: 'jmal',
password: '123456'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
@ -116,7 +116,6 @@ export default {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})

167
src/views/setting/index.vue Normal file
View File

@ -0,0 +1,167 @@
<template>
<div class="app-container">
<el-tabs type="border-card" :value="tabsDefault" @tab-click="tabClick">
<el-tab-pane name="1">
<span slot="label"><i class="el-icon-user"></i> 个人信息</span>
<!--<div class="img-dialog" :v-show="dialogVisible">-->
<!--<img id="image" :src="userInfo.avatar"/>-->
<!--</div>-->
<el-dialog class="dialog-avatar" title="修改头像" :visible.sync="dialogVisible">
<cropper-dialog :src="userInfo.avatar" @croppedCanvas="saveAvatar"></cropper-dialog>
</el-dialog>
<el-form ref="form" :model="form" label-width="120px">
<el-form-item label="头像" class="form-item-avatar">
<el-avatar class="avatar-value" shape="circle" :size="100" fit="fit" :src="userInfo.avatar" ></el-avatar>
<el-avatar class="avatar-overlay" shape="circle" :size="100" @click.native="dialogVisible = true"> 修改头像 </el-avatar>
</el-form-item>
<el-form-item label="username">
<el-input readonly="readonly" v-model="form.username" />
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.showName" />
</el-form-item>
<el-form-item label="标语">
<el-input class="form-slogan" v-model="form.slogan" />
</el-form-item>
<el-form-item label="个人简介">
<el-input type="textarea" v-model="form.introduction"></el-input>
</el-form-item>
<el-form-item label="使用情况">
<el-progress class="quota-space" :percentage="percentage" :format="progressFormat" :color="customColors"></el-progress>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane name="3" label="消息中心">
</el-tab-pane>
<el-tab-pane name="4" label="角色管理">
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { getInfo, userUpdate} from '@/api/user'
import { formatSize } from '@/utils/number'
import CropperDialog from '@/components/Cropper/dialog'
export default {
components: {
CropperDialog
},
data() {
return {
tabsDefault: '1',
userInfo: {},
dialogVisible:false,
cropper:'',
percentage: 0,
customColors: [
{color: '#f56c6c', percentage: 100},
{color: '#e6a23c', percentage: 80},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 40},
{color: '#6f7ad3', percentage: 20}
],
form: {
avatar: '',
showName: '',
}
}
},
computed: {
},
mounted() {
if(this.$route.query.tab){
this.tabsDefault = this.$route.query.tab
}
this.getInfo()
},
methods: {
getInfo(){
getInfo({
id: this.$store.state.user.userId,
takeUpSpace: true,
}).then(res => {
console.log(res.data)
this.userInfo = res.data
this.$nextTick(() => {
this.form = this.userInfo
})
})
},
tabClick(tab) {
this.$router.push({path: this.$route.path, query: {tab: tab.name}})
},
onSubmit() {
this.userUpdate(this.setFormData())
// this.$message('submit!')
},
onCancel() {
this.$message({
message: 'cancel!',
type: 'warning'
})
},
progressFormat() {
const space = this.userInfo.takeUpSpace/1024/1024/1024
const quota = this.userInfo.quota
if(space && quota > 0){
this.percentage = (space/quota * 100).toFixed(1)
return `${this.percentage}% (${formatSize(this.userInfo.takeUpSpace)}/${quota}GB)`;
// return `${this.percentage}%`;
}
},
setFormData() {
let data = new FormData();
for (let formKey in this.form) {
if(this.form.hasOwnProperty(formKey)){
if(formKey !== 'avatar'){
console.log(formKey,this.form[formKey])
data.append(formKey,this.form[formKey])
}
}
}
return data
},
// 保存头像
saveAvatar(dataURL) {
this.dialogVisible = false
this.userInfo.avatar = dataURL
let data = new FormData();
data.append('id', this.$store.state.user.userId)
data.append('avatar', dataURL)
this.userUpdate(data,dataURL)
},
userUpdate(data, dataURL){
userUpdate(data).then(() => {
this.$message({
message: '保存成功!',
type: 'success',
duration: 1000
})
if(dataURL){
this.$store.state.user.avatar = dataURL
}
}).catch(() => {
this.$message({
message: '保存失败!',
type: 'error'
})
})
}
}
}
</script>
<!--<style src="@styles/cropper.css"></style>-->
<style lang="scss" scoped>
@import "src/styles/setting";
</style>

View File

@ -1,194 +0,0 @@
<template>
<div class="app-container">
<div class="img-dialog" :v-show="dialogVisible">
<img id="image" :src="form.avater"/>
</div>
<el-form ref="form" :model="form" label-width="120px">
<el-form-item label="头像" class="form-item-avatar">
<el-upload
class="avatar-uploader"
:action="UploadAvatarURL"
:show-file-list="false"
:on-change="addAvatar"
:auto-upload="false"
:before-upload="beforeAvatarUpload">
<img id="imageAvatar" v-if="form.avater" :src="form.avater" class="avatar">
<!-- <el-image v-if="form.avater" class="avatar" :src="form.avater" :fit="cover"></el-image>-->
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;" />
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;" />
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio label="Sponsor" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button @click="onCancel">Cancel</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import "@/assets/css/cropper.css"
import Cropper from 'cropperjs'
import api from '@/api/upload-api'
export default {
data() {
return {
dialogVisible:false,
cropper:'',
UploadAvatarURL: api.simpleUploadAvatarURL,
form: {
avater: '',
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
}
},
mounted() {
let _this = this;
let image = document.getElementById('image');
this.cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 1,
background:false,
zoomable:false,
ready: function () {
}
});
},
methods: {
onSubmit() {
this.$message('submit!')
},
onCancel() {
this.$message({
message: 'cancel!',
type: 'warning'
})
},
addAvatar(file, fileList) {
// const isLt2M = file.size / 1024 / 1024 < 2;
// if (!isLt2M) {
// this.$message.error('上传头像图片大小不能超过 2MB!');
// return
// }
let reader = new FileReader();
const _this = this
reader.onload = function (e) {
_this.form.avater = e.target.result
_this.dialogVisible = true
_this.cropper.replace(e.target.result);
};
reader.readAsDataURL(file.raw)
},
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isLt2M;
},
crop () {
this.panel = false;
var croppedCanvas;
var roundedCanvas;
if (!this.croppable) {
return;
}
// Crop
croppedCanvas = this.cropper.getCroppedCanvas();
console.log(this.cropper)
// Round
roundedCanvas = this.getRoundedCanvas(croppedCanvas);
this.form.avater = roundedCanvas.toDataURL();
},
}
}
</script>
<!--<style src="@styles/cropper.css"></style>-->
<style scoped>
.line{
text-align: center;
}
.img-dialog {
}
/deep/ .avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
width: 100px;
height: 100px;
line-height: 100px;
border-radius: 50%;
cursor: pointer;
position: relative;
overflow: hidden;
}
/deep/.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
/deep/.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
/deep/.avatar {
width: 100px;
height: 100px;
display: block;
}
.form-item-avatar {
line-height: 100px;
}
</style>