feat(项目初始化 by claude code)

This commit is contained in:
ni ziyi 2025-12-29 13:29:07 +08:00
commit c767d35d32
28 changed files with 4887 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# uni-app
node_modules/
unpackage/
dist/
.hbuilderx/
# npm
package-lock.json
yarn.lock
pnpm-lock.yaml
# IDE
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

168
App.vue Normal file
View File

@ -0,0 +1,168 @@
<script setup>
import { onLaunch, onShow } from '@dcloudio/uni-app'
onLaunch(() => {
console.log('App Launch - 电竞服务平台启动')
// ID ()
const tenantId = uni.getStorageSync('tenantId')
if (!tenantId) {
//
uni.setStorageSync('tenantId', 10001)
}
})
onShow(() => {
console.log('App Show')
})
</script>
<style lang="scss">
/* 电竞赛博朋克风格 - 全局样式 */
@import './uni.scss';
page {
background: linear-gradient(180deg, #0a0e27 0%, #0f1229 50%, #1a1d3a 100%);
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', sans-serif;
}
/* 通用容器 */
.page-container {
min-height: 100vh;
padding-bottom: 20rpx;
}
/* 赛博朋克卡片基础样式 */
.cyber-card {
background: linear-gradient(135deg, rgba(20, 25, 50, 0.9) 0%, rgba(10, 14, 39, 0.8) 100%);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 16rpx;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.5) 50%,
transparent 100%
);
animation: scan 3s infinite;
}
}
/* 扫描动画 */
@keyframes scan {
0%, 100% { opacity: 0; transform: translateY(0); }
50% { opacity: 1; transform: translateY(100px); }
}
/* 霓虹发光文字 */
.neon-text {
color: #00ffff;
text-shadow:
0 0 5px rgba(0, 255, 255, 0.5),
0 0 10px rgba(0, 255, 255, 0.3),
0 0 15px rgba(0, 255, 255, 0.2);
}
/* 主要按钮 - 赛博朋克风格 */
.cyber-button {
background: linear-gradient(135deg, #00ffff 0%, #0099ff 100%);
color: #0a0e27;
border: none;
border-radius: 8rpx;
font-weight: bold;
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
&:active::after {
left: 100%;
}
}
/* 次要按钮 */
.cyber-button-outline {
background: transparent;
color: #00ffff;
border: 1px solid #00ffff;
border-radius: 8rpx;
&:active {
background: rgba(0, 255, 255, 0.1);
}
}
/* 标签 */
.cyber-tag {
display: inline-block;
padding: 4rpx 12rpx;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 4rpx;
color: #00ffff;
font-size: 24rpx;
}
/* 分割线 */
.cyber-divider {
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.3) 50%,
transparent 100%
);
margin: 20rpx 0;
}
/* 加载动画 */
.loading-dot {
display: inline-block;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #00ffff;
margin: 0 4rpx;
animation: pulse 1.4s infinite ease-in-out;
&:nth-child(1) { animation-delay: -0.32s; }
&:nth-child(2) { animation-delay: -0.16s; }
}
@keyframes pulse {
0%, 80%, 100% {
box-shadow: 0 0 0 0 rgba(0, 255, 255, 0.7);
transform: scale(0.8);
}
40% {
box-shadow: 0 0 10px 5px rgba(0, 255, 255, 0);
transform: scale(1);
}
}
/* 渐变文字 */
.gradient-text {
background: linear-gradient(135deg, #00ffff 0%, #ff00ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
</style>

230
README.md Normal file
View File

@ -0,0 +1,230 @@
# 🎮 游戏服务交易平台 - 电竞赛博朋克风格
> 基于 uni-app 开发的游戏服务交易平台微信小程序,采用电竞赛博朋克视觉风格,融合游戏元素、科技感和竞技感。
## ✨ 项目特色
### 🎨 视觉设计
- **电竞赛博朋克风格**: 霓虹色、数字雨效果、科技光效
- **游戏化UI**: 游戏主题图标、竞技感配色、等级系统
- **动效丰富**: 扫描线动画、霓虹发光、粒子效果
- **深色主题**: 深蓝黑背景,青色霓虹主色调
### 🛠️ 技术亮点
- **Vue 3 Composition API**: 最新的Vue3语法
- **静态数据Mock**: 完整的Mock数据支持,无需后端即可运行
- **组件化开发**: 高度复用的公共组件
- **响应式设计**: 适配各种屏幕尺寸
## 📁 项目结构
```
game-service-miniapp/
├── pages/ # 页面
│ ├── index/ # 首页
│ │ └── index.vue # 游戏分类、热门推荐
│ ├── category/ # 分类
│ │ └── list.vue # 服务列表(筛选、排序)
│ ├── service/ # 服务
│ │ └── detail.vue # 服务详情
│ ├── order/ # 订单
│ │ ├── create.vue # 创建订单
│ │ ├── list.vue # 订单列表
│ │ └── detail.vue # 订单详情
│ └── user/ # 用户
│ └── index.vue # 个人中心
├── components/ # 公共组件
│ ├── service-card/ # 服务卡片组件
│ ├── order-card/ # 订单卡片组件
│ └── game-category/ # 游戏分类组件
├── mock/ # Mock数据
│ ├── categories.js # 游戏分类数据
│ ├── services.js # 服务数据
│ ├── orders.js # 订单数据
│ ├── user.js # 用户数据
│ ├── players.js # 选手数据
│ ├── evaluations.js # 评价数据
│ └── index.js # 统一导出
├── static/ # 静态资源
│ ├── images/ # 图片
│ └── icons/ # 图标
├── App.vue # 应用入口
├── main.js # 主入口
├── uni.scss # 全局样式变量
├── pages.json # 页面配置
├── manifest.json # 应用配置
└── package.json # 项目配置
```
## 🎯 核心功能
### 1. 首页
- ✅ 搜索功能入口
- ✅ 游戏分类横向滚动
- ✅ 服务类型快捷入口
- ✅ 热门服务推荐
### 2. 服务列表
- ✅ 按分类筛选
- ✅ 多种排序方式(价格/销量/评分)
- ✅ 服务卡片展示
### 3. 服务详情
- ✅ 服务信息展示
- ✅ 商家信息
- ✅ 用户评价
- ✅ 一键下单
### 4. 订单管理
- ✅ 创建订单(表单填写)
- ✅ 订单列表(Tab分类)
- ✅ 订单详情(状态流转)
- ✅ 订单操作(支付/取消/确认/评价)
### 5. 个人中心
- ✅ 用户信息展示
- ✅ 资产信息(余额/积分/优惠券)
- ✅ 订单统计
- ✅ 功能菜单
## 🎨 设计系统
### 配色方案
```scss
// 主色调
$neon-cyan: #00ffff // 青色霓虹(主色)
$neon-magenta: #ff00ff // 洋红霓虹
$neon-blue: #0099ff // 蓝色霓虹
$neon-pink: #ff1493 // 粉色霓虹
// 背景色
$bg-primary: #0a0e27 // 主背景
$bg-secondary: #0f1229 // 次背景
$bg-tertiary: #1a1d3a // 三级背景
// 文字颜色
$text-primary: #ffffff // 主文字
$text-secondary: #a0a4c4 // 次文字
$text-tertiary: #7a7e9d // 三级文字
```
### 视觉元素
- **霓虹边框**: 青色发光边框
- **渐变背景**: 深色渐变卡片背景
- **扫描线**: 赛博朋克扫描动画
- **光效**: 霓虹光晕效果
- **粒子**: 数字雨背景(可选)
## 🚀 快速开始
### 环境要求
- Node.js >= 14.x
- HBuilderX (推荐) 或 微信开发者工具
### 方式一:使用 HBuilderX (推荐)
1. **安装 HBuilderX**
- 下载地址: https://www.dcloud.io/hbuilderx.html
2. **导入项目**
```
HBuilderX > 文件 > 导入 > 从本地目录导入
选择 game-service-miniapp 文件夹
```
3. **运行到微信开发者工具**
```
运行 > 运行到小程序模拟器 > 微信开发者工具
```
### 方式二:使用命令行
1. **安装依赖**
```bash
cd game-service-miniapp
npm install
```
2. **运行开发环境**
```bash
npm run dev:mp-weixin
```
3. **用微信开发者工具打开**
```
打开微信开发者工具
导入项目 > 选择 dist/dev/mp-weixin 目录
```
## 📱 功能演示
### 首页
- 顶部搜索栏 + 通知按钮
- 游戏分类滑动选择
- 服务类型网格
- 热门服务瀑布流
### 服务详情
- 大图封面展示
- 价格、评分、销量统计
- 商家信息卡片
- 用户评价列表
- 底部下单按钮
### 订单流程
```
浏览服务 → 查看详情 → 填写订单 → 确认下单 →
待支付 → 待接单 → 进行中 → 待确认 → 已完成 → 评价
```
## 📊 Mock数据说明
项目包含完整的Mock数据,无需后端即可运行:
- **8种游戏分类**: 王者荣耀、英雄联盟、VALORANT等
- **10+服务套餐**: 代练、陪玩、教学等
- **5个订单示例**: 覆盖各种订单状态
- **4位选手数据**: 包含头像、评分、接单数
- **5条用户评价**: 展示评价系统
所有数据位于 `/mock` 目录,可根据需求自行修改。
## 🎯 下一步计划
- [ ] 接入真实后端API
- [ ] 支付功能集成
- [ ] IM聊天系统
- [ ] 分享功能
- [ ] 更多动效优化
- [ ] 性能优化
## 📝 注意事项
1. **图片资源**: 项目使用 picsum.photos 占位图,实际使用时需替换为真实图片
2. **AppID配置**: manifest.json 中的 appid 需替换为你的小程序appid
3. **API接口**: 当前使用Mock数据,接入后端时需修改 `/api` 目录
4. **支付功能**: 支付相关功能为演示,实际使用需接入微信支付
## 💡 技术栈
- **框架**: uni-app (Vue 3)
- **语言**: JavaScript ES6+
- **样式**: SCSS
- **状态管理**: Pinia (可选)
- **构建工具**: Vue CLI
## 📄 License
MIT License
## 🙏 致谢
感谢 uni-app 团队提供的优秀跨端框架!
---
**🎮 Powered by Cyber Tech - 让游戏服务更智能**

View File

@ -0,0 +1,144 @@
<template>
<view class="game-category" @click="handleClick">
<view class="category-icon" :style="{ background: getCategoryGradient(category.color) }">
<text class="icon-text">{{ category.icon }}</text>
<!-- 霓虹光圈 -->
<view class="glow-ring" :style="{ borderColor: category.color }"></view>
</view>
<view class="category-name">{{ category.name }}</view>
<view v-if="category.hot" class="hot-badge">
<text class="hot-text">HOT</text>
</view>
<view v-if="showCount" class="service-count">
{{ category.serviceCount }}
</view>
</view>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
category: {
type: Object,
required: true
},
showCount: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click', props.category)
}
const getCategoryGradient = (color) => {
return `linear-gradient(135deg, ${color}40 0%, ${color}20 100%)`
}
</script>
<style lang="scss" scoped>
.game-category {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: 20rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
}
.category-icon {
width: 120rpx;
height: 120rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
position: relative;
border: 1px solid rgba(0, 255, 255, 0.3);
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
}
.game-category:active .category-icon {
border-color: rgba(0, 255, 255, 0.6);
box-shadow:
0 0 20rpx rgba(0, 255, 255, 0.4),
0 0 40rpx rgba(0, 255, 255, 0.2);
}
.icon-text {
font-size: 56rpx;
position: relative;
z-index: 2;
}
.glow-ring {
position: absolute;
top: -4rpx;
left: -4rpx;
right: -4rpx;
bottom: -4rpx;
border: 2px solid;
border-radius: 26rpx;
opacity: 0;
transition: all 0.3s ease;
}
.game-category:active .glow-ring {
opacity: 0.6;
animation: glow 1.5s infinite;
}
@keyframes glow {
0%, 100% {
transform: scale(1);
opacity: 0.6;
}
50% {
transform: scale(1.1);
opacity: 0.3;
}
}
.category-name {
font-size: 26rpx;
font-weight: bold;
color: #ffffff;
text-align: center;
margin-bottom: 8rpx;
}
.hot-badge {
position: absolute;
top: 12rpx;
right: 12rpx;
padding: 4rpx 12rpx;
background: linear-gradient(135deg, rgba(255, 107, 107, 0.9) 0%, rgba(255, 71, 71, 0.9) 100%);
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.4);
}
.hot-text {
font-size: 20rpx;
font-weight: bold;
color: #ffffff;
}
.service-count {
font-size: 22rpx;
color: #7a7e9d;
}
</style>

