This merge consolidates recent fixes and refactors across auth, member, goods widgets, and recommendation pages, with a focus on runtime stability, i18n consistency, and UI warning cleanup.

This commit is contained in:
moyayu
2026-04-24 17:25:38 +08:00
parent 626aaf6cb1
commit 139e82d40e
14 changed files with 111 additions and 67 deletions

View File

@@ -185,7 +185,7 @@ function SpGoodsItem(props) {
{!info.point && showPrice && (
<View className='goods-price'>
<View className='gd-price'>
<View className='gd-price flex flex-align-center'>
{isPurchase && (
<>
{info.activityPrice && enPurActivityPrice ? (
@@ -201,14 +201,14 @@ function SpGoodsItem(props) {
{!isPurchase && (
<SpPrice size={36} value={info.activityPrice || info.price}></SpPrice>
)}
{/* {info.marketPrice > 0 && enMarketPrice && (
{info.marketPrice > 0 && enMarketPrice && (
<SpPrice
size={26}
className='mkt-price'
lineThrough
value={info.marketPrice}
></SpPrice>
)} */}
)}
</View>
{!info.activityPrice && isLogin && !isPurchase && (
<View className='more-price'>

View File

@@ -78,13 +78,17 @@ function SpGoodsHeroCard(props) {
{info.promotion?.map((tag, index) => (
<SpTag
className='tag-wrapper__tag'
key={index}
key={`promotion-${tag.type || 't'}-${tag.tag_name || index}`}
label={tag.tag_name}
type={tag.type}
/>
))}
{info.couponList?.map((tag, index) => (
<SpTag key={index} label={tag.discount_rule} type='warning'></SpTag>
<SpTag
key={`coupon-${tag.coupon_id || tag.discount_rule || index}`}
label={tag.discount_rule}
type='warning'
></SpTag>
))}
</View>
)}

View File

@@ -190,10 +190,7 @@ export default class WgtGoodsCard extends Component {
const isFav = Boolean(item.favStatus ?? favs?.[item.item_id])
return (
<View key={key} className='goods-card' style={styleNames(goodsCardInnerStyle)}>
<View
className='goods-card__header'
onClick={this.handleClickItem.bind(this, item)}
>
<View className='goods-card__header' onClick={this.handleClickItem.bind(this, item)}>
<SpImage src={item.img_url} width={160} height={160} isOss />
<View className='goods-card__info'>
<View className='goods-card__info-title'>{item.item_name}</View>

View File

@@ -3,6 +3,8 @@
* See LICENSE file for license details.
*/
.wgt-goods-card {
.wgt-bd {
.goods-card {
&:not(:last-child) {
@@ -55,8 +57,8 @@
height: 64px;
font-size: 28px;
color: $color-brand-primary;
background: #ffffff;
border: 1px solid #dcdfe6;
background: #FFFFFF;
border: 1PX solid #DCDFE6;
border-radius: 12px;
}
@@ -72,4 +74,4 @@
}
}
}
}
}

View File

@@ -122,35 +122,35 @@ export function getGlobalBaseStyle(baseStyle) {
// 处理边距
if (baseStyle.paddedt !== undefined && baseStyle.paddedt !== null) {
style['padding-top'] = Taro.pxTransform(baseStyle.paddedt)
style.paddingTop = Taro.pxTransform(baseStyle.paddedt)
}
if (baseStyle.paddedb !== undefined && baseStyle.paddedb !== null) {
style['padding-bottom'] = Taro.pxTransform(baseStyle.paddedb)
style.paddingBottom = Taro.pxTransform(baseStyle.paddedb)
}
if (baseStyle.paddedl !== undefined && baseStyle.paddedl !== null) {
style['padding-left'] = Taro.pxTransform(baseStyle.paddedl)
style.paddingLeft = Taro.pxTransform(baseStyle.paddedl)
}
if (baseStyle.paddedr !== undefined && baseStyle.paddedr !== null) {
style['padding-right'] = Taro.pxTransform(baseStyle.paddedr)
style.paddingRight = Taro.pxTransform(baseStyle.paddedr)
}
// 处理背景
const bgType = baseStyle.bgType
if (bgType === 'color' && baseStyle.bgColor) {
style['background-color'] = baseStyle.bgColor
style.backgroundColor = baseStyle.bgColor
} else if (bgType === 'pic' && baseStyle.bgPic) {
style['background-image'] = `url(${baseStyle.bgPic})`
style['background-size'] = 'cover'
style['background-position'] = 'center'
style['background-repeat'] = 'no-repeat'
style.backgroundImage = `url(${baseStyle.bgPic})`
style.backgroundSize = 'cover'
style.backgroundPosition = 'center'
style.backgroundRepeat = 'no-repeat'
} else if (bgType === 'gradient' && baseStyle.startColor) {
// 如果 endColor 为空,使用 startColor 作为结束颜色(单色渐变)
const endColor = baseStyle.endColor || baseStyle.startColor
style['background-image'] = `linear-gradient(${baseStyle.startColor}, ${endColor})`
style['background-size'] = 'cover'
style.backgroundImage = `linear-gradient(${baseStyle.startColor}, ${endColor})`
style.backgroundSize = 'cover'
}
return style

View File

@@ -124,6 +124,7 @@ function GuideRecommendDetail(props) {
}
}
return (
<SpPage
className='pages-recommend-detail'

View File

@@ -9,6 +9,7 @@ import { SpPage, SpInput as AtInput } from '@/components'
import { classNames, validate, showToast } from '@/utils'
import { AtForm, AtButton } from 'taro-ui'
import api from '@/api'
import { normalizeAuthRedirectParam } from './util'
import './forgotpwd.scss'
const PageForgotPwdEmail = () => {
@@ -96,7 +97,9 @@ const PageForgotPwdEmail = () => {
className='btn-text'
onClick={() =>
Taro.navigateTo({
url: `/subpages/auth/login?redirect=${encodeURIComponent(rediUrlParam)}`
url: `/subpages/auth/login?redirect=${encodeURIComponent(
normalizeAuthRedirectParam(rediUrlParam)
)}`
})
}
>

View File

@@ -13,6 +13,7 @@ import api from '@/api'
import { useImmer } from 'use-immer'
import { useSelector } from 'react-redux'
import { CompPasswordInput } from './comps'
import { normalizeAuthRedirectParam } from './util'
import './forgotpwd.scss'
const SYMBOL = 'forgot_password'
@@ -60,7 +61,7 @@ const PageBindPhone = () => {
const showModalReg = async () => {
let url = `/subpages/auth/reg`
if (redi_url) {
url += `?redi_url=${encodeURIComponent(redi_url)}`
url += `?redi_url=${encodeURIComponent(normalizeAuthRedirectParam(redi_url))}`
}
const { is_new } = await api.wx.getIsNew({ mobile: username })
setState((_state) => {
@@ -201,7 +202,11 @@ const PageBindPhone = () => {
/>
</View>
<View className='btn-field'>
<SpTimer defaultMsg='发送验证码' msg='重新发送' onStart={handleTimerStart} />
<SpTimer
defaultMsg='发送验证码'
msg='重新发送'
onStart={handleTimerStart}
/>
</View>
</View>

View File

@@ -14,7 +14,13 @@ import S from '@/spx'
import api from '@/api'
import { classNames, navigateTo, validate, showToast, tokenParseH5 } from '@/utils'
import { CompOtherLogin, CompPasswordInput, CompInputPhone } from './comps'
import { navigationToReg, setToken, setTokenAndRedirect, addListener } from './util'
import {
navigationToReg,
setToken,
setTokenAndRedirect,
addListener,
normalizeAuthRedirectParam
} from './util'
import './login.scss'
/** 注册页 redirect 带来的 email query可能与 reg 侧 `encodeEmailForLoginQuery` 成对使用) */
@@ -112,7 +118,7 @@ export default class Login extends Component {
Taro.setNavigationBarTitle({ title: '登录' })
const { redirect } = this.$instance?.router?.params
if (S.getAuthToken()) {
const url = redirect ? decodeURIComponent(redirect) : '/subpages/member/index'
const url = redirect ? normalizeAuthRedirectParam(redirect) : '/subpages/member/index'
window.location.href = url
}
// 部分环境下 query 仅在 didShow 就绪,用于补一次「注册成功带回邮箱」的预填
@@ -473,7 +479,7 @@ export default class Login extends Component {
Taro.navigateTo({
url: `/subpages/auth/edit-password?phone=${encodeURIComponent(
emailTrim
)}&redi_url=${encodeURIComponent(redirect || '')}`
)}&redi_url=${encodeURIComponent(normalizeAuthRedirectParam(redirect || ''))}`
})
return
}
@@ -483,7 +489,7 @@ export default class Login extends Component {
setToken(token)
Taro.navigateTo({
url: `/subpages/auth/edit-password?phone=${mobile}&redi_url=${encodeURIComponent(
redirect || ''
normalizeAuthRedirectParam(redirect || '')
)}`
})
return
@@ -547,7 +553,7 @@ export default class Login extends Component {
if (accountMode === 'email') {
const parts = []
if (redirect) {
parts.push(`redi_url=${encodeURIComponent(redirect)}`)
parts.push(`redi_url=${encodeURIComponent(normalizeAuthRedirectParam(redirect))}`)
}
if (email) {
parts.push(`email=${encodeURIComponent(email.trim())}`)
@@ -562,7 +568,7 @@ export default class Login extends Component {
const parts = []
if (redirect) {
parts.push(`redi_url=${encodeURIComponent(redirect)}`)
parts.push(`redi_url=${encodeURIComponent(normalizeAuthRedirectParam(redirect))}`)
}
if (mobile) {
parts.push(`phone=${mobile}`)
@@ -684,7 +690,7 @@ export default class Login extends Component {
>
<View style={{ padding: '0 32px' }}>
<View className='auth-hd'>
<View className='title'>欢迎登录</View>
<View className='title'>{'欢迎登录'}</View>
{/* <View className='desc'>使用已注册的手机号登录</View> */}
</View>
<View className='auth-bd'>
@@ -695,7 +701,7 @@ export default class Login extends Component {
})}
onClick={() => this.handleAccountModeChange('mobile')}
>
手机号登录
{'手机号登录'}
</View>
<View
className={classNames('login-type-tab', {
@@ -703,7 +709,7 @@ export default class Login extends Component {
})}
onClick={() => this.handleAccountModeChange('email')}
>
邮箱登录
{'邮箱登录'}
</View>
</View>
<AtForm className='form'>
@@ -736,7 +742,7 @@ export default class Login extends Component {
clear
name='yzm'
value={info.yzm}
placeholder='请输入图形验证码'
placeholder={'请输入图形验证码'}
onChange={this.handleInputChange.bind(this, 'yzm')}
placeholderClass='input-placeholder'
{...inputProp}
@@ -760,7 +766,7 @@ export default class Login extends Component {
clear
name='vcode'
value={info.vcode}
placeholder='请输入验证码'
placeholder={'请输入验证码'}
onChange={this.handleInputChange.bind(this, 'vcode')}
placeholderClass='input-placeholder'
{...inputProp}
@@ -780,7 +786,7 @@ export default class Login extends Component {
</Text>
{passwordLogin && (
<Text className='btn-text forgot-password' onClick={this.handleForgotPsd}>
忘记密码
{'忘记密码?'}
</Text>
)}
</View>
@@ -793,7 +799,7 @@ export default class Login extends Component {
name='email'
type='text'
value={info.email}
placeholder='请输入登录邮箱'
placeholder={'请输入登录邮箱'}
onChange={this.handleInputChange.bind(this, 'email')}
placeholderClass='input-placeholder'
{...inputProp}
@@ -803,7 +809,7 @@ export default class Login extends Component {
<View className='form-field'>
<View className='input-field'>
<CompPasswordInput
placeholder='请输入密码8-20位须含字母与数字'
placeholder={'请输入密码8-20位须含字母与数字'}
onChange={this.handleInputChange.bind(this, 'password')}
{...inputProp}
value={info.password}
@@ -818,7 +824,7 @@ export default class Login extends Component {
clear
name='email-yzm'
value={info.yzm}
placeholder='请输入图形验证码'
placeholder={'请输入图形验证码'}
onChange={this.handleInputChange.bind(this, 'yzm')}
placeholderClass='input-placeholder'
{...inputProp}
@@ -842,7 +848,7 @@ export default class Login extends Component {
clear
name='email-vcode'
value={info.vcode}
placeholder='请输入邮箱验证码'
placeholder={'请输入邮箱验证码'}
onChange={this.handleInputChange.bind(this, 'vcode')}
placeholderClass='input-placeholder'
{...inputProp}
@@ -884,7 +890,7 @@ export default class Login extends Component {
className='login-button'
onClick={this.handleSubmit.bind(this)}
>
{'登 录'}
</AtButton>
<AtButton
circle
@@ -892,7 +898,7 @@ export default class Login extends Component {
className='reg-button'
onClick={this.handleNavigateReg}
>
{'注 册'}
</AtButton>
</View>
</AtForm>
@@ -904,29 +910,29 @@ export default class Login extends Component {
<SpFloatLayout
className='login-resend-activate-modal'
title='重发激活邮件'
title={'重发激活邮件'}
open={showResendActivateModal}
onClose={this.handleCloseResendActivateModal}
renderFooter={
<View className='resend-activate-footer'>
<AtButton circle onClick={this.handleCloseResendActivateModal}>
取消
{'取消'}
</AtButton>
<AtButton circle type='primary' onClick={this.handleConfirmResendActivate}>
确认
{'确认'}
</AtButton>
</View>
}
>
<View className='resend-activate-body'>
<Text className='resend-activate-body__text'>是否向该邮箱重新发送激活邮件</Text>
<Text className='resend-activate-body__text'>{'是否向该邮箱重新发送激活邮件?'}</Text>
<View className='form-field resend-activate-captcha'>
<View className='input-field'>
<AtInput
clear
name='resend-activate-yzm'
value={resendActivateYzm}
placeholder='请输入图形验证码'
placeholder={'请输入图形验证码'}
onChange={this.handleResendActivateYzmChange}
placeholderClass='input-placeholder'
/>

View File

@@ -13,6 +13,7 @@ import { AtForm, AtButton } from 'taro-ui'
import api from '@/api'
import { useImmer } from 'use-immer'
import { CompPasswordInput } from './comps'
import { normalizeAuthRedirectParam } from './util'
import './reg.scss'
/** query 里邮箱保留 `@`,避免链接或路由层把 `%40` 当字面量带进输入框;其余字符仍按 URI 编码 */
@@ -38,11 +39,13 @@ function buildPostRegisterLoginUrl(accountMode, fields, routerParams) {
qs.push(`email=${encodeEmailForLoginQuery(fields.email)}`)
}
const { redi_url, redirect } = routerParams || {}
if (redi_url) {
qs.push(`redi_url=${encodeURIComponent(redi_url)}`)
const rediPlain = redi_url ? normalizeAuthRedirectParam(redi_url) : ''
const redirectPlain = redirect ? normalizeAuthRedirectParam(redirect) : ''
if (rediPlain) {
qs.push(`redi_url=${encodeURIComponent(rediPlain)}`)
}
if (redirect) {
qs.push(`redirect=${encodeURIComponent(redirect)}`)
if (redirectPlain) {
qs.push(`redirect=${encodeURIComponent(redirectPlain)}`)
}
return `/subpages/auth/login?${qs.join('&')}`
}
@@ -114,7 +117,7 @@ const Reg = () => {
const redi_url = getCurrentInstance()?.router?.params?.redi_url
const parts = []
if (redi_url) {
parts.push(`redi_url=${encodeURIComponent(redi_url)}`)
parts.push(`redi_url=${encodeURIComponent(normalizeAuthRedirectParam(redi_url))}`)
}
const e = email.trim()
if (e) {
@@ -385,7 +388,7 @@ const Reg = () => {
clear
name='yzm'
value={state.yzm}
placeholder='请输入图形验证码'
placeholder={'请输入图形验证码'}
onChange={handleInputChange('yzm')}
/>
</View>
@@ -406,7 +409,7 @@ const Reg = () => {
clear
name='vcode'
value={state.vcode}
placeholder='请输入验证码'
placeholder={'请输入验证码'}
onChange={handleInputChange('vcode')}
/>
</View>
@@ -423,7 +426,7 @@ const Reg = () => {
<View className='form-field'>
<View className='input-field'>
<CompPasswordInput
placeholder='请输入密码'
placeholder={'请输入密码'}
onChange={handleInputChange('password')}
value={state.password}
/>
@@ -449,7 +452,7 @@ const Reg = () => {
clear
name='email-yzm'
value={state.yzm}
placeholder='请输入图形验证码'
placeholder={'请输入图形验证码'}
onChange={handleInputChange('yzm')}
/>
</View>
@@ -467,7 +470,7 @@ const Reg = () => {
<View className='form-field'>
<View className='input-field'>
<CompPasswordInput
placeholder='密码需8-20位且同时包含字母与数字'
placeholder={'密码需8-20位且同时包含字母与数字'}
onChange={handleInputChange('password')}
value={state.password}
/>

View File

@@ -7,10 +7,31 @@ import { showToast } from '@/utils'
import Taro, { getCurrentInstance } from '@tarojs/taro'
import { SG_CHECK_STORE_RULE } from '@/consts'
/**
* 将 redirect / redi_url 规范成「未 URI 编码的站内路径」,
* 避免 登录→注册→登录 等链路里对已编码字符串再次 encode 出现 %252F、%25252F 乱码。
*/
export function normalizeAuthRedirectParam(raw) {
if (raw == null) return ''
let s = String(raw).trim()
if (!s) return ''
for (let i = 0; i < 8; i += 1) {
try {
const next = decodeURIComponent(s)
if (next === s) break
s = next
} catch {
break
}
}
return s
}
//跳转到注册页
function navigationToReg(redirect) {
const plain = normalizeAuthRedirectParam(redirect)
Taro.navigateTo({
url: `/subpages/auth/reg?redi_url=${encodeURIComponent(redirect)}`
url: `/subpages/auth/reg?redi_url=${encodeURIComponent(plain)}`
})
}
@@ -44,9 +65,9 @@ async function setTokenAndRedirect(token = '', tokenSetSuccessCallback, options)
const url = forceMemberCenter
? '/subpages/member/index'
: redi_url
? decodeURIComponent(redi_url)
? normalizeAuthRedirectParam(redi_url)
: redirect
? decodeURIComponent(redirect)
? normalizeAuthRedirectParam(redirect)
: '/subpages/member/index'
// 清空店铺进店规则检查
Taro.setStorageSync(SG_CHECK_STORE_RULE, 0)

View File

@@ -79,6 +79,7 @@ function GuideRecommendDetail(props) {
})
}
return (
<SpPage
className='guide-recommend-detail'
@@ -104,7 +105,9 @@ function GuideRecommendDetail(props) {
{item.name === 'slider' && <WgtSlider info={item} />}
{item.name === 'writing' && <WgtWriting info={item} />}
{item.name === 'heading' && <WgtHeading info={item} />}
{item.name === 'goodsCard' && <WgtGoodsCard info={item} />}
{item.name === 'goodsCard' && (
<WgtGoodsCard info={item} />
)}
</View>
))}
</View>

View File

@@ -8,7 +8,7 @@ import { updateUserInfo, updateCheckChief } from '@/store/slices/user'
import { WgtsContext } from '@/pages/home/wgts/wgts-context'
import { platformTemplateName } from '@/utils/platform'
import { View, Text, ScrollView } from '@tarojs/components'
import { SG_APP_CONFIG } from '@/consts'
import { SG_APP_CONFIG, DEFAULT_POINT_NAME } from '@/consts'
import { useSelector, useDispatch } from 'react-redux'
import HomeWgts from '@/pages/home/comps/home-wgts'
import { useImmer } from 'use-immer'
@@ -110,7 +110,6 @@ const initialState = {
shareInfo: {},
footerHeight: 0,
pageData: null,
shareInfo: {},
bodyHeight: 0
}
@@ -136,7 +135,7 @@ function MemberIndex(props) {
const [policyModal, setPolicyModal] = useState(false)
const { userInfo = {}, vipInfo = {} } = useSelector((state) => state.user)
const { pointName } = useSelector((state) => state.sys)
const pointName = useSelector((state) => state.sys?.pointName ?? DEFAULT_POINT_NAME())
log.debug(`store userInfo: ${JSON.stringify(userInfo)}`)
const dispatch = useDispatch()

View File

@@ -54,7 +54,7 @@
width: 144px;
height: 144px;
border-radius: 50%;
border: 1px solid #ebebeb;
border: 1PX solid #ebebeb;
overflow: hidden;
position: absolute;
left: 36px;