feat: activity records link, coupon login flow, hot zone contact, i18n

- Marketing: add "View registration records" on activity cards when recordId exists; navigate to item-activity with activity_id
- item-activity: filter registration list by activity_id query param; refetch when it changes
- Home imghot-zone: use openType=contact Button for hot zone id customerService
- Coupon center: wrap claim button with SpLogin when logged out to avoid 401 redirect to profile
- i18n: add keys for paid/fail/registration record labels
- Purchase auth email: reset at-input horizontal margin

Made-with: Cursor
This commit is contained in:
moyayu
2026-04-10 18:00:20 +08:00
parent 171fe33427
commit 3e5ebbb794
8 changed files with 139 additions and 29 deletions

View File

@@ -4,12 +4,11 @@
*/
import React, { useEffect, useRef } from 'react'
import { useImmer } from 'use-immer'
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
import { View, ScrollView, Button } from '@tarojs/components'
import Taro, { useDidShow } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { SpPage, SpScrollView, SpSearchBar } from '@/components'
import { SpTagBar, SpSelectModal } from '@/subpages/components'
import api from '@/api'
import doc from '@/doc'
import * as activityDoc from '@/doc/activity'
import { pickBy } from '@/utils'
import CompActivityItem from './comps/comp-activity-item'
@@ -37,7 +36,6 @@ function ActivityIist(props) {
const [state, setState] = useImmer(initialState)
const { tradeStatus, status, recordList, isOpened, selectOptions, activityInfo, keyword } = state
const recordRef = useRef()
const router = useRouter()
useDidShow(() => {
setState((draft) => {
@@ -83,6 +81,14 @@ function ActivityIist(props) {
})
}
const handleViewRecords = (item) => {
const id = item?.activityId
if (id == null || id === '') return
Taro.navigateTo({
url: `/marketing/pages/member/item-activity?activity_id=${encodeURIComponent(id)}`
})
}
const registrationSubmitFetch = async ({ activityId }) => {
await api.user.joinActivity({ activity_id: activityId })
Taro.showToast({
@@ -191,6 +197,7 @@ function ActivityIist(props) {
info={item}
onClick={handleItemClick}
onBtnAction={onBtnAction}
onViewRecords={handleViewRecords}
/>
</View>
))}

View File

@@ -13,7 +13,13 @@ import classNames from 'classnames'
import './comp-activity-item.scss'
function CompActivityItem(props) {
const { info = {}, isActivity = false, onClick = () => {}, onBtnAction = () => {} } = props
const {
info = {},
isActivity = false,
onClick = () => {},
onBtnAction = () => {},
onViewRecords = () => {}
} = props
if (!info) {
return null
@@ -58,8 +64,6 @@ function CompActivityItem(props) {
onBtnAction(info, type)
}
console.log('info', info)
return (
<View
className={classNames('activity-item', { 'has-end': status == 'end' })}
@@ -82,8 +86,21 @@ function CompActivityItem(props) {
<Text className='activity-item__content-reject-reason'>{reason}</Text>
</View>
)}
<View className='activity-item__content-btns'>
{isActivity && <View className='activity-item__content-btn activity-btn'>会员免费</View>}
<View className='activity-item__content-btn-wrapper'>
{isActivity && recordId ? (
<View
className='activity-item__view-records'
onClick={(e) => {
e.stopPropagation()
onViewRecords(info)
}}
>
查看报名记录
</View>
) : null}
{actionEdit && !isActivity && (
<View
className='activity-item__content-btn'
@@ -108,7 +125,7 @@ function CompActivityItem(props) {
立即报名
</View>
)}
</View>
</View></View>
</View>
</View>
)

View File

@@ -80,6 +80,8 @@
}
}
&-btns {
display: flex;
justify-content: flex-end;
@@ -107,6 +109,21 @@
margin-left: 0;
}
}
.activity-item__content-btn-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.activity-item__view-records {
padding: 12px 24px;
box-sizing: border-box;
background: linear-gradient(to right, #f67f70 0%, #cd352a 100%);
border-radius: 32px;
font-weight: 600;
font-size: 24px;
color: #ffffff;
}
.sp-scrollview-body {
background: #f5f5f5;
@@ -119,6 +136,7 @@
}
&__content {
&-title,
&-time {
color: #c7c7c7;
@@ -136,4 +154,4 @@
}
}
}
}
}

View File

@@ -42,6 +42,7 @@ function ItemActivity(props) {
state
const recordRef = useRef()
const router = useRouter()
const filterActivityId = router.params?.activity_id
// useEffect(() => {
// Taro.eventCenter.on('onEventRecordStatusChange', () => {
@@ -78,7 +79,7 @@ function ItemActivity(props) {
draft.recordList = []
})
recordRef.current.reset()
}, [status])
}, [status, filterActivityId])
const fetch = async ({ pageIndex, pageSize }) => {
const params = {
@@ -87,6 +88,9 @@ function ItemActivity(props) {
order_type: 'normal',
status
}
if (filterActivityId) {
params.activity_id = filterActivityId
}
const { list, total_count: total } = await api.user.registrationRecordList(params)
const nList = pickBy(list, activityDoc.RECORD_LIST)
setState((draft) => {

View File

@@ -4,7 +4,7 @@
*/
import React, { useMemo } from 'react'
import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import { Button, View } from '@tarojs/components'
import { SpImage, SpLogin } from '@/components'
import { linkPage, classNames, styleNames, isArray, getDistributorId } from '@/utils'
import { needLoginPage, needLoginPageType } from '@/consts'
@@ -92,6 +92,17 @@ function WgtImgHotZone(props) {
distributor_id
})
if (item.id === 'customerService') {
return (
<Button
openType='contact'
key={item.id || Math.random()}
className='wgt-imghot-zone__body_zone opacity-0'
style={getZoneStyle(item)}
/>
)
}
if (needLogin(item)) {
return (
<SpLogin key={`imghotzone-login-${index}`} onChange={clickHandler}>

View File

@@ -16234,5 +16234,41 @@
"en": "",
"zh-tw": "",
"ar": ""
},
"e90jf3": {
"zh-cn": "已支付",
"en": "",
"zh-tw": "",
"ar": ""
},
"fy1g2": {
"zh-cn": "失败",
"en": "",
"zh-tw": "",
"ar": ""
},
"csw52l4": {
"zh-cn": "报名记录",
"en": "",
"zh-tw": "",
"ar": ""
},
"csq0ue4": {
"zh-cn": "报名时间",
"en": "",
"zh-tw": "",
"ar": ""
},
"ep7l2": {
"zh-cn": "原因",
"en": "",
"zh-tw": "",
"ar": ""
},
"i0uebq4": {
"zh-cn": "记录编号",
"en": "",
"zh-tw": "",
"ar": ""
}
}
}

View File

@@ -11,7 +11,8 @@ import doc from '@/doc'
import qs from 'qs'
import { View, Text } from '@tarojs/components'
import { pickBy, showToast, isWeixin, entryLaunch } from '@/utils'
import { SpPage, SpScrollView, SpCoupon } from '@/components'
import { SpPage, SpScrollView, SpCoupon, SpLogin } from '@/components'
import { useLogin } from '@/hooks'
import { SG_GUIDE_PARAMS } from '@/consts/localstorage'
import './coupon-center.scss'
@@ -22,6 +23,7 @@ function CouponCenter(props) {
const $instance = getCurrentInstance() || {}
const [state, setState] = useImmer(initialState)
const { couponList } = state
const { isLogin } = useLogin({ autoLogin: false })
const { gu } = Taro.getStorageSync(SG_GUIDE_PARAMS)
let work_userid = ''
if (gu) {
@@ -55,7 +57,6 @@ function CouponCenter(props) {
pagers: { total: total }
} = await api.member.homeCouponList(params)
console.log(pickBy(list, doc.coupon.COUPON))
setState((draft) => {
draft.couponList = couponList.concat(pickBy(list, doc.coupon.COUPON))
})
@@ -119,25 +120,39 @@ function CouponCenter(props) {
}
}
const couponBtnLabel = (status) =>
({
0: '已领完',
1: '立即领取',
2: '已领取'
})[status]
const renderCouponBtn = (item, index) => {
const label = couponBtnLabel(item.couponStatus)
const text = <Text>{label}</Text>
// 未登录领取:用 SpLogin 在本页弹窗登录,避免请求 401 被全局重定向到个人中心
if (item.couponStatus === 1 && !isLogin) {
return (
<SpLogin onChange={() => handleClickCouponItem(item, index)}>{text}</SpLogin>
)
}
return text
}
return (
<SpPage className='page-coupon-center' scrollToTopBtn>
<SpScrollView className='list-scroll' fetch={fetch}>
{couponList.map((item, index) => (
<View className='coupon-item-wrap' key={`coupon-item__${index}`}>
<SpCoupon info={item} onClick={handleClickCouponItem.bind(this, item, index)}>
<Text>
{
{
0: '已领完',
1: '立即领取',
2: '已领取'
}[item.couponStatus]
}
{/* {time > item.send_begin_time && item.getted !== 2 && item.getted !== 1
? '立即领取'
: ''} */}
{/* {item.card_type === 'new_gift' && time < item.send_begin_time ? '未开始' : ''} */}
</Text>
<SpCoupon
info={item}
onClick={
item.couponStatus === 1 && !isLogin
? () => {}
: handleClickCouponItem.bind(this, item, index)
}
>
{renderCouponBtn(item, index)}
</SpCoupon>
</View>
))}

View File

@@ -5,7 +5,9 @@
.page-purchase-auth-email {
height: 100vh;
background-color: #fff;
.at-input{
margin-left: 0 !important;
}
&.select-component {
padding: 56px 50px;
box-sizing: border-box;