feat(companion): 全面扩展同城生活陪伴体系(陪吃·陪喝·陪玩),完成TDD测试与安全核销闭环

这个提交包含在:
wikiboss
2026-09-16 20:12:54 +08:00
父节点 a90cd3c0d9
当前提交 341023c77b
共有 15 个文件被更改,包括 972 次插入 和 23 次删除

查看文件

@@ -223,5 +223,46 @@ export const ICONS = {
<line x1="19" y1="5" x2="13.6" y2="10.4"></line>
<polyline points="15 5 19 5 19 9"></polyline>
</svg>
`),
// 陪吃·美食探店
utensils: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 2v6a3 3 0 0 1-3 3 3 3 0 0 1-3-3V2"></path>
<path d="M15 2v18"></path>
<path d="M5 2v7a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2V2"></path>
<path d="M8 11v9"></path>
</svg>
`),
// 陪喝·咖啡下午茶/茶室清吧
coffee: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8h1a4 4 0 0 1 0 8h-1"></path>
<path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path>
<line x1="6" y1="1" x2="6" y2="4"></line>
<line x1="10" y1="1" x2="10" y2="4"></line>
<line x1="14" y1="1" x2="14" y2="4"></line>
</svg>
`),
// 线下陪玩·同城桌游/剧本杀
dice: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#EC4899" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="2" width="20" height="20" rx="4"></rect>
<circle cx="7.5" cy="7.5" r="1.5" fill="#EC4899"></circle>
<circle cx="16.5" cy="16.5" r="1.5" fill="#EC4899"></circle>
<circle cx="12" cy="12" r="1.5" fill="#EC4899"></circle>
<circle cx="16.5" cy="7.5" r="1.5" fill="#EC4899"></circle>
<circle cx="7.5" cy="16.5" r="1.5" fill="#EC4899"></circle>
</svg>
`),
// 同城位置·商圈
mapPin: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
`)
};

查看文件

@@ -2,12 +2,13 @@ import { ICONS } from './icons.js';
export const GAMES_LIST = [
{ id: 'all', name: '全部', icon: ICONS.gameAll, hot: false },
{ id: 'wzry', name: '王者荣耀', icon: ICONS.gameCrown, hot: true },
{ id: 'offline_meal', name: '美食陪吃', icon: ICONS.utensils, hot: true },
{ id: 'offline_drink', name: '下午茶陪喝', icon: ICONS.coffee, hot: false },
{ id: 'offline_play', name: '同城陪玩', icon: ICONS.dice, hot: true },
{ id: 'wzry', name: '王者荣耀', icon: ICONS.gameCrown, hot: false },
{ id: 'hpjy', name: '和平精英', icon: ICONS.gameCrosshair, hot: false },
{ id: 'lol', name: '英雄联盟', icon: ICONS.gameSwords, hot: false },
{ id: 'val', name: '无畏契约', icon: ICONS.gameTarget, hot: false },
{ id: 'yjjw', name: '永劫无间', icon: ICONS.gameBlade, hot: false },
{ id: 'pubg', name: '绝地求生', icon: ICONS.gameTrophy, hot: false },
];
export const BANNERS = [
@@ -118,6 +119,58 @@ export const EXPERTS_LIST = [
],
reviews: [],
},
{
id: 'exp_105',
nickname: '橙子小七',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=400&fit=crop',
gender: 'female',
age: 22,
game: '美食陪吃',
track: 'OFFLINE_MEAL',
city: '北京市 · 朝阳/三里屯',
rankTag: '美食探店达人 · 火锅烤肉搭子',
levelTag: '同城红人',
price: 68,
rating: 5.0,
orderCount: 420,
isOnline: true,
voiceDuration: 14,
bio: '资深美食探店博主,性格随和开朗,陪你吃遍同城网红餐厅与地道市井美味,消费约定AA或请客~',
photos: ['https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=500&fit=crop'],
services: [
{ id: 'srv_7', title: '同城美食探店 · 火锅/烤肉饭搭子', price: 68, unit: '小时', desc: '公开商场餐厅,绝不踩雷,现场AA或买单' },
{ id: 'srv_8', title: '商圈早午餐/轻食下午茶陪伴', price: 58, unit: '小时', desc: '轻松闲聊,氛围愉快,阳光公开场所' },
],
reviews: [
{ id: 'r4', userNickname: '吃货小张', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['随和健谈', '推荐靠谱'], content: '火锅店推荐的太好吃了,聊得很投缘!', time: '昨天' },
],
},
{
id: 'exp_106',
nickname: '莫吉托学长',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=800&h=400&fit=crop',
gender: 'male',
age: 24,
game: '下午茶陪喝',
track: 'OFFLINE_DRINK',
city: '北京市 · 国贸/望京',
rankTag: '手冲咖啡师 · 清吧倾听解压',
levelTag: '暖心导师',
price: 58,
rating: 4.9,
orderCount: 310,
isOnline: true,
voiceDuration: 16,
bio: '前五星酒店调酒师与SCA认证咖啡师,善于倾听情绪解压,阳光透明,约定公开正规咖啡馆或清吧。',
photos: ['https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=500&fit=crop'],
services: [
{ id: 'srv_9', title: '精品咖啡品鉴 · 聊天解压倾听', price: 58, unit: '小时', desc: '星巴克臻选/独立咖啡馆,温和交流' },
{ id: 'srv_10', title: '正规清吧微醺特调陪伴', price: 78, unit: '小时', desc: '限公开正规商业清吧,严禁过量饮酒' },
],
reviews: [],
},
];
export const ACTIVITIES_LIST = [
@@ -176,6 +229,25 @@ export const ACTIVITIES_LIST = [
status: 'RECRUITING',
participants: [],
},
{
id: 'act_104',
title: '【AA制火锅】周末三里屯地道铜锅涮肉探店搭子',
game: '美食陪吃',
track: 'OFFLINE_MEAL',
payType: 'AA',
totalFee: 200,
aaFee: 50,
maxParticipants: 4,
currentParticipants: 2,
startTime: '周六 18:30',
location: '朝阳区三里屯商圈聚宝源',
creatorNickname: '橙子小七',
creatorAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100',
status: 'RECRUITING',
participants: [
{ id: 'p6', nickname: '涮肉小行家', avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', verifyCode: '5821', checkedIn: false },
],
},
];
export const ORDERS_LIST = [
@@ -189,6 +261,19 @@ export const ORDERS_LIST = [
status: 'IN_SERVICE',
createdAt: '2026-09-16 15:30',
},
{
id: 'ord_4',
orderSn: 'PW20260916099',
expert: { id: 'exp_105', nickname: '橙子小七', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', game: '美食陪吃' },
serviceTitle: '同城美食探店 · 火锅/烤肉饭搭子',
quantity: 2,
totalAmount: 136,
status: 'IN_SERVICE',
meetVenue: '朝阳大悦城海底捞',
expenseBurden: 'AA制各自付',
verifyCode: '6829',
createdAt: '2026-09-16 18:00',
},
{
id: 'ord_2',
orderSn: 'PW20260915088',

查看文件

@@ -3,7 +3,7 @@
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "电竞陪玩大厅",
"navigationBarTitleText": "陪玩中心",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
@@ -93,7 +93,7 @@
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "电竞陪玩",
"navigationBarTitleText": "陪玩中心",
"navigationBarBackgroundColor": "#0A0F1D",
"backgroundColor": "#0A0F1D"
},

查看文件

@@ -26,9 +26,9 @@
</view>
</view>
<!-- 游戏品类选择 -->
<!-- 品类选择 -->
<view class="form-card">
<view class="card-label">选择开黑游戏</view>
<view class="card-label">选择活动品类</view>
<view class="game-pills">
<view
v-for="g in games"
@@ -49,27 +49,27 @@
<input
class="row-input"
v-model="form.title"
placeholder="如:今晚百星王者五排极速冲分"
placeholder="如:三里屯火锅探店 / 百星五排极速冲分"
placeholder-class="ph"
/>
</view>
<view class="input-row">
<text class="row-label">开黑时段</text>
<text class="row-label">活动时段</text>
<input
class="row-input"
v-model="form.startTime"
placeholder="如:今晚 20:30"
placeholder="如:今晚 20:30 / 周六 18:30"
placeholder-class="ph"
/>
</view>
<view class="input-row">
<text class="row-label">语音频道</text>
<text class="row-label">地点/频道</text>
<input
class="row-input"
v-model="form.location"
placeholder="如:开黑语音 1 号房间 / YY 668899"
placeholder="如:朝阳区聚宝源 / 开黑语音 1 号房间"
placeholder-class="ph"
/>
</view>
@@ -133,7 +133,7 @@ import { ref, computed } from 'vue';
import { ICONS } from '@/common/icons.js';
import { ACTIVITIES_LIST } from '@/common/mock.js';
const games = ['王者荣耀', '和平精英', '英雄联盟', '无畏契约'];
const games = ['王者荣耀', '和平精英', '美食陪吃', '同城陪玩', '下午茶陪喝', '英雄联盟'];
const form = ref({
payType: 'AA',

查看文件

@@ -25,6 +25,11 @@
<text class="rank-tag">{{ expert.rankTag }}</text>
</view>
<view class="city-row" v-if="expert.city">
<image class="city-pin-svg" :src="ICONS.mapPin" mode="aspectFit" />
<text class="city-text">{{ expert.city }}</text>
</view>
<view class="data-row">
<view class="rating-box">
<image class="star-svg" :src="ICONS.star" mode="aspectFit" />
@@ -82,6 +87,15 @@
</view>
</view>
</view>
<!-- 线下安全守则提示卡 -->
<view class="offline-safety-card" v-if="expert.city">
<image class="safety-icon-svg" :src="ICONS.shield" mode="aspectFit" />
<view class="safety-content">
<text class="safety-title">绿色合规陪伴服务承诺</text>
<text class="safety-desc">该大神已完成公安实名联网比对与健康认证。同城陪玩/陪吃/陪喝仅限在公开正规商业场所履约,到场出示4位核销码现场签到。</text>
</view>
</view>
</view>
<!-- 战绩与生活照片 -->
@@ -322,6 +336,23 @@ const goToOrder = () => {
}
}
.city-row {
display: flex;
align-items: center;
gap: 6rpx;
margin-bottom: 8rpx;
.city-pin-svg {
width: 22rpx;
height: 22rpx;
}
.city-text {
font-size: 20rpx;
color: #38BDF8;
font-weight: 500;
}
}
.data-row {
display: flex;
align-items: center;
@@ -494,6 +525,41 @@ const goToOrder = () => {
}
}
.offline-safety-card {
margin-top: 20rpx;
background: rgba(16, 185, 129, 0.08);
border: 1px solid rgba(16, 185, 129, 0.2);
border-radius: 16rpx;
padding: 16rpx 20rpx;
display: flex;
align-items: flex-start;
gap: 12rpx;
.safety-icon-svg {
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
margin-top: 4rpx;
}
.safety-content {
flex: 1;
.safety-title {
font-size: 22rpx;
font-weight: 600;
color: #10B981;
display: block;
margin-bottom: 4rpx;
}
.safety-desc {
font-size: 20rpx;
color: #94A3B8;
line-height: 1.4;
display: block;
}
}
}
.gallery-scroll {
white-space: nowrap;

查看文件

@@ -235,6 +235,13 @@ const poolOrders = ref([
price: 45,
desc: '微信区/四排海岛/刚枪上分/kd保4以上',
countdown: 32
},
{
id: 'pool_103',
game: '美食陪吃',
price: 68,
desc: '朝阳大悦城海底捞/周末饭搭子/消费现场AA/需守时礼貌',
countdown: 56
}
]);
@@ -247,6 +254,15 @@ const activeOrders = ref([
hours: 2,
totalAmount: 70,
statusText: '正在开黑连麦对局中'
},
{
id: 'ord_active_2',
buyerName: '北京吃货小张',
buyerAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop',
serviceTitle: '火锅探店饭搭子',
hours: 2,
totalAmount: 136,
statusText: '已核销4位码入座,服务中'
}
]);

查看文件

@@ -4,7 +4,7 @@
<view class="header-search">
<view class="search-box">
<image class="search-icon-img" :src="ICONS.search" mode="aspectFit" />
<input class="search-input" placeholder="搜索陪玩大神、游戏昵称或项目" placeholder-class="placeholder-text" />
<input class="search-input" placeholder="搜索电竞陪玩、同城陪吃陪喝或大神" placeholder-class="placeholder-text" />
</view>
</view>
@@ -110,7 +110,11 @@
<image class="gender-svg" :src="expert.gender === 'female' ? ICONS.female : ICONS.male" mode="aspectFit" />
</view>
</view>
<view class="rank-badge">{{ expert.rankTag }}</view>
<view class="city-badge" v-if="expert.city">
<image class="city-pin-svg" :src="ICONS.mapPin" mode="aspectFit" />
<text class="city-text">{{ expert.city }}</text>
</view>
<view class="rank-badge" v-else>{{ expert.rankTag }}</view>
<!-- 语音试听条 (矢量SVG播放图标,带播放高亮光晕) -->
<view class="voice-bar btn-active" :class="{ playing: playingId === expert.id }" @click.stop="toggleVoice(expert.id)">
@@ -151,6 +155,9 @@ const playingId = ref(null);
const filteredExperts = computed(() => {
if (currentCategory.value === 'all') return experts.value;
const gameMap = {
offline_meal: '美食陪吃',
offline_drink: '下午茶陪喝',
offline_play: '同城陪玩',
wzry: '王者荣耀',
hpjy: '和平精英',
lol: '英雄联盟',
@@ -563,6 +570,24 @@ const navTo = (url) => {
}
}
.city-badge {
font-size: 20rpx;
color: #38BDF8;
background: rgba(56, 189, 248, 0.12);
padding: 4rpx 10rpx;
border-radius: 6rpx;
display: inline-flex;
align-items: center;
margin-bottom: 12rpx;
border: 1px solid rgba(56, 189, 248, 0.2);
.city-pin-svg {
width: 20rpx;
height: 20rpx;
margin-right: 6rpx;
}
}
.rank-badge {
font-size: 20rpx;
color: #F59E0B;

查看文件

@@ -61,7 +61,7 @@
</view>
</view>
<!-- 优惠抵扣与备注 -->
<!-- 优惠抵扣与履约约定 -->
<view class="order-card">
<view class="coupon-row btn-active" @click="selectCoupon">
<text class="cell-label">优惠券</text>
@@ -70,9 +70,49 @@
<text class="arrow">></text>
</view>
</view>
<view class="remark-row">
<!-- 线下同城专属:商圈场所与消费约定 -->
<block v-if="isOffline">
<view class="remark-row">
<text class="cell-label">碰面场所</text>
<input class="remark-input" v-model="meetVenue" placeholder="必须为公开正规场所,如:朝阳大悦城海底捞" placeholder-class="ph" />
</view>
<view class="expense-row">
<text class="cell-label">现场消费</text>
<view class="expense-pills">
<view
class="expense-pill btn-active"
:class="{ active: expenseBurden === 'AA' }"
@click="expenseBurden = 'AA'"
>
AA制各自付
</view>
<view
class="expense-pill btn-active"
:class="{ active: expenseBurden === 'TREAT' }"
@click="expenseBurden = 'TREAT'"
>
老板请客
</view>
<view
class="expense-pill btn-active"
:class="{ active: expenseBurden === 'SELF' }"
@click="expenseBurden = 'SELF'"
>
大神自理
</view>
</view>
</view>
<view class="safety-tip-card">
<image class="safety-icon-svg" :src="ICONS.shield" mode="aspectFit" />
<text class="safety-text">绿色阳光陪伴声明:服务仅限在正规公开商业场所进行。双方碰面后将出示 4 位数字核销码进行现场签到,保障双方人身与财产安全。</text>
</view>
</block>
<!-- 线上游戏专属:开黑区服与要求 -->
<view class="remark-row" v-else>
<text class="cell-label">开黑要求/游戏ID</text>
<input class="remark-input" v-model="remark" placeholder="如:QQ区/王者段位/包赢" placeholder-class="ph" />
<input class="remark-input" v-model="remark" placeholder="如:微信区/王者星耀段位/包赢" placeholder-class="ph" />
</view>
</view>
@@ -115,6 +155,19 @@ const hours = ref(2);
const timeType = ref('NOW');
const discount = ref(10);
const remark = ref('');
const meetVenue = ref('朝阳大悦城海底捞');
const expenseBurden = ref('AA');
const isOffline = computed(() => {
return expert.value && (
expert.value.track === 'OFFLINE_MEAL' ||
expert.value.track === 'OFFLINE_DRINK' ||
expert.value.track === 'OFFLINE_PLAY' ||
expert.value.game === '美食陪吃' ||
expert.value.game === '下午茶陪喝' ||
expert.value.game === '同城陪玩'
);
});
onLoad((query) => {
if (query && query.expertId) {
@@ -389,6 +442,63 @@ const handlePay = () => {
}
}
.expense-row {
display: flex;
align-items: center;
padding: 20rpx 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
.cell-label {
font-size: 24rpx;
color: #64748B;
width: 220rpx;
}
.expense-pills {
display: flex;
gap: 12rpx;
.expense-pill {
font-size: 22rpx;
color: #94A3B8;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 6rpx 16rpx;
border-radius: 12rpx;
&.active {
color: #F59E0B;
background: rgba(245, 158, 11, 0.15);
border-color: #F59E0B;
font-weight: 600;
}
}
}
}
.safety-tip-card {
margin-top: 20rpx;
background: rgba(16, 185, 129, 0.08);
border: 1px solid rgba(16, 185, 129, 0.2);
border-radius: 16rpx;
padding: 16rpx 20rpx;
display: flex;
align-items: flex-start;
gap: 12rpx;
.safety-icon-svg {
width: 28rpx;
height: 28rpx;
flex-shrink: 0;
margin-top: 4rpx;
}
.safety-text {
font-size: 20rpx;
color: #10B981;
line-height: 1.5;
}
}
.fee-row {
display: flex;
justify-content: space-between;