View File

@ -0,0 +1,327 @@
<template>
<view class="order-card cyber-card" @click="handleClick">
<!-- 订单头部 -->
<view class="order-header">
<view class="order-info">
<text class="order-no">订单号: {{ order.orderNo }}</text>
<view class="status-badge" :style="{ background: getStatusColor(order.status) }">
{{ getStatusText(order.status) }}
</view>
</view>
<text class="order-time">{{ formatTime(order.createTime) }}</text>
</view>
<!-- 分割线 -->
<view class="cyber-divider"></view>
<!-- 服务信息 -->
<view class="service-info">
<image :src="order.serviceCover" class="service-cover" mode="aspectFill" />
<view class="service-detail">
<view class="service-name">{{ order.serviceName }}</view>
<view class="category-tag">{{ order.categoryName }}</view>
<!-- 选手信息 (如果已接单) -->
<view v-if="order.playerName" class="player-info">
<image :src="order.playerAvatar" class="player-avatar" />
<text class="player-name">{{ order.playerName }}</text>
<text v-if="order.status === 3" class="online-dot"></text>
</view>
</view>
<view class="price-info">
<view class="price-value">¥{{ order.actualPrice }}</view>
</view>
</view>
<!-- 订单操作按钮 -->
<view class="order-actions">
<!-- 待支付 -->
<template v-if="order.status === 0">
<button class="action-btn btn-cancel" @click.stop="handleCancel">取消订单</button>
<button class="action-btn btn-primary" @click.stop="handlePay">立即支付</button>
</template>
<!-- 待接单/已接单 -->
<template v-else-if="order.status === 1 || order.status === 2">
<button class="action-btn btn-outline" @click.stop="handleContact">联系商家</button>
<button class="action-btn btn-cancel" @click.stop="handleCancel">取消订单</button>
</template>
<!-- 进行中 -->
<template v-else-if="order.status === 3">
<button class="action-btn btn-outline" @click.stop="handleContact">联系选手</button>
<button class="action-btn btn-primary" @click.stop="handleDetail">查看详情</button>
</template>
<!-- 待确认 -->
<template v-else-if="order.status === 4">
<button class="action-btn btn-outline" @click.stop="handleRefund">申请退款</button>
<button class="action-btn btn-primary" @click.stop="handleConfirm">确认完成</button>
</template>
<!-- 已完成 -->
<template v-else-if="order.status === 5">
<button class="action-btn btn-outline" @click.stop="handleDetail">查看详情</button>
<button class="action-btn btn-primary" @click.stop="handleEvaluate">去评价</button>
</template>
<!-- 已评价 -->
<template v-else-if="order.status === 6">
<button class="action-btn btn-outline" @click.stop="handleDetail">查看评价</button>
<button class="action-btn btn-primary" @click.stop="handleRebuy">再来一单</button>
</template>
<!-- 已取消 -->
<template v-else-if="order.status === 9">
<button class="action-btn btn-outline" @click.stop="handleDelete">删除订单</button>
<button class="action-btn btn-primary" @click.stop="handleRebuy">再来一单</button>
</template>
</view>
<!-- 霓虹发光效果 -->
<view class="scan-line"></view>
</view>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
import { OrderStatus } from '../../mock/orders'
const props = defineProps({
order: {
type: Object,
required: true
}
})
const emit = defineEmits(['click', 'action'])
const handleClick = () => {
emit('click', props.order)
}
const handleAction = (action) => {
emit('action', { action, order: props.order })
}
const handleCancel = () => handleAction('cancel')
const handlePay = () => handleAction('pay')
const handleContact = () => handleAction('contact')
const handleDetail = () => handleAction('detail')
const handleConfirm = () => handleAction('confirm')
const handleEvaluate = () => handleAction('evaluate')
const handleRefund = () => handleAction('refund')
const handleRebuy = () => handleAction('rebuy')
const handleDelete = () => handleAction('delete')
const getStatusColor = (status) => {
const statusMap = {
0: 'rgba(255, 170, 0, 0.2)',
1: 'rgba(0, 255, 255, 0.2)',
2: 'rgba(0, 255, 136, 0.2)',
3: 'rgba(0, 153, 255, 0.2)',
4: 'rgba(157, 0, 255, 0.2)',
5: 'rgba(0, 255, 136, 0.2)',
6: 'rgba(122, 126, 157, 0.2)',
9: 'rgba(255, 51, 102, 0.2)'
}
return statusMap[status] || 'rgba(0, 255, 255, 0.2)'
}
const getStatusText = (status) => {
const statusObj = Object.values(OrderStatus).find(s => s.code === status)
return statusObj ? statusObj.text : '未知'
}
const formatTime = (time) => {
return time.substring(0, 16)
}
</script>
<style lang="scss" scoped>
.order-card {
margin-bottom: 24rpx;
padding: 24rpx;
background: linear-gradient(135deg, rgba(20, 25, 50, 0.95) 0%, rgba(10, 14, 39, 0.9) 100%);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 16rpx;
position: relative;
overflow: hidden;
}
.order-header {
margin-bottom: 20rpx;
}
.order-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.order-no {
font-size: 26rpx;
color: #a0a4c4;
}
.status-badge {
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: bold;
color: #ffffff;
backdrop-filter: blur(10rpx);
}
.order-time {
font-size: 24rpx;
color: #7a7e9d;
}
.service-info {
display: flex;
gap: 20rpx;
margin-bottom: 24rpx;
}
.service-cover {
width: 160rpx;
height: 120rpx;
border-radius: 12rpx;
border: 1px solid rgba(0, 255, 255, 0.2);
}
.service-detail {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.service-name {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.category-tag {
display: inline-block;
padding: 4rpx 12rpx;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 6rpx;
font-size: 22rpx;
color: #00ffff;
align-self: flex-start;
}
.player-info {
display: flex;
align-items: center;
gap: 12rpx;
}
.player-avatar {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 1px solid rgba(0, 255, 255, 0.3);
}
.player-name {
font-size: 24rpx;
color: #a0a4c4;
}
.online-dot {
width: 12rpx;
height: 12rpx;
color: #00ff88;
font-size: 20rpx;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.price-info {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
}
.price-value {
font-size: 36rpx;
font-weight: bold;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
}
.action-btn {
padding: 14rpx 32rpx;
border-radius: 8rpx;
font-size: 26rpx;
font-weight: bold;
border: none;
transition: all 0.3s ease;
&.btn-primary {
background: linear-gradient(135deg, #00ffff 0%, #0099ff 100%);
color: #0a0e27;
}
&.btn-outline {
background: transparent;
border: 1px solid #00ffff;
color: #00ffff;
}
&.btn-cancel {
background: rgba(122, 126, 157, 0.2);
border: 1px solid rgba(122, 126, 157, 0.4);
color: #7a7e9d;
}
&:active {
transform: scale(0.95);
}
}
.scan-line {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent 0%, rgba(0, 255, 255, 0.6) 50%, transparent 100%);
animation: scan 3s infinite;
}
@keyframes scan {
0%, 100% {
opacity: 0;
transform: translateY(0);
}
50% {
opacity: 1;
transform: translateY(200rpx);
}
}
</style>

View File

@ -0,0 +1,304 @@
<template>
<view class="service-card cyber-card" @click="handleClick">
<!-- 封面图片 -->
<view class="card-cover">
<image :src="service.coverImage" mode="aspectFill" class="cover-image" />
<!-- 渐变遮罩 -->
<view class="cover-overlay"></view>
<!-- 热门标签 -->
<view v-if="service.salesCount > 1000" class="hot-badge">
<text class="hot-icon">🔥</text>
<text class="hot-text">HOT</text>
</view>
<!-- 分类标签 -->
<view class="category-tag">
{{ service.categoryName }}
</view>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<!-- 服务名称 -->
<view class="service-name">{{ service.name }}</view>
<!-- 服务描述 -->
<view class="service-desc">{{ service.description }}</view>
<!-- 标签 -->
<view class="tags-container">
<view v-for="tag in service.tags" :key="tag" class="tag-item">
{{ tag }}
</view>
</view>
<!-- 商家信息 -->
<view class="merchant-info">
<image :src="service.merchantAvatar" class="merchant-avatar" />
<text class="merchant-name">{{ service.merchantName }}</text>
<text class="player-count">{{ service.playerCount }}位选手</text>
</view>
<!-- 底部信息 -->
<view class="card-footer">
<view class="price-container">
<text class="price-symbol">¥</text>
<text class="price-value">{{ service.price }}</text>
<text v-if="service.originalPrice > service.price" class="original-price">
¥{{ service.originalPrice }}
</text>
</view>
<view class="stats-container">
<view class="stat-item">
<text class="stat-icon"></text>
<text class="stat-text">{{ service.rating }}</text>
</view>
<view class="stat-item">
<text class="stat-icon">📦</text>
<text class="stat-text">{{ formatSales(service.salesCount) }}</text>
</view>
</view>
</view>
</view>
<!-- 霓虹边框效果 -->
<view class="neon-border"></view>
</view>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
service: {
type: Object,
required: true
}
})
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click', props.service)
}
const formatSales = (count) => {
if (count >= 10000) {
return (count / 10000).toFixed(1) + 'w'
} else if (count >= 1000) {
return (count / 1000).toFixed(1) + 'k'
}
return count
}
</script>
<style lang="scss" scoped>
.service-card {
margin-bottom: 24rpx;
background: linear-gradient(135deg, rgba(20, 25, 50, 0.95) 0%, rgba(10, 14, 39, 0.9) 100%);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 16rpx;
overflow: hidden;
position: relative;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
border-color: rgba(0, 255, 255, 0.5);
}
}
.card-cover {
position: relative;
width: 100%;
height: 360rpx;
overflow: hidden;
}
.cover-image {
width: 100%;
height: 100%;
}
.cover-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: linear-gradient(180deg, transparent 0%, rgba(10, 14, 39, 0.9) 100%);
}
.hot-badge {
position: absolute;
top: 20rpx;
right: 20rpx;
display: flex;
align-items: center;
padding: 8rpx 16rpx;
background: linear-gradient(135deg, rgba(255, 107, 107, 0.9) 0%, rgba(255, 71, 71, 0.9) 100%);
border-radius: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.4);
}
.hot-icon {
font-size: 24rpx;
margin-right: 4rpx;
}
.hot-text {
font-size: 24rpx;
font-weight: bold;
color: #ffffff;
}
.category-tag {
position: absolute;
top: 20rpx;
left: 20rpx;
padding: 8rpx 16rpx;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 0.4);
border-radius: 8rpx;
backdrop-filter: blur(10rpx);
font-size: 24rpx;
color: #00ffff;
}
.card-content {
padding: 24rpx;
}
.service-name {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.service-desc {
font-size: 26rpx;
color: #a0a4c4;
margin-bottom: 16rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 20rpx;
}
.tag-item {
padding: 6rpx 16rpx;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 6rpx;
font-size: 22rpx;
color: #00ffff;
}
.merchant-info {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.merchant-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 12rpx;
border: 1px solid rgba(0, 255, 255, 0.3);
}
.merchant-name {
font-size: 24rpx;
color: #a0a4c4;
flex: 1;
}
.player-count {
font-size: 22rpx;
color: #7a7e9d;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.price-container {
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: 28rpx;
font-weight: bold;
color: #00ffff;
}
.price-value {
font-size: 40rpx;
font-weight: bold;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
margin-right: 12rpx;
}
.original-price {
font-size: 24rpx;
color: #7a7e9d;
text-decoration: line-through;
}
.stats-container {
display: flex;
gap: 24rpx;
}
.stat-item {
display: flex;
align-items: center;
gap: 6rpx;
}
.stat-icon {
font-size: 24rpx;
}
.stat-text {
font-size: 24rpx;
color: #a0a4c4;
}
.neon-border {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 16rpx;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
box-shadow:
0 0 20rpx rgba(0, 255, 255, 0.3),
0 0 40rpx rgba(0, 255, 255, 0.2),
inset 0 0 20rpx rgba(0, 255, 255, 0.1);
}
.service-card:active .neon-border {
opacity: 1;
}
</style>

16
index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>电竞服务平台</title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

15
main.js Normal file
View File

@ -0,0 +1,15 @@
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
return {
app,
pinia
}
}

27
manifest.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "电竞服务平台",
"appid": "__UNI__GAME_SERVICE",
"description": "游戏服务交易平台 - 电竞赛博朋克风格",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true,
"minified": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于查找附近的游戏服务"
}
}
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3"
}

