feat(client): 完成uni-app用户端8大核心静态页面开发并验证微信小程序与H5双端构建成功
这个提交包含在:
1
client/.npmrc
普通文件
1
client/.npmrc
普通文件
@@ -0,0 +1 @@
|
||||
node-linker=hoisted
|
||||
20
client/index.html
普通文件
20
client/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="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
client/package.json
普通文件
31
client/package.json
普通文件
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "peiwan-client",
|
||||
"version": "1.0.0",
|
||||
"description": "游戏陪玩小程序客户端 (uni-app Vue 3 + TypeScript)",
|
||||
"scripts": {
|
||||
"dev:h5": "uni",
|
||||
"build:h5": "uni build",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"build:mp-weixin": "uni build -p mp-weixin"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-components": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-h5": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-5020620260914001",
|
||||
"vue": "^3.4.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-automator": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-alpha-5020620260914001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-5020620260914001",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"sass": "^1.71.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "5.2.8"
|
||||
}
|
||||
}
|
||||
8088
client/pnpm-lock.yaml
自动生成的
普通文件
8088
client/pnpm-lock.yaml
自动生成的
普通文件
文件差异内容过多而无法显示
加载差异
45
client/src/App.vue
普通文件
45
client/src/App.vue
普通文件
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch: 陪玩小程序初始化');
|
||||
});
|
||||
onShow(() => {
|
||||
console.log('App Show');
|
||||
});
|
||||
onHide(() => {
|
||||
console.log('App Hide');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './uni.scss';
|
||||
|
||||
/* 全局应用基础重置 */
|
||||
page {
|
||||
background-color: $bg-primary;
|
||||
color: $text-primary;
|
||||
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>
|
||||
9
client/src/main.ts
普通文件
9
client/src/main.ts
普通文件
@@ -0,0 +1,9 @@
|
||||
import { createSSRApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
return {
|
||||
app,
|
||||
};
|
||||
}
|
||||
24
client/src/manifest.json
普通文件
24
client/src/manifest.json
普通文件
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "电竞陪玩",
|
||||
"appid": "",
|
||||
"description": "电竞陪玩与活动约玩小程序",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "touristappid",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"h5": {
|
||||
"title": "电竞陪玩平台",
|
||||
"router": {
|
||||
"mode": "hash"
|
||||
}
|
||||
}
|
||||
}
|
||||
297
client/src/mock/data.ts
普通文件
297
client/src/mock/data.ts
普通文件
@@ -0,0 +1,297 @@
|
||||
export interface GameCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
hot: boolean;
|
||||
}
|
||||
|
||||
export interface ExpertServiceItem {
|
||||
id: string;
|
||||
title: string;
|
||||
price: number;
|
||||
unit: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export interface ExpertReview {
|
||||
id: string;
|
||||
userNickname: string;
|
||||
userAvatar: string;
|
||||
rating: number;
|
||||
tags: string[];
|
||||
content: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export interface ExpertItem {
|
||||
id: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
gender: 'male' | 'female';
|
||||
age: number;
|
||||
game: string;
|
||||
rankTag: string;
|
||||
levelTag: string;
|
||||
price: number;
|
||||
rating: number;
|
||||
orderCount: number;
|
||||
isOnline: boolean;
|
||||
voiceDuration: number;
|
||||
bio: string;
|
||||
coverImage: string;
|
||||
photos: string[];
|
||||
services: ExpertServiceItem[];
|
||||
reviews: ExpertReview[];
|
||||
}
|
||||
|
||||
export interface ActivityItem {
|
||||
id: string;
|
||||
title: string;
|
||||
game: string;
|
||||
payType: 'AA' | 'PROJECT';
|
||||
totalFee: number;
|
||||
aaFee: number;
|
||||
maxParticipants: number;
|
||||
currentParticipants: number;
|
||||
startTime: string;
|
||||
location: string;
|
||||
creatorNickname: string;
|
||||
creatorAvatar: string;
|
||||
status: 'RECRUITING' | 'RUNNING' | 'FINISHED';
|
||||
participants: { id: string; nickname: string; avatar: string; verifyCode: string; checkedIn: boolean }[];
|
||||
}
|
||||
|
||||
export interface OrderItem {
|
||||
id: string;
|
||||
orderSn: string;
|
||||
expert: { id: string; nickname: string; avatar: string; game: string };
|
||||
serviceTitle: string;
|
||||
quantity: number;
|
||||
totalAmount: number;
|
||||
status: 'PENDING_PAY' | 'PENDING_ACCEPT' | 'IN_SERVICE' | 'PENDING_CONFIRM' | 'COMPLETED';
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export const GAMES_LIST: GameCategory[] = [
|
||||
{ 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: ExpertItem[] = [
|
||||
{
|
||||
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: ActivityItem[] = [
|
||||
{
|
||||
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: OrderItem[] = [
|
||||
{
|
||||
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,
|
||||
};
|
||||
106
client/src/pages.json
普通文件
106
client/src/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": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
392
client/src/pages/activity/detail.vue
普通文件
392
client/src/pages/activity/detail.vue
普通文件
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<view class="detail-container" v-if="activity">
|
||||
<!-- 头部活动卡片 -->
|
||||
<view class="hero-card">
|
||||
<view class="header-tags">
|
||||
<view class="type-pill" :class="activity.payType === 'AA' ? 'pill-aa' : 'pill-project'">
|
||||
{{ activity.payType === 'AA' ? 'AA制付费活动' : '项目制发起活动' }}
|
||||
</view>
|
||||
<view class="game-badge">{{ activity.game }}</view>
|
||||
</view>
|
||||
<view class="act-title">{{ activity.title }}</view>
|
||||
<view class="time-location-box">
|
||||
<view class="row">
|
||||
<text class="label">活动时间:</text>
|
||||
<text class="val">{{ activity.startTime }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">开黑频道:</text>
|
||||
<text class="val">{{ activity.location }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发起人卡片 -->
|
||||
<view class="section-card creator-card">
|
||||
<view class="section-title">活动发起人</view>
|
||||
<view class="creator-info">
|
||||
<image class="avatar" :src="activity.creatorAvatar" mode="aspectFill" />
|
||||
<view class="text-wrap">
|
||||
<view class="name">{{ activity.creatorNickname }}</view>
|
||||
<view class="desc">活动房主 · 负责活动组织与现场核销</view>
|
||||
</view>
|
||||
<view class="contact-btn btn-active" @click="handleContact">
|
||||
联系TA
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 费用平摊与结算明细 -->
|
||||
<view class="section-card fee-card">
|
||||
<view class="section-title">费用结算规则</view>
|
||||
<view class="fee-detail" v-if="activity.payType === 'AA'">
|
||||
<view class="fee-row">
|
||||
<text class="fee-name">活动总预算</text>
|
||||
<text class="fee-val">¥{{ activity.totalFee }}</text>
|
||||
</view>
|
||||
<view class="fee-row">
|
||||
<text class="fee-name">招募参与人数</text>
|
||||
<text class="fee-val">{{ activity.maxParticipants }} 人</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="fee-row highlight">
|
||||
<text class="fee-name">当前每人预估分摊</text>
|
||||
<text class="fee-val total">¥{{ activity.aaFee }}</text>
|
||||
</view>
|
||||
<view class="rule-tip">* 报名后费用将锁定在平台监管账户,现场完成【4位核销码】签到后解冻。</view>
|
||||
</view>
|
||||
<view class="fee-detail" v-else>
|
||||
<view class="fee-row">
|
||||
<text class="fee-name">项目制总包款项</text>
|
||||
<text class="fee-val">¥{{ activity.totalFee }}</text>
|
||||
</view>
|
||||
<view class="fee-row">
|
||||
<text class="fee-name">发起人支付状态</text>
|
||||
<text class="fee-val" style="color: #10B981;">已全额买断支付</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="fee-row highlight">
|
||||
<text class="fee-name">水友参与报名费用</text>
|
||||
<text class="fee-val total" style="color: #10B981;">0 元免费</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 当前已报名人员列表与核销情况 -->
|
||||
<view class="section-card participants-card">
|
||||
<view class="header-split">
|
||||
<view class="section-title">已报名名单 ({{ activity.participants.length }})</view>
|
||||
<view class="limit-text">上限 {{ activity.maxParticipants > 0 ? activity.maxParticipants : '不限' }} 人</view>
|
||||
</view>
|
||||
<view class="user-list">
|
||||
<view
|
||||
v-for="(p, index) in activity.participants"
|
||||
:key="p.id"
|
||||
class="user-row"
|
||||
>
|
||||
<image class="user-avatar" :src="p.avatar" mode="aspectFill" />
|
||||
<view class="user-meta">
|
||||
<text class="user-nickname">{{ p.nickname }}</text>
|
||||
<text class="user-seq">第 {{ index + 1 }} 位加入</text>
|
||||
</view>
|
||||
<view class="checkin-tag" :class="p.checkedIn ? 'checked' : 'waiting'">
|
||||
{{ p.checkedIn ? '已核销签到' : '已生成核销码' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定报名 CTA 栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="price-box">
|
||||
<text class="label">{{ activity.payType === 'AA' ? '应付平摊费:' : '报名费:' }}</text>
|
||||
<text class="currency">¥</text>
|
||||
<text class="price">{{ activity.payType === 'AA' ? activity.aaFee : 0 }}</text>
|
||||
</view>
|
||||
<button class="join-btn btn-active" @click="handleJoinActivity">
|
||||
立即报名生成 4 位核销码
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { ACTIVITIES_LIST, ActivityItem } from '../../mock/data';
|
||||
|
||||
const activity = ref<ActivityItem | null>(null);
|
||||
|
||||
onLoad((options) => {
|
||||
const actId = options?.id || 'act_101';
|
||||
activity.value = ACTIVITIES_LIST.find((a) => a.id === actId) || ACTIVITIES_LIST[0];
|
||||
});
|
||||
|
||||
const handleContact = () => {
|
||||
uni.showToast({ title: '已进入与发起人的即时沟通会话', icon: 'none' });
|
||||
};
|
||||
|
||||
const handleJoinActivity = () => {
|
||||
uni.showModal({
|
||||
title: '确认报名活动',
|
||||
content: `报名成功后,系统将为您生成该活动专属的【4位纯数字核销码】作为入场凭证!`,
|
||||
confirmText: '支付并生成',
|
||||
confirmColor: '#6366F1',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '生成核销码中...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.navigateTo({
|
||||
url: `/pages/activity/my-ticket?id=${activity.value?.id}&code=8392`
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 160rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
background: linear-gradient(135deg, $bg-surface 0%, $bg-surface-elevated 100%);
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.header-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.type-pill {
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: $radius-sm;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.pill-aa {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: $color-accent;
|
||||
border: 1rpx solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
&.pill-project {
|
||||
background-color: rgba(99, 102, 241, 0.2);
|
||||
color: $color-brand-light;
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.game-badge {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.act-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.time-location-box {
|
||||
background-color: rgba(10, 15, 29, 0.6);
|
||||
border-radius: $radius-md;
|
||||
padding: 16rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
.row {
|
||||
font-size: 24rpx;
|
||||
.label { color: $text-secondary; }
|
||||
.val { color: $text-primary; font-weight: 500; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.creator-card {
|
||||
.creator-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
flex: 1;
|
||||
.name { font-size: 28rpx; font-weight: 600; color: $text-primary; }
|
||||
.desc { font-size: 22rpx; color: $text-secondary; margin-top: 4rpx; }
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
padding: 12rpx 28rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-full;
|
||||
font-size: 24rpx;
|
||||
color: $color-brand-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fee-card {
|
||||
.fee-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
|
||||
.fee-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
|
||||
.fee-name { color: $text-secondary; }
|
||||
.fee-val { color: $text-primary; font-weight: 500; }
|
||||
|
||||
&.highlight {
|
||||
.fee-name { color: #FFFFFF; font-weight: bold; }
|
||||
.fee-val.total { font-size: 36rpx; font-weight: bold; color: $color-danger; }
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: $border-subtle;
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
|
||||
.rule-tip {
|
||||
font-size: 20rpx;
|
||||
color: $text-muted;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.participants-card {
|
||||
.header-split {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.section-title { margin-bottom: 0; }
|
||||
.limit-text { font-size: 22rpx; color: $text-secondary; }
|
||||
}
|
||||
|
||||
.user-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
|
||||
.user-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1rpx solid rgba(30, 41, 59, 0.4);
|
||||
|
||||
.user-avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.user-nickname { font-size: 26rpx; color: $text-primary; font-weight: 500; }
|
||||
.user-seq { font-size: 20rpx; color: $text-muted; }
|
||||
}
|
||||
|
||||
.checkin-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: $radius-sm;
|
||||
|
||||
&.checked {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: $color-accent;
|
||||
}
|
||||
&.waiting {
|
||||
background-color: rgba(99, 102, 241, 0.15);
|
||||
color: $color-brand-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
padding: 16rpx 32rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background-color: $bg-surface;
|
||||
border-top: 1rpx solid $border-subtle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 100;
|
||||
|
||||
.price-box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6rpx;
|
||||
|
||||
.label { font-size: 24rpx; color: $text-secondary; }
|
||||
.currency { font-size: 26rpx; color: $color-danger; font-weight: bold; }
|
||||
.price { font-size: 44rpx; color: $color-danger; font-weight: bold; }
|
||||
}
|
||||
|
||||
.join-btn {
|
||||
height: 80rpx;
|
||||
padding: 0 36rpx;
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
border-radius: $radius-full;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-brand;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
451
client/src/pages/activity/list.vue
普通文件
451
client/src/pages/activity/list.vue
普通文件
@@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<view class="activity-container">
|
||||
<!-- 顶部 AA制 / 项目制 双模切换 -->
|
||||
<view class="mode-tabs">
|
||||
<view
|
||||
class="tab-item btn-active"
|
||||
:class="{ active: currentTab === 'ALL' }"
|
||||
@click="currentTab = 'ALL'"
|
||||
>
|
||||
全部活动
|
||||
</view>
|
||||
<view
|
||||
class="tab-item btn-active"
|
||||
:class="{ active: currentTab === 'AA' }"
|
||||
@click="currentTab = 'AA'"
|
||||
>
|
||||
<text class="tab-badge aa">AA制</text>
|
||||
AA平摊拼单
|
||||
</view>
|
||||
<view
|
||||
class="tab-item btn-active"
|
||||
:class="{ active: currentTab === 'PROJECT' }"
|
||||
@click="currentTab = 'PROJECT'"
|
||||
>
|
||||
<text class="tab-badge project">项目制</text>
|
||||
包揽/水友赛
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活动规则提示微卡片 -->
|
||||
<view class="rule-hint-card">
|
||||
<text class="hint-icon">💡</text>
|
||||
<view class="hint-text">
|
||||
<text class="highlight">AA制</text>报名平摊服务费;<text class="highlight">项目制</text>由发起人买断总预算。报名均生成<text class="highlight-green">4位核销码</text>凭码签到!
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活动列表 -->
|
||||
<view class="activity-list">
|
||||
<view
|
||||
v-for="act in filteredActivities"
|
||||
:key="act.id"
|
||||
class="activity-card card-active"
|
||||
@click="navToDetail(act.id)"
|
||||
>
|
||||
<!-- 头部:类型标签与状态 -->
|
||||
<view class="card-header">
|
||||
<view class="type-wrap">
|
||||
<view class="type-pill" :class="act.payType === 'AA' ? 'pill-aa' : 'pill-project'">
|
||||
{{ act.payType === 'AA' ? 'AA制付费' : '项目制买断' }}
|
||||
</view>
|
||||
<view class="game-tag">{{ act.game }}</view>
|
||||
</view>
|
||||
<view class="status-pill" :class="act.status.toLowerCase()">
|
||||
{{ act.status === 'RECRUITING' ? '招募中' : act.status === 'RUNNING' ? '进行中' : '已结束' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="card-title">{{ act.title }}</view>
|
||||
|
||||
<!-- 发起人与时间地点 -->
|
||||
<view class="meta-row">
|
||||
<view class="creator-box">
|
||||
<image class="creator-avatar" :src="act.creatorAvatar" mode="aspectFill" />
|
||||
<text class="creator-name">{{ act.creatorNickname }} (发起人)</text>
|
||||
</view>
|
||||
<view class="time-box">⏰ {{ act.startTime }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 费用与进度看板 -->
|
||||
<view class="footer-row">
|
||||
<view class="fee-info">
|
||||
<template v-if="act.payType === 'AA'">
|
||||
<text class="fee-label">每人预估:</text>
|
||||
<text class="fee-currency">¥</text>
|
||||
<text class="fee-amount">{{ act.aaFee }}</text>
|
||||
<text class="fee-sub">(总包¥{{ act.totalFee }})</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="fee-label">项目预算:</text>
|
||||
<text class="fee-currency">¥</text>
|
||||
<text class="fee-amount">{{ act.totalFee }}</text>
|
||||
<text class="fee-free">免费参团</text>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 报名进度与参与人头像群 -->
|
||||
<view class="progress-box">
|
||||
<view class="participant-avatars" v-if="act.participants.length > 0">
|
||||
<image
|
||||
v-for="(p, i) in act.participants.slice(0, 3)"
|
||||
:key="p.id"
|
||||
class="stacked-avatar"
|
||||
:src="p.avatar"
|
||||
:style="{ right: (i * 18) + 'rpx' }"
|
||||
/>
|
||||
</view>
|
||||
<view class="progress-text">
|
||||
已报 <text class="current-num">{{ act.currentParticipants }}</text>
|
||||
<text v-if="act.maxParticipants > 0">/{{ act.maxParticipants }}人</text>
|
||||
<text v-else>/不限人数</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮:发起活动按钮 -->
|
||||
<view class="floating-create-btn btn-active" @click="handleCreateActivity">
|
||||
<text class="create-icon">+</text>
|
||||
<text class="create-text">发起活动</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ACTIVITIES_LIST } from '../../mock/data';
|
||||
|
||||
const currentTab = ref<'ALL' | 'AA' | 'PROJECT'>('ALL');
|
||||
|
||||
const filteredActivities = computed(() => {
|
||||
if (currentTab.value === 'ALL') return ACTIVITIES_LIST;
|
||||
return ACTIVITIES_LIST.filter((a) => a.payType === currentTab.value);
|
||||
});
|
||||
|
||||
const navToDetail = (id: string) => {
|
||||
uni.navigateTo({ url: `/pages/activity/detail?id=${id}` });
|
||||
};
|
||||
|
||||
const handleCreateActivity = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['发起 AA制 平摊车队', '发起 项目制 水友活动'],
|
||||
success: (res) => {
|
||||
const type = res.tapIndex === 0 ? 'AA制' : '项目制';
|
||||
uni.showModal({
|
||||
title: `发起${type}活动`,
|
||||
content: `请确认发起模式为【${type}】。创建成功后参与者将自动生成 4 位核销码!`,
|
||||
confirmText: '确定发起',
|
||||
confirmColor: '#6366F1',
|
||||
success: (mRes) => {
|
||||
if (mRes.confirm) {
|
||||
uni.showToast({ title: `${type}活动发起成功`, icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.activity-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 140rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
/* 顶部 Tab 切换 */
|
||||
.mode-tabs {
|
||||
display: flex;
|
||||
background-color: $bg-surface;
|
||||
border-radius: $radius-lg;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 1rpx solid $border-subtle;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 18rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
border-radius: $radius-md;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.tab-badge {
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: $radius-sm;
|
||||
|
||||
&.aa {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: $color-accent;
|
||||
}
|
||||
|
||||
&.project {
|
||||
background-color: rgba(99, 102, 241, 0.2);
|
||||
color: $color-brand-light;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
|
||||
.tab-badge {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 规则微提示卡片 */
|
||||
.rule-hint-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background-color: rgba(99, 102, 241, 0.1);
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.25);
|
||||
border-radius: $radius-md;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.hint-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.4;
|
||||
|
||||
.highlight {
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.highlight-green {
|
||||
color: $color-accent;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 活动卡片列表 */
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
|
||||
.activity-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.type-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.type-pill {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: $radius-sm;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.pill-aa {
|
||||
background-color: rgba(16, 185, 129, 0.15);
|
||||
color: $color-accent;
|
||||
border: 1rpx solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
&.pill-project {
|
||||
background-color: rgba(99, 102, 241, 0.15);
|
||||
color: $color-brand-light;
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.game-tag {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: $radius-full;
|
||||
|
||||
&.recruiting {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: $color-accent;
|
||||
}
|
||||
|
||||
&.running {
|
||||
background-color: rgba(245, 158, 11, 0.2);
|
||||
color: $color-warning;
|
||||
}
|
||||
|
||||
&.finished {
|
||||
background-color: rgba(100, 116, 139, 0.2);
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
|
||||
.creator-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
|
||||
.creator-avatar {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.creator-name {
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-top: 12rpx;
|
||||
border-top: 1rpx solid rgba(30, 41, 59, 0.6);
|
||||
|
||||
.fee-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6rpx;
|
||||
|
||||
.fee-label {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.fee-currency {
|
||||
font-size: 24rpx;
|
||||
color: $color-danger;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fee-amount {
|
||||
font-size: 36rpx;
|
||||
color: $color-danger;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fee-sub {
|
||||
font-size: 20rpx;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.fee-free {
|
||||
font-size: 22rpx;
|
||||
color: $color-accent;
|
||||
font-weight: bold;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.participant-avatars {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 40rpx;
|
||||
width: 70rpx;
|
||||
|
||||
.stacked-avatar {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid $bg-surface;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
|
||||
.current-num {
|
||||
color: $color-brand-light;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 悬浮发起活动按钮 */
|
||||
.floating-create-btn {
|
||||
position: fixed;
|
||||
bottom: 160rpx;
|
||||
right: 32rpx;
|
||||
height: 88rpx;
|
||||
padding: 0 36rpx;
|
||||
background: linear-gradient(135deg, $color-accent 0%, #059669 100%);
|
||||
border-radius: $radius-full;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
box-shadow: $shadow-accent;
|
||||
z-index: 99;
|
||||
|
||||
.create-icon {
|
||||
font-size: 36rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.create-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<view class="ticket-container">
|
||||
<!-- 电子凭证主卡片 -->
|
||||
<view class="ticket-card">
|
||||
<!-- 头部:活动基本信息 -->
|
||||
<view class="ticket-header">
|
||||
<view class="ticket-badge">活动签到凭证</view>
|
||||
<view class="activity-title">{{ activityTitle }}</view>
|
||||
<view class="activity-time">⏰ 活动时间:2026-09-20 20:00</view>
|
||||
</view>
|
||||
|
||||
<!-- 撕裂齿线分割 -->
|
||||
<view class="ticket-tear">
|
||||
<view class="circle-notch left"></view>
|
||||
<view class="dashed-line"></view>
|
||||
<view class="circle-notch right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 核心核销区:4 位纯数字大字插槽 -->
|
||||
<view class="verify-section">
|
||||
<view class="section-hint">向活动发起人出示以下 4 位核销码</view>
|
||||
|
||||
<view class="code-slots">
|
||||
<view class="slot-box" v-for="(digit, index) in codeDigits" :key="index">
|
||||
<text class="digit-text">{{ digit }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模拟条形码与二维码 -->
|
||||
<view class="barcode-wrap">
|
||||
<view class="barcode-bars">
|
||||
<view class="bar" v-for="n in 36" :key="n" :style="{ width: (n % 3 === 0 ? 6 : 3) + 'rpx' }"></view>
|
||||
</view>
|
||||
<view class="barcode-num">TICKET-2026-{{ verifyCode }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态防伪时间戳 -->
|
||||
<view class="security-timestamp">
|
||||
<view class="radar-dot"></view>
|
||||
<text class="time-text">动态防伪已激活:{{ currentTime }} ({{ refreshCountdown }}s刷新)</text>
|
||||
</view>
|
||||
|
||||
<!-- 核销状态标签 -->
|
||||
<view class="status-stamp" :class="isCheckedIn ? 'stamp-verified' : 'stamp-pending'">
|
||||
{{ isCheckedIn ? '已核销签到' : '待核销使用' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部使用须知 -->
|
||||
<view class="guide-card">
|
||||
<view class="guide-title">核销使用说明</view>
|
||||
<view class="guide-item">1. 到达活动现场或进入专属开黑连麦频道后,请向发起人提供此 4 位数字核销码。</view>
|
||||
<view class="guide-item">2. 发起人输入核销码确认后即视为签到成功,平摊款项将在活动结束后自动清算。</view>
|
||||
<view class="guide-item">3. 核销码仅可核销一次,请勿向第三方泄露,谨防被他人误核销。</view>
|
||||
</view>
|
||||
|
||||
<!-- 模拟操作按钮 (供快速测试核销状态) -->
|
||||
<view class="test-action-bar">
|
||||
<button class="test-toggle-btn btn-active" @click="toggleCheckinStatus">
|
||||
{{ isCheckedIn ? '重置为待核销状态' : '模拟已被发起人核销' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const activityTitle = ref('【AA制五排】今晚8点王者百星车队冲分');
|
||||
const verifyCode = ref('8392');
|
||||
const isCheckedIn = ref(false);
|
||||
const currentTime = ref('');
|
||||
const refreshCountdown = ref(30);
|
||||
let timer: any = null;
|
||||
|
||||
const codeDigits = computed(() => {
|
||||
return verifyCode.value.split('');
|
||||
});
|
||||
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
const h = String(now.getHours()).padStart(2, '0');
|
||||
const m = String(now.getMinutes()).padStart(2, '0');
|
||||
const s = String(now.getSeconds()).padStart(2, '0');
|
||||
currentTime.value = `${h}:${m}:${s}`;
|
||||
};
|
||||
|
||||
onLoad((options) => {
|
||||
if (options?.code) {
|
||||
verifyCode.value = options.code;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
updateTime();
|
||||
timer = setInterval(() => {
|
||||
updateTime();
|
||||
refreshCountdown.value--;
|
||||
if (refreshCountdown.value <= 0) {
|
||||
refreshCountdown.value = 30;
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
const toggleCheckinStatus = () => {
|
||||
isCheckedIn.value = !isCheckedIn.value;
|
||||
uni.showToast({
|
||||
title: isCheckedIn.value ? '已模拟核销完成' : '已重置为待核销',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.ticket-container {
|
||||
min-height: 100vh;
|
||||
padding: 32rpx 32rpx 60rpx;
|
||||
background-color: $bg-primary;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
/* 门票主卡片 */
|
||||
.ticket-card {
|
||||
background: linear-gradient(180deg, #1A233A 0%, #151D30 100%);
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-xl;
|
||||
box-shadow: $shadow-card;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.ticket-header {
|
||||
padding: 36rpx 32rpx 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
|
||||
.ticket-badge {
|
||||
align-self: flex-start;
|
||||
padding: 6rpx 18rpx;
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.4);
|
||||
border-radius: $radius-full;
|
||||
font-size: 22rpx;
|
||||
color: $color-brand-light;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/* 门票打孔虚线 */
|
||||
.ticket-tear {
|
||||
position: relative;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.circle-notch {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background-color: $bg-primary;
|
||||
|
||||
&.left { left: -20rpx; }
|
||||
&.right { right: -20rpx; }
|
||||
}
|
||||
|
||||
.dashed-line {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
margin: 0 32rpx;
|
||||
border-top: 2rpx dashed $border-subtle;
|
||||
}
|
||||
}
|
||||
|
||||
/* 核心核销区 */
|
||||
.verify-section {
|
||||
padding: 32rpx 32rpx 48rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.section-hint {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
/* 4 个独立大字插槽 */
|
||||
.code-slots {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 36rpx;
|
||||
|
||||
.slot-box {
|
||||
width: 120rpx;
|
||||
height: 140rpx;
|
||||
background-color: #0E1626;
|
||||
border: 2rpx solid $color-accent;
|
||||
border-radius: $radius-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 20rpx $color-accent-glow;
|
||||
|
||||
.digit-text {
|
||||
font-size: 72rpx;
|
||||
font-weight: 900;
|
||||
color: $color-accent;
|
||||
font-family: monospace, 'Courier New';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 条形码 */
|
||||
.barcode-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.barcode-bars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70rpx;
|
||||
gap: 6rpx;
|
||||
|
||||
.bar {
|
||||
height: 100%;
|
||||
background-color: $text-primary;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.barcode-num {
|
||||
font-size: 20rpx;
|
||||
color: $text-secondary;
|
||||
font-family: monospace;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 防伪时间戳 */
|
||||
.security-timestamp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
border-radius: $radius-full;
|
||||
|
||||
.radar-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background-color: $color-accent;
|
||||
box-shadow: 0 0 10rpx $color-accent;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 20rpx;
|
||||
color: $color-accent;
|
||||
}
|
||||
}
|
||||
|
||||
/* 状态印章水印 */
|
||||
.status-stamp {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: $radius-sm;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
border: 2rpx solid;
|
||||
transform: rotate(-12deg);
|
||||
|
||||
&.stamp-pending {
|
||||
color: $color-accent;
|
||||
border-color: $color-accent;
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
&.stamp-verified {
|
||||
color: $text-muted;
|
||||
border-color: $text-muted;
|
||||
background-color: rgba(100, 116, 139, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 使用指南卡片 */
|
||||
.guide-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14rpx;
|
||||
|
||||
.guide-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.test-action-bar {
|
||||
.test-toggle-btn {
|
||||
height: 76rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-full;
|
||||
font-size: 24rpx;
|
||||
color: $color-brand-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
516
client/src/pages/expert/detail.vue
普通文件
516
client/src/pages/expert/detail.vue
普通文件
@@ -0,0 +1,516 @@
|
||||
<template>
|
||||
<view class="expert-detail-container" v-if="expert">
|
||||
<!-- 头部背景与大图 -->
|
||||
<view class="hero-header">
|
||||
<image class="cover-image" :src="expert.coverImage" mode="aspectFill" />
|
||||
<view class="cover-mask"></view>
|
||||
<view class="profile-summary">
|
||||
<view class="avatar-wrap">
|
||||
<image class="avatar" :src="expert.avatar" mode="aspectFill" />
|
||||
<view class="online-indicator" v-if="expert.isOnline">在线</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ expert.nickname }}</text>
|
||||
<text class="level-badge">{{ expert.levelTag }}</text>
|
||||
</view>
|
||||
<view class="game-rank">{{ expert.game }} · {{ expert.rankTag }}</view>
|
||||
<view class="stats-row">
|
||||
<text class="stat-item">⭐ {{ expert.rating }}分</text>
|
||||
<text class="dot">·</text>
|
||||
<text class="stat-item">已接 {{ expert.orderCount }} 单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 语音自我介绍试听卡片 -->
|
||||
<view class="voice-card">
|
||||
<view class="voice-info">
|
||||
<text class="voice-title">专属语音打招呼</text>
|
||||
<text class="voice-time">{{ expert.voiceDuration }} 秒</text>
|
||||
</view>
|
||||
<view class="voice-player btn-active" @click="toggleVoice">
|
||||
<view class="play-icon">{{ isPlayingVoice ? '⏸' : '▶' }}</view>
|
||||
<view class="wave-bars" :class="{ playing: isPlayingVoice }">
|
||||
<view class="wb" v-for="n in 16" :key="n" :style="{ height: (8 + (n * 3) % 24) + 'rpx' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 个人介绍 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">大神简介</view>
|
||||
<view class="bio-text">{{ expert.bio }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务项目列表 (选择服务包) -->
|
||||
<view class="section-card">
|
||||
<view class="section-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="selectedServiceId = srv.id"
|
||||
>
|
||||
<view class="service-meta">
|
||||
<view class="service-title">{{ srv.title }}</view>
|
||||
<view class="service-desc">{{ srv.desc }}</view>
|
||||
</view>
|
||||
<view class="service-price">
|
||||
<text class="currency">¥</text>
|
||||
<text class="amount">{{ srv.price }}</text>
|
||||
<text class="unit">/{{ srv.unit }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 高光战绩与写真相册 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">高光战绩与风采</view>
|
||||
<scroll-view class="photo-scroll" scroll-x show-scrollbar="false">
|
||||
<view class="photo-list">
|
||||
<image
|
||||
v-for="(img, idx) in expert.photos"
|
||||
:key="idx"
|
||||
class="photo-item"
|
||||
:src="img"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(img)"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 买家评价列表 -->
|
||||
<view class="section-card">
|
||||
<view class="header-split">
|
||||
<view class="section-title">玩家评价 ({{ expert.reviews.length }})</view>
|
||||
<view class="rating-highlight">综合好评 99%</view>
|
||||
</view>
|
||||
|
||||
<view class="review-list" v-if="expert.reviews.length > 0">
|
||||
<view v-for="rev in expert.reviews" :key="rev.id" class="review-item">
|
||||
<view class="review-user">
|
||||
<image class="rev-avatar" :src="rev.userAvatar" mode="aspectFill" />
|
||||
<view class="rev-meta">
|
||||
<text class="rev-name">{{ rev.userNickname }}</text>
|
||||
<text class="rev-time">{{ rev.time }}</text>
|
||||
</view>
|
||||
<view class="rev-stars">★★★★★</view>
|
||||
</view>
|
||||
<view class="tag-row">
|
||||
<text v-for="(t, i) in rev.tags" :key="i" class="tag-pill">{{ t }}</text>
|
||||
</view>
|
||||
<view class="rev-content">{{ rev.content }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-review" v-else>
|
||||
暂无评价,快来抢先体验并发表评价吧~
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部预约下单结算固定操作条 -->
|
||||
<view class="bottom-action-bar">
|
||||
<view class="price-display">
|
||||
<text class="label">当前服务:</text>
|
||||
<text class="currency">¥</text>
|
||||
<text class="amount">{{ currentSelectedPrice }}</text>
|
||||
</view>
|
||||
<button class="order-btn btn-active" @click="handleGoOrder">
|
||||
立即预约下单
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { EXPERTS_LIST, ExpertItem } from '../../mock/data';
|
||||
|
||||
const expert = ref<ExpertItem | null>(null);
|
||||
const selectedServiceId = ref('');
|
||||
const isPlayingVoice = ref(false);
|
||||
|
||||
onLoad((options) => {
|
||||
const expId = options?.id || 'exp_101';
|
||||
expert.value = EXPERTS_LIST.find((e) => e.id === expId) || EXPERTS_LIST[0];
|
||||
if (expert.value?.services?.length > 0) {
|
||||
selectedServiceId.value = expert.value.services[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
const currentSelectedPrice = computed(() => {
|
||||
if (!expert.value) return 0;
|
||||
const srv = expert.value.services.find((s) => s.id === selectedServiceId.value);
|
||||
return srv ? srv.price : expert.value.price;
|
||||
});
|
||||
|
||||
const toggleVoice = () => {
|
||||
isPlayingVoice.value = !isPlayingVoice.value;
|
||||
uni.showToast({
|
||||
title: isPlayingVoice.value ? '播放大神语音介绍' : '已暂停',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const previewImage = (current: string) => {
|
||||
if (expert.value?.photos) {
|
||||
uni.previewImage({
|
||||
urls: expert.value.photos,
|
||||
current
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoOrder = () => {
|
||||
if (!selectedServiceId.value) {
|
||||
uni.showToast({ title: '请选择陪玩服务包', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/create?expertId=${expert.value?.id}&serviceId=${selectedServiceId.value}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.expert-detail-container {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 160rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
.hero-header {
|
||||
position: relative;
|
||||
height: 420rpx;
|
||||
|
||||
.cover-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cover-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(10, 15, 29, 0.2) 0%, rgba(10, 15, 29, 0.95) 100%);
|
||||
}
|
||||
|
||||
.profile-summary {
|
||||
position: absolute;
|
||||
bottom: 24rpx;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 20rpx;
|
||||
|
||||
.avatar-wrap {
|
||||
position: relative;
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid $color-brand;
|
||||
}
|
||||
|
||||
.online-indicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 2rpx 10rpx;
|
||||
background-color: $color-accent;
|
||||
border-radius: $radius-full;
|
||||
font-size: 18rpx;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
background: linear-gradient(135deg, $color-warning 0%, #D97706 100%);
|
||||
border-radius: $radius-sm;
|
||||
font-size: 18rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.game-rank {
|
||||
font-size: 24rpx;
|
||||
color: $color-brand-light;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.voice-card {
|
||||
margin: 24rpx;
|
||||
padding: 20rpx 28rpx;
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%);
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.3);
|
||||
border-radius: $radius-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.voice-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.voice-title { font-size: 26rpx; color: #FFFFFF; font-weight: 500; }
|
||||
.voice-time { font-size: 20rpx; color: $color-brand-light; margin-top: 4rpx; }
|
||||
}
|
||||
|
||||
.voice-player {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background-color: $bg-surface;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: $radius-full;
|
||||
|
||||
.play-icon {
|
||||
font-size: 24rpx;
|
||||
color: $color-accent;
|
||||
}
|
||||
|
||||
.wave-bars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
height: 32rpx;
|
||||
|
||||
.wb {
|
||||
width: 4rpx;
|
||||
background-color: $color-accent;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
&.playing .wb {
|
||||
animation: soundWave 0.6s infinite alternate ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx;
|
||||
margin: 0 24rpx 24rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.header-split {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
.section-title { margin-bottom: 0; }
|
||||
.rating-highlight { font-size: 22rpx; color: $color-warning; }
|
||||
}
|
||||
|
||||
.bio-text {
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.service-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 2rpx solid transparent;
|
||||
border-radius: $radius-md;
|
||||
|
||||
&.selected {
|
||||
border-color: $color-brand;
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%);
|
||||
}
|
||||
|
||||
.service-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
.service-title { font-size: 26rpx; font-weight: 500; color: $text-primary; }
|
||||
.service-desc { font-size: 20rpx; color: $text-secondary; }
|
||||
}
|
||||
|
||||
.service-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.currency { font-size: 22rpx; color: $color-danger; font-weight: bold; }
|
||||
.amount { font-size: 36rpx; color: $color-danger; font-weight: bold; }
|
||||
.unit { font-size: 20rpx; color: $text-secondary; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.photo-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
.photo-list {
|
||||
display: inline-flex;
|
||||
gap: 16rpx;
|
||||
|
||||
.photo-item {
|
||||
width: 220rpx;
|
||||
height: 160rpx;
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.review-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
|
||||
.review-item {
|
||||
border-bottom: 1rpx solid rgba(30, 41, 59, 0.5);
|
||||
padding-bottom: 16rpx;
|
||||
|
||||
.review-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.rev-avatar {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.rev-meta {
|
||||
flex: 1;
|
||||
.rev-name { font-size: 24rpx; color: $text-primary; font-weight: 500; }
|
||||
.rev-time { font-size: 18rpx; color: $text-muted; margin-left: 12rpx; }
|
||||
}
|
||||
|
||||
.rev-stars {
|
||||
color: $color-warning;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
margin: 10rpx 0;
|
||||
|
||||
.tag-pill {
|
||||
padding: 2rpx 10rpx;
|
||||
background-color: rgba(99, 102, 241, 0.15);
|
||||
color: $color-brand-light;
|
||||
font-size: 18rpx;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.rev-content {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-review {
|
||||
font-size: 24rpx;
|
||||
color: $text-muted;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.bottom-action-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
padding: 16rpx 32rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background-color: $bg-surface;
|
||||
border-top: 1rpx solid $border-subtle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 100;
|
||||
|
||||
.price-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.label { font-size: 24rpx; color: $text-secondary; }
|
||||
.currency { font-size: 26rpx; color: $color-danger; font-weight: bold; }
|
||||
.amount { font-size: 40rpx; color: $color-danger; font-weight: bold; }
|
||||
}
|
||||
|
||||
.order-btn {
|
||||
height: 80rpx;
|
||||
padding: 0 48rpx;
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
border-radius: $radius-full;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-brand;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes soundWave {
|
||||
0% { transform: scaleY(0.4); }
|
||||
100% { transform: scaleY(1.5); }
|
||||
}
|
||||
</style>
|
||||
659
client/src/pages/index/index.vue
普通文件
659
client/src/pages/index/index.vue
普通文件
@@ -0,0 +1,659 @@
|
||||
<template>
|
||||
<view class="index-container">
|
||||
<!-- 顶部搜索栏与即时匹配入口 -->
|
||||
<view class="header-section">
|
||||
<view class="search-bar">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索大神ID / 昵称 / 游戏"
|
||||
placeholder-style="color: #64748B;"
|
||||
v-model="searchKey"
|
||||
/>
|
||||
<text v-if="searchKey" class="clear-btn" @click="searchKey = ''">×</text>
|
||||
</view>
|
||||
<view class="instant-match-btn btn-active" @click="handleInstantMatch">
|
||||
<text class="instant-text">即时派单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 轮播图 Banner -->
|
||||
<swiper class="banner-swiper" circular autoplay indicator-dots indicator-active-color="#6366F1">
|
||||
<swiper-item v-for="item in BANNERS" :key="item.id">
|
||||
<view class="banner-item" :style="{ background: item.bg }">
|
||||
<view class="banner-tag">{{ item.tag }}</view>
|
||||
<view class="banner-title">{{ item.title }}</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 游戏分类横向滚动胶囊 -->
|
||||
<scroll-view class="game-categories" scroll-x show-scrollbar="false">
|
||||
<view class="category-list">
|
||||
<view
|
||||
v-for="game in GAMES_LIST"
|
||||
:key="game.id"
|
||||
class="category-capsule btn-active"
|
||||
:class="{ active: selectedGameId === game.id }"
|
||||
@click="selectGame(game.id)"
|
||||
>
|
||||
<text class="category-icon">{{ game.icon }}</text>
|
||||
<text class="category-name">{{ game.name }}</text>
|
||||
<view v-if="game.hot" class="hot-badge">HOT</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 筛选与排序工具栏 -->
|
||||
<view class="filter-toolbar">
|
||||
<view class="sort-tabs">
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: currentSort === 'recommend' }"
|
||||
@click="currentSort = 'recommend'"
|
||||
>
|
||||
综合推荐
|
||||
</view>
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: currentSort === 'rating' }"
|
||||
@click="currentSort = 'rating'"
|
||||
>
|
||||
高评分
|
||||
</view>
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: currentSort === 'price' }"
|
||||
@click="togglePriceSort"
|
||||
>
|
||||
价格 {{ priceSortDirection === 'asc' ? '↑' : priceSortDirection === 'desc' ? '↓' : '' }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="online-filter btn-active"
|
||||
:class="{ active: onlyOnline }"
|
||||
@click="onlyOnline = !onlyOnline"
|
||||
>
|
||||
<view class="dot" :class="{ 'dot-online': onlyOnline }"></view>
|
||||
<text>仅看在线</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 陪玩大神卡片瀑布流 -->
|
||||
<view class="expert-grid">
|
||||
<view
|
||||
v-for="expert in filteredExperts"
|
||||
:key="expert.id"
|
||||
class="expert-card card-active"
|
||||
@click="navToExpertDetail(expert.id)"
|
||||
>
|
||||
<!-- 头像与在线呼吸灯 -->
|
||||
<view class="avatar-wrap">
|
||||
<image class="avatar-img" :src="expert.avatar" mode="aspectFill" />
|
||||
<view class="online-pulse" v-if="expert.isOnline">
|
||||
<view class="pulse-ring"></view>
|
||||
<view class="pulse-core"></view>
|
||||
</view>
|
||||
<view class="offline-tag" v-else>离线</view>
|
||||
<view class="level-badge">{{ expert.levelTag }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 信息区 -->
|
||||
<view class="info-wrap">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ expert.nickname }}</text>
|
||||
<text class="gender-tag" :class="expert.gender">{{ expert.gender === 'female' ? '♀' : '♂' }} {{ expert.age }}</text>
|
||||
</view>
|
||||
|
||||
<view class="rank-tag">{{ expert.rankTag }}</view>
|
||||
|
||||
<!-- 语音试听条 -->
|
||||
<view class="voice-bar btn-active" @click.stop="togglePlayVoice(expert.id)">
|
||||
<view class="voice-wave" :class="{ playing: playingVoiceId === expert.id }">
|
||||
<view class="bar b1"></view>
|
||||
<view class="bar b2"></view>
|
||||
<view class="bar b3"></view>
|
||||
</view>
|
||||
<text class="voice-time">{{ expert.voiceDuration }}" 听声音</text>
|
||||
</view>
|
||||
|
||||
<!-- 价格与评分 -->
|
||||
<view class="price-row">
|
||||
<view class="price-box">
|
||||
<text class="currency">¥</text>
|
||||
<text class="amount">{{ expert.price }}</text>
|
||||
<text class="unit">/小时</text>
|
||||
</view>
|
||||
<view class="rating-box">
|
||||
<text class="star">★</text>
|
||||
<text class="score">{{ expert.rating }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { GAMES_LIST, BANNERS, EXPERTS_LIST } from '../../mock/data';
|
||||
|
||||
const searchKey = ref('');
|
||||
const selectedGameId = ref('all');
|
||||
const currentSort = ref('recommend');
|
||||
const priceSortDirection = ref<'none' | 'asc' | 'desc'>('none');
|
||||
const onlyOnline = ref(false);
|
||||
const playingVoiceId = ref<string | null>(null);
|
||||
|
||||
const selectGame = (gameId: string) => {
|
||||
selectedGameId.value = gameId;
|
||||
};
|
||||
|
||||
const togglePriceSort = () => {
|
||||
currentSort.value = 'price';
|
||||
if (priceSortDirection.value === 'none') {
|
||||
priceSortDirection.value = 'asc';
|
||||
} else if (priceSortDirection.value === 'asc') {
|
||||
priceSortDirection.value = 'desc';
|
||||
} else {
|
||||
priceSortDirection.value = 'none';
|
||||
currentSort.value = 'recommend';
|
||||
}
|
||||
};
|
||||
|
||||
const togglePlayVoice = (id: string) => {
|
||||
if (playingVoiceId.value === id) {
|
||||
playingVoiceId.value = null;
|
||||
} else {
|
||||
playingVoiceId.value = id;
|
||||
uni.showToast({ title: '播放专属打招呼语音', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstantMatch = () => {
|
||||
uni.showModal({
|
||||
title: '即时派单匹配',
|
||||
content: '系统正根据您当前游戏偏好,为您广播给最近的在线金牌大神!',
|
||||
confirmText: '立即寻找',
|
||||
cancelText: '取消',
|
||||
confirmColor: '#6366F1',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '匹配大神中...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.navigateTo({ url: `/pages/expert/detail?id=exp_101` });
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const navToExpertDetail = (id: string) => {
|
||||
uni.navigateTo({ url: `/pages/expert/detail?id=${id}` });
|
||||
};
|
||||
|
||||
const filteredExperts = computed(() => {
|
||||
let list = [...EXPERTS_LIST];
|
||||
|
||||
// 1. 游戏分类筛选
|
||||
if (selectedGameId.value !== 'all') {
|
||||
const targetGame = GAMES_LIST.find((g) => g.id === selectedGameId.value)?.name;
|
||||
if (targetGame) {
|
||||
list = list.filter((e) => e.game.includes(targetGame));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 在线筛选
|
||||
if (onlyOnline.value) {
|
||||
list = list.filter((e) => e.isOnline);
|
||||
}
|
||||
|
||||
// 3. 搜索关键词
|
||||
if (searchKey.value.trim()) {
|
||||
const kw = searchKey.value.trim().toLowerCase();
|
||||
list = list.filter((e) => e.nickname.toLowerCase().includes(kw) || e.game.includes(kw));
|
||||
}
|
||||
|
||||
// 4. 排序
|
||||
if (currentSort.value === 'rating') {
|
||||
list.sort((a, b) => b.rating - a.rating);
|
||||
} else if (currentSort.value === 'price') {
|
||||
if (priceSortDirection.value === 'asc') {
|
||||
list.sort((a, b) => a.price - b.price);
|
||||
} else if (priceSortDirection.value === 'desc') {
|
||||
list.sort((a, b) => b.price - a.price);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.index-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
/* 顶部搜索栏 */
|
||||
.header-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
height: 76rpx;
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-full;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24rpx;
|
||||
gap: 12rpx;
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 32rpx;
|
||||
color: $text-secondary;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.instant-match-btn {
|
||||
height: 76rpx;
|
||||
padding: 0 28rpx;
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
border-radius: $radius-full;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-brand;
|
||||
|
||||
.instant-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 轮播 Banner */
|
||||
.banner-swiper {
|
||||
height: 180rpx;
|
||||
border-radius: $radius-lg;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.banner-item {
|
||||
height: 100%;
|
||||
padding: 24rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border-radius: $radius-lg;
|
||||
|
||||
.banner-tag {
|
||||
align-self: flex-start;
|
||||
padding: 4rpx 14rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: $radius-sm;
|
||||
font-size: 20rpx;
|
||||
color: #FFFFFF;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 游戏分类滑动栏 */
|
||||
.game-categories {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.category-list {
|
||||
display: inline-flex;
|
||||
gap: 16rpx;
|
||||
|
||||
.category-capsule {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding: 14rpx 28rpx;
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-full;
|
||||
position: relative;
|
||||
|
||||
.category-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.hot-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
background-color: $color-danger;
|
||||
color: #FFFFFF;
|
||||
font-size: 16rpx;
|
||||
font-weight: bold;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%);
|
||||
border-color: $color-brand;
|
||||
|
||||
.category-name {
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 筛选与排序 */
|
||||
.filter-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.sort-tabs {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
|
||||
.sort-item {
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
position: relative;
|
||||
padding-bottom: 8rpx;
|
||||
|
||||
&.active {
|
||||
color: $text-primary;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32rpx;
|
||||
height: 4rpx;
|
||||
background-color: $color-brand;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.online-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: $bg-surface;
|
||||
border-radius: $radius-full;
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
border: 1rpx solid $border-subtle;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background-color: $text-muted;
|
||||
|
||||
&.dot-online {
|
||||
background-color: $color-accent;
|
||||
box-shadow: 0 0 8rpx $color-accent;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $color-accent;
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 瀑布流大神卡片 */
|
||||
.expert-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.expert-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.avatar-wrap {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
position: relative;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.online-pulse {
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
left: 16rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.pulse-core {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
background-color: $color-accent;
|
||||
}
|
||||
|
||||
.pulse-ring {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid $color-accent;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.offline-tag {
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
left: 16rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: $radius-sm;
|
||||
font-size: 18rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
position: absolute;
|
||||
bottom: 12rpx;
|
||||
right: 12rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background: linear-gradient(135deg, $color-warning 0%, #D97706 100%);
|
||||
border-radius: $radius-sm;
|
||||
font-size: 18rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.info-wrap {
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.nickname {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 180rpx;
|
||||
}
|
||||
|
||||
.gender-tag {
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: $radius-sm;
|
||||
|
||||
&.female {
|
||||
background-color: rgba(236, 72, 153, 0.2);
|
||||
color: #F472B6;
|
||||
}
|
||||
|
||||
&.male {
|
||||
background-color: rgba(59, 130, 246, 0.2);
|
||||
color: #60A5FA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rank-tag {
|
||||
font-size: 20rpx;
|
||||
color: $color-brand-light;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.voice-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 14rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border-radius: $radius-full;
|
||||
width: fit-content;
|
||||
|
||||
.voice-wave {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3rpx;
|
||||
height: 18rpx;
|
||||
|
||||
.bar {
|
||||
width: 4rpx;
|
||||
background-color: $color-accent;
|
||||
border-radius: 2rpx;
|
||||
|
||||
&.b1 { height: 10rpx; }
|
||||
&.b2 { height: 18rpx; }
|
||||
&.b3 { height: 14rpx; }
|
||||
}
|
||||
|
||||
&.playing .bar {
|
||||
animation: soundWave 0.8s infinite alternate ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.voice-time {
|
||||
font-size: 18rpx;
|
||||
color: $color-accent;
|
||||
}
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-top: 6rpx;
|
||||
|
||||
.price-box {
|
||||
.currency {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
color: $color-danger;
|
||||
}
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: $color-danger;
|
||||
}
|
||||
.unit {
|
||||
font-size: 20rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
|
||||
.star {
|
||||
color: $color-warning;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(0.9); opacity: 0.8; }
|
||||
50% { transform: scale(1.6); opacity: 0; }
|
||||
100% { transform: scale(0.9); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes soundWave {
|
||||
0% { transform: scaleY(0.4); }
|
||||
100% { transform: scaleY(1.4); }
|
||||
}
|
||||
</style>
|
||||
405
client/src/pages/order/create.vue
普通文件
405
client/src/pages/order/create.vue
普通文件
@@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<view class="order-create-container">
|
||||
<!-- 大神与服务包简要卡片 -->
|
||||
<view class="section-card expert-card" v-if="expert">
|
||||
<image class="avatar" :src="expert.avatar" mode="aspectFill" />
|
||||
<view class="meta">
|
||||
<view class="name-row">
|
||||
<text class="name">{{ expert.nickname }}</text>
|
||||
<text class="game">{{ expert.game }}</text>
|
||||
</view>
|
||||
<view class="service-name">{{ selectedService?.title || '陪玩服务' }}</view>
|
||||
<view class="price-rate">¥{{ selectedService?.price || expert.price }}/{{ selectedService?.unit || '小时' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数量与时段配置 -->
|
||||
<view class="section-card config-card">
|
||||
<view class="config-row">
|
||||
<text class="label">服务数量 ({{ selectedService?.unit || '小时' }})</text>
|
||||
<view class="stepper">
|
||||
<view class="step-btn btn-active" @click="changeQuantity(-1)">-</view>
|
||||
<text class="step-val">{{ quantity }}</text>
|
||||
<view class="step-btn btn-active" @click="changeQuantity(1)">+</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<view class="config-row">
|
||||
<text class="label">预约开始时间</text>
|
||||
<view class="time-options">
|
||||
<view
|
||||
class="time-pill btn-active"
|
||||
:class="{ active: timeType === 'NOW' }"
|
||||
@click="timeType = 'NOW'"
|
||||
>
|
||||
即刻开始 (5分钟内)
|
||||
</view>
|
||||
<view
|
||||
class="time-pill btn-active"
|
||||
:class="{ active: timeType === 'LATER' }"
|
||||
@click="timeType = 'LATER'"
|
||||
>
|
||||
预约今晚 21:00
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 游戏信息与备注 -->
|
||||
<view class="section-card form-card">
|
||||
<view class="section-title">玩家游戏信息</view>
|
||||
<view class="input-row">
|
||||
<text class="input-label">游戏大区</text>
|
||||
<input class="form-input" placeholder="例如:微信大区 / QQ大区" v-model="gameServer" />
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<text class="input-label">游戏角色名</text>
|
||||
<input class="form-input" placeholder="输入游戏内ID方便拉进车队" v-model="gameId" />
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<text class="input-label">特殊打法要求</text>
|
||||
<input class="form-input" placeholder="例如:主玩射手,需要硬辅贴身保护" v-model="gameNotes" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 优惠券选择 -->
|
||||
<view class="section-card coupon-card" @click="toggleCouponSelect">
|
||||
<view class="coupon-left">
|
||||
<text class="coupon-icon">🎟️</text>
|
||||
<text class="coupon-label">平台优惠券</text>
|
||||
</view>
|
||||
<view class="coupon-right">
|
||||
<text class="discount-text" v-if="appliedCoupon > 0">- ¥{{ appliedCoupon }} 元</text>
|
||||
<text class="no-coupon" v-else>暂无可用券</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 费用明细清单 -->
|
||||
<view class="section-card fee-summary-card">
|
||||
<view class="fee-row">
|
||||
<text class="text">服务总计</text>
|
||||
<text class="val">¥{{ rawTotal }}</text>
|
||||
</view>
|
||||
<view class="fee-row" v-if="appliedCoupon > 0">
|
||||
<text class="text">优惠券抵扣</text>
|
||||
<text class="val" style="color: #10B981;">- ¥{{ appliedCoupon }}</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="fee-row highlight">
|
||||
<text class="text">实际支付金额</text>
|
||||
<text class="val total">¥{{ finalPayAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部支付操作栏 -->
|
||||
<view class="bottom-pay-bar">
|
||||
<view class="total-box">
|
||||
<text class="label">实付:</text>
|
||||
<text class="currency">¥</text>
|
||||
<text class="price">{{ finalPayAmount }}</text>
|
||||
</view>
|
||||
<button class="pay-btn btn-active" @click="handleWechatPay">
|
||||
微信安全支付
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { EXPERTS_LIST, ExpertItem } from '../../mock/data';
|
||||
|
||||
const expert = ref<ExpertItem | null>(null);
|
||||
const selectedServiceId = ref('');
|
||||
const quantity = ref(1);
|
||||
const timeType = ref<'NOW' | 'LATER'>('NOW');
|
||||
const gameServer = ref('微信大区');
|
||||
const gameId = ref('超能战神007');
|
||||
const gameNotes = ref('');
|
||||
const appliedCoupon = ref(10); // 默认抵扣新人满减券 10 元
|
||||
|
||||
onLoad((options) => {
|
||||
const expId = options?.expertId || 'exp_101';
|
||||
selectedServiceId.value = options?.serviceId || 'srv_1';
|
||||
expert.value = EXPERTS_LIST.find((e) => e.id === expId) || EXPERTS_LIST[0];
|
||||
});
|
||||
|
||||
const selectedService = computed(() => {
|
||||
return expert.value?.services.find((s) => s.id === selectedServiceId.value) || expert.value?.services[0];
|
||||
});
|
||||
|
||||
const rawTotal = computed(() => {
|
||||
const unitPrice = selectedService.value ? selectedService.value.price : 35;
|
||||
return unitPrice * quantity.value;
|
||||
});
|
||||
|
||||
const finalPayAmount = computed(() => {
|
||||
return Math.max(0, rawTotal.value - appliedCoupon.value);
|
||||
});
|
||||
|
||||
const changeQuantity = (delta: number) => {
|
||||
const target = quantity.value + delta;
|
||||
if (target >= 1 && target <= 10) {
|
||||
quantity.value = target;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCouponSelect = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['使用「新人首单满减券」-10元', '使用「周末冲分9折券」-5元', '不使用优惠券'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) appliedCoupon.value = 10;
|
||||
else if (res.tapIndex === 1) appliedCoupon.value = 5;
|
||||
else appliedCoupon.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleWechatPay = () => {
|
||||
uni.showLoading({ title: '调起微信支付...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showModal({
|
||||
title: '支付成功',
|
||||
content: `订单已创建并完成支付!大神接单后将第一时间通过微信服务通知提醒您。`,
|
||||
showCancel: false,
|
||||
confirmText: '查看我的订单',
|
||||
confirmColor: '#6366F1',
|
||||
success: () => {
|
||||
uni.switchTab({ url: '/pages/order/list' });
|
||||
}
|
||||
});
|
||||
}, 600);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.order-create-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 160rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.expert-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.meta {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
.name { font-size: 30rpx; font-weight: bold; color: $text-primary; }
|
||||
.game { font-size: 22rpx; color: $color-brand-light; }
|
||||
}
|
||||
|
||||
.service-name { font-size: 24rpx; color: $text-secondary; }
|
||||
.price-rate { font-size: 24rpx; color: $color-danger; font-weight: bold; }
|
||||
}
|
||||
}
|
||||
|
||||
.config-card {
|
||||
.config-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8rpx 0;
|
||||
|
||||
.label { font-size: 26rpx; color: $text-primary; }
|
||||
|
||||
.stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: $bg-surface-elevated;
|
||||
border-radius: $radius-md;
|
||||
overflow: hidden;
|
||||
|
||||
.step-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: $color-brand-light;
|
||||
}
|
||||
|
||||
.step-val {
|
||||
width: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.time-options {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
|
||||
.time-pill {
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: $radius-full;
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid transparent;
|
||||
|
||||
&.active {
|
||||
color: #FFFFFF;
|
||||
border-color: $color-brand;
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: $border-subtle;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-card {
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.input-label {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 76rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-md;
|
||||
padding: 0 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.coupon-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
.coupon-icon { font-size: 28rpx; }
|
||||
.coupon-label { font-size: 26rpx; color: $text-primary; }
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
.discount-text { font-size: 26rpx; font-weight: bold; color: $color-accent; }
|
||||
.no-coupon { font-size: 24rpx; color: $text-muted; }
|
||||
.arrow { font-size: 32rpx; color: $text-muted; }
|
||||
}
|
||||
}
|
||||
|
||||
.fee-summary-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14rpx;
|
||||
|
||||
.fee-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 26rpx;
|
||||
.text { color: $text-secondary; }
|
||||
.val { color: $text-primary; }
|
||||
|
||||
&.highlight {
|
||||
.text { font-weight: bold; color: #FFFFFF; }
|
||||
.val.total { font-size: 36rpx; font-weight: bold; color: $color-danger; }
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: $border-subtle;
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-pay-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
padding: 16rpx 32rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background-color: $bg-surface;
|
||||
border-top: 1rpx solid $border-subtle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 100;
|
||||
|
||||
.total-box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.label { font-size: 24rpx; color: $text-secondary; }
|
||||
.currency { font-size: 26rpx; color: $color-danger; font-weight: bold; }
|
||||
.price { font-size: 44rpx; color: $color-danger; font-weight: bold; }
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
height: 80rpx;
|
||||
padding: 0 48rpx;
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
border-radius: $radius-full;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-brand;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
327
client/src/pages/order/list.vue
普通文件
327
client/src/pages/order/list.vue
普通文件
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<view class="order-list-container">
|
||||
<!-- 顶部状态切换 Tab -->
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item btn-active"
|
||||
:class="{ active: currentTab === tab.key }"
|
||||
@click="currentTab = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="orders-wrap">
|
||||
<view
|
||||
v-for="order in filteredOrders"
|
||||
:key="order.id"
|
||||
class="order-card card-active"
|
||||
>
|
||||
<!-- 订单编号与状态 -->
|
||||
<view class="card-header">
|
||||
<text class="order-sn">单号:{{ order.orderSn }}</text>
|
||||
<view class="status-pill" :class="order.status.toLowerCase()">
|
||||
{{ getStatusText(order.status) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 大神与服务信息 -->
|
||||
<view class="card-body">
|
||||
<image class="expert-avatar" :src="order.expert.avatar" mode="aspectFill" />
|
||||
<view class="detail">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ order.expert.nickname }}</text>
|
||||
<text class="game-tag">{{ order.expert.game }}</text>
|
||||
</view>
|
||||
<view class="service-title">{{ order.serviceTitle }}</view>
|
||||
<view class="time-meta">下单时间:{{ order.createdAt }}</view>
|
||||
</view>
|
||||
<view class="price-col">
|
||||
<view class="amount">¥{{ order.totalAmount }}</view>
|
||||
<view class="qty">共 {{ order.quantity }} 小时/局</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮组 (根据状态动态渲染) -->
|
||||
<view class="card-actions">
|
||||
<template v-if="order.status === 'PENDING_PAY'">
|
||||
<button class="action-btn secondary btn-active" @click="handleCancel(order.id)">取消订单</button>
|
||||
<button class="action-btn primary btn-active" @click="handlePay(order.id)">立即付款</button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="order.status === 'IN_SERVICE'">
|
||||
<button class="action-btn secondary btn-active" @click="handleChat(order.expert.nickname)">联系大神</button>
|
||||
<button class="action-btn danger btn-active" @click="handleRefund(order.id)">申请退款</button>
|
||||
<button class="action-btn primary btn-active" @click="handleConfirm(order.id)">确认完成</button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="order.status === 'COMPLETED'">
|
||||
<button class="action-btn secondary btn-active" @click="handleComplaint(order.id)">售后/申诉</button>
|
||||
<button class="action-btn primary btn-active" @click="handleReview(order.id)">发表好评</button>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" v-if="filteredOrders.length === 0">
|
||||
<view class="empty-icon">📂</view>
|
||||
<view class="empty-text">暂无相关订单记录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ORDERS_LIST, OrderItem } from '../../mock/data';
|
||||
|
||||
const tabs = [
|
||||
{ key: 'ALL', label: '全部' },
|
||||
{ key: 'IN_SERVICE', label: '进行中' },
|
||||
{ key: 'COMPLETED', label: '已完成' },
|
||||
{ key: 'PENDING_PAY', label: '待付款' },
|
||||
];
|
||||
|
||||
const currentTab = ref('ALL');
|
||||
const orders = ref<OrderItem[]>([...ORDERS_LIST]);
|
||||
|
||||
const filteredOrders = computed(() => {
|
||||
if (currentTab.value === 'ALL') return orders.value;
|
||||
return orders.value.filter((o) => o.status === currentTab.value);
|
||||
});
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'PENDING_PAY': return '待付款';
|
||||
case 'PENDING_ACCEPT': return '待接单';
|
||||
case 'IN_SERVICE': return '服务进行中';
|
||||
case 'PENDING_CONFIRM': return '待买家确认';
|
||||
case 'COMPLETED': return '已完成';
|
||||
default: return '已取消';
|
||||
}
|
||||
};
|
||||
|
||||
const handlePay = (id: string) => {
|
||||
uni.showLoading({ title: '拉起微信支付...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '支付成功,大神准备中', icon: 'success' });
|
||||
const target = orders.value.find((o) => o.id === id);
|
||||
if (target) target.status = 'IN_SERVICE';
|
||||
}, 600);
|
||||
};
|
||||
|
||||
const handleCancel = (id: string) => {
|
||||
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 handleChat = (nickname: string) => {
|
||||
uni.showToast({ title: `进入与 [${nickname}] 的聊天室`, icon: 'none' });
|
||||
};
|
||||
|
||||
const handleRefund = (id: string) => {
|
||||
uni.showModal({
|
||||
title: '申请平台介入退款',
|
||||
content: '请简要说明退款理由(如大神迟到、挂机等),平台将在2小时内审核',
|
||||
confirmColor: '#EF4444',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '退款申请已提交仲裁池', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = (id: string) => {
|
||||
uni.showModal({
|
||||
title: '确认验收服务',
|
||||
content: '确认大神已履约完成陪玩服务?款项将结算划入大神钱包。',
|
||||
confirmColor: '#6366F1',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const target = orders.value.find((o) => o.id === id);
|
||||
if (target) target.status = 'COMPLETED';
|
||||
uni.showToast({ title: '服务确认完成', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleComplaint = (id: string) => {
|
||||
uni.showToast({ title: '已进入售后申诉工单处理', icon: 'none' });
|
||||
};
|
||||
|
||||
const handleReview = (id: string) => {
|
||||
uni.showToast({ title: '感谢您的五星好评!', icon: 'success' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.order-list-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
background-color: $bg-primary;
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
background-color: $bg-surface;
|
||||
border-radius: $radius-lg;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid $border-subtle;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 18rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
border-radius: $radius-md;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.orders-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
|
||||
.order-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1rpx solid rgba(30, 41, 59, 0.5);
|
||||
padding-bottom: 14rpx;
|
||||
|
||||
.order-sn {
|
||||
font-size: 22rpx;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: $radius-sm;
|
||||
|
||||
&.pending_pay { color: $color-warning; background-color: rgba(245, 158, 11, 0.15); }
|
||||
&.in_service { color: $color-accent; background-color: rgba(16, 185, 129, 0.15); }
|
||||
&.completed { color: $color-brand-light; background-color: rgba(99, 102, 241, 0.15); }
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.expert-avatar {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
.nickname { font-size: 28rpx; font-weight: 600; color: $text-primary; }
|
||||
.game-tag { font-size: 20rpx; color: $color-brand-light; }
|
||||
}
|
||||
|
||||
.service-title { font-size: 24rpx; color: $text-secondary; }
|
||||
.time-meta { font-size: 20rpx; color: $text-muted; }
|
||||
}
|
||||
|
||||
.price-col {
|
||||
text-align: right;
|
||||
.amount { font-size: 34rpx; font-weight: bold; color: $color-danger; }
|
||||
.qty { font-size: 20rpx; color: $text-muted; }
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16rpx;
|
||||
padding-top: 10rpx;
|
||||
|
||||
.action-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 24rpx;
|
||||
border-radius: $radius-full;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
|
||||
&.primary {
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background-color: $bg-surface-elevated;
|
||||
color: $text-primary;
|
||||
border: 1rpx solid $border-subtle;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: $color-danger;
|
||||
border: 1rpx solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
gap: 16rpx;
|
||||
|
||||
.empty-icon { font-size: 64rpx; }
|
||||
.empty-text { font-size: 26rpx; color: $text-muted; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
442
client/src/pages/user/index.vue
普通文件
442
client/src/pages/user/index.vue
普通文件
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<view class="user-container">
|
||||
<!-- 用户基础信息卡片 -->
|
||||
<view class="profile-card">
|
||||
<image class="avatar" :src="user.avatar" mode="aspectFill" />
|
||||
<view class="info">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ user.nickname }}</text>
|
||||
<text class="uid">ID: {{ user.id }}</text>
|
||||
</view>
|
||||
<view class="role-badge">当前身份:娱乐玩家</view>
|
||||
</view>
|
||||
<view class="edit-btn btn-active" @click="handleEditProfile">编辑资料</view>
|
||||
</view>
|
||||
|
||||
<!-- 【核心重点】双身份切换流光卡片 -->
|
||||
<view class="role-switch-card card-active" @click="handleSwitchToExpert">
|
||||
<view class="switch-left">
|
||||
<view class="switch-icon">⚡</view>
|
||||
<view class="switch-text">
|
||||
<view class="title">成为入驻大神 · 开启接单赚钱</view>
|
||||
<view class="sub">实名认证通过后,即可一键切换至大神接单端</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="switch-btn">
|
||||
一键切换
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资产钱包卡片 -->
|
||||
<view class="wallet-card">
|
||||
<view class="wallet-header">
|
||||
<text class="title">我的钱包资产</text>
|
||||
<text class="flow-link btn-active" @click="handleViewFlow">消费流水 ›</text>
|
||||
</view>
|
||||
<view class="balance-row">
|
||||
<view class="balance-col">
|
||||
<text class="label">可用余额 (元)</text>
|
||||
<text class="val">¥{{ user.balance.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="balance-col">
|
||||
<text class="label">分销佣金 (元)</text>
|
||||
<text class="val commission">¥{{ user.commission.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wallet-actions">
|
||||
<button class="wallet-btn recharge btn-active" @click="handleRecharge">余额充值</button>
|
||||
<button class="wallet-btn withdraw btn-active" @click="handleWithdraw">佣金提现</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 二级分销推广专区 -->
|
||||
<view class="distribution-card">
|
||||
<view class="dist-header">
|
||||
<view class="title-box">
|
||||
<text class="fire-icon">🔥</text>
|
||||
<text class="title">邀请好友 · 赚二级佣金</text>
|
||||
</view>
|
||||
<text class="rate-tag">最高享 7% 永久返佣</text>
|
||||
</view>
|
||||
<view class="dist-stats">
|
||||
<view class="stat-item">
|
||||
<text class="num">{{ user.invitedCount }}</text>
|
||||
<text class="desc">我的团队 (人)</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="num">¥{{ user.commission.toFixed(2) }}</text>
|
||||
<text class="desc">累计提现 (元)</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="num">1 级</text>
|
||||
<text class="desc">分销等级</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="poster-btn btn-active" @click="handleGeneratePoster">
|
||||
生成我的专属推广太阳码海报
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 系统功能菜单列表 -->
|
||||
<view class="menu-card">
|
||||
<view class="menu-item btn-active" @click="handleMenu('我的收藏')">
|
||||
<view class="item-left">
|
||||
<text class="icon">⭐</text>
|
||||
<text class="name">我收藏的大神</text>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item btn-active" @click="handleMenu('我的动态')">
|
||||
<view class="item-left">
|
||||
<text class="icon">📝</text>
|
||||
<text class="name">我的战绩动态</text>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item btn-active" @click="handleMenu('优惠券中心')">
|
||||
<view class="item-left">
|
||||
<text class="icon">🎟️</text>
|
||||
<text class="name">我的优惠券</text>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item btn-active" @click="handleMenu('隐私与通知设置')">
|
||||
<view class="item-left">
|
||||
<text class="icon">⚙️</text>
|
||||
<text class="name">系统设置与隐私</text>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { USER_PROFILE } from '../../mock/data';
|
||||
|
||||
const user = ref({ ...USER_PROFILE });
|
||||
|
||||
const handleEditProfile = () => {
|
||||
uni.showToast({ title: '资料编辑暂未开放修改', icon: 'none' });
|
||||
};
|
||||
|
||||
const handleSwitchToExpert = () => {
|
||||
uni.showModal({
|
||||
title: '切换至陪玩接单端',
|
||||
content: '成为大神需要先完成实名认证与游戏段位核验,是否立即前往申请入驻?',
|
||||
confirmText: '立即认证',
|
||||
confirmColor: '#6366F1',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '已进入实名认证通道', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewFlow = () => {
|
||||
uni.showToast({ title: '查看近期消费与充值账单', icon: 'none' });
|
||||
};
|
||||
|
||||
const handleRecharge = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['充值 50 元', '充值 100 元 (赠10元)', '充值 200 元 (赠30元)'],
|
||||
success: (res) => {
|
||||
const amounts = [50, 110, 230];
|
||||
user.value.balance += amounts[res.tapIndex];
|
||||
uni.showToast({ title: '充值成功', icon: 'success' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
uni.showModal({
|
||||
title: '申请佣金提现',
|
||||
content: `当前可提现佣金为 ¥${user.value.commission} 元,平台将打款至您的微信零钱。`,
|
||||
confirmText: '申请提现',
|
||||
confirmColor: '#10B981',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '提现申请已提交,等待审核', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleGeneratePoster = () => {
|
||||
uni.showLoading({ title: '正在合成太阳码海报...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '专属推广海报已保存至相册', icon: 'success' });
|
||||
}, 700);
|
||||
};
|
||||
|
||||
const handleMenu = (title: string) => {
|
||||
uni.showToast({ title: `进入【${title}】`, icon: 'none' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../uni.scss';
|
||||
|
||||
.user-container {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 60rpx;
|
||||
background-color: $bg-primary;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.avatar {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid $color-brand;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12rpx;
|
||||
.nickname { font-size: 32rpx; font-weight: bold; color: $text-primary; }
|
||||
.uid { font-size: 20rpx; color: $text-muted; }
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
align-self: flex-start;
|
||||
padding: 2rpx 12rpx;
|
||||
background-color: rgba(99, 102, 241, 0.15);
|
||||
border-radius: $radius-sm;
|
||||
font-size: 20rpx;
|
||||
color: $color-brand-light;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 10rpx 24rpx;
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-full;
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/* 核心双身份切换卡片 */
|
||||
.role-switch-card {
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(16, 185, 129, 0.25) 100%);
|
||||
border: 2rpx solid rgba(99, 102, 241, 0.4);
|
||||
border-radius: $radius-lg;
|
||||
padding: 24rpx 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: $shadow-card;
|
||||
|
||||
.switch-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.switch-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.switch-text {
|
||||
.title { font-size: 28rpx; font-weight: bold; color: #FFFFFF; }
|
||||
.sub { font-size: 20rpx; color: $text-secondary; margin-top: 4rpx; }
|
||||
}
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
border-radius: $radius-full;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
|
||||
.arrow { font-size: 26rpx; }
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 28rpx;
|
||||
|
||||
.wallet-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.title { font-size: 28rpx; font-weight: 600; color: $text-primary; }
|
||||
.flow-link { font-size: 22rpx; color: $text-secondary; }
|
||||
}
|
||||
|
||||
.balance-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.balance-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.label { font-size: 22rpx; color: $text-secondary; }
|
||||
.val {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
|
||||
&.commission { color: $color-accent; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
.wallet-btn {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: $radius-full;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.recharge {
|
||||
background: linear-gradient(135deg, $color-brand 0%, $color-brand-purple 100%);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&.withdraw {
|
||||
background-color: $bg-surface-elevated;
|
||||
border: 1rpx solid $border-subtle;
|
||||
color: $color-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.distribution-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 28rpx;
|
||||
|
||||
.dist-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.title-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
.fire-icon { font-size: 28rpx; }
|
||||
.title { font-size: 28rpx; font-weight: 600; color: $text-primary; }
|
||||
}
|
||||
|
||||
.rate-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
background-color: rgba(245, 158, 11, 0.15);
|
||||
color: $color-warning;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
}
|
||||
|
||||
.dist-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 16rpx 0 24rpx;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
|
||||
.num { font-size: 32rpx; font-weight: bold; color: $color-brand-light; }
|
||||
.desc { font-size: 20rpx; color: $text-secondary; }
|
||||
}
|
||||
}
|
||||
|
||||
.poster-btn {
|
||||
height: 76rpx;
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
|
||||
border-radius: $radius-full;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
background-color: $bg-surface;
|
||||
border: 1rpx solid $border-subtle;
|
||||
border-radius: $radius-lg;
|
||||
padding: 8rpx 24rpx;
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid rgba(30, 41, 59, 0.4);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
.icon { font-size: 28rpx; }
|
||||
.name { font-size: 26rpx; color: $text-primary; }
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 32rpx;
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
client/src/uni.scss
普通文件
39
client/src/uni.scss
普通文件
@@ -0,0 +1,39 @@
|
||||
/* ==========================================================================
|
||||
Cyber-Esports Dark Elegance Design System Tokens (750rpx Standard)
|
||||
========================================================================== */
|
||||
|
||||
// 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 4.5:1)
|
||||
$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);
|
||||
18
client/tsconfig.json
普通文件
18
client/tsconfig.json
普通文件
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": false,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"types": ["@dcloudio/types"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
10
client/vite.config.ts
普通文件
10
client/vite.config.ts
普通文件
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import uni from '@dcloudio/vite-plugin-uni';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [uni()],
|
||||
server: {
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
# 游戏陪玩小程序 uni-app 静态 UI 架构规划与 UI/UX 约束规范文档
|
||||
|
||||
> **编制标准**:基于 `/ui-ux-pro-max` 移动端设计智能规范,面向 750rpx 移动端栅格系统,兼顾电竞潮酷视觉质感、WCAG AA 级色彩无障碍对比度与严苛的触控交互体验。
|
||||
> **编制标准**:基于 `/ui-ux-pro-max` 符合中式审美 移动端设计智能规范,面向 750rpx 移动端栅格系统,兼顾电竞潮酷视觉质感、WCAG AA 级色彩无障碍对比度与严苛的触控交互体验。
|
||||
|
||||
---
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户