feat(lbs): 真实距离与按距离派单落地

- 数据库:orders/activities 新增 latitude/longitude(含存量表 ALTER 迁移)

- 服务端:Haversine 计算 distanceKm/distanceText,派单池支持 lat/lng/radius 过滤并按距离升序;历史无坐标订单用腾讯地理编码兜底并回写缓存

- 新增 /api/lbs/reverse 逆地址解析(Key 仅服务端持有,登录 + 限流)

- 小程序:雷达/接单大厅改用真实定位与真实距离,删除全部写死演示距离;下单页新增地图选点,组局页保存选点经纬度

- 修复接单大厅点接单会新建订单的问题,改为真正 claimOrder 认领

- 新增 lbs-distance 护栏(110 套件/729 用例全绿)
这个提交包含在:
xcbyy
2026-10-21 14:29:33 +08:00
父节点 ecc4a116c4
当前提交 68e41ca962
共有 10 个文件被更改,包括 584 次插入 和 235 次删除

查看文件

@@ -87,6 +87,8 @@ export async function createActivity(payload) {
title: payload.title,
gameCategory: payload.game || payload.gameCategory || '美食陪吃',
location: payload.location,
latitude: payload.latitude,
longitude: payload.longitude,
startTime: payload.startTime,
payType: payload.payType === 'AA' ? 1 : 2,
totalFeeCents: Math.round((Number(payload.totalFee) || 0) * 100),
@@ -684,16 +686,38 @@ export async function getUserFinanceRecords(userId) {
/**
* 获取可抢订单池 (真实读取 MySQL orders 待接单列表)
*/
export async function getOrdersPool() {
export async function getOrdersPool(options = {}) {
try {
const res = await http.get('/orders/pool');
if (Array.isArray(res) && res.length > 0) {
const lat = Number(options.lat);
const lng = Number(options.lng);
const radius = Number(options.radius);
const qs = [];
if (Number.isFinite(lat) && Number.isFinite(lng) && !(lat === 0 && lng === 0)) {
qs.push(`lat=${lat}`, `lng=${lng}`);
if (Number.isFinite(radius) && radius > 0) qs.push(`radius=${radius}`);
}
const res = await http.get(`/orders/pool${qs.length ? `?${qs.join('&')}` : ''}`);
if (Array.isArray(res)) {
return res;
}
} catch (err) {
console.warn('[API] 获取可抢订单池异常:', err.message);
throw err;
}
return [];
}
/**
* 坐标反查行政区/地址(雷达顶部定位文案)
*/
export async function reverseGeocode(latitude, longitude) {
try {
const res = await http.get(`/lbs/reverse?lat=${Number(latitude)}&lng=${Number(longitude)}`);
return res || null;
} catch (err) {
console.warn('[API] 逆地址解析异常:', err.message);
return null;
}
return null;
}
/**

查看文件

@@ -121,7 +121,7 @@
:key="zoneItem"
class="zone-chip-item btn-active"
:class="{ selected: form.location === zoneItem }"
@click="form.location = zoneItem"
@click="selectZone(zoneItem)"
>
{{ zoneItem.split(' ')[0] }}
</view>
@@ -380,6 +380,8 @@ const form = ref({
title: '',
startTime: '',
location: '',
latitude: null,
longitude: null,
maxParticipants: 4,
aaFee: '',
pricingMode: 'PER_PERSON',
@@ -470,12 +472,20 @@ const selectQuickTime = (timeItem) => {
}
};
const selectZone = (zoneItem) => {
form.value.location = zoneItem;
form.value.latitude = null;
form.value.longitude = null;
};
const handleChooseLocation = () => {
// #ifdef MP-WEIXIN || APP-PLUS
uni.chooseLocation({
success: (res) => {
if (res && (res.name || res.address)) {
form.value.location = res.name ? `${res.address ? res.address + ' ' : ''}${res.name}` : res.address;
form.value.latitude = Number.isFinite(Number(res.latitude)) ? Number(res.latitude) : null;
form.value.longitude = Number.isFinite(Number(res.longitude)) ? Number(res.longitude) : null;
uni.showToast({ title: '已选取地点', icon: 'success' });
}
},
@@ -483,7 +493,7 @@ const handleChooseLocation = () => {
console.warn('[ActivityCreate] 地图选点取消或未授权:', err);
// 若未授权或在H5/开发环境,平滑引导点选推荐公开商圈
if (recommendPublicZones.value.length > 0) {
form.value.location = recommendPublicZones.value[0];
selectZone(recommendPublicZones.value[0]);
uni.showToast({ title: '已填入推荐商圈', icon: 'none' });
}
}
@@ -492,7 +502,7 @@ const handleChooseLocation = () => {
// #ifndef MP-WEIXIN || APP-PLUS
if (recommendPublicZones.value.length > 0) {
form.value.location = recommendPublicZones.value[0];
selectZone(recommendPublicZones.value[0]);
uni.showToast({ title: '已为您填入示范商圈', icon: 'none' });
}
// #endif
@@ -647,6 +657,8 @@ const handleSubmit = async () => {
maxParticipants: form.value.maxParticipants,
startTime: trimmedTime,
location: trimmedLocation,
latitude: form.value.latitude,
longitude: form.value.longitude,
creatorNickname: cachedUser.nickname || '同城局长',
creatorAvatar: cachedUser.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop',
};

查看文件

@@ -58,6 +58,10 @@
<!-- 派单池订单列表 -->
<view class="dispatch-list">
<view v-if="filteredOrders.length === 0" class="hall-empty">
<text class="hall-empty-title">暂无待接需求单</text>
<text class="hall-empty-desc">开启定位并稍后刷新,附近新单会优先推给你</text>
</view>
<view
v-for="order in filteredOrders"
:key="order.id"
@@ -227,7 +231,8 @@ import AppTabBar from '@/components/CustomTabBar.vue';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { isExpertCertified } from '@/common/role.js';
import { createOrder, getOrdersPool, getExpertStats, claimOrder, updateCompanionProfile, getUserProfile } from '@/common/api.js';
import { claimOrder, getOrdersPool, getExpertStats, updateCompanionProfile, getUserProfile } from '@/common/api.js';
import { getUserLocation } from '@/common/location.js';
import { getNavBarInfo } from '@/common/nav.js';
const currentThemeKey = ref(getCurrentThemeKey());
@@ -289,6 +294,13 @@ const loadHallData = async () => {
isOnline.value = uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0';
// 2. 并行动态读取最新资料、经营数据与派单池
let myCoords = { lat: null, lng: null };
try {
const pos = await getUserLocation();
myCoords = { lat: pos.latitude, lng: pos.longitude };
} catch (_) {
// 未授权定位:距离显示「同城」,不影响大厅浏览
}
try {
await Promise.allSettled([
getUserProfile(uid).then((profile) => {
@@ -317,8 +329,8 @@ const loadHallData = async () => {
}
}).catch(() => {}),
getOrdersPool().then((pool) => {
if (pool && pool.length > 0) {
getOrdersPool(myCoords).then((pool) => {
if (Array.isArray(pool)) {
orders.value = pool.map(p => {
let typeClass = 'cat-food';
if (p.category.includes('喝') || p.category.includes('咖啡')) typeClass = 'cat-drink';
@@ -328,7 +340,7 @@ const loadHallData = async () => {
...p,
typeClass,
timeSlot: p.timeSlot || '今日内随时可约',
distance: '1.2km内',
distance: p.distanceText || '同城',
requirement: p.requirement || '按约定准时到场,文明健康社交。'
};
});
@@ -382,64 +394,7 @@ const categories = ref([
{ key: 'game', name: '线上电竞', count: 3, icon: ICONS.gameCrosshair }
]);
const orders = ref([
{
id: 101,
category: '同城美食陪吃',
typeClass: 'cat-food',
payMethod: 'AA制各付',
reward: 120,
title: '三里屯北京烤鸭探店·寻2位好脾气饭搭子',
location: '朝阳区 · 三里屯SOHO商场',
distance: '1.1km',
timeSlot: '今日 18:30 - 20:30',
requirement: '已提前订座,现场各自出示4位核销码,性格随和开朗。',
userName: '小鹿斑比',
userAvatar: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=100'
},
{
id: 102,
category: '线上电竞开黑',
typeClass: 'cat-game',
payMethod: '老板全包',
reward: 70,
title: '王者荣耀 · 微信区荣耀王者晋级双排',
location: '线上远程开黑',
distance: '全国',
timeSlot: '接单后即刻开始 (2局)',
requirement: '擅长射手或打野位,带飞晋级,游戏内清晰沟通不压力。',
userName: '想赢一把好难',
userAvatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100'
},
{
id: 103,
category: '同城下午茶陪喝',
typeClass: 'cat-drink',
payMethod: '房主买单',
reward: 150,
title: '工体精品手冲咖啡馆 · 倾听解压与交流',
location: '朝阳区 · 工体北路',
distance: '1.8km',
timeSlot: '今日 15:00 - 17:00',
requirement: '纯正规咖啡馆,工作压力大想倾诉,求善解人意的倾听导师。',
userName: '风吹过的夏天',
userAvatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=100'
},
{
id: 104,
category: '同城KTV陪唱',
typeClass: 'cat-sing',
payMethod: 'AA分摊',
reward: 200,
title: '量贩纯K包厢麦霸欢唱 · 流行情歌对唱',
location: '朝阳区 · 青年路大悦城店',
distance: '3.2km',
timeSlot: '今日 20:00 - 23:00',
requirement: '爱唱歌、不跑调,会带动气氛,严禁任何违规,正规量贩KTV。',
userName: '麦霸小王子',
userAvatar: 'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=100'
}
]);
const orders = ref([]);
const filteredOrders = computed(() => {
if (activeCategory.value === 'all') return orders.value;
@@ -476,20 +431,10 @@ const handleGrabOrder = (order) => {
if (res.confirm) {
uni.showLoading({ title: '正在锁定订单...' });
try {
await createOrder({
buyerId: 2001,
clientName: order.userName || '客户老板',
await claimOrder(order.id, {
expertId: expert.value.id || 2006,
companionName: expert.value.nickname || '接单大神',
expertAvatar: expert.value.avatar || DEFAULT_EXPERT_AVATAR,
track: order.category || '同城生活',
serviceTitle: order.title,
hourlyRate: order.reward,
durationHours: 2,
amount: order.reward,
location: order.location,
expenseBurden: order.payMethod,
status: '履约中'
expertAvatar: expert.value.avatar || DEFAULT_EXPERT_AVATAR
});
} catch (err) {
uni.hideLoading();
@@ -802,6 +747,29 @@ const copyOrderNo = () => {
flex-direction: column;
gap: 20rpx;
.hall-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx;
padding: 60rpx 24rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.03);
border: 1rpx dashed rgba(255, 255, 255, 0.12);
.hall-empty-title {
font-size: 28rpx;
font-weight: 600;
color: #E2E8F0;
}
.hall-empty-desc {
font-size: 24rpx;
color: #94A3B8;
}
}
.dispatch-card {
background: #151D30;
border-radius: 24rpx;

查看文件

@@ -125,6 +125,10 @@
<!-- 订单卡片流 -->
<view class="orders-stream">
<view v-if="nearbyOrders.length === 0" class="pool-empty">
<text class="pool-empty-title">附近 {{ currentRadius }}km 暂无待接需求单</text>
<text class="pool-empty-desc">调整巡航半径或点击右上角刷新试试</text>
</view>
<view
v-for="item in nearbyOrders"
:key="item.id"
@@ -185,7 +189,8 @@ import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { isExpertCertified } from '@/common/role.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getOrdersPool, claimOrder, createOrder } from '@/common/api.js';
import { getOrdersPool, claimOrder, createOrder, reverseGeocode } from '@/common/api.js';
import { getUserLocation } from '@/common/location.js';
const navInfo = ref(getNavBarInfo());
const navStyle = computed(() => ({
@@ -203,9 +208,12 @@ const currentRadius = ref(5);
const rangeOptions = [1, 3, 5];
const selectedOrderId = ref(1);
const myCoords = ref({ lat: null, lng: null });
const locating = ref(false);
const currentLocation = ref({
name: '朝阳区 · 三里屯太古里北区',
address: '北京市朝阳区三里屯路19号院 (精度 ±5m)'
name: '正在定位…',
address: '正在获取当前位置'
});
const onThemeChanged = (newTheme) => {
@@ -213,63 +221,98 @@ const onThemeChanged = (newTheme) => {
applyThemeToNativeUI(newTheme);
};
const markerPosition = (distanceKm, idx) => {
const radius = Math.max(1, Number(currentRadius.value) || 5);
const ratio = Number.isFinite(Number(distanceKm))
? Math.min(0.92, Math.max(0.16, Number(distanceKm) / radius))
: 0.5;
const angle = (Math.PI * 2 * idx) / 5 - Math.PI / 2;
const top = 50 + Math.sin(angle) * ratio * 40;
const left = 50 + Math.cos(angle) * ratio * 40;
return { top: `${top.toFixed(1)}%`, left: `${left.toFixed(1)}%` };
};
const loadNearbyOrdersFromPool = async () => {
try {
const pool = await getOrdersPool();
if (pool && pool.length > 0) {
const coords = [
{ top: '28%', left: '22%', dist: '650m' },
{ top: '32%', left: '72%', dist: '1.4km' },
{ top: '68%', left: '30%', dist: '2.2km' },
{ top: '65%', left: '75%', dist: '3.8km' },
{ top: '45%', left: '50%', dist: '900m' }
];
nearbyOrders.value = pool.slice(0, 5).map((p, idx) => {
let typeClass = 'type-food';
let typeIcon = ICONS.utensils;
if (p.category.includes('喝') || p.category.includes('咖啡')) {
typeClass = 'type-coffee';
typeIcon = ICONS.coffee;
} else if (p.category.includes('唱') || p.category.includes('KTV')) {
typeClass = 'type-ktv';
typeIcon = ICONS.micVocal;
} else if (p.category.includes('游戏') || p.category.includes('电竞')) {
typeClass = 'type-game';
typeIcon = ICONS.gameAll;
}
const coord = coords[idx % coords.length];
return {
id: p.id,
categoryName: p.category,
title: p.title,
requirement: p.requirement || '按约定准时到场,现场出示4位核销码,文明健康社交。',
reward: p.reward,
duration: '2小时',
distance: coord.dist,
timeAgo: '刚刚',
buyerName: p.userName || '客户老板',
buyerAvatar: p.userAvatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120',
typeClass,
typeIcon,
markerTop: coord.top,
markerLeft: coord.left
};
});
if (nearbyOrders.value.length > 0) {
selectedOrderId.value = nearbyOrders.value[0].id;
const pool = await getOrdersPool({
lat: myCoords.value.lat,
lng: myCoords.value.lng,
radius: currentRadius.value
});
nearbyOrders.value = (pool || []).slice(0, 5).map((p, idx) => {
let typeClass = 'type-food';
let typeIcon = ICONS.utensils;
if (String(p.category || '').includes('喝') || String(p.category || '').includes('咖啡')) {
typeClass = 'type-coffee';
typeIcon = ICONS.coffee;
} else if (String(p.category || '').includes('唱') || String(p.category || '').includes('KTV')) {
typeClass = 'type-ktv';
typeIcon = ICONS.micVocal;
} else if (String(p.category || '').includes('游戏') || String(p.category || '').includes('电竞')) {
typeClass = 'type-game';
typeIcon = ICONS.gameAll;
}
const pos = markerPosition(p.distanceKm, idx);
return {
id: p.id,
categoryName: p.category,
title: p.title,
requirement: p.requirement || '按约定准时到场,现场出示4位核销码,文明健康社交。',
reward: p.reward,
duration: '2小时',
distance: p.distanceText || '同城',
distanceKm: p.distanceKm,
timeAgo: '刚刚',
buyerName: p.userName || '客户老板',
buyerAvatar: p.userAvatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120',
typeClass,
typeIcon,
markerTop: pos.top,
markerLeft: pos.left
};
});
if (nearbyOrders.value.length > 0) {
selectedOrderId.value = nearbyOrders.value[0].id;
}
} catch (err) {
console.warn('[Radar] 加载雷达单池异常:', err);
uni.showToast({ title: '获取附近需求单失败,请稍后重试', icon: 'none' });
}
};
const refreshLocation = async () => {
if (locating.value) return;
locating.value = true;
try {
const pos = await getUserLocation();
myCoords.value = { lat: pos.latitude, lng: pos.longitude };
const geo = await reverseGeocode(pos.latitude, pos.longitude);
if (geo && geo.name) {
currentLocation.value = { name: geo.name, address: geo.address || '当前位置' };
} else {
currentLocation.value = {
name: '当前位置',
address: `经纬度 ${Number(pos.latitude).toFixed(4)}, ${Number(pos.longitude).toFixed(4)}`
};
}
} catch (err) {
myCoords.value = { lat: null, lng: null };
currentLocation.value = {
name: '未开启定位',
address: '开启定位权限后可查看附近需求单的真实距离'
};
uni.showToast({ title: '未获取到定位,距离将显示为同城', icon: 'none' });
} finally {
locating.value = false;
}
await loadNearbyOrdersFromPool();
};
onMounted(() => {
navInfo.value = getNavBarInfo();
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
uni.$on('theme-change', onThemeChanged);
loadNearbyOrdersFromPool();
});
onShow(() => {
@@ -288,7 +331,7 @@ onShow(() => {
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
uni.hideTabBar({ animation: false, fail: () => {} });
loadNearbyOrdersFromPool();
refreshLocation();
});
onUnmounted(() => {
@@ -296,90 +339,27 @@ onUnmounted(() => {
});
const nearbyOrders = ref([
{
id: 1,
categoryName: '同城美食陪吃',
title: '三里屯太古里 · 铜锅涮肉AA搭子',
requirement: '吃货随和好相处,现场出示4位核销码,餐费已预订AA',
reward: 98,
duration: '2小时',
distance: '650m',
timeAgo: '5分钟前',
buyerName: '北京吃货小王',
buyerAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120',
typeClass: 'type-food',
typeIcon: ICONS.utensils,
markerTop: '28%',
markerLeft: '22%'
},
{
id: 2,
categoryName: '同城下午茶陪喝',
title: '工体北路 · 独立手冲咖啡闲聊倾听',
requirement: '安静舒适咖啡馆,主要是倾听工作解压,老板全买单请客',
reward: 120,
duration: '2小时',
distance: '1.4km',
timeAgo: '12分钟前',
buyerName: '晴天小筑',
buyerAvatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120',
typeClass: 'type-coffee',
typeIcon: ICONS.coffee,
markerTop: '32%',
markerLeft: '72%'
},
{
id: 3,
categoryName: '同城KTV陪唱',
title: '工体量贩纯K · 麦霸对唱与气氛组',
requirement: '会唱周杰伦/陈奕迅经典情歌,气氛欢快,拒绝私密',
reward: 180,
duration: '2.5小时',
distance: '2.2km',
timeAgo: '18分钟前',
buyerName: '欢唱麦霸哥',
buyerAvatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=120',
typeClass: 'type-ktv',
typeIcon: ICONS.micVocal,
markerTop: '68%',
markerLeft: '30%'
},
{
id: 4,
categoryName: '线上王者冲分',
title: '王者荣耀 · 荣耀王者晋级双排急召',
requirement: '擅长打野/射手节奏位,全程语音沟通,保星连胜',
reward: 70,
duration: '2局',
distance: '3.8km',
timeAgo: '25分钟前',
buyerName: '摘星少年',
buyerAvatar: 'https://images.unsplash.com/photo-1527980965255-d3b416303d12?w=120',
typeClass: 'type-game',
typeIcon: ICONS.gameAll,
markerTop: '65%',
markerLeft: '75%'
}
]);
const nearbyOrders = ref([]);
const changeRadius = (r) => {
const changeRadius = async (r) => {
currentRadius.value = r;
uni.showToast({
title: `已更新探测范围至周边 ${r} 公里`,
icon: 'none'
});
await loadNearbyOrdersFromPool();
};
const handleRelocate = () => {
uni.showLoading({ title: '正在校准GPS...' });
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: 'GPS定位已校准 (精度 ±3m)',
icon: 'success'
});
}, 400);
const handleRelocate = async () => {
uni.showLoading({ title: '正在重新定位...' });
try {
await refreshLocation();
} catch (_) {}
uni.hideLoading();
uni.showToast({
title: myCoords.value.lat ? '定位已更新' : '定位失败,请检查定位权限',
icon: myCoords.value.lat ? 'success' : 'none'
});
};
const toggleRadarSwitch = (e) => {
@@ -394,12 +374,14 @@ const selectMarkerOrder = (id) => {
selectedOrderId.value = id;
};
const refreshNearbyOrders = () => {
const refreshNearbyOrders = async () => {
uni.showLoading({ title: '雷达刷新中...' });
setTimeout(() => {
uni.hideLoading();
uni.showToast({ title: '已匹配最新 5km 派单池', icon: 'success' });
}, 500);
await refreshLocation();
uni.hideLoading();
uni.showToast({
title: `已匹配 ${currentRadius.value}km 内最新派单池`,
icon: 'success'
});
};
const handleTakeOrder = (order) => {
@@ -938,6 +920,29 @@ const handleTakeOrder = (order) => {
flex-direction: column;
gap: 20rpx;
.pool-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx;
padding: 60rpx 24rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.03);
border: 1rpx dashed rgba(255, 255, 255, 0.12);
.pool-empty-title {
font-size: 28rpx;
font-weight: 600;
color: #E2E8F0;
}
.pool-empty-desc {
font-size: 24rpx;
color: #94A3B8;
}
}
.nearby-card {
background: #151D30;
border-radius: 24rpx;

查看文件

@@ -76,6 +76,7 @@
<view class="remark-row">
<text class="cell-label">碰面场所</text>
<input class="remark-input" v-model="meetVenue" placeholder="正规场所,如:工体纯K量贩KTV/朝阳大悦城" placeholder-class="ph" />
<view class="pick-loc-btn btn-active" @click="pickVenue">地图选点</view>
</view>
<view class="expense-row">
<text class="cell-label">现场消费</text>
@@ -326,6 +327,28 @@ const timeType = ref('NOW');
const discount = ref(0);
const remark = ref('');
const meetVenue = ref('朝阳大悦城海底捞');
const meetCoords = ref({ latitude: null, longitude: null });
const pickVenue = () => {
// #ifdef MP-WEIXIN || APP-PLUS
uni.chooseLocation({
success: (res) => {
if (!res) return;
const name = res.name ? `${res.address ? res.address + ' ' : ''}${res.name}` : res.address;
if (name) meetVenue.value = name;
meetCoords.value = {
latitude: Number.isFinite(Number(res.latitude)) ? Number(res.latitude) : null,
longitude: Number.isFinite(Number(res.longitude)) ? Number(res.longitude) : null
};
uni.showToast({ title: '已选择碰面地点', icon: 'success' });
},
fail: () => uni.showToast({ title: '未选择地点,可手动填写', icon: 'none' })
});
// #endif
// #ifndef MP-WEIXIN || APP-PLUS
uni.showToast({ title: '当前环境不支持地图选点,请手动填写', icon: 'none' });
// #endif
};
const expenseBurden = ref('AA');
const isOffline = computed(() => {
@@ -518,6 +541,8 @@ const handlePay = async () => {
userCouponId: usingUserCoupon ? String(currentCoupon.value.id) : '',
payType: payMethod.value,
location: isOffline.value ? meetVenue.value : (remark.value || '线上语音开黑'),
latitude: isOffline.value ? meetCoords.value.latitude : null,
longitude: isOffline.value ? meetCoords.value.longitude : null,
expenseBurden: expenseBurden.value === 'AA' ? 'AA制各自付' : (expenseBurden.value === 'CLIENT' ? '客户全包' : '大神自理'),
remark: remark.value
};
@@ -829,6 +854,15 @@ const handlePay = async () => {
.ph {
color: var(--text-muted);
}
.pick-loc-btn {
flex-shrink: 0;
margin-left: 12rpx;
padding: 8rpx 18rpx;
border-radius: 24rpx;
font-size: 22rpx;
color: #FFFFFF;
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
}
}
.expense-row {