79
mock/categories.js Normal file
View File

@ -0,0 +1,79 @@
/**
* 游戏分类数据
*/
export const gameCategories = [
{
id: 1,
name: '王者荣耀',
icon: '🎮',
color: '#ff6b6b',
hot: true,
serviceCount: 156
},
{
id: 2,
name: '英雄联盟',
icon: '⚔️',
color: '#4ecdc4',
hot: true,
serviceCount: 98
},
{
id: 3,
name: 'VALORANT',
icon: '🔫',
color: '#a8dadc',
hot: false,
serviceCount: 45
},
{
id: 4,
name: 'CSGO',
icon: '🎯',
color: '#f4a261',
hot: true,
serviceCount: 67
},
{
id: 5,
name: 'DOTA2',
icon: '🛡️',
color: '#e76f51',
hot: false,
serviceCount: 34
},
{
id: 6,
name: '原神',
icon: '✨',
color: '#2a9d8f',
hot: true,
serviceCount: 89
},
{
id: 7,
name: '和平精英',
icon: '🔥',
color: '#e63946',
hot: false,
serviceCount: 112
},
{
id: 8,
name: '永劫无间',
icon: '⚡',
color: '#457b9d',
hot: false,
serviceCount: 23
}
]
/**
* 服务类型
*/
export const serviceTypes = [
{ id: 1, name: '上分代练', icon: '📈' },
{ id: 2, name: '娱乐陪玩', icon: '🎵' },
{ id: 3, name: '技术教学', icon: '📚' },
{ id: 4, name: '账号租赁', icon: '🎭' }
]

144
mock/evaluations.js Normal file
View File

@ -0,0 +1,144 @@
/**
* 评价数据
*/
export const evaluations = [
{
id: 1,
orderId: 100003,
serviceId: 2002,
serviceName: 'LOL 娱乐陪玩 御姐音',
playerId: 30018,
playerName: '冰霜女王',
customerId: 20001,
customerName: '电竞少年',
customerAvatar: 'https://picsum.photos/100/100?random=301',
rating: 5,
content: '小姐姐声音真的超好听!技术也很好,打得很开心!五星好评!',
images: [],
isAnonymous: false,
reply: '谢谢小哥哥的好评~下次继续约我哦💕',
replyTime: '2025-01-26 21:00:00',
createTime: '2025-01-26 20:35:00'
},
{
id: 2,
orderId: 100006,
serviceId: 1002,
serviceName: '王者荣耀 娱乐陪玩 甜美女声',
playerId: 30005,
playerName: '甜心小鹿',
customerId: 20002,
customerName: '匿名用户',
customerAvatar: 'https://picsum.photos/100/100?random=302',
rating: 5,
content: '甜心小姐姐真的太温柔了,声音超甜,还教我玩法师,超级耐心!强烈推荐!',
images: [
'https://picsum.photos/400/300?random=61',
'https://picsum.photos/400/300?random=62'
],
isAnonymous: true,
reply: '谢谢支持呀~很开心能帮到你🦌',
replyTime: '2025-01-25 19:30:00',
createTime: '2025-01-25 18:45:00'
},
{
id: 3,
orderId: 100007,
serviceId: 1003,
serviceName: '王者荣耀 一对一教学 国服射手',
playerId: 30012,
playerName: '狙击之王',
customerId: 20003,
customerName: '游戏新手',
customerAvatar: 'https://picsum.photos/100/100?random=303',
rating: 5,
content: '教练真的专业!我的后羿从青铜打法变成了钻石水平,讲解很细致,物超所值!',
images: [
'https://picsum.photos/400/300?random=63'
],
isAnonymous: false,
reply: '加油!继续努力,下个赛季冲王者!',
replyTime: '2025-01-24 16:20:00',
createTime: '2025-01-24 15:55:00'
},
{
id: 4,
orderId: 100008,
serviceId: 6001,
serviceName: '原神 深渊12层满星代打',
playerId: 30025,
playerName: '璃月大佬',
customerId: 20004,
customerName: '旅行者',
customerAvatar: 'https://picsum.photos/100/100?random=304',
rating: 4,
content: '代打很快,半小时就满星了,就是没看到过程有点遗憾',
images: [],
isAnonymous: false,
reply: '下次可以提前说要录屏哦~',
replyTime: '2025-01-23 14:30:00',
createTime: '2025-01-23 14:15:00'
},
{
id: 5,
orderId: 100009,
serviceId: 2002,
serviceName: 'LOL 娱乐陪玩 御姐音',
playerId: 30018,
playerName: '冰霜女王',
customerId: 20005,
customerName: '召唤师',
customerAvatar: 'https://picsum.photos/100/100?random=305',
rating: 5,
content: '御姐音太棒了!而且辅助玩得真好,这局躺赢!',
images: [],
isAnonymous: false,
reply: '谢谢夸奖~继续努力💪',
replyTime: '2025-01-22 21:45:00',
createTime: '2025-01-22 21:30:00'
}
]
/**
* 根据服务ID获取评价列表
*/
export function getEvaluationsByServiceId(serviceId) {
return evaluations.filter(e => e.serviceId === serviceId)
}
/**
* 根据选手ID获取评价列表
*/
export function getEvaluationsByPlayerId(playerId) {
return evaluations.filter(e => e.playerId === playerId)
}
/**
* 评价统计
*/
export function getEvaluationStats(serviceId) {
const evals = getEvaluationsByServiceId(serviceId)
const total = evals.length
if (total === 0) {
return {
total: 0,
avgRating: 5.0,
distribution: { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }
}
}
const distribution = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }
let sum = 0
evals.forEach(e => {
distribution[e.rating]++
sum += e.rating
})
return {
total,
avgRating: (sum / total).toFixed(2),
distribution
}
}

29
mock/index.js Normal file
View File

@ -0,0 +1,29 @@
/**
* Mock数据统一导出
*/
export * from './categories'
export * from './services'
export * from './orders'
export * from './user'
export * from './players'
export * from './evaluations'
/**
* 模拟API延迟
*/
export function mockDelay(ms = 500) {
return new Promise(resolve => setTimeout(resolve, ms))
}
/**
* 模拟API响应
*/
export async function mockApiResponse(data, delay = 500) {
await mockDelay(delay)
return {
code: 200,
msg: 'success',
data
}
}

222
mock/orders.js Normal file
View File

@ -0,0 +1,222 @@
/**
* 订单数据
*/
// 订单状态枚举
export const OrderStatus = {
WAIT_PAY: { code: 0, text: '待支付', color: '#ffaa00' },
WAIT_ACCEPT: { code: 1, text: '待接单', color: '#00ffff' },
ACCEPTED: { code: 2, text: '已接单', color: '#00ff88' },
IN_PROGRESS: { code: 3, text: '进行中', color: '#0099ff' },
WAIT_CONFIRM: { code: 4, text: '待确认', color: '#9d00ff' },
COMPLETED: { code: 5, text: '已完成', color: '#00ff88' },
EVALUATED: { code: 6, text: '已评价', color: '#7a7e9d' },
CANCELLED: { code: 9, text: '已取消', color: '#ff3366' }
}
/**
* 订单列表
*/
export const orders = [
{
id: 100001,
orderNo: 'ORD202501281234567',
tenantId: 10001,
customerId: 20001,
serviceId: 1002,
serviceName: '王者荣耀 娱乐陪玩 甜美女声',
serviceCover: 'https://picsum.photos/400/300?random=2',
categoryName: '王者荣耀',
price: 35.00,
actualPrice: 35.00,
status: 3, // 进行中
playerId: 30005,
playerName: '甜心小鹿',
playerAvatar: 'https://picsum.photos/100/100?random=201',
merchantName: '星光陪玩',
gameInfo: {
gameId: 'Sweet_Deer_123',
server: '微信区',
rank: '钻石II'
},
contactInfo: {
qq: '123456789',
wechat: 'sweetdeer'
},
remark: '希望小姐姐温柔一点~',
payType: 'wechat',
payTime: '2025-01-28 14:30:00',
acceptTime: '2025-01-28 14:32:00',
startTime: '2025-01-28 14:35:00',
createTime: '2025-01-28 14:28:00'
},
{
id: 100002,
orderNo: 'ORD202501271234568',
tenantId: 10002,
customerId: 20001,
serviceId: 1003,
serviceName: '王者荣耀 一对一教学 国服射手',
serviceCover: 'https://picsum.photos/400/300?random=3',
categoryName: '王者荣耀',
price: 88.00,
actualPrice: 88.00,
status: 5, // 已完成
playerId: 30012,
playerName: '狙击之王',
playerAvatar: 'https://picsum.photos/100/100?random=202',
merchantName: '巅峰电竞学院',
gameInfo: {
gameId: 'Sniper_King_Pro',
server: 'QQ区',
rank: '荣耀王者'
},
remark: '想学后羿和公孙离',
payType: 'wechat',
payTime: '2025-01-27 10:15:00',
acceptTime: '2025-01-27 10:20:00',
startTime: '2025-01-27 10:30:00',
finishTime: '2025-01-27 12:30:00',
confirmTime: '2025-01-27 12:35:00',
createTime: '2025-01-27 10:10:00'
},
{
id: 100003,
orderNo: 'ORD202501261234569',
tenantId: 10003,
customerId: 20001,
serviceId: 2002,
serviceName: 'LOL 娱乐陪玩 御姐音',
serviceCover: 'https://picsum.photos/400/300?random=5',
categoryName: '英雄联盟',
price: 45.00,
actualPrice: 45.00,
status: 6, // 已评价
playerId: 30018,
playerName: '冰霜女王',
playerAvatar: 'https://picsum.photos/100/100?random=203',
merchantName: '梦幻陪玩',
gameInfo: {
gameId: 'FrostQueen',
server: '艾欧尼亚',
rank: '钻石I'
},
remark: '想玩辅助',
evaluation: {
rating: 5,
content: '小姐姐声音真的超好听!技术也很好,打得很开心!',
images: [],
createTime: '2025-01-26 20:35:00'
},
payType: 'wechat',
payTime: '2025-01-26 19:00:00',
acceptTime: '2025-01-26 19:05:00',
startTime: '2025-01-26 19:10:00',
finishTime: '2025-01-26 20:10:00',
confirmTime: '2025-01-26 20:15:00',
createTime: '2025-01-26 18:55:00'
},
{
id: 100004,
orderNo: 'ORD202501251234570',
tenantId: 10005,
customerId: 20001,
serviceId: 6001,
serviceName: '原神 深渊12层满星代打',
serviceCover: 'https://picsum.photos/400/300?random=8',
categoryName: '原神',
price: 68.00,
actualPrice: 68.00,
status: 4, // 待确认
playerId: 30025,
playerName: '璃月大佬',
playerAvatar: 'https://picsum.photos/100/100?random=204',
merchantName: '提瓦特工作室',
gameInfo: {
uid: '123456789',
server: '天空岛',
level: 60
},
serviceFiles: [
'https://picsum.photos/400/300?random=51',
'https://picsum.photos/400/300?random=52'
],
remark: '账号密码:test123',
payType: 'wechat',
payTime: '2025-01-25 15:20:00',
acceptTime: '2025-01-25 15:25:00',
startTime: '2025-01-25 15:30:00',
createTime: '2025-01-25 15:18:00'
},
{
id: 100005,
orderNo: 'ORD202501241234571',
tenantId: 10001,
customerId: 20001,
serviceId: 1001,
serviceName: '王者荣耀 星耀→王者 上分',
serviceCover: 'https://picsum.photos/400/300?random=1',
categoryName: '王者荣耀',
price: 198.00,
actualPrice: 198.00,
status: 1, // 待接单
merchantName: '电竞之星工作室',
gameInfo: {
gameId: 'Player_Pro_888',
server: '微信区',
currentRank: '星耀II',
targetRank: '王者'
},
remark: '希望3天内完成',
payType: 'wechat',
payTime: '2025-01-24 09:30:00',
createTime: '2025-01-24 09:25:00'
}
]
/**
* 根据状态获取订单数量
*/
export function getOrderCountByStatus(status) {
if (status === 'all') return orders.length
return orders.filter(o => o.status === status).length
}
/**
* 根据状态获取订单列表
*/
export function getOrdersByStatus(status) {
if (status === 'all') return orders
return orders.filter(o => o.status === status)
}
/**
* 根据ID获取订单详情
*/
export function getOrderById(id) {
return orders.find(o => o.id === id)
}
/**
* 订单流转记录
*/
export function getOrderFlowById(orderId) {
// 模拟订单流转记录
const flows = {
100001: [
{ time: '2025-01-28 14:28:00', status: '创建订单', desc: '订单创建成功' },
{ time: '2025-01-28 14:30:00', status: '支付完成', desc: '微信支付成功' },
{ time: '2025-01-28 14:32:00', status: '选手接单', desc: '甜心小鹿 接单' },
{ time: '2025-01-28 14:35:00', status: '开始服务', desc: '服务进行中...' }
],
100002: [
{ time: '2025-01-27 10:10:00', status: '创建订单', desc: '订单创建成功' },
{ time: '2025-01-27 10:15:00', status: '支付完成', desc: '微信支付成功' },
{ time: '2025-01-27 10:20:00', status: '选手接单', desc: '狙击之王 接单' },
{ time: '2025-01-27 10:30:00', status: '开始服务', desc: '开始一对一教学' },
{ time: '2025-01-27 12:30:00', status: '服务完成', desc: '选手提交服务资料' },
{ time: '2025-01-27 12:35:00', status: '确认完成', desc: '顾客确认服务完成' }
]
}
return flows[orderId] || []
}

