style(uiux): 严格对齐UIUX设计约束规范,全面替换系统Emoji为电竞矢量SVG图标并修复H5居中自适应

这个提交包含在:
wikiboss
2026-09-16 17:01:01 +08:00
父节点 935d6fa840
当前提交 a377327465
共有 11 个文件被更改,包括 497 次插入 和 124 次删除

查看文件

@@ -26,7 +26,7 @@ view, text, image, input, button {
box-sizing: border-box;
}
/* 触控反馈标准类 (满足 UI/UX Pro Max 规范) */
/* 触控反馈标准类 (严格满足 UI/UX 规范:100ms 内微缩放与透明度反馈,无布局位移) */
.btn-active:active, .card-active:active {
transform: scale(0.98);
opacity: 0.9;
@@ -40,4 +40,46 @@ view, text, image, input, button {
height: 0;
color: transparent;
}
/* H5 PC 模式响应式移动端容器居中(如在浏览器访问 http://localhost:5174/ 时) */
/* #ifdef H5 */
html, body {
background-color: #050811 !important;
margin: 0;
padding: 0;
overflow-x: hidden;
height: 100%;
}
uni-app {
max-width: 450px !important;
margin: 0 auto !important;
min-height: 100vh !important;
box-shadow: 0 0 60rpx rgba(0, 0, 0, 0.9) !important;
position: relative !important;
background-color: #0A0F1D !important;
}
uni-page-head {
max-width: 450px !important;
left: 0 !important;
right: 0 !important;
margin: 0 auto !important;
}
uni-tabbar, .uni-tabbar {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
margin: 0 auto !important;
max-width: 450px !important;
width: 100% !important;
z-index: 9999 !important;
}
uni-tabbar .uni-tabbar__bd {
background-color: #151D30 !important;
}
/* #endif */
</style>

189
pwuniapp/common/icons.js 普通文件
查看文件

@@ -0,0 +1,189 @@
/**
* 电竞暗夜流光矢量 SVG 图标库 (Cyber-Esports SVG Icons)
* 严格遵照 UI/UX 规范:杜绝系统 Emoji,全矢量渲染,支持无损缩放与跨端显示
*/
function svgToUri(svg) {
return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg.trim());
}
export const ICONS = {
// 基础与通用
search: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#94A3B8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
`),
// 游戏分类
gameAll: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#818CF8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="6" width="20" height="12" rx="4"></rect>
<line x1="6" y1="12" x2="10" y2="12"></line>
<line x1="8" y1="10" x2="8" y2="14"></line>
<circle cx="15" cy="11" r="1" fill="#818CF8"></circle>
<circle cx="18" cy="13" r="1" fill="#818CF8"></circle>
</svg>
`),
gameCrown: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 4l3 12h14l3-12-6 7-4-7-4 7-6-7z"></path>
<circle cx="12" cy="19" r="1" fill="#F59E0B"></circle>
</svg>
`),
gameCrosshair: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<line x1="12" y1="3" x2="12" y2="7"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
<line x1="3" y1="12" x2="7" y2="12"></line>
<line x1="17" y1="12" x2="21" y2="12"></line>
</svg>
`),
gameSwords: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#60A5FA" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="14.5 17.5 3 6 3 3 6 3 17.5 14.5"></polyline>
<line x1="13" y1="19" x2="19" y2="13"></line>
<line x1="16" y1="16" x2="20" y2="20"></line>
<line x1="19" y1="21" x2="21" y2="19"></line>
<polyline points="14.5 6.5 18 3 21 3 21 6 17.5 9.5"></polyline>
<line x1="5" y1="14" x2="10" y2="19"></line>
</svg>
`),
gameTarget: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#EC4899" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<circle cx="12" cy="12" r="6"></circle>
<circle cx="12" cy="12" r="2" fill="#EC4899"></circle>
</svg>
`),
gameBlade: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#A78BFA" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 20l4-4m0 0l12-12a2 2 0 0 0-2.83-2.83L5.17 13.17M8 16l-4 4"></path>
</svg>
`),
gameTrophy: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 9H4a2 2 0 0 1-2-2V5h4m12 4h2a2 2 0 0 0 2-2V5h-4M6 5h12v4a6 6 0 0 1-12 0V5zm3 10v4h6v-4m-7 4h8"></path>
</svg>
`),
// 金刚区与功能
teamUp: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#8B5CF6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
`),
ticket: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 7v2a3 3 0 0 0 0 6v2a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-2a3 3 0 0 0 0-6V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z"></path>
<line x1="9" y1="6" x2="9" y2="18" stroke-dasharray="2 2"></line>
</svg>
`),
orderList: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#6366F1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
`),
expertBadge: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
`),
// 动作与状态
bolt: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
</svg>
`),
clock: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#94A3B8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
`),
mic: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#818CF8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" y1="19" x2="12" y2="23"></line>
<line x1="8" y1="23" x2="16" y2="23"></line>
</svg>
`),
chat: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#60A5FA" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
`),
headphones: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#818CF8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 18v-6a9 9 0 0 1 18 0v6"></path>
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path>
</svg>
`),
shield: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
`),
bell: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
`),
settings: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#94A3B8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
`),
share: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#8B5CF6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
`),
play: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#818CF8" stroke="none">
<polygon points="6 4 20 12 6 20 6 4"></polygon>
</svg>
`),
pause: svgToUri(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#818CF8" stroke="none">
<rect x="6" y="4" width="4" height="16"></rect>
<rect x="14" y="4" width="4" height="16"></rect>
</svg>
`)
};

查看文件

@@ -1,11 +1,13 @@
import { ICONS } from './icons.js';
export const GAMES_LIST = [
{ id: 'all', name: '全部', icon: '🎮', hot: true },
{ id: 'wzry', name: '王者荣耀', icon: '👑', hot: true },
{ id: 'hpjy', name: '和平精英', icon: '🎯', hot: true },
{ id: 'lol', name: '英雄联盟', icon: '⚔️', hot: true },
{ id: 'val', name: '无畏契约', icon: '🔫', hot: true },
{ id: 'yjjw', name: '永劫无间', icon: '🗡️', hot: false },
{ id: 'pubg', name: '绝地求生', icon: '🏆', hot: false },
{ id: 'all', name: '全部', icon: ICONS.gameAll, hot: true },
{ id: 'wzry', name: '王者荣耀', icon: ICONS.gameCrown, hot: true },
{ id: 'hpjy', name: '和平精英', icon: ICONS.gameCrosshair, hot: true },
{ id: 'lol', name: '英雄联盟', icon: ICONS.gameSwords, hot: true },
{ id: 'val', name: '无畏契约', icon: ICONS.gameTarget, hot: true },
{ id: 'yjjw', name: '永劫无间', icon: ICONS.gameBlade, hot: false },
{ id: 'pubg', name: '绝地求生', icon: ICONS.gameTrophy, hot: false },
];
export const BANNERS = [
@@ -18,7 +20,7 @@ export const EXPERTS_LIST = [
{
id: 'exp_101',
nickname: '月色浅浅',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&h=200&fit=crop&crop=faces',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=800&h=400&fit=crop',
gender: 'female',
age: 21,
@@ -47,7 +49,7 @@ export const EXPERTS_LIST = [
{
id: 'exp_102',
nickname: '夜鹰狙神',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=faces',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?w=800&h=400&fit=crop',
gender: 'male',
age: 23,
@@ -75,7 +77,7 @@ export const EXPERTS_LIST = [
{
id: 'exp_103',
nickname: '软软喵',
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop&crop=faces',
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1538481199705-c710c4e965fc?w=800&h=400&fit=crop',
gender: 'female',
age: 20,
@@ -97,7 +99,7 @@ export const EXPERTS_LIST = [
{
id: 'exp_104',
nickname: '瞬移者雷克',
avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&h=200&fit=crop&crop=faces',
avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=300&fit=crop&crop=faces',
coverImage: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=800&h=400&fit=crop',
gender: 'male',
age: 22,

查看文件

@@ -93,7 +93,7 @@
<!-- 底部固定操作栏 -->
<view class="bottom-bar">
<view class="ticket-link btn-active" @click="goToMyTicket">
<text class="ticket-icon">🎟️</text>
<image class="ticket-svg" :src="ICONS.ticket" mode="aspectFit" />
<text class="ticket-text">我的门票</text>
</view>
<button class="btn-join btn-active" @click="handleJoin">
@@ -107,6 +107,7 @@
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { ACTIVITIES_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const activity = ref(ACTIVITIES_LIST[0]);
@@ -441,6 +442,8 @@ const handleJoin = () => {
padding: 0 32rpx;
gap: 24rpx;
z-index: 100;
max-width: 450px;
margin: 0 auto;
.ticket-link {
display: flex;
@@ -449,12 +452,14 @@ const handleJoin = () => {
justify-content: center;
width: 120rpx;
.ticket-icon {
font-size: 34rpx;
.ticket-svg {
width: 38rpx;
height: 38rpx;
}
.ticket-text {
font-size: 20rpx;
color: #94A3B8;
margin-top: 4rpx;
}
}

查看文件

@@ -1,6 +1,6 @@
<template>
<view class="activity-container">
<!-- 顶部模式切换选项卡 -->
<!-- 顶部模式切换选项卡 (矢量SVG模式图标) -->
<view class="tab-header">
<view
class="tab-item btn-active"
@@ -15,7 +15,8 @@
:class="{ active: currentTab === 'AA' }"
@click="switchTab('AA')"
>
<text class="tab-text">⚡ AA制车队</text>
<image class="tab-svg" :src="ICONS.bolt" mode="aspectFit" />
<text class="tab-text">AA制车队</text>
<view class="tab-indicator" v-if="currentTab === 'AA'"></view>
</view>
<view
@@ -23,15 +24,16 @@
:class="{ active: currentTab === 'PROJECT' }"
@click="switchTab('PROJECT')"
>
<text class="tab-text">🏆 项目制赛事</text>
<image class="tab-svg" :src="ICONS.gameTrophy" mode="aspectFit" />
<text class="tab-text">项目制赛事</text>
<view class="tab-indicator" v-if="currentTab === 'PROJECT'"></view>
</view>
</view>
<!-- 顶部状态小提示 -->
<!-- 顶部状态小提示 (矢量盾牌图标) -->
<view class="tips-bar">
<text class="tips-icon">💡</text>
<text class="tips-text">AA制活动人均AA平摊,项目制为房主全额悬赏或奖池分配</text>
<image class="tips-svg" :src="ICONS.shield" mode="aspectFit" />
<text class="tips-text">AA制活动人均AA平摊,项目制为房主全额悬赏或赛事奖池</text>
</view>
<!-- 活动列表 -->
@@ -56,14 +58,14 @@
<!-- 卡片标题 -->
<view class="activity-title">{{ act.title }}</view>
<!-- 详细信息条 -->
<!-- 详细信息条 (矢量时钟与麦克风) -->
<view class="meta-row">
<view class="meta-item">
<text class="meta-icon">⏰</text>
<image class="meta-svg" :src="ICONS.clock" mode="aspectFit" />
<text class="meta-val">{{ act.startTime }}</text>
</view>
<view class="meta-item">
<text class="meta-icon">🎙️</text>
<image class="meta-svg" :src="ICONS.mic" mode="aspectFit" />
<text class="meta-val">{{ act.location }}</text>
</view>
</view>
@@ -114,6 +116,7 @@
<script setup>
import { ref, computed } from 'vue';
import { ACTIVITIES_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const activities = ref(ACTIVITIES_LIST);
const currentTab = ref('ALL');
@@ -159,11 +162,19 @@ const createActivity = () => {
.tab-item {
flex: 1;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 18rpx 0;
position: relative;
border-radius: 14rpx;
.tab-svg {
width: 26rpx;
height: 26rpx;
}
.tab-text {
font-size: 26rpx;
color: #94A3B8;
@@ -202,9 +213,11 @@ const createActivity = () => {
padding: 12rpx 20rpx;
margin-bottom: 28rpx;
.tips-icon {
font-size: 24rpx;
.tips-svg {
width: 28rpx;
height: 28rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.tips-text {
font-size: 22rpx;
@@ -293,9 +306,11 @@ const createActivity = () => {
font-size: 22rpx;
color: #94A3B8;
.meta-icon {
font-size: 22rpx;
.meta-svg {
width: 24rpx;
height: 24rpx;
margin-right: 8rpx;
opacity: 0.8;
}
}
}

查看文件

@@ -250,14 +250,14 @@ const goBack = () => {
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20rpx rgba(16, 185, 129, 0.3);
box-shadow: 0 0 24rpx rgba(16, 185, 129, 0.45);
.digit-text {
font-size: 64rpx;
font-weight: 800;
color: #10B981;
font-family: 'Courier New', Courier, monospace;
text-shadow: 0 0 12rpx rgba(16, 185, 129, 0.6);
text-shadow: 0 0 16rpx rgba(16, 185, 129, 0.7);
}
}
}
@@ -305,6 +305,7 @@ const goBack = () => {
height: 12rpx;
border-radius: 50%;
background-color: #10B981;
box-shadow: 0 0 8rpx #10B981;
}
.time-text {
@@ -339,6 +340,7 @@ const goBack = () => {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.4);
}
.btn-back {

查看文件

@@ -33,13 +33,13 @@
</view>
</view>
<!-- 专属打招呼试听语音 -->
<!-- 专属打招呼试听语音 (矢量SVG播放条) -->
<view class="voice-card btn-active" @click="toggleVoice">
<view class="voice-icon-wrap">
<text class="play-icon">{{ isPlayingVoice ? '⏸' : '▶' }}</text>
<image class="play-svg" :src="isPlayingVoice ? ICONS.pause : ICONS.play" mode="aspectFit" />
</view>
<view class="voice-info">
<text class="voice-title">{{ isPlayingVoice ? '正在连麦试听语音...' : '点击试听打招呼语音' }}</text>
<text class="voice-title">{{ isPlayingVoice ? '正在连麦试听语音...' : '点击试听打招呼原声' }}</text>
<view class="voice-wave" v-if="isPlayingVoice">
<view class="bar b1"></view>
<view class="bar b2"></view>
@@ -125,10 +125,10 @@
</view>
</view>
<!-- 底部固定栏 -->
<!-- 底部固定栏 (限制居中与安全区) -->
<view class="bottom-bar">
<view class="icon-action btn-active" @click="chatOnline">
<text class="action-icon">💬</text>
<image class="action-svg" :src="ICONS.chat" mode="aspectFit" />
<text class="action-text">私聊</text>
</view>
<view class="price-summary">
@@ -144,6 +144,7 @@
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { EXPERTS_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const expert = ref(EXPERTS_LIST[0]);
const selectedServiceId = ref(EXPERTS_LIST[0].services[0] ? EXPERTS_LIST[0].services[0].id : null);
@@ -173,7 +174,7 @@ const selectService = (srv) => {
const toggleVoice = () => {
isPlayingVoice.value = !isPlayingVoice.value;
if (isPlayingVoice.value) {
uni.showToast({ title: '正在播放大神语音打招呼', icon: 'none' });
uni.showToast({ title: '正在播放大神打招呼原声', icon: 'none' });
setTimeout(() => {
isPlayingVoice.value = false;
}, 4000);
@@ -181,7 +182,7 @@ const toggleVoice = () => {
};
const chatOnline = () => {
uni.showToast({ title: '已开启即时连麦通道', icon: 'none' });
uni.showToast({ title: '已开启即时加密连麦通道', icon: 'none' });
};
const previewImage = (index) => {
@@ -261,6 +262,7 @@ const goToOrder = () => {
&.online {
background: #10B981;
box-shadow: 0 0 8rpx #10B981;
}
.pulse-dot {
@@ -351,9 +353,9 @@ const goToOrder = () => {
justify-content: center;
margin-right: 18rpx;
.play-icon {
color: #FFF;
font-size: 24rpx;
.play-svg {
width: 24rpx;
height: 24rpx;
}
}
@@ -569,6 +571,8 @@ const goToOrder = () => {
padding: 0 32rpx;
gap: 20rpx;
z-index: 100;
max-width: 450px;
margin: 0 auto;
.icon-action {
display: flex;
@@ -576,12 +580,14 @@ const goToOrder = () => {
align-items: center;
width: 80rpx;
.action-icon {
font-size: 32rpx;
.action-svg {
width: 36rpx;
height: 36rpx;
}
.action-text {
font-size: 18rpx;
color: #94A3B8;
margin-top: 4rpx;
}
}

查看文件

@@ -1,14 +1,14 @@
<template>
<view class="index-container">
<!-- 顶部搜索栏 -->
<!-- 顶部搜索栏 (矢量SVG搜索图标,满足无Emoji规范) -->
<view class="header-search">
<view class="search-box">
<text class="search-icon">🔍</text>
<image class="search-icon-img" :src="ICONS.search" mode="aspectFit" />
<input class="search-input" placeholder="搜索陪玩大神、游戏昵称或项目" placeholder-class="placeholder-text" />
</view>
</view>
<!-- 游戏分类滑动栏 -->
<!-- 游戏分类滑动栏 (SVG分类图标) -->
<scroll-view scroll-x class="category-scroll" :show-scrollbar="false">
<view class="category-list">
<view
@@ -18,7 +18,7 @@
:class="{ active: currentCategory === item.id }"
@click="selectCategory(item.id)"
>
<text class="cat-icon">{{ item.icon }}</text>
<image class="cat-icon-svg" :src="item.icon" mode="aspectFit" />
<text class="cat-name">{{ item.name }}</text>
<view v-if="item.hot" class="cat-badge">HOT</view>
</view>
@@ -36,25 +36,33 @@
</swiper-item>
</swiper>
<!-- 快捷功能金刚区 -->
<!-- 快捷功能金刚区 (纯矢量SVG图标) -->
<view class="quick-grid">
<view class="quick-item btn-active" @click="navTo('/pages/activity/list')">
<view class="quick-icon-bg bg-purple">🔥</view>
<view class="quick-icon-bg bg-purple">
<image class="quick-svg" :src="ICONS.teamUp" mode="aspectFit" />
</view>
<text class="quick-title">组队开黑</text>
<text class="quick-sub">AA制五排</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/activity/my-ticket')">
<view class="quick-icon-bg bg-green">🎟️</view>
<view class="quick-icon-bg bg-green">
<image class="quick-svg" :src="ICONS.ticket" mode="aspectFit" />
</view>
<text class="quick-title">我的门票</text>
<text class="quick-sub">4位核销码</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/order/list')">
<view class="quick-icon-bg bg-blue">⚡</view>
<view class="quick-icon-bg bg-blue">
<image class="quick-svg" :src="ICONS.orderList" mode="aspectFit" />
</view>
<text class="quick-title">服务订单</text>
<text class="quick-sub">状态实时看</text>
</view>
<view class="quick-item btn-active" @click="navTo('/pages/user/index')">
<view class="quick-icon-bg bg-orange">👑</view>
<view class="quick-icon-bg bg-orange">
<image class="quick-svg" :src="ICONS.expertBadge" mode="aspectFit" />
</view>
<text class="quick-title">大神中心</text>
<text class="quick-sub">双身份切换</text>
</view>
@@ -66,7 +74,10 @@
<view class="title-line"></view>
<text>精选在线大神</text>
</view>
<text class="section-more">实时在线</text>
<view class="section-more">
<view class="pulse-point"></view>
<text>实时在线</text>
</view>
</view>
<view class="expert-grid">
@@ -76,9 +87,11 @@
class="expert-card card-active"
@click="goToExpert(expert.id)"
>
<!-- 封面图 -->
<!-- 封面图构图优化:标准黄金比例与底部暗夜流光渐变 -->
<view class="card-cover-wrap">
<image class="card-cover" :src="expert.avatar" mode="aspectFill" />
<view class="cover-gradient"></view>
<view class="status-tag" :class="expert.isOnline ? 'online' : 'offline'">
<view class="pulse-dot" v-if="expert.isOnline"></view>
<text>{{ expert.isOnline ? '在线' : '离线' }}</text>
@@ -94,10 +107,10 @@
</view>
<view class="rank-badge">{{ expert.rankTag }}</view>
<!-- 语音试听条 -->
<!-- 语音试听条 (矢量SVG播放图标) -->
<view class="voice-bar btn-active" @click.stop="toggleVoice(expert.id)">
<text class="voice-icon">{{ playingId === expert.id ? '🔊' : '▶' }}</text>
<text class="voice-text">{{ playingId === expert.id ? '试听中...' : expert.voiceDuration + 's 语音' }}</text>
<image class="voice-icon-svg" :src="playingId === expert.id ? ICONS.pause : ICONS.play" mode="aspectFit" />
<text class="voice-text">{{ playingId === expert.id ? '试听中...' : expert.voiceDuration + 's 原声试听' }}</text>
<view class="wave-lines" v-if="playingId === expert.id">
<view class="wave-item w1"></view>
<view class="wave-item w2"></view>
@@ -122,6 +135,7 @@
<script setup>
import { ref, computed } from 'vue';
import { GAMES_LIST, BANNERS, EXPERTS_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const games = ref(GAMES_LIST);
const banners = ref(BANNERS);
@@ -201,9 +215,11 @@ const navTo = (url) => {
padding: 0 28rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
.search-icon {
font-size: 28rpx;
.search-icon-img {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
opacity: 0.8;
}
.search-input {
flex: 1;
@@ -219,24 +235,32 @@ const navTo = (url) => {
.category-scroll {
white-space: nowrap;
padding: 10rpx 32rpx 20rpx;
width: 100%;
box-sizing: border-box;
.category-list {
display: flex;
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 16rpx;
}
.category-item {
display: inline-flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
white-space: nowrap;
padding: 14rpx 28rpx;
background-color: #151D30;
border-radius: 32rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
position: relative;
.cat-icon {
font-size: 26rpx;
margin-right: 8rpx;
.cat-icon-svg {
width: 28rpx;
height: 28rpx;
margin-right: 10rpx;
}
.cat-name {
font-size: 24rpx;
@@ -283,7 +307,7 @@ const navTo = (url) => {
.banner-tag {
align-self: flex-start;
background: rgba(0, 0, 0, 0.25);
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(8px);
padding: 4rpx 14rpx;
border-radius: 8rpx;
@@ -320,19 +344,23 @@ const navTo = (url) => {
align-items: center;
.quick-icon-bg {
width: 76rpx;
height: 76rpx;
border-radius: 22rpx;
width: 80rpx;
height: 80rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 34rpx;
margin-bottom: 12rpx;
&.bg-purple { background: rgba(139, 92, 246, 0.2); }
&.bg-green { background: rgba(16, 185, 129, 0.2); }
&.bg-blue { background: rgba(99, 102, 241, 0.2); }
&.bg-orange { background: rgba(245, 158, 11, 0.2); }
.quick-svg {
width: 40rpx;
height: 40rpx;
}
&.bg-purple { background: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); }
&.bg-green { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
&.bg-blue { background: rgba(99, 102, 241, 0.15); border: 1px solid rgba(99, 102, 241, 0.3); }
&.bg-orange { background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
}
.quick-title {
@@ -371,11 +399,22 @@ const navTo = (url) => {
}
.section-more {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 22rpx;
color: #10B981;
background: rgba(16, 185, 129, 0.15);
padding: 4rpx 14rpx;
background: rgba(16, 185, 129, 0.12);
padding: 6rpx 16rpx;
border-radius: 16rpx;
.pulse-point {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #10B981;
box-shadow: 0 0 8rpx #10B981;
}
}
}
@@ -393,11 +432,20 @@ const navTo = (url) => {
.card-cover-wrap {
position: relative;
height: 320rpx;
height: 260rpx;
overflow: hidden;
.card-cover {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
}
.cover-gradient {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,0.1) 0%, rgba(21, 29, 48, 0.8) 100%);
}
.status-tag {
@@ -413,8 +461,9 @@ const navTo = (url) => {
font-weight: 600;
&.online {
background: rgba(16, 185, 129, 0.85);
background: rgba(16, 185, 129, 0.9);
color: #FFF;
box-shadow: 0 0 10rpx rgba(16, 185, 129, 0.5);
}
&.offline {
background: rgba(100, 116, 139, 0.8);
@@ -422,8 +471,8 @@ const navTo = (url) => {
}
.pulse-dot {
width: 10rpx;
height: 10rpx;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #FFF;
animation: pulse 1.5s infinite;
@@ -434,8 +483,9 @@ const navTo = (url) => {
position: absolute;
bottom: 14rpx;
right: 14rpx;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(4px);
background: rgba(10, 15, 29, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(6px);
font-size: 18rpx;
color: #E2E8F0;
padding: 4rpx 10rpx;
@@ -486,6 +536,7 @@ const navTo = (url) => {
border-radius: 6rpx;
display: inline-block;
margin-bottom: 12rpx;
border: 1px solid rgba(245, 158, 11, 0.2);
}
.voice-bar {
@@ -498,9 +549,9 @@ const navTo = (url) => {
padding: 0 16rpx;
margin-bottom: 16rpx;
.voice-icon {
font-size: 20rpx;
color: #818CF8;
.voice-icon-svg {
width: 20rpx;
height: 20rpx;
margin-right: 8rpx;
}
.voice-text {

查看文件

@@ -32,7 +32,7 @@
</view>
</view>
<!-- 开黑时段选择 -->
<!-- 开黑时段选择 (矢量SVG时段图标) -->
<view class="order-card">
<view class="section-title">服务时间</view>
<view class="time-tabs">
@@ -41,7 +41,10 @@
:class="{ active: timeType === 'NOW' }"
@click="timeType = 'NOW'"
>
<text class="tab-t">⚡ 即刻出发</text>
<view class="tab-title-wrap">
<image class="tab-svg" :src="ICONS.bolt" mode="aspectFit" />
<text class="tab-t">即刻出发</text>
</view>
<text class="tab-d">付款后立即连麦开黑</text>
</view>
<view
@@ -49,7 +52,10 @@
:class="{ active: timeType === 'APPOINT' }"
@click="timeType = 'APPOINT'"
>
<text class="tab-t">📅 预约时间</text>
<view class="tab-title-wrap">
<image class="tab-svg" :src="ICONS.clock" mode="aspectFit" />
<text class="tab-t">预约时段</text>
</view>
<text class="tab-d">今晚 20:30 开打</text>
</view>
</view>
@@ -101,6 +107,7 @@
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { EXPERTS_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const expert = ref(EXPERTS_LIST[0]);
const selectedService = ref(EXPERTS_LIST[0].services[0]);
@@ -301,12 +308,23 @@ const handlePay = () => {
}
}
.tab-t {
font-size: 26rpx;
color: #CBD5E1;
display: block;
.tab-title-wrap {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
margin-bottom: 6rpx;
.tab-svg {
width: 28rpx;
height: 28rpx;
}
.tab-t {
font-size: 26rpx;
color: #CBD5E1;
}
}
.tab-d {
font-size: 20rpx;
color: #64748B;
@@ -416,6 +434,8 @@ const handlePay = () => {
align-items: center;
padding: 0 32rpx;
z-index: 100;
max-width: 450px;
margin: 0 auto;
.amount-wrap {
display: flex;

查看文件

@@ -67,7 +67,7 @@
</view>
<view v-if="filteredOrders.length === 0" class="empty-state">
<text class="empty-icon">📦</text>
<image class="empty-svg" :src="ICONS.orderList" mode="aspectFit" />
<text class="empty-text">暂无此类订单</text>
</view>
</view>
@@ -77,6 +77,7 @@
<script setup>
import { ref, computed } from 'vue';
import { ORDERS_LIST } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const tabs = [
{ key: 'ALL', name: '全部' },
@@ -398,10 +399,12 @@ const rateExpert = (id) => {
text-align: center;
padding: 100rpx 0;
.empty-icon {
font-size: 64rpx;
.empty-svg {
width: 72rpx;
height: 72rpx;
opacity: 0.5;
margin: 0 auto 16rpx;
display: block;
margin-bottom: 16rpx;
}
.empty-text {
color: #64748B;

查看文件

@@ -8,27 +8,30 @@
<text class="user-name">{{ user.nickname }}</text>
<text class="vip-badge">VIP 黄金玩家</text>
</view>
<text class="user-uid">ID: {{ user.id }}</text>
<text class="user-uid">UID: {{ user.id }}</text>
</view>
<view class="edit-btn btn-active" @click="editProfile">
<text class="edit-icon">⚙️</text>
<image class="edit-svg" :src="ICONS.settings" mode="aspectFit" />
</view>
</view>
<!-- 核心亮点:双身份流光切换卡 -->
<!-- 核心亮点:双身份流光切换卡 (纯矢量与流光动效) -->
<view class="role-switch-card card-active" @click="toggleRole">
<view class="role-glow-border"></view>
<view class="role-content">
<view class="role-left">
<view class="role-title-row">
<text class="current-role-tag">{{ currentRole === 'PLAYER' ? '🎮 当前身份:普通玩家' : '👑 当前身份:接单大神' }}</text>
<view class="role-dot" :class="{ expert: currentRole === 'EXPERT' }"></view>
<text class="current-role-tag">
{{ currentRole === 'PLAYER' ? '当前身份:普通玩家' : '当前身份:接单大神' }}
</text>
</view>
<text class="role-sub">
{{ currentRole === 'PLAYER' ? '一键切换至大神接单工作台,开始接单赚收益' : '一键切换回玩家大厅,自由寻找开黑大神与车队' }}
{{ currentRole === 'PLAYER' ? '一键切换至大神接单工作台,开启派单收益' : '一键切换回玩家大厅,自由寻找开黑车队' }}
</text>
</view>
<button class="btn-switch-role btn-active">
{{ currentRole === 'PLAYER' ? '切换为大神 ⚡' : '切换回玩家 🔄' }}
{{ currentRole === 'PLAYER' ? '切换为大神' : '切回玩家端' }}
</button>
</view>
</view>
@@ -55,44 +58,52 @@
</view>
</view>
<!-- 核心功能金刚区 -->
<!-- 核心功能金刚区 (纯矢量SVG图标) -->
<view class="menu-grid-card">
<view class="menu-item btn-active" @click="navTo('/pages/activity/my-ticket')">
<view class="menu-icon bg-green">🎟️</view>
<view class="menu-icon bg-green">
<image class="menu-svg" :src="ICONS.ticket" mode="aspectFit" />
</view>
<text class="menu-title">我的门票</text>
<text class="menu-desc">4位核销码</text>
</view>
<view class="menu-item btn-active" @click="showInvitePoster">
<view class="menu-icon bg-purple">🤝</view>
<view class="menu-icon bg-purple">
<image class="menu-svg" :src="ICONS.share" mode="aspectFit" />
</view>
<text class="menu-title">推广赚钱</text>
<text class="menu-desc">二级返佣</text>
</view>
<view class="menu-item btn-active" @click="navTo('/pages/order/list')">
<view class="menu-icon bg-blue">📋</view>
<view class="menu-icon bg-blue">
<image class="menu-svg" :src="ICONS.orderList" mode="aspectFit" />
</view>
<text class="menu-title">所有订单</text>
<text class="menu-desc">对局与退款</text>
</view>
<view class="menu-item btn-active" @click="applyExpert">
<view class="menu-icon bg-orange">✨</view>
<view class="menu-icon bg-orange">
<image class="menu-svg" :src="ICONS.expertBadge" mode="aspectFit" />
</view>
<text class="menu-title">大神认证</text>
<text class="menu-desc">入驻接单</text>
</view>
</view>
<!-- 列表式功能菜单 -->
<!-- 列表式功能菜单 (纯矢量SVG图标) -->
<view class="list-menu-card">
<view class="list-row btn-active" @click="contactService">
<text class="row-icon">🎧</text>
<image class="row-svg" :src="ICONS.headphones" mode="aspectFit" />
<text class="row-label">官方在线客服</text>
<text class="row-arrow">></text>
</view>
<view class="list-row btn-active" @click="openNotice">
<text class="row-icon">🔔</text>
<image class="row-svg" :src="ICONS.bell" mode="aspectFit" />
<text class="row-label">平台消息与通知</text>
<text class="row-arrow">></text>
</view>
<view class="list-row btn-active" @click="openAbout">
<text class="row-icon">🛡️</text>
<image class="row-svg" :src="ICONS.shield" mode="aspectFit" />
<text class="row-label">平台规范与服务协议</text>
<text class="row-arrow">></text>
</view>
@@ -103,6 +114,7 @@
<script setup>
import { ref } from 'vue';
import { USER_PROFILE } from '@/common/mock.js';
import { ICONS } from '@/common/icons.js';
const user = ref(USER_PROFILE);
const currentRole = ref('PLAYER');
@@ -153,7 +165,7 @@ const handleWithdraw = () => {
const showInvitePoster = () => {
uni.showModal({
title: '🎉 二级分销推广',
title: '二级分销推广',
content: `您的专属邀请码为:PW${user.value.id}\n已成功邀请 ${user.value.invitedCount} 位好友!\n好友下单您将实时享受最高 15% 自动分润提成。`,
showCancel: false,
confirmText: '保存太阳码'
@@ -250,8 +262,10 @@ const navTo = (url) => {
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.08);
.edit-icon {
font-size: 32rpx;
.edit-svg {
width: 32rpx;
height: 32rpx;
opacity: 0.8;
}
}
}
@@ -275,12 +289,30 @@ const navTo = (url) => {
flex: 1;
margin-right: 20rpx;
.current-role-tag {
font-size: 28rpx;
font-weight: 700;
color: #FFFFFF;
display: block;
.role-title-row {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 6rpx;
.role-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #6366F1;
box-shadow: 0 0 8rpx #6366F1;
&.expert {
background: #10B981;
box-shadow: 0 0 8rpx #10B981;
}
}
.current-role-tag {
font-size: 28rpx;
font-weight: 700;
color: #FFFFFF;
}
}
.role-sub {
@@ -410,13 +442,17 @@ const navTo = (url) => {
display: flex;
align-items: center;
justify-content: center;
font-size: 34rpx;
margin-bottom: 10rpx;
&.bg-green { background: rgba(16, 185, 129, 0.2); }
&.bg-purple { background: rgba(139, 92, 246, 0.2); }
&.bg-blue { background: rgba(99, 102, 241, 0.2); }
&.bg-orange { background: rgba(245, 158, 11, 0.2); }
.menu-svg {
width: 38rpx;
height: 38rpx;
}
&.bg-green { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
&.bg-purple { background: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); }
&.bg-blue { background: rgba(99, 102, 241, 0.15); border: 1px solid rgba(99, 102, 241, 0.3); }
&.bg-orange { background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
}
.menu-title {
@@ -448,9 +484,11 @@ const navTo = (url) => {
border-bottom: none;
}
.row-icon {
font-size: 28rpx;
.row-svg {
width: 32rpx;
height: 32rpx;
margin-right: 20rpx;
opacity: 0.85;
}
.row-label {