update:排位赛界面、排行榜界面改版,新增agents描述文档

This commit is contained in:
2026-05-09 10:04:15 +08:00
parent 8c66ef78c6
commit 0e936b8e20
22 changed files with 1249 additions and 335 deletions

269
AGENTS.md Normal file
View File

@@ -0,0 +1,269 @@
# AI Agent 企业级行为策略Ultimate Edition
## 核心目标
AI 应:
* 像高级工程师一样思考
* 保持智能
* 保持上下文理解能力
* 保持组件联动能力
* 同时避免无意义 token 消耗
目标不是限制 AI。
目标是:
* 智能
* 克制
* 稳定
* 高效
---
# AI 工作模式
默认采用:
Think First
Explore Second
Modify Last
即:
1. 先理解需求
2. 再推理可能相关文件
3. 再最小化读取
4. 最后修改代码
禁止:
* 无脑全项目扫描
* 不经思考直接 grep
* 无限递归读取
---
# 智能按需扫描(核心规则)
允许 AI 自动:
* 分析当前任务
* 分析 import
* 分析组件依赖
* 分析 store 依赖
* 分析 api 依赖
* 分析 types 依赖
* 分析 utils 依赖
允许:
* 自动读取直接依赖文件
* 自动修复 import
* 自动修复类型引用
* 自动分析运行链路
但必须:
* 最小化扫描范围
* 最小化 token 消耗
* 禁止无限递归探索
---
# 扫描深度限制
默认最大依赖深度:
2 层
例如:
index.vue
-> ProductCard.vue
-> product.ts
允许读取:
* ProductCard.vue
* product.ts
禁止继续无限扫描。
如果任务复杂:
必须先输出分析计划,
等待确认后再扩大扫描范围。
---
# AI 自由发挥边界
允许:
* 合理重构
* 合理组件化
* 合理优化结构
* 合理优化样式
* 合理优化复用
* 合理修复低级问题
* 合理修复 import
* 合理修复类型错误
禁止:
* 为了炫技重构项目
* 无意义抽象
* 过度设计
* 无意义拆分
* 无意义新增依赖
* 自动升级依赖
---
# Token 经济策略
Token 应优先用于:
* 推理
* 架构理解
* 业务逻辑
* UI 结构优化
* 类型安全
* 组件联动
禁止浪费在:
* 全项目 grep
* 重复读取
* 重复输出
* 重复解释
* 输出完整项目
* 输出未修改代码
---
# 页面生成规则Figma / uni-app
允许:
* 自动组件化
* 自动布局优化
* 自动结构优化
* 自动提取公共组件
优先:
* flex 布局
* 可维护性
* uni-app 最佳实践
* 低嵌套结构
* 高复用结构
禁止:
* div 套 div
* 全 absolute 页面
* 垃圾 HTML
* 无意义嵌套
* 内联 style 泛滥
---
# uni-app 规则
必须:
* 使用 view/text/image
* px 转 rpx
* 使用 script setup
* scoped scss
* 兼容:
* H5
* 微信小程序
* App
---
# 大任务策略
复杂任务:
必须:
1. 先分析
2. 先规划
3. 先输出方案
4. 等待确认
再:
5. 编码
禁止直接进入大规模代码生成。
---
# 修改策略
优先:
* diff 修改
* 小范围 patch
* 保持现有架构
* 保持现有组件体系
* 保持现有 API 结构
允许:
* 小范围智能优化
禁止:
* 全项目重构
* 无关文件修改
---
# 高级工程师行为模式
AI 应像高级工程师:
* 先思考
* 再探索
* 再修改
而不是:
* 无脑扫描器
* Token 消耗机器
* 低级代码生成器
AI 应主动:
* 控制扫描范围
* 控制输出长度
* 控制修改范围
* 控制复杂度
同时保持:
* 智能
* 联动能力
* 架构理解能力
---
# 默认输出规则
默认:
* 仅输出修改部分
* 不重复未修改代码
* 少解释
* 优先 patch
* 优先 diff
除非用户明确要求:
否则不要输出完整项目。

View File