99
mock/players.js Normal file
View File

@ -0,0 +1,99 @@
/**
* 选手数据
*/
export const players = [
{
id: 30005,
tenantId: 10001,
name: '甜心小鹿',
avatar: 'https://picsum.photos/100/100?random=201',
gender: 'female',
age: 22,
gameId: 'Sweet_Deer_123',
level: '钻石II',
rating: 4.96,
orderCount: 3456,
completeCount: 3234,
tags: ['甜美', '温柔', '技术好'],
intro: '温柔小姐姐,陪你开心玩游戏~',
voiceSample: '',
status: 'online',
games: [
{ name: '王者荣耀', rank: '钻石II', winRate: 65 },
{ name: '英雄联盟', rank: '黄金I', winRate: 58 }
]
},
{
id: 30012,
tenantId: 10002,
name: '狙击之王',
avatar: 'https://picsum.photos/100/100?random=202',
gender: 'male',
age: 25,
gameId: 'Sniper_King_Pro',
level: '荣耀王者',
rating: 5.0,
orderCount: 567,
completeCount: 556,
tags: ['国服', '射手', '教学'],
intro: '前职业选手,专注射手教学3年',
status: 'busy',
games: [
{ name: '王者荣耀', rank: '荣耀王者', winRate: 78 }
]
},
{
id: 30018,
tenantId: 10003,
name: '冰霜女王',
avatar: 'https://picsum.photos/100/100?random=203',
gender: 'female',
age: 24,
gameId: 'FrostQueen',
level: '钻石I',
rating: 4.92,
orderCount: 2234,
completeCount: 2156,
tags: ['御姐', '技术', 'LOL'],
intro: '成熟御姐音,带你轻松上分',
status: 'online',
games: [
{ name: '英雄联盟', rank: '钻石I', winRate: 68 },
{ name: 'VALORANT', rank: '白金III', winRate: 62 }
]
},
{
id: 30025,
tenantId: 10005,
name: '璃月大佬',
avatar: 'https://picsum.photos/100/100?random=204',
gender: 'male',
age: 23,
gameId: 'Liyue_Master',
level: '满级',
rating: 4.97,
orderCount: 1567,
completeCount: 1534,
tags: ['满命', '深渊', '快速'],
intro: '原神全角色满命,深渊专家',
status: 'online',
games: [
{ name: '原神', rank: '满级', winRate: 100 }
]
}
]
/**
* 根据ID获取选手信息
*/
export function getPlayerById(id) {
return players.find(p => p.id === id)
}
/**
* 根据服务ID获取可用选手列表
*/
export function getPlayersByServiceId(serviceId) {
// 简化处理:返回前3个选手
return players.slice(0, 3)
}

235
mock/services.js Normal file
View File

@ -0,0 +1,235 @@
/**
* 服务/商品数据
*/
export const services = [
// 王者荣耀服务
{
id: 1001,
tenantId: 10001,
categoryId: 1,
categoryName: '王者荣耀',
name: '王者荣耀 星耀→王者 上分',
coverImage: 'https://picsum.photos/400/300?random=1',
price: 198.00,
originalPrice: 298.00,
description: '专业代练,3天快速上分,保证胜率,安全可靠',
serviceTime: 180,
salesCount: 1245,
rating: 4.9,
reviewCount: 856,
tags: ['热门', '高胜率', '快速'],
merchantName: '电竞之星工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=101',
playerCount: 15,
status: 0
},
{
id: 1002,
tenantId: 10001,
categoryId: 1,
categoryName: '王者荣耀',
name: '王者荣耀 娱乐陪玩 甜美女声',
coverImage: 'https://picsum.photos/400/300?random=2',
price: 35.00,
originalPrice: 50.00,
description: '温柔小姐姐陪你开黑,声音甜美,技术在线',
serviceTime: 60,
salesCount: 3456,
rating: 4.95,
reviewCount: 2134,
tags: ['甜美', '高评分', '在线'],
merchantName: '星光陪玩',
merchantAvatar: 'https://picsum.photos/100/100?random=102',
playerCount: 28,
status: 0
},
{
id: 1003,
tenantId: 10002,
categoryId: 1,
categoryName: '王者荣耀',
name: '王者荣耀 一对一教学 国服射手',
coverImage: 'https://picsum.photos/400/300?random=3',
price: 88.00,
originalPrice: 120.00,
description: '前职业选手教学,射手专精,助你快速提升',
serviceTime: 120,
salesCount: 567,
rating: 5.0,
reviewCount: 445,
tags: ['专业', '国服', '教学'],
merchantName: '巅峰电竞学院',
merchantAvatar: 'https://picsum.photos/100/100?random=103',
playerCount: 8,
status: 0
},
// 英雄联盟服务
{
id: 2001,
tenantId: 10001,
categoryId: 2,
categoryName: '英雄联盟',
name: 'LOL 黄金→钻石 上分代练',
coverImage: 'https://picsum.photos/400/300?random=4',
price: 288.00,
originalPrice: 399.00,
description: '大师级代练,胜率保证75%+,安全稳定',
serviceTime: 240,
salesCount: 892,
rating: 4.8,
reviewCount: 678,
tags: ['高胜率', '大师', '稳定'],
merchantName: '电竞之星工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=101',
playerCount: 12,
status: 0
},
{
id: 2002,
tenantId: 10003,
categoryId: 2,
categoryName: '英雄联盟',
name: 'LOL 娱乐陪玩 御姐音',
coverImage: 'https://picsum.photos/400/300?random=5',
price: 45.00,
originalPrice: 60.00,
description: '成熟御姐音,实力钻石,陪你轻松上分',
serviceTime: 60,
salesCount: 2234,
rating: 4.92,
reviewCount: 1567,
tags: ['御姐', '钻石', '热门'],
merchantName: '梦幻陪玩',
merchantAvatar: 'https://picsum.photos/100/100?random=104',
playerCount: 35,
status: 0
},
// VALORANT服务
{
id: 3001,
tenantId: 10002,
categoryId: 3,
categoryName: 'VALORANT',
name: 'VALORANT 白金→钻石 快速上分',
coverImage: 'https://picsum.photos/400/300?random=6',
price: 328.00,
originalPrice: 450.00,
description: '职业战队选手,VALORANT国际赛经验',
serviceTime: 300,
salesCount: 234,
rating: 4.95,
reviewCount: 189,
tags: ['职业', '国际赛', '快速'],
merchantName: '巅峰电竞学院',
merchantAvatar: 'https://picsum.photos/100/100?random=103',
playerCount: 6,
status: 0
},
// CSGO服务
{
id: 4001,
tenantId: 10004,
categoryId: 4,
categoryName: 'CSGO',
name: 'CSGO 黄金→老鹰 代练',
coverImage: 'https://picsum.photos/400/300?random=7',
price: 258.00,
originalPrice: 350.00,
description: '前CNCS职业选手,枪法精准,意识一流',
serviceTime: 180,
salesCount: 445,
rating: 4.88,
reviewCount: 334,
tags: ['职业', 'CNCS', '精准'],
merchantName: 'FPS大师工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=105',
playerCount: 10,
status: 0
},
// 原神服务
{
id: 6001,
tenantId: 10005,
categoryId: 6,
categoryName: '原神',
name: '原神 深渊12层满星代打',
coverImage: 'https://picsum.photos/400/300?random=8',
price: 68.00,
originalPrice: 88.00,
description: '满命大佬,深渊12层一次性满星',
serviceTime: 90,
salesCount: 1567,
rating: 4.97,
reviewCount: 1234,
tags: ['满命', '深渊', '快速'],
merchantName: '提瓦特工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=106',
playerCount: 18,
status: 0
},
{
id: 6002,
tenantId: 10005,
categoryId: 6,
categoryName: '原神',
name: '原神 每日委托代做',
coverImage: 'https://picsum.photos/400/300?random=9',
price: 15.00,
originalPrice: 20.00,
description: '每日委托快速完成,解放双手',
serviceTime: 30,
salesCount: 3456,
rating: 4.85,
reviewCount: 2567,
tags: ['日常', '便宜', '快速'],
merchantName: '提瓦特工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=106',
playerCount: 25,
status: 0
},
// 和平精英服务
{
id: 7001,
tenantId: 10006,
categoryId: 7,
categoryName: '和平精英',
name: '和平精英 钻石→皇冠 上分',
coverImage: 'https://picsum.photos/400/300?random=10',
price: 188.00,
originalPrice: 258.00,
description: '职业战队退役选手,吃鸡率80%+',
serviceTime: 150,
salesCount: 789,
rating: 4.91,
reviewCount: 567,
tags: ['职业', '高吃鸡', '稳定'],
merchantName: '绝地求生工作室',
merchantAvatar: 'https://picsum.photos/100/100?random=107',
playerCount: 14,
status: 0
}
]
/**
* 热门推荐服务
*/
export const hotServices = services.filter(s => s.salesCount > 1000).slice(0, 6)
/**
* 根据分类获取服务
*/
export function getServicesByCategory(categoryId) {
return services.filter(s => s.categoryId === categoryId)
}
/**
* 根据ID获取服务详情
*/
export function getServiceById(id) {
return services.find(s => s.id === id)
}

68
mock/user.js Normal file
View File

@ -0,0 +1,68 @@
/**
* 用户数据
*/
export const currentUser = {
id: 20001,
openid: 'oXYZ123456789',
nickname: '电竞少年',
avatar: 'https://picsum.photos/100/100?random=301',
phone: '138****8888',
vipLevel: 2,
vipName: '黄金会员',
points: 2580,
balance: 156.50,
orderCount: 28,
evaluateCount: 15,
createTime: '2024-08-15 10:30:00',
lastLoginTime: '2025-01-28 14:00:00'
}
/**
* 用户统计数据
*/
export const userStats = {
orderCount: {
all: 28,
waitPay: 0,
waitAccept: 1,
inProgress: 1,
waitConfirm: 1,
completed: 25
},
favoriteCount: 12,
couponCount: 3,
messageCount: 5
}
/**
* 我的优惠券
*/
export const coupons = [
{
id: 1,
name: '新人专享券',
type: 'discount',
value: 20,
minAmount: 50,
expireTime: '2025-02-28',
status: 'available'
},
{
id: 2,
name: '满100减30',
type: 'cash',
value: 30,
minAmount: 100,
expireTime: '2025-03-15',
status: 'available'
},
{
id: 3,
name: '8.8折优惠券',
type: 'discount',
value: 12,
minAmount: 80,
expireTime: '2025-01-20',
status: 'expired'
}
]

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "game-service-miniapp",
"version": "1.0.0",
"description": "游戏服务交易平台 - 电竞赛博朋克风格",
"main": "main.js",
"scripts": {
"dev:h5": "uni",
"build:h5": "uni build",
"dev:mp-weixin": "uni -p mp-weixin",
"build:mp-weixin": "uni build -p mp-weixin"
},
"keywords": ["uni-app", "游戏服务", "赛博朋克"],
"author": "",
"license": "MIT",
"dependencies": {
"vue": "^3.2.47",
"pinia": "^2.1.3"
},
"devDependencies": {
"@dcloudio/uni-app": "^3.0.0",
"@dcloudio/uni-h5": "^3.0.0",
"@dcloudio/uni-mp-weixin": "^3.0.0",
"@dcloudio/vite-plugin-uni": "^3.0.0",
"vite": "^4.3.9",
"sass": "^1.62.1",
"sass-loader": "^13.2.2"
}
}

