main
王文龙 2024-03-13 15:16:33 +08:00
parent 812f9b0e3d
commit 28c2adc9ec
20 changed files with 1150 additions and 42 deletions

View File

@ -76,6 +76,10 @@ export function registrationSubmit (data) {
return req.post('/registrationSubmit', data)
}
export function verifyRegistrationRecord (data) {
return req.post('/verifyRegistrationRecord', data)
}
export function registrationRecordList (data) {
return req.get('/registrationRecordList', data)
}
@ -88,8 +92,12 @@ export function scancodeAddcart (data) {
return req.post('/goods/scancodeAddcart', data)
}
export function cancelRegistrationRecord (data) {
return req.post('/cancelRegistrationRecord', data)
}
export function newWxaMsgTmpl (params = {}) {
if(isAlipay){
if (isAlipay) {
return req.get('/alitemplatemessage', params)
}
return req.get('/newtemplate', params)

View File

@ -17,7 +17,9 @@ const config = {
'pages/item/espier-detail', // 商品详情页面
'pages/article/index', // 文章页面
'pages/recommend/list', // 推荐软文列表
'pages/recommend/list', // 推荐软文列表 现在改为活动介绍
'pages/recommend/detail', // 活动介绍
'pages/recommend/status', // 活动状态
'pages/member/item-fav', // 收藏页面
'pages/custom/custom-page', // 自定义页面
@ -78,7 +80,8 @@ const config = {
'point-detail', // 积分明细
'point-rule', // 积分规则
'member-level', // 会员等级
'user-info' // 个人信息
'user-info', // 个人信息
'scan', // 员工扫码核销
]
},
{

BIN
src/assets/icon/map.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -132,7 +132,7 @@ function SpPage (props, ref) {
draft.pageTitle = pageTitle
draft.isTabBarPage = isTabBarPage
draft.showLeftContainer = !['/subpages/guide/index', '/pages/index'].includes(`/${page?.route}`)
draft.showCustomNavigation = ['/pages/cart/espier-index', '/pages/index', '/subpages/member/index', '/pages/category/index', '/pages/recommend/list'].includes(`/${page?.route}`)
draft.showCustomNavigation = ['/pages/cart/espier-index', '/pages/index', '/subpages/member/index', '/pages/category/index', '/pages/recommend/list', '/pages/recommend/list'].includes(`/${page?.route}`)
// draft.showCustomNavigation = true
draft.showNavCartIcon = ['/subpages/marketing/coupon-center', '/subpages/marketing/coupon', '/pages/item/list'].includes(`/${page?.route}`)
})

View File

@ -97,8 +97,8 @@
}
.left-container {
width: auto;
// margin-left: 60px;
margin-left: 26px;
margin-left: 60px;
// margin-left: 26px;
display: flex;
align-items: center;
.chazhao {

View File

@ -4,7 +4,7 @@
page {
.sp-page {
.sp-loading {
margin-top: 100px;
margin-top: 70px;
}
// &.has-navbar {
// padding-top: 0 !important;

View File

@ -0,0 +1,3 @@
export default {
navigationBarTitleText: '活动介绍'
}

View File

@ -0,0 +1,220 @@
import React, { useEffect } from 'react'
import { View, Text, Input, Image } from '@tarojs/components'
import Taro, { getCurrentInstance, useDidShow } from '@tarojs/taro'
import { SpPage, SpHtml, SpCheckbox } from '@/components'
import { useSelector } from 'react-redux'
import { getActiveDetail } from '@/api/community'
import { useImmer } from 'use-immer'
import api from '@/api'
import dayjs from 'dayjs'
import { AtIcon } from 'taro-ui'
import { classNames, navigateTo } from '@/utils'
import './detail.scss'
const initState = {
activeInfo: {},
isLoading: true,
visible: false,
checked: false,
formInfo: {},
status: ''
}
export default function detail () {
const $instance = getCurrentInstance()
const [state, setState] = useImmer(initState)
const { appName } = useSelector((_state) => _state.sys)
const { userInfo = {} } = useSelector((_state) => _state.user)
useDidShow(() => {
getActiveDetail()
}, [])
useEffect(() => {
setState(draft => {
draft.formInfo = {
username: userInfo.username,
mobile: userInfo.mobile,
}
})
}, [userInfo])
const getActiveDetail = async () => {
const { id: activity_id } = $instance.router.params
if (!activity_id) return
const { status, activity_info = {}, total_count, user_code } = await api.user.registrationActivity({ activity_id })
const _start = dayjs(activity_info.start_time * 1000)
const _end = dayjs(activity_info.end_time * 1000)
setState(draft => {
draft.isLoading = false,
draft.activeInfo = {
...activity_info,
user_code,
total_count,
start: `${_start.month() + 1}${_start.date()}`,
end: `${_end.month() + 1}${_end.date()}`,
status,
isLoading: false
}
})
}
const { isLoading, activeInfo = {}, visible, formInfo, checked, status } = state
const renderFooter = () => {
if (isLoading || status === 'passed') {
return null
}
return <View className={"bottom"}>
<View className='check-box'>
<SpCheckbox checked={checked} colors="#000" onChange={() => setState((draft) => { draft.checked = !checked })} />
<View>
<Text onClick={() => setState((draft) => { draft.checked = !checked })} >我已经阅读并同意</Text>
<Text
onClick={() => Taro.navigateTo({ url: '/subpages/auth/reg-rule?type=member_logout' })}
style={`color: #000000;text-decoration: underline;`}
>
{`${appName}活动细则》`}
</Text>
</View>
</View>
<View onClick={async () => {
if (!checked) {
const res = await Taro.showModal({
title: '提示',
content: `请先阅读并同意${appName}活动细则`,
showCancel: true,
cancel: '取消',
cancelText: '拒绝',
confirmText: '同意',
cancelColor: '#a5a5a5',
confirmColor: '#000'
})
if (!res.confirm) return
setState((draft) => {
draft.checked = true
draft.visible = true
})
}
setState((draft) => {
draft.visible = true
})
}} className={"btn"}>{!status ? '确认报名' : status === 'pending' ? '报名中' : '已报名'}</View>
</View>
}
const onIptChange = (e, name) => {
const { value } = e.detail
setState((draft) => {
draft.formInfo[name] = value
})
}
const handleSubmit = async () => {
if (!formInfo.username?.trim()) {
Taro.showToast({
title: '请输入姓名',
icon: 'none'
})
return
}
if (!formInfo.mobile?.trim()) {
Taro.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
const content = {
title: '',
sort: 1,
formdata: formItem.map((d) => {
return {
...d,
answer: formInfo[d.field_name]
}
})
}
const { status } = await api.user.registrationSubmit({
formdata: { content: JSON.stringify([content]) },
activity_id: activeInfo.activity_id
})
if (status) {
// Taro.showToast({
// title: '报名成功',
// icon: 'none'
// })
setState((draft) => {
draft.visible = false
draft.status = status
})
Taro.requestSubscribeMessage({
tmplIds: ['tGJR7BS3HM9xLD4QUdBwtTAJkgzfl7cw_8-ngd74JM4'],
success: () => {
// onSubscribe()
console.log("🚀 ~ onSubscribe:", 'onSubscribe')
},
fail: (err) => {
console.log("🚀 ~ err:", err)
// onSubscribe()
},
complete: () => {
Taro.navigateTo({ url: '/pages/recommend/status?success=true&id=' + activeInfo.activity_id })
}
})
}
}
const formItem = activeInfo.formdata?.key_index || []
return (
<SpPage renderFooter={!!activeInfo?.total_count ? <></> : renderFooter()} loading={isLoading} className='page-recommend-detail' isBlack>
<View className="activity_name">{activeInfo.activity_name}</View>
<View className="activity_time">{activeInfo.start} - {activeInfo.end}</View>
<View className="activity_addr">
<Image mode="widthFix" src={require('@/assets/icon/map.png')}></Image>
<Text>{activeInfo.address}</Text>
</View>
<SpHtml content={activeInfo.intro} />
<View className="sp-picker">
<View
className={classNames('mask', {
visible: visible
})}
onTap={() => setState((draft) => {
draft.visible = false
})}
catchtouchmove
></View>
<View
className={classNames('sp-picker-cnt', {
visible: visible
})}
>
<View className={classNames('sp-picker-hd')} catchtouchmove>
<Text className='center'>{'报名信息'}</Text>
<AtIcon onClick={() => setState((draft) => {
draft.visible = false
})} value='close' size={14} color='#000' ></AtIcon>
</View>
<View className='sp-picker-bd'>
{formItem.map((item, index) => (
<View className="user-form-item" key={`userinfo-item__${index}`}>
<View className="user-form-item-title">{item.field_title}</View>
<View className="user-form-item-input">
{item.form_element === 'text' ? <Input
name={item.field_name}
value={formInfo[item.field_name]}
class='input-field-1'
placeholder={`请输入`}
onInput={(e) => onIptChange(e, item.field_name)}
/> : null}
</View>
</View>
))}
<View onClick={handleSubmit} className={"btn"}>确认报名信息</View>
</View>
</View>
</View>
</SpPage>
)
}

View File

@ -0,0 +1,236 @@
.page-recommend-detail {
.sp-page-body {
padding: 30px 58px;
}
.activity_name {
font-size: 40px;
font-weight: bold;
margin-bottom: 30px;
}
.activity_time {
font-size: 26px;
// color: #999;
margin-bottom: 60px;
}
.activity_addr {
font-size: 26px;
// color: #999;
margin-bottom: 40px;
display: flex;
align-items: center;
> image {
width: 18px;
height: 24px;
margin-right: 20px;
}
}
.sp-picker {
.mask {
position: fixed;
z-index: 302;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
// color: #fff;
&.visible {
visibility: visible;
opacity: 1;
}
}
&-cnt {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transition: all 0.3s ease-in-out;
transform: translateY(100%);
z-index: 503;
background-color: #fff;
border-radius: 30rpx 30rpx 0 0;
&.visible {
transform: translateY(0);
}
}
&-hd {
padding: 30px 50px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: none;
position: relative;
font-size: 26px;
.center {
margin: auto;
}
.at-icon-close {
padding: 20px 30px;
position: absolute;
right: 20px;
}
}
&-bd {
padding: 0 50px 50px;
&-item {
display: flex;
align-items: center;
border-bottom: none;
padding: 0 90px;
view {
color: #000;
font-size: 26px;
line-height: 1;
}
.tit {
padding: 28px 50px 28px 30px;
box-sizing: border-box;
}
.content {
display: flex;
flex: 1;
color: #000;
.icon-qianwang-01 {
font-size: 20px;
}
}
}
.btn {
margin-top: 130px;
display: flex;
height: 80px;
align-items: center;
justify-content: center;
font-size: 32px;
line-height: 1;
background: #000;
color: #fff;
border-radius: 6px;
&.notop {
margin-top: 10px;
}
&-disabled {
background: #666;
}
}
.user-form {
margin-top: 30px;
&-item {
margin-bottom: 50px;
&-title {
font-size: 24px;
color: #000;
margin-bottom: 10px;
}
&-input {
background: #e5e5e5;
border-radius: 6px;
padding: 20px 40px;
color: #000;
position: relative;
.icon-arrowRight {
font-size: 34px;
position: absolute;
right: 30px;
top: 30%;
// transform: translateY(-50%);
color: #000;
rotate: 90deg;
}
}
&-phone {
width: 100%;
}
.picker {
height: 40px;
width: 100%;
color: #a5a5a5;
&-value {
color: #000;
&.placeholder {
color: #a5a5a5 !important;
}
}
}
}
}
}
}
.toast {
position: fixed;
z-index: 2;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 400px;
height: 120px;
background: #666;
color: #fff;
border-radius: 10px;
view {
font-size: 30px;
}
&.visible {
visibility: visible;
opacity: 1;
}
}
}
.sp-page-footer {
min-height: 200px;
padding: 0 50px;
.check-box {
margin-top: 100px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: 10px auto;
.icon-my {
font-size: 36px;
}
view {
font-size: 22px;
}
}
.bottom {
// position: fixed;
// bottom: 80px;
// left: 0;
// right: 0;
width: 100%;
margin-bottom: 50px;
.btn {
display: flex;
height: 80px;
align-items: center;
justify-content: center;
font-size: 32px;
line-height: 1;
background: #000;
color: #fff;
border-radius: 6px;
&.notop {
margin-top: 10px;
}
&-disabled {
background: #666;
}
}
}
}

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'
import Taro, { getCurrentInstance } from '@tarojs/taro'
import { View, Text, ScrollView, Picker, Video } from '@tarojs/components'
import { View, Text, ScrollView, Picker, Video, SwiperItem, Swiper, Image } from '@tarojs/components'
import { SpAddress } from '@/components'
import { withPager, withBackToTop } from '@/hocs'
import { connect } from 'react-redux'
@ -50,7 +50,24 @@ export default class RecommendList extends Component {
windowWidth: 750,
page: {
isLoading: true
}
},
currentIndex: 0,
// swiperList: [
// {
// type: 'image',
// src: process.env.APP_IMAGE_CDN_NEW + '/index/22.jpg'
// },
// {
// type: 'image',
// src: process.env.APP_IMAGE_CDN_NEW + '/index/33.jpg'
// },
// // {
// // type: 'video',
// // src: 'https://espier-oss-cdn.oss-cn-shanghai.aliyuncs.com/default_project/wxAssets/index/video-1.mp4'
// // }
// ],
swiperList: [],
activeInfo: {}
}
}
@ -79,17 +96,17 @@ export default class RecommendList extends Component {
// this.setState({
// list: []
// })
setTimeout(() => {
// this.nextPage()
// Taro.hideLoading()
this.setState({
page: {
isLoading: false
}
})
}, 200)
// setTimeout(() => {
// // this.nextPage()
// // Taro.hideLoading()
// this.setState({
// page: {
// isLoading: false
// }
// })
// }, 200)
// this.praiseNum()
this.getActiveDetail()
}
onShareAppMessage () {
@ -114,6 +131,24 @@ export default class RecommendList extends Component {
}
}
getActiveDetail = async (id = 1) => {
const { status, activity_info = {}, total_count } = await api.user.registrationActivity({ activity_id: id })
this.setState({
page: {
isLoading: false,
},
activeInfo: {
...activity_info,
total_count,
status
},
swiperList: activity_info.pics.map(item => ({
type: 'image',
src: item
}))
})
}
async fetch (params) {
const { page_no: page, page_size: pageSize } = params
const { columnList, areaList } = this.state
@ -563,6 +598,23 @@ export default class RecommendList extends Component {
play
})
}
onSwiperChange = (e) => {
const { current } = e.detail
this.setState({
currentIndex: current
})
}
handleApply = () => {
const { activeInfo = {} } = this.state
let url = '/pages/recommend/detail?id='
if (activeInfo.total_count) {
url = `/pages/recommend/status?id=`
}
Taro.navigateTo({
url: url + activeInfo.activity_id
})
}
render () {
const {
@ -580,6 +632,9 @@ export default class RecommendList extends Component {
isShowSearch,
isSpAddressOpened,
play,
swiperList,
currentIndex,
activeInfo
} = this.state
const { colors } = this.props
let address = info.province + info.city
@ -587,7 +642,7 @@ export default class RecommendList extends Component {
const height = windowWidth * 9 / 17
return (
<SpPage renderFooter={<SpTabbar />} loading={page.isLoading} className='has-nav' title="" isBlack>
<SpPage renderFooter={<SpTabbar />} loading={page.isLoading} className='has-nav' title="" isBlack isTop>
<View className='page-recommend-list'>
{false && <>
<View className='recommend-list__toolbar'>
@ -722,7 +777,7 @@ export default class RecommendList extends Component {
)}
</ScrollView>
</>}
{page.isLoading ? <Loading></Loading> :
{/* {page.isLoading ? <Loading></Loading> :
<View className="recommend-content">
<Video
id="recommend-video"
@ -751,21 +806,6 @@ export default class RecommendList extends Component {
!play && this.handleVideoClick(true)
}}
/>
{/* <View className="top-txt">
<View className="top-txt-title">BIRKENSTOCK产品的核心和灵魂</View>
<View className="top-txt-dec">
<View className="top-txt-dec-item">标志性的鞋床是每一双BIRKENSTOCK的核心和灵魂</View>
<View className="top-txt-dec-item"> 其精美的解剖学设计是Birkenstock家族世代相传的知识结晶</View>
<View className="top-txt-dec-item">它的发明源于一个朴素但难以实现的愿望让人们能够自然地行走</View>
<View className="top-txt-dec-item">为了实现这个功能</View>
<View className="top-txt-dec-item">鞋床的轮廓被精确设计以用于从解剖学上符合支撑人类脚部的功效</View>
<View className="top-txt-dec-item">仿佛在沙滩上的脚印</View>
<View className="top-txt-dec-item">它的分层结构采用独特且天然的材料组合</View>
<View className="top-txt-dec-item">经过一系列专门的制造工艺加工确保产品拥有最大程度上的耐用性和灵活性</View>
<View className="top-txt-dec-item">这也意味着BIRKENSTOCK鞋床的功能是无可匹敌的</View>
</View>
<View className="line"></View>
</View> */}
<View className="content-item">
<SpImage height={height * 2} width={windowWidth * 2} mode="aspectFill" src='recommend/03.jpg' isShowMenuByLongpress={false} lazyLoad isNew />
<SpImage height={height * 2} width={windowWidth * 2} mode="aspectFill" src='recommend/02.jpg' isShowMenuByLongpress={false} lazyLoad isNew />
@ -777,6 +817,69 @@ export default class RecommendList extends Component {
<SpImage height={height * 2} width={windowWidth * 2} mode="aspectFill" src='recommend/09.jpg' isShowMenuByLongpress={false} lazyLoad isNew />
</View>
</View>
} */}
{page.isLoading ? <Loading></Loading> :
<View className="active-content">
<Swiper
className={classNames('swiper')}
indicatorColor="#999"
indicatorActiveColor="#333"
current={currentIndex}
interval={5000}
duration={800}
// indicatorDots
circular
autoplay={true}
onChange={this.onSwiperChange}
>
{swiperList.map((item, index) => (
<SwiperItem key={index} className="" style={{ height: "100%" }}>
{item.type === "image" && (
<Image
src={item.src}
style={{ width: "100%", height: "100%", objectPosition: "center" }}
mode={'aspectFill'}
showMenuByLongpress={false}
/>
)}
{item.type === "video" && (
<Video
id="video"
src={item.src}
poster={item.poster}
initialTime={0}
controls={false}
autoplay={true}
loop={true}
muted={true}
showProgress={false}
showFullscreenBtn={false}
showPlayBtn={false}
showCenterPlayBtn={false}
enableProgressGesture={false}
objectFit="cover"
style={{ width: "100%", height: "100%" }}
onPlay={() => {
}}
onEnded={() => {
}}
/>
)}
</SwiperItem>
))}
</Swiper>
<View className="item-btns">
<View className="item-tit">{activeInfo.activity_name}</View>
{activeInfo.end_time * 1000 >= Date.now() && <View className="item-btn" onClick={this.handleApply}>
<View className="item-btn-txt">{activeInfo.total_count ? '已报名' : '活动申请'}</View>
</View>}
</View>
<View className='spot-pagination'>
{swiperList.map((_, index) => (
<View key={index} className={'spot-pagination-bullet ' + ((currentIndex == index) ? 'spot-pagination-bullet-active' : "")} style={{ width: 1 / swiperList.length * 100 + '%' }}></View>
))}
</View>
</View>
}
<BackToTop show={showBackToTop} onClick={this.scrollBackToTop} />

View File

@ -1,7 +1,8 @@
@import '../../style/imports';
.loading {
margin-top: 60px;
.sp-loading {
margin-top: 70px;
}
.page-recommend-list {
.recommend-content {
width: 100%;
@ -38,6 +39,75 @@
margin-bottom: 50px;
}
}
.active-content {
position: relative;
.swiper {
width: 100%;
height: calc(100vh - #{$page-ipx-footer-height} - var(--status-bar-height));
transition: height 0.3s ease-in-out;
position: relative;
}
.spot-pagination {
animation: fadeInAnimation 0.35s ease-in;
position: absolute;
left: 50%;
transform: translateX(-50%);
// bottom: 10%;
bottom: 100px;
display: flex;
justify-content: center;
width: 80%;
border-radius: 10%;
.spot-pagination-bullet {
height: 4px;
width: 30px;
background: #ddd;
transition: all 0.3s ease-in-out 0s;
}
//
.spot-pagination-bullet-active {
background: #fff;
}
}
.item-btns {
display: flex;
flex-direction: column;
align-items: flex-start;
position: absolute;
left: 66px;
bottom: 170px;
z-index: 10;
// justify-content: flex-start;
.item-tit {
font-size: 42px;
font-weight: bold;
margin-bottom: 36px;
color: #fff;
}
.item-btn {
// width: 180px;
// height: 56px;
padding: 30px 70px 30px 90px;
color: #fff;
font-size: 28px;
border-radius: 10px;
background-color: #00000085;
border-width: 2px;
border-style: solid;
border-color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 22px;
line-height: 1;
.icon-my {
font-size: 28px;
font-weight: bold;
margin-left: 16px;
}
}
}
}
/* #ifdef alipay */
.filter-bar {
position: absolute;

View File

@ -0,0 +1,3 @@
export default {
navigationBarTitleText: ''
}

View File

@ -0,0 +1,167 @@
import React, { useEffect } from 'react'
import { View, Text, Input, Image } from '@tarojs/components'
import Taro, { getCurrentInstance, useDidShow } from '@tarojs/taro'
import { SpPage, SpHtml, SpCheckbox, SpImage } from '@/components'
import { useSelector } from 'react-redux'
import { getActiveDetail } from '@/api/community'
import { useImmer } from 'use-immer'
import api from '@/api'
import dayjs from 'dayjs'
import { AtIcon } from 'taro-ui'
import { classNames, navigateTo, isArray } from '@/utils'
import QRCode from 'qrcode'
import './status.scss'
const initState = {
cur_activity_info: [],
loading: true,
activeInfo: {},
qrUrl: ''
}
export default function status () {
const $instance = getCurrentInstance()
const [state, setState] = useImmer(initState)
const { cur_activity_info, loading, activeInfo, qrUrl } = state
useEffect(() => {
getActiveDetail()
}, [])
const getActiveDetail = async () => {
const { id, success } = $instance.router.params
const { activity_info = {}, total_count, user_code, recordList = {} } = await api.user.registrationActivity({ activity_id: id })
const _start = dayjs(activity_info.start_time * 1000)
const _end = dayjs(activity_info.end_time * 1000)
let url = ''
const qrData = JSON.stringify({
activity_id: id,
user_code: user_code || ''
})
await QRCode.toDataURL(qrData).then((_url) => {
url = _url
})
const _item = recordList.list?.[0] || {}
const list = _item.content?.[0]?.formdata || []
const record = {}
list.map((item) => {
record[item.field_name] = item.answer || ''
})
setState(draft => {
draft.activeInfo = {
...activity_info,
total_count,
start: `${_start.month() + 1}${_start.date()}`,
end: `${_end.month() + 1}${_end.date()}`,
status: _item.is_write_off === '1' ? 'done' : _item.status,
...record
}
draft.loading = false
draft.qrUrl = url
})
}
const subscribe = async () => {
// await api.user.subscribeGoods(info.itemId, { distributor_id: dtid })
// const { template_id } = await api.user.newWxaMsgTmpl({
// temp_name: 'yykweishop',
// source_type: 'goods'
// })
Taro.requestSubscribeMessage({
tmplIds: ['tGJR7BS3HM9xLD4QUdBwtTAJkgzfl7cw_8-ngd74JM4'],
success: () => {
// onSubscribe()
console.log("🚀 ~ onSubscribe:", 'onSubscribe')
},
fail: (err) => {
console.log("🚀 ~ err:", err)
// onSubscribe()
}
})
}
const fetchDetail = async () => {
const { content } = await api.user.registrationRecordInfo({
record_id: $instance.router.params.record_id
})
let answer_data = []
content.map((item) => {
if (item.formdata && item.formdata.length > 0) {
item.formdata.map((sec_item) => {
if (isArray(sec_item.answer)) {
if (sec_item.form_element === 'checkbox') {
sec_item.answer = sec_item.answer.join(',')
}
if (sec_item.form_element === 'area') {
sec_item.answer = sec_item.answer.join('')
}
}
answer_data.push({
field_title: sec_item.field_title,
answer: sec_item.answer
})
})
}
})
setState({
cur_activity_info: answer_data
})
}
const handleCancel = async () => {
const res = await Taro.showModal({
title: '提示',
content: `确定要取消预约吗?`,
showCancel: true,
cancel: '取消',
cancelText: '取消',
confirmText: '确认',
cancelColor: '#a5a5a5',
confirmColor: '#000'
})
if (!res.confirm) return
const { id } = $instance.router.params
await api.user.cancelRegistrationRecord({
activity_id: id
})
navigateTo('/pages/recommend/list')
}
return (
<SpPage loading={loading} title={activeInfo.status === 'pending' ? '待审核' : '预约成功'} className='page-recommend-status' isBlack>
<View className="">
<SpCheckbox checked={true} colors="#000" label={activeInfo.status === 'pending' ? '待审核' : activeInfo.status === 'done' ? '已签到' : "已预约"}></SpCheckbox>
</View>
<View className="activity_name">{activeInfo?.activity_name || ''}</View>
<View className="activity_info">
<View className="activity_info-item flex-col">
<Text className="title">姓名</Text>
<Text className="value">{activeInfo?.username}</Text>
</View>
<View className="activity_info-item flex-col">
<Text className="title">手机号</Text>
<Text className="value">{activeInfo?.mobile}</Text>
</View>
</View>
<View className="activity_time flex-col">
<Text className="title">活动时间</Text>
<Text className="value">{activeInfo?.start} - {activeInfo?.end}</Text>
</View>
<View className="activity_addr flex-col">
<Text className="title">活动地点</Text>
<View className="value">
<Image mode="widthFix" src={require('@/assets/icon/map.png')}></Image>
<Text className='value'>{activeInfo?.address}</Text></View>
</View>
<View className="activity_intro" onClick={() => Taro.navigateTo({ url: '/pages/recommend/detail?id=' + activeInfo?.activity_id })}>活动介绍</View>
{activeInfo.status === 'passed' && activeInfo.status !== 'done' ? <View className="code-box flex-col">
<SpImage src={`member/logo-lan.png`} height={40} mode='heightFix' isNew />
<View className="img-box">
<Image className='member-code-box-qr' mode='aspectFit' src={qrUrl} />
</View>
<View className="code-box-txt">入场请出示预约凭证</View>
</View> : <View style={'height:315px;'}></View>}
{activeInfo.status !== 'done' && <View className="cancel-txt" onClick={handleCancel}>取消预约</View>}
</SpPage>
)
}

View File

@ -0,0 +1,99 @@
.page-recommend-status {
padding: 10px 58px;
.sp-checkbox-new {
padding: 30px 0;
.icon-checkbox-xuanzhong {
font-size: 30px;
}
.sp-checkbox-new__label {
font-size: 40px;
}
}
.activity_name {
font-size: 40px;
font-weight: bold;
margin-bottom: 36px;
}
.activity_info {
display: flex;
align-items: center;
margin-bottom: 30px;
&-item {
flex: 1;
margin-right: 30px;
&:last-child {
margin-right: 0;
}
}
}
.flex-col {
display: flex;
flex-direction: column;
.title {
font-size: 24px;
margin-bottom: 2px;
color: #000;
}
.value {
color: #999;
font-size: 24px;
display: flex;
align-items: center;
> image {
width: 18px;
height: 24px;
margin-right: 20px;
}
}
}
.activity_time {
font-size: 24px;
color: #999;
margin-bottom: 50px;
}
.activity_addr {
font-size: 24px;
color: #999;
}
.activity_intro {
font-size: 30px;
text-decoration: underline;
margin: 90px 0 50px;
}
.img-box {
width: 300px;
height: 300px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background-color: #fff;
border: 1px solid #00000070;
margin: 34px 0 26px;
.member-code-box-qr {
width: 100%;
height: 100%;
scale: 1.2;
}
}
.code-box {
margin: 0 auto;
width: 500px;
padding: 64px 100px;
border-radius: 10px;
background-color: #e5e5e5;
align-items: center;
box-sizing: border-box;
&-txt {
font-size: 22px;
// color: #999;
}
}
.cancel-txt {
font-size: 18px;
text-align: center;
margin-top: 30px;
text-decoration: underline;
}
}

View File

@ -103,7 +103,8 @@ const initialState = {
zitiNum: 0,
deposit: 0,
isOpen: false,
loading: false
loading: false,
isInternal: false,
}
function MemberIndex (props) {
@ -299,6 +300,7 @@ function MemberIndex (props) {
})
setState((draft) => {
draft.deposit = memberRes.deposit / 100
draft.isInternal = memberRes?.memberInfo?.is_internal
})
dispatch(updateUserInfo(memberRes))
}
@ -485,10 +487,16 @@ function MemberIndex (props) {
/>
</SpLogin>
</View>
<View className='header-hd__body' onClick={isLogin && handleClickLink.bind(this, '/subpages/member/user-info')}>
<View className='username-wrap'>
<View className='header-hd__body' >
<View className='username-wrap' onClick={isLogin && handleClickLink.bind(this, '/subpages/member/user-info')}>
<View className='join-us'>{VipGradeDom()}</View>
</View>
{state.isInternal && <SpLogin newUser={isNewUser}>
<View className='username-wrap san-btn' onClick={handleClickLink.bind(this, '/subpages/member/scan')}>
扫码入口
</View>
</SpLogin>}
</View>
{/* <View className='header-hd__footer'>
{config.menu.member_code && (

View File

@ -76,6 +76,17 @@
}
}
.username-wrap {
padding: 20px 0;
}
.san-btn {
padding: 14px 30px;
align-items: center;
justify-content: center;
font-size: 28px;
line-height: 1;
background: #00000085;
color: #fff;
border-radius: 6px;
}
.user-grade-name {
line-height: 35px;

View File

@ -0,0 +1,3 @@
export default {
navigationBarTitleText: '活动扫码',
}

113
src/subpages/member/scan.js Normal file
View File

@ -0,0 +1,113 @@
import React, { useEffect } from 'react'
import { View, Text, Input, Image } from '@tarojs/components'
import Taro, { getCurrentInstance, useDidShow } from '@tarojs/taro'
import { SpPage, SpHtml, SpCheckbox } from '@/components'
import { useSelector } from 'react-redux'
import { getActiveDetail } from '@/api/community'
import { useImmer } from 'use-immer'
import api from '@/api'
import dayjs from 'dayjs'
import { AtIcon } from 'taro-ui'
import { classNames, navigateTo, getQueryVariable } from '@/utils'
import './scan.scss'
const initState = {
list: []
}
export default function scan () {
const [state, setState] = useImmer(initState)
useEffect(() => {
getRecords()
}, [])
const handleScan = async (e) => {
e?.stopPropagation()
try {
Taro.scanCode().then(async (res) => {
if (res.errMsg != 'scanCode:ok') {
throw new Error('识别失败')
}
Taro.showLoading({ title: '' })
const reqData = JSON.parse(res.result)
await api.user.verifyRegistrationRecord(reqData)
await getRecords()
Taro.hideLoading()
const _res = await Taro.showModal({
title: '提示',
content: `验证成功,是否继续扫码?`,
showCancel: true,
cancelText: '继续',
confirmText: '返回',
cancelColor: '#a5a5a5',
confirmColor: '#000'
})
if (_res.confirm) return
handleScan()
}).catch((error) => {
Taro.hideLoading()
error.errMsg !== 'scanCode:fail cancel' && Taro.showToast({
title: '识别失败!请重试~',
icon: 'none'
})
})
} catch (error) {
Taro.showToast({
title: '识别失败!请重试~',
icon: 'none'
})
}
}
const getRecords = async () => {
const data = await api.user.registrationRecordList()
const _list = (data?.list || []).filter(n => n.is_write_off == 1).map(item => {
let formData = item.content?.[0]?.formdata || []
if (formData.length > 0) {
formData.forEach(_item => {
item[_item.field_name] = _item.answer
})
}
item.date = item.is_write_off_date_format ? dayjs(item.is_write_off_date_format).format('YYYY-MM-DD') : ''
return item
})
setState((draft) => {
draft.list = _list
})
}
return (
<SpPage className='page-member-scan' title='活动扫码' isBlack>
<View className='scan__btn' onClick={handleScan}>扫码</View>
<View className="txt">活动扫码记录</View>
<View className="item">
<View className='left'>
<Text>活动名称</Text>
<Text>姓名</Text>
<Text>签到时间</Text>
{/* <Text>手机号</Text> */}
{/* <Text>签到时间</Text> */}
</View>
<View className='right'>
<Text>状态</Text>
</View>
</View>
<View className="scroll-box">
{state.list?.map((item, index) => {
return (
<View className="item" key={index}>
<View className='left'>
<Text>{item.activity_name}</Text>
<Text>{item.username}</Text>
<Text>{item.date}</Text>
{/* <Text>{item.mobile}</Text> */}
</View>
<View className='right'>
{item.is_write_off == 1 ? '已签到' : '未签到'}
</View>
</View>
)
})}
</View>
</SpPage>
)
}

View File

@ -0,0 +1,61 @@
.page-member-scan {
height: 95vh;
overflow: hidden;
.sp-page-body {
padding: 30px 40px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.scan__btn {
width: 100%;
height: 80px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
line-height: 1;
background: #000;
color: #fff;
border-radius: 6px;
}
.txt {
font-size: 32px;
margin: 30px auto;
font-weight: bold;
text-align: center;
}
.scroll-box {
// @include page-scroll(calc(var(--nav-height) + 150px), 40px);
// height: calc(var(--nav-height) + 150px);
margin-bottom: 10px;
box-sizing: border-box;
flex-direction: column;
display: flex;
flex: 1;
overflow-y: scroll;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
}
.left {
display: flex;
align-items: center;
> text {
display: inline-block;
width: 180px;
margin-right: 10px;
@include text-overflow();
&::last-child {
margin-right: 0;
}
}
}
.right {
flex: 1;
}
}

View File

@ -619,7 +619,7 @@ function MemberUserInfo () {
{/* <Checkbox value='选中' checked={state.checked} >选中</Checkbox> */}
<SpCheckbox checked={checked} colors="#000" onChange={() => setState((draft) => { draft.checked = !checked })} />
<View>
<Text onChange={() => setState((draft) => { draft.checked = !draft.checked })} >我已经阅读并同意</Text>
<Text onClick={() => setState((draft) => { draft.checked = !draft.checked })} >我已经阅读并同意</Text>
<Text
onClick={() => Taro.navigateTo({ url: '/subpages/auth/reg-rule?type=member_logout' })}
style={`color: #000000`}