@@ -496,3 +496,63 @@ export const kickPlayerAPI = (number, userId) => {
userId, userId,
}); });
}; };
// 获取赛季列表
export const getSeasonList = () => {
return request("GET", "/index/season/list");
};
// 获取赛季统计
export const getSeasonStats = (seasonId) => {
const data = {};
if (seasonId !== undefined && seasonId !== null) data.seasonId = seasonId;
return request("GET", "/index/season/stats", data);
};
//获取积分榜
export const getScoreRankList = (seasonId, page, perPage) => {
return request("GET", "/index/score/rank/list", {
seasonId,
page,
perPage
});
};
// 获取10环排行榜
export const getTenRingRankList = (seasonId, page, perPage) => {
return request("GET", "/index/tenRing/rank/list", {
seasonId,
page,
perPage
});
};
// 获取MVP排行榜
export const getMvpRankList = (seasonId, page, perPage) => {
return request("GET", "/index/mvp/rank/list", {
seasonId,
page,
perPage
});
};
// 获取我的积分排名
export const getMyScoreRank = (seasonId) => {
const data = {};
if (seasonId !== undefined && seasonId !== null) data.seasonId = seasonId;
return request("GET", "/index/myScoreRank", data);
};
// 获取我的MVP排名
export const getMyMvpRank = (seasonId) => {
const data = {};
if (seasonId !== undefined && seasonId !== null) data.seasonId = seasonId;
return request("GET", "/index/myMvpRank", data);
};
// 获取我的10环排名
export const getMyTenRingRank = (seasonId) => {
const data = {};
if (seasonId !== undefined && seasonId !== null) data.seasonId = seasonId;
return request("GET", "/index/myTenRingRank", data);
};

View File