103
pages.json Normal file
View File

@ -0,0 +1,103 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^service-card": "@/components/service-card/index.vue",
"^order-card": "@/components/order-card/index.vue",
"^game-category": "@/components/game-category/index.vue"
}
},
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "电竞服务",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
}
},
{
"path": "pages/category/list",
"style": {
"navigationBarTitleText": "服务列表",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/service/detail",
"style": {
"navigationBarTitleText": "服务详情",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/order/create",
"style": {
"navigationBarTitleText": "下单确认",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/order/list",
"style": {
"navigationBarTitleText": "我的订单",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/order/detail",
"style": {
"navigationBarTitleText": "订单详情",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "个人中心",
"navigationBarBackgroundColor": "#0a0e27",
"navigationBarTextStyle": "white"
}
}
],
"tabBar": {
"color": "#7a7e9d",
"selectedColor": "#00ffff",
"backgroundColor": "#0a0e27",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/icons/home.png",
"selectedIconPath": "static/icons/home-active.png"
},
{
"pagePath": "pages/order/list",
"text": "订单",
"iconPath": "static/icons/order.png",
"selectedIconPath": "static/icons/order-active.png"
},
{
"pagePath": "pages/user/index",
"text": "我的",
"iconPath": "static/icons/user.png",
"selectedIconPath": "static/icons/user-active.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "电竞服务平台",
"navigationBarBackgroundColor": "#0a0e27",
"backgroundColor": "#0f1229",
"backgroundColorTop": "#0a0e27",
"backgroundColorBottom": "#0f1229"
}
}

190
pages/category/list.vue Normal file
View File

@ -0,0 +1,190 @@
<template>
<view class="category-list-page page-container">
<!-- 筛选栏 -->
<view class="filter-bar">
<scroll-view class="filter-scroll" scroll-x show-scrollbar="false">
<view class="filter-tags">
<view
v-for="(item, index) in filters"
:key="index"
class="filter-tag"
:class="{ active: currentFilter === index }"
@click="changeFilter(index)"
>
{{ item }}
</view>
</view>
</scroll-view>
<view class="sort-btn" @click="toggleSort">
<text class="sort-icon"></text>
<text class="sort-text">{{ sortText }}</text>
</view>
</view>
<!-- 服务列表 -->
<view class="services-list">
<service-card
v-for="service in filteredServices"
:key="service.id"
:service="service"
@click="goServiceDetail"
/>
</view>
<!-- 空状态 -->
<view v-if="filteredServices.length === 0" class="empty-state">
<text class="empty-icon">📦</text>
<text class="empty-text">暂无服务</text>
</view>
</view>
</template>
<script setup>
import { ref, computed, onLoad } from '@dcloudio/uni-app'
import { services, getServicesByCategory } from '../../mock'
const categoryId = ref(0)
const categoryName = ref('')
const currentFilter = ref(0)
const currentSort = ref(0)
const filters = ['全部', '代练', '陪玩', '教学']
const sortOptions = ['默认', '价格', '销量', '评分']
const sortText = computed(() => sortOptions[currentSort.value])
const allServices = ref([])
const filteredServices = computed(() => {
let result = allServices.value
//
if (currentFilter.value > 0) {
const filterMap = { 1: '代练', 2: '陪玩', 3: '教学' }
const keyword = filterMap[currentFilter.value]
result = result.filter(s => s.name.includes(keyword))
}
//
if (currentSort.value === 1) {
result = [...result].sort((a, b) => a.price - b.price)
} else if (currentSort.value === 2) {
result = [...result].sort((a, b) => b.salesCount - a.salesCount)
} else if (currentSort.value === 3) {
result = [...result].sort((a, b) => b.rating - a.rating)
}
return result
})
onLoad((options) => {
if (options.categoryId) {
categoryId.value = parseInt(options.categoryId)
categoryName.value = options.categoryName || ''
allServices.value = getServicesByCategory(categoryId.value)
} else {
allServices.value = services
}
uni.setNavigationBarTitle({
title: categoryName.value || '服务列表'
})
})
const changeFilter = (index) => {
currentFilter.value = index
}
const toggleSort = () => {
currentSort.value = (currentSort.value + 1) % sortOptions.length
}
const goServiceDetail = (service) => {
uni.navigateTo({
url: `/pages/service/detail?id=${service.id}`
})
}
</script>
<style lang="scss" scoped>
.category-list-page {
padding: 24rpx;
}
.filter-bar {
display: flex;
gap: 20rpx;
margin-bottom: 28rpx;
}
.filter-scroll {
flex: 1;
white-space: nowrap;
}
.filter-tags {
display: inline-flex;
gap: 16rpx;
}
.filter-tag {
display: inline-block;
padding: 12rpx 32rpx;
background: rgba(20, 25, 50, 0.9);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 50rpx;
font-size: 26rpx;
color: #a0a4c4;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #00ffff 0%, #0099ff 100%);
color: #0a0e27;
border-color: transparent;
font-weight: bold;
}
}
.sort-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: rgba(20, 25, 50, 0.9);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 50rpx;
font-size: 26rpx;
color: #00ffff;
&:active {
transform: scale(0.95);
}
}
.sort-icon {
font-size: 28rpx;
}
.services-list {
display: flex;
flex-direction: column;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #7a7e9d;
}
</style>

368
pages/index/index.vue Normal file
View File

@ -0,0 +1,368 @@
<template>
<view class="index-page page-container">
<!-- 顶部搜索栏 -->
<view class="search-bar">
<view class="search-input" @click="goSearch">
<text class="search-icon">🔍</text>
<text class="search-placeholder">搜索游戏服务...</text>
</view>
<view class="notification-btn" @click="goNotification">
<text class="notif-icon">🔔</text>
<view v-if="hasUnread" class="unread-dot"></view>
</view>
</view>
<!-- 赛博朋克标题 -->
<view class="page-title">
<view class="title-line"></view>
<text class="title-text neon-text">GAMING SERVICES</text>
<view class="title-line"></view>
</view>
<!-- 游戏分类 -->
<view class="section">
<view class="section-header">
<text class="section-title">游戏分类</text>
<text class="section-more" @click="goAllCategories">全部 ></text>
</view>
<scroll-view class="categories-scroll" scroll-x show-scrollbar="false">
<view class="categories-container">
<game-category
v-for="category in categories"
:key="category.id"
:category="category"
:show-count="true"
@click="goCategory"
/>
</view>
</scroll-view>
</view>
<!-- 服务类型快捷入口 -->
<view class="service-types">
<view
v-for="type in serviceTypes"
:key="type.id"
class="type-item cyber-card"
@click="goServiceType(type)"
>
<text class="type-icon">{{ type.icon }}</text>
<text class="type-name">{{ type.name }}</text>
<view class="type-arrow"></view>
</view>
</view>
<!-- 热门推荐 -->
<view class="section">
<view class="section-header">
<text class="section-title">🔥 热门推荐</text>
<text class="section-subtitle">TRENDING NOW</text>
</view>
<view class="services-list">
<service-card
v-for="service in hotServices"
:key="service.id"
:service="service"
@click="goServiceDetail"
/>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loading" class="loading-container">
<view class="loading-dot"></view>
<view class="loading-dot"></view>
<view class="loading-dot"></view>
</view>
<!-- 底部装饰 -->
<view class="bottom-decoration">
<view class="decoration-line"></view>
<text class="decoration-text">// POWERED BY CYBER TECH //</text>
<view class="decoration-line"></view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { gameCategories, serviceTypes, hotServices } from '../../mock'
const categories = ref([])
const services = ref([])
const loading = ref(false)
const hasUnread = ref(true)
onMounted(() => {
loadData()
})
const loadData = async () => {
loading.value = true
//
setTimeout(() => {
categories.value = gameCategories
services.value = hotServices
loading.value = false
}, 500)
}
const goSearch = () => {
uni.navigateTo({
url: '/pages/index/search'
})
}
const goNotification = () => {
uni.showToast({
title: '暂无新消息',
icon: 'none'
})
}
const goAllCategories = () => {
uni.showToast({
title: '全部分类',
icon: 'none'
})
}
const goCategory = (category) => {
uni.navigateTo({
url: `/pages/category/list?categoryId=${category.id}&categoryName=${category.name}`
})
}
const goServiceType = (type) => {
uni.navigateTo({
url: `/pages/category/list?type=${type.id}&typeName=${type.name}`
})
}
const goServiceDetail = (service) => {
uni.navigateTo({
url: `/pages/service/detail?id=${service.id}`
})
}
</script>
<style lang="scss" scoped>
.index-page {
padding: 24rpx;
}
.search-bar {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 32rpx;
}
.search-input {
flex: 1;
display: flex;
align-items: center;
gap: 16rpx;
padding: 24rpx 32rpx;
background: linear-gradient(135deg, rgba(20, 25, 50, 0.9) 0%, rgba(10, 14, 39, 0.8) 100%);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 50rpx;
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
&:active {
border-color: rgba(0, 255, 255, 0.6);
box-shadow: 0 0 20rpx rgba(0, 255, 255, 0.3);
}
}
.search-icon {
font-size: 32rpx;
}
.search-placeholder {
font-size: 28rpx;
color: #7a7e9d;
}
.notification-btn {
position: relative;
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(20, 25, 50, 0.9) 0%, rgba(10, 14, 39, 0.8) 100%);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 50%;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
border-color: rgba(0, 255, 255, 0.6);
}
}
.notif-icon {
font-size: 36rpx;
}
.unread-dot {
position: absolute;
top: 18rpx;
right: 18rpx;
width: 16rpx;
height: 16rpx;
background: #ff3366;
border-radius: 50%;
border: 2px solid #0a0e27;
animation: pulse 2s infinite;
}
.page-title {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 40rpx;
}
.title-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.5) 50%,
transparent 100%
);
}
.title-text {
font-size: 32rpx;
font-weight: bold;
letter-spacing: 4rpx;
}
.section {
margin-bottom: 48rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 28rpx;
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
}
.section-subtitle {
font-size: 22rpx;
color: #7a7e9d;
letter-spacing: 2rpx;
margin-left: 16rpx;
}
.section-more {
font-size: 26rpx;
color: #00ffff;
&:active {
opacity: 0.7;
}
}
.categories-scroll {
white-space: nowrap;
margin: 0 -24rpx;
padding: 0 24rpx;
}
.categories-container {
display: inline-flex;
gap: 12rpx;
}
.service-types {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: 48rpx;
}
.type-item {
display: flex;
align-items: center;
gap: 20rpx;
padding: 28rpx 32rpx;
background: linear-gradient(135deg, rgba(20, 25, 50, 0.9) 0%, rgba(10, 14, 39, 0.8) 100%);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 16rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
border-color: rgba(0, 255, 255, 0.5);
}
}
.type-icon {
font-size: 40rpx;
}
.type-name {
flex: 1;
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
}
.type-arrow {
font-size: 32rpx;
color: #00ffff;
}
.services-list {
display: flex;
flex-direction: column;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
gap: 16rpx;
padding: 40rpx 0;
}
.bottom-decoration {
display: flex;
align-items: center;
gap: 24rpx;
margin-top: 60rpx;
padding: 40rpx 0;
opacity: 0.5;
}
.decoration-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(122, 126, 157, 0.3) 50%,
transparent 100%
);
}
.decoration-text {
font-size: 20rpx;
color: #7a7e9d;
letter-spacing: 2rpx;
font-family: monospace;
}
</style>

356
pages/order/create.vue Normal file
View File

