feat(pwuniapp): 在HBuilderX原生工程中实现电竞暗夜流光全套静态UI页面及4位门票核销

这个提交包含在:
wikiboss
2026-09-16 16:47:30 +08:00
父节点 1fafbf977a
当前提交 935d6fa840
共有 34 个文件被更改,包括 3919 次插入 和 11244 次删除

9
pwuniapp/.editorconfig 普通文件
查看文件

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

3
pwuniapp/.gitignore vendored 普通文件
查看文件

@@ -0,0 +1,3 @@
unpackage/
.DS_Store
.hbuilderx/

43
pwuniapp/App.vue 普通文件
查看文件

@@ -0,0 +1,43 @@
<script>
export default {
onLaunch: function () {
console.log('App Launch: 电竞陪玩小程序初始化');
},
onShow: function () {
console.log('App Show');
},
onHide: function () {
console.log('App Hide');
},
};
</script>
<style lang="scss">
/* 全局暗黑电竞基础重置 */
page {
background-color: #0A0F1D;
color: #F8FAFC;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', sans-serif;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
view, text, image, input, button {
box-sizing: border-box;
}
/* 触控反馈标准类 (满足 UI/UX Pro Max 规范) */
.btn-active:active, .card-active:active {
transform: scale(0.98);
opacity: 0.9;
transition: transform 100ms ease-out, opacity 100ms ease-out;
}
/* 隐藏移动端滚动条 */
::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
}
</style>

223
pwuniapp/common/mock.js 普通文件
查看文件

@@ -0,0 +1,223 @@
export const GAMES_LIST = [
{ id: 'all', name: '全部', icon: '🎮', hot: true },
{ id: 'wzry', name: '王者荣耀', icon: '👑', hot: true },
{ id: 'hpjy', name: '和平精英', icon: '🎯', hot: true },
{ id: 'lol', name: '英雄联盟', icon: '⚔️', hot: true },
{ id: 'val', name: '无畏契约', icon: '🔫', hot: true },
{ id: 'yjjw', name: '永劫无间', icon: '🗡️', hot: false },
{ id: 'pubg', name: '绝地求生', icon: '🏆', hot: false },
];
export const BANNERS = [
{ id: '1', title: '周末车队狂欢 | AA制五排冲分', tag: '热门活动', bg: 'linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)' },
{ id: '2', title: '新用户专享 | 首单立减20元', tag: '新人福利', bg: 'linear-gradient(135deg, #059669 0%, #10B981 100%)' },
{ id: '3', title: '全国水友杯内战 | 项目制万元奖池', tag: '巅峰赛事', bg: 'linear-gradient(135deg, #D97706 0%, #F59E0B 100%)' },
];
export const EXPERTS_LIST = [
{
id: 'exp_101',
nickname: '月色浅浅',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&h=200&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=800&h=400&fit=crop',
gender: 'female',
age: 21,
game: '王者荣耀',
rankTag: '百星王者 · 国服瑶妹',
levelTag: '金牌导师',
price: 35,
rating: 4.9,
orderCount: 1280,
isOnline: true,
voiceDuration: 12,
bio: '全天在线可连麦,心态超好,软妹音/御姐音切换,带你轻松摘星~',
photos: [
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=500&fit=crop',
'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=500&fit=crop',
],
services: [
{ id: 'srv_1', title: '王者荣耀 · 排位包胜率', price: 35, unit: '小时', desc: '不包赢不退,高效稳定冲星' },
{ id: 'srv_2', title: '王者荣耀 · 娱乐连麦陪练', price: 28, unit: '小时', desc: '温柔互动,欢乐开黑' },
],
reviews: [
{ id: 'r1', userNickname: '电竞刺客', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['声音甜美', '技术过硬'], content: '小姐姐声音太好听了,操作也很稳,连赢5把!', time: '2小时前' },
{ id: 'r2', userNickname: '峡谷追梦人', userAvatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', rating: 5, tags: ['准时上号', '耐心教学'], content: '很耐心教辅助意识,体验绝佳。', time: '昨天' },
],
},
{
id: 'exp_102',
nickname: '夜鹰狙神',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?w=800&h=400&fit=crop',
gender: 'male',
age: 23,
game: '和平精英',
rankTag: '无敌战神 · 15KD',
levelTag: '顶流大神',
price: 45,
rating: 5.0,
orderCount: 2360,
isOnline: true,
voiceDuration: 18,
bio: '前职业青训选手,意识拉满,带粉乱杀,绝不白给!',
photos: [
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&fit=crop',
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&fit=crop',
],
services: [
{ id: 'srv_3', title: '和平精英 · 战神四排车队', price: 45, unit: '小时', desc: '战神指引,全程高能护航' },
{ id: 'srv_4', title: '吃鸡指导 · 枪法与身法特训', price: 60, unit: '小时', desc: '1对1定制身法特训' },
],
reviews: [
{ id: 'r3', userNickname: 'AWM战狂', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['枪法如神', '落地包稳'], content: '真的太猛了,连吃三把鸡,KD直接飙升!', time: '前天' },
],
},
{
id: 'exp_103',
nickname: '软软喵',
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1538481199705-c710c4e965fc?w=800&h=400&fit=crop',
gender: 'female',
age: 20,
game: '英雄联盟',
rankTag: '傲世宗师 · 中单法王',
levelTag: '新星大神',
price: 30,
rating: 4.8,
orderCount: 650,
isOnline: false,
voiceDuration: 9,
bio: '峡谷高分妹子,阿狸/辛德拉本命,能C能辅,幽默开心果~',
photos: ['https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&fit=crop'],
services: [
{ id: 'srv_5', title: 'LOL峡谷排位双排', price: 30, unit: '小时', desc: '双排上分,稳定Carry' },
],
reviews: [],
},
{
id: 'exp_104',
nickname: '瞬移者雷克',
avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&h=200&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=800&h=400&fit=crop',
gender: 'male',
age: 22,
game: '无畏契约',
rankTag: '赋能战魂 · 芮娜绝活',
levelTag: '优质陪练',
price: 40,
rating: 4.9,
orderCount: 890,
isOnline: true,
voiceDuration: 15,
bio: '瓦罗兰特高分车头,定位准身法快,报点清晰,带你上神话!',
photos: ['https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&fit=crop'],
services: [
{ id: 'srv_6', title: '无畏契约冲分排位', price: 40, unit: '小时', desc: '带队突破,绝不拖泥带水' },
],
reviews: [],
},
];
export const ACTIVITIES_LIST = [
{
id: 'act_101',
title: '【AA制五排】今晚8点王者百星车队冲分',
game: '王者荣耀',
payType: 'AA',
totalFee: 120,
aaFee: 30,
maxParticipants: 4,
currentParticipants: 3,
startTime: '今晚 20:00',
location: '开黑语音1号频道',
creatorNickname: '月色浅浅',
creatorAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100',
status: 'RECRUITING',
participants: [
{ id: 'p1', nickname: '峡谷追梦', avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', verifyCode: '8392', checkedIn: false },
{ id: 'p2', nickname: '无敌小刺客', avatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', verifyCode: '6147', checkedIn: true },
{ id: 'p3', nickname: '快乐小风男', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100', verifyCode: '9025', checkedIn: false },
],
},
{
id: 'act_102',
title: '【项目制·粉丝福利】夜鹰带队水友吃鸡四排',
game: '和平精英',
payType: 'PROJECT',
totalFee: 300,
aaFee: 0,
maxParticipants: 3,
currentParticipants: 3,
startTime: '周六 15:00',
location: 'YY频道 668899',
creatorNickname: '夜鹰狙神',
creatorAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100',
status: 'RUNNING',
participants: [
{ id: 'p4', nickname: '绝地菜鸟', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100', verifyCode: '2481', checkedIn: true },
{ id: 'p5', nickname: '伏地魔本魔', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100', verifyCode: '7390', checkedIn: false },
],
},
{
id: 'act_103',
title: '【AA制】无畏契约五黑内战水友欢乐局',
game: '无畏契约',
payType: 'AA',
totalFee: 160,
aaFee: 20,
maxParticipants: 8,
currentParticipants: 5,
startTime: '周日 19:30',
location: '自定义内战房间',
creatorNickname: '瞬移者雷克',
creatorAvatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100',
status: 'RECRUITING',
participants: [],
},
];
export const ORDERS_LIST = [
{
id: 'ord_1',
orderSn: 'PW20260916001',
expert: { id: 'exp_101', nickname: '月色浅浅', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', game: '王者荣耀' },
serviceTitle: '王者荣耀 · 排位包胜率',
quantity: 2,
totalAmount: 70,
status: 'IN_SERVICE',
createdAt: '2026-09-16 15:30',
},
{
id: 'ord_2',
orderSn: 'PW20260915088',
expert: { id: 'exp_102', nickname: '夜鹰狙神', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100', game: '和平精英' },
serviceTitle: '和平精英 · 战神四排车队',
quantity: 1,
totalAmount: 45,
status: 'COMPLETED',
createdAt: '2026-09-15 19:00',
},
{
id: 'ord_3',
orderSn: 'PW20260914032',
expert: { id: 'exp_103', nickname: '软软喵', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100', game: '英雄联盟' },
serviceTitle: 'LOL峡谷排位双排',
quantity: 3,
totalAmount: 90,
status: 'PENDING_PAY',
createdAt: '2026-09-14 21:10',
},
];
export const USER_PROFILE = {
id: 'u_888',
nickname: '超能电竞玩家',
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop&crop=faces',
gender: 'male',
role: 'PLAYER',
balance: 380.5,
frozenBalance: 0,
commission: 128.0,
invitedCount: 16,
isCertifiedExpert: false,
};

20
pwuniapp/index.html 普通文件
查看文件

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

24
pwuniapp/main.js 普通文件
查看文件

@@ -0,0 +1,24 @@
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

69
pwuniapp/manifest.json 普通文件
查看文件

@@ -0,0 +1,69 @@
{
"name" : "pwuniapp",
"appid" : "__UNI__02D4155",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios" : {},
/* SDK配置 */
"sdkConfigs" : {}
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"vueVersion" : "3"
}

106
pwuniapp/pages.json 普通文件
查看文件

@@ -0,0 +1,106 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "电竞陪玩大厅",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/activity/list",
"style": {
"navigationBarTitleText": "组队活动大厅",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/activity/detail",
"style": {
"navigationBarTitleText": "活动详情",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/activity/my-ticket",
"style": {
"navigationBarTitleText": "我的活动门票",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/expert/detail",
"style": {
"navigationBarTitleText": "陪玩大神主页",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/order/create",
"style": {
"navigationBarTitleText": "确认预约订单",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/order/list",
"style": {
"navigationBarTitleText": "我的订单",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "个人中心",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "电竞陪玩",
"navigationBarBackgroundColor": "#0A0F1D",
"backgroundColor": "#0A0F1D"
},
"tabBar": {
"color": "#94A3B8",
"selectedColor": "#6366F1",
"backgroundColor": "#151D30",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/activity/list",
"text": "活动项目"
},
{
"pagePath": "pages/order/list",
"text": "订单中心"
},
{
"pagePath": "pages/user/index",
"text": "我的"
}
]
}
}

查看文件

@@ -0,0 +1,474 @@
<template>
<view class="detail-container">
<!-- 头部活动基本信息 -->
<view class="hero-card">
<view class="badge-row">
<text class="game-tag">{{ activity.game }}</text>
<text class="mode-tag" :class="activity.payType === 'AA' ? 'aa-mode' : 'project-mode'">
{{ activity.payType === 'AA' ? 'AA制平摊车队' : '项目制悬赏' }}
</text>
</view>
<view class="act-title">{{ activity.title }}</view>
<!-- 费用看板 -->
<view class="fee-board">
<view class="fee-col">
<text class="col-label">总费用</text>
<text class="col-val">¥{{ activity.totalFee }}</text>
</view>
<view class="fee-divider"></view>
<view class="fee-col">
<text class="col-label">{{ activity.payType === 'AA' ? '人均分摊' : '奖金池' }}</text>
<text class="col-val highlight">
¥{{ activity.payType === 'AA' ? activity.aaFee : activity.totalFee }}
</text>
</view>
<view class="fee-divider"></view>
<view class="fee-col">
<text class="col-label">招募席位</text>
<text class="col-val">{{ activity.currentParticipants }}/{{ activity.maxParticipants }}人</text>
</view>
</view>
</view>
<!-- 房主与开黑信息 -->
<view class="section-card">
<view class="sec-header">
<text class="sec-title">开黑阵地与房主</text>
</view>
<view class="creator-box">
<image class="creator-avatar" :src="activity.creatorAvatar" mode="aspectFill" />
<view class="creator-info">
<view class="creator-name-row">
<text class="creator-name">{{ activity.creatorNickname }}</text>
<text class="host-badge">活动房主</text>
</view>
<text class="creator-sub">前职业选手 · 胜率保障车头</text>
</view>
<button class="btn-chat btn-active" @click="chatWithHost">私聊</button>
</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">开始时间</text>
<text class="info-val">{{ activity.startTime }}</text>
</view>
<view class="info-row">
<text class="info-label">语音频道</text>
<view class="copy-val" @click="copyLocation">
<text class="info-val">{{ activity.location }}</text>
<text class="copy-btn">复制</text>
</view>
</view>
<view class="info-row">
<text class="info-label">核销机制</text>
<text class="info-val green-text">入队出示4位动态门票码核销</text>
</view>
</view>
</view>
<!-- 已报名队友成员列表 -->
<view class="section-card">
<view class="sec-header">
<text class="sec-title">已报名队友 ({{ activity.participants ? activity.participants.length : 0 }})</text>
<text class="sec-sub">满员即锁车</text>
</view>
<view class="member-list" v-if="activity.participants && activity.participants.length > 0">
<view v-for="(p, index) in activity.participants" :key="p.id" class="member-item">
<image class="member-avatar" :src="p.avatar" mode="aspectFill" />
<view class="member-info">
<text class="member-name">{{ p.nickname }}</text>
<text class="member-seat">#{{ index + 1 }}号位</text>
</view>
<view class="checkin-badge" :class="{ checked: p.checkedIn }">
{{ p.checkedIn ? '已核销入队' : '已购票待验' }}
</view>
</view>
</view>
<view v-else class="empty-members">
<text>暂无其他队员,快抢先上车!</text>
</view>
</view>
<!-- 底部固定操作栏 -->
<view class="bottom-bar">
<view class="ticket-link btn-active" @click="goToMyTicket">
<text class="ticket-icon">🎟️</text>
<text class="ticket-text">我的门票</text>
</view>
<button class="btn-join btn-active" @click="handleJoin">
立即上车 (¥{{ activity.payType === 'AA' ? activity.aaFee : 0 }})
</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { ACTIVITIES_LIST } from '@/common/mock.js';
const activity = ref(ACTIVITIES_LIST[0]);
onLoad((query) => {
if (query && query.id) {
const found = ACTIVITIES_LIST.find(a => a.id === query.id);
if (found) {
activity.value = found;
}
}
});
const copyLocation = () => {
uni.setClipboardData({
data: activity.value.location,
success: () => {
uni.showToast({ title: '语音频道已复制', icon: 'success' });
}
});
};
const chatWithHost = () => {
uni.showToast({ title: '已建立与房主的加密通道', icon: 'none' });
};
const goToMyTicket = () => {
uni.navigateTo({
url: `/pages/activity/my-ticket?id=${activity.value.id}`
});
};
const handleJoin = () => {
uni.showModal({
title: '确认报名',
content: `报名该【${activity.value.payType === 'AA' ? 'AA制' : '项目制'}】活动,需支付分摊款 ¥${activity.value.aaFee}。支付成功后自动生成4位入队核销码。`,
confirmText: '微信支付',
confirmColor: '#6366F1',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '报名成功!已生成门票', icon: 'success' });
setTimeout(() => {
goToMyTicket();
}, 1200);
}
}
});
};
</script>
<style lang="scss" scoped>
.detail-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 24rpx 32rpx 160rpx;
}
.hero-card {
background: linear-gradient(135deg, #1E1B4B 0%, #151D30 100%);
border: 1px solid rgba(99, 102, 241, 0.25);
border-radius: 28rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.badge-row {
display: flex;
gap: 12rpx;
margin-bottom: 16rpx;
.game-tag {
background: rgba(255, 255, 255, 0.1);
color: #F8FAFC;
font-size: 22rpx;
padding: 4rpx 14rpx;
border-radius: 8rpx;
}
.mode-tag {
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-weight: 600;
&.aa-mode {
background: rgba(16, 185, 129, 0.2);
color: #10B981;
border: 1px solid rgba(16, 185, 129, 0.4);
}
&.project-mode {
background: rgba(245, 158, 11, 0.2);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.4);
}
}
}
.act-title {
font-size: 34rpx;
font-weight: 700;
color: #FFFFFF;
line-height: 1.4;
margin-bottom: 28rpx;
}
.fee-board {
display: flex;
justify-content: space-around;
align-items: center;
background: rgba(10, 15, 29, 0.6);
border-radius: 18rpx;
padding: 20rpx 0;
.fee-col {
display: flex;
flex-direction: column;
align-items: center;
.col-label {
font-size: 22rpx;
color: #94A3B8;
margin-bottom: 6rpx;
}
.col-val {
font-size: 30rpx;
font-weight: 700;
color: #F8FAFC;
&.highlight {
color: #10B981;
font-size: 34rpx;
}
}
}
.fee-divider {
width: 1px;
height: 40rpx;
background: rgba(255, 255, 255, 0.1);
}
}
}
.section-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.sec-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.sec-title {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
}
.sec-sub {
font-size: 22rpx;
color: #64748B;
}
}
.creator-box {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.03);
border-radius: 16rpx;
padding: 16rpx;
margin-bottom: 24rpx;
.creator-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 16rpx;
}
.creator-info {
flex: 1;
.creator-name-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 6rpx;
.creator-name {
font-size: 26rpx;
font-weight: 600;
color: #F8FAFC;
}
.host-badge {
background: rgba(99, 102, 241, 0.2);
color: #818CF8;
font-size: 18rpx;
padding: 2rpx 10rpx;
border-radius: 6rpx;
}
}
.creator-sub {
font-size: 20rpx;
color: #94A3B8;
}
}
.btn-chat {
background: rgba(99, 102, 241, 0.15);
border: 1px solid rgba(99, 102, 241, 0.3);
color: #818CF8;
font-size: 22rpx;
padding: 0 24rpx;
height: 52rpx;
line-height: 50rpx;
border-radius: 26rpx;
margin: 0;
}
}
.info-list {
display: flex;
flex-direction: column;
gap: 16rpx;
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
.info-label {
color: #64748B;
}
.info-val {
color: #E2E8F0;
font-weight: 500;
&.green-text {
color: #10B981;
}
}
.copy-val {
display: flex;
align-items: center;
gap: 12rpx;
.copy-btn {
color: #6366F1;
font-size: 22rpx;
text-decoration: underline;
}
}
}
}
.member-list {
display: flex;
flex-direction: column;
gap: 16rpx;
.member-item {
display: flex;
align-items: center;
padding: 12rpx 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
&:last-child {
border-bottom: none;
}
.member-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
margin-right: 16rpx;
}
.member-info {
flex: 1;
.member-name {
font-size: 24rpx;
font-weight: 600;
color: #F8FAFC;
display: block;
}
.member-seat {
font-size: 20rpx;
color: #64748B;
}
}
.checkin-badge {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 12rpx;
background: rgba(100, 116, 139, 0.2);
color: #94A3B8;
&.checked {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
}
}
}
.empty-members {
text-align: center;
padding: 30rpx 0;
color: #64748B;
font-size: 24rpx;
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: rgba(21, 29, 48, 0.95);
backdrop-filter: blur(12px);
border-top: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
padding: 0 32rpx;
gap: 24rpx;
z-index: 100;
.ticket-link {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 120rpx;
.ticket-icon {
font-size: 34rpx;
}
.ticket-text {
font-size: 20rpx;
color: #94A3B8;
}
}
.btn-join {
flex: 1;
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFFFFF;
font-size: 28rpx;
font-weight: 700;
border-radius: 44rpx;
height: 80rpx;
line-height: 80rpx;
box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.35);
margin: 0;
}
}
</style>

