update:排位赛界面、排行榜界面改版,新增agents描述文档
269
AGENTS.md
Normal 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
|
||||||
|
|
||||||
|
除非用户明确要求:
|
||||||
|
否则不要输出完整项目。
|
||||||
60
src/apis.js
@@ -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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
BIN
src/static/rank/battle-choose.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
src/static/rank/battle10.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/static/rank/battle1v1.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src/static/rank/battle2v2.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/static/rank/battle3v3.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/static/rank/battle5.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/static/rank/bubble-tip.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/static/rank/rank-bg.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
src/static/rank/star.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/static/rank/triangle.png
Normal file
|
After Width: | Height: | Size: 208 B |