@ -0,0 +1,356 @@
<template>
<view class="create-order-page page-container">
<!-- 服务信息卡片 -->
<view class="service-card cyber-card">
<image :src="service.coverImage" class="service-cover" mode="aspectFill" />
<view class="service-info">
<view class="service-name">{{ service.name }}</view>
<view class="service-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ service.price }}</text>
</view>
</view>
</view>
<!-- 游戏信息表单 -->
<view class="form-section cyber-card">
<view class="section-title">游戏信息</view>
<view class="form-item">
<text class="label">游戏ID</text>
<input
class="input"
v-model="formData.gameId"
placeholder="请输入您的游戏ID"
placeholder-class="placeholder"
/>
</view>
<view class="form-item">
<text class="label">游戏区服</text>
<picker mode="selector" :range="servers" @change="onServerChange">
<view class="picker">
{{ formData.server || '请选择区服' }}
</view>
</picker>
</view>
<view class="form-item">
<text class="label">当前段位</text>
<input
class="input"
v-model="formData.currentRank"
placeholder="请输入当前段位"
placeholder-class="placeholder"
/>
</view>
</view>
<!-- 联系方式 -->
<view class="form-section cyber-card">
<view class="section-title">联系方式</view>
<view class="form-item">
<text class="label">QQ号</text>
<input
class="input"
v-model="formData.qq"
placeholder="请输入QQ号"
placeholder-class="placeholder"
/>
</view>
<view class="form-item">
<text class="label">微信号</text>
<input
class="input"
v-model="formData.wechat"
placeholder="请输入微信号"
placeholder-class="placeholder"
/>
</view>
</view>
<!-- 备注 -->
<view class="form-section cyber-card">
<view class="section-title">订单备注</view>
<textarea
class="textarea"
v-model="formData.remark"
placeholder="请输入订单备注(选填)"
placeholder-class="placeholder"
maxlength="200"
/>
</view>
<!-- 价格明细 -->
<view class="price-detail cyber-card">
<view class="detail-row">
<text class="label">服务费用</text>
<text class="value">¥{{ service.price }}</text>
</view>
<view class="cyber-divider"></view>
<view class="detail-row total">
<text class="label">应付金额</text>
<text class="value neon-text">¥{{ service.price }}</text>
</view>
</view>
<!-- 底部提交按钮 -->
<view class="bottom-bar">
<view class="total-price">
<text class="label">总计:</text>
<text class="price">¥{{ service.price }}</text>
</view>
<button class="submit-btn cyber-button" @click="submitOrder">
确认下单
</button>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onLoad } from '@dcloudio/uni-app'
import { getServiceById } from '../../mock'
const service = ref({
id: 0,
name: '',
coverImage: '',
price: 0
})
const servers = ['微信区', 'QQ区', '安卓区', 'iOS区']
const formData = reactive({
gameId: '',
server: '',
currentRank: '',
qq: '',
wechat: '',
remark: ''
})
onLoad((options) => {
if (options.serviceId) {
const serviceData = getServiceById(parseInt(options.serviceId))
if (serviceData) {
service.value = serviceData
}
}
})
const onServerChange = (e) => {
formData.server = servers[e.detail.value]
}
const submitOrder = () => {
//
if (!formData.gameId) {
uni.showToast({ title: '请输入游戏ID', icon: 'none' })
return
}
if (!formData.server) {
uni.showToast({ title: '请选择区服', icon: 'none' })
return
}
if (!formData.qq && !formData.wechat) {
uni.showToast({ title: '请至少填写一种联系方式', icon: 'none' })
return
}
uni.showLoading({ title: '提交中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '下单成功!',
icon: 'success'
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/order/list'
})
}, 1500)
}, 1000)
}
</script>
<style lang="scss" scoped>
.create-order-page {
padding: 24rpx;
padding-bottom: 180rpx;
}
.service-card {
display: flex;
gap: 24rpx;
padding: 24rpx;
margin-bottom: 24rpx;
}
.service-cover {
width: 160rpx;
height: 120rpx;
border-radius: 12rpx;
}
.service-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.service-name {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
}
.service-price {
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: 24rpx;
color: #00ffff;
}
.price-value {
font-size: 36rpx;
font-weight: bold;
color: #00ffff;
}
.form-section {
padding: 32rpx;
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 28rpx;
}
.form-item {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
display: block;
font-size: 26rpx;
color: #a0a4c4;
margin-bottom: 16rpx;
}
.input,
.picker {
width: 100%;
height: 88rpx;
padding: 0 28rpx;
background: rgba(10, 14, 39, 0.6);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 12rpx;
font-size: 28rpx;
color: #ffffff;
line-height: 88rpx;
}
.textarea {
width: 100%;
min-height: 200rpx;
padding: 20rpx 28rpx;
background: rgba(10, 14, 39, 0.6);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 12rpx;
font-size: 28rpx;
color: #ffffff;
}
.placeholder {
color: #7a7e9d;
}
.price-detail {
padding: 32rpx;
margin-bottom: 24rpx;
}
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
margin-bottom: 20rpx;
&.total {
margin-bottom: 0;
.label,
.value {
font-size: 32rpx;
font-weight: bold;
}
}
}
.detail-row .label {
color: #a0a4c4;
}
.detail-row .value {
color: #ffffff;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
gap: 24rpx;
padding: 24rpx;
background: rgba(10, 14, 39, 0.98);
border-top: 1px solid rgba(0, 255, 255, 0.2);
backdrop-filter: blur(20rpx);
}
.total-price {
display: flex;
align-items: baseline;
gap: 12rpx;
}
.total-price .label {
font-size: 26rpx;
color: #a0a4c4;
}
.total-price .price {
font-size: 40rpx;
font-weight: bold;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.submit-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
}
</style>

465
pages/order/detail.vue Normal file
View File