查看文件

@@ -0,0 +1,406 @@
<template>
<view class="activity-container">
<!-- 顶部模式切换选项卡 -->
<view class="tab-header">
<view
class="tab-item btn-active"
:class="{ active: currentTab === 'ALL' }"
@click="switchTab('ALL')"
>
<text class="tab-text">全部活动</text>
<view class="tab-indicator" v-if="currentTab === 'ALL'"></view>
</view>
<view
class="tab-item btn-active"
:class="{ active: currentTab === 'AA' }"
@click="switchTab('AA')"
>
<text class="tab-text">⚡ AA制车队</text>
<view class="tab-indicator" v-if="currentTab === 'AA'"></view>
</view>
<view
class="tab-item btn-active"
:class="{ active: currentTab === 'PROJECT' }"
@click="switchTab('PROJECT')"
>
<text class="tab-text">🏆 项目制赛事</text>
<view class="tab-indicator" v-if="currentTab === 'PROJECT'"></view>
</view>
</view>
<!-- 顶部状态小提示 -->
<view class="tips-bar">
<text class="tips-icon">💡</text>
<text class="tips-text">AA制活动人均AA平摊,项目制为房主全额悬赏或奖池分配</text>
</view>
<!-- 活动列表 -->
<view class="activity-list">
<view
v-for="act in filteredActivities"
:key="act.id"
class="activity-card card-active"
@click="goToDetail(act.id)"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="game-tag">{{ act.game }}</view>
<view class="mode-tag" :class="act.payType === 'AA' ? 'aa-mode' : 'project-mode'">
{{ act.payType === 'AA' ? 'AA制平摊' : '项目制悬赏' }}
</view>
<view class="status-pill" :class="act.status">
{{ act.status === 'RECRUITING' ? '招募中' : '进行中' }}
</view>
</view>
<!-- 卡片标题 -->
<view class="activity-title">{{ act.title }}</view>
<!-- 详细信息条 -->
<view class="meta-row">
<view class="meta-item">
<text class="meta-icon">⏰</text>
<text class="meta-val">{{ act.startTime }}</text>
</view>
<view class="meta-item">
<text class="meta-icon">🎙️</text>
<text class="meta-val">{{ act.location }}</text>
</view>
</view>
<!-- 进度与头像 -->
<view class="progress-section">
<view class="progress-info">
<text class="progress-label">组队进度</text>
<text class="progress-count">{{ act.currentParticipants }} / {{ act.maxParticipants }}人</text>
</view>
<view class="progress-track">
<view
class="progress-fill"
:style="{ width: (act.currentParticipants / act.maxParticipants * 100) + '%' }"
></view>
</view>
</view>
<!-- 底部房主与价格 -->
<view class="card-footer">
<view class="creator-info">
<image class="creator-avatar" :src="act.creatorAvatar" mode="aspectFill" />
<text class="creator-name">房主:{{ act.creatorNickname }}</text>
</view>
<view class="fee-info">
<template v-if="act.payType === 'AA'">
<text class="fee-label">人均预估</text>
<text class="fee-amount">¥{{ act.aaFee }}</text>
</template>
<template v-else>
<text class="fee-label">奖金池</text>
<text class="fee-amount project-gold">¥{{ act.totalFee }}</text>
</template>
</view>
</view>
</view>
</view>
<!-- 悬浮发起活动按钮 -->
<view class="fab-btn btn-active" @click="createActivity">
<text class="fab-icon">+</text>
<text class="fab-text">发起组队</text>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { ACTIVITIES_LIST } from '@/common/mock.js';
const activities = ref(ACTIVITIES_LIST);
const currentTab = ref('ALL');
const filteredActivities = computed(() => {
if (currentTab.value === 'ALL') return activities.value;
return activities.value.filter(a => a.payType === currentTab.value);
});
const switchTab = (tab) => {
currentTab.value = tab;
};
const goToDetail = (id) => {
uni.navigateTo({
url: `/pages/activity/detail?id=${id}`
});
};
const createActivity = () => {
uni.showToast({
title: '发起组队模块已就绪(支持AA制与项目制)',
icon: 'none',
duration: 2500
});
};
</script>
<style lang="scss" scoped>
.activity-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 20rpx 32rpx 140rpx;
}
.tab-header {
display: flex;
background-color: #151D30;
border-radius: 20rpx;
padding: 8rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.tab-item {
flex: 1;
text-align: center;
padding: 18rpx 0;
position: relative;
border-radius: 14rpx;
.tab-text {
font-size: 26rpx;
color: #94A3B8;
font-weight: 500;
}
&.active {
background: rgba(99, 102, 241, 0.18);
.tab-text {
color: #FFFFFF;
font-weight: 700;
}
.tab-indicator {
position: absolute;
bottom: 4rpx;
left: 50%;
transform: translateX(-50%);
width: 32rpx;
height: 6rpx;
background: #6366F1;
border-radius: 3rpx;
box-shadow: 0 0 10rpx #6366F1;
}
}
}
}
.tips-bar {
display: flex;
align-items: center;
background: rgba(99, 102, 241, 0.08);
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 12rpx;
padding: 12rpx 20rpx;
margin-bottom: 28rpx;
.tips-icon {
font-size: 24rpx;
margin-right: 12rpx;
}
.tips-text {
font-size: 22rpx;
color: #818CF8;
line-height: 1.4;
}
}
.activity-list {
display: flex;
flex-direction: column;
gap: 24rpx;
.activity-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
.card-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 16rpx;
.game-tag {
background: rgba(255, 255, 255, 0.08);
color: #E2E8F0;
font-size: 22rpx;
padding: 4rpx 14rpx;
border-radius: 8rpx;
}
.mode-tag {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 8rpx;
font-weight: 600;
&.aa-mode {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
&.project-mode {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.3);
}
}
.status-pill {
margin-left: auto;
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 12rpx;
&.RECRUITING {
background: rgba(99, 102, 241, 0.2);
color: #818CF8;
}
&.RUNNING {
background: rgba(16, 185, 129, 0.2);
color: #10B981;
}
}
}
.activity-title {
font-size: 30rpx;
font-weight: 700;
color: #F8FAFC;
line-height: 1.4;
margin-bottom: 16rpx;
}
.meta-row {
display: flex;
gap: 28rpx;
margin-bottom: 20rpx;
.meta-item {
display: flex;
align-items: center;
font-size: 22rpx;
color: #94A3B8;
.meta-icon {
font-size: 22rpx;
margin-right: 8rpx;
}
}
}
.progress-section {
margin-bottom: 24rpx;
.progress-info {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
font-size: 22rpx;
.progress-label {
color: #64748B;
}
.progress-count {
color: #10B981;
font-weight: 600;
}
}
.progress-track {
height: 10rpx;
background: rgba(255, 255, 255, 0.08);
border-radius: 5rpx;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #10B981, #059669);
border-radius: 5rpx;
}
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding-top: 18rpx;
.creator-info {
display: flex;
align-items: center;
.creator-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
margin-right: 12rpx;
}
.creator-name {
font-size: 22rpx;
color: #94A3B8;
}
}
.fee-info {
display: flex;
align-items: baseline;
gap: 8rpx;
.fee-label {
font-size: 20rpx;
color: #64748B;
}
.fee-amount {
font-size: 32rpx;
font-weight: 700;
color: #10B981;
&.project-gold {
color: #F59E0B;
}
}
}
}
}
}
.fab-btn {
position: fixed;
right: 36rpx;
bottom: 160rpx;
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
border-radius: 44rpx;
padding: 18rpx 32rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 28rpx rgba(99, 102, 241, 0.5);
border: 1px solid rgba(255, 255, 255, 0.2);
z-index: 100;
.fab-icon {
font-size: 32rpx;
color: #FFF;
font-weight: 700;
margin-right: 8rpx;
}
.fab-text {
font-size: 26rpx;
color: #FFF;
font-weight: 600;
}
}
</style>

