提交 3a37af3f authored 作者: 王鹏飞's avatar 王鹏飞

feat: 新增个人信息模块

上级 f369ccc8
......@@ -8,23 +8,28 @@
"version": "0.0.0",
"dependencies": {
"axios": "^0.21.1",
"blueimp-md5": "^2.18.0",
"clipboard": "^2.0.8",
"element-ui": "^2.15.5",
"qrcode.vue": "^1.7.0",
"query-string": "^7.0.1",
"vant": "^2.12.26",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},
"devDependencies": {
"@rollup/plugin-eslint": "^8.0.1",
"ali-oss": "^6.16.0",
"babel-plugin-import": "^1.13.3",
"chalk": "^4.1.2",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-vue": "^7.16.0",
"sass": "^1.38.0",
"vite": "^2.5.0",
......@@ -1656,6 +1661,16 @@
"babel-runtime": "^6.22.0"
}
},
"node_modules/babel-plugin-import": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.3.tgz",
"integrity": "sha512-1qCWdljJOrDRH/ybaCZuDgySii4yYrtQ8OJQwrcDqdt0y67N30ng3X3nABg6j7gR7qUJgcMa9OMhc4AGViDwWw==",
"dev": true,
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/runtime": "^7.0.0"
}
},
"node_modules/babel-plugin-syntax-async-functions": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
......@@ -2108,6 +2123,11 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"node_modules/blueimp-md5": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q=="
},
"node_modules/bowser": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
......@@ -2252,6 +2272,16 @@
"fsevents": "~2.3.2"
}
},
"node_modules/clipboard": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
......@@ -2491,6 +2521,11 @@
"node": ">= 6"
}
},
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
......@@ -3008,15 +3043,12 @@
}
},
"node_modules/eslint-plugin-promise": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz",
"integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz",
"integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==",
"dev": true,
"engines": {
"node": "^10.12.0 || >=12.0.0"
},
"peerDependencies": {
"eslint": "^7.0.0"
"node": ">=6"
}
},
"node_modules/eslint-plugin-vue": {
......@@ -3548,6 +3580,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"dependencies": {
"delegate": "^3.1.2"
}
},
"node_modules/graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
......@@ -4963,6 +5003,14 @@
"node": ">=6"
}
},
"node_modules/qrcode.vue": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz",
"integrity": "sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g==",
"peerDependencies": {
"vue": "^2.0.0"
}
},
"node_modules/qs": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
......@@ -5254,6 +5302,11 @@
"get-ready": "~1.0.0"
}
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
},
"node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
......@@ -5397,6 +5450,11 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/sortablejs": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
......@@ -5686,6 +5744,11 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"node_modules/to-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
......@@ -6129,6 +6192,14 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"node_modules/vuedraggable": {
"version": "2.24.3",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
"integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
"dependencies": {
"sortablejs": "1.10.2"
}
},
"node_modules/vuex": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
......@@ -7530,6 +7601,16 @@
"babel-runtime": "^6.22.0"
}
},
"babel-plugin-import": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.3.tgz",
"integrity": "sha512-1qCWdljJOrDRH/ybaCZuDgySii4yYrtQ8OJQwrcDqdt0y67N30ng3X3nABg6j7gR7qUJgcMa9OMhc4AGViDwWw==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/runtime": "^7.0.0"
}
},
"babel-plugin-syntax-async-functions": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
......@@ -7970,6 +8051,11 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"blueimp-md5": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q=="
},
"bowser": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
......@@ -8074,6 +8160,16 @@
"readdirp": "~3.6.0"
}
},
"clipboard": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
......@@ -8254,6 +8350,11 @@
"esprima": "^4.0.0"
}
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
......@@ -8672,11 +8773,10 @@
}
},
"eslint-plugin-promise": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz",
"integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==",
"dev": true,
"requires": {}
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz",
"integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==",
"dev": true
},
"eslint-plugin-vue": {
"version": "7.16.0",
......@@ -9063,6 +9163,14 @@
"type-fest": "^0.20.2"
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"requires": {
"delegate": "^3.1.2"
}
},
"graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
......@@ -10150,6 +10258,12 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"qrcode.vue": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz",
"integrity": "sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g==",
"requires": {}
},
"qs": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
......@@ -10376,6 +10490,11 @@
"get-ready": "~1.0.0"
}
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
......@@ -10478,6 +10597,11 @@
}
}
},
"sortablejs": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
......@@ -10714,6 +10838,11 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"to-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
......@@ -11065,6 +11194,14 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vuedraggable": {
"version": "2.24.3",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
"integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
"requires": {
"sortablejs": "1.10.2"
}
},
"vuex": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
......
......@@ -13,6 +13,7 @@
},
"dependencies": {
"axios": "^0.21.1",
"blueimp-md5": "^2.18.0",
"clipboard": "^2.0.8",
"element-ui": "^2.15.5",
"qrcode.vue": "^1.7.0",
......@@ -20,6 +21,7 @@
"vant": "^2.12.26",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},
"devDependencies": {
......
<template>
<div class="app-card">
<div class="app-card-hd">
<slot name="header">
<h2 class="app-card-hd__title" v-if="title">{{ title }}</h2>
<slot name="header-aside"></slot>
</slot>
</div>
<div class="app-card-bd">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'AppCard',
props: { title: String }
}
</script>
<style>
.app-card {
background-color: #fff;
border-radius: 8px;
border: 1px solid #dadce0;
box-sizing: border-box;
overflow: hidden;
margin-top: 24px;
}
.app-card-hd {
display: flex;
padding: 24px;
}
.app-card-hd__title {
flex: 1;
font-size: 1.375rem;
font-weight: 400;
letter-spacing: 0;
line-height: 1.75rem;
word-break: break-word;
word-wrap: break-word;
color: #202124;
}
</style>
<template>
<header class="app-header">
<div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg" /></router-link>
</div>
<div class="app-header-right">
<el-dropdown>
<div class="avatar">
<img :src="user.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'" />
</div>
<el-dropdown-menu slot="dropdown" style="width: 354px">
<div class="app-header-user">
<div class="app-header-user-avatar">
<img :src="user.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'" />
</div>
<div class="app-header-user-main">
<h3>{{ user.realname || user.nickname }}</h3>
<p>{{ user.email || user.mobile }}</p>
</div>
<div class="app-header-user-buttons">
<router-link to="/settings">
<el-button size="medium" round>个人中心</el-button>
</router-link>
</div>
</div>
<ul>
<li class="app-header-dropwodn-item">
<router-link to="/payment"><i class="el-icon-money"></i><span>支付记录</span></router-link>
</li>
</ul>
<div class="app-header-buttons">
<el-button size="medium" @click="logout">退出</el-button>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
export default {
name: 'AppHeader',
computed: {
user() {
return this.$store.state.user.user
}
},
methods: {
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
})
}
}
}
</script>
<style lang="scss">
.app-header {
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
background-color: #fff;
color: #fff;
// box-shadow: 0px 1px 2px 0px rgb(60 64 67 / 30%), 0px 2px 6px 2px rgb(60 64 67 / 15%);
.logo {
width: 120px;
}
}
.app-header-right {
display: flex;
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
overflow: hidden;
padding: 4px;
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
overflow: hidden;
}
&:hover {
background-color: rgba(60, 64, 67, 0.08);
}
}
}
.app-header-user {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 16px;
}
.app-header-user-avatar {
margin-bottom: 6px;
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.app-header-user-main {
h3 {
color: #202124;
font: 500 16px/22px Helvetica, Arial, sans-serif;
letter-spacing: 0.29px;
margin: 0;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
}
p {
color: #5f6368;
font: 400 14px/19px Helvetica, Arial, sans-serif;
letter-spacing: normal;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
}
}
.app-header-user-buttons {
padding: 16px 0;
}
.app-header-dropwodn-item {
border-bottom: 1px solid #e8eaed;
border-top: 1px solid #e8eaed;
a {
display: flex;
align-items: center;
background-color: #ffffff;
color: #3c4043;
font: 500 14px/16px Helvetica, Arial, sans-serif;
letter-spacing: 0.25px;
padding: 14px 40px;
box-sizing: border-box;
&:hover {
background-color: #ebf1ff;
}
}
span {
padding-left: 16px;
white-space: normal;
}
}
.app-header-buttons {
text-align: center;
padding-top: 14px;
}
</style>
<template>
<div class="app-layout">
<app-header></app-header>
<div class="app-layout-container">
<app-main></app-main>
</div>
</div>
</template>
<script>
import AppHeader from './Header.vue'
import AppMain from './Main.vue'
export default {
name: 'AppLayout',
components: { AppHeader, AppMain }
}
</script>
<style>
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
/* background-color: #f0f2f5; */
}
.app-layout-container {
flex: 1;
display: flex;
}
</style>
<template>
<section class="app-main">
<div class="app-main-inner">
<div class="app-main-container">
<router-view></router-view>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'AppMain'
}
</script>
<style>
.app-main {
position: relative;
flex: 1;
padding: 20px;
}
.app-main-inner {
margin: 0 auto;
}
.app-main-container::after {
content: '';
display: table;
clear: both;
}
.el-form--label-top .el-form-item__label {
padding-bottom: 0;
}
</style>
<template>
<div class="app-upload">
<draggable
v-model="fileList"
group="people"
class="file-list el-upload-list--picture-card"
@end="onEnd"
v-if="showFileList"
>
<div class="file-item" v-for="(file, index) in fileList" :key="index">
<!-- <el-image style="width: 100%; height: 100%" :src="file.url" :preview-src-list="fileList.map(item => item.url)"> -->
<img :src="file.url" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview" @click="handlePreview(file)">
<i class="el-icon-zoom-in"></i>
</span>
<span v-if="!$attrs.disabled" class="el-upload-list__item-delete" @click="handleRemove(index)">
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</draggable>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
type="drag"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:show-file-list="false"
:on-exceed="handleExceed"
:data="data"
v-bind="$attrs"
v-on="$listeners"
style="display: inline"
>
<slot>
<i class="el-icon-plus"></i>
</slot>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="previewFile.url" />
</el-dialog>
</div>
</template>
<script>
import { getSignature } from '@/api/base'
import md5 from 'blueimp-md5'
import draggable from 'vuedraggable'
export default {
name: 'AppUpload',
components: { draggable },
props: {
value: { type: [String, Array] },
prefix: { type: String, default: 'upload/accounts/' },
showFileList: { type: Boolean, default: true }
},
data() {
return {
data: {}, // 请求参数
fileList: [], // 文件列表
dialogVisible: false,
previewFile: {},
message: null
}
},
watch: {
value: {
immediate: true,
handler(value) {
if (!value) {
return
}
let fileList = []
if (this.isMultiple) {
fileList = value.map(item => {
return { name: item.name || item, url: item.url || item }
})
} else {
fileList.push({ name: '附件', url: value })
}
this.fileList = [...fileList]
}
}
},
computed: {
isMultiple() {
return Array.isArray(this.value)
}
},
methods: {
beforeUpload(file) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
this.message && this.message.close()
this.message = this.$message({ type: 'error', message: '文件超出个数限制' })
return false
}
const fileName = file.name
const key = this.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
return new Promise((resolve, reject) => {
getSignature()
.then(response => {
const { accessid, policy, signature, host } = response
this.data = { key, OSSAccessKeyId: accessid, policy, signature, success_action_status: '200' }
file.url = `${host}/${key}`
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
let value = null
if (this.isMultiple) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
return
}
this.fileList.push({ name: file.name, url: file.raw.url })
value = this.fileList
} else {
this.fileList = [file.raw.url]
value = file.raw.url
}
this.$emit('input', value)
this.dispatch('ElFormItem', 'el.form.change', value)
},
// 删除
handleRemove(index) {
this.fileList.splice(index, 1)
this.$emit('input', this.fileList)
this.dispatch('ElFormItem', 'el.form.change', this.fileList)
},
// 预览
handlePreview(file) {
this.previewFile = file
this.dialogVisible = true
},
handleExceed() {
this.$message({ type: 'error', message: '文件超出个数限制' })
},
onEnd() {
this.$emit('input', this.fileList)
},
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root
let name = parent.$options.componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.componentName
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
}
}
}
</script>
<style lang="scss">
.el-upload-list--picture-card .el-upload-list__item-thumbnail {
object-fit: cover;
}
.el-upload-list__item {
transition: none !important;
}
.file-list {
margin: 0;
display: inline;
vertical-align: top;
}
.file-item {
position: relative;
overflow: hidden;
background-color: #fff;
border: 1px solid #c0ccda;
border-radius: 6px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 148px;
height: 148px;
margin: 0 8px 8px 0;
display: inline-block;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>
<template>
<app-upload class="avatar-uploader" accept="image/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadImage',
components: { AppUpload },
data() {
return {}
},
computed: {
imageUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
<template>
<app-upload class="video-uploader" accept="video/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="videoUrl" :src="`${videoUrl}?x-oss-process=video/snapshot,t_10,f_jpg,w_0,h_0,m_fast`" class="avatar" />
<i v-else class="el-icon-plus video-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadVideo',
components: { AppUpload },
data() {
return {}
},
computed: {
videoUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.video-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.video-uploader .el-upload:hover {
border-color: #409eff;
}
.video-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
import httpRequest from '@/utils/axios'
// 获取用户信息
export function updateUser(data) {
return httpRequest.post('/api/usercenter/user/update-user', data)
}
// 获取用户信息
export function updatePassword(data) {
return httpRequest.post('/api/usercenter/user/change-pwd-by-cookie', data)
}
<template>
<app-card title="基本信息">
<ul class="app-card-list">
<li class="app-card-item" @click="avatarDialogVisible = true">
<div class="app-card-item-label">照片</div>
<div class="app-card-item-content"></div>
<div class="app-card-item-aside">
<div class="avatar">
<img :src="user.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'" />
<div class="cover">
<i class="el-icon-camera-solid"></i>
</div>
</div>
</div>
</li>
<li class="app-card-item">
<div class="app-card-item-label">账号</div>
<div class="app-card-item-content">{{ user.username }}</div>
<div class="app-card-item-aside"></div>
</li>
<li class="app-card-item" @click="showUpdateInfo">
<div class="app-card-item-label">昵称</div>
<div class="app-card-item-content">{{ user.nickname }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
<li class="app-card-item" @click="showUpdateInfo">
<div class="app-card-item-label">真实姓名</div>
<div class="app-card-item-content">{{ user.realname }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
<li class="app-card-item" @click="showUpdateInfo">
<div class="app-card-item-label">生日</div>
<div class="app-card-item-content">{{ user.birthday }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
<li class="app-card-item" @click="showUpdateInfo">
<div class="app-card-item-label">性别</div>
<div class="app-card-item-content">{{ genderText(user.gender) }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
</ul>
<!-- 头像修改 -->
<el-dialog
class="avatar-dialog"
:visible.sync="avatarDialogVisible"
:close-on-click-modal="false"
@close="resetFormData"
>
<div class="avatar-dialog-body">
<upload-image v-model="form.avatar"></upload-image>
<h2>个人资料照片</h2>
<p>照片可帮助他人认出您,并可让您了解自己已登录帐号</p>
</div>
<div slot="footer" class="avatar-dialog-footer">
<el-button size="medium" icon="el-icon-delete" @click="handleUpdateAvatar('')">移除</el-button>
<el-button type="primary" size="medium" icon="el-icon-edit" @click="handleUpdateAvatar(form.avatar)"
>更改</el-button
>
</div>
</el-dialog>
<!-- 基本信息修改 -->
<el-dialog title="基本信息" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="resetFormData">
<el-form ref="form" :model="form" :rules="rules" :hide-required-asterisk="true" label-position="top">
<el-form-item label="昵称" prop="nickname">
<el-input v-model="form.nickname" clearable></el-input>
</el-form-item>
<el-form-item label="真实姓名" prop="realname">
<el-input v-model="form.realname" clearable></el-input>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker v-model="form.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期">
</el-date-picker>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
<el-radio :label="0">不便透露</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleCancelUpdateInfo">取消</el-button>
<el-button type="primary" size="medium" @click="handlePrimaryUpdateInfo">保存</el-button>
</div>
</el-dialog>
</app-card>
</template>
<script>
import AppCard from '@/components/base/AppCard.vue'
import UploadImage from '@/components/upload/UploadImage.vue'
import { updateUser } from '../api.js'
export default {
components: { AppCard, UploadImage },
data() {
return {
form: {},
rules: {
nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
realname: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }]
},
dialogVisible: false,
avatarDialogVisible: false
}
},
computed: {
user() {
return this.$store.state.user.user
}
},
watch: {
user: {
deep: true,
immediate: true,
handler(user) {
this.form = Object.assign({}, user)
}
}
},
methods: {
genderText(value) {
const map = {
0: '不方便透露',
1: '男',
2: '女'
}
return map[value]
},
showUpadeAvatar() {
this.avatarDialogVisible = true
},
handleUpdateAvatar(url) {
this.form.avatar = url
this.updateHandler({ avatar: url })
},
showUpdateInfo() {
this.dialogVisible = true
},
handleCancelUpdateInfo() {
this.dialogVisible = false
},
handlePrimaryUpdateInfo() {
this.$refs.form.validate().then(() => {
const params = {
nickname: this.form.nickname,
real_name: this.form.realname,
birthday: this.form.birthday,
gender: this.form.gender
}
this.updateHandler(params, () => {
this.dialogVisible = false
})
})
},
updateHandler(params, callback) {
return updateUser(params)
.then(resp => {
this.$store.dispatch('getUser')
callback && callback(resp)
})
.catch(resp => this.$message.error(resp.msg))
},
resetFormData() {
this.form = Object.assign({}, this.user)
this.$refs.form && this.$refs.form.resetFields()
}
}
}
</script>
<style lang="scss" scoped>
.app-card-item {
position: relative;
margin-left: 24px;
display: flex;
align-items: center;
padding: 16px 24px 16px 0;
border-top: 1px solid #dadce0;
cursor: pointer;
&:hover {
margin-left: 0;
padding-left: 24px;
background-color: rgba(128, 134, 139, 0.04);
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
border-top: 1px solid #dadce0;
}
}
}
.app-card-item-label {
flex-basis: 156px;
margin-right: 24px;
font-weight: 500;
color: #5f6368;
}
.app-card-item-content {
flex: 1;
}
.avatar {
position: relative;
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cover {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 20px;
background-color: rgba(32, 33, 36, 0.6);
color: #fff;
text-align: center;
}
}
::v-deep .el-dialog {
max-width: 490px;
}
.avatar-dialog-body {
text-align: center;
::v-deep .el-upload {
width: 288px;
height: 288px;
border-radius: 50%;
}
::v-deep .avatar-uploader-icon {
font-size: 40px;
line-height: 288px;
}
h2 {
margin-top: 24px;
line-height: 2rem;
font-size: 1.5rem;
letter-spacing: 0;
font-weight: 400;
}
p {
margin-top: 4px;
line-height: 1.5rem;
font-size: 1rem;
letter-spacing: 0.00625em;
font-weight: 400;
}
}
.avatar-dialog-footer {
display: flex;
::v-deep .el-button {
flex: 1;
}
}
</style>
<template>
<app-card title="联系信息">
<ul class="app-card-list">
<li class="app-card-item" @click="showUpdate">
<div class="app-card-item-label">电子邮件</div>
<div class="app-card-item-content">{{ user.email }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
<li class="app-card-item" @click="showUpdate">
<div class="app-card-item-label">电话</div>
<div class="app-card-item-content">{{ user.mobile }}</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
</ul>
<!-- 联系信息修改 -->
<el-dialog title="联系信息" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="resetFormData">
<el-form ref="form" :model="form" :rules="rules" :hide-required-asterisk="true" label-position="top">
<el-form-item label="电子邮件" prop="email">
<el-input v-model="form.email" clearable></el-input>
</el-form-item>
<el-form-item label="电话" prop="mobile">
<el-input v-model="form.mobile" clearable></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleCancelUpdate">取消</el-button>
<el-button type="primary" size="medium" @click="handlePrimaryUpdate">保存</el-button>
</div>
</el-dialog>
</app-card>
</template>
<script>
import AppCard from '@/components/base/AppCard.vue'
import { updateUser } from '../api.js'
export default {
components: { AppCard },
data() {
return {
form: {},
rules: {
email: [{ required: true, type: 'email', message: '请输入电子邮件', trigger: 'blur' }],
mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: '请输入手机号', trigger: 'blur' }]
},
dialogVisible: false
}
},
computed: {
user() {
return this.$store.state.user.user
}
},
watch: {
user: {
deep: true,
immediate: true,
handler(user) {
this.form = Object.assign({}, user)
}
}
},
methods: {
showUpdate() {
this.dialogVisible = true
},
handleCancelUpdate() {
this.dialogVisible = false
},
handlePrimaryUpdate() {
this.$refs.form.validate().then(() => {
const params = { email: this.form.email, mobile: this.form.mobile }
this.updateHandler(params, () => {
this.dialogVisible = false
})
})
},
updateHandler(params, callback) {
return updateUser(params)
.then(resp => {
this.$store.dispatch('getUser')
callback && callback(resp)
})
.catch(resp => this.$message.error(resp.msg))
},
resetFormData() {
this.form = Object.assign({}, this.user)
this.$refs.form && this.$refs.form.resetFields()
}
}
}
</script>
<style lang="scss" scoped>
.app-card-item {
position: relative;
margin-left: 24px;
display: flex;
align-items: center;
padding: 16px 24px 16px 0;
border-top: 1px solid #dadce0;
cursor: pointer;
&:hover {
margin-left: 0;
padding-left: 24px;
background-color: rgba(128, 134, 139, 0.04);
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
border-top: 1px solid #dadce0;
}
}
}
.app-card-item-label {
flex-basis: 156px;
margin-right: 24px;
font-weight: 500;
color: #5f6368;
}
.app-card-item-content {
flex: 1;
}
::v-deep .el-dialog {
max-width: 490px;
}
</style>
<template>
<app-card title="密码">
<ul class="app-card-list">
<li class="app-card-item" @click="showUpdate">
<div class="app-card-item-content">••••••••</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
</ul>
<!-- 密码修改 -->
<el-dialog title="密码" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="resetFormData">
<el-form ref="form" :model="form" :rules="rules" :hide-required-asterisk="true" label-position="top">
<el-form-item label="旧密码" prop="old_password">
<el-input type="password" v-model="form.old_password"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="password">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item label="确认新密码" prop="passwordR">
<el-input type="password" v-model="form.passwordR"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleCancelUpdate">取消</el-button>
<el-button type="primary" size="medium" @click="handlePrimaryUpdate">保存</el-button>
</div>
</el-dialog>
</app-card>
</template>
<script>
import AppCard from '@/components/base/AppCard.vue'
import { updatePassword } from '../api.js'
export default {
components: { AppCard },
data() {
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入新密码'))
} else if (value !== this.form.password) {
callback(new Error('密码不一致'))
} else {
callback()
}
}
return {
form: {},
rules: {
old_password: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
password: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度为6-20个字符', trigger: 'blur' }
],
passwordR: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ validator: validatePass, trigger: 'blur' }
]
},
dialogVisible: false
}
},
computed: {
user() {
return this.$store.state.user
}
},
methods: {
showUpdate() {
this.dialogVisible = true
},
handleCancelUpdate() {
this.dialogVisible = false
},
handlePrimaryUpdate() {
this.$refs.form.validate().then(() => {
this.updateHandler(this.form, () => {
this.dialogVisible = false
})
})
},
updateHandler(params, callback) {
return updatePassword(params)
.then(resp => {
this.$store.dispatch('getUser')
callback && callback(resp)
})
.catch(resp => this.$message.error(resp.msg))
},
resetFormData() {
this.form = {}
this.$refs.form && this.$refs.form.resetFields()
}
}
}
</script>
<style lang="scss" scoped>
.app-card-item {
position: relative;
margin-left: 24px;
display: flex;
align-items: center;
padding: 16px 24px 16px 0;
border-top: 1px solid #dadce0;
cursor: pointer;
&:hover {
margin-left: 0;
padding-left: 24px;
background-color: rgba(128, 134, 139, 0.04);
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
border-top: 1px solid #dadce0;
}
}
}
.app-card-item-label {
flex-basis: 156px;
margin-right: 24px;
font-weight: 500;
color: #5f6368;
}
.app-card-item-content {
flex: 1;
}
::v-deep .el-dialog {
max-width: 490px;
}
</style>
const routes = [
{
path: '/settings',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: '',
component: () => import('./views/Index.vue')
}
]
}
]
export { routes }
<template>
<div class="settings">
<h1 class="title">个人信息</h1>
<p class="tips">您在各种紫荆服务中的个人信息和偏好设置</p>
<settings-base></settings-base>
<settings-contact></settings-contact>
<settings-password></settings-password>
</div>
</template>
<script>
import SettingsBase from '../components/Base.vue'
import SettingsPassword from '../components/Password.vue'
import SettingsContact from '../components/Contact.vue'
export default {
metaInfo: {
title: '个人信息'
},
components: { SettingsBase, SettingsPassword, SettingsContact }
}
</script>
<style lang="scss" scoped>
.settings {
max-width: 838px;
margin: 0 auto;
}
.title {
margin-bottom: 8px;
font-size: 1.75rem;
font-weight: 400;
letter-spacing: 0;
line-height: 2.25rem;
word-break: break-word;
word-wrap: break-word;
color: #202124;
text-align: center;
}
.tips {
letter-spacing: 0.00625em;
font-family: Roboto, Arial, sans-serif;
font-size: 1rem;
font-weight: 400;
line-height: 1.5rem;
hyphens: auto;
word-break: break-word;
word-wrap: break-word;
color: #5f6368;
margin-left: auto;
margin-right: auto;
max-width: 600px;
text-align: center;
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论