@ -0,0 +1,465 @@
<template>
<view class="order-detail-page page-container">
<!-- 订单状态 -->
<view class="status-card cyber-card">
<view class="status-icon">{{ getStatusIcon(order.status) }}</view>
<view class="status-text">{{ getStatusText(order.status) }}</view>
<view v-if="order.status === 3" class="status-desc">
服务进行中,请耐心等待选手完成服务
</view>
</view>
<!-- 选手信息 (已接单) -->
<view v-if="order.playerName" class="player-card cyber-card">
<view class="card-title">服务选手</view>
<view class="player-info">
<image :src="order.playerAvatar" class="player-avatar" />
<view class="player-detail">
<text class="player-name">{{ order.playerName }}</text>
<text class="player-status">{{ order.status === 3 ? '服务中...' : '已接单' }}</text>
</view>
<button class="contact-btn" @click="contactPlayer">联系选手</button>
</view>
</view>
<!-- 服务信息 -->
<view class="service-card cyber-card">
<view class="card-title">服务信息</view>
<view class="service-content">
<image :src="order.serviceCover" class="service-cover" mode="aspectFill" />
<view class="service-info">
<text class="service-name">{{ order.serviceName }}</text>
<text class="service-category">{{ order.categoryName }}</text>
</view>
<view class="service-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ order.actualPrice }}</text>
</view>
</view>
</view>
<!-- 游戏信息 -->
<view class="info-card cyber-card">
<view class="card-title">游戏信息</view>
<view class="info-row">
<text class="label">游戏ID</text>
<text class="value">{{ order.gameInfo?.gameId }}</text>
</view>
<view class="info-row">
<text class="label">区服</text>
<text class="value">{{ order.gameInfo?.server }}</text>
</view>
<view v-if="order.gameInfo?.currentRank" class="info-row">
<text class="label">段位</text>
<text class="value">{{ order.gameInfo?.currentRank }}</text>
</view>
<view v-if="order.remark" class="info-row">
<text class="label">备注</text>
<text class="value">{{ order.remark }}</text>
</view>
</view>
<!-- 订单流转 -->
<view class="flow-card cyber-card">
<view class="card-title">订单流转</view>
<view class="flow-list">
<view v-for="(item, index) in orderFlow" :key="index" class="flow-item">
<view class="flow-dot" :class="{ active: index === 0 }"></view>
<view class="flow-line" v-if="index < orderFlow.length - 1"></view>
<view class="flow-content">
<text class="flow-status">{{ item.status }}</text>
<text class="flow-desc">{{ item.desc }}</text>
<text class="flow-time">{{ item.time }}</text>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info-card cyber-card">
<view class="card-title">订单信息</view>
<view class="info-row">
<text class="label">订单号</text>
<text class="value">{{ order.orderNo }}</text>
</view>
<view class="info-row">
<text class="label">创建时间</text>
<text class="value">{{ order.createTime }}</text>
</view>
<view v-if="order.payTime" class="info-row">
<text class="label">支付时间</text>
<text class="value">{{ order.payTime }}</text>
</view>
<view v-if="order.finishTime" class="info-row">
<text class="label">完成时间</text>
<text class="value">{{ order.finishTime }}</text>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<button v-if="order.status === 0" class="action-btn btn-cancel" @click="handleCancel">取消订单</button>
<button v-if="order.status === 0" class="action-btn btn-primary" @click="handlePay">立即支付</button>
<button v-if="order.status === 4" class="action-btn btn-outline" @click="handleRefund">申请退款</button>
<button v-if="order.status === 4" class="action-btn btn-primary" @click="handleConfirm">确认完成</button>
<button v-if="order.status === 5" class="action-btn btn-primary" @click="handleEvaluate">去评价</button>
</view>
</view>
</template>
<script setup>
import { ref, onLoad } from '@dcloudio/uni-app'
import { getOrderById, getOrderFlowById, OrderStatus } from '../../mock'
const order = ref({})
const orderFlow = ref([])
onLoad((options) => {
if (options.id) {
const orderData = getOrderById(parseInt(options.id))
if (orderData) {
order.value = orderData
orderFlow.value = getOrderFlowById(orderData.id)
}
}
})
const getStatusIcon = (status) => {
const icons = {
0: '⏰',
1: '👀',
2: '✅',
3: '🎮',
4: '⭐',
5: '✨',
6: '💬',
9: '❌'
}
return icons[status] || '📦'
}
const getStatusText = (status) => {
const statusObj = Object.values(OrderStatus).find(s => s.code === status)
return statusObj ? statusObj.text : '未知状态'
}
const contactPlayer = () => {
uni.showToast({ title: '联系选手', icon: 'none' })
}
const handleCancel = () => {
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '订单已取消', icon: 'none' })
}
}
})
}
const handlePay = () => {
uni.showToast({ title: '跳转支付...', icon: 'none' })
}
const handleRefund = () => {
uni.showModal({
title: '申请退款',
content: '请说明退款原因',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '退款申请已提交', icon: 'none' })
}
}
})
}
const handleConfirm = () => {
uni.showModal({
title: '确认完成',
content: '确认服务已完成?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已确认完成', icon: 'success' })
}
}
})
}
const handleEvaluate = () => {
uni.navigateTo({
url: `/pages/order/evaluate?orderId=${order.value.id}`
})
}
</script>
<style lang="scss" scoped>
.order-detail-page {
padding: 24rpx;
padding-bottom: 180rpx;
}
.status-card {
padding: 48rpx 32rpx;
text-align: center;
margin-bottom: 24rpx;
}
.status-icon {
font-size: 120rpx;
margin-bottom: 20rpx;
}
.status-text {
font-size: 36rpx;
font-weight: bold;
color: #00ffff;
margin-bottom: 16rpx;
}
.status-desc {
font-size: 26rpx;
color: #a0a4c4;
}
.player-card,
.service-card,
.info-card,
.flow-card,
.order-info-card {
padding: 32rpx;
margin-bottom: 24rpx;
}
.card-title {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 28rpx;
}
.player-info {
display: flex;
align-items: center;
gap: 20rpx;
}
.player-avatar {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
border: 2px solid rgba(0, 255, 255, 0.3);
}
.player-detail {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.player-name {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
}
.player-status {
font-size: 24rpx;
color: #00ff88;
}
.contact-btn {
padding: 16rpx 32rpx;
background: transparent;
border: 1px solid #00ffff;
border-radius: 8rpx;
font-size: 26rpx;
color: #00ffff;
}
.service-content {
display: flex;
gap: 24rpx;
align-items: center;
}
.service-cover {
width: 160rpx;
height: 120rpx;
border-radius: 12rpx;
}
.service-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.service-name {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
}
.service-category {
padding: 4rpx 16rpx;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 6rpx;
font-size: 22rpx;
color: #00ffff;
align-self: flex-start;
}
.service-price {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.price-symbol {
font-size: 24rpx;
color: #00ffff;
}
.price-value {
font-size: 36rpx;
font-weight: bold;
color: #00ffff;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid rgba(0, 255, 255, 0.1);
&:last-child {
border-bottom: none;
}
}
.info-row .label {
font-size: 26rpx;
color: #7a7e9d;
}
.info-row .value {
font-size: 26rpx;
color: #ffffff;
text-align: right;
flex: 1;
margin-left: 40rpx;
}
.flow-list {
position: relative;
}
.flow-item {
position: relative;
display: flex;
gap: 24rpx;
padding-left: 48rpx;
padding-bottom: 40rpx;
&:last-child {
padding-bottom: 0;
}
}
.flow-dot {
position: absolute;
left: 0;
top: 4rpx;
width: 24rpx;
height: 24rpx;
background: rgba(122, 126, 157, 0.5);
border: 2px solid rgba(122, 126, 157, 0.8);
border-radius: 50%;
&.active {
background: rgba(0, 255, 255, 0.3);
border-color: #00ffff;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
}
.flow-line {
position: absolute;
left: 11rpx;
top: 28rpx;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, rgba(0, 255, 255, 0.3) 0%, transparent 100%);
}
.flow-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.flow-status {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
}
.flow-desc {
font-size: 24rpx;
color: #a0a4c4;
}
.flow-time {
font-size: 22rpx;
color: #7a7e9d;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 24rpx;
padding: 24rpx;
background: rgba(10, 14, 39, 0.98);
border-top: 1px solid rgba(0, 255, 255, 0.2);
backdrop-filter: blur(20rpx);
}
.action-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 30rpx;
font-weight: bold;
border-radius: 12rpx;
&.btn-primary {
background: linear-gradient(135deg, #00ffff 0%, #0099ff 100%);
color: #0a0e27;
border: none;
}
&.btn-outline {
background: transparent;
border: 1px solid #00ffff;
color: #00ffff;
}
&.btn-cancel {
background: rgba(122, 126, 157, 0.2);
border: 1px solid rgba(122, 126, 157, 0.4);
color: #7a7e9d;
}
}
</style>

220
pages/order/list.vue Normal file
View File

@ -0,0 +1,220 @@
<template>
<view class="order-list-page page-container">
<!-- Tab标签栏 -->
<view class="tabs-bar">
<scroll-view class="tabs-scroll" scroll-x show-scrollbar="false">
<view class="tabs-container">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: currentTab === index }"
@click="changeTab(index)"
>
<text class="tab-text">{{ tab.name }}</text>
<text v-if="tab.count > 0" class="tab-badge">{{ tab.count }}</text>
<view v-if="currentTab === index" class="tab-indicator"></view>
</view>
</view>
</scroll-view>
</view>
<!-- 订单列表 -->
<scroll-view class="orders-scroll" scroll-y refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
<view class="orders-list">
<order-card
v-for="order in displayOrders"
:key="order.id"
:order="order"
@click="goOrderDetail"
@action="handleOrderAction"
/>
</view>
<!-- 空状态 -->
<view v-if="displayOrders.length === 0" class="empty-state">
<text class="empty-icon">📦</text>
<text class="empty-text">暂无订单</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { orders, getOrdersByStatus, getOrderCountByStatus } from '../../mock'
const currentTab = ref(0)
const refreshing = ref(false)
const tabs = computed(() => [
{ name: '全部', status: 'all', count: getOrderCountByStatus('all') },
{ name: '待支付', status: 0, count: getOrderCountByStatus(0) },
{ name: '待接单', status: 1, count: getOrderCountByStatus(1) },
{ name: '进行中', status: 3, count: getOrderCountByStatus(3) },
{ name: '待确认', status: 4, count: getOrderCountByStatus(4) },
{ name: '已完成', status: 5, count: getOrderCountByStatus(5) }
])
const displayOrders = computed(() => {
const status = tabs.value[currentTab.value].status
return getOrdersByStatus(status)
})
const changeTab = (index) => {
currentTab.value = index
}
const onRefresh = () => {
refreshing.value = true
setTimeout(() => {
refreshing.value = false
uni.showToast({ title: '刷新成功', icon: 'none' })
}, 1000)
}
const goOrderDetail = (order) => {
uni.navigateTo({
url: `/pages/order/detail?id=${order.id}`
})
}
const handleOrderAction = ({ action, order }) => {
console.log('Action:', action, 'Order:', order)
switch (action) {
case 'pay':
uni.showToast({ title: '跳转支付...', icon: 'none' })
break
case 'cancel':
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '订单已取消', icon: 'none' })
}
}
})
break
case 'confirm':
uni.showModal({
title: '确认完成',
content: '确认服务已完成?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已确认完成', icon: 'success' })
}
}
})
break
case 'evaluate':
uni.showToast({ title: '去评价', icon: 'none' })
break
case 'detail':
goOrderDetail(order)
break
default:
uni.showToast({ title: action, icon: 'none' })
}
}
</script>
<style lang="scss" scoped>
.order-list-page {
display: flex;
flex-direction: column;
height: 100vh;
}
.tabs-bar {
padding: 0 24rpx;
margin-bottom: 20rpx;
}
.tabs-scroll {
white-space: nowrap;
}
.tabs-container {
display: inline-flex;
gap: 12rpx;
}
.tab-item {
position: relative;
display: flex;
align-items: center;
gap: 8rpx;
padding: 16rpx 32rpx;
background: rgba(20, 25, 50, 0.6);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 50rpx;
transition: all 0.3s ease;
&.active {
background: rgba(0, 255, 255, 0.15);
border-color: rgba(0, 255, 255, 0.5);
.tab-text {
color: #00ffff;
font-weight: bold;
}
}
}
.tab-text {
font-size: 26rpx;
color: #a0a4c4;
}
.tab-badge {
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
padding: 0 8rpx;
background: #ff3366;
border-radius: 18rpx;
font-size: 20rpx;
color: #ffffff;
text-align: center;
}
.tab-indicator {
position: absolute;
bottom: 8rpx;
left: 50%;
transform: translateX(-50%);
width: 40%;
height: 4rpx;
background: linear-gradient(90deg, #00ffff 0%, #0099ff 100%);
border-radius: 2rpx;
}
.orders-scroll {
flex: 1;
}
.orders-list {
padding: 0 24rpx 24rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #7a7e9d;
}
</style>

467
pages/service/detail.vue Normal file
View File

@ -0,0 +1,467 @@
<template>
<view class="service-detail-page page-container">
<!-- 服务封面 -->
<view class="service-cover">
<image :src="service.coverImage" mode="aspectFill" class="cover-image" />
<view class="cover-overlay"></view>
<!-- 返回按钮 -->
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
</view>
<!-- 分享按钮 -->
<view class="share-btn" @click="handleShare">
<text class="share-icon">📤</text>
</view>
</view>
<!-- 服务信息 -->
<view class="service-info cyber-card">
<view class="service-header">
<view class="service-title">{{ service.name }}</view>
<view class="price-container">
<text class="price-symbol">¥</text>
<text class="price-value">{{ service.price }}</text>
</view>
</view>
<view class="service-stats">
<view class="stat-item">
<text class="stat-label">评分</text>
<text class="stat-value neon-text">{{ service.rating }} </text>
</view>
<view class="stat-item">
<text class="stat-label">销量</text>
<text class="stat-value">{{ service.salesCount }}</text>
</view>
<view class="stat-item">
<text class="stat-label">时长</text>
<text class="stat-value">{{ service.serviceTime }}分钟</text>
</view>
</view>
<view class="tags-container">
<view v-for="tag in service.tags" :key="tag" class="tag-item">
{{ tag }}
</view>
</view>
</view>
<!-- 商家信息 -->
<view class="merchant-card cyber-card">
<view class="merchant-header">
<image :src="service.merchantAvatar" class="merchant-avatar" />
<view class="merchant-info">
<text class="merchant-name">{{ service.merchantName }}</text>
<text class="merchant-desc">{{ service.playerCount }}位专业选手</text>
</view>
<button class="contact-btn" @click="contactMerchant">联系商家</button>
</view>
</view>
<!-- 服务详情 -->
<view class="service-detail cyber-card">
<view class="section-title">服务详情</view>
<view class="detail-content">
<text class="detail-text">{{ service.description }}</text>
</view>
</view>
<!-- 用户评价 -->
<view class="evaluations-card cyber-card">
<view class="section-title">
用户评价 ({{ service.reviewCount }})
</view>
<view class="evaluation-item" v-for="i in 3" :key="i">
<view class="eval-header">
<image src="https://picsum.photos/100/100?random=401" class="user-avatar" />
<view class="user-info">
<text class="user-name">用户{{ i }}</text>
<view class="rating"></view>
</view>
<text class="eval-time">2天前</text>
</view>
<text class="eval-content">服务很棒,选手很专业,下次还会再来!</text>
</view>
<view class="view-all" @click="viewAllEvaluations">
查看全部评价 >
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="action-btns">
<view class="icon-btn" @click="toggleFavorite">
<text class="icon">{{ isFavorite ? '❤️' : '🤍' }}</text>
<text class="label">收藏</text>
</view>
<view class="icon-btn" @click="contactService">
<text class="icon">💬</text>
<text class="label">客服</text>
</view>
</view>
<button class="order-btn cyber-button" @click="goOrder">
立即下单
</button>
</view>
</view>
</template>
<script setup>
import { ref, onLoad } from '@dcloudio/uni-app'
import { getServiceById } from '../../mock'
const service = ref({
id: 0,
name: '',
coverImage: '',
price: 0,
originalPrice: 0,
description: '',
serviceTime: 0,
salesCount: 0,
rating: 5.0,
reviewCount: 0,
tags: [],
merchantName: '',
merchantAvatar: '',
playerCount: 0
})
const isFavorite = ref(false)
onLoad((options) => {
if (options.id) {
const serviceData = getServiceById(parseInt(options.id))
if (serviceData) {
service.value = serviceData
}
}
})
const goBack = () => {
uni.navigateBack()
}
const handleShare = () => {
uni.showShareMenu({
title: service.value.name,
path: `/pages/service/detail?id=${service.value.id}`
})
}
const toggleFavorite = () => {
isFavorite.value = !isFavorite.value
uni.showToast({
title: isFavorite.value ? '已收藏' : '取消收藏',
icon: 'none'
})
}
const contactMerchant = () => {
uni.showToast({ title: '联系商家', icon: 'none' })
}
const contactService = () => {
uni.showToast({ title: '联系客服', icon: 'none' })
}
const viewAllEvaluations = () => {
uni.showToast({ title: '查看全部评价', icon: 'none' })
}
const goOrder = () => {
uni.navigateTo({
url: `/pages/order/create?serviceId=${service.value.id}`
})
}
</script>
<style lang="scss" scoped>
.service-detail-page {
padding-bottom: 160rpx;
}
.service-cover {
position: relative;
width: 100%;
height: 600rpx;
margin-bottom: 24rpx;
}
.cover-image {
width: 100%;
height: 100%;
}
.cover-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 200rpx;
background: linear-gradient(180deg, transparent 0%, rgba(10, 14, 39, 0.9) 100%);
}
.back-btn,
.share-btn {
position: absolute;
top: 40rpx;
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(10, 14, 39, 0.8);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 50%;
backdrop-filter: blur(10rpx);
}
.back-btn {
left: 24rpx;
}
.share-btn {
right: 24rpx;
}
.back-icon,
.share-icon {
font-size: 36rpx;
color: #00ffff;
}
.service-info,
.merchant-card,
.service-detail,
.evaluations-card {
margin: 0 24rpx 24rpx;
padding: 32rpx;
}
.service-header {
margin-bottom: 28rpx;
}
.service-title {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 20rpx;
}
.price-container {
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: 32rpx;
font-weight: bold;
color: #00ffff;
}
.price-value {
font-size: 56rpx;
font-weight: bold;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.service-stats {
display: flex;
gap: 48rpx;
margin-bottom: 28rpx;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #7a7e9d;
}
.stat-value {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.tag-item {
padding: 8rpx 20rpx;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 8rpx;
font-size: 24rpx;
color: #00ffff;
}
.merchant-header {
display: flex;
align-items: center;
gap: 20rpx;
}
.merchant-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: 1px solid rgba(0, 255, 255, 0.3);
}
.merchant-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.merchant-name {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
}
.merchant-desc {
font-size: 24rpx;
color: #7a7e9d;
}
.contact-btn {
padding: 16rpx 32rpx;
background: transparent;
border: 1px solid #00ffff;
border-radius: 8rpx;
font-size: 26rpx;
color: #00ffff;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 24rpx;
}
.detail-content {
padding: 24rpx;
background: rgba(10, 14, 39, 0.5);
border-radius: 12rpx;
}
.detail-text {
font-size: 28rpx;
line-height: 1.8;
color: #a0a4c4;
}
.evaluation-item {
padding: 24rpx 0;
border-bottom: 1px solid rgba(0, 255, 255, 0.1);
}
.eval-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
}
.user-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.user-name {
font-size: 26rpx;
color: #ffffff;
}
.rating {
font-size: 20rpx;
}
.eval-time {
font-size: 22rpx;
color: #7a7e9d;
}
.eval-content {
font-size: 26rpx;
line-height: 1.6;
color: #a0a4c4;
}
.view-all {
text-align: center;
padding: 24rpx 0;
font-size: 26rpx;
color: #00ffff;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
gap: 24rpx;
padding: 24rpx;
background: linear-gradient(180deg, transparent 0%, rgba(10, 14, 39, 0.95) 20%, rgba(10, 14, 39, 1) 100%);
border-top: 1px solid rgba(0, 255, 255, 0.2);
backdrop-filter: blur(20rpx);
}
.action-btns {
display: flex;
gap: 32rpx;
}
.icon-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.icon {
font-size: 40rpx;
}
.label {
font-size: 22rpx;
color: #7a7e9d;
}
.order-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
}
</style>

