feat: migrate comprehensive venue module
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { authStorage, request } from './request'
|
||||
import { toNumber } from './mappers'
|
||||
|
||||
const toMember = (backendMember = {}) => ({
|
||||
id: backendMember.id ?? null,
|
||||
openid: backendMember.openid || '',
|
||||
unionid: backendMember.unionid || '',
|
||||
nickname: backendMember.nickname || '',
|
||||
phone: backendMember.phone || '',
|
||||
avatar_url: backendMember.avatar_url || '',
|
||||
balance: backendMember.balance || '0.00',
|
||||
is_active: backendMember.is_active === true,
|
||||
created_at: backendMember.created_at || ''
|
||||
})
|
||||
|
||||
const toWechatLogin = (backendData = {}) => ({
|
||||
access_token: backendData.access_token || '',
|
||||
token_type: backendData.token_type || 'Bearer',
|
||||
expires_in: toNumber(backendData.expires_in),
|
||||
member: toMember(backendData.member)
|
||||
})
|
||||
|
||||
export const authApi = {
|
||||
async wechatLogin(payload = {}) {
|
||||
const data = typeof payload === 'string' ? { code: payload } : payload
|
||||
const loginData = await request({
|
||||
url: '/api/auth/wechat-login',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: data.code,
|
||||
nickname: data.nickname || '',
|
||||
avatar_url: data.avatar_url || ''
|
||||
}
|
||||
}).then(toWechatLogin)
|
||||
|
||||
authStorage.setToken(loginData.access_token)
|
||||
return loginData
|
||||
},
|
||||
logout() {
|
||||
authStorage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
export { authStorage }
|
||||
@@ -0,0 +1,90 @@
|
||||
import { request } from './request'
|
||||
import { toArray, toNumber, toPageQuery, toPagination } from './mappers'
|
||||
import { toOrderBrief } from './order'
|
||||
|
||||
const toBookingCourse = (backendCourse = {}) => ({
|
||||
id: backendCourse.id ?? null,
|
||||
title: backendCourse.title || '',
|
||||
cover_image: backendCourse.cover_image || '',
|
||||
coach_name: backendCourse.coach_name || '',
|
||||
start_at: backendCourse.start_at || '',
|
||||
end_at: backendCourse.end_at || '',
|
||||
capacity: toNumber(backendCourse.capacity),
|
||||
booked_count: toNumber(backendCourse.booked_count),
|
||||
remaining_count: toNumber(backendCourse.remaining_count),
|
||||
price: backendCourse.price || '0.00',
|
||||
courts: toArray(backendCourse.courts)
|
||||
})
|
||||
|
||||
const toPrivateCourt = (backendCourt = {}) => ({
|
||||
id: backendCourt.id ?? null,
|
||||
name: backendCourt.name || '',
|
||||
court_type: backendCourt.court_type || '',
|
||||
court_type_label: backendCourt.court_type_label || ''
|
||||
})
|
||||
|
||||
const toCourseBooking = (backendBooking = {}) => ({
|
||||
id: backendBooking.id ?? null,
|
||||
course_id: backendBooking.course_id ?? null,
|
||||
order_id: backendBooking.order_id ?? null,
|
||||
status: backendBooking.status || '',
|
||||
status_label: backendBooking.status_label || backendBooking.status || '',
|
||||
expire_at: backendBooking.expire_at || null,
|
||||
checked_in_at: backendBooking.checked_in_at || null,
|
||||
cancelled_at: backendBooking.cancelled_at || null,
|
||||
cancel_by: backendBooking.cancel_by || '',
|
||||
cancel_reason: backendBooking.cancel_reason || '',
|
||||
can_cancel: backendBooking.can_cancel === true,
|
||||
cancel_deadline_at: backendBooking.cancel_deadline_at || null,
|
||||
created_at: backendBooking.created_at || '',
|
||||
course: toBookingCourse(backendBooking.course),
|
||||
order: toOrderBrief(backendBooking.order)
|
||||
})
|
||||
|
||||
export const toPrivateBooking = (backendBooking = {}) => ({
|
||||
id: backendBooking.id ?? null,
|
||||
order_id: backendBooking.order_id ?? null,
|
||||
status: backendBooking.status || '',
|
||||
status_label: backendBooking.status_label || backendBooking.status || '',
|
||||
expire_at: backendBooking.expire_at || null,
|
||||
start_at: backendBooking.start_at || null,
|
||||
end_at: backendBooking.end_at || null,
|
||||
courts: toArray(backendBooking.courts).map(toPrivateCourt),
|
||||
checked_in_at: backendBooking.checked_in_at || null,
|
||||
cancelled_at: backendBooking.cancelled_at || null,
|
||||
cancel_by: backendBooking.cancel_by || '',
|
||||
cancel_reason: backendBooking.cancel_reason || '',
|
||||
created_at: backendBooking.created_at || '',
|
||||
order: toOrderBrief(backendBooking.order)
|
||||
})
|
||||
|
||||
export const bookingApi = {
|
||||
getCourseBookings(params = {}) {
|
||||
return request({
|
||||
url: '/api/bookings/courses',
|
||||
data: toPageQuery(params)
|
||||
}).then((data) => toPagination(data, toCourseBooking))
|
||||
},
|
||||
getCourseBookingDetail(id) {
|
||||
return request({
|
||||
url: `/api/bookings/courses/${id}`
|
||||
}).then(toCourseBooking)
|
||||
},
|
||||
cancelCourseBooking(id) {
|
||||
return request({
|
||||
url: `/api/bookings/courses/${id}/cancel`,
|
||||
method: 'POST'
|
||||
}).then(toCourseBooking)
|
||||
},
|
||||
getPrivateBookings(params = {}) {
|
||||
return request({
|
||||
url: '/api/bookings/private',
|
||||
data: toPageQuery(params)
|
||||
}).then((data) => toPagination(data, toPrivateBooking))
|
||||
},
|
||||
getPrivateBookingDetail(id) {
|
||||
return request({
|
||||
url: `/api/bookings/private/${id}`
|
||||
}).then(toPrivateBooking)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { request } from './request'
|
||||
import { compactParams, toArray, toNumber, toPageQuery, toPagination } from './mappers'
|
||||
import { toCourse } from './course'
|
||||
|
||||
const toBanner = (backendBanner = {}) => ({
|
||||
id: backendBanner.id ?? null,
|
||||
title: backendBanner.title || '',
|
||||
image: backendBanner.image || '',
|
||||
sort_order: toNumber(backendBanner.sort_order)
|
||||
})
|
||||
|
||||
const toArticle = (backendArticle = {}) => ({
|
||||
id: backendArticle.id ?? null,
|
||||
title: backendArticle.title || '',
|
||||
cover_image: backendArticle.cover_image || '',
|
||||
published_at: backendArticle.published_at || null,
|
||||
created_at: backendArticle.created_at || '',
|
||||
content_html: backendArticle.content_html || '',
|
||||
updated_at: backendArticle.updated_at || ''
|
||||
})
|
||||
|
||||
const toHomeContent = (backendData = {}) => ({
|
||||
banners: toArray(backendData.banners).map(toBanner),
|
||||
articles: toArray(backendData.articles).map(toArticle),
|
||||
upcoming_courses: toArray(backendData.upcoming_courses).map(toCourse),
|
||||
venue: {
|
||||
name: backendData.venue?.name || ''
|
||||
}
|
||||
})
|
||||
|
||||
export const contentApi = {
|
||||
getHomeContent(params = {}) {
|
||||
return request({
|
||||
url: '/api/contents/home',
|
||||
data: compactParams({
|
||||
banner_limit: params.banner_limit || params.bannerLimit,
|
||||
article_limit: params.article_limit || params.articleLimit
|
||||
})
|
||||
}).then(toHomeContent)
|
||||
},
|
||||
getArticles(params = {}) {
|
||||
return request({
|
||||
url: '/api/contents/articles',
|
||||
data: toPageQuery(params)
|
||||
}).then((data) => toPagination(data, toArticle))
|
||||
},
|
||||
getArticleDetail(id) {
|
||||
return request({
|
||||
url: `/api/contents/articles/${id}`
|
||||
}).then(toArticle)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { request } from './request'
|
||||
import { compactParams, toArray, toNumber, toPageQuery, toPagination } from './mappers'
|
||||
|
||||
const toCourseCourt = (backendCourt = {}) => ({
|
||||
id: backendCourt.id ?? null,
|
||||
name: backendCourt.name || '',
|
||||
court_type: backendCourt.court_type || '',
|
||||
court_type_label: backendCourt.court_type_label || ''
|
||||
})
|
||||
|
||||
export const toCourse = (backendCourse = {}) => ({
|
||||
id: backendCourse.id ?? null,
|
||||
title: backendCourse.title || '',
|
||||
cover_image: backendCourse.cover_image || '',
|
||||
coach_id: backendCourse.coach_id ?? null,
|
||||
coach_name: backendCourse.coach_name || '',
|
||||
start_at: backendCourse.start_at || '',
|
||||
end_at: backendCourse.end_at || '',
|
||||
capacity: toNumber(backendCourse.capacity),
|
||||
booked_count: toNumber(backendCourse.booked_count),
|
||||
remaining_count: toNumber(backendCourse.remaining_count),
|
||||
price: backendCourse.price || '0.00',
|
||||
status: backendCourse.status || '',
|
||||
status_label: backendCourse.status_label || backendCourse.status || '',
|
||||
courts: toArray(backendCourse.courts).map(toCourseCourt),
|
||||
detail_html: backendCourse.detail_html || '',
|
||||
created_at: backendCourse.created_at || '',
|
||||
updated_at: backendCourse.updated_at || ''
|
||||
})
|
||||
|
||||
const toCourseQuery = (params = {}) => compactParams({
|
||||
...toPageQuery(params),
|
||||
date: params.date,
|
||||
coach_id: params.coachId || params.coach_id,
|
||||
court_type: params.courtType || params.court_type
|
||||
})
|
||||
|
||||
export const courseApi = {
|
||||
getCourses(params = {}) {
|
||||
return request({
|
||||
url: '/api/courses',
|
||||
data: toCourseQuery(params)
|
||||
}).then((data) => toPagination(data, toCourse))
|
||||
},
|
||||
getCourseDetail(id) {
|
||||
return request({
|
||||
url: `/api/courses/${id}`
|
||||
}).then(toCourse)
|
||||
},
|
||||
createCourseBooking(courseId) {
|
||||
return request({
|
||||
url: '/api/bookings/course',
|
||||
method: 'POST',
|
||||
data: { course_id: courseId }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export const toNumber = (value, defaultValue = 0) => {
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : defaultValue
|
||||
}
|
||||
|
||||
export const toArray = (value) => (Array.isArray(value) ? value : [])
|
||||
|
||||
export const compactParams = (params = {}) => {
|
||||
return Object.entries(params).reduce((result, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
export const toPageQuery = (params = {}) => compactParams({
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || params.page_size || 20
|
||||
})
|
||||
|
||||
export const toPagination = (backendData = {}, itemMapper = (item) => item) => ({
|
||||
items: toArray(backendData.items).map(itemMapper),
|
||||
total: toNumber(backendData.total),
|
||||
page: toNumber(backendData.page, 1),
|
||||
page_size: toNumber(backendData.page_size, 20)
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
import { request, uploadFile } from './request'
|
||||
import { toNumber, toPageQuery, toPagination } from './mappers'
|
||||
|
||||
const toMember = (backendMember = {}) => ({
|
||||
id: backendMember.id ?? null,
|
||||
openid: backendMember.openid || '',
|
||||
unionid: backendMember.unionid || '',
|
||||
nickname: backendMember.nickname || '',
|
||||
phone: backendMember.phone || '',
|
||||
avatar_url: backendMember.avatar_url || '',
|
||||
balance: backendMember.balance || '0.00',
|
||||
is_active: backendMember.is_active === true,
|
||||
created_at: backendMember.created_at || ''
|
||||
})
|
||||
|
||||
const toBalance = (backendData = {}) => ({
|
||||
member_id: backendData.member_id ?? null,
|
||||
balance: backendData.balance || '0.00'
|
||||
})
|
||||
|
||||
const toBalanceRecord = (backendRecord = {}) => ({
|
||||
id: backendRecord.id ?? null,
|
||||
amount: backendRecord.amount || '0.00',
|
||||
kind: backendRecord.kind || '',
|
||||
kind_label: backendRecord.kind_label || backendRecord.kind || '',
|
||||
order_id: backendRecord.order_id ?? null,
|
||||
remark: backendRecord.remark || '',
|
||||
created_at: backendRecord.created_at || ''
|
||||
})
|
||||
|
||||
export const memberApi = {
|
||||
getMemberMe() {
|
||||
return request({
|
||||
url: '/api/members/me'
|
||||
}).then(toMember)
|
||||
},
|
||||
updateMemberProfile(payload = {}) {
|
||||
return request({
|
||||
url: '/api/members/profile',
|
||||
method: 'POST',
|
||||
data: {
|
||||
nickname: payload.nickname || null,
|
||||
avatar_url: payload.avatar_url || null
|
||||
}
|
||||
}).then(toMember)
|
||||
},
|
||||
uploadAvatar(filePath) {
|
||||
return uploadFile({
|
||||
url: '/api/members/avatar',
|
||||
filePath
|
||||
}).then((data) => ({
|
||||
avatar_url: data.avatar_url || '',
|
||||
member: toMember(data.member)
|
||||
}))
|
||||
},
|
||||
bindMemberPhone(code) {
|
||||
return request({
|
||||
url: '/api/members/phone/bind',
|
||||
method: 'POST',
|
||||
data: { code }
|
||||
}).then(toMember)
|
||||
},
|
||||
getBalance() {
|
||||
return request({
|
||||
url: '/api/members/balance'
|
||||
}).then(toBalance)
|
||||
},
|
||||
getBalanceRecords(params = {}) {
|
||||
return request({
|
||||
url: '/api/members/balance-records',
|
||||
data: toPageQuery(params)
|
||||
}).then((data) => {
|
||||
const pagination = toPagination(data, toBalanceRecord)
|
||||
return {
|
||||
...pagination,
|
||||
total: toNumber(pagination.total)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { request } from './request'
|
||||
import { compactParams, toArray, toNumber, toPageQuery, toPagination } from './mappers'
|
||||
|
||||
export const toOrderBrief = (backendOrder = {}) => ({
|
||||
id: backendOrder.id ?? null,
|
||||
order_no: backendOrder.order_no || '',
|
||||
business_type: backendOrder.business_type || '',
|
||||
business_type_label: backendOrder.business_type_label || backendOrder.business_type || '',
|
||||
amount: backendOrder.amount || '0.00',
|
||||
status: backendOrder.status || '',
|
||||
status_label: backendOrder.status_label || backendOrder.status || '',
|
||||
expire_at: backendOrder.expire_at || null,
|
||||
created_at: backendOrder.created_at || '',
|
||||
updated_at: backendOrder.updated_at || ''
|
||||
})
|
||||
|
||||
const toPayment = (backendPayment = {}) => ({
|
||||
id: backendPayment.id ?? null,
|
||||
order_id: backendPayment.order_id ?? null,
|
||||
method: backendPayment.method || '',
|
||||
method_label: backendPayment.method_label || backendPayment.method || '',
|
||||
amount: backendPayment.amount || '0.00',
|
||||
status: backendPayment.status || '',
|
||||
status_label: backendPayment.status_label || backendPayment.status || '',
|
||||
external_trade_no: backendPayment.external_trade_no || '',
|
||||
paid_at: backendPayment.paid_at || null,
|
||||
created_at: backendPayment.created_at || ''
|
||||
})
|
||||
|
||||
const toRefund = (backendRefund = {}) => ({
|
||||
id: backendRefund.id ?? null,
|
||||
payment_id: backendRefund.payment_id ?? null,
|
||||
refund_no: backendRefund.refund_no || '',
|
||||
amount: backendRefund.amount || '0.00',
|
||||
status: backendRefund.status || '',
|
||||
status_label: backendRefund.status_label || backendRefund.status || '',
|
||||
external_refund_no: backendRefund.external_refund_no || '',
|
||||
reason: backendRefund.reason || '',
|
||||
refunded_at: backendRefund.refunded_at || null,
|
||||
created_at: backendRefund.created_at || ''
|
||||
})
|
||||
|
||||
const toOrderDetail = (backendOrder = {}) => ({
|
||||
...toOrderBrief(backendOrder),
|
||||
payments: toArray(backendOrder.payments).map(toPayment),
|
||||
refunds: toArray(backendOrder.refunds).map(toRefund),
|
||||
updated_at: backendOrder.updated_at || ''
|
||||
})
|
||||
|
||||
const toWechatPayment = (backendData = {}) => ({
|
||||
payment: toPayment(backendData.payment),
|
||||
wechat: {
|
||||
timeStamp: backendData.wechat?.timeStamp || '',
|
||||
nonceStr: backendData.wechat?.nonceStr || '',
|
||||
package: backendData.wechat?.package || '',
|
||||
signType: backendData.wechat?.signType || 'RSA',
|
||||
paySign: backendData.wechat?.paySign || ''
|
||||
}
|
||||
})
|
||||
|
||||
const toOrderQuery = (params = {}) => compactParams({
|
||||
...toPageQuery(params),
|
||||
business_type: params.businessType || params.business_type,
|
||||
status: params.status
|
||||
})
|
||||
|
||||
export const orderApi = {
|
||||
getOrders(params = {}) {
|
||||
return request({
|
||||
url: '/api/orders',
|
||||
data: toOrderQuery(params)
|
||||
}).then((data) => toPagination(data, toOrderBrief))
|
||||
},
|
||||
getOrderDetail(id) {
|
||||
return request({
|
||||
url: `/api/orders/${id}`
|
||||
}).then(toOrderDetail)
|
||||
},
|
||||
closeOrder(id) {
|
||||
return request({
|
||||
url: `/api/orders/${id}/close`,
|
||||
method: 'POST'
|
||||
}).then(toOrderDetail)
|
||||
},
|
||||
payOrderByBalance(id) {
|
||||
return request({
|
||||
url: `/api/orders/${id}/pay/balance`,
|
||||
method: 'POST'
|
||||
}).then((payment) => ({
|
||||
...payment,
|
||||
amount: payment?.amount || '0.00',
|
||||
id: payment?.id ?? null,
|
||||
order_id: payment?.order_id ?? toNumber(id)
|
||||
}))
|
||||
},
|
||||
payOrderByWechat(id) {
|
||||
return request({
|
||||
url: `/api/orders/${id}/pay/wechat`,
|
||||
method: 'POST'
|
||||
}).then(toWechatPayment)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { request } from './request'
|
||||
import { compactParams, toArray, toNumber } from './mappers'
|
||||
import { toPrivateBooking } from './booking'
|
||||
import { toOrderBrief } from './order'
|
||||
|
||||
const toQuoteCourt = (backendCourt = {}) => ({
|
||||
id: backendCourt.id ?? null,
|
||||
name: backendCourt.name || '',
|
||||
court_type: backendCourt.court_type || '',
|
||||
court_type_label: backendCourt.court_type_label || ''
|
||||
})
|
||||
|
||||
const toPrivateQuote = (backendQuote = {}) => ({
|
||||
amount: backendQuote.amount || '0.00',
|
||||
unit_count: toNumber(backendQuote.unit_count),
|
||||
start_at: backendQuote.start_at || '',
|
||||
end_at: backendQuote.end_at || '',
|
||||
courts: toArray(backendQuote.courts).map(toQuoteCourt)
|
||||
})
|
||||
|
||||
const toPrivateCreateResult = (backendData = {}) => ({
|
||||
booking: toPrivateBooking(backendData.booking),
|
||||
order: toOrderBrief(backendData.order)
|
||||
})
|
||||
|
||||
const toPrivateTimeOption = (backendOption = {}) => ({
|
||||
start_at: backendOption.start_at || '',
|
||||
end_at: backendOption.end_at || '',
|
||||
available_court_count: toNumber(backendOption.available_court_count),
|
||||
available_courts: toArray(backendOption.available_courts).map(toQuoteCourt)
|
||||
})
|
||||
|
||||
const toPrivateBookingOptions = (backendData = {}) => ({
|
||||
date: backendData.date || '',
|
||||
court_type: backendData.court_type || '',
|
||||
court_type_label: backendData.court_type_label || '',
|
||||
open_time: backendData.open_time || '',
|
||||
close_time: backendData.close_time || '',
|
||||
unit_price: backendData.unit_price || '0.00',
|
||||
duration_units: toNumber(backendData.duration_units, 2),
|
||||
duration_minutes: toNumber(backendData.duration_minutes, 60),
|
||||
amount_per_court: backendData.amount_per_court || '0.00',
|
||||
total_court_count: toNumber(backendData.total_court_count),
|
||||
time_options: toArray(backendData.time_options).map(toPrivateTimeOption)
|
||||
})
|
||||
|
||||
export const privateBookingApi = {
|
||||
getPrivateBookingOptions(params = {}) {
|
||||
return request({
|
||||
url: '/api/bookings/private/options',
|
||||
data: compactParams({
|
||||
date: params.date,
|
||||
court_type: params.courtType || params.court_type,
|
||||
duration_units: params.durationUnits || params.duration_units
|
||||
})
|
||||
}).then(toPrivateBookingOptions)
|
||||
},
|
||||
quotePrivateBooking(payload = {}) {
|
||||
return request({
|
||||
url: '/api/bookings/private/quote',
|
||||
method: 'POST',
|
||||
data: {
|
||||
court_ids: toArray(payload.court_ids),
|
||||
start_at: payload.start_at,
|
||||
end_at: payload.end_at
|
||||
}
|
||||
}).then(toPrivateQuote)
|
||||
},
|
||||
createPrivateBooking(payload = {}) {
|
||||
return request({
|
||||
url: '/api/bookings/private',
|
||||
method: 'POST',
|
||||
data: {
|
||||
court_ids: toArray(payload.court_ids),
|
||||
start_at: payload.start_at,
|
||||
end_at: payload.end_at
|
||||
}
|
||||
}).then(toPrivateCreateResult)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
const KEY_TOKEN = 'COMPREHENSIVE_VENUE_TOKEN'
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_COMPREHENSIVE_API_BASE_URL || 'http://127.0.0.1:8001'
|
||||
|
||||
const normalizeError = (res, fallbackMessage = '请求服务失败') => {
|
||||
const body = res?.data || res || {}
|
||||
return {
|
||||
code: body.code || res?.statusCode || 50000,
|
||||
message: body.message || fallbackMessage,
|
||||
errors: body.errors || null,
|
||||
statusCode: res?.statusCode
|
||||
}
|
||||
}
|
||||
|
||||
export const authStorage = {
|
||||
setToken(token) {
|
||||
uni.setStorageSync(KEY_TOKEN, token)
|
||||
},
|
||||
getToken() {
|
||||
return uni.getStorageSync(KEY_TOKEN)
|
||||
},
|
||||
clear() {
|
||||
uni.removeStorageSync(KEY_TOKEN)
|
||||
},
|
||||
isLoggedIn() {
|
||||
return !!uni.getStorageSync(KEY_TOKEN)
|
||||
}
|
||||
}
|
||||
|
||||
export function request(options = {}) {
|
||||
const token = authStorage.getToken()
|
||||
const header = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.header || {})
|
||||
}
|
||||
|
||||
if (token) {
|
||||
header.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: options.url.startsWith('http') ? options.url : `${API_BASE_URL}${options.url}`,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
header,
|
||||
success: (res) => {
|
||||
const body = res.data
|
||||
|
||||
if (res.statusCode === 401 || body?.code === 40001) {
|
||||
authStorage.clear()
|
||||
}
|
||||
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && body?.code === 0) {
|
||||
resolve(body.data)
|
||||
return
|
||||
}
|
||||
|
||||
const error = normalizeError(res, `请求错误 ${res.statusCode}`)
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none'
|
||||
})
|
||||
reject(error)
|
||||
},
|
||||
fail: (err) => {
|
||||
const error = normalizeError(err, '网络连接失败,请检查服务')
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none'
|
||||
})
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function uploadFile(options = {}) {
|
||||
const token = authStorage.getToken()
|
||||
const header = {
|
||||
...(options.header || {})
|
||||
}
|
||||
|
||||
if (token) {
|
||||
header.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: options.url.startsWith('http') ? options.url : `${API_BASE_URL}${options.url}`,
|
||||
filePath: options.filePath,
|
||||
name: options.name || 'file',
|
||||
formData: options.formData || {},
|
||||
header,
|
||||
success: (res) => {
|
||||
let body = {}
|
||||
try {
|
||||
body = JSON.parse(res.data || '{}')
|
||||
} catch (error) {
|
||||
body = {}
|
||||
}
|
||||
|
||||
if (res.statusCode === 401 || body?.code === 40001) {
|
||||
authStorage.clear()
|
||||
}
|
||||
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && body?.code === 0) {
|
||||
resolve(body.data)
|
||||
return
|
||||
}
|
||||
|
||||
const uploadError = normalizeError({ ...res, data: body }, `上传错误 ${res.statusCode}`)
|
||||
uni.showToast({
|
||||
title: uploadError.message,
|
||||
icon: 'none'
|
||||
})
|
||||
reject(uploadError)
|
||||
},
|
||||
fail: (err) => {
|
||||
const uploadError = normalizeError(err, '文件上传失败,请检查服务')
|
||||
uni.showToast({
|
||||
title: uploadError.message,
|
||||
icon: 'none'
|
||||
})
|
||||
reject(uploadError)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { request } from './request'
|
||||
import { toArray, toNumber, toPageQuery } from './mappers'
|
||||
import { toOrderBrief } from './order'
|
||||
|
||||
const toTicketProduct = (backendProduct = {}) => ({
|
||||
id: backendProduct.id ?? null,
|
||||
unit_price: backendProduct.unit_price || '0.00',
|
||||
is_active: backendProduct.is_active === true,
|
||||
created_at: backendProduct.created_at || '',
|
||||
updated_at: backendProduct.updated_at || ''
|
||||
})
|
||||
|
||||
const toTicketRecord = (backendRecord = {}) => ({
|
||||
id: backendRecord.id ?? null,
|
||||
order_id: backendRecord.order_id ?? null,
|
||||
order_no: backendRecord.order_no || '',
|
||||
order_status: backendRecord.order_status || '',
|
||||
status: backendRecord.status || '',
|
||||
status_label: backendRecord.status_label || backendRecord.status || '',
|
||||
checked_in_at: backendRecord.checked_in_at || null,
|
||||
cancelled_at: backendRecord.cancelled_at || null,
|
||||
cancel_reason: backendRecord.cancel_reason || '',
|
||||
created_at: backendRecord.created_at || ''
|
||||
})
|
||||
|
||||
const toTicketAccount = (backendAccount = {}) => ({
|
||||
remaining_count: toNumber(backendAccount.remaining_count),
|
||||
records: toArray(backendAccount.records).map(toTicketRecord),
|
||||
total: toNumber(backendAccount.total),
|
||||
page: toNumber(backendAccount.page, 1),
|
||||
page_size: toNumber(backendAccount.page_size, 20)
|
||||
})
|
||||
|
||||
const toTicketOrder = (backendData = {}) => ({
|
||||
order: toOrderBrief(backendData.order),
|
||||
product: toTicketProduct(backendData.product),
|
||||
quantity: toNumber(backendData.quantity)
|
||||
})
|
||||
|
||||
export const ticketApi = {
|
||||
getTicketProduct() {
|
||||
return request({
|
||||
url: '/api/tickets/product'
|
||||
}).then(toTicketProduct)
|
||||
},
|
||||
getTicketAccount(params = {}) {
|
||||
return request({
|
||||
url: '/api/tickets/account',
|
||||
data: toPageQuery(params)
|
||||
}).then(toTicketAccount)
|
||||
},
|
||||
createTicketOrder(quantity) {
|
||||
return request({
|
||||
url: '/api/tickets/orders',
|
||||
method: 'POST',
|
||||
data: { quantity }
|
||||
}).then(toTicketOrder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { request } from './request'
|
||||
import { toArray } from './mappers'
|
||||
|
||||
const toCoach = (backendCoach = {}) => ({
|
||||
id: backendCoach.id ?? null,
|
||||
name: backendCoach.name || '',
|
||||
phone: backendCoach.phone || '',
|
||||
is_active: backendCoach.is_active === true
|
||||
})
|
||||
|
||||
export const venueApi = {
|
||||
getCoaches() {
|
||||
return request({
|
||||
url: '/api/venues/coaches'
|
||||
}).then((data = {}) => ({
|
||||
items: toArray(data.items).map(toCoach)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { authApi, authStorage } from '@/modules/comprehensive/api/auth'
|
||||
import { memberApi } from '@/modules/comprehensive/api/member'
|
||||
|
||||
export const useComprehensiveSessionStore = defineStore('comprehensiveVenueSession', {
|
||||
state: () => ({
|
||||
isReady: false,
|
||||
isLoggedIn: false,
|
||||
member: null
|
||||
}),
|
||||
actions: {
|
||||
async init() {
|
||||
this.isLoggedIn = authStorage.isLoggedIn()
|
||||
if (this.isLoggedIn) {
|
||||
try {
|
||||
this.member = await memberApi.getMemberMe()
|
||||
} catch (error) {
|
||||
if ([40001, 40002, 401, 403].includes(error.code) || [401, 403].includes(error.statusCode)) {
|
||||
authStorage.clear()
|
||||
this.member = null
|
||||
this.isLoggedIn = false
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isReady = true
|
||||
},
|
||||
async login() {
|
||||
const loginResult = await loginByWechat()
|
||||
const data = await authApi.wechatLogin({ code: loginResult.code })
|
||||
this.member = data.member
|
||||
this.isLoggedIn = true
|
||||
},
|
||||
logout() {
|
||||
authApi.logout()
|
||||
this.member = null
|
||||
this.isLoggedIn = false
|
||||
},
|
||||
async refreshMember() {
|
||||
if (!authStorage.isLoggedIn()) return
|
||||
this.member = await memberApi.getMemberMe()
|
||||
this.isLoggedIn = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const loginByWechat = () => new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (res) => {
|
||||
if (!res.code) {
|
||||
reject(new Error('微信登录未返回 code'))
|
||||
return
|
||||
}
|
||||
resolve(res)
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export const formatDate = (value, fallback = '-') => {
|
||||
if (!value) return fallback
|
||||
return dayjs(value).format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
export const formatDateTime = (value, fallback = '-') => {
|
||||
if (!value) return fallback
|
||||
return dayjs(value).format('MM-DD HH:mm')
|
||||
}
|
||||
|
||||
export const formatTime = (value, fallback = '-') => {
|
||||
if (!value) return fallback
|
||||
return dayjs(value).format('HH:mm')
|
||||
}
|
||||
|
||||
export const formatTimeRange = (startAt, endAt) => {
|
||||
if (!startAt || !endAt) return '-'
|
||||
const start = dayjs(startAt)
|
||||
const end = dayjs(endAt)
|
||||
if (start.format('YYYY-MM-DD') === end.format('YYYY-MM-DD')) {
|
||||
return `${start.format('MM-DD HH:mm')} - ${end.format('HH:mm')}`
|
||||
}
|
||||
return `${start.format('MM-DD HH:mm')} - ${end.format('MM-DD HH:mm')}`
|
||||
}
|
||||
|
||||
export const formatDuration = (startAt, endAt) => {
|
||||
if (!startAt || !endAt) return '-'
|
||||
const minutes = dayjs(endAt).diff(dayjs(startAt), 'minute')
|
||||
if (minutes <= 0) return '-'
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const rest = minutes % 60
|
||||
if (hours && rest) return `${hours}小时${rest}分钟`
|
||||
if (hours) return `${hours}小时`
|
||||
return `${rest}分钟`
|
||||
}
|
||||
|
||||
export const todayString = () => dayjs().format('YYYY-MM-DD')
|
||||
@@ -0,0 +1,10 @@
|
||||
export const toAmountNumber = (value) => {
|
||||
const amount = Number(value)
|
||||
return Number.isFinite(amount) ? amount : 0
|
||||
}
|
||||
|
||||
export const formatAmount = (value) => toAmountNumber(value).toFixed(2)
|
||||
|
||||
export const multiplyAmount = (unitPrice, quantity) => {
|
||||
return formatAmount(toAmountNumber(unitPrice) * Number(quantity || 0))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export const statusBadgeClass = (status) => {
|
||||
if (['paid', 'booked', 'available', 'success', 'published'].includes(status)) return 'success'
|
||||
if (['pending_pay', 'pending'].includes(status)) return 'warning'
|
||||
if (['cancelled', 'closed', 'expired', 'refunded'].includes(status)) return 'danger'
|
||||
return 'muted'
|
||||
}
|
||||
|
||||
export const courtTypeLabel = (type) => {
|
||||
const map = {
|
||||
badminton: '羽毛球',
|
||||
basketball: '篮球'
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
Reference in New Issue
Block a user