@@ -51,6 +51,12 @@ const props = defineProps({
src="https://static.shelingxingqiu.com/attachment/2026-01-05/dfgf3b5kp459tfyn0f.png" src="https://static.shelingxingqiu.com/attachment/2026-01-05/dfgf3b5kp459tfyn0f.png"
mode="widthFix" mode="widthFix"
/> />
<image
class="bg-image"
v-if="type === 6"
src="../static/rank/rank-bg.png"
mode="widthFix"
/>
<view class="bg-overlay" v-if="type === 0"></view> <view class="bg-overlay" v-if="type === 0"></view>
</view> </view>
</template> </template>
@@ -67,7 +73,7 @@ const props = defineProps({
.bg-image { .bg-image {
width: 100%; width: 100%;
height: 100%; /* height: 100%; */
} }
.bg-overlay { .bg-overlay {

View File

@@ -13,7 +13,7 @@ import {
getDeviceBatteryAPI, getDeviceBatteryAPI,
getHomeData, getHomeData,
getMyDevicesAPI, getMyDevicesAPI,
getRankListAPI, getScoreRankList,
silentLoginAPI, silentLoginAPI,
} from "@/apis"; } from "@/apis";
import {topThreeColors} from "@/constants"; import {topThreeColors} from "@/constants";
@@ -26,15 +26,23 @@ const {
updateConfig, updateConfig,
updateUser, updateUser,
updateDevice, updateDevice,
updateRank,
getLvlName, getLvlName,
getLvlNameByScore, getLvlNameByScore,
updateOnline, updateOnline,
} = store; } = store;
const {user, device, rankData, online, game} = storeToRefs(store); const {user, device, online, game} = storeToRefs(store);
const showModal = ref(false); const showModal = ref(false);
const showGuide = ref(false); const showGuide = ref(false);
const scoreRankList = ref([]);
// 提取积分榜接口返回的榜单数组,兼容数组和对象两种返回格式。
const getScoreRankData = (result) => {
if (Array.isArray(result)) return result;
if (Array.isArray(result?.list)) return result.list;
if (Array.isArray(result?.items)) return result.items;
return [];
};
const toPage = async (path) => { const toPage = async (path) => {
if (!user.value.id) { if (!user.value.id) {
@@ -84,15 +92,15 @@ onShow(async () => {
} }
} }
const promises = [getRankListAPI()]; const promises = [getScoreRankList(undefined, 1, 10)];
if (token || user.value.id) { if (token || user.value.id) {
promises.push(getHomeData()); promises.push(getHomeData());
} }
const [rankList, homeData] = await Promise.all(promises); const [rankList, homeData] = await Promise.all(promises);
console.log("排行数据", rankList); console.log("积分榜数据", rankList);
updateRank(rankList); scoreRankList.value = getScoreRankData(rankList).slice(0, 10);
if (homeData) { if (homeData) {
console.log("首页数据:", homeData); console.log("首页数据:", homeData);
@@ -216,7 +224,7 @@ onShareTimeline(() => {
class="player-avatar" class="player-avatar"
:style="{ :style="{
zIndex: 8 - i, zIndex: 8 - i,
borderColor: rankData.rank[i - 1] borderColor: scoreRankList[i - 1]
? topThreeColors[i - 1] || '#000' ? topThreeColors[i - 1] || '#000'
: '#000', : '#000',
}" }"
@@ -227,15 +235,15 @@ onShareTimeline(() => {
<view v-if="i > 3">{{ i }}</view> <view v-if="i > 3">{{ i }}</view>
<image <image
:src=" :src="
rankData.rank[i - 1] scoreRankList[i - 1]
? rankData.rank[i - 1].avatar ? (scoreRankList[i - 1].avatar || '../static/user-icon.png')
: '../static/user-icon-dark.png' : '../static/user-icon-dark.png'
" "
mode="aspectFill" mode="aspectFill"
/> />
</view> </view>
<view class="more-players"> <view class="more-players">
<text>{{ rankData.rank.length }}</text> <text>{{ scoreRankList.length }}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@@ -1,58 +1,333 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { computed, nextTick, onMounted, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Avatar from "@/components/Avatar.vue"; import Avatar from "@/components/Avatar.vue";
import {
getMvpRankList,
getMyMvpRank,
getMyScoreRank,
getMyTenRingRank,
getScoreRankList,
getTenRingRankList,
} from "@/apis";
import { capsuleHeight } from "@/util"; import { capsuleHeight } from "@/util";
import useStore from "@/store"; import useStore from "@/store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
const PAGE_SIZE = 10;
const store = useStore(); const store = useStore();
const { user, rankData } = storeToRefs(store); const { user } = storeToRefs(store);
const { getLvlName } = store; const { getLvlName } = store;
const selectedIndex = ref(0); const createRankState = () => ({
const currentList = ref([]); list: [],
const myData = ref({}); page: 0,
const addBg = ref(false); pageSize: PAGE_SIZE,
loading: false,
onMounted(async () => { noMore: false,
handleSelect(0); loaded: false,
scrollTop: 0,
myData: null,
myDataLoaded: false,
}); });
const handleSelect = (index) => { const rankTabs = [
selectedIndex.value = index; {
myData.value = {}; key: "score",
currentList.value = []; title: "积分榜",
if (index === 0) { subTitle: "排位赛积分",
currentList.value = rankData.value.rank; listApi: getScoreRankList,
} else if (index === 1) { myApi: getMyScoreRank,
currentList.value = rankData.value.mvpRank; },
} else if (index === 2) { {
currentList.value = rankData.value.ringRank; key: "mvp",
title: "MVP榜",
subTitle: "MVP次数",
listApi: getMvpRankList,
myApi: getMyMvpRank,
},
{
key: "tenRing",
title: "十环榜",
subTitle: "十环次数",
listApi: getTenRingRankList,
myApi: getMyTenRingRank,
},
];
// 解析 ranking 页面传入的榜单参数,进入页面时默认选中对应 tab。
const getTabIndexByRouteParam = (tab) => {
if (tab === "mvp") return 1;
if (tab === "tenRing") return 2;
return 0;
};
const rankStates = ref({
score: createRankState(),
mvp: createRankState(),
tenRing: createRankState(),
});
const selectedIndex = ref(0);
const initialTabIndex = ref(0);
const pageMounted = ref(false);
const initializedFromRoute = ref(false);
const addBg = ref(false);
const currentScrollTop = ref(0);
const restoreScrollTop = ref(0);
const tabSwitchAnimating = ref(false);
const suppressScrollSync = ref(false);
const suppressLoadMore = ref(false);
const stickyTabsTop = capsuleHeight + 50;
const stickyTabsActive = ref(false);
const tabsStickyThreshold = ref(0);
const tabsStickyReady = ref(false);
const tabsHeight = ref(0);
const getTabConfig = (index = selectedIndex.value) => rankTabs[index];
const getTabKey = (index = selectedIndex.value) => getTabConfig(index).key;
// 统一提取榜单接口返回的列表数据,兼容数组和对象两种返回格式。
const getRankListFromResponse = (result) => {
if (Array.isArray(result)) return result;
if (Array.isArray(result?.list)) return result.list;
if (Array.isArray(result?.items)) return result.items;
return [];
};
// 为当前登录用户构造默认的个人榜单信息,避免接口未返回时底部区域缺数据。
const buildDefaultMyData = () => ({
rank: null,
userId: user.value.id,
name: user.value.nickName,
avatar: user.value.avatar,
totalScore: 0,
mvpCount: 0,
tenRings: 0,
totalGames: 0,
totalCount: 0,
rankName: user.value.lvlName,
rankLvl: user.value.rankLvl,
});
const currentTabKey = computed(() => getTabKey(selectedIndex.value));
const currentState = computed(() => rankStates.value[currentTabKey.value]);
const currentList = computed(() => currentState.value.list);
const currentSubTitle = computed(() => getTabConfig(selectedIndex.value).subTitle);
const currentMyData = computed(() => {
if (!user.value.id) return null;
return currentState.value.myData || buildDefaultMyData();
});
// 统一格式化段位和场次文案,兼容不同接口的字段命名。
const formatLevelText = (item = {}) => {
const levelName = item.rankName || getLvlName(item.rankLvl) || "暂无段位";
const totalGames = item.totalGames ?? item.TotalGames ?? 0;
return `${levelName}${totalGames}`;
};
// 统一读取榜单项的排名字段,没有后端 rank 时回退到前端序号。
const getDisplayRank = (item = {}, index = 0) => {
return item.rank ?? index + 1;
};
// 底部个人排名在未上榜时展示占位符,而不是空白。
const getDisplayMyRank = (item = {}) => {
return item.rank ?? "-";
};
const getScoreValue = (item = {}) => item.totalScore ?? 0;
const getMvpValue = (item = {}) => item.mvpCount ?? item.totalScore ?? 0;
const getTenRingValue = (item = {}) =>
item.tenRings ?? item.TenRings ?? item.totalScore ?? 0;
// 根据当前选中的榜单类型,读取对应的展示值。
const getRankValue = (item = {}, index = selectedIndex.value) => {
if (index === 0) return getScoreValue(item);
if (index === 1) return getMvpValue(item);
return getTenRingValue(item);
};
const getRankUnit = (index = selectedIndex.value) => {
if (index === 0) return "分";
return "次";
};
// 统一设置页面当前的视觉滚动状态,避免吸顶和顶部背景不同步。
const syncScrollVisualState = (scrollTop = 0) => {
currentScrollTop.value = scrollTop;
addBg.value = scrollTop > 100;
if (!tabsStickyReady.value) {
stickyTabsActive.value = false;
return;
} }
if (user.value.id) { stickyTabsActive.value = scrollTop >= tabsStickyThreshold.value;
currentList.value.some((item) => { };
if (item.userId === user.value.id) {
myData.value = item; // 只保留一条滚动恢复链路:从当前滚动位置平滑滚到目标位置,避免多套控制同时生效造成闪烁。
return true; const applyScrollPosition = async (
} fromScrollTop = currentScrollTop.value,
return false; toScrollTop = 0,
}); withAnimation = false
if (!myData.value.userId) { ) => {
myData.value = { tabSwitchAnimating.value = withAnimation;
userId: user.value.id, restoreScrollTop.value = fromScrollTop;
TotalGames: 0, await nextTick();
totalScore: 0, restoreScrollTop.value = toScrollTop;
mvpCount: 0, syncScrollVisualState(toScrollTop);
TenRings: 0, };
};
// 请求指定榜单的某一页数据,只有当前榜单会追加分页,不影响其他榜单的浏览状态。
const loadRankPage = async (tabKey, { reset = false } = {}) => {
const state = rankStates.value[tabKey];
const config = rankTabs.find((item) => item.key === tabKey);
if (!config || state.loading) return;
if (!reset && state.noMore) return;
const nextPage = reset ? 1 : state.page + 1;
state.loading = true;
if (reset) state.noMore = false;
try {
const result = await config.listApi(undefined, nextPage, PAGE_SIZE);
const list = getRankListFromResponse(result);
state.list = reset ? list : state.list.concat(list);
state.page = nextPage;
state.loaded = true;
state.noMore = list.length < PAGE_SIZE;
} catch (error) {
if (reset) {
state.list = [];
state.page = 0;
state.loaded = false;
state.noMore = false;
} }
uni.showToast({
title: "排行榜加载失败",
icon: "none",
});
console.error("load rank page error", error);
} finally {
state.loading = false;
} }
}; };
const onScrollView = (e) => { // 每个榜单独立请求一次个人排名信息,切回该榜单时直接复用,避免打断浏览上下文。
addBg.value = e.detail.scrollTop > 100; const loadMyRankData = async (tabKey) => {
if (!user.value.id) return;
const state = rankStates.value[tabKey];
const config = rankTabs.find((item) => item.key === tabKey);
if (!config || state.myDataLoaded) return;
try {
const result = await config.myApi();
state.myData = {
...buildDefaultMyData(),
...(result || {}),
};
} catch (error) {
state.myData = buildDefaultMyData();
console.error("load my rank data error", error);
} finally {
state.myDataLoaded = true;
}
}; };
const subTitles = ["排位赛积分", "MVP次数", "十环次数"]; // 首次进入或切换到未加载过的榜单时,初始化它的分页数据和个人横条数据。
const ensureTabReady = async (index = selectedIndex.value) => {
const tabKey = getTabKey(index);
const state = rankStates.value[tabKey];
if (!state.loaded) {
await loadRankPage(tabKey, { reset: true });
}
if (user.value.id && !state.myDataLoaded) {
await loadMyRankData(tabKey);
}
await nextTick();
return state.scrollTop || 0;
};
onLoad((options = {}) => {
initialTabIndex.value = getTabIndexByRouteParam(options.tab);
selectedIndex.value = initialTabIndex.value;
if (pageMounted.value && !initializedFromRoute.value) {
initializePage();
}
});
// 页面初始化同时兼容 onLoad 和 onMounted 的先后顺序,确保首屏一定落到路由指定的榜单。
const initializePage = async () => {
if (initializedFromRoute.value) return;
initializedFromRoute.value = true;
const nextScrollTop = await ensureTabReady(selectedIndex.value);
await applyScrollPosition(0, nextScrollTop, false);
setTimeout(() => {
measureTabsMetrics();
}, 0);
};
onMounted(async () => {
pageMounted.value = true;
await initializePage();
});
// 切换榜单时保留原榜单的列表和滚动位置,切回来后继续从之前的位置浏览。
const handleSelect = async (index) => {
if (index === selectedIndex.value) return;
const previousTabKey = currentTabKey.value;
rankStates.value[previousTabKey].scrollTop = currentScrollTop.value;
const previousScrollTop = currentScrollTop.value;
suppressScrollSync.value = true;
suppressLoadMore.value = true;
selectedIndex.value = index;
const nextScrollTop = await ensureTabReady(index);
await applyScrollPosition(previousScrollTop, nextScrollTop, false);
setTimeout(() => {
tabSwitchAnimating.value = false;
suppressScrollSync.value = false;
suppressLoadMore.value = false;
}, 220);
};
// 触底后只加载当前榜单的下一页数据,其他榜单的数据和页码保持不变。
const loadMore = async () => {
if (suppressLoadMore.value) return;
await loadRankPage(currentTabKey.value);
};
// 实时记录当前榜单的滚动位置,切换回来时恢复到上一次浏览位置。
const onScrollView = (e) => {
const scrollTop = e.detail.scrollTop || 0;
if (suppressScrollSync.value) return;
syncScrollVisualState(scrollTop);
rankStates.value[currentTabKey.value].scrollTop = scrollTop;
};
// 计算 tab 在滚动内容中的真实位置和高度,作为吸顶切换的唯一依据。
const measureTabsMetrics = () => {
const query = uni.createSelectorQuery();
query
.select("#rank-list-content-start")
.boundingClientRect()
.select(".rank-tabs-anchor")
.boundingClientRect()
.exec((res = []) => {
const [startRect, rect] = res;
if (!startRect || !rect) return;
const tabOffset = rect.top - startRect.top;
tabsStickyThreshold.value = Math.max(0, tabOffset - 92);
tabsHeight.value = rect.height || 0;
tabsStickyReady.value = true;
syncScrollVisualState(currentScrollTop.value);
});
};
</script> </script>
<template> <template>
@@ -75,18 +350,32 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
</view> </view>
<scroll-view <scroll-view
scroll-y scroll-y
:scroll-with-animation="tabSwitchAnimating"
:scroll-top="restoreScrollTop"
@scroll="onScrollView" @scroll="onScrollView"
:style="{ height: myData.userId ? '90vh' : '100vh' }" @scrolltolower="loadMore"
:style="{ height: user.id ? '90vh' : '100vh' }"
> >
<view id="rank-list-content-start" class="content-start-anchor"></view>
<image <image
src="https://static.shelingxingqiu.com/attachment/2025-09-25/dd1p9b3wcrwnlnghiq.png" src="https://static.shelingxingqiu.com/attachment/2025-09-25/dd1p9b3wcrwnlnghiq.png"
mode="widthFix" mode="widthFix"
class="header-bg" class="header-bg"
@load="measureTabsMetrics"
/> />
<view class="rank-tabs"> <view
v-if="stickyTabsActive"
class="rank-tabs-placeholder"
:style="{ height: `${tabsHeight}px` }"
/>
<view
class="rank-tabs rank-tabs-anchor"
:class="{ 'rank-tabs-anchor-fixed': stickyTabsActive }"
:style="stickyTabsActive ? { top: `${stickyTabsTop}px` } : {}"
>
<view <view
v-for="(rankType, index) in ['积分榜', 'MVP榜', '十环榜']" v-for="(rankType, index) in rankTabs"
:key="index" :key="rankType.key"
:style="{ :style="{
fontSize: index === selectedIndex ? '16px' : '14px', fontSize: index === selectedIndex ? '16px' : '14px',
color: index === selectedIndex ? '#000' : '#fff', color: index === selectedIndex ? '#000' : '#fff',
@@ -94,18 +383,18 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
}" }"
@tap="handleSelect(index)" @tap="handleSelect(index)"
> >
{{ rankType }} {{ rankType.title }}
</view> </view>
</view> </view>
<view class="rank-list"> <view class="rank-list">
<view class="rank-list-header"> <view class="rank-list-header">
<text>排名</text> <text>排名</text>
<text>用户ID</text> <text>用户ID</text>
<text>{{ subTitles[selectedIndex] }}</text> <text>{{ currentSubTitle }}</text>
</view> </view>
<view <view
v-for="(item, index) in currentList" v-for="(item, index) in currentList"
:key="index" :key="`${currentTabKey}-${index}-${item.userId || item.name}`"
class="rank-list-item" class="rank-list-item"
:style="{ :style="{
backgroundColor: index % 2 === 0 ? '#9898981f' : 'transparent', backgroundColor: index % 2 === 0 ? '#9898981f' : 'transparent',
@@ -147,65 +436,60 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
src="../static/champ3.png" src="../static/champ3.png"
mode="widthFix" mode="widthFix"
/> />
<view v-if="index > 2" class="view-crown">{{ index + 1 }}</view> <view v-if="index > 2" class="view-crown">
{{ getDisplayRank(item, index) }}
</view>
<Avatar :src="item.avatar" /> <Avatar :src="item.avatar" />
<view class="rank-item-content"> <view class="rank-item-content">
<text class="truncate">{{ item.name }}</text> <text class="truncate">{{ item.name }}</text>
<text>{{ getLvlName(item.rankLvl) }}{{ item.TotalGames }}</text> <text>{{ formatLevelText(item) }}</text>
</view> </view>
<text class="rank-item-integral" v-if="selectedIndex === 0"> <text class="rank-item-integral">
<text <text
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }" :style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ item.totalScore }} </text >
> {{ getRankValue(item) }}
</text> </text>
<text class="rank-item-integral" v-if="selectedIndex === 1"> {{ getRankUnit() }}
<text
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ item.mvpCount }} </text
>
</text>
<text class="rank-item-integral" v-if="selectedIndex === 2">
<text
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ item.TenRings }} </text
>
</text> </text>
</view> </view>
<view v-if="!currentList.length" class="no-data"> <view
<text>筹备中...</text> v-if="currentState.loading && !currentList.length"
class="no-data"
>
<text>加载中...</text>
</view>
<view
v-else-if="!currentState.loading && !currentList.length"
class="no-data"
>
<text>暂无数据</text>
</view>
<view v-else class="list-tip">
<text v-if="currentState.loading">加载中...</text>
<text v-else-if="currentState.noMore">没有更多了</text>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view class="my-rank-data" v-if="myData.userId"> <view class="my-rank-data" v-if="currentMyData">
<image <image
src="https://static.shelingxingqiu.com/attachment/2025-08-05/dbuaf19pf7qd8ps0uh.png" src="https://static.shelingxingqiu.com/attachment/2025-08-05/dbuaf19pf7qd8ps0uh.png"
mode="widthFix" mode="widthFix"
/> />
<text>{{ myData.rank }}</text> <text>{{ getDisplayMyRank(currentMyData) }}</text>
<Avatar :src="user.avatar" /> <Avatar :src="currentMyData.avatar || user.avatar" />
<view class="rank-item-content"> <view class="rank-item-content">
<text class="truncate">{{ user.nickName }}</text> <text class="truncate">{{ currentMyData.name || user.nickName }}</text>
<text>{{ user.lvlName }}{{ myData.TotalGames }}</text> <text>{{ formatLevelText(currentMyData) }}</text>
</view> </view>
<text class="rank-item-integral" v-if="selectedIndex === 0"> <text class="rank-item-integral">
<text <text
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }" :style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ myData.totalScore || 0 }}</text >
></text {{ getRankValue(currentMyData) }}
> </text>
<text class="rank-item-integral" v-if="selectedIndex === 1"> {{ getRankUnit() }}
<text </text>
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ myData.mvpCount || 0 }}</text
></text
>
<text class="rank-item-integral" v-if="selectedIndex === 2">
<text
:style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
>{{ myData.TenRings || 0 }}</text
></text
>
</view> </view>
</view> </view>
</template> </template>
@@ -214,9 +498,16 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
.container { .container {
width: 100%; width: 100%;
} }
.content-start-anchor {
width: 100%;
height: 0;
}
.header-bg { .header-bg {
width: 100%; width: 100%;
} }
.header { .header {
width: 100%; width: 100%;
height: 50px; height: 50px;
@@ -227,6 +518,7 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
z-index: 10; z-index: 10;
overflow: hidden; overflow: hidden;
} }
.header-back { .header-back {
width: 22px; width: 22px;
height: 22px; height: 22px;
@@ -234,6 +526,7 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
margin-top: 5px; margin-top: 5px;
position: relative; position: relative;
} }
.header > image:first-child { .header > image:first-child {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
@@ -242,25 +535,40 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
left: 0; left: 0;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
.header > text { .header > text {
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
transition: all 0.5s ease; transition: all 0.5s ease;
position: relative; position: relative;
} }
.rank-tabs { .rank-tabs {
width: calc(100% - 20px); width: calc(100% - 20px);
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
padding: 0 10px; padding: 20rpx 10px;
margin-top: -15px;
} }
.rank-tabs > view { .rank-tabs > view {
width: 25%; width: 25%;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
border-radius: 20px; border-radius: 20px;
} }
.rank-tabs-placeholder {
width: 100%;
}
.rank-tabs-anchor-fixed {
position: fixed;
left: 0;
z-index: 11;
background: #000000;
backdrop-filter: blur(8px);
}
.rank-list { .rank-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -268,11 +576,12 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
width: calc(100% - 20px); width: calc(100% - 20px);
color: #fff9; color: #fff9;
font-size: 12px; font-size: 12px;
margin: 10px; margin: 0 10px 10px 10px;
border: 1px solid rgb(255 217 71 / 0.2); border: 1px solid rgb(255 217 71 / 0.2);
border-radius: 10px; border-radius: 10px;
background-color: #313131; background-color: #313131;
} }
.rank-list > view { .rank-list > view {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -281,20 +590,25 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.rank-list-header { .rank-list-header {
width: calc(100% - 20px) !important; width: calc(100% - 20px) !important;
padding: 10px; padding: 10px;
} }
.rank-list-header > text:nth-child(2) { .rank-list-header > text:nth-child(2) {
width: 14%; width: 14%;
} }
.rank-list-header > text:last-child { .rank-list-header > text:last-child {
width: 30%; width: 30%;
text-align: right; text-align: right;
} }
.rank-list-item { .rank-list-item {
padding: 10px 0; padding: 10px 0;
} }
.player-bg { .player-bg {
position: absolute; position: absolute;
width: 100%; width: 100%;
@@ -302,12 +616,14 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
top: 0; top: 0;
left: 0; left: 0;
} }
.player-crown { .player-crown {
position: relative; position: relative;
width: 27px; width: 27px;
height: 27px; height: 27px;
margin: 0 15px; margin: 0 15px;
} }
.view-crown { .view-crown {
width: 27px; width: 27px;
height: 27px; height: 27px;
@@ -319,6 +635,7 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
background-color: #676767; background-color: #676767;
position: relative; position: relative;
} }
.rank-item-content { .rank-item-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -328,17 +645,20 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
position: relative; position: relative;
padding-left: 10px; padding-left: 10px;
} }
.rank-item-content > text:first-child { .rank-item-content > text:first-child {
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
margin-bottom: 3px; margin-bottom: 3px;
width: 120px; width: 120px;
} }
.rank-list-item > text:last-child { .rank-list-item > text:last-child {
margin-right: 10px; margin-right: 10px;
width: 56px; width: 56px;
text-align: right; text-align: right;
} }
.my-rank-data { .my-rank-data {
width: calc(100% - 30px); width: calc(100% - 30px);
padding: 15px; padding: 15px;
@@ -352,12 +672,14 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
overflow: hidden; overflow: hidden;
border-radius: 10px; border-radius: 10px;
} }
.my-rank-data > image:first-child { .my-rank-data > image:first-child {
position: absolute; position: absolute;
width: 100%; width: 100%;
left: 0; left: 0;
top: -5px; top: -5px;
} }
.my-rank-data > text:nth-child(2) { .my-rank-data > text:nth-child(2) {
background-color: #c1a434; background-color: #c1a434;
position: relative; position: relative;
@@ -369,20 +691,24 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
min-width: 15px; min-width: 15px;
text-align: center; text-align: center;
} }
.my-rank-data > text:last-child { .my-rank-data > text:last-child {
position: relative; position: relative;
margin-right: 10px; margin-right: 10px;
width: 65px; width: 65px;
text-align: right; text-align: right;
} }
.my-rank-data > .rank-item-content > text:first-child { .my-rank-data > .rank-item-content > text:first-child {
color: #fed847; color: #fed847;
} }
.my-rank-data > .rank-item-integral { .my-rank-data > .rank-item-integral {
color: #fff9; color: #fff9;
font-size: 12px; font-size: 12px;
margin-right: 0 !important; margin-right: 0 !important;
} }
.no-data { .no-data {
width: 100%; width: 100%;
height: 400px; height: 400px;
@@ -392,4 +718,11 @@ const subTitles = ["排位赛积分", "MVP次数", "十环次数"];
color: #fff9; color: #fff9;
font-size: 14px; font-size: 14px;
} }
.list-tip {
justify-content: center !important;
color: #fff9;
font-size: 12px;
min-height: 60rpx;
}
</style> </style>

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/static/rank/battle5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/static/rank/rank-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
src/static/rank/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B