431
pages/user/index.vue Normal file
View File

@ -0,0 +1,431 @@
<template>
<view class="user-page page-container">
<!-- 用户信息卡片 -->
<view class="user-card cyber-card">
<image :src="user.avatar" class="user-avatar" />
<view class="user-info">
<text class="user-name">{{ user.nickname }}</text>
<view class="vip-badge">
<text class="vip-icon">👑</text>
<text class="vip-text">{{ user.vipName }}</text>
</view>
</view>
<view class="setting-btn" @click="goSetting">
<text class="setting-icon"></text>
</view>
</view>
<!-- 资产信息 -->
<view class="assets-card cyber-card">
<view class="asset-item" @click="goWallet">
<text class="asset-value neon-text">{{ user.balance }}</text>
<text class="asset-label">余额</text>
</view>
<view class="asset-divider"></view>
<view class="asset-item" @click="goPoints">
<text class="asset-value neon-text">{{ user.points }}</text>
<text class="asset-label">积分</text>
</view>
<view class="asset-divider"></view>
<view class="asset-item" @click="goCoupons">
<text class="asset-value neon-text">{{ userStats.couponCount }}</text>
<text class="asset-label">优惠券</text>
</view>
</view>
<!-- 订单统计 -->
<view class="order-stats cyber-card">
<view class="stats-header">
<text class="stats-title">我的订单</text>
<text class="stats-more" @click="goAllOrders">全部 ></text>
</view>
<view class="stats-grid">
<view class="stat-item" @click="goOrders(0)">
<text class="stat-icon">💳</text>
<text class="stat-name">待支付</text>
<view v-if="userStats.orderCount.waitPay > 0" class="stat-badge">
{{ userStats.orderCount.waitPay }}
</view>
</view>
<view class="stat-item" @click="goOrders(1)">
<text class="stat-icon">👀</text>
<text class="stat-name">待接单</text>
<view v-if="userStats.orderCount.waitAccept > 0" class="stat-badge">
{{ userStats.orderCount.waitAccept }}
</view>
</view>
<view class="stat-item" @click="goOrders(3)">
<text class="stat-icon">🎮</text>
<text class="stat-name">进行中</text>
<view v-if="userStats.orderCount.inProgress > 0" class="stat-badge">
{{ userStats.orderCount.inProgress }}
</view>
</view>
<view class="stat-item" @click="goOrders(4)">
<text class="stat-icon"></text>
<text class="stat-name">待确认</text>
<view v-if="userStats.orderCount.waitConfirm > 0" class="stat-badge">
{{ userStats.orderCount.waitConfirm }}
</view>
</view>
<view class="stat-item" @click="goOrders(5)">
<text class="stat-icon"></text>
<text class="stat-name">已完成</text>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-card cyber-card">
<view class="menu-item" @click="goFavorites">
<text class="menu-icon"></text>
<text class="menu-name">我的收藏</text>
<text class="menu-extra">{{ userStats.favoriteCount }}</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goMessages">
<text class="menu-icon">💬</text>
<text class="menu-name">消息中心</text>
<view v-if="userStats.messageCount > 0" class="menu-badge">
{{ userStats.messageCount }}
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goHelp">
<text class="menu-icon"></text>
<text class="menu-name">帮助中心</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goAbout">
<text class="menu-icon"></text>
<text class="menu-name">关于我们</text>
<text class="menu-arrow"></text>
</view>
</view>
<!-- 底部装饰 -->
<view class="bottom-decoration">
<view class="decoration-line"></view>
<text class="decoration-text">// GAME SERVICE v1.0.0 //</text>
<view class="decoration-line"></view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { currentUser, userStats } from '../../mock'
const user = ref(currentUser)
const goSetting = () => {
uni.showToast({ title: '设置', icon: 'none' })
}
const goWallet = () => {
uni.showToast({ title: '我的余额', icon: 'none' })
}
const goPoints = () => {
uni.showToast({ title: '我的积分', icon: 'none' })
}
const goCoupons = () => {
uni.showToast({ title: '优惠券', icon: 'none' })
}
const goAllOrders = () => {
uni.switchTab({
url: '/pages/order/list'
})
}
const goOrders = (status) => {
uni.navigateTo({
url: `/pages/order/list?status=${status}`
})
}
const goFavorites = () => {
uni.showToast({ title: '我的收藏', icon: 'none' })
}
const goMessages = () => {
uni.showToast({ title: '消息中心', icon: 'none' })
}
const goHelp = () => {
uni.showToast({ title: '帮助中心', icon: 'none' })
}
const goAbout = () => {
uni.showToast({ title: '关于我们', icon: 'none' })
}
</script>
<style lang="scss" scoped>
.user-page {
padding: 24rpx;
}
.user-card {
display: flex;
align-items: center;
gap: 28rpx;
padding: 40rpx 32rpx;
margin-bottom: 24rpx;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 3px solid rgba(0, 255, 255, 0.5);
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.user-name {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
}
.vip-badge {
display: inline-flex;
align-items: center;
gap: 8rpx;
padding: 6rpx 20rpx;
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 165, 0, 0.2) 100%);
border: 1px solid rgba(255, 215, 0, 0.5);
border-radius: 20rpx;
align-self: flex-start;
}
.vip-icon {
font-size: 24rpx;
}
.vip-text {
font-size: 22rpx;
color: #ffd700;
font-weight: bold;
}
.setting-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(20, 25, 50, 0.6);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 50%;
&:active {
transform: scale(0.95);
}
}
.setting-icon {
font-size: 36rpx;
}
.assets-card {
display: flex;
padding: 32rpx;
margin-bottom: 24rpx;
}
.asset-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
&:active {
opacity: 0.7;
}
}
.asset-value {
font-size: 40rpx;
font-weight: bold;
color: #00ffff;
}
.asset-label {
font-size: 24rpx;
color: #7a7e9d;
}
.asset-divider {
width: 1px;
background: linear-gradient(180deg,
transparent 0%,
rgba(0, 255, 255, 0.3) 50%,
transparent 100%
);
}
.order-stats {
padding: 32rpx;
margin-bottom: 24rpx;
}
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
}
.stats-title {
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
}
.stats-more {
font-size: 26rpx;
color: #00ffff;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 24rpx;
}
.stat-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
&:active {
opacity: 0.7;
}
}
.stat-icon {
font-size: 48rpx;
}
.stat-name {
font-size: 24rpx;
color: #a0a4c4;
}
.stat-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
padding: 0 8rpx;
background: #ff3366;
border-radius: 16rpx;
font-size: 20rpx;
color: #ffffff;
text-align: center;
}
.menu-card {
padding: 32rpx;
margin-bottom: 24rpx;
}
.menu-item {
display: flex;
align-items: center;
padding: 28rpx 0;
border-bottom: 1px solid rgba(0, 255, 255, 0.1);
&:last-child {
border-bottom: none;
}
&:active {
opacity: 0.7;
}
}
.menu-icon {
font-size: 40rpx;
margin-right: 24rpx;
}
.menu-name {
flex: 1;
font-size: 28rpx;
color: #ffffff;
}
.menu-extra {
font-size: 24rpx;
color: #7a7e9d;
margin-right: 16rpx;
}
.menu-badge {
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
padding: 0 8rpx;
background: #ff3366;
border-radius: 16rpx;
font-size: 20rpx;
color: #ffffff;
text-align: center;
margin-right: 16rpx;
}
.menu-arrow {
font-size: 32rpx;
color: #7a7e9d;
}
.bottom-decoration {
display: flex;
align-items: center;
gap: 24rpx;
margin-top: 60rpx;
padding: 40rpx 0;
opacity: 0.5;
}
.decoration-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(122, 126, 157, 0.3) 50%,
transparent 100%
);
}
.decoration-text {
font-size: 20rpx;
color: #7a7e9d;
letter-spacing: 2rpx;
font-family: monospace;
}
</style>

41
static/icons/README.md Normal file
View File

@ -0,0 +1,41 @@
# TabBar 图标说明
## 图标尺寸要求
- 推荐尺寸: 81px * 81px
- 格式: PNG 透明背景
## 需要的图标
### 首页
- `home.png` - 普通状态 (灰色)
- `home-active.png` - 选中状态 (青色)
**建议图标**: 🏠 房子 或 🎮 游戏手柄
### 订单
- `order.png` - 普通状态 (灰色)
- `order-active.png` - 选中状态 (青色)
**建议图标**: 📋 订单 或 📦 包裹
### 我的
- `user.png` - 普通状态 (灰色)
- `user-active.png` - 选中状态 (青色)
**建议图标**: 👤 用户 或 ⚙️ 设置
## 配色方案
- **普通状态**: #7a7e9d (灰紫色)
- **选中状态**: #00ffff (青色霓虹)
## 临时解决方案
在图标未准备好之前,可以:
1. 使用纯文字tabbar (删除pages.json中的iconPath配置)
2. 使用在线图标生成工具快速制作
3. 使用Emoji作为临时图标
## 在线工具推荐
- Iconfont: https://www.iconfont.cn/
- Flaticon: https://www.flaticon.com/
- Icons8: https://icons8.com/

71
uni.scss Normal file
View File

@ -0,0 +1,71 @@
/* 电竞赛博朋克配色方案 */
/* ============ 主色调 ============ */
// 主背景色
$bg-primary: #0a0e27;
$bg-secondary: #0f1229;
$bg-tertiary: #1a1d3a;
// 霓虹色
$neon-cyan: #00ffff; // 青色霓虹
$neon-magenta: #ff00ff; // 洋红霓虹
$neon-purple: #9d00ff; // 紫色霓虹
$neon-blue: #0099ff; // 蓝色霓虹
$neon-pink: #ff1493; // 粉色霓虹
$neon-green: #00ff88; // 绿色霓虹
// 功能色
$color-success: #00ff88;
$color-warning: #ffaa00;
$color-error: #ff3366;
$color-info: #00ffff;
/* ============ 文字颜色 ============ */
$text-primary: #ffffff;
$text-secondary: #a0a4c4;
$text-tertiary: #7a7e9d;
$text-disabled: #4a4e6d;
/* ============ 边框颜色 ============ */
$border-primary: rgba(0, 255, 255, 0.3);
$border-secondary: rgba(0, 255, 255, 0.15);
$border-glow: rgba(0, 255, 255, 0.6);
/* ============ 阴影效果 ============ */
$shadow-neon-cyan: 0 0 10px rgba(0, 255, 255, 0.5), 0 0 20px rgba(0, 255, 255, 0.3);
$shadow-neon-magenta: 0 0 10px rgba(255, 0, 255, 0.5), 0 0 20px rgba(255, 0, 255, 0.3);
$shadow-card: 0 4px 20px rgba(0, 0, 0, 0.5);
/* ============ 尺寸 ============ */
$spacing-xs: 8rpx;
$spacing-sm: 16rpx;
$spacing-md: 24rpx;
$spacing-lg: 32rpx;
$spacing-xl: 48rpx;
$border-radius-sm: 8rpx;
$border-radius-md: 16rpx;
$border-radius-lg: 24rpx;
/* ============ 字体大小 ============ */
$font-xs: 20rpx;
$font-sm: 24rpx;
$font-md: 28rpx;
$font-lg: 32rpx;
$font-xl: 36rpx;
$font-xxl: 48rpx;
/* ============ 渐变背景 ============ */
$gradient-primary: linear-gradient(135deg, #00ffff 0%, #0099ff 100%);
$gradient-secondary: linear-gradient(135deg, #ff00ff 0%, #9d00ff 100%);
$gradient-card: linear-gradient(135deg, rgba(20, 25, 50, 0.9) 0%, rgba(10, 14, 39, 0.8) 100%);
$gradient-overlay: linear-gradient(180deg, transparent 0%, rgba(10, 14, 39, 0.8) 100%);
/* ============ 游戏等级颜色 ============ */
$rank-bronze: #cd7f32;
$rank-silver: #c0c0c0;
$rank-gold: #ffd700;
$rank-platinum: #e5e4e2;
$rank-diamond: #b9f2ff;
$rank-master: #ff00ff;
$rank-king: #ff1493;

10
vite.config.js Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
export default defineConfig({
plugins: [uni()],
server: {
port: 8080,
open: true
}
})