feat: 支付订单
This commit is contained in:
@@ -2,10 +2,12 @@ import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } f
|
||||
import { View, Text, Button, Swiper, SwiperItem, Image, Map, ScrollView } from '@tarojs/components'
|
||||
import { Cell, Avatar, Progress, Popover } from '@nutui/nutui-react-taro'
|
||||
import Taro, { useRouter, useShareAppMessage, useShareTimeline, useDidShow } from '@tarojs/taro'
|
||||
import dayjs, { locale } from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
// 导入API服务
|
||||
import DetailService from '../../services/detailService'
|
||||
import DetailService, { MATCH_STATUS} from '../../services/detailService'
|
||||
import { updateUserProfile, get_user_info } from '../../services/loginService'
|
||||
import { getCurrentLocation } from '../../utils/locationUtils'
|
||||
import { getCurrentLocation, calculateDistance } from '../../utils/locationUtils'
|
||||
import {
|
||||
useUserInfo,
|
||||
useUserActions,
|
||||
@@ -15,6 +17,8 @@ import { getTextColorOnImage } from '../../utils'
|
||||
import './index.scss'
|
||||
import { CommonPopup } from '@/components'
|
||||
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
const images = [
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png',
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/cf5a82ba-90af-4138-a1b3-9119adcde9e0.png',
|
||||
@@ -96,10 +100,10 @@ function StickyButton(props) {
|
||||
const { handleShare, handleJoinGame, detail } = props
|
||||
const userInfo = useUserInfo()
|
||||
const { id } = userInfo
|
||||
const { publisher_id, status } = detail || {}
|
||||
const { publisher_id, match_status, price } = detail || {}
|
||||
|
||||
const role = Number(publisher_id) === id ? 'ownner' : 'visitor'
|
||||
console.log(status, role)
|
||||
console.log(match_status, role)
|
||||
return (
|
||||
<View className="sticky-bottom-bar">
|
||||
<View className="sticky-bottom-bar-share-and-comment">
|
||||
@@ -117,7 +121,110 @@ function StickyButton(props) {
|
||||
<Text>🎾</Text>
|
||||
<Text>立即加入</Text>
|
||||
<View className='game-price'>
|
||||
<Text>¥ 65</Text>
|
||||
<Text>¥ {price}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// 球局信息
|
||||
function GameInfo(props) {
|
||||
const { detail, currentLocation } = props
|
||||
const { latitude, longitude, location, location_name, start_time, end_time } = detail || {}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude, // 纬度(必填)
|
||||
longitude, // 经度(必填)
|
||||
name: location_name, // 位置名(可选)
|
||||
address: location, // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const [c_latitude, c_longitude] = currentLocation
|
||||
const distance = calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000
|
||||
|
||||
const startTime = dayjs(start_time)
|
||||
const endTime = dayjs(end_time)
|
||||
const game_length = endTime.diff(startTime, 'minutes') / 60
|
||||
|
||||
const startMonth = startTime.format('M')
|
||||
const startDay = startTime.format('D')
|
||||
const theDayOfWeek = startTime.format('dddd')
|
||||
const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`
|
||||
const gameRange = `${startTime.format('HH:mm')} - ${endTime.format('HH:mm')}`
|
||||
|
||||
|
||||
return (
|
||||
<View className='detail-page-content-game-info'>
|
||||
{/* Date and Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather'>
|
||||
{/* Calendar and Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date'>
|
||||
{/* Calendar */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-calendar'>
|
||||
<View className="month">{startMonth}月</View>
|
||||
<View className="day">{startDay}</View>
|
||||
</View>
|
||||
{/* Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-date'>
|
||||
<View className="date">{startDate}</View>
|
||||
<View className="venue-time">{gameRange} ({game_length}小时)</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather'>
|
||||
{/* Weather icon */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-icon'>
|
||||
<Image className="weather-icon" src={img.ICON_WEATHER_SUN} />
|
||||
</View>
|
||||
{/* Weather text and temperature */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-text-temperature'>
|
||||
<Text>28℃ - 32℃</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Place */}
|
||||
<View className='detail-page-content-game-info-place'>
|
||||
{/* venue location message */}
|
||||
<View className='location-message'>
|
||||
{/* location icon */}
|
||||
<View className='location-message-icon'>
|
||||
<Image className='location-message-icon-image' src={img.ICON_DETAIL_MAP} />
|
||||
</View>
|
||||
{/* location message */}
|
||||
<View className='location-message-text'>
|
||||
{/* venue name and distance */}
|
||||
<View className='location-message-text-name-distance' onClick={openMap}>
|
||||
<Text>{location_name || '-'}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{distance.toFixed(1)}km</Text>
|
||||
<Image className='location-message-text-name-distance-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
{/* venue address */}
|
||||
<View className='location-message-text-address'>
|
||||
<Text>{location || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* venue map */}
|
||||
<View className='location-map'>
|
||||
{longitude && latitude && (
|
||||
<Map
|
||||
className='location-map-map'
|
||||
longitude={latitude}
|
||||
latitude={longitude}
|
||||
markers={[{ id: 1, latitude: longitude, longitude: latitude, iconPath: require('@/static/detail/icon-stark.svg'), width: 16, height: 16 }]}
|
||||
includePoints={[{ latitude: longitude, longitude: latitude }, { latitude: currentLocation[0], longitude: currentLocation[1] }]}
|
||||
includePadding={{ left: 50, right: 50, top: 50, bottom: 50 }}
|
||||
onError={() => {}}
|
||||
// hide business msg
|
||||
showLocation
|
||||
theme='dark'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -133,11 +240,13 @@ function Index() {
|
||||
// const [textColor, setTextColor] = useState<string []>([])
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const { params } = useRouter()
|
||||
const [currentLocation, setCurrentLocation] = useState([0, 0])
|
||||
const [currentLocation, setCurrentLocation] = useState<[number, number]>([0, 0])
|
||||
const { id, autoShare, from } = params
|
||||
const { fetchUserInfo, updateUserInfo } = useUserActions()
|
||||
|
||||
console.log('from', from)
|
||||
console.group('params')
|
||||
console.log(params)
|
||||
console.groupEnd()
|
||||
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -168,7 +277,7 @@ function Index() {
|
||||
}
|
||||
|
||||
const fetchDetail = async () => {
|
||||
const res = await DetailService.getDetail(242/* Number(id) */)
|
||||
const res = await DetailService.getDetail(243/* Number(id) */)
|
||||
if (res.code === 0) {
|
||||
console.log(res.data)
|
||||
setDetail(res.data)
|
||||
@@ -192,19 +301,9 @@ function Index() {
|
||||
sharePopupRef.current.show()
|
||||
}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude: detail?.longitude, // 纬度(必填)
|
||||
longitude: detail?.latitude, // 经度(必填)
|
||||
name: '上海体育场', // 位置名(可选)
|
||||
address: '上海市徐汇区肇嘉浜路128号', // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const handleJoinGame = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/orderCheck/index?id=${id}`,
|
||||
url: `/pages/orderCheck/index?gameId=${243/* id */}`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -364,76 +463,7 @@ function Index() {
|
||||
<Text className='detail-page-content-title-text'>{title}</Text>
|
||||
</View>
|
||||
{/* Date and Place and weather */}
|
||||
<View className='detail-page-content-game-info'>
|
||||
{/* Date and Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather'>
|
||||
{/* Calendar and Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date'>
|
||||
{/* Calendar */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-calendar'>
|
||||
<View className="month">3月</View>
|
||||
<View className="day">25</View>
|
||||
</View>
|
||||
{/* Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-date'>
|
||||
<View className="date">3月25日 周一</View>
|
||||
<View className="venue-time">19:00-21:00 (2小时)</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather'>
|
||||
{/* Weather icon */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-icon'>
|
||||
<Image className="weather-icon" src={img.ICON_WEATHER_SUN} />
|
||||
</View>
|
||||
{/* Weather text and temperature */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-text-temperature'>
|
||||
<Text>28℃ - 32℃</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Place */}
|
||||
<View className='detail-page-content-game-info-place'>
|
||||
{/* venue location message */}
|
||||
<View className='location-message'>
|
||||
{/* location icon */}
|
||||
<View className='location-message-icon'>
|
||||
<Image className='location-message-icon-image' src={img.ICON_DETAIL_MAP} />
|
||||
</View>
|
||||
{/* location message */}
|
||||
<View className='location-message-text'>
|
||||
{/* venue name and distance */}
|
||||
<View className='location-message-text-name-distance' onClick={openMap}>
|
||||
<Text>上海体育场</Text>
|
||||
<Text>·</Text>
|
||||
<Text>1.2km</Text>
|
||||
<Image className='location-message-text-name-distance-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
{/* venue address */}
|
||||
<View className='location-message-text-address'>
|
||||
<Text>上海市徐汇区肇嘉浜路128号</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* venue map */}
|
||||
<View className='location-map'>
|
||||
{longitude && latitude && (
|
||||
<Map
|
||||
className='location-map-map'
|
||||
longitude={latitude}
|
||||
latitude={longitude}
|
||||
markers={[{ id: 1, latitude: longitude, longitude: latitude, iconPath: require('@/static/detail/icon-stark.svg'), width: 16, height: 16 }]}
|
||||
includePoints={[{ latitude: longitude, longitude: latitude }, { latitude: currentLocation[0], longitude: currentLocation[1] }]}
|
||||
includePadding={{ left: 50, right: 50, top: 50, bottom: 50 }}
|
||||
onError={() => {}}
|
||||
// hide business msg
|
||||
showLocation
|
||||
theme='dark'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<GameInfo detail={detail} currentLocation={currentLocation} />
|
||||
{/* detail */}
|
||||
<View className='detail-page-content-detail'>
|
||||
{/* venue detail title and venue ordered status */}
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
|
||||
import { delay } from '@/utils'
|
||||
import orderService from '@/services/orderService'
|
||||
import detailService, { GameDetail } from '@/services/detailService'
|
||||
|
||||
const OrderCheck = () => {
|
||||
const { params } = useRouter()
|
||||
const { id, gameId } = params
|
||||
const [detail ,setDetail] = useState<GameDetail | {}>({})
|
||||
|
||||
useDidShow(async () => {
|
||||
const res = await detailService.getDetail(Number(gameId))
|
||||
console.log(res)
|
||||
if (res.code === 0) {
|
||||
setDetail(res.data)
|
||||
}
|
||||
})
|
||||
|
||||
//TODO: get order msg from id
|
||||
const handlePay = async () => {
|
||||
Taro.showLoading({
|
||||
title: '支付中...',
|
||||
mask: true
|
||||
})
|
||||
await delay(2000)
|
||||
const res = await orderService.createOrder(Number(gameId))
|
||||
if (res.code === 0) {
|
||||
const { payment_required, payment_params } = res.data
|
||||
if (payment_required) {
|
||||
const { timeStamp, nonceStr, package: package_, signType, paySign } = payment_params
|
||||
await Taro.requestPayment({
|
||||
timeStamp,
|
||||
nonceStr,
|
||||
package: package_,
|
||||
signType,
|
||||
paySign,
|
||||
success: async () => {
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
@@ -19,10 +45,23 @@ const OrderCheck = () => {
|
||||
Taro.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text>OrderCheck</Text>
|
||||
<Text>球局名称:{detail?.title || '-'}</Text>
|
||||
<Text>价格:¥{detail?.price || '-'}</Text>
|
||||
<Button onClick={handlePay}>支付</Button>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -20,6 +20,12 @@ export interface GameDetail {
|
||||
updated_at: string,
|
||||
}
|
||||
|
||||
export enum MATCH_STATUS {
|
||||
NOT_STARTED = 0, // 未开始
|
||||
IN_PROGRESS = 1, //进行中
|
||||
FINISHED = 2 //已结束
|
||||
}
|
||||
|
||||
// 响应接口
|
||||
export interface Response {
|
||||
code: string
|
||||
|
||||
46
src/services/orderService.ts
Normal file
46
src/services/orderService.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import httpService from './httpService'
|
||||
import type { ApiResponse } from './httpService'
|
||||
import { requestPayment } from '@tarojs/taro'
|
||||
|
||||
export interface SignType {
|
||||
/** 仅在微信支付 v2 版本接口适用 */
|
||||
MD5
|
||||
/** 仅在微信支付 v2 版本接口适用 */
|
||||
'HMAC-SHA256'
|
||||
/** 仅在微信支付 v3 版本接口适用 */
|
||||
RSA
|
||||
}
|
||||
|
||||
export interface PayMentParams {
|
||||
order_id: number,
|
||||
order_no: string,
|
||||
status: number,
|
||||
appId: string,
|
||||
timeStamp: string,
|
||||
nonceStr: string,
|
||||
package: string,
|
||||
signType: keyof SignType,
|
||||
paySign: string
|
||||
}
|
||||
|
||||
// 用户接口
|
||||
export interface OrderResponse {
|
||||
participant_id: number,
|
||||
payment_required: boolean,
|
||||
payment_params: PayMentParams
|
||||
}
|
||||
|
||||
// 发布球局类
|
||||
class OrderService {
|
||||
// 用户登录
|
||||
async createOrder(game_id: number): Promise<ApiResponse<OrderResponse>> {
|
||||
return httpService.post('/payment/create_order', { game_id }, {
|
||||
showLoading: true,
|
||||
})
|
||||
}
|
||||
|
||||
// async getOrderInfo()
|
||||
}
|
||||
|
||||
// 导出认证服务实例
|
||||
export default new OrderService()
|
||||
Reference in New Issue
Block a user