查看文件

@@ -0,0 +1,359 @@
<template>
<view class="ticket-container">
<view class="ticket-card">
<!-- 票据头部 -->
<view class="ticket-header">
<view class="game-badge">{{ ticketData.game }}</view>
<view class="stamp-badge" :class="{ verified: isCheckedIn }">
{{ isCheckedIn ? '已核销入队' : '待核销' }}
</view>
</view>
<view class="ticket-title">{{ ticketData.title }}</view>
<view class="ticket-meta">
<view class="meta-row">
<text class="meta-k">开黑时间</text>
<text class="meta-v">{{ ticketData.startTime }}</text>
</view>
<view class="meta-row">
<text class="meta-k">语音频道</text>
<text class="meta-v">{{ ticketData.location }}</text>
</view>
<view class="meta-row">
<text class="meta-k">房主昵称</text>
<text class="meta-v">{{ ticketData.creatorNickname }}</text>
</view>
</view>
<!-- 撕票齿轮分割虚线与两侧凹槽 -->
<view class="ticket-punch-wrap">
<view class="punch punch-left"></view>
<view class="dash-line"></view>
<view class="punch punch-right"></view>
</view>
<!-- 核心亮点:4位数字核销码专属卡槽 -->
<view class="code-section">
<view class="code-tips">进房向房主出示 4 位入队核销码</view>
<view class="code-slots">
<view v-for="(char, idx) in verifyCodeDigits" :key="idx" class="digit-box">
<text class="digit-text">{{ char }}</text>
</view>
</view>
</view>
<!-- 条形码与二维码模拟区域 -->
<view class="qrcode-section">
<view class="barcode-graphic">
<view class="bar" v-for="i in 36" :key="i" :style="{ width: (i % 3 === 0 ? '6rpx' : '2rpx'), marginRight: (i % 2 === 0 ? '4rpx' : '2rpx') }"></view>
</view>
<text class="barcode-num">TICKET-2026-9025</text>
</view>
<!-- 动态防伪时间与安全保障 -->
<view class="security-footer">
<view class="security-time">
<text class="dot-green"></text>
<text class="time-text">动态防伪时间:{{ currentTime }}</text>
</view>
<view class="anti-counterfeit-bar"></view>
</view>
</view>
<!-- 演示操作按钮 -->
<view class="action-wrap">
<button class="btn-verify-demo btn-active" @click="toggleCheckIn">
{{ isCheckedIn ? '重置为待核销状态' : '模拟房主核销此门票' }}
</button>
<button class="btn-back btn-active" @click="goBack">返回活动详情</button>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { ACTIVITIES_LIST } from '@/common/mock.js';
const ticketData = ref(ACTIVITIES_LIST[0]);
const verifyCode = ref('8392');
const isCheckedIn = ref(false);
const currentTime = ref('');
const verifyCodeDigits = computed(() => {
return verifyCode.value.split('');
});
let timer = null;
const updateTime = () => {
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
const y = now.getFullYear();
const m = pad(now.getMonth() + 1);
const d = pad(now.getDate());
const hh = pad(now.getHours());
const mm = pad(now.getMinutes());
const ss = pad(now.getSeconds());
currentTime.value = `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
};
onMounted(() => {
updateTime();
timer = setInterval(updateTime, 1000);
});
onUnmounted(() => {
if (timer) clearInterval(timer);
});
const toggleCheckIn = () => {
isCheckedIn.value = !isCheckedIn.value;
uni.showToast({
title: isCheckedIn.value ? '房主已成功核销该4位门票码!' : '状态已重置为待核销',
icon: 'none',
duration: 2000
});
};
const goBack = () => {
uni.navigateBack();
};
</script>
<style lang="scss" scoped>
.ticket-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 40rpx 36rpx;
}
.ticket-card {
background: #151D30;
border-radius: 32rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.5);
overflow: hidden;
position: relative;
.ticket-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 32rpx 16rpx;
.game-badge {
background: rgba(99, 102, 241, 0.2);
color: #818CF8;
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 8rpx;
font-weight: 600;
}
.stamp-badge {
font-size: 22rpx;
padding: 6rpx 20rpx;
border-radius: 24rpx;
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.3);
font-weight: 700;
&.verified {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
border-color: rgba(16, 185, 129, 0.4);
transform: rotate(-4deg);
}
}
}
.ticket-title {
font-size: 32rpx;
font-weight: 700;
color: #FFFFFF;
padding: 0 32rpx 24rpx;
line-height: 1.4;
}
.ticket-meta {
padding: 0 32rpx 32rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
.meta-row {
display: flex;
justify-content: space-between;
font-size: 24rpx;
.meta-k {
color: #64748B;
}
.meta-v {
color: #CBD5E1;
font-weight: 500;
}
}
}
.ticket-punch-wrap {
position: relative;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.punch {
position: absolute;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background-color: #0A0F1D;
&.punch-left {
left: -24rpx;
}
&.punch-right {
right: -24rpx;
}
}
.dash-line {
width: 84%;
height: 2rpx;
border-bottom: 2rpx dashed rgba(255, 255, 255, 0.15);
}
}
.code-section {
padding: 36rpx 32rpx 24rpx;
text-align: center;
.code-tips {
font-size: 22rpx;
color: #94A3B8;
margin-bottom: 24rpx;
}
.code-slots {
display: flex;
justify-content: center;
gap: 20rpx;
.digit-box {
width: 110rpx;
height: 130rpx;
background: rgba(10, 15, 29, 0.8);
border: 2rpx solid #10B981;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20rpx rgba(16, 185, 129, 0.3);
.digit-text {
font-size: 64rpx;
font-weight: 800;
color: #10B981;
font-family: 'Courier New', Courier, monospace;
text-shadow: 0 0 12rpx rgba(16, 185, 129, 0.6);
}
}
}
}
.qrcode-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 32rpx 28rpx;
.barcode-graphic {
display: flex;
height: 60rpx;
align-items: center;
opacity: 0.8;
.bar {
height: 100%;
background-color: #F8FAFC;
}
}
.barcode-num {
font-size: 20rpx;
color: #64748B;
letter-spacing: 4rpx;
margin-top: 10rpx;
}
}
.security-footer {
background: rgba(10, 15, 29, 0.5);
padding: 20rpx 32rpx;
border-top: 1px solid rgba(255, 255, 255, 0.04);
.security-time {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
.dot-green {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #10B981;
}
.time-text {
font-size: 20rpx;
color: #94A3B8;
font-family: monospace;
}
}
.anti-counterfeit-bar {
height: 4rpx;
margin-top: 12rpx;
background: linear-gradient(90deg, #6366F1, #10B981, #F59E0B, #6366F1);
background-size: 200% 100%;
border-radius: 2rpx;
animation: gradientMove 3s infinite linear;
}
}
}
.action-wrap {
margin-top: 48rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.btn-verify-demo {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFFFFF;
font-size: 28rpx;
font-weight: 600;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
}
.btn-back {
background: #151D30;
color: #94A3B8;
font-size: 26rpx;
height: 84rpx;
line-height: 84rpx;
border-radius: 44rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
}
}
@keyframes gradientMove {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
</style>

查看文件

@@ -0,0 +1,618 @@
<template>
<view class="expert-detail-container">
<!-- 顶部封面与基本信息 -->
<view class="hero-header">
<image class="cover-bg" :src="expert.coverImage || expert.avatar" mode="aspectFill" />
<view class="cover-mask"></view>
<view class="profile-card">
<view class="avatar-wrap">
<image class="avatar-img" :src="expert.avatar" mode="aspectFill" />
<view class="status-badge" :class="{ online: expert.isOnline }">
<view class="pulse-dot" v-if="expert.isOnline"></view>
<text>{{ expert.isOnline ? '在线' : '离线' }}</text>
</view>
</view>
<view class="info-wrap">
<view class="name-row">
<text class="nickname">{{ expert.nickname }}</text>
<text class="level-tag">{{ expert.levelTag }}</text>
</view>
<view class="rank-row">
<text class="game-tag">{{ expert.game }}</text>
<text class="rank-tag">{{ expert.rankTag }}</text>
</view>
<view class="data-row">
<text class="rating-val">⭐ {{ expert.rating }}</text>
<text class="order-val">已陪玩 {{ expert.orderCount }} 次</text>
</view>
</view>
</view>
</view>
<!-- 专属打招呼试听语音 -->
<view class="voice-card btn-active" @click="toggleVoice">
<view class="voice-icon-wrap">
<text class="play-icon">{{ isPlayingVoice ? '⏸' : '▶' }}</text>
</view>
<view class="voice-info">
<text class="voice-title">{{ isPlayingVoice ? '正在连麦试听语音...' : '点击试听打招呼语音' }}</text>
<view class="voice-wave" v-if="isPlayingVoice">
<view class="bar b1"></view>
<view class="bar b2"></view>
<view class="bar b3"></view>
<view class="bar b4"></view>
</view>
<text class="voice-dur" v-else>{{ expert.voiceDuration }}s 真实原声</text>
</view>
<text class="listen-tag">试听</text>
</view>
<!-- 个人简介 -->
<view class="section-card">
<view class="sec-title">个人介绍</view>
<text class="bio-text">{{ expert.bio }}</text>
</view>
<!-- 服务项目选择 -->
<view class="section-card">
<view class="sec-title">服务项目</view>
<view class="service-list">
<view
v-for="srv in expert.services"
:key="srv.id"
class="service-item btn-active"
:class="{ selected: selectedServiceId === srv.id }"
@click="selectService(srv)"
>
<view class="srv-left">
<view class="srv-title">{{ srv.title }}</view>
<view class="srv-desc">{{ srv.desc }}</view>
</view>
<view class="srv-right">
<text class="price-sym">¥</text>
<text class="price-num">{{ srv.price }}</text>
<text class="price-unit">/{{ srv.unit }}</text>
</view>
</view>
</view>
</view>
<!-- 战绩与生活照片 -->
<view class="section-card" v-if="expert.photos && expert.photos.length > 0">
<view class="sec-title">战绩相册</view>
<scroll-view scroll-x class="gallery-scroll" :show-scrollbar="false">
<view class="gallery-list">
<image
v-for="(photo, index) in expert.photos"
:key="index"
class="gallery-item"
:src="photo"
mode="aspectFill"
@click="previewImage(index)"
/>
</view>
</scroll-view>
</view>
<!-- 真实买家评价 -->
<view class="section-card">
<view class="sec-header">
<text class="sec-title">服务好评 ({{ expert.reviews ? expert.reviews.length : 0 }})</text>
<text class="rate-percent">好评率 99.8%</text>
</view>
<view class="review-list" v-if="expert.reviews && expert.reviews.length > 0">
<view v-for="rev in expert.reviews" :key="rev.id" class="review-item">
<image class="rev-avatar" :src="rev.userAvatar" mode="aspectFill" />
<view class="rev-body">
<view class="rev-top">
<text class="rev-name">{{ rev.userNickname }}</text>
<text class="rev-time">{{ rev.time }}</text>
</view>
<view class="tag-row">
<text class="rev-tag" v-for="(t, i) in rev.tags" :key="i">{{ t }}</text>
</view>
<text class="rev-content">{{ rev.content }}</text>
</view>
</view>
</view>
<view v-else class="empty-reviews">
<text>近期新增服务,暂无评价记录</text>
</view>
</view>
<!-- 底部固定栏 -->
<view class="bottom-bar">
<view class="icon-action btn-active" @click="chatOnline">
<text class="action-icon">💬</text>
<text class="action-text">私聊</text>
</view>
<view class="price-summary">
<text class="summary-label">当前选定:</text>
<text class="summary-price">¥{{ currentSelectedService ? currentSelectedService.price : expert.price }}</text>
</view>
<button class="btn-book btn-active" @click="goToOrder">立即预约</button>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { EXPERTS_LIST } from '@/common/mock.js';
const expert = ref(EXPERTS_LIST[0]);
const selectedServiceId = ref(EXPERTS_LIST[0].services[0] ? EXPERTS_LIST[0].services[0].id : null);
const isPlayingVoice = ref(false);
onLoad((query) => {
if (query && query.id) {
const found = EXPERTS_LIST.find(e => e.id === query.id);
if (found) {
expert.value = found;
if (found.services && found.services.length > 0) {
selectedServiceId.value = found.services[0].id;
}
}
}
});
const currentSelectedService = computed(() => {
if (!expert.value.services) return null;
return expert.value.services.find(s => s.id === selectedServiceId.value);
});
const selectService = (srv) => {
selectedServiceId.value = srv.id;
};
const toggleVoice = () => {
isPlayingVoice.value = !isPlayingVoice.value;
if (isPlayingVoice.value) {
uni.showToast({ title: '正在播放大神语音打招呼', icon: 'none' });
setTimeout(() => {
isPlayingVoice.value = false;
}, 4000);
}
};
const chatOnline = () => {
uni.showToast({ title: '已开启即时连麦通道', icon: 'none' });
};
const previewImage = (index) => {
uni.previewImage({
current: index,
urls: expert.value.photos
});
};
const goToOrder = () => {
const srv = currentSelectedService.value || expert.value.services[0];
uni.navigateTo({
url: `/pages/order/create?expertId=${expert.value.id}&serviceId=${srv.id}`
});
};
</script>
<style lang="scss" scoped>
.expert-detail-container {
min-height: 100vh;
background-color: #0A0F1D;
padding-bottom: 140rpx;
}
.hero-header {
position: relative;
height: 440rpx;
overflow: hidden;
.cover-bg {
width: 100%;
height: 100%;
filter: blur(8px);
transform: scale(1.1);
}
.cover-mask {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(10, 15, 29, 0.4) 0%, #0A0F1D 100%);
}
.profile-card {
position: absolute;
bottom: 20rpx;
left: 32rpx;
right: 32rpx;
display: flex;
align-items: flex-end;
gap: 24rpx;
.avatar-wrap {
position: relative;
width: 140rpx;
height: 140rpx;
flex-shrink: 0;
.avatar-img {
width: 100%;
height: 100%;
border-radius: 70rpx;
border: 4rpx solid #6366F1;
}
.status-badge {
position: absolute;
bottom: 0;
right: 0;
background: #64748B;
color: #FFF;
font-size: 18rpx;
padding: 2rpx 10rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
gap: 4rpx;
&.online {
background: #10B981;
}
.pulse-dot {
width: 8rpx;
height: 8rpx;
background: #FFF;
border-radius: 50%;
}
}
}
.info-wrap {
flex: 1;
.name-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 8rpx;
.nickname {
font-size: 34rpx;
font-weight: 700;
color: #FFF;
}
.level-tag {
background: linear-gradient(135deg, #F59E0B, #D97706);
color: #FFF;
font-size: 18rpx;
padding: 2rpx 10rpx;
border-radius: 6rpx;
font-weight: 600;
}
}
.rank-row {
display: flex;
gap: 10rpx;
margin-bottom: 8rpx;
.game-tag {
background: rgba(255, 255, 255, 0.15);
color: #E2E8F0;
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 6rpx;
}
.rank-tag {
background: rgba(99, 102, 241, 0.2);
color: #818CF8;
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 6rpx;
}
}
.data-row {
display: flex;
gap: 20rpx;
font-size: 22rpx;
color: #94A3B8;
.rating-val {
color: #F59E0B;
font-weight: 600;
}
}
}
}
}
.voice-card {
margin: 16rpx 32rpx 24rpx;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.1) 100%);
border: 1px solid rgba(99, 102, 241, 0.35);
border-radius: 20rpx;
padding: 20rpx 24rpx;
display: flex;
align-items: center;
.voice-icon-wrap {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background: #6366F1;
display: flex;
align-items: center;
justify-content: center;
margin-right: 18rpx;
.play-icon {
color: #FFF;
font-size: 24rpx;
}
}
.voice-info {
flex: 1;
.voice-title {
font-size: 24rpx;
color: #F8FAFC;
font-weight: 600;
display: block;
margin-bottom: 4rpx;
}
.voice-dur {
font-size: 20rpx;
color: #818CF8;
}
.voice-wave {
display: flex;
align-items: flex-end;
gap: 6rpx;
height: 24rpx;
.bar {
width: 6rpx;
background-color: #818CF8;
border-radius: 3rpx;
&.b1 { height: 16rpx; }
&.b2 { height: 24rpx; }
&.b3 { height: 12rpx; }
&.b4 { height: 20rpx; }
}
}
}
.listen-tag {
font-size: 20rpx;
color: #818CF8;
border: 1px solid rgba(129, 140, 248, 0.4);
padding: 4rpx 14rpx;
border-radius: 20rpx;
}
}
.section-card {
margin: 0 32rpx 24rpx;
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.sec-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.rate-percent {
font-size: 22rpx;
color: #10B981;
}
}
.sec-title {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
margin-bottom: 18rpx;
}
.bio-text {
font-size: 24rpx;
color: #94A3B8;
line-height: 1.6;
}
.service-list {
display: flex;
flex-direction: column;
gap: 16rpx;
.service-item {
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16rpx;
padding: 20rpx 24rpx;
&.selected {
background: rgba(99, 102, 241, 0.15);
border-color: #6366F1;
}
.srv-left {
.srv-title {
font-size: 26rpx;
color: #F8FAFC;
font-weight: 600;
margin-bottom: 4rpx;
}
.srv-desc {
font-size: 20rpx;
color: #64748B;
}
}
.srv-right {
color: #10B981;
.price-sym { font-size: 22rpx; }
.price-num { font-size: 34rpx; font-weight: 700; }
.price-unit { font-size: 20rpx; color: #64748B; }
}
}
}
.gallery-scroll {
white-space: nowrap;
.gallery-list {
display: flex;
gap: 16rpx;
}
.gallery-item {
width: 220rpx;
height: 160rpx;
border-radius: 16rpx;
border: 1px solid rgba(255, 255, 255, 0.1);
}
}
.review-list {
display: flex;
flex-direction: column;
gap: 20rpx;
.review-item {
display: flex;
gap: 16rpx;
.rev-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
}
.rev-body {
flex: 1;
.rev-top {
display: flex;
justify-content: space-between;
margin-bottom: 6rpx;
.rev-name {
font-size: 24rpx;
font-weight: 600;
color: #E2E8F0;
}
.rev-time {
font-size: 20rpx;
color: #64748B;
}
}
.tag-row {
display: flex;
gap: 10rpx;
margin-bottom: 8rpx;
.rev-tag {
font-size: 18rpx;
color: #818CF8;
background: rgba(99, 102, 241, 0.15);
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
}
.rev-content {
font-size: 22rpx;
color: #94A3B8;
line-height: 1.5;
}
}
}
}
.empty-reviews {
text-align: center;
color: #64748B;
font-size: 22rpx;
padding: 20rpx 0;
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: rgba(21, 29, 48, 0.95);
backdrop-filter: blur(12px);
border-top: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
padding: 0 32rpx;
gap: 20rpx;
z-index: 100;
.icon-action {
display: flex;
flex-direction: column;
align-items: center;
width: 80rpx;
.action-icon {
font-size: 32rpx;
}
.action-text {
font-size: 18rpx;
color: #94A3B8;
}
}
.price-summary {
flex: 1;
display: flex;
align-items: baseline;
.summary-label {
font-size: 22rpx;
color: #64748B;
}
.summary-price {
font-size: 36rpx;
font-weight: 700;
color: #10B981;
margin-left: 6rpx;
}
}
.btn-book {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFF;
font-size: 28rpx;
font-weight: 700;
border-radius: 44rpx;
height: 80rpx;
line-height: 80rpx;
padding: 0 48rpx;
box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.4);
margin: 0;
}
}
</style>

查看文件

@@ -0,0 +1,560 @@
<template>
<view class="index-container">
<!-- 顶部搜索栏 -->
<view class="header-search">
<view class="search-box">
<text class="search-icon">🔍</text>
<input class="search-input" placeholder="搜索陪玩大神、游戏昵称或项目" placeholder-class="placeholder-text" />
</view>
</view>
<!-- 游戏分类滑动栏 -->
<scroll-view scroll-x class="category-scroll" :show-scrollbar="false">
<view class="category-list">
<view
v-for="item in games"
:key="item.id"
class="category-item btn-active"
:class="{ active: currentCategory === item.id }"
@click="selectCategory(item.id)"
>
<text class="cat-icon">{{ item.icon }}</text>
<text class="cat-name">{{ item.name }}</text>
<view v-if="item.hot" class="cat-badge">HOT</view>
</view>
</view>
</scroll-view>
<!-- 精彩活动横幅轮播 -->
<swiper class="banner-swiper" circular autoplay interval="4000" indicator-dots indicator-color="rgba(255,255,255,0.2)" indicator-active-color="#6366F1">
<swiper-item v-for="banner in banners" :key="banner.id" class="banner-item" @click="goToActivity">
<view class="banner-card" :style="{ background: banner.bg }">
<view class="banner-tag">{{ banner.tag }}</view>
<view class="banner-title">{{ banner.title }}</view>
<view class="banner-sub">点击立即参与 ⚡</view>
</view>
</swiper-item>
</swiper>
<!-- 快捷功能金刚区 -->
<view class="quick-grid">
<view class="quick-item btn-active" @click="navTo('/pages/activity/list')">
<view class="quick-icon-bg bg-purple">🔥</view>
<text class="quick-title">组队开黑</text>
<text class="quick-sub">AA制五排</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/activity/my-ticket')">
<view class="quick-icon-bg bg-green">🎟️</view>
<text class="quick-title">我的门票</text>
<text class="quick-sub">4位核销码</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/order/list')">
<view class="quick-icon-bg bg-blue">⚡</view>
<text class="quick-title">服务订单</text>
<text class="quick-sub">状态实时看</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/user/index')">
<view class="quick-icon-bg bg-orange">👑</view>
<text class="quick-title">大神中心</text>
<text class="quick-sub">双身份切换</text>
</view>
</view>
<!-- 陪玩大神瀑布流 -->
<view class="section-title-wrap">
<view class="section-title">
<view class="title-line"></view>
<text>精选在线大神</text>
</view>
<text class="section-more">实时在线</text>
</view>
<view class="expert-grid">
<view
v-for="expert in filteredExperts"
:key="expert.id"
class="expert-card card-active"
@click="goToExpert(expert.id)"
>
<!-- 封面图 -->
<view class="card-cover-wrap">
<image class="card-cover" :src="expert.avatar" mode="aspectFill" />
<view class="status-tag" :class="expert.isOnline ? 'online' : 'offline'">
<view class="pulse-dot" v-if="expert.isOnline"></view>
<text>{{ expert.isOnline ? '在线' : '离线' }}</text>
</view>
<view class="game-badge">{{ expert.game }}</view>
</view>
<!-- 信息区 -->
<view class="card-body">
<view class="row-nickname">
<text class="nickname">{{ expert.nickname }}</text>
<text class="gender-tag" :class="expert.gender">{{ expert.gender === 'female' ? '♀' : '♂' }}</text>
</view>
<view class="rank-badge">{{ expert.rankTag }}</view>
<!-- 语音试听条 -->
<view class="voice-bar btn-active" @click.stop="toggleVoice(expert.id)">
<text class="voice-icon">{{ playingId === expert.id ? '🔊' : '▶' }}</text>
<text class="voice-text">{{ playingId === expert.id ? '试听中...' : expert.voiceDuration + 's 语音' }}</text>
<view class="wave-lines" v-if="playingId === expert.id">
<view class="wave-item w1"></view>
<view class="wave-item w2"></view>
<view class="wave-item w3"></view>
</view>
</view>
<view class="card-footer">
<view class="price-wrap">
<text class="price-symbol">¥</text>
<text class="price-val">{{ expert.price }}</text>
<text class="price-unit">/时</text>
</view>
<text class="order-count">{{ expert.orderCount }}单</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { GAMES_LIST, BANNERS, EXPERTS_LIST } from '@/common/mock.js';
const games = ref(GAMES_LIST);
const banners = ref(BANNERS);
const experts = ref(EXPERTS_LIST);
const currentCategory = ref('all');
const playingId = ref(null);
const filteredExperts = computed(() => {
if (currentCategory.value === 'all') return experts.value;
const gameMap = {
wzry: '王者荣耀',
hpjy: '和平精英',
lol: '英雄联盟',
val: '无畏契约'
};
const target = gameMap[currentCategory.value];
return target ? experts.value.filter(e => e.game === target) : experts.value;
});
const selectCategory = (id) => {
currentCategory.value = id;
};
const toggleVoice = (id) => {
if (playingId.value === id) {
playingId.value = null;
} else {
playingId.value = id;
uni.showToast({ title: '正在试听打招呼语音', icon: 'none' });
setTimeout(() => {
if (playingId.value === id) playingId.value = null;
}, 4000);
}
};
const goToExpert = (id) => {
uni.navigateTo({
url: `/pages/expert/detail?id=${id}`
});
};
const goToActivity = () => {
uni.switchTab({
url: '/pages/activity/list'
});
};
const navTo = (url) => {
if (url === '/pages/activity/list' || url === '/pages/order/list' || url === '/pages/user/index') {
uni.switchTab({ url });
} else {
uni.navigateTo({ url });
}
};
</script>
<style lang="scss" scoped>
.index-container {
min-height: 100vh;
background-color: #0A0F1D;
padding-bottom: 140rpx;
}
.header-search {
padding: 20rpx 32rpx;
background-color: #0A0F1D;
position: sticky;
top: 0;
z-index: 50;
.search-box {
display: flex;
align-items: center;
background-color: #151D30;
height: 80rpx;
border-radius: 40rpx;
padding: 0 28rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
.search-icon {
font-size: 28rpx;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 26rpx;
color: #F8FAFC;
}
.placeholder-text {
color: #64748B;
}
}
}
.category-scroll {
white-space: nowrap;
padding: 10rpx 32rpx 20rpx;
.category-list {
display: flex;
gap: 16rpx;
}
.category-item {
display: inline-flex;
align-items: center;
padding: 14rpx 28rpx;
background-color: #151D30;
border-radius: 32rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
position: relative;
.cat-icon {
font-size: 26rpx;
margin-right: 8rpx;
}
.cat-name {
font-size: 24rpx;
color: #94A3B8;
font-weight: 500;
}
.cat-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
background: #EF4444;
color: #FFF;
font-size: 16rpx;
padding: 2rpx 8rpx;
border-radius: 12rpx;
font-weight: 700;
}
&.active {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
border-color: #818CF8;
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
.cat-name {
color: #FFFFFF;
font-weight: 600;
}
}
}
}
.banner-swiper {
height: 200rpx;
margin: 10rpx 32rpx 24rpx;
.banner-card {
height: 100%;
border-radius: 20rpx;
padding: 28rpx 32rpx;
display: flex;
flex-direction: column;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.4);
.banner-tag {
align-self: flex-start;
background: rgba(0, 0, 0, 0.25);
backdrop-filter: blur(8px);
padding: 4rpx 14rpx;
border-radius: 8rpx;
font-size: 20rpx;
color: #FFF;
margin-bottom: 8rpx;
}
.banner-title {
font-size: 30rpx;
font-weight: 700;
color: #FFF;
margin-bottom: 6rpx;
}
.banner-sub {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.85);
}
}
}
.quick-grid {
display: flex;
justify-content: space-between;
margin: 0 32rpx 32rpx;
background-color: #151D30;
border-radius: 24rpx;
padding: 24rpx 16rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.quick-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.quick-icon-bg {
width: 76rpx;
height: 76rpx;
border-radius: 22rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 34rpx;
margin-bottom: 12rpx;
&.bg-purple { background: rgba(139, 92, 246, 0.2); }
&.bg-green { background: rgba(16, 185, 129, 0.2); }
&.bg-blue { background: rgba(99, 102, 241, 0.2); }
&.bg-orange { background: rgba(245, 158, 11, 0.2); }
}
.quick-title {
font-size: 24rpx;
color: #F8FAFC;
font-weight: 600;
margin-bottom: 4rpx;
}
.quick-sub {
font-size: 18rpx;
color: #64748B;
}
}
}
.section-title-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32rpx 20rpx;
.section-title {
display: flex;
align-items: center;
font-size: 30rpx;
font-weight: 700;
color: #F8FAFC;
.title-line {
width: 8rpx;
height: 28rpx;
background: linear-gradient(180deg, #6366F1, #8B5CF6);
border-radius: 4rpx;
margin-right: 14rpx;
}
}
.section-more {
font-size: 22rpx;
color: #10B981;
background: rgba(16, 185, 129, 0.15);
padding: 4rpx 14rpx;
border-radius: 16rpx;
}
}
.expert-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 32rpx;
.expert-card {
background-color: #151D30;
border-radius: 20rpx;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.06);
.card-cover-wrap {
position: relative;
height: 320rpx;
.card-cover {
width: 100%;
height: 100%;
}
.status-tag {
position: absolute;
top: 14rpx;
left: 14rpx;
display: flex;
align-items: center;
gap: 6rpx;
padding: 4rpx 12rpx;
border-radius: 20rpx;
font-size: 18rpx;
font-weight: 600;
&.online {
background: rgba(16, 185, 129, 0.85);
color: #FFF;
}
&.offline {
background: rgba(100, 116, 139, 0.8);
color: #FFF;
}
.pulse-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #FFF;
animation: pulse 1.5s infinite;
}
}
.game-badge {
position: absolute;
bottom: 14rpx;
right: 14rpx;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(4px);
font-size: 18rpx;
color: #E2E8F0;
padding: 4rpx 10rpx;
border-radius: 8rpx;
}
}
.card-body {
padding: 18rpx;
.row-nickname {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
.nickname {
font-size: 26rpx;
font-weight: 600;
color: #F8FAFC;
max-width: 200rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gender-tag {
font-size: 20rpx;
padding: 2rpx 8rpx;
border-radius: 8rpx;
&.female {
background: rgba(236, 72, 153, 0.2);
color: #F472B6;
}
&.male {
background: rgba(59, 130, 246, 0.2);
color: #60A5FA;
}
}
}
.rank-badge {
font-size: 20rpx;
color: #F59E0B;
background: rgba(245, 158, 11, 0.12);
padding: 4rpx 10rpx;
border-radius: 6rpx;
display: inline-block;
margin-bottom: 12rpx;
}
.voice-bar {
background: rgba(99, 102, 241, 0.15);
border: 1px solid rgba(99, 102, 241, 0.3);
height: 48rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
padding: 0 16rpx;
margin-bottom: 16rpx;
.voice-icon {
font-size: 20rpx;
color: #818CF8;
margin-right: 8rpx;
}
.voice-text {
font-size: 20rpx;
color: #818CF8;
font-weight: 600;
}
.wave-lines {
display: flex;
align-items: flex-end;
gap: 4rpx;
margin-left: 8rpx;
.wave-item {
width: 4rpx;
background: #818CF8;
border-radius: 2rpx;
&.w1 { height: 14rpx; animation: bounce 0.6s infinite ease-in-out; }
&.w2 { height: 22rpx; animation: bounce 0.6s infinite ease-in-out 0.2s; }
&.w3 { height: 10rpx; animation: bounce 0.6s infinite ease-in-out 0.4s; }
}
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: baseline;
.price-wrap {
color: #10B981;
.price-symbol { font-size: 22rpx; font-weight: 600; }
.price-val { font-size: 32rpx; font-weight: 700; margin: 0 2rpx; }
.price-unit { font-size: 20rpx; color: #64748B; }
}
.order-count {
font-size: 20rpx;
color: #64748B;
}
}
}
}
}
@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 8rpx rgba(255, 255, 255, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
@keyframes bounce {
0%, 100% { transform: scaleY(0.4); }
50% { transform: scaleY(1); }
}
</style>

查看文件

@@ -0,0 +1,448 @@
<template>
<view class="create-order-container">
<!-- 服务大神与项目信息 -->
<view class="order-card">
<view class="expert-profile">
<image class="expert-avatar" :src="expert.avatar" mode="aspectFill" />
<view class="expert-meta">
<view class="name-row">
<text class="expert-name">{{ expert.nickname }}</text>
<text class="game-tag">{{ expert.game }}</text>
</view>
<text class="service-title">{{ selectedService.title }}</text>
</view>
<view class="price-single">
<text class="price-unit">¥{{ selectedService.price }}/{{ selectedService.unit }}</text>
</view>
</view>
</view>
<!-- 数量/时长步进器 -->
<view class="order-card">
<view class="stepper-row">
<view class="stepper-label">
<text class="label-main">购买时长</text>
<text class="label-sub">以{{ selectedService.unit }}为计费单位</text>
</view>
<view class="stepper-box">
<button class="step-btn btn-active" :disabled="hours <= 1" @click="changeHours(-1)">-</button>
<text class="step-val">{{ hours }} {{ selectedService.unit }}</text>
<button class="step-btn btn-active" :disabled="hours >= 10" @click="changeHours(1)">+</button>
</view>
</view>
</view>
<!-- 开黑时段选择 -->
<view class="order-card">
<view class="section-title">服务时间</view>
<view class="time-tabs">
<view
class="time-tab btn-active"
:class="{ active: timeType === 'NOW' }"
@click="timeType = 'NOW'"
>
<text class="tab-t">⚡ 即刻出发</text>
<text class="tab-d">付款后立即连麦开黑</text>
</view>
<view
class="time-tab btn-active"
:class="{ active: timeType === 'APPOINT' }"
@click="timeType = 'APPOINT'"
>
<text class="tab-t">📅 预约时间</text>
<text class="tab-d">今晚 20:30 开打</text>
</view>
</view>
</view>
<!-- 优惠抵扣与备注 -->
<view class="order-card">
<view class="coupon-row btn-active" @click="selectCoupon">
<text class="cell-label">优惠券</text>
<view class="cell-right">
<text class="coupon-val">-¥{{ discount }} (新人首单立减)</text>
<text class="arrow">></text>
</view>
</view>
<view class="remark-row">
<text class="cell-label">开黑要求/游戏ID</text>
<input class="remark-input" v-model="remark" placeholder="如:QQ区/王者段位/包赢" placeholder-class="ph" />
</view>
</view>
<!-- 费用明细清单 -->
<view class="order-card">
<view class="fee-row">
<text class="fee-k">服务小计</text>
<text class="fee-v">¥{{ rawTotal }}</text>
</view>
<view class="fee-row">
<text class="fee-k">优惠券抵扣</text>
<text class="fee-v green">-¥{{ discount }}</text>
</view>
<view class="fee-row total-row">
<text class="fee-k">实付金额</text>
<text class="fee-v pay-val">¥{{ finalTotal }}</text>
</view>
</view>
<!-- 底部固定结算条 -->
<view class="bottom-bar">
<view class="amount-wrap">
<text class="amount-label">实付:</text>
<text class="amount-num">¥{{ finalTotal }}</text>
</view>
<button class="btn-pay btn-active" @click="handlePay">微信支付</button>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { EXPERTS_LIST } from '@/common/mock.js';
const expert = ref(EXPERTS_LIST[0]);
const selectedService = ref(EXPERTS_LIST[0].services[0]);
const hours = ref(2);
const timeType = ref('NOW');
const discount = ref(10);
const remark = ref('');
onLoad((query) => {
if (query && query.expertId) {
const e = EXPERTS_LIST.find(item => item.id === query.expertId);
if (e) {
expert.value = e;
if (query.serviceId && e.services) {
const s = e.services.find(srv => srv.id === query.serviceId);
if (s) selectedService.value = s;
} else if (e.services && e.services.length > 0) {
selectedService.value = e.services[0];
}
}
}
});
const rawTotal = computed(() => {
return (selectedService.value ? selectedService.value.price : 35) * hours.value;
});
const finalTotal = computed(() => {
const total = rawTotal.value - discount.value;
return total > 0 ? total : 0;
});
const changeHours = (delta) => {
hours.value = Math.max(1, Math.min(10, hours.value + delta));
};
const selectCoupon = () => {
uni.showToast({ title: '已自动匹配最高优惠券 -¥10', icon: 'none' });
};
const handlePay = () => {
uni.showLoading({ title: '正在拉起微信支付...' });
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '支付成功!服务已生成',
icon: 'success',
duration: 1500
});
setTimeout(() => {
uni.switchTab({
url: '/pages/order/list'
});
}, 1200);
}, 1000);
};
</script>
<style lang="scss" scoped>
.create-order-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 24rpx 32rpx 160rpx;
}
.order-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.expert-profile {
display: flex;
align-items: center;
.expert-avatar {
width: 96rpx;
height: 96rpx;
border-radius: 48rpx;
margin-right: 20rpx;
}
.expert-meta {
flex: 1;
.name-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 8rpx;
.expert-name {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
}
.game-tag {
font-size: 20rpx;
background: rgba(99, 102, 241, 0.2);
color: #818CF8;
padding: 2rpx 10rpx;
border-radius: 6rpx;
}
}
.service-title {
font-size: 24rpx;
color: #94A3B8;
}
}
.price-single {
.price-unit {
font-size: 26rpx;
font-weight: 600;
color: #10B981;
}
}
}
.stepper-row {
display: flex;
justify-content: space-between;
align-items: center;
.stepper-label {
display: flex;
flex-direction: column;
.label-main {
font-size: 28rpx;
color: #F8FAFC;
font-weight: 600;
}
.label-sub {
font-size: 20rpx;
color: #64748B;
margin-top: 4rpx;
}
}
.stepper-box {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.04);
border-radius: 16rpx;
padding: 6rpx;
.step-btn {
width: 60rpx;
height: 60rpx;
line-height: 56rpx;
padding: 0;
background: #1E2942;
color: #FFF;
font-size: 32rpx;
border-radius: 12rpx;
border: none;
margin: 0;
}
.step-val {
font-size: 28rpx;
color: #F8FAFC;
font-weight: 600;
margin: 0 24rpx;
}
}
}
.section-title {
font-size: 26rpx;
font-weight: 600;
color: #F8FAFC;
margin-bottom: 18rpx;
}
.time-tabs {
display: flex;
gap: 16rpx;
.time-tab {
flex: 1;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16rpx;
padding: 20rpx;
text-align: center;
&.active {
background: rgba(99, 102, 241, 0.15);
border-color: #6366F1;
.tab-t {
color: #818CF8;
font-weight: 700;
}
}
.tab-t {
font-size: 26rpx;
color: #CBD5E1;
display: block;
margin-bottom: 6rpx;
}
.tab-d {
font-size: 20rpx;
color: #64748B;
}
}
}
.coupon-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20rpx;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
.cell-label {
font-size: 26rpx;
color: #CBD5E1;
}
.cell-right {
display: flex;
align-items: center;
gap: 8rpx;
.coupon-val {
font-size: 24rpx;
color: #10B981;
font-weight: 600;
}
.arrow {
font-size: 24rpx;
color: #64748B;
}
}
}
.remark-row {
display: flex;
align-items: center;
padding-top: 20rpx;
.cell-label {
font-size: 24rpx;
color: #64748B;
width: 220rpx;
}
.remark-input {
flex: 1;
font-size: 24rpx;
color: #F8FAFC;
}
.ph {
color: #475569;
}
}
.fee-row {
display: flex;
justify-content: space-between;
font-size: 24rpx;
margin-bottom: 12rpx;
.fee-k {
color: #94A3B8;
}
.fee-v {
color: #E2E8F0;
font-weight: 500;
&.green {
color: #10B981;
}
}
&.total-row {
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding-top: 16rpx;
margin-top: 16rpx;
margin-bottom: 0;
.fee-k {
font-size: 26rpx;
color: #F8FAFC;
font-weight: 600;
}
.pay-val {
font-size: 34rpx;
color: #10B981;
font-weight: 700;
}
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: rgba(21, 29, 48, 0.95);
backdrop-filter: blur(12px);
border-top: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32rpx;
z-index: 100;
.amount-wrap {
display: flex;
align-items: baseline;
.amount-label {
font-size: 24rpx;
color: #94A3B8;
}
.amount-num {
font-size: 40rpx;
font-weight: 700;
color: #10B981;
}
}
.btn-pay {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFF;
font-size: 28rpx;
font-weight: 700;
border-radius: 44rpx;
height: 80rpx;
line-height: 80rpx;
padding: 0 60rpx;
box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.35);
margin: 0;
}
}
</style>

查看文件

@@ -0,0 +1,412 @@
<template>
<view class="order-list-container">
<!-- 顶部状态分类 Tab -->
<view class="tab-header">
<view
v-for="tab in tabs"
:key="tab.key"
class="tab-item btn-active"
:class="{ active: currentTab === tab.key }"
@click="switchTab(tab.key)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="tab-indicator" v-if="currentTab === tab.key"></view>
</view>
</view>
<!-- 订单列表 -->
<view class="orders-wrap">
<view
v-for="order in filteredOrders"
:key="order.id"
class="order-card"
>
<!-- 订单卡片顶部 -->
<view class="order-head">
<text class="order-sn">单号:{{ order.orderSn }}</text>
<view class="status-pill" :class="order.status">
{{ getStatusText(order.status) }}
</view>
</view>
<!-- 陪玩与服务信息 -->
<view class="expert-block">
<image class="expert-avatar" :src="order.expert.avatar" mode="aspectFill" />
<view class="expert-meta">
<view class="name-row">
<text class="expert-name">{{ order.expert.nickname }}</text>
<text class="game-tag">{{ order.expert.game }}</text>
</view>
<text class="service-name">{{ order.serviceTitle }}</text>
<text class="order-time">下单时间:{{ order.createdAt }}</text>
</view>
<view class="amount-box">
<text class="amount-val">¥{{ order.totalAmount }}</text>
<text class="quantity-text">共 {{ order.quantity }} 小时</text>
</view>
</view>
<!-- 底部业务操作按钮组 -->
<view class="order-actions">
<template v-if="order.status === 'PENDING_PAY'">
<button class="action-btn cancel-btn btn-active" @click="cancelOrder(order.id)">取消</button>
<button class="action-btn primary-btn btn-active" @click="payOrder(order.id)">立即付款</button>
</template>
<template v-else-if="order.status === 'IN_SERVICE'">
<button class="action-btn chat-btn btn-active" @click="contactExpert(order.expert.nickname)">联系大神</button>
<button class="action-btn refund-btn btn-active" @click="applyRefund(order.id)">申请退款</button>
<button class="action-btn success-btn btn-active" @click="confirmComplete(order.id)">确认验收</button>
</template>
<template v-else-if="order.status === 'COMPLETED'">
<button class="action-btn chat-btn btn-active" @click="reBook(order.expert.id)">再次预约</button>
<button class="action-btn rate-btn btn-active" @click="rateExpert(order.id)">评价大神</button>
</template>
</view>
</view>
<view v-if="filteredOrders.length === 0" class="empty-state">
<text class="empty-icon">📦</text>
<text class="empty-text">暂无此类订单</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { ORDERS_LIST } from '@/common/mock.js';
const tabs = [
{ key: 'ALL', name: '全部' },
{ key: 'IN_SERVICE', name: '进行中' },
{ key: 'PENDING_PAY', name: '待付款' },
{ key: 'COMPLETED', name: '已完成' },
];
const currentTab = ref('ALL');
const orders = ref(ORDERS_LIST);
const filteredOrders = computed(() => {
if (currentTab.value === 'ALL') return orders.value;
return orders.value.filter(o => o.status === currentTab.value);
});
const switchTab = (tabKey) => {
currentTab.value = tabKey;
};
const getStatusText = (status) => {
const map = {
PENDING_PAY: '待付款',
IN_SERVICE: '服务中',
COMPLETED: '已完成',
REFUNDING: '退款中',
};
return map[status] || status;
};
const cancelOrder = (id) => {
uni.showModal({
title: '提示',
content: '确定要取消此订单吗?',
confirmColor: '#EF4444',
success: (res) => {
if (res.confirm) {
orders.value = orders.value.filter(o => o.id !== id);
uni.showToast({ title: '订单已取消', icon: 'none' });
}
}
});
};
const payOrder = (id) => {
uni.showToast({ title: '拉起微信支付成功', icon: 'success' });
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'IN_SERVICE';
};
const contactExpert = (name) => {
uni.showToast({ title: `已呼叫大神【${name}】进入开黑语音`, icon: 'none' });
};
const applyRefund = (id) => {
uni.showModal({
title: '申请退款/售后仲裁',
content: '申请后客服及平台专家将审核对局录像与连麦记录,款项将原路退回。',
confirmText: '确认申请',
confirmColor: '#EF4444',
success: (res) => {
if (res.confirm) {
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'REFUNDING';
uni.showToast({ title: '退款申请已提交,等待审核', icon: 'none' });
}
}
});
};
const confirmComplete = (id) => {
uni.showModal({
title: '确认验收',
content: '确认大神已圆满完成陪玩时长?确认后款项将结算入大神钱包。',
confirmText: '确认验收',
confirmColor: '#10B981',
success: (res) => {
if (res.confirm) {
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'COMPLETED';
uni.showToast({ title: '验收完成,去给大神好评吧!', icon: 'success' });
}
}
});
};
const reBook = (expertId) => {
uni.navigateTo({
url: `/pages/expert/detail?id=${expertId}`
});
};
const rateExpert = (id) => {
uni.showToast({ title: '评价功能已就绪,感谢支持!', icon: 'success' });
};
</script>
<style lang="scss" scoped>
.order-list-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 20rpx 32rpx 140rpx;
}
.tab-header {
display: flex;
background-color: #151D30;
border-radius: 20rpx;
padding: 8rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.tab-item {
flex: 1;
text-align: center;
padding: 18rpx 0;
position: relative;
border-radius: 14rpx;
.tab-text {
font-size: 26rpx;
color: #94A3B8;
font-weight: 500;
}
&.active {
background: rgba(99, 102, 241, 0.18);
.tab-text {
color: #FFFFFF;
font-weight: 700;
}
.tab-indicator {
position: absolute;
bottom: 4rpx;
left: 50%;
transform: translateX(-50%);
width: 32rpx;
height: 6rpx;
background: #6366F1;
border-radius: 3rpx;
box-shadow: 0 0 10rpx #6366F1;
}
}
}
}
.orders-wrap {
display: flex;
flex-direction: column;
gap: 24rpx;
.order-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
.order-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
.order-sn {
font-size: 22rpx;
color: #64748B;
font-family: monospace;
}
.status-pill {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 12rpx;
font-weight: 600;
&.PENDING_PAY {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
}
&.IN_SERVICE {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
}
&.COMPLETED {
background: rgba(148, 163, 184, 0.15);
color: #94A3B8;
}
&.REFUNDING {
background: rgba(239, 68, 68, 0.15);
color: #EF4444;
}
}
}
.expert-block {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.expert-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
margin-right: 18rpx;
}
.expert-meta {
flex: 1;
.name-row {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 6rpx;
.expert-name {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
}
.game-tag {
font-size: 20rpx;
color: #818CF8;
background: rgba(99, 102, 241, 0.15);
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
}
.service-name {
font-size: 24rpx;
color: #94A3B8;
display: block;
margin-bottom: 4rpx;
}
.order-time {
font-size: 20rpx;
color: #64748B;
}
}
.amount-box {
text-align: right;
.amount-val {
font-size: 34rpx;
font-weight: 700;
color: #10B981;
display: block;
}
.quantity-text {
font-size: 20rpx;
color: #64748B;
}
}
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
padding-top: 16rpx;
border-top: 1px solid rgba(255, 255, 255, 0.05);
.action-btn {
height: 60rpx;
line-height: 58rpx;
padding: 0 26rpx;
font-size: 22rpx;
border-radius: 30rpx;
margin: 0;
&.cancel-btn {
background: transparent;
color: #94A3B8;
border: 1px solid rgba(255, 255, 255, 0.12);
}
&.primary-btn {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFF;
border: none;
}
&.chat-btn {
background: rgba(99, 102, 241, 0.12);
color: #818CF8;
border: 1px solid rgba(99, 102, 241, 0.25);
}
&.refund-btn {
background: rgba(239, 68, 68, 0.1);
color: #EF4444;
border: 1px solid rgba(239, 68, 68, 0.25);
}
&.success-btn {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFF;
border: none;
}
&.rate-btn {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.3);
}
}
}
}
.empty-state {
text-align: center;
padding: 100rpx 0;
.empty-icon {
font-size: 64rpx;
display: block;
margin-bottom: 16rpx;
}
.empty-text {
color: #64748B;
font-size: 26rpx;
}
}
}
</style>

查看文件

@@ -0,0 +1,468 @@
<template>
<view class="user-container">
<!-- 用户头部个人资料卡 -->
<view class="profile-card">
<image class="user-avatar" :src="user.avatar" mode="aspectFill" />
<view class="user-info">
<view class="name-row">
<text class="user-name">{{ user.nickname }}</text>
<text class="vip-badge">VIP 黄金玩家</text>
</view>
<text class="user-uid">ID: {{ user.id }}</text>
</view>
<view class="edit-btn btn-active" @click="editProfile">
<text class="edit-icon">⚙️</text>
</view>
</view>
<!-- 核心亮点:双身份流光切换卡 -->
<view class="role-switch-card card-active" @click="toggleRole">
<view class="role-glow-border"></view>
<view class="role-content">
<view class="role-left">
<view class="role-title-row">
<text class="current-role-tag">{{ currentRole === 'PLAYER' ? '🎮 当前身份:普通玩家' : '👑 当前身份:接单大神' }}</text>
</view>
<text class="role-sub">
{{ currentRole === 'PLAYER' ? '一键切换至大神接单工作台,开始接单赚收益' : '一键切换回玩家大厅,自由寻找开黑大神与车队' }}
</text>
</view>
<button class="btn-switch-role btn-active">
{{ currentRole === 'PLAYER' ? '切换为大神 ⚡' : '切换回玩家 🔄' }}
</button>
</view>
</view>
<!-- 钱包与资产看板 -->
<view class="wallet-card">
<view class="wallet-head">
<text class="wallet-title">我的资产</text>
<text class="wallet-detail" @click="viewWalletLog">账单明细 ></text>
</view>
<view class="asset-grid">
<view class="asset-col">
<text class="asset-label">可用余额 (元)</text>
<text class="asset-val green-val">¥{{ user.balance }}</text>
<button class="asset-btn recharge btn-active" @click="handleRecharge">充值</button>
</view>
<view class="asset-divider"></view>
<view class="asset-col">
<text class="asset-label">分销佣金收益 (元)</text>
<text class="asset-val gold-val">¥{{ user.commission }}</text>
<button class="asset-btn withdraw btn-active" @click="handleWithdraw">提现</button>
</view>
</view>
</view>
<!-- 核心功能金刚区 -->
<view class="menu-grid-card">
<view class="menu-item btn-active" @click="navTo('/pages/activity/my-ticket')">
<view class="menu-icon bg-green">🎟️</view>
<text class="menu-title">我的门票</text>
<text class="menu-desc">4位核销码</text>
</view>
<view class="menu-item btn-active" @click="showInvitePoster">
<view class="menu-icon bg-purple">🤝</view>
<text class="menu-title">推广赚钱</text>
<text class="menu-desc">二级返佣</text>
</view>
<view class="menu-item btn-active" @click="navTo('/pages/order/list')">
<view class="menu-icon bg-blue">📋</view>
<text class="menu-title">所有订单</text>
<text class="menu-desc">对局与退款</text>
</view>
<view class="menu-item btn-active" @click="applyExpert">
<view class="menu-icon bg-orange">✨</view>
<text class="menu-title">大神认证</text>
<text class="menu-desc">入驻接单</text>
</view>
</view>
<!-- 列表式功能菜单 -->
<view class="list-menu-card">
<view class="list-row btn-active" @click="contactService">
<text class="row-icon">🎧</text>
<text class="row-label">官方在线客服</text>
<text class="row-arrow">></text>
</view>
<view class="list-row btn-active" @click="openNotice">
<text class="row-icon">🔔</text>
<text class="row-label">平台消息与通知</text>
<text class="row-arrow">></text>
</view>
<view class="list-row btn-active" @click="openAbout">
<text class="row-icon">🛡️</text>
<text class="row-label">平台规范与服务协议</text>
<text class="row-arrow">></text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { USER_PROFILE } from '@/common/mock.js';
const user = ref(USER_PROFILE);
const currentRole = ref('PLAYER');
const toggleRole = () => {
if (currentRole.value === 'PLAYER') {
currentRole.value = 'EXPERT';
uni.showToast({
title: '已无缝切换为【大神接单端】!',
icon: 'success',
duration: 2000
});
} else {
currentRole.value = 'PLAYER';
uni.showToast({
title: '已切换回【普通玩家端】!',
icon: 'none',
duration: 2000
});
}
};
const editProfile = () => {
uni.showToast({ title: '个人信息设置已就绪', icon: 'none' });
};
const viewWalletLog = () => {
uni.showToast({ title: '已加载近期收支明细', icon: 'none' });
};
const handleRecharge = () => {
uni.showToast({ title: '微信充值通道已拉起', icon: 'none' });
};
const handleWithdraw = () => {
uni.showModal({
title: '佣金提现申请',
content: `当前可提现佣金为 ¥${user.value.commission},提现将自动发起微信企业付款打款到微信零钱。`,
confirmText: '确认提现',
confirmColor: '#10B981',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '提现申请已提交,将在1个工作日内到账', icon: 'success' });
}
}
});
};
const showInvitePoster = () => {
uni.showModal({
title: '🎉 二级分销推广',
content: `您的专属邀请码为:PW${user.value.id}\n已成功邀请 ${user.value.invitedCount} 位好友!\n好友下单您将实时享受最高 15% 自动分润提成。`,
showCancel: false,
confirmText: '保存太阳码'
});
};
const applyExpert = () => {
uni.showToast({ title: '已提交大神实名技能审核', icon: 'none' });
};
const contactService = () => {
uni.showToast({ title: '正在连接 7x24h 官方电竞客服', icon: 'none' });
};
const openNotice = () => {
uni.showToast({ title: '暂无未读系统公告', icon: 'none' });
};
const openAbout = () => {
uni.showModal({
title: '平台服务协议',
content: '严禁发布任何违规言论及作弊挂机行为,平台全面保障用户资金安全与绿色电竞环境。',
showCancel: false
});
};
const navTo = (url) => {
if (url === '/pages/order/list' || url === '/pages/activity/list') {
uni.switchTab({ url });
} else {
uni.navigateTo({ url });
}
};
</script>
<style lang="scss" scoped>
.user-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: 24rpx 32rpx 140rpx;
}
.profile-card {
display: flex;
align-items: center;
margin-bottom: 28rpx;
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid #6366F1;
margin-right: 24rpx;
}
.user-info {
flex: 1;
.name-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 8rpx;
.user-name {
font-size: 32rpx;
font-weight: 700;
color: #F8FAFC;
}
.vip-badge {
font-size: 18rpx;
background: linear-gradient(135deg, #F59E0B, #D97706);
color: #FFF;
padding: 2rpx 10rpx;
border-radius: 8rpx;
font-weight: 600;
}
}
.user-uid {
font-size: 22rpx;
color: #64748B;
font-family: monospace;
}
}
.edit-btn {
width: 68rpx;
height: 68rpx;
background: #151D30;
border-radius: 34rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.08);
.edit-icon {
font-size: 32rpx;
}
}
}
.role-switch-card {
position: relative;
border-radius: 24rpx;
background: linear-gradient(135deg, #1E1B4B 0%, #0F172A 100%);
border: 2rpx solid #6366F1;
box-shadow: 0 8rpx 28rpx rgba(99, 102, 241, 0.35);
overflow: hidden;
padding: 28rpx;
margin-bottom: 28rpx;
.role-content {
display: flex;
justify-content: space-between;
align-items: center;
.role-left {
flex: 1;
margin-right: 20rpx;
.current-role-tag {
font-size: 28rpx;
font-weight: 700;
color: #FFFFFF;
display: block;
margin-bottom: 6rpx;
}
.role-sub {
font-size: 20rpx;
color: #94A3B8;
line-height: 1.4;
}
}
.btn-switch-role {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFF;
font-size: 22rpx;
font-weight: 700;
border-radius: 32rpx;
padding: 0 24rpx;
height: 64rpx;
line-height: 64rpx;
margin: 0;
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
}
}
}
.wallet-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.wallet-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.wallet-title {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
}
.wallet-detail {
font-size: 22rpx;
color: #64748B;
}
}
.asset-grid {
display: flex;
align-items: center;
justify-content: space-around;
.asset-col {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.asset-label {
font-size: 22rpx;
color: #94A3B8;
margin-bottom: 8rpx;
}
.asset-val {
font-size: 36rpx;
font-weight: 700;
margin-bottom: 14rpx;
&.green-val {
color: #10B981;
}
&.gold-val {
color: #F59E0B;
}
}
.asset-btn {
font-size: 20rpx;
padding: 0 24rpx;
height: 48rpx;
line-height: 46rpx;
border-radius: 24rpx;
margin: 0;
&.recharge {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
&.withdraw {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.3);
}
}
}
.asset-divider {
width: 1px;
height: 80rpx;
background: rgba(255, 255, 255, 0.08);
}
}
}
.menu-grid-card {
display: flex;
justify-content: space-between;
background-color: #151D30;
border-radius: 24rpx;
padding: 24rpx 16rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.menu-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.menu-icon {
width: 76rpx;
height: 76rpx;
border-radius: 22rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 34rpx;
margin-bottom: 10rpx;
&.bg-green { background: rgba(16, 185, 129, 0.2); }
&.bg-purple { background: rgba(139, 92, 246, 0.2); }
&.bg-blue { background: rgba(99, 102, 241, 0.2); }
&.bg-orange { background: rgba(245, 158, 11, 0.2); }
}
.menu-title {
font-size: 24rpx;
color: #F8FAFC;
font-weight: 600;
margin-bottom: 4rpx;
}
.menu-desc {
font-size: 18rpx;
color: #64748B;
}
}
}
.list-menu-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 8rpx 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.list-row {
display: flex;
align-items: center;
padding: 26rpx 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
&:last-child {
border-bottom: none;
}
.row-icon {
font-size: 28rpx;
margin-right: 20rpx;
}
.row-label {
flex: 1;
font-size: 26rpx;
color: #E2E8F0;
}
.row-arrow {
font-size: 24rpx;
color: #64748B;
}
}
}
</style>

二进制
pwuniapp/static/logo.png 普通文件

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 3.9 KiB

查看文件

@@ -0,0 +1,14 @@
uni.addInterceptor({
returnValue(res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then ===
"function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => {
if (!res) return resolve(res)
return res[0] ? reject(res[0]) : resolve(res[1])
});
});
},
});

44
pwuniapp/uni.scss 普通文件
查看文件

@@ -0,0 +1,44 @@
/**
* uni-app 内置样式变量与电竞暗夜流光设计系统 (Cyber-Esports Dark Elegance)
*/
/* 1. 全局背景与表面色阶 */
$bg-primary: #0A0F1D; // 页面最底色(深邃夜空黑)
$bg-surface: #151D30; // 卡片/模块容器底色
$bg-surface-elevated: #1E2942; // 浮层/弹窗底色
$bg-surface-glass: rgba(21, 29, 48, 0.85); // 拟态磨砂玻璃
/* 2. 品牌核心色与流光 */
$color-brand: #6366F1; // 核心电竞靛蓝
$color-brand-light: #818CF8; // 亮色高光
$color-brand-purple: #8B5CF6; // 紫罗兰流光
$color-brand-glow: rgba(99, 102, 241, 0.35);
/* 3. 业务强调与状态色 */
$color-accent: #10B981; // 翠绿高光(活动项目、在线呼吸灯、核销码)
$color-accent-glow: rgba(16, 185, 129, 0.3);
$color-warning: #F59E0B; // 暖橙金(金牌段位、倒计时)
$color-danger: #EF4444; // 警示红(退款、仲裁)
/* 4. 文本与排版无障碍色阶 (满足 WCAG AA 标准) */
$text-primary: #F8FAFC; // 一级标题与主要文字 (纯白)
$text-secondary: #94A3B8; // 二级描述文字 (冷灰蓝)
$text-muted: #64748B; // 三级占位说明文字
$border-subtle: #1E293B; // 微弱分割线与描边
/* 5. 圆角阶梯 */
$radius-sm: 8rpx;
$radius-md: 16rpx;
$radius-lg: 24rpx;
$radius-xl: 32rpx;
$radius-full: 9999rpx;
/* 6. 统一阴影与电竞流光 */
$shadow-card: 0 8rpx 24rpx rgba(0, 0, 0, 0.4);
$shadow-brand: 0 8rpx 24rpx rgba(99, 102, 241, 0.35);
$shadow-accent: 0 8rpx 24rpx rgba(16, 185, 129, 0.3);
/* 默认变量兼容器 */
$uni-color-primary: #6366F1;
$uni-bg-color: #0A0F1D;
$uni-text-color: #F8FAFC;