mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-09-20 11:52:02 +08:00
feat(miniprogram): 微信小程序个人资料页面功能优化
This commit is contained in:
@@ -1,9 +1,32 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<form class="person-msg" @submit="onWxSubmit">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="person-msg">
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
v-if="useWxChooseAvatar"
|
||||
class="head avatar-btn"
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
@error="onChooseAvatarError"
|
||||
>
|
||||
<image :src="form.face || '/static/missing-face.png'" mode="aspectFill"></image>
|
||||
<view>点击修改头像</view>
|
||||
</button>
|
||||
<view v-else class="head" @click="changeFace">
|
||||
<image :src="form.face || '/static/missing-face.png'" mode="aspectFill"></image>
|
||||
<view>点击修改头像</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="head" @click="changeFace">
|
||||
<image :src="form.face || '/static/missing-face.png'" mode="aspectFill"></image>
|
||||
<view>点击修改头像</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<up-form
|
||||
:model="form"
|
||||
@@ -13,12 +36,26 @@
|
||||
label-width="180rpx"
|
||||
>
|
||||
<up-form-item label="昵称" label-width="180rpx" :border-bottom="true">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<input
|
||||
class="wx-nickname"
|
||||
type="nickname"
|
||||
name="nickName"
|
||||
:value="form.nickName"
|
||||
placeholder="请输入昵称"
|
||||
placeholder-class="wx-nickname-placeholder"
|
||||
@blur="onNicknameBlur"
|
||||
@nicknamereview="onNicknameReview"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<u-input
|
||||
v-model="form.nickName"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="性别" label-width="180rpx" :border-bottom="true">
|
||||
@@ -55,8 +92,13 @@
|
||||
</up-form>
|
||||
|
||||
<view class="bottom">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="submit" form-type="submit">保存</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="submit" @click="submit">保存</view>
|
||||
<view class="submit light" @click="quiteLoginOut">退出登录</view>
|
||||
<!-- #endif -->
|
||||
<view class="submit light" @click="goBack">返回</view>
|
||||
</view>
|
||||
|
||||
<u-datetime-picker
|
||||
@@ -77,19 +119,27 @@
|
||||
@funcValue="getPickerParentValue"
|
||||
pickerSize="4"
|
||||
></m-city>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
</form>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { quiteLoginOut } from '@/utils/filters.js'
|
||||
import { saveUserInfo } from '@/api/members.js'
|
||||
import { upload } from '@/api/common.js'
|
||||
import storage from '@/utils/storage.js'
|
||||
import MCity from '@/components/m-city/m-city.vue'
|
||||
|
||||
const DEFAULT_FACE = '/static/missing-face.png'
|
||||
const NICKNAME_MIN = 2
|
||||
const NICKNAME_MAX = 20
|
||||
|
||||
function parseBirthdayTimestamp(str?: string) {
|
||||
if (!str) return Date.now()
|
||||
const timestamp = Date.parse(String(str).replace(/-/g, '/'))
|
||||
@@ -111,7 +161,7 @@ const userInfo = storage.getUserInfo() || {}
|
||||
const form = reactive<Record<string, any>>({
|
||||
nickName: userInfo.nickName || '',
|
||||
birthday: userInfo.birthday || '',
|
||||
face: userInfo.face || '/static/missing-face.png',
|
||||
face: userInfo.face || DEFAULT_FACE,
|
||||
regionId: [],
|
||||
region: userInfo.region || [],
|
||||
sex: userInfo.sex != null ? String(userInfo.sex) : '1',
|
||||
@@ -126,6 +176,31 @@ const maxBirthdayDate = Date.now()
|
||||
const region = ref([{ id: '', localName: '请选择', children: [] }])
|
||||
const showBirthday = ref(false)
|
||||
const cityPicker = ref<any>(null)
|
||||
const useWxChooseAvatar = ref(false)
|
||||
|
||||
function isWeixinDevtools() {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync() as Record<string, any>
|
||||
return sys?.platform === 'devtools'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
useWxChooseAvatar.value = !isWeixinDevtools()
|
||||
// #endif
|
||||
|
||||
function ensurePrivacyAuthorize() {
|
||||
// #ifdef MP-WEIXIN
|
||||
const wxApi = (globalThis as any).wx
|
||||
if (!wxApi?.requirePrivacyAuthorize) return
|
||||
wxApi.requirePrivacyAuthorize({
|
||||
success: () => {},
|
||||
fail: () => {},
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
function getPickerParentValue(selected: any[]) {
|
||||
form.region = []
|
||||
@@ -145,7 +220,33 @@ function clickRegion() {
|
||||
cityPicker.value?.show()
|
||||
}
|
||||
|
||||
function isTempFacePath(filePath: string) {
|
||||
return /^(wxfile:|http:\/\/tmp\/|https:\/\/tmp\/)/i.test(filePath)
|
||||
}
|
||||
|
||||
function validateNickName(nickName: string) {
|
||||
if (nickName.length < NICKNAME_MIN || nickName.length > NICKNAME_MAX) {
|
||||
uni.showToast({
|
||||
title: `昵称需为${NICKNAME_MIN}-${NICKNAME_MAX}个字符`,
|
||||
icon: 'none',
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const nickName = String(form.nickName || '').trim()
|
||||
form.nickName = nickName
|
||||
if (!validateNickName(nickName)) return
|
||||
if (isTempFacePath(form.face)) {
|
||||
uni.showToast({
|
||||
title: '头像上传中,请稍后再保存',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const params = JSON.parse(JSON.stringify(form))
|
||||
delete params.regionPath
|
||||
saveUserInfo(params).then((res) => {
|
||||
@@ -156,23 +257,74 @@ function submit() {
|
||||
})
|
||||
}
|
||||
|
||||
function changeFace() {
|
||||
uni.chooseImage({
|
||||
success: (chooseImageRes) => {
|
||||
uni.uploadFile({
|
||||
url: upload,
|
||||
filePath: chooseImageRes.tempFilePaths[0],
|
||||
name: 'file',
|
||||
header: { accessToken: storage.getAccessToken() },
|
||||
success: (uploadFileRes) => {
|
||||
const data = JSON.parse(uploadFileRes.data)
|
||||
function onWxSubmit(e: any) {
|
||||
const nickName = String(e?.detail?.value?.nickName ?? form.nickName ?? '').trim()
|
||||
form.nickName = nickName
|
||||
submit()
|
||||
}
|
||||
|
||||
function onNicknameBlur(e: any) {
|
||||
form.nickName = String(e?.detail?.value ?? '').trim()
|
||||
}
|
||||
|
||||
function onNicknameReview(e: any) {
|
||||
if (e?.detail?.pass === false) {
|
||||
form.nickName = ''
|
||||
uni.showToast({
|
||||
title: '昵称未通过安全检测',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function uploadFace(filePath: string) {
|
||||
if (!filePath) return
|
||||
uni.showLoading({ title: '上传中', mask: true })
|
||||
uni.uploadFile({
|
||||
url: upload,
|
||||
filePath,
|
||||
name: 'file',
|
||||
header: { accessToken: storage.getAccessToken() },
|
||||
success: (uploadFileRes) => {
|
||||
try {
|
||||
const data = JSON.parse(uploadFileRes.data)
|
||||
if (data.result) {
|
||||
form.face = data.result
|
||||
},
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: '头像上传失败', icon: 'none' })
|
||||
}
|
||||
} catch {
|
||||
uni.showToast({ title: '头像上传失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '头像上传失败', icon: 'none' })
|
||||
},
|
||||
complete: () => {
|
||||
uni.hideLoading()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function changeFace() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (chooseImageRes) => {
|
||||
uploadFace(chooseImageRes.tempFilePaths[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function onChooseAvatar(e: any) {
|
||||
const avatarUrl = e?.detail?.avatarUrl
|
||||
if (!avatarUrl) return
|
||||
uploadFace(avatarUrl)
|
||||
}
|
||||
|
||||
function onChooseAvatarError() {
|
||||
changeFace()
|
||||
}
|
||||
|
||||
function selectTime(e: any) {
|
||||
const timestamp = typeof e?.value === 'number' ? e.value : birthdayValue.value
|
||||
const formatted = formatBirthday(timestamp)
|
||||
@@ -192,11 +344,15 @@ function navigateToBindMobile(username: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function syncUserInfo() {
|
||||
const latest = storage.getUserInfo() || {}
|
||||
form.nickName = latest.nickName || ''
|
||||
form.birthday = latest.birthday || ''
|
||||
form.face = latest.face || '/static/missing-face.png'
|
||||
form.face = latest.face || DEFAULT_FACE
|
||||
form.region = latest.region || []
|
||||
form.sex = latest.sex != null ? String(latest.sex) : '1'
|
||||
form.regionPath = latest.region
|
||||
@@ -208,6 +364,7 @@ function syncUserInfo() {
|
||||
|
||||
onShow(() => {
|
||||
syncUserInfo()
|
||||
ensurePrivacyAuthorize()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -239,6 +396,22 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-btn {
|
||||
width: 100%;
|
||||
padding: 40rpx 0 20rpx;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
line-height: 2em;
|
||||
font-size: $font-sm;
|
||||
color: $font-color-light;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -274,6 +447,22 @@ page {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.wx-nickname {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 44rpx;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.wx-nickname-placeholder {
|
||||
color: #c0c4cc;
|
||||
font-size: 28rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sex-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -322,6 +511,13 @@ page {
|
||||
font-size: 30rpx;
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.submit + .submit {
|
||||
|
||||
@@ -234,7 +234,7 @@ import {
|
||||
getStoreIsCollect,
|
||||
} from '@/api/members.js'
|
||||
import config from '@/config/config'
|
||||
import { getGoodsList } from '@/api/goods.js'
|
||||
import { getGoodsList, getMpScene } from '@/api/goods.js'
|
||||
import { getAllCoupons } from '@/api/promotions.js'
|
||||
import { getFloorStoreData } from '@/api/home'
|
||||
import {
|
||||
@@ -504,12 +504,41 @@ function getCoupon(item: any) {
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
storeId.value = options.id
|
||||
console.log(storeId.value, 'this.storeId')
|
||||
function parseShopIdFromScene(raw: string) {
|
||||
const scene = decodeURIComponent(String(raw || ''))
|
||||
if (!scene) return ''
|
||||
if (scene.includes('id=')) {
|
||||
return scene.split('id=')[1].split('&')[0]
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
goodsParams.storeId = options.id
|
||||
couponParams.storeId = options.id
|
||||
async function resolveShopId(options: any) {
|
||||
if (options?.id) return String(options.id)
|
||||
if (!options?.scene) return ''
|
||||
const scene = decodeURIComponent(String(options.scene))
|
||||
if (scene.includes('id=')) {
|
||||
return parseShopIdFromScene(scene)
|
||||
}
|
||||
try {
|
||||
const res = await getMpScene(scene)
|
||||
const payload = res?.data != null ? res.data : res
|
||||
if (payload?.success && payload.result) {
|
||||
return parseShopIdFromScene(String(payload.result))
|
||||
}
|
||||
} catch (e) {
|
||||
// 短链解析失败时,把 scene 当店铺ID用
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
storeId.value = await resolveShopId(options)
|
||||
goodsParams.storeId = storeId.value
|
||||
couponParams.storeId = storeId.value
|
||||
if (storeId.value) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
|
||||
onPageScroll((e) => {
|
||||
@@ -531,7 +560,6 @@ onMounted(() => {
|
||||
withShareTicket: true,
|
||||
})
|
||||
// #endif
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user