index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <template>
  2. <wrapper
  3. class="c-profile"
  4. fill
  5. margin
  6. background
  7. >
  8. <div class="l-flex__none l-flex--col center c-profile__header has-padding">
  9. <div
  10. class="o-avatar u-pointer"
  11. @click="onAvatarClick"
  12. >
  13. <i
  14. class="o-avatar__img"
  15. :style="avatarStyle"
  16. />
  17. <i class="o-avatar__upload el-icon-camera" />
  18. <input
  19. ref="upload"
  20. type="file"
  21. accept="image/*"
  22. style="display: none;"
  23. @change="onUpload"
  24. >
  25. </div>
  26. <div class="u-bold">{{ name }}</div>
  27. <div class="u-relative">
  28. <i class="c-profile__icon el-icon-user" />
  29. {{ roleTip }}
  30. </div>
  31. </div>
  32. <div class="l-flex__fill u-overflow-y--auto">
  33. <el-result
  34. v-if="error"
  35. icon="warning"
  36. >
  37. <template #extra>
  38. <el-link
  39. class="u-pointer"
  40. type="warning"
  41. @click="getUserInfo"
  42. >
  43. 出错了,点击重试
  44. </el-link>
  45. </template>
  46. </el-result>
  47. <div
  48. v-else
  49. class="c-form"
  50. >
  51. <div class="c-form__section">
  52. <span class="c-form__label">手机:</span>
  53. <el-input
  54. v-model="phone"
  55. class="c-form__item"
  56. @change="onPhoneChange"
  57. @keydown.enter="$event.target.blur()"
  58. />
  59. </div>
  60. <div class="c-form__section has-bottom-padding">
  61. <span class="c-form__label">邮箱:</span>
  62. <el-input
  63. v-model="email"
  64. class="c-form__item"
  65. @change="onEmailChange"
  66. @keydown.enter="$event.target.blur()"
  67. />
  68. </div>
  69. <div class="c-form__section">
  70. <i class="o-wechat" />
  71. <div
  72. class="has-padding u-pointer"
  73. :class="color"
  74. @click="onClickWechat"
  75. >
  76. {{ tip }}
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. <el-dialog
  82. :visible.sync="showQr"
  83. custom-class="c-preview"
  84. width="0"
  85. @close="onCloseQr"
  86. >
  87. <div class="c-wechat has-padding">
  88. <div class="l-flex--row center has-bottom-padding">
  89. <i class="c-sibling-item o-wechat" />
  90. <span class="c-sibling-item u-color--black u-bold">请使用微信扫一扫</span>
  91. </div>
  92. <div
  93. v-loading="loading"
  94. class="c-wechat__wrapper"
  95. :class="{ retry }"
  96. @click="getQr"
  97. >
  98. <img
  99. class="c-wechat__qr"
  100. :src="qr"
  101. >
  102. </div>
  103. </div>
  104. </el-dialog>
  105. </wrapper>
  106. </template>
  107. <script>
  108. import { mapState } from 'vuex'
  109. import {
  110. userinfo,
  111. updateUser
  112. } from '@/api/user'
  113. import { Role } from '@/constant'
  114. export default {
  115. name: 'Profile',
  116. data () {
  117. return {
  118. error: false,
  119. user: null,
  120. avatar: '',
  121. phone: '',
  122. email: '',
  123. wechat: '',
  124. showQr: false,
  125. qr: '',
  126. loading: false,
  127. retry: false
  128. }
  129. },
  130. computed: {
  131. ...mapState('user', ['name', 'roles']),
  132. avatarStyle () {
  133. const avatar = this.avatar
  134. return avatar ? {
  135. backgroundImage: `url("${avatar}")`
  136. } : null
  137. },
  138. roleTip () {
  139. if (this.roles.includes(Role.ADMIN)) {
  140. return '超级管理员'
  141. }
  142. if (this.roles.includes(Role.SUPERVISOR)) {
  143. return '主管'
  144. }
  145. if (this.roles.includes(Role.STAFF)) {
  146. return '员工'
  147. }
  148. return '游客'
  149. },
  150. tip () {
  151. return this.wechat ? '解除绑定' : '绑定微信'
  152. },
  153. color () {
  154. return this.wechat ? 'u-color--error' : 'u-color--primary'
  155. }
  156. },
  157. created () {
  158. this.$timer = null
  159. this.$checkTimer = null
  160. this.getUserInfo()
  161. },
  162. beforeDestroy () {
  163. this.onCloseQr()
  164. clearInterval(this.$checkTimer)
  165. },
  166. methods: {
  167. onAvatarClick () {
  168. this.$refs.upload.click()
  169. },
  170. onUpload (e) {
  171. const file = e.target.files[0]
  172. e.target.value = null
  173. if (file.size >= 1024 * 1024) {
  174. this.$message({
  175. type: 'warning',
  176. message: '请选择1M以下的图片'
  177. })
  178. return
  179. }
  180. const reader = new FileReader()
  181. reader.onload = () => {
  182. this.avatar = reader.result
  183. }
  184. reader.readAsDataURL(file)
  185. },
  186. getUserInfo () {
  187. this.error = false
  188. userinfo().then(data => {
  189. // this.avatar = this.getOrCreateAttribute(data, 'avatar', '')
  190. this.phone = this.getOrCreateAttribute(data, 'phone', '')
  191. this.email = data.email || ''
  192. this.wechat = this.getOrCreateAttribute(data, 'wechat', '')
  193. this.user = data
  194. }, () => {
  195. this.error = true
  196. })
  197. },
  198. getOrCreateAttribute (user, key, defaults = '') {
  199. if (!user.attributes[key]) {
  200. user.attributes[key] = [defaults]
  201. }
  202. return user.attributes[key][0]
  203. },
  204. updateUser (data, meesage) {
  205. return updateUser(this.user.id, data, meesage)
  206. },
  207. changeAttribute (key, value, meesage) {
  208. if (this.user.attributes[key][0] !== value) {
  209. return this.updateUser({
  210. attributes: {
  211. ...this.user.attributes,
  212. [key]: [value]
  213. }
  214. }, meesage).then(
  215. () => {
  216. this.user.attributes[key][0] = value
  217. },
  218. e => {
  219. this.resetAttribute(key)
  220. return Promise.reject(e)
  221. }
  222. )
  223. }
  224. return Promise.resolve()
  225. },
  226. resetAttribute (key) {
  227. this[key] = this.user.attributes[key][0]
  228. },
  229. onPhoneChange () {
  230. if (!this.phone) {
  231. this.$message({
  232. type: 'warning',
  233. message: '手机号不能为空'
  234. })
  235. this.resetAttribute('phone')
  236. return
  237. }
  238. if (!(/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phone))) {
  239. this.$message({
  240. type: 'warning',
  241. message: '手机号格式错误'
  242. })
  243. this.resetAttribute('phone')
  244. return
  245. }
  246. this.changeAttribute('phone', this.phone, '更新手机号')
  247. },
  248. onEmailChange () {
  249. if (!this.email) {
  250. this.$message({
  251. type: 'warning',
  252. message: '邮箱不能为空'
  253. })
  254. this.resetAttribute('email')
  255. return
  256. }
  257. if (!(/^([a-zA-Z0-9]+[_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(this.email))) {
  258. this.$message({
  259. type: 'warning',
  260. message: '邮箱格式错误'
  261. })
  262. this.resetAttribute('email')
  263. return
  264. }
  265. this.updateUser({ email: this.email }, '更新邮箱').then(
  266. () => {
  267. this.user.email = this.email
  268. },
  269. () => {
  270. this.email = this.user.email
  271. }
  272. )
  273. },
  274. onClickWechat () {
  275. if (this.wechat) {
  276. this.$confirm('解除绑定后将无法再收到消息推送', '确定解绑微信?', { type: 'warning' }).then(
  277. () => {
  278. this.changeAttribute('wechat', '', '解绑微信').then(() => {
  279. this.wechat = ''
  280. this.user.attributes.wechat[0] = ''
  281. })
  282. }
  283. )
  284. } else {
  285. this.showQr = true
  286. this.getQr()
  287. }
  288. },
  289. onCloseQr () {
  290. this.showQr = false
  291. clearTimeout(this.$timer)
  292. },
  293. getQr () {
  294. if (this.loading || this.qr && !this.retry) {
  295. return
  296. }
  297. this.fetchQr()
  298. },
  299. resetQr () {
  300. clearInterval(this.$checkTimer)
  301. this.$checkTimer = null
  302. this.qr = ''
  303. this.retry = false
  304. },
  305. fetchQr () {
  306. if (!this.showQr) {
  307. this.qr = ''
  308. return
  309. }
  310. if (!this.qr || this.retry) {
  311. this.loading = true
  312. }
  313. this.retry = false
  314. clearTimeout(this.$timer)
  315. new Promise((resolve, reject) => {
  316. setTimeout(Math.random() + 0.5 | 0 ? resolve : reject, 1000)
  317. }).then(
  318. () => {
  319. if (!this.wechat) {
  320. this.qr = Math.random()
  321. this.$timer = setTimeout(this.fetchQr, 5000)
  322. this.checkBind()
  323. }
  324. },
  325. () => {
  326. if (!this.wechat) {
  327. this.retry = true
  328. }
  329. }
  330. ).finally(() => {
  331. this.loading = false
  332. })
  333. },
  334. checkBind () {
  335. if (!this.wechat && !this.$checkTimer) {
  336. this.$checkTimer = setInterval(this.check, 2000)
  337. setTimeout(() => {
  338. this.updateUser({
  339. attributes: {
  340. ...this.user.attributes,
  341. wechat: ['9999']
  342. }
  343. }, null)
  344. }, 5000)
  345. }
  346. },
  347. check () {
  348. userinfo(true).then(data => {
  349. const wechat = data.attributes.wechat?.[0]
  350. if (wechat) {
  351. this.$message({
  352. type: 'success',
  353. message: '绑定微信成功'
  354. })
  355. this.wechat = wechat
  356. this.user.attributes.wechat[0] = wechat
  357. this.onCloseQr()
  358. this.resetQr()
  359. }
  360. })
  361. }
  362. }
  363. }
  364. </script>
  365. <style lang="scss" scoped>
  366. .c-profile {
  367. overflow: hidden;
  368. &__header {
  369. justify-content: space-between;
  370. height: 176px;
  371. color: #fff;
  372. line-height: 1;
  373. background-color: $blue;
  374. }
  375. &__icon {
  376. position: absolute;
  377. top: 50%;
  378. transform: translate(calc(-100% - 16px), -50%);
  379. }
  380. }
  381. .o-avatar {
  382. display: inline-block;
  383. position: relative;
  384. width: 80px;
  385. height: 80px;
  386. padding: 2px;
  387. border-radius: 50%;
  388. background-color: #fff;
  389. &__img {
  390. display: inline-block;
  391. width: 100%;
  392. height: 100%;
  393. border-radius: 50%;
  394. background: url("~@/assets/icon_avatar.png") 0 0 / 100% 100% no-repeat;
  395. }
  396. &__upload {
  397. display: inline-flex;
  398. justify-content: center;
  399. align-items: center;
  400. position: absolute;
  401. right: 0;
  402. bottom: 0;
  403. width: 26px;
  404. height: 26px;
  405. color: $blue;
  406. border-radius: 50%;
  407. background-color: #fff;
  408. }
  409. }
  410. .o-wechat {
  411. display: inline-block;
  412. width: 32px;
  413. height: 32px;
  414. background: url("~@/assets/icon_wechat.png") 0 0 / 100% 100% no-repeat;
  415. }
  416. .c-wechat {
  417. border-radius: $radius;
  418. background-color: #fff;
  419. &__wrapper {
  420. position: relative;
  421. width: 240px;
  422. height: 240px;
  423. &.retry::after {
  424. content: "点击重试";
  425. display: inline-flex;
  426. justify-content: center;
  427. align-items: center;
  428. position: absolute;
  429. top: 0;
  430. left: 0;
  431. width: 100%;
  432. height: 100%;
  433. color: $black;
  434. font-weight: bold;
  435. background-color: rgba(#fff, 0.8);
  436. cursor: pointer;
  437. }
  438. }
  439. &__qr {
  440. display: inline-block;
  441. width: 100%;
  442. height: 100%;
  443. }
  444. }
  445. </style>