update:对接节奏训练

This commit is contained in:
2026-08-25 17:36:54 +08:00
parent 35e0ee3ca0
commit 9a06dcf942
7 changed files with 74 additions and 73 deletions
@@ -247,6 +247,13 @@ const metricConfigs = {
deltaKeys: ["deltaTotalRings", "delta_total_rings"], deltaKeys: ["deltaTotalRings", "delta_total_rings"],
deltaUnit: "环", deltaUnit: "环",
}, },
{
label: "十环次数",
valueKeys: ["tenRingCount", "ten_ring_count"],
unit: "次",
deltaKeys: ["deltaTenRingCount", "delta_ten_ring_count"],
deltaUnit: "次",
},
], ],
endurance: [ endurance: [
{ {
@@ -364,7 +371,7 @@ const calories = computed(
<view v-else class="stat-value">--</view> <view v-else class="stat-value">--</view>
</view> </view>
</view> </view>
<view class="stat-row"> <view v-if="resultTrainingType !== 'rhythm'" class="stat-row">
<image class="stat-bg" src="https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-c-bg.png" mode="scaleToFill" /> <image class="stat-bg" src="https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-c-bg.png" mode="scaleToFill" />
<view class="stat-cell"> <view class="stat-cell">
<text class="stat-label">消耗卡路里</text> <text class="stat-label">消耗卡路里</text>
@@ -447,7 +454,7 @@ const calories = computed(
</view> </view>
</view> </view>
</ScreenHint> </ScreenHint>
<BowData :total="arrows.length" :arrows="result.details" :show="showBowData" <BowData :total="details.length" :arrows="details" :show="showBowData"
:onClose="() => (showBowData = false)" /> :onClose="() => (showBowData = false)" />
<UserUpgrade :show="showUpgrade" :onClose="() => (showUpgrade = false)" :lvl="userLevel" /> <UserUpgrade :show="showUpgrade" :onClose="() => (showUpgrade = false)" :lvl="userLevel" />
</view> </view>
+24 -62
View File
@@ -15,9 +15,9 @@ const trainingModeRouteMap = {
endurance: "endurance", endurance: "endurance",
precision: "precision", precision: "precision",
rhythm: "rhythm", rhythm: "rhythm",
strength: "power", stability: "power",
}; };
const unavailableTrainingIds = new Set(["strength"]); const unavailableTrainingIds = new Set(["stability"]);
// 训练项目卡片右侧主图标。 // 训练项目卡片右侧主图标。
const trainingModeIconMap = { const trainingModeIconMap = {
base_bow: base_bow:
@@ -39,21 +39,22 @@ const trainingModeTitleImageMap = {
"https://static.shelingxingqiu.com/shootmini/static/training-home/jingzhunxunlian.png", "https://static.shelingxingqiu.com/shootmini/static/training-home/jingzhunxunlian.png",
rhythm: rhythm:
"https://static.shelingxingqiu.com/shootmini/static/training-home/jiezouxunlian.png", "https://static.shelingxingqiu.com/shootmini/static/training-home/jiezouxunlian.png",
strength: stability:
"https://static.shelingxingqiu.com/shootmini/static/training-home/wendingxunlian.png", "https://static.shelingxingqiu.com/shootmini/static/training-home/wendingxunlian.png",
}; };
const defaultWeekDays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; const defaultWeekDays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
const defaultRadarDimensions = [ const defaultRadarDimensions = [
{ name: "基础", score: 0 }, { name: "基础", score: 0 },
{ name: "精准", score: 0 }, { name: "精准", score: 0 },
{ name: "力量", score: 0 }, { name: "稳定", score: 0 },
{ name: "节奏", score: 0 }, { name: "节奏", score: 0 },
{ name: "耐力", score: 0 }, { name: "耐力", score: 0 },
]; ];
const radarDimensionTrainingIdMap = Object.freeze({ const radarDimensionTrainingIdMap = Object.freeze({
基础: "base", 基础: "base",
精准: "precision", 精准: "precision",
力量: "strength", 稳定: "stability",
力量: "stability",
节奏: "rhythm", 节奏: "rhythm",
耐力: "endurance", 耐力: "endurance",
}); });
@@ -66,7 +67,7 @@ const createDefaultTrainingData = () => ({
total_arrows: 0, total_arrows: 0,
target_rate: 0, target_rate: 0,
ten_ring_count: 0, ten_ring_count: 0,
total_calories: 0, average_ring: 0,
}, },
beat_percent: 0, beat_percent: 0,
radar_max: 0, radar_max: 0,
@@ -137,7 +138,6 @@ const getLevelText = (item) => {
return `当前进度 LV${level} >`; return `当前进度 LV${level} >`;
}; };
// 接口字段暂时沿用 total_calories,页面按平均环数展示。
const getAverageRingValue = (value) => formatValue(value); const getAverageRingValue = (value) => formatValue(value);
const getTrainingIcon = (item = {}) => const getTrainingIcon = (item = {}) =>
@@ -289,7 +289,7 @@ const loadPersonalTrainingData = async () => {
total_arrows: result?.stats?.total_arrows ?? 0, total_arrows: result?.stats?.total_arrows ?? 0,
target_rate: result?.stats?.target_rate ?? 0, target_rate: result?.stats?.target_rate ?? 0,
ten_ring_count: result?.stats?.ten_ring_count ?? 0, ten_ring_count: result?.stats?.ten_ring_count ?? 0,
total_calories: result?.stats?.total_calories ?? 0, average_ring: result?.stats?.average_ring ?? 0,
}, },
beat_percent: result?.beat_percent ?? 0, beat_percent: result?.beat_percent ?? 0,
radar_max: result?.radar_max ?? 0, radar_max: result?.radar_max ?? 0,
@@ -458,7 +458,7 @@ onShow(async () => {
<view class="stats-value-row"> <view class="stats-value-row">
<view class="stats-value-group"> <view class="stats-value-group">
<text class="stats-value"> <text class="stats-value">
{{ getAverageRingValue(trainingData.stats.total_calories) }} {{ getAverageRingValue(trainingData.stats.average_ring) }}
</text> </text>
<text class="stats-unit"></text> <text class="stats-unit"></text>
<view class="stats-value-decoration"></view> <view class="stats-value-decoration"></view>
@@ -531,19 +531,26 @@ onShow(async () => {
</view> </view>
</view> </view>
<view class="featured-card" @click="$clickSound(openRoutineTraining)"> <view class="mode-grid">
<view
class="mode-card"
@click="$clickSound(openRoutineTraining)"
>
<view class="mode-card-copy">
<image <image
class="featured-card-bg" class="mode-card-title-image"
src="https://static.shelingxingqiu.com/shootmini/static/training-home/img_22.png" src="https://static.shelingxingqiu.com/shootmini/static/training-home/ziyouxunlian.png"
mode="widthFix" mode="widthFix"
/> />
<view class="featured-card-mask"></view> <text class="mode-card-progress">无级别限制</text>
<view class="featured-card-copy">
<text class="featured-card-subtitle">12箭练习</text>
</view> </view>
<image
class="mode-card-icon"
src="https://static.shelingxingqiu.com/shootmini/static/training-home/img_2.png"
mode="aspectFit"
/>
</view> </view>
<view class="mode-grid">
<view <view
v-for="item in visibleTrainingItems" v-for="item in visibleTrainingItems"
:key="item.id" :key="item.id"
@@ -861,51 +868,6 @@ onShow(async () => {
width: 92rpx; width: 92rpx;
} }
.featured-card {
position: relative;
width: 100%;
height: 130rpx;
margin-top: 38rpx;
border-radius: 16rpx;
overflow: hidden;
}
.featured-card-bg {
width: 100%;
}
.featured-card-mask {
position: absolute;
left: 0;
top: 0;
width: 278rpx;
height: 130rpx;
}
.featured-card-copy {
position: absolute;
left: 174rpx;
top: 60rpx;
display: flex;
flex-direction: column;
}
.featured-card-title {
display: block;
color: #895409;
font-size: 34rpx;
font-family: "AlimamaShuHeiTi-Bold", "PingFang SC", sans-serif;
font-weight: 700;
line-height: 42rpx;
}
.featured-card-subtitle {
display: block;
color: #895409;
font-size: 20rpx;
line-height: 28rpx;
}
.mode-grid { .mode-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -919,7 +881,7 @@ onShow(async () => {
box-shadow: inset 2rpx 2rpx 6rpx 0rpx rgba(255, 255, 255, 0.27); box-shadow: inset 2rpx 2rpx 6rpx 0rpx rgba(255, 255, 255, 0.27);
border-radius: 16rpx; border-radius: 16rpx;
border: 2rpx solid rgba(235, 184, 123, 0.5); border: 2rpx solid rgba(235, 184, 123, 0.5);
background: rgba(0, 0, 0, 0.5); /* background: rgba(0, 0, 0, 0.5); */
overflow: hidden; overflow: hidden;
} }
+31 -1
View File
@@ -92,6 +92,8 @@ const rhythmServerTimestamp = ref(0);
const rhythmFallbackWindowStart = ref(0); const rhythmFallbackWindowStart = ref(0);
const rhythmFallbackTimestamp = ref(0); const rhythmFallbackTimestamp = ref(0);
const rhythmHasActiveServerAnchor = ref(false); const rhythmHasActiveServerAnchor = ref(false);
// 节奏训练目标箭数以服务端 total_arrows 为准;首帧缺失时用 arrows_left 初始化并保持稳定。
const rhythmTargetArrows = ref(0);
// 服务端状态立即落到 practiceInfo,精准训练的目标区域单独延迟展示。 // 服务端状态立即落到 practiceInfo,精准训练的目标区域单独延迟展示。
const visiblePrecisionTarget = ref({ const visiblePrecisionTarget = ref({
randomBlock: 0, randomBlock: 0,
@@ -416,7 +418,7 @@ const trainingCopy = computed(() => {
} }
if (trainingType.value === "rhythm") { if (trainingType.value === "rhythm") {
const targetArrows = getPositiveInteger(total.value); const targetArrows = getPositiveInteger(rhythmTargetArrows.value);
const arrowsLeft = Math.min( const arrowsLeft = Math.min(
targetArrows, targetArrows,
Math.max( Math.max(
@@ -494,6 +496,8 @@ const practiceInfoFields = [
"deltaTotalArrows", "deltaTotalArrows",
"deltaAverageRing", "deltaAverageRing",
"deltaStability", "deltaStability",
"tenRingCount",
"deltaTenRingCount",
"beforeExp", "beforeExp",
"beforeLevel", "beforeLevel",
"currentExp", "currentExp",
@@ -522,6 +526,8 @@ const practiceResultFields = [
"deltaTotalArrows", "deltaTotalArrows",
"deltaAverageRing", "deltaAverageRing",
"deltaStability", "deltaStability",
"tenRingCount",
"deltaTenRingCount",
"beforeExp", "beforeExp",
"beforeLevel", "beforeLevel",
"currentExp", "currentExp",
@@ -569,8 +575,31 @@ const cacheRhythmTrainingConfig = (message = {}) => {
} }
}; };
const cacheRhythmTargetArrows = (message = {}) => {
const messageTrainingType = String(
message.trainingType ?? message.training_type ?? trainingType.value
).trim();
if (messageTrainingType !== "rhythm") return;
const totalArrows = getPositiveInteger(
message.totalArrows ?? message.total_arrows
);
if (totalArrows > 0) {
rhythmTargetArrows.value = totalArrows;
return;
}
// 兼容服务端首帧 total_arrows 暂为 0:只初始化一次,避免剩余数递减时分母跟着变化。
if (rhythmTargetArrows.value === 0) {
rhythmTargetArrows.value = getPositiveInteger(
message.arrowsLeft ?? message.arrows_left
);
}
};
const syncPracticeInfo = (message = {}) => { const syncPracticeInfo = (message = {}) => {
cacheRhythmTrainingConfig(message); cacheRhythmTrainingConfig(message);
cacheRhythmTargetArrows(message);
const nextInfo = practiceInfoFields.reduce((result, field) => { const nextInfo = practiceInfoFields.reduce((result, field) => {
if (Object.prototype.hasOwnProperty.call(message, field)) { if (Object.prototype.hasOwnProperty.call(message, field)) {
result[field] = message[field]; result[field] = message[field];
@@ -1427,6 +1456,7 @@ async function onRetry() {
rhythmFallbackWindowStart.value = 0; rhythmFallbackWindowStart.value = 0;
rhythmFallbackTimestamp.value = 0; rhythmFallbackTimestamp.value = 0;
rhythmHasActiveServerAnchor.value = false; rhythmHasActiveServerAnchor.value = false;
rhythmTargetArrows.value = 0;
invalidateShotPresentations({ resetVisible: true }); invalidateShotPresentations({ resetVisible: true });
start.value = false; start.value = false;
scores.value = []; scores.value = [];
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -7,8 +7,8 @@ const { Reader, Writer } = protobuf;
// 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。 // 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。
// <match-schema-generated> // <match-schema-generated>
// 此区块由 scripts/generate-match-schema.mjs 自动生成,请勿手动修改。 // 此区块由 scripts/generate-match-schema.mjs 自动生成,请勿手动修改。
// 来源:src/utils/match.min.jssha256: a7ab92ae43e405e7 // 来源:src/utils/match.min.jssha256: fbf97fd3d08ae314
// 协议命名空间:rpc;消息数:12;字段数:153 // 协议命名空间:rpc;消息数:12;字段数:155
export const ServerMessageType = { export const ServerMessageType = {
SERVER_MSG_UNKNOWN: 0, SERVER_MSG_UNKNOWN: 0,
@@ -196,6 +196,8 @@ const SCHEMAS = {
51: { name: "shoot_window_start", kind: "int64" }, 51: { name: "shoot_window_start", kind: "int64" },
52: { name: "in_shoot_window", kind: "bool" }, 52: { name: "in_shoot_window", kind: "bool" },
53: { name: "shoot_time", kind: "int32" }, 53: { name: "shoot_time", kind: "int32" },
54: { name: "ten_ring_count", kind: "int32" },
55: { name: "delta_ten_ring_count", kind: "int32" },
}, },
MatchInfo: { MatchInfo: {
1: { name: "match_id", kind: "string" }, 1: { name: "match_id", kind: "string" },