完善下单逻辑及其ui展示,修复支付倒计时显示错误bug
This commit is contained in:
@@ -17,6 +17,15 @@
|
||||
<input class="input" v-model="regionString" placeholder="省市区县、乡镇等" placeholder-class="placeholder" />
|
||||
<text class="arrow-icon">›</text>
|
||||
</view>
|
||||
<view class="location-action-row">
|
||||
<view class="location-action-btn" @click="fillCurrentLocation">
|
||||
<text class="location-action-text">获取当前位置</text>
|
||||
</view>
|
||||
<view class="location-action-btn" @click="pickLocation">
|
||||
<text class="location-action-text">地图选点</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="locationHint != ''" class="location-hint">{{ locationHint }}</text>
|
||||
<view class="form-item detail-item">
|
||||
<text class="label">详细地址</text>
|
||||
<textarea class="textarea" v-model="formData.detail" placeholder="街道、楼牌号等" placeholder-class="placeholder" maxlength="100"></textarea>
|
||||
@@ -85,6 +94,9 @@ type Address = {
|
||||
detail: string
|
||||
isDefault: boolean
|
||||
label?: string
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
coordinateType?: string
|
||||
}
|
||||
|
||||
const isEdit = ref(false)
|
||||
@@ -92,6 +104,9 @@ const addressId = ref('')
|
||||
const regionString = ref('')
|
||||
const tags = ['家', '公司', '学校']
|
||||
const smartInput = ref('')
|
||||
const locationHint = ref('')
|
||||
const latitude = ref(0)
|
||||
const longitude = ref(0)
|
||||
|
||||
type AddressForm = {
|
||||
name: string
|
||||
@@ -120,6 +135,9 @@ const loadAddress = async (id: string) => {
|
||||
formData.isDefault = address.is_default
|
||||
formData.label = address.label ?? ''
|
||||
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||
latitude.value = address.latitude ?? 0
|
||||
longitude.value = address.longitude ?? 0
|
||||
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||
} else {
|
||||
// 如果Supabase没有找到,尝试从本地存储加载
|
||||
const storedAddresses = uni.getStorageSync('addresses')
|
||||
@@ -133,6 +151,9 @@ const loadAddress = async (id: string) => {
|
||||
formData.isDefault = localAddress.isDefault
|
||||
formData.label = localAddress.label ?? ''
|
||||
regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()
|
||||
latitude.value = localAddress.latitude ?? 0
|
||||
longitude.value = localAddress.longitude ?? 0
|
||||
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +172,9 @@ const loadAddress = async (id: string) => {
|
||||
formData.isDefault = address.isDefault
|
||||
formData.label = address.label ?? ''
|
||||
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||
latitude.value = address.latitude ?? 0
|
||||
longitude.value = address.longitude ?? 0
|
||||
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析本地地址数据失败', e)
|
||||
@@ -159,6 +183,42 @@ const loadAddress = async (id: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const applyLocation = (latitudeValue: number, longitudeValue: number, addressText: string, locationName: string) => {
|
||||
latitude.value = latitudeValue
|
||||
longitude.value = longitudeValue
|
||||
locationHint.value = `已定位:${latitudeValue}, ${longitudeValue}`
|
||||
if (addressText != '') {
|
||||
regionString.value = addressText
|
||||
}
|
||||
if (locationName != '' && formData.detail == '') {
|
||||
formData.detail = locationName
|
||||
}
|
||||
}
|
||||
|
||||
const fillCurrentLocation = () => {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
applyLocation(res.latitude, res.longitude, regionString.value, '')
|
||||
uni.showToast({ title: '已获取当前位置', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '定位失败,请手动输入地址', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const pickLocation = () => {
|
||||
uni.chooseLocation({
|
||||
success: (res) => {
|
||||
applyLocation(res.latitude, res.longitude, res.address ?? '', res.name ?? '')
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '当前环境不支持地图选点,可手动输入', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options == null) return
|
||||
const optionsObj = options as UTSJSONObject
|
||||
@@ -216,7 +276,10 @@ const saveAddress = async () => {
|
||||
detail_address: formData.detail,
|
||||
postal_code: '', // 如果需要可以添加邮政编码字段
|
||||
is_default: formData.isDefault,
|
||||
label: formData.label
|
||||
label: formData.label,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
coordinate_type: 'gcj02'
|
||||
} as AddAddressParams
|
||||
|
||||
let success = false
|
||||
@@ -231,8 +294,11 @@ const saveAddress = async () => {
|
||||
district: district,
|
||||
detail_address: formData.detail,
|
||||
postal_code: '',
|
||||
is_default: formData.isDefault,
|
||||
label: formData.label
|
||||
is_default: formData.isDefault,
|
||||
label: formData.label,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
coordinate_type: 'gcj02'
|
||||
} as UpdateAddressParams
|
||||
success = await supabaseService.updateAddress(addressId.value, updateData)
|
||||
} else {
|
||||
@@ -271,7 +337,10 @@ const saveAddress = async () => {
|
||||
district: district,
|
||||
detail: formData.detail,
|
||||
isDefault: formData.isDefault,
|
||||
label: formData.label
|
||||
label: formData.label,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
coordinateType: 'gcj02'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -284,7 +353,10 @@ const saveAddress = async () => {
|
||||
district: district,
|
||||
detail: formData.detail,
|
||||
isDefault: formData.isDefault,
|
||||
label: formData.label
|
||||
label: formData.label,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
coordinateType: 'gcj02'
|
||||
}
|
||||
addresses.push(newAddress)
|
||||
}
|
||||
@@ -437,6 +509,38 @@ const deleteAddress = () => {
|
||||
border-radius: 16px; /* 详细地址区域也增加圆角 */
|
||||
}
|
||||
|
||||
.location-action-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.location-action-btn {
|
||||
flex: 1;
|
||||
min-height: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f1f5f9;
|
||||
border-radius: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.location-action-btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.location-action-text {
|
||||
font-size: 13px;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.location-hint {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ type Address = {
|
||||
detail: string
|
||||
isDefault: boolean
|
||||
label?: string
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
coordinateType?: string
|
||||
}
|
||||
|
||||
const addresses = ref<Address[]>([])
|
||||
@@ -71,7 +74,10 @@ const loadAddresses = async () => {
|
||||
district: item.district,
|
||||
detail: item.detail_address,
|
||||
isDefault: item.is_default,
|
||||
label: ''
|
||||
label: '',
|
||||
latitude: item.latitude ?? 0,
|
||||
longitude: item.longitude ?? 0,
|
||||
coordinateType: item.coordinate_type ?? 'gcj02'
|
||||
} as Address
|
||||
transformedAddresses.push(addr)
|
||||
}
|
||||
@@ -99,8 +105,8 @@ const loadAddresses = async () => {
|
||||
|
||||
onLoad((options) => {
|
||||
if (options == null) return
|
||||
const optionsObj = options as UTSJSONObject
|
||||
if ((optionsObj.getString('selectMode') ?? '') == 'true') {
|
||||
const selectMode = options['selectMode']
|
||||
if (selectMode != null && String(selectMode) == 'true') {
|
||||
selectionMode.value = true
|
||||
}
|
||||
})
|
||||
@@ -166,12 +172,23 @@ const selectAddress = (item: Address) => {
|
||||
if (selectionMode.value) {
|
||||
uni.$emit('addressSelected', {
|
||||
id: item.id,
|
||||
addressId: item.id,
|
||||
userId: '',
|
||||
recipient_name: item.name,
|
||||
contactName: item.name,
|
||||
phone: item.phone,
|
||||
contactPhone: item.phone,
|
||||
province: item.province,
|
||||
city: item.city,
|
||||
district: item.district,
|
||||
detail: item.detail,
|
||||
addressDetail: item.detail,
|
||||
houseNumber: item.detail,
|
||||
fullAddress: getFullAddress(item),
|
||||
remark: item.label ?? '',
|
||||
latitude: item.latitude ?? 0,
|
||||
longitude: item.longitude ?? 0,
|
||||
coordinateType: item.coordinateType ?? 'gcj02',
|
||||
is_default: item.isDefault
|
||||
})
|
||||
uni.navigateBack()
|
||||
|
||||
@@ -149,6 +149,13 @@ async function loadData() {
|
||||
bookingDays.value = getBookingDayOptions()
|
||||
bookingSlots.value = getBookingTimeSlots()
|
||||
services.value = await fetchHomeServiceCatalog()
|
||||
if (services.value.length > 0) {
|
||||
selectService(services.value[0].id, services.value[0].name)
|
||||
return
|
||||
}
|
||||
selectedServiceId.value = ''
|
||||
form.serviceId = ''
|
||||
form.serviceName = ''
|
||||
}
|
||||
|
||||
function selectService(serviceId: string, serviceName: string) {
|
||||
@@ -192,6 +199,10 @@ function selectSlot(slotId: string, available: boolean) {
|
||||
}
|
||||
|
||||
async function submitApplication() {
|
||||
if (form.serviceId == '' || form.serviceName == '') {
|
||||
uni.showToast({ title: '当前没有可预约的服务项目', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (form.applicantName == '' || form.elderName == '' || form.phone == '' || form.address == '' || form.preferredTime == '') {
|
||||
uni.showToast({ title: '请补全申请信息', icon: 'none' })
|
||||
return
|
||||
@@ -200,6 +211,10 @@ async function submitApplication() {
|
||||
const parsedAge = parseInt(ageText.value)
|
||||
form.age = isNaN(parsedAge) ? 0 : parsedAge
|
||||
const created = await createHomeServiceApplication(form)
|
||||
if (created == null) {
|
||||
uni.showToast({ title: '申请提交失败,请检查登录和预约信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '申请已提交', icon: 'success' })
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uv
|
||||
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
||||
import { fetchConsumerAcceptanceDetail, submitConsumerAcceptance } from '@/services/homeServiceService.uts'
|
||||
import { HomeServiceAcceptanceType } from '@/types/home-service.uts'
|
||||
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
|
||||
const caseId = ref('')
|
||||
const detail = ref<HomeServiceAcceptanceType | null>(null)
|
||||
@@ -62,17 +64,31 @@ const selectedTags = ref<Array<string>>([])
|
||||
const scores = [1, 2, 3, 4, 5]
|
||||
const allTags = ['准时上门', '沟通清楚', '动作规范', '记录完整', '需进一步整改']
|
||||
|
||||
async function ensureLogin(): Promise<boolean> {
|
||||
const user = await getCurrentUser()
|
||||
if (user == null || getCurrentUserId() == '') {
|
||||
goToLogin('/pages/mall/consumer/home-service/feedback?id=' + caseId.value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const id = options['id']
|
||||
if (id != null) {
|
||||
caseId.value = id as string
|
||||
fetchConsumerAcceptanceDetail(caseId.value).then((res) => {
|
||||
if (res != null) {
|
||||
detail.value = res
|
||||
rating.value = res.rating
|
||||
feedback.value = res.feedback
|
||||
selectedTags.value = res.tags.slice(0)
|
||||
ensureLogin().then((ok) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
fetchConsumerAcceptanceDetail(caseId.value).then((res) => {
|
||||
if (res != null) {
|
||||
detail.value = res
|
||||
rating.value = res.rating
|
||||
feedback.value = res.feedback
|
||||
selectedTags.value = res.tags.slice(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -87,6 +103,9 @@ function toggleTag(tag: string) {
|
||||
}
|
||||
|
||||
async function submitResult(approved: boolean) {
|
||||
if (!(await ensureLogin())) {
|
||||
return
|
||||
}
|
||||
if (caseId.value == '' || feedback.value == '') {
|
||||
uni.showToast({ title: '请填写反馈说明', icon: 'none' })
|
||||
return
|
||||
|
||||
@@ -89,6 +89,8 @@ import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
||||
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
|
||||
import { fetchConsumerHomeServiceCases, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||
import { HomeServiceCatalogType, HomeServiceCaseType } from '@/types/home-service.uts'
|
||||
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
import {
|
||||
HomeServiceCategoryType,
|
||||
HomeServiceItemType,
|
||||
@@ -125,9 +127,23 @@ async function loadData() {
|
||||
categoryGrid.value = getHomeServiceCategories()
|
||||
promoCards.value = getHomeServicePromoCards()
|
||||
services.value = await fetchHomeServiceCatalog()
|
||||
const user = await getCurrentUser()
|
||||
if (user == null || getCurrentUserId() == '') {
|
||||
cases.value = [] as Array<HomeServiceCaseType>
|
||||
return
|
||||
}
|
||||
cases.value = await fetchConsumerHomeServiceCases()
|
||||
}
|
||||
|
||||
async function ensureLogin(redirectUrl: string): Promise<boolean> {
|
||||
const user = await getCurrentUser()
|
||||
if (user == null || getCurrentUserId() == '') {
|
||||
goToLogin(redirectUrl)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function switchCategory(categoryId: string) {
|
||||
selectedCategory.value = categoryId
|
||||
}
|
||||
@@ -141,11 +157,21 @@ function goDetail(serviceId: string) {
|
||||
}
|
||||
|
||||
function goBooking(serviceId: string) {
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking' })
|
||||
ensureLogin('/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking').then((ok) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking' })
|
||||
})
|
||||
}
|
||||
|
||||
function goOrderDetail(caseId: string) {
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + caseId })
|
||||
ensureLogin('/pages/mall/consumer/home-service/order-detail?id=' + caseId).then((ok) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + caseId })
|
||||
})
|
||||
}
|
||||
|
||||
function goDetailByName(serviceName: string) {
|
||||
|
||||
@@ -59,7 +59,17 @@
|
||||
</view>
|
||||
</ServicePanel>
|
||||
|
||||
<ServicePanel title="服务过程" subtitle="当前以 mock 时间线展示预约受理、派单和上门过程。">
|
||||
<ServicePanel title="服务过程" subtitle="基于真实状态日志展示预约受理、派单、上门与验收进度。">
|
||||
<ServiceInfoList
|
||||
:items="[
|
||||
{ label: '签到时间:', value: detail.checkinTime != '' ? detail.checkinTime : '暂未签到' },
|
||||
{ label: '签到地点:', value: detail.checkinAddress != '' ? detail.checkinAddress : '暂未记录' },
|
||||
{ label: '开始服务:', value: detail.serviceStartedAt != '' ? detail.serviceStartedAt : '暂未开始' },
|
||||
{ label: '完成服务:', value: detail.serviceFinishedAt != '' ? detail.serviceFinishedAt : '暂未完成' },
|
||||
{ label: '执行摘要:', value: detail.executionSummary != '' ? detail.executionSummary : '服务人员暂未提交执行摘要' },
|
||||
{ label: '证据数量:', value: detail.evidenceCount > 0 ? String(detail.evidenceCount) + ' 份' : '暂未上传' }
|
||||
]"
|
||||
></ServiceInfoList>
|
||||
<ServiceTimeline :items="detail.timeline"></ServiceTimeline>
|
||||
</ServicePanel>
|
||||
|
||||
@@ -82,14 +92,29 @@ import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
|
||||
import ServiceTimeline from '@/components/homeService/ServiceTimeline.uvue'
|
||||
import { fetchConsumerHomeServiceCaseDetail } from '@/services/homeServiceService.uts'
|
||||
import { HomeServiceCaseType } from '@/types/home-service.uts'
|
||||
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
|
||||
const caseId = ref('')
|
||||
const detail = ref<HomeServiceCaseType | null>(null)
|
||||
|
||||
async function ensureLogin(): Promise<boolean> {
|
||||
const user = await getCurrentUser()
|
||||
if (user == null || getCurrentUserId() == '') {
|
||||
goToLogin('/pages/mall/consumer/home-service/order-detail?id=' + caseId.value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
if (caseId.value == '') {
|
||||
return
|
||||
}
|
||||
if (!(await ensureLogin())) {
|
||||
detail.value = null
|
||||
return
|
||||
}
|
||||
detail.value = await fetchConsumerHomeServiceCaseDetail(caseId.value)
|
||||
}
|
||||
|
||||
@@ -97,7 +122,12 @@ function goFeedback() {
|
||||
if (caseId.value == '') {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/feedback?id=' + caseId.value })
|
||||
ensureLogin().then((ok) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/feedback?id=' + caseId.value })
|
||||
})
|
||||
}
|
||||
|
||||
function bookAgain() {
|
||||
|
||||
@@ -28,12 +28,20 @@
|
||||
</view>
|
||||
|
||||
<ServicePanel title="Step1 服务地址" subtitle="确认上门地址、楼层与入户条件。">
|
||||
<view class="form-item">
|
||||
<text class="label">上门地址</text>
|
||||
<view class="value-card" @click="selectAddress">
|
||||
<text class="value-card-text">{{ addressLineText }}</text>
|
||||
<text class="value-card-action">点击更换</text>
|
||||
<view class="service-address-card" @click="selectAddress">
|
||||
<view class="service-address-main">
|
||||
<text class="service-address-title">服务地址</text>
|
||||
<view v-if="selectedAddress != null" class="service-address-detail">
|
||||
<view class="service-address-contact-row">
|
||||
<text class="service-address-contact">{{ selectedAddress.contactName }}</text>
|
||||
<text class="service-address-phone">{{ getSelectedAddressPhone() }}</text>
|
||||
</view>
|
||||
<text class="service-address-full">{{ selectedAddress.fullAddress }}</text>
|
||||
<text v-if="selectedAddress.remark != ''" class="service-address-remark">备注:{{ selectedAddress.remark }}</text>
|
||||
</view>
|
||||
<text v-else class="service-address-placeholder">请选择服务地址</text>
|
||||
</view>
|
||||
<text class="service-address-arrow">></text>
|
||||
</view>
|
||||
<view class="form-grid">
|
||||
<view class="form-grid-item">
|
||||
@@ -271,11 +279,13 @@
|
||||
|
||||
<script setup lang="uts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import ServicePageHeader from '@/components/homeService/ServicePageHeader.uvue'
|
||||
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
||||
import { createHomeServiceApplication, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||
import { HomeServiceApplicationDraftType, HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||
import { HomeServiceApplicationDraftType, HomeServiceCatalogType, HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||||
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
import {
|
||||
BookingDayOptionType,
|
||||
BookingTimeSlotType,
|
||||
@@ -390,6 +400,7 @@ const servicePackages = ref<Array<ServicePackageOptionType>>([])
|
||||
|
||||
const selectedDayId = ref('day-1')
|
||||
const selectedSlotId = ref('slot-1')
|
||||
const selectedAddress = ref<HomeServiceSelectedAddressType | null>(null)
|
||||
|
||||
const careAddress = ref(defaultCareAddress.address)
|
||||
const careDetailAddress = ref(defaultCareAddress.detailAddress)
|
||||
@@ -499,10 +510,56 @@ function buildDefaultPackages(currentServiceId: string, basePrice: number, durat
|
||||
}
|
||||
|
||||
const addressLineText = computed((): string => {
|
||||
if (selectedAddress.value != null && selectedAddress.value.fullAddress != '') {
|
||||
return selectedAddress.value.fullAddress
|
||||
}
|
||||
const fullAddress = careAddress.value + ' ' + careDetailAddress.value
|
||||
return fullAddress.trim() != '' ? fullAddress : '请选择上门服务地址'
|
||||
return fullAddress.trim() != '' ? fullAddress : '请选择服务地址'
|
||||
})
|
||||
|
||||
function applySelectedAddress(address: HomeServiceSelectedAddressType | null): void {
|
||||
selectedAddress.value = address
|
||||
if (address == null) {
|
||||
careAddress.value = ''
|
||||
careDetailAddress.value = ''
|
||||
return
|
||||
}
|
||||
const locationAddress = address.locationAddress != null && address.locationAddress != '' ? address.locationAddress : address.addressDetail
|
||||
const doorNo = address.doorNo != null && address.doorNo != '' ? address.doorNo : address.houseNumber
|
||||
careAddress.value = locationAddress
|
||||
careDetailAddress.value = doorNo
|
||||
if (contactName.value == '') {
|
||||
contactName.value = address.contactName
|
||||
}
|
||||
const phoneText = address.phone != null && address.phone != '' ? address.phone : address.contactPhone
|
||||
if (contactPhone.value == '') {
|
||||
contactPhone.value = phoneText
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedAddressPhone(): string {
|
||||
if (selectedAddress.value == null) {
|
||||
return ''
|
||||
}
|
||||
if (selectedAddress.value.phone != null && selectedAddress.value.phone != '') {
|
||||
return selectedAddress.value.phone
|
||||
}
|
||||
return selectedAddress.value.contactPhone
|
||||
}
|
||||
|
||||
function loadCachedSelectedAddress(): void {
|
||||
try {
|
||||
const cachedAddress = uni.getStorageSync('hss_selected_service_address') as HomeServiceSelectedAddressType | null
|
||||
if (cachedAddress != null && cachedAddress.fullAddress != '') {
|
||||
applySelectedAddress(cachedAddress)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('读取服务地址缓存失败', error)
|
||||
}
|
||||
applySelectedAddress(null)
|
||||
}
|
||||
|
||||
const selectedTimeText = computed((): string => {
|
||||
let selectedDayLabel = ''
|
||||
for (let i = 0; i < bookingDays.value.length; i++) {
|
||||
@@ -611,9 +668,20 @@ function buildDemandSummary(): string {
|
||||
+ ',服务需求:' + selectedNeeds
|
||||
+ ',详细需求:' + demandDetailText.value
|
||||
+ ',紧急联系人:' + emergencyName.value + ' ' + emergencyPhone.value
|
||||
+ ',服务地址备注:' + (selectedAddress.value != null ? selectedAddress.value.remark : '')
|
||||
+ ',备注:' + remarkText.value
|
||||
}
|
||||
|
||||
function setUnavailableServiceState() {
|
||||
serviceTitle.value = '服务暂未配置'
|
||||
serviceSubtitle.value = '当前服务目录未找到该项目,请稍后再试。'
|
||||
servicePrice.value = 0
|
||||
serviceDuration.value = '待配置'
|
||||
serviceSuitableFor.value = '请联系管理员初始化服务目录。'
|
||||
serviceImageText.value = '服务'
|
||||
servicePackages.value = [] as Array<ServicePackageOptionType>
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
bookingDays.value = getBookingDayOptions()
|
||||
bookingSlots.value = getBookingTimeSlots()
|
||||
@@ -633,15 +701,7 @@ async function loadData() {
|
||||
}
|
||||
}
|
||||
if (matchedService == null) {
|
||||
const fallbackItems = getHomeServiceItems(catalog)
|
||||
if (fallbackItems.length > 0) {
|
||||
serviceTitle.value = fallbackItems[0].title
|
||||
serviceSubtitle.value = fallbackItems[0].subtitle
|
||||
servicePrice.value = fallbackItems[0].price
|
||||
serviceSuitableFor.value = fallbackItems[0].suitableFor
|
||||
serviceImageText.value = fallbackItems[0].imageText
|
||||
}
|
||||
servicePackages.value = buildDefaultPackages(serviceId.value, servicePrice.value, serviceDuration.value)
|
||||
setUnavailableServiceState()
|
||||
return
|
||||
}
|
||||
serviceTitle.value = matchedService.name
|
||||
@@ -656,17 +716,22 @@ async function loadData() {
|
||||
servicePackages.value = buildDefaultPackages(serviceId.value, servicePrice.value, serviceDuration.value)
|
||||
}
|
||||
|
||||
function selectAddress() {
|
||||
if (careAddress.value == '' || careDetailAddress.value == '') {
|
||||
careAddress.value = defaultCareAddress.address
|
||||
careDetailAddress.value = defaultCareAddress.detailAddress
|
||||
careArea.value = defaultCareAddress.area
|
||||
careFloor.value = defaultCareAddress.floor
|
||||
careHasElevator.value = defaultCareAddress.hasElevator
|
||||
uni.showToast({ title: '已填入示例地址', icon: 'none' })
|
||||
return
|
||||
async function ensureLogin(): Promise<boolean> {
|
||||
const user = await getCurrentUser()
|
||||
if (user == null || getCurrentUserId() == '') {
|
||||
goToLogin('/pages/mall/consumer/home-service/service-detail?id=' + serviceId.value + '&mode=booking')
|
||||
return false
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/address-list' })
|
||||
return true
|
||||
}
|
||||
|
||||
function selectAddress() {
|
||||
ensureLogin().then((ok) => {
|
||||
if (!ok) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/address/address-list' })
|
||||
})
|
||||
}
|
||||
|
||||
function handleAgencyReplace() {
|
||||
@@ -746,8 +811,15 @@ function selectServicePackage(packageId: string) {
|
||||
}
|
||||
|
||||
async function submitBooking() {
|
||||
if (careAddress.value == '' || careDetailAddress.value == '') {
|
||||
uni.showToast({ title: '请选择上门服务地址', icon: 'none' })
|
||||
if (!(await ensureLogin())) {
|
||||
return
|
||||
}
|
||||
if (servicePackages.value.length == 0 || servicePrice.value <= 0) {
|
||||
uni.showToast({ title: '当前服务暂不可预约', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (selectedAddress.value == null) {
|
||||
uni.showToast({ title: '请选择服务地址', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (recipientName.value == '') {
|
||||
@@ -781,10 +853,15 @@ async function submitBooking() {
|
||||
phone: contactPhone.value,
|
||||
address: addressLineText.value,
|
||||
preferredTime: selectedTimeText.value,
|
||||
demandSummary: buildDemandSummary()
|
||||
demandSummary: buildDemandSummary(),
|
||||
serviceAddressSnapshot: selectedAddress.value
|
||||
}
|
||||
|
||||
const created = await createHomeServiceApplication(draft)
|
||||
if (created == null) {
|
||||
uni.showToast({ title: '预约提交失败,请稍后重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '预约已提交', icon: 'success' })
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
||||
}
|
||||
@@ -796,12 +873,14 @@ onLoad((options) => {
|
||||
}
|
||||
const mode = options['mode']
|
||||
if (mode != null && mode == 'booking') {
|
||||
careAddress.value = defaultCareAddress.address
|
||||
careDetailAddress.value = defaultCareAddress.detailAddress
|
||||
careArea.value = defaultCareAddress.area
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
loadCachedSelectedAddress()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -845,6 +924,81 @@ onLoad((options) => {
|
||||
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.service-address-card {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1rpx solid rgba(255, 125, 151, 0.16);
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 10rpx 20rpx rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.service-address-main {
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.service-address-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.service-address-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.service-address-contact-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.service-address-contact,
|
||||
.service-address-phone,
|
||||
.service-address-full,
|
||||
.service-address-remark,
|
||||
.service-address-placeholder,
|
||||
.service-address-arrow {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.service-address-contact {
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.service-address-phone {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.service-address-full {
|
||||
color: #374151;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.service-address-remark {
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.service-address-placeholder {
|
||||
color: #9ca3af;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.service-address-arrow {
|
||||
color: #c18b95;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.summary-top-row,
|
||||
.summary-tag-row,
|
||||
.summary-price-row,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<view class="status-title-row">
|
||||
<text class="status-emoji">{{ getStatusIcon() }}</text>
|
||||
<text class="status-text">{{ getStatusText() }}</text>
|
||||
<text v-if="getPendingCountdownText() != ''" class="status-countdown">{{ getPendingCountdownText() }}</text>
|
||||
</view>
|
||||
<text class="status-desc">{{ getStatusDesc() }}</text>
|
||||
</view>
|
||||
@@ -135,7 +136,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-right">
|
||||
<view v-if="order?.order_status === 1" class="btn-group">
|
||||
<view v-if="order?.order_status === 1 && !isTimeoutOrder()" class="btn-group">
|
||||
<button class="btn" @click="cancelOrder">取消订单</button>
|
||||
<button class="btn primary" @click="payOrder">立即支付</button>
|
||||
</view>
|
||||
@@ -157,9 +158,11 @@
|
||||
<button class="btn primary" @click="goToReview">评价订单</button>
|
||||
</view>
|
||||
|
||||
<view v-if="order?.order_status === 5" class="btn-group">
|
||||
<button class="btn primary" @click="rePurchase">重新购买</button>
|
||||
</view>
|
||||
<view v-if="shouldShowCancelledActions()" class="btn-group">
|
||||
<button class="btn" @click="deleteOrder">删除订单</button>
|
||||
<button class="btn" @click="viewSimilar">看相似</button>
|
||||
<button class="btn primary" @click="rePurchase">再次购买</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -167,16 +170,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { onBackPress, onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { formatCountdownHMS, getOrderDisplayStatus, getRemainingSeconds, isOrderPayExpired, ORDER_STATUS_CANCELLED, ORDER_STATUS_PENDING, ORDER_TIMEOUT_CANCEL_REASON, PAYMENT_STATUS_TIMEOUT, PAYMENT_STATUS_UNPAID, type OrderStatusSource } from '@/utils/orderStatus.uts'
|
||||
|
||||
// 定义订单类型
|
||||
type OrderType = {
|
||||
order_no: string,
|
||||
order_status: number,
|
||||
payment_status: number,
|
||||
cancel_reason: string,
|
||||
pay_expire_at: string,
|
||||
consumer_deleted_at: string,
|
||||
total_amount: number,
|
||||
product_amount: number,
|
||||
shipping_fee: number,
|
||||
@@ -221,10 +229,51 @@ const orderItems = ref<OrderItemType[]>([])
|
||||
const shopName = ref('店铺名称')
|
||||
const deliveryAddress = ref<AddressType | null>(null)
|
||||
const deliveryInfo = ref<DeliveryInfoType | null>(null)
|
||||
const nowTick = ref<number>(Date.now())
|
||||
let detailTicker = 0
|
||||
|
||||
const toOrderStatusSource = (): OrderStatusSource | null => {
|
||||
const currentOrder = order.value
|
||||
if (currentOrder == null) return null
|
||||
return {
|
||||
order_status: currentOrder.order_status,
|
||||
payment_status: currentOrder.payment_status,
|
||||
pay_expire_at: currentOrder.pay_expire_at,
|
||||
created_at: currentOrder.created_at,
|
||||
cancel_reason: currentOrder.cancel_reason
|
||||
}
|
||||
}
|
||||
|
||||
const isTimeoutOrder = (): boolean => {
|
||||
const source = toOrderStatusSource()
|
||||
if (source == null) return false
|
||||
return isOrderPayExpired(source)
|
||||
}
|
||||
|
||||
const getPendingCountdownText = (): string => {
|
||||
const source = toOrderStatusSource()
|
||||
if (source == null) return ''
|
||||
const currentTick = nowTick.value
|
||||
if (currentTick < 0) return ''
|
||||
if (getOrderDisplayStatus(source) != 'pending') return ''
|
||||
return formatCountdownHMS(getRemainingSeconds(source))
|
||||
}
|
||||
|
||||
const shouldShowCancelledActions = (): boolean => {
|
||||
const source = toOrderStatusSource()
|
||||
if (source == null) return false
|
||||
return getOrderDisplayStatus(source) == 'cancelled'
|
||||
}
|
||||
|
||||
// 辅助函数 - 必须在调用前定义
|
||||
const getStatusText = (): string => {
|
||||
const status = order.value?.order_status ?? 0
|
||||
const source = toOrderStatusSource()
|
||||
if (source != null) {
|
||||
const displayStatus = getOrderDisplayStatus(source)
|
||||
if (displayStatus == 'pending') return '待付款'
|
||||
if (displayStatus == 'cancelled') return '已取消'
|
||||
}
|
||||
if (status == 1) return '待付款'
|
||||
if (status == 2) return '待发货'
|
||||
if (status == 3) return '待收货'
|
||||
@@ -237,6 +286,20 @@ const getStatusText = (): string => {
|
||||
|
||||
const getStatusDesc = (): string => {
|
||||
const status = order.value?.order_status ?? 0
|
||||
const source = toOrderStatusSource()
|
||||
if (source != null) {
|
||||
const displayStatus = getOrderDisplayStatus(source)
|
||||
if (displayStatus == 'pending') {
|
||||
return '请在 ' + getPendingCountdownText() + ' 内支付'
|
||||
}
|
||||
if (displayStatus == 'cancelled') {
|
||||
const currentReason = order.value?.cancel_reason ?? ''
|
||||
if (currentReason.indexOf('超时') >= 0) {
|
||||
return '订单超时未支付,已自动取消'
|
||||
}
|
||||
return '订单已取消'
|
||||
}
|
||||
}
|
||||
if (status == 1) return '请尽快完成支付'
|
||||
if (status == 2) return '商家正在打包商品'
|
||||
if (status == 3) return '商品正在赶往您的地址'
|
||||
@@ -249,6 +312,7 @@ const getStatusDesc = (): string => {
|
||||
|
||||
const getStatusIcon = (): string => {
|
||||
const status = order.value?.order_status ?? 0
|
||||
if (shouldShowCancelledActions()) return '⏰'
|
||||
if (status === 1) return '💳'
|
||||
if (status === 2) return '📦'
|
||||
if (status === 3) return '🚚'
|
||||
@@ -257,8 +321,14 @@ const getStatusIcon = (): string => {
|
||||
}
|
||||
|
||||
const getStatusClass = (): string => {
|
||||
const source = toOrderStatusSource()
|
||||
if (source != null) {
|
||||
const displayStatus = getOrderDisplayStatus(source)
|
||||
if (displayStatus == 'pending') return 'status-pending'
|
||||
if (displayStatus == 'cancelled') return 'status-cancelled'
|
||||
}
|
||||
const status = order.value?.order_status ?? 0
|
||||
return `status-${status}`
|
||||
return 'status-' + status
|
||||
}
|
||||
|
||||
const getFullAddress = (addr: any): string => {
|
||||
@@ -389,6 +459,10 @@ const loadOrderDetail = async () => {
|
||||
order.value = {
|
||||
order_no: (dataObj.get('order_no') ?? '') as string,
|
||||
order_status: (dataObj.get('order_status') ?? 1) as number,
|
||||
payment_status: (dataObj.get('payment_status') ?? 1) as number,
|
||||
cancel_reason: (dataObj.get('cancel_reason') ?? '') as string,
|
||||
pay_expire_at: (dataObj.get('pay_expire_at') ?? '') as string,
|
||||
consumer_deleted_at: (dataObj.get('consumer_deleted_at') ?? '') as string,
|
||||
total_amount: (dataObj.get('total_amount') ?? 0) as number,
|
||||
product_amount: (dataObj.get('product_amount') ?? 0) as number,
|
||||
shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,
|
||||
@@ -424,6 +498,15 @@ const loadOrderDetail = async () => {
|
||||
orderItems.value.push(orderItem)
|
||||
}
|
||||
}
|
||||
|
||||
if (order.value.consumer_deleted_at != '') {
|
||||
order.value = null
|
||||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
|
||||
}, 600)
|
||||
return
|
||||
}
|
||||
|
||||
const addressRaw = dataObj.get('shipping_address')
|
||||
console.log('[loadOrderDetail] 收货地址数据:', addressRaw)
|
||||
@@ -470,6 +553,7 @@ const loadOrderDetail = async () => {
|
||||
}
|
||||
|
||||
console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)
|
||||
syncTimeoutState()
|
||||
} else {
|
||||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||
}
|
||||
@@ -481,6 +565,35 @@ const loadOrderDetail = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const syncTimeoutState = (): void => {
|
||||
nowTick.value = Date.now()
|
||||
const currentOrder = order.value
|
||||
if (currentOrder == null) return
|
||||
if (currentOrder.order_status == ORDER_STATUS_PENDING && currentOrder.payment_status == PAYMENT_STATUS_UNPAID && isTimeoutOrder()) {
|
||||
currentOrder.order_status = ORDER_STATUS_CANCELLED
|
||||
currentOrder.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||
if (currentOrder.cancel_reason == '') {
|
||||
currentOrder.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
|
||||
}
|
||||
supabaseService.expireOrder(orderId.value)
|
||||
}
|
||||
}
|
||||
|
||||
const startDetailTicker = (): void => {
|
||||
if (detailTicker > 0) return
|
||||
syncTimeoutState()
|
||||
detailTicker = setInterval(() => {
|
||||
syncTimeoutState()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const stopDetailTicker = (): void => {
|
||||
if (detailTicker > 0) {
|
||||
clearInterval(detailTicker)
|
||||
detailTicker = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 动作函数
|
||||
const contactService = () => {
|
||||
const userId = supabaseService.getCurrentUserId()
|
||||
@@ -508,13 +621,49 @@ const contactService = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const payOrder = () => {
|
||||
const payOrder = async () => {
|
||||
if (isTimeoutOrder()) {
|
||||
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
|
||||
loadOrderDetail()
|
||||
return
|
||||
}
|
||||
|
||||
const totalAmount = order.value?.total_amount ?? 0
|
||||
const userId = supabaseService.getCurrentUserId()
|
||||
if (userId == null || userId === '') {
|
||||
goToLogin(`/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`)
|
||||
return
|
||||
}
|
||||
|
||||
const latestOrder = await supabaseService.getOrderDetail(orderId.value)
|
||||
if (latestOrder != null) {
|
||||
const latestObj = JSON.parse(JSON.stringify(latestOrder)) as UTSJSONObject
|
||||
const latestStatus = latestObj.getNumber('order_status') ?? 1
|
||||
const latestPaymentStatus = latestObj.getNumber('payment_status') ?? 1
|
||||
const latestCancelReason = latestObj.getString('cancel_reason') ?? ''
|
||||
const latestPayExpireAt = latestObj.getString('pay_expire_at') ?? ''
|
||||
if (order.value != null) {
|
||||
order.value.order_status = latestStatus
|
||||
order.value.payment_status = latestPaymentStatus
|
||||
order.value.cancel_reason = latestCancelReason
|
||||
order.value.pay_expire_at = latestPayExpireAt
|
||||
}
|
||||
if (isTimeoutOrder()) {
|
||||
await supabaseService.expireOrder(orderId.value)
|
||||
if (order.value != null) {
|
||||
order.value.order_status = ORDER_STATUS_CANCELLED
|
||||
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||
order.value.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
|
||||
}
|
||||
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (latestStatus != ORDER_STATUS_PENDING || latestPaymentStatus != PAYMENT_STATUS_UNPAID) {
|
||||
uni.showToast({ title: '订单状态已变更,不能继续支付', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
|
||||
})
|
||||
@@ -522,23 +671,14 @@ const payOrder = () => {
|
||||
|
||||
const doCancelOrder = async () => {
|
||||
try {
|
||||
const updatePayload = new UTSJSONObject()
|
||||
updatePayload.set('order_status', 5)
|
||||
updatePayload.set('updated_at', new Date().toISOString())
|
||||
|
||||
const result = await supa
|
||||
.from('ml_orders')
|
||||
.update(updatePayload)
|
||||
.eq('id', orderId.value)
|
||||
.execute()
|
||||
|
||||
if (result.error == null) {
|
||||
const result = await supabaseService.cancelOrder(orderId.value)
|
||||
if (result) {
|
||||
if (order.value != null) {
|
||||
order.value.order_status = 5
|
||||
order.value.order_status = ORDER_STATUS_CANCELLED
|
||||
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||
}
|
||||
uni.showToast({ title: '订单已取消' })
|
||||
} else {
|
||||
console.error('[doCancelOrder] 取消订单失败:', result.error)
|
||||
uni.showToast({ title: '取消失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -547,6 +687,34 @@ const doCancelOrder = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteOrder = async () => {
|
||||
uni.showLoading({ title: '删除中...' })
|
||||
try {
|
||||
const success = await supabaseService.softDeleteOrderForConsumer(orderId.value)
|
||||
uni.hideLoading()
|
||||
if (!success) {
|
||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '订单已删除', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
|
||||
}, 600)
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const viewSimilar = () => {
|
||||
if (orderItems.value.length == 0) {
|
||||
uni.showToast({ title: '暂无相似商品', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const keyword = orderItems.value[0].product_name != '' ? orderItems.value[0].product_name : '商品'
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}` })
|
||||
}
|
||||
|
||||
const cancelOrder = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
@@ -801,6 +969,21 @@ onLoad((options) => {
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
if (orderId.value != '') {
|
||||
loadOrderDetail()
|
||||
}
|
||||
startDetailTicker()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
stopDetailTicker()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
stopDetailTicker()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -868,12 +1051,26 @@ onLoad((options) => {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.status-countdown {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
font-size: 14px;
|
||||
opacity: 0.95;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: linear-gradient(135deg, #ff9000, #ff5000);
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: linear-gradient(135deg, #9aa5b1, #6b7280);
|
||||
}
|
||||
|
||||
/* 分享免单入口 */
|
||||
.share-free-entry {
|
||||
margin-top: 20px;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,27 @@ const orderId = ref('')
|
||||
const orderNo = ref('')
|
||||
const amount = ref(0)
|
||||
|
||||
const getOptionString = (options: UTSJSONObject, key: string): string => {
|
||||
try {
|
||||
const value = options.getString(key)
|
||||
if (value != null) {
|
||||
return value
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
try {
|
||||
const normalized = JSON.parse(JSON.stringify(options)) as UTSJSONObject
|
||||
const value = normalized.getString(key)
|
||||
if (value != null) {
|
||||
return value
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
// 定义 loadOrderInfo 函数(必须在 onMounted 之前)
|
||||
const loadOrderInfo = async () => {
|
||||
try {
|
||||
@@ -60,12 +81,12 @@ onLoad((options) => {
|
||||
if (options == null) return
|
||||
|
||||
const optionsObj = options as UTSJSONObject
|
||||
const orderIdValue = optionsObj.getString('orderId') ?? ''
|
||||
const orderIdValue = getOptionString(optionsObj, 'orderId')
|
||||
if (orderIdValue != '') {
|
||||
orderId.value = orderIdValue
|
||||
orderNo.value = orderIdValue
|
||||
|
||||
const amountValue = optionsObj.getString('amount') ?? ''
|
||||
const amountValue = getOptionString(optionsObj, 'amount')
|
||||
if (amountValue != '') {
|
||||
const amountStr = amountValue
|
||||
console.log('[payment-success] amountStr:', amountStr)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="search-page">
|
||||
<view class="search-navbar" :style="navbarStyle">
|
||||
<view class="back-btn" @click="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
<text class="back-icon"><</text>
|
||||
</view>
|
||||
|
||||
<view class="jd-search-box">
|
||||
@@ -12,7 +12,7 @@
|
||||
:value="searchKeyword"
|
||||
@input="onInput"
|
||||
@confirm="onSearch"
|
||||
:placeholder="(placeholderKeyword != null && placeholderKeyword !== '' && searchKeyword === '') ? placeholderKeyword : '请输入商品名称、店铺'"
|
||||
:placeholder="(placeholderKeyword != null && placeholderKeyword !== '' && searchKeyword === '') ? placeholderKeyword : '请输入商品名称、店铺'"
|
||||
placeholder-class="jd-placeholder"
|
||||
:focus="autoFocus"
|
||||
confirm-type="search"
|
||||
@@ -23,7 +23,7 @@
|
||||
</view>
|
||||
|
||||
<view class="camera-wrap" @tap.stop="handleCameraSearch">
|
||||
<text class="camera-icon">📷</text>
|
||||
<text class="camera-icon">拍</text>
|
||||
</view>
|
||||
|
||||
<view class="search-submit-btn" @tap.stop="onSearch">
|
||||
@@ -32,23 +32,25 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态(模拟服务器超时) -->
|
||||
<!-- 错误状态 -->
|
||||
<view v-if="isError" class="error-state" @click="retryLoad">
|
||||
<view class="error-content">
|
||||
<text class="error-icon">⚠️</text>
|
||||
<text class="error-title">加载服务器超时</text>
|
||||
<text class="error-icon">!</text>
|
||||
<text class="error-title">加载服务超时</text>
|
||||
<text class="error-desc">请点击屏幕重试</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容区域:使用 scroll-view 支持安卓端滚动 -->
|
||||
<!-- 涓诲唴瀹瑰尯鍩燂細浣跨敤 scroll-view 鏀寔瀹夊崜绔粴鍔?-->
|
||||
<scroll-view
|
||||
v-else
|
||||
class="main-content"
|
||||
direction="vertical"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<!-- 初始状态(无搜索词) -->
|
||||
v-else
|
||||
class="main-content"
|
||||
direction="vertical"
|
||||
:show-scrollbar="false"
|
||||
:lower-threshold="120"
|
||||
@scrolltolower="handleMainScrollToLower"
|
||||
>
|
||||
<!-- 初始状态 -->
|
||||
<view v-if="searchKeyword == '' && showResults == false">
|
||||
<!-- 搜索历史 -->
|
||||
<view v-if="searchHistory.length > 0" class="search-history">
|
||||
@@ -97,51 +99,21 @@
|
||||
>
|
||||
<text class="hot-rank" :class="index < 3 ? 'top-three' : ''">{{ index + 1 }}</text>
|
||||
<text class="hot-text">{{ item.keyword }}</text>
|
||||
<text v-if="item.hot == true" class="hot-icon">🔥</text>
|
||||
<text v-if="item.hot == true" class="hot-icon">热</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推荐商品(猜你喜欢) -->
|
||||
<!-- 猜你喜欢 -->
|
||||
<view class="guess-you-like">
|
||||
<view class="section-header">
|
||||
<view class="title-with-icon">
|
||||
<text class="section-icon">✨</text>
|
||||
<text class="section-title">猜你喜欢</text>
|
||||
</view>
|
||||
<text class="refresh-btn" @click="refreshGuessList">换一批</text>
|
||||
</view>
|
||||
|
||||
<view class="guess-grid">
|
||||
<view
|
||||
v-for="item in recommendList"
|
||||
:key="(item.id != null ? item.id : item.name)"
|
||||
class="guess-item"
|
||||
@click="viewProductDetail(item)"
|
||||
>
|
||||
<image class="guess-img" :src="item.image" mode="aspectFill" />
|
||||
<view v-if="item.cardTags.length > 0" class="card-tags-row">
|
||||
<text v-for="(tag, index) in item.cardTags" :key="item.id + '-guess-tag-' + index" class="card-tag">{{ tag }}</text>
|
||||
</view>
|
||||
<text class="guess-name" :lines="2">{{ item.name }}</text>
|
||||
<text v-if="item.highlight !== ''" class="card-highlight">{{ item.highlight }}</text>
|
||||
<view v-if="item.serviceTags.length > 0" class="service-tags-row">
|
||||
<text v-for="(tag, index) in item.serviceTags" :key="item.id + '-guess-service-' + index" class="service-tag">{{ tag }}</text>
|
||||
</view>
|
||||
<view class="guess-bottom">
|
||||
<view class="price-stack">
|
||||
<text class="guess-price">¥{{ item.price }}</text>
|
||||
<text v-if="item.marketPrice !== ''" class="market-price">¥{{ item.marketPrice }}</text>
|
||||
</view>
|
||||
<view class="guess-add-btn" @click.stop="addToCart(item)">
|
||||
<text class="guess-add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="item.salesText !== ''" class="card-sales-text">{{ item.salesText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<GuessYouLike
|
||||
title="猜你喜欢"
|
||||
:pageSize="8"
|
||||
:loadMoreKey="guessLoadMoreKey"
|
||||
@productClick="goToProductDetail"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索建议 -->
|
||||
<view v-if="searchKeyword != '' && showResults == false" class="search-suggestions">
|
||||
@@ -152,7 +124,7 @@
|
||||
class="suggestion-item"
|
||||
@click="selectSuggestion(suggestion)"
|
||||
>
|
||||
<view class="suggestion-icon">🔍</view>
|
||||
<view class="suggestion-icon">搜</view>
|
||||
<text class="suggestion-text">{{ suggestion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -176,7 +148,7 @@
|
||||
<image class="shop-logo" :src="shop.logo" mode="aspectFill" />
|
||||
<view class="shop-info">
|
||||
<text class="shop-name-txt">{{ shop.name }}</text>
|
||||
<text class="shop-products-txt">共{{ shop.productCount }}件商品</text>
|
||||
<text class="shop-products-txt">共 {{ shop.productCount }} 件商品</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -195,13 +167,13 @@
|
||||
class="filter-tab"
|
||||
:class="{ active: activeSort === 'sales' }"
|
||||
@click="switchSort('sales')"
|
||||
>销量</text>
|
||||
>销量</text>
|
||||
<text
|
||||
class="filter-tab"
|
||||
:class="{ active: activeSort === 'price' }"
|
||||
@click="switchSort('price')"
|
||||
>
|
||||
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
|
||||
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -235,14 +207,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空结果 - 仅在非加载状态且无结果时显示 -->
|
||||
<!-- 空结果 -->
|
||||
<view v-if="!loading && searchResults.length === 0" class="empty-result">
|
||||
<text class="empty-icon">🤔</text>
|
||||
<text class="empty-icon">!</text>
|
||||
<text class="empty-text">未找到相关商品</text>
|
||||
<text class="empty-sub">换个关键词试试吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多/加载中 - 在加载状态或有更多数据时显示 -->
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="loading" class="loading-more">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
@@ -252,41 +224,14 @@
|
||||
<text class="no-more-text">--- 到底了 ---</text>
|
||||
</view>
|
||||
|
||||
<view v-if="searchResults.length > 0 && recommendList.length > 0" class="guess-you-like" style="margin-top: 16rpx;">
|
||||
<view class="section-header">
|
||||
<view class="title-with-icon">
|
||||
<text class="section-icon">✨</text>
|
||||
<text class="section-title">猜你喜欢</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="guess-grid">
|
||||
<view
|
||||
v-for="item in recommendList"
|
||||
:key="(item.id != null ? item.id : '') + '_rec'"
|
||||
class="guess-item"
|
||||
@click="viewProductDetail(item)"
|
||||
>
|
||||
<image class="guess-img" :src="item.image" mode="aspectFill" />
|
||||
<view v-if="item.cardTags.length > 0" class="card-tags-row">
|
||||
<text v-for="(tag, index) in item.cardTags" :key="item.id + '-rec-tag-' + index" class="card-tag">{{ tag }}</text>
|
||||
</view>
|
||||
<text class="guess-name" :lines="2">{{ item.name }}</text>
|
||||
<text v-if="item.highlight !== ''" class="card-highlight">{{ item.highlight }}</text>
|
||||
<view v-if="item.serviceTags.length > 0" class="service-tags-row">
|
||||
<text v-for="(tag, index) in item.serviceTags" :key="item.id + '-rec-service-' + index" class="service-tag">{{ tag }}</text>
|
||||
</view>
|
||||
<view class="guess-bottom">
|
||||
<view class="price-stack">
|
||||
<text class="guess-price">¥{{ item.price }}</text>
|
||||
<text v-if="item.marketPrice !== ''" class="market-price">¥{{ item.marketPrice }}</text>
|
||||
</view>
|
||||
<view class="guess-add-btn" @click.stop="addToCart(item)">
|
||||
<text class="guess-add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="item.salesText !== ''" class="card-sales-text">{{ item.salesText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="searchResults.length > 0" class="guess-you-like" style="margin-top: 16rpx;">
|
||||
<GuessYouLike
|
||||
title="猜你喜欢"
|
||||
:pageSize="8"
|
||||
:excludeProductIds="searchResultProductIds"
|
||||
:loadMoreKey="guessLoadMoreKey"
|
||||
@productClick="goToProductDetail"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -302,6 +247,7 @@ import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
import type { Product } from '@/utils/supabaseService.uts'
|
||||
import { goToLogin } from '@/utils/utils.uts'
|
||||
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||||
|
||||
// 状态定义
|
||||
const statusBarHeight = ref(0)
|
||||
@@ -316,6 +262,7 @@ const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const isError = ref(false)
|
||||
const autoFocus = ref(true)
|
||||
const guessLoadMoreKey = ref<number>(0)
|
||||
|
||||
const navbarStyle = computed(() => {
|
||||
const top = navBarTop.value > 0 ? navBarTop.value : statusBarHeight.value
|
||||
@@ -330,7 +277,6 @@ const handleCameraSearch = () => {
|
||||
|
||||
const activeSort = ref('default') // 当前排序方式: default, sales, price
|
||||
const priceSortAsc = ref(false) // 价格排序是否为升序
|
||||
|
||||
type HotSearchItemType = {
|
||||
keyword: string
|
||||
hot: boolean
|
||||
@@ -436,21 +382,21 @@ const guessList = ref<Array<GuessItemType>>([])
|
||||
const allGuessItems = ref<Array<GuessItemType>>([])
|
||||
|
||||
const DEFAULT_HOT_KEYWORDS: Array<string> = [
|
||||
'大疆neo2',
|
||||
'澶х枂neo2',
|
||||
'iPhone 15 Pro',
|
||||
'Nike Air Max 270',
|
||||
'厨具',
|
||||
'老干妈',
|
||||
'钢化膜',
|
||||
'手机壳',
|
||||
'零食坚果',
|
||||
'新鲜水果',
|
||||
'液态硅胶壳',
|
||||
'充电宝',
|
||||
'蓝牙耳机'
|
||||
'厨具',
|
||||
'老干妈',
|
||||
'钢化膜',
|
||||
'手机壳',
|
||||
'零食坚果',
|
||||
'新鲜水果',
|
||||
'液态硅胶壳',
|
||||
'充电宝',
|
||||
'蓝牙耳机'
|
||||
]
|
||||
|
||||
// 推荐商品区(用于 “猜你喜欢/推荐商品”)
|
||||
// 推荐商品区
|
||||
const recommendList = ref<Array<GuessItemType>>([])
|
||||
const recommendPool = ref<Array<GuessItemType>>([])
|
||||
const recommendPage = ref(0)
|
||||
@@ -459,6 +405,16 @@ const recommendAppendSize = ref(20)
|
||||
const loadingRecommend = ref(false)
|
||||
const searchResults = ref<Array<SearchResultType>>([])
|
||||
const searchShopResults = ref<Array<ShopResultType>>([])
|
||||
const searchResultProductIds = computed((): Array<string> => {
|
||||
const ids: Array<string> = []
|
||||
for (let i = 0; i < searchResults.value.length; i++) {
|
||||
const id = searchResults.value[i].id
|
||||
if (id !== '' && ids.indexOf(id) < 0) {
|
||||
ids.push(id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
})
|
||||
|
||||
const SEARCH_HISTORY_KEY = 'consumer_search_history'
|
||||
|
||||
@@ -504,8 +460,8 @@ const MAX_EXPANDED_COUNT = 24
|
||||
|
||||
const clearHistory = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定清空搜索历史吗?',
|
||||
title: '提示',
|
||||
content: '确定清空搜索历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
searchHistory.value = []
|
||||
@@ -525,8 +481,6 @@ const deleteHistoryItem = (index: number) => {
|
||||
const toggleHistoryEdit = () => { isEditMode.value = !isEditMode.value }
|
||||
const toggleHistoryExpanded = () => { historyExpanded.value = !historyExpanded.value }
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
const visibleHistory = computed(() => {
|
||||
if (historyExpanded.value) {
|
||||
const maxLen = searchHistory.value.length < MAX_EXPANDED_COUNT ? searchHistory.value.length : MAX_EXPANDED_COUNT
|
||||
@@ -577,7 +531,7 @@ const loadData = async (): Promise<void> => {
|
||||
const hotResult = await supabaseService.getHotProducts(30)
|
||||
hotProducts = hotResult as Product[]
|
||||
} catch (hotError) {
|
||||
console.error('获取热销商品失败,使用空列表:', hotError)
|
||||
console.error('获取热销商品失败,使用空列表:', hotError)
|
||||
hotProducts = []
|
||||
}
|
||||
|
||||
@@ -656,7 +610,7 @@ const loadData = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const retryLoad = () => {
|
||||
uni.showLoading({ title: '重新加载中' })
|
||||
uni.showLoading({ title: '重新加载中...' })
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
loadData()
|
||||
@@ -774,7 +728,7 @@ const performSearch = async (): Promise<void> => {
|
||||
image: p.main_image_url ?? '/static/default.jpg',
|
||||
price: p.base_price ?? 0,
|
||||
marketPrice: formatMarketPriceText(p),
|
||||
specification: p.specification ?? '标准规格',
|
||||
specification: p.specification ?? '标准规格',
|
||||
tag: tag,
|
||||
sales: p.sale_count ?? 0,
|
||||
salesText: formatSalesText(p),
|
||||
@@ -848,7 +802,7 @@ const initPage = () => {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('初始化失败', e)
|
||||
console.error('初始化失败:', e)
|
||||
isError.value = true
|
||||
}
|
||||
}
|
||||
@@ -891,16 +845,16 @@ const onSearch = () => {
|
||||
}
|
||||
if (effective === '') return
|
||||
addToHistory(effective)
|
||||
// 如果搜索词来自 placeholder,确保输入框仍为空但执行搜索
|
||||
// 如果搜索词来自 placeholder,保持输入框为空但执行搜索
|
||||
if (userInput === '') {
|
||||
// 保持 searchKeyword 为空 but perform search with effective
|
||||
// 保持 searchKeyword 为空,但使用 effective 搜索
|
||||
searchKeyword.value = ''
|
||||
}
|
||||
// 将 searchKeyword 临时设置为 effective 以便 performSearch 使用(performSearch 使用 searchKeyword)
|
||||
// 临时将 searchKeyword 设置为 effective,供 performSearch 使用
|
||||
const prev = searchKeyword.value
|
||||
searchKeyword.value = effective
|
||||
performSearch()
|
||||
// 恢复输入框为空状态(如果用户未输入)
|
||||
// 如果用户没有手动输入,则恢复为空
|
||||
if (userInput === '') searchKeyword.value = prev
|
||||
}
|
||||
|
||||
@@ -934,12 +888,12 @@ const switchSort = (type: string) => {
|
||||
priceSortAsc.value = !priceSortAsc.value
|
||||
} else {
|
||||
activeSort.value = 'price'
|
||||
priceSortAsc.value = true // 默认升序
|
||||
priceSortAsc.value = true // 默认升序
|
||||
}
|
||||
} else {
|
||||
activeSort.value = type
|
||||
}
|
||||
// 重新执行搜索以获取正确排序的数据
|
||||
// 重新搜索以获取正确排序的数据
|
||||
performSearch()
|
||||
}
|
||||
|
||||
@@ -984,7 +938,7 @@ const loadMore = async (): Promise<void> => {
|
||||
image: p.main_image_url ?? '/static/default.jpg',
|
||||
price: p.base_price ?? 0,
|
||||
marketPrice: formatMarketPriceText(p),
|
||||
specification: p.specification ?? '标准规格',
|
||||
specification: p.specification ?? '标准规格',
|
||||
tag: tag,
|
||||
sales: p.sale_count ?? 0,
|
||||
salesText: formatSalesText(p),
|
||||
@@ -1004,67 +958,26 @@ const loadMore = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecommendGoods(appendSize: number): Promise<void> {
|
||||
if (loadingRecommend.value) return
|
||||
loadingRecommend.value = true
|
||||
try {
|
||||
// 如果后端支持分页接口,可在此调用;当前使用本地 pool
|
||||
if (appendSize == null || appendSize <= 0) {
|
||||
// 不追加时确保至少保持初始量
|
||||
recommendList.value = recommendPool.value.slice(0, recommendInitialSize.value)
|
||||
recommendPage.value = 1
|
||||
} else {
|
||||
const startIndex = recommendList.value.length
|
||||
const endIndex = startIndex + appendSize
|
||||
const slice = recommendPool.value.slice(startIndex, endIndex)
|
||||
// 如果池不够,尝试循环或留空
|
||||
if (slice.length === 0) {
|
||||
// 将池随机打乱并继续追加(循环播放)
|
||||
const arr = recommendPool.value.slice()
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
const t = arr[i]; arr[i] = arr[j]; arr[j] = t
|
||||
}
|
||||
recommendPool.value = arr
|
||||
const more = recommendPool.value.slice(0, appendSize)
|
||||
recommendList.value = recommendList.value.concat(more)
|
||||
} else {
|
||||
recommendList.value = recommendList.value.concat(slice)
|
||||
}
|
||||
recommendPage.value += 1
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载推荐商品失败', e)
|
||||
} finally {
|
||||
loadingRecommend.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function refreshGuessList(): void {
|
||||
loadRecommendGoods(recommendAppendSize.value)
|
||||
}
|
||||
|
||||
function handleReachBottom(): void {
|
||||
if (showResults.value === true) {
|
||||
loadMore()
|
||||
if (hasMore.value) {
|
||||
loadMore()
|
||||
return
|
||||
}
|
||||
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||
return
|
||||
}
|
||||
loadRecommendGoods(recommendAppendSize.value)
|
||||
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||
}
|
||||
|
||||
function handleMainScrollToLower(): void {
|
||||
handleReachBottom()
|
||||
}
|
||||
|
||||
onReachBottom(() => {
|
||||
handleReachBottom()
|
||||
})
|
||||
|
||||
const viewProductDetail = (item: SearchResultType | GuessItemType) => {
|
||||
const id = (item as GuessItemType).id
|
||||
const price = (item as GuessItemType).price
|
||||
const name = (item as GuessItemType).name
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}`
|
||||
})
|
||||
}
|
||||
|
||||
const viewShopDetail = (shop: ShopResultType) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
|
||||
@@ -1077,40 +990,40 @@ const addToCart = async (product: SearchResultType | GuessItemType) => {
|
||||
goToLogin('/pages/mall/consumer/search')
|
||||
return
|
||||
}
|
||||
uni.showLoading({ title: '检查商品...' })
|
||||
uni.showLoading({ title: '检查商品...' })
|
||||
try {
|
||||
// 统一转换为 UTSJSONObject 访问属性
|
||||
const prodObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||||
// 统一转换为 UTSJSONObject 访问属性
|
||||
const prodObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||||
const productId = prodObj.getString('id') ?? ''
|
||||
const merchantId = prodObj.getString('merchant_id') ?? ''
|
||||
|
||||
// 检查商品是否有SKU
|
||||
// 检查商品是否有 SKU
|
||||
const skus = await supabaseService.getProductSkus(productId)
|
||||
uni.hideLoading()
|
||||
|
||||
if (skus.length > 0) {
|
||||
// 有规格,提示并跳转到商品详情页选择规格
|
||||
uni.showToast({ title: '请选择规格', icon: 'none' })
|
||||
// 有规格时跳商品详情选择规格
|
||||
uni.showToast({ title: '请选择规格', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/product-detail?id=' + productId
|
||||
})
|
||||
}, 500)
|
||||
} else {
|
||||
// 无规格,直接加入购物车
|
||||
uni.showLoading({ title: '添加中...' })
|
||||
// 无规格则直接加入购物车
|
||||
uni.showLoading({ title: '添加中...' })
|
||||
const success = await supabaseService.addToCart(productId, 1, '', merchantId)
|
||||
uni.hideLoading()
|
||||
if (success) {
|
||||
uni.showToast({ title: '已添加到购物车', icon: 'success' })
|
||||
uni.showToast({ title: '已添加到购物车', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: '添加失败,请先登录', icon: 'none' })
|
||||
uni.showToast({ title: '添加失败,请先登录', icon: 'none' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('添加到购物车异常', e)
|
||||
console.error('添加到购物车异常', e)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '操作异常', icon: 'none' })
|
||||
uni.showToast({ title: '操作异常', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,31 +1032,31 @@ const openCamera = () => {
|
||||
count: 1,
|
||||
sourceType: ['camera'],
|
||||
success: (res) => {
|
||||
console.log('拍摄图片路径:', (res.tempFilePaths as string[])[0])
|
||||
uni.showToast({ title: '已启用相机', icon: 'none' })
|
||||
console.log('拍摄图片路径:', (res.tempFilePaths as string[])[0])
|
||||
uni.showToast({ title: '已启用相机', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('启用相机失败', err)
|
||||
console.error('鍚敤鐩告満澶辫触', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
if (showResults.value) {
|
||||
// 如果在搜索结果页,先返回到搜索初始页
|
||||
showResults.value = false
|
||||
searchKeyword.value = ''
|
||||
} else {
|
||||
// 如果在搜索初始页,则返回上一页
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
// 如果只有一页(由于深链接或重定向),返回首页
|
||||
uni.switchTab({
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
if (showResults.value) {
|
||||
// 如果在搜索结果页,先返回搜索初始页
|
||||
showResults.value = false
|
||||
searchKeyword.value = ''
|
||||
} else {
|
||||
// 如果在搜索初始页,则返回上一页
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
// 如果只有一个页面,则回首页
|
||||
uni.switchTab({
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1158,7 +1071,7 @@ const goBack = () => {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* 店铺搜索结果 */
|
||||
/* 搴楅摵鎼滅储缁撴灉 */
|
||||
.shop-results-section {
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
@@ -1486,7 +1399,7 @@ const goBack = () => {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
/* 猜你需要 */
|
||||
/* 鐚滀綘闇€瑕?*/
|
||||
.guess-you-like {
|
||||
margin-bottom: 24rpx;
|
||||
background-color: #ffffff;
|
||||
@@ -1840,7 +1753,7 @@ const goBack = () => {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 错误状态 */
|
||||
/* 閿欒鐘舵€?*/
|
||||
.error-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -1872,7 +1785,7 @@ const goBack = () => {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
/* 鍔犺浇鏇村 */
|
||||
.loading-more {
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
@@ -1932,3 +1845,5 @@ const goBack = () => {
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user