update:对接个人训练首页
@@ -264,6 +264,7 @@ AI 应主动:
|
||||
* 少解释
|
||||
* 优先 patch
|
||||
* 优先 diff
|
||||
* 写好中文注释
|
||||
|
||||
除非用户明确要求:
|
||||
否则不要输出完整项目。
|
||||
|
||||
@@ -407,6 +407,10 @@ export const getPractiseDataAPI = async () => {
|
||||
return request("GET", "/user/practice/statistics");
|
||||
};
|
||||
|
||||
export const getPersonalTrainingAPI = async () => {
|
||||
return request("GET", "/personal/training");
|
||||
};
|
||||
|
||||
export const getBattleDataAPI = async () => {
|
||||
return request("GET", "/user/fight/statistics");
|
||||
};
|
||||
|
||||
@@ -4,43 +4,43 @@ export const trainingHomeWeekSchedule = [
|
||||
key: "mon",
|
||||
label: "周一",
|
||||
status: "done",
|
||||
icon: "../../static/training-home/slices/done.png",
|
||||
icon: "../../static/training-home/done.png",
|
||||
},
|
||||
{
|
||||
key: "tue",
|
||||
label: "周二",
|
||||
status: "done",
|
||||
icon: "../../static/training-home/slices/done.png",
|
||||
icon: "../../static/training-home/done.png",
|
||||
},
|
||||
{
|
||||
key: "wed",
|
||||
label: "周三",
|
||||
status: "missed",
|
||||
icon: "../../static/training-home/slices/missed.png",
|
||||
icon: "../../static/training-home/missed.png",
|
||||
},
|
||||
{
|
||||
key: "thu",
|
||||
label: "周四",
|
||||
status: "missed",
|
||||
icon: "../../static/training-home/slices/missed.png",
|
||||
icon: "../../static/training-home/missed.png",
|
||||
},
|
||||
{
|
||||
key: "fri",
|
||||
label: "周五",
|
||||
status: "done",
|
||||
icon: "../../static/training-home/slices/done.png",
|
||||
icon: "../../static/training-home/done.png",
|
||||
},
|
||||
{
|
||||
key: "sat",
|
||||
label: "周六",
|
||||
status: "done",
|
||||
icon: "../../static/training-home/slices/done.png",
|
||||
icon: "../../static/training-home/done.png",
|
||||
},
|
||||
{
|
||||
key: "sun",
|
||||
label: "周日",
|
||||
status: "missed",
|
||||
icon: "../../static/training-home/slices/missed.png",
|
||||
icon: "../../static/training-home/missed.png",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -73,7 +73,7 @@ export const trainingHomeModes = [
|
||||
key: "endurance",
|
||||
title: "耐力训练",
|
||||
progressText: "当前进度 LV5 >",
|
||||
icon: "../../static/training-home/slices/img_3.png",
|
||||
icon: "../../static/training-home/img_3.png",
|
||||
recommended: true,
|
||||
disabled: false,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ export const trainingHomeModes = [
|
||||
key: "precision",
|
||||
title: "精准训练",
|
||||
progressText: "当前进度 LV3 >",
|
||||
icon: "../../static/training-home/slices/img_4.png",
|
||||
icon: "../../static/training-home/img_4.png",
|
||||
recommended: false,
|
||||
disabled: false,
|
||||
},
|
||||
@@ -89,7 +89,7 @@ export const trainingHomeModes = [
|
||||
key: "rhythm",
|
||||
title: "节奏训练",
|
||||
progressText: "当前进度 LV6 >",
|
||||
icon: "../../static/training-home/slices/img_5.png",
|
||||
icon: "../../static/training-home/img_5.png",
|
||||
recommended: false,
|
||||
disabled: false,
|
||||
},
|
||||
@@ -97,7 +97,7 @@ export const trainingHomeModes = [
|
||||
key: "power",
|
||||
title: "力量训练",
|
||||
progressText: "Coming! LV10",
|
||||
icon: "../../static/training-home/slices/img_6.png",
|
||||
icon: "../../static/training-home/img_6.png",
|
||||
recommended: false,
|
||||
disabled: true,
|
||||
},
|
||||
|
||||
@@ -13,13 +13,14 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const getDisplayText = (arrow = {}) => {
|
||||
if (!arrow || !arrow.ring) return "";
|
||||
if (!arrow) return "";
|
||||
if (!arrow.ring) return "-";
|
||||
return arrow.ringX ? "X" : String(arrow.ring);
|
||||
};
|
||||
|
||||
const isLowScore = (arrow = {}) => {
|
||||
if (!arrow || !arrow.ring || arrow.ringX) return false;
|
||||
return Number(arrow.ring) < 8;
|
||||
if (!arrow || arrow.ringX) return false;
|
||||
return Number(arrow.ring) < 6;
|
||||
};
|
||||
|
||||
const displayArrows = computed(() => {
|
||||
@@ -39,7 +40,7 @@ const displayArrows = computed(() => {
|
||||
:key="index"
|
||||
class="score-card"
|
||||
>
|
||||
<image class="score-card-bg" src="../../../static/training-difficulty-design/block-gold.png"></image>
|
||||
<image class="score-card-bg" :src="isLowScore(arrow)?'/static/training-difficulty-design/block-gray.png':'/static/training-difficulty-design/block-gold.png'"></image>
|
||||
<text
|
||||
class="score-value"
|
||||
:class="{ 'score-value--low': isLowScore(arrow) }"
|
||||
|
||||
628
src/pages/training/components/ScoreResult.vue
Normal file
@@ -0,0 +1,628 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import ScreenHint from "@/components/ScreenHint.vue";
|
||||
import BowData from "@/components/BowData.vue";
|
||||
import UserUpgrade from "@/components/UserUpgrade.vue";
|
||||
import { directionAdjusts } from "@/constants";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const store = useStore();
|
||||
const { user } = storeToRefs(store);
|
||||
|
||||
const props = defineProps({
|
||||
onClose: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
},
|
||||
onRetry: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
rowCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
result: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
tipSrc: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const showPanel = ref(true);
|
||||
const showComment = ref(false);
|
||||
const showBowData = ref(false);
|
||||
const showUpgrade = ref(false);
|
||||
|
||||
const closePanel = () => {
|
||||
showPanel.value = false;
|
||||
setTimeout(() => {
|
||||
props.onClose();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const retryPractice = () => {
|
||||
showPanel.value = false;
|
||||
setTimeout(() => {
|
||||
props.onRetry();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
function onClickShare() {
|
||||
uni.$emit("share-image");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.result.lvl > user.value.lvl) {
|
||||
showUpgrade.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
const details = computed(() => props.result.details || []);
|
||||
|
||||
const arrows = computed(() => {
|
||||
const data = new Array(props.total).fill(null);
|
||||
details.value.forEach((arrow, index) => {
|
||||
data[index] = arrow;
|
||||
});
|
||||
return data;
|
||||
});
|
||||
|
||||
const validArrows = computed(() => arrows.value.filter((a) => !!a?.ring).length);
|
||||
const totalRing = computed(() =>
|
||||
details.value.reduce((last, next) => last + (Number(next.ring) || 0), 0)
|
||||
);
|
||||
|
||||
const gainedExp = computed(
|
||||
() => props.result.exp || props.result.experience || validArrows.value
|
||||
);
|
||||
|
||||
const currentLevel = computed(
|
||||
() => props.result.lvl || user.value.lvl || user.value.rankLvl || 1
|
||||
);
|
||||
|
||||
const currentExp = computed(
|
||||
() => props.result.currentExp || props.result.score || user.value.scores || 0
|
||||
);
|
||||
|
||||
const nextExp = computed(
|
||||
() => props.result.nextExp || props.result.upgradeScore || 100
|
||||
);
|
||||
|
||||
const expPercent = computed(() => {
|
||||
if (!nextExp.value) return 0;
|
||||
return Math.min(100, Math.max(0, (currentExp.value / nextExp.value) * 100));
|
||||
});
|
||||
|
||||
const findValue = (...keys) => {
|
||||
const item = keys.find((key) => props.result[key] !== undefined);
|
||||
return item ? props.result[item] : undefined;
|
||||
};
|
||||
|
||||
const formatDuration = (value) => {
|
||||
const seconds = Number(value || 0);
|
||||
if (!seconds) return "--";
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const rest = seconds % 60;
|
||||
return minutes ? `${minutes}分${rest}秒` : `${rest}秒`;
|
||||
};
|
||||
|
||||
const usedTime = computed(() =>
|
||||
findValue("duration", "usedTime", "shootTime", "time")
|
||||
);
|
||||
|
||||
const hitCompare = computed(
|
||||
() => Number(findValue("hitCompare", "hitDiff", "hitDelta") || 0)
|
||||
);
|
||||
|
||||
const timeCompare = computed(
|
||||
() => Number(findValue("timeCompare", "timeDiff", "durationDiff") || 0)
|
||||
);
|
||||
|
||||
const calories = computed(
|
||||
() => Number(findValue("calories", "calorie", "kcal") || 0)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="['result-mask', showPanel ? 'result-mask--show' : 'result-mask--hide']">
|
||||
<image class="hero-glow" src="/static/training-difficulty-design/result-bg.png" mode="widthFix" />
|
||||
<view class="result-title">
|
||||
<image class="result-title-bg" src="/static/training-difficulty-design/result-t-bg.png" mode="widthFix" />
|
||||
<view class="result-title-text">Lv{{ currentLevel }}</view>
|
||||
</view>
|
||||
|
||||
<view class="result-panel">
|
||||
<view class="line-top"></view>
|
||||
<view class="line-bottom"></view>
|
||||
<view class="stats">
|
||||
<view class="stat-row">
|
||||
<image class="stat-bg" src="/static/training-difficulty-design/result-c-bg.png" mode="scaleToFill" />
|
||||
<view class="stat-cell">
|
||||
<text class="stat-label">共命中目标</text>
|
||||
<view class="stat-value">
|
||||
<text>{{ validArrows }}</text>
|
||||
<text class="stat-unit">次</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-cell stat-cell--compare">
|
||||
<text class="stat-label">对比上次</text>
|
||||
<view class="stat-value">
|
||||
<text>{{ Math.abs(hitCompare) }}</text>
|
||||
<text class="stat-unit">次</text>
|
||||
<image class="trend-icon" :class="{ 'trend-icon--down': hitCompare < 0 }"
|
||||
src="/static/training-difficulty-design/result-up.png" mode="widthFix" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stat-row">
|
||||
<image class="stat-bg" src="/static/training-difficulty-design/result-c-bg.png" mode="scaleToFill" />
|
||||
<view class="stat-cell">
|
||||
<text class="stat-label">用时</text>
|
||||
<view class="stat-value">
|
||||
<text>{{ formatDuration(usedTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-cell stat-cell--compare">
|
||||
<text class="stat-label">对比上次</text>
|
||||
<view class="stat-value">
|
||||
<text>{{ formatDuration(Math.abs(timeCompare)) }}</text>
|
||||
<image class="trend-icon" :class="{ 'trend-icon--down': timeCompare <= 0 }"
|
||||
src="/static/training-difficulty-design/result-up.png" mode="widthFix" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-row">
|
||||
<image class="stat-bg" src="/static/training-difficulty-design/result-c-bg.png" mode="scaleToFill" />
|
||||
<view class="stat-cell">
|
||||
<text class="stat-label">消耗卡路里</text>
|
||||
<view class="stat-value">
|
||||
<text>{{ calories }}卡</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stat-equal">≈</text>
|
||||
<!-- <view class="stat-divider"></view> -->
|
||||
<view class="stat-cell stat-cell--compare">
|
||||
<view class="stat-value">
|
||||
<image v-for="index in 3" :key="index" class="rice-icon"
|
||||
src="/static/training-difficulty-design/result-rice.png" mode="widthFix" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="actions">
|
||||
<view class="action-item" @click="() => (showBowData = true)">
|
||||
<image class="action-icon" src="/static/training-difficulty-design/result-icon-1.png" mode="widthFix" />
|
||||
<text>查看靶纸</text>
|
||||
</view>
|
||||
<view v-if="validArrows === total" class="action-item" @click="() => (showComment = true)">
|
||||
<image class="action-icon" src="/static/training-difficulty-design/result-icon-2.png" mode="widthFix" />
|
||||
<text>教练点评</text>
|
||||
</view>
|
||||
<view v-if="validArrows === total" class="action-item" @click="onClickShare">
|
||||
<image class="action-icon" src="/static/training-difficulty-design/result-icon-3.png" mode="widthFix" />
|
||||
<text>分享成绩</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="oper-box">
|
||||
<view class="exp-area">
|
||||
<text class="exp-gain">+{{ gainedExp }}经验</text>
|
||||
<view class="level-progress">
|
||||
<text class="level-text">LV.{{ currentLevel }}</text>
|
||||
<view class="progress-track">
|
||||
<view class="progress-fill" :style="{ width: `${expPercent}%` }"></view>
|
||||
</view>
|
||||
<text class="progress-text">{{ currentExp }} / {{ nextExp }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-actions">
|
||||
<view class="result-btn result-btn--muted" @click="closePanel">
|
||||
<text>{{ validArrows === total ? "完成" : "返回" }}</text>
|
||||
</view>
|
||||
<view class="result-btn result-btn--primary" @click="retryPractice">
|
||||
<text>再来一次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<ScreenHint :show="showComment" :onClose="() => (showComment = false)" mode="tall">
|
||||
<view class="coach-comment">
|
||||
<text>
|
||||
您本次练习取得了<text class="gold-text">{{ totalRing }}</text>环的成绩,所有箭支上靶后的平均点间距离为<text class="gold-text">{{
|
||||
Number((result.average_distance || 0).toFixed(2))
|
||||
}}</text>,{{
|
||||
result.spreadEvaluation === "Dispersed"
|
||||
? "还需要持续改进哦~"
|
||||
: "成绩优秀。"
|
||||
}}
|
||||
</text>
|
||||
<view>
|
||||
<image src="https://static.shelingxingqiu.com/attachment/2025-11-26/deihtj15xjwcz3c1tx.png" mode="widthFix" />
|
||||
<text class="coach-suggestion">
|
||||
针对您本次的练习,{{
|
||||
result.spreadEvaluation === "Dispersed"
|
||||
? "我们建议您充分练习推弓、靠位以及撒放动作一致性。"
|
||||
: totalRing >= 100
|
||||
? "我们建议您继续保持即可。"
|
||||
: `我们建议您将设备的瞄准器${directionAdjusts[result.adjustmentHint]
|
||||
}调整。`
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</ScreenHint>
|
||||
<BowData :total="arrows.length" :arrows="result.details" :show="showBowData"
|
||||
:onClose="() => (showBowData = false)" />
|
||||
<UserUpgrade :show="showUpgrade" :onClose="() => (showUpgrade = false)" :lvl="result.lvl" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.result-mask {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg,
|
||||
rgba(24, 22, 17, 0.38) 0%,
|
||||
rgba(24, 22, 17, 0.56) 28%,
|
||||
rgba(17, 17, 25, 0.92) 58%),
|
||||
rgba(0, 0, 0, 0.72);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.result-mask--show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.result-mask--hide {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.hero-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 264rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.result-title-bg {
|
||||
width: 100%;
|
||||
height: 264rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-title-text {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #FBFCE6;
|
||||
font-weight: 600;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 116rpx;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.result-panel {
|
||||
width: 100vw;
|
||||
height: 634rpx;
|
||||
padding: 144rpx 80rpx 0 80rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1;
|
||||
margin-top: -100rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stats {
|
||||
width: 100%;
|
||||
margin-top: 34rpx;
|
||||
}
|
||||
|
||||
.line-top {
|
||||
background: linear-gradient(45deg, rgba(205, 183, 122, 0) 0%, #CDB77A 49.92%, rgba(205, 183, 122, 0) 100%);
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
opacity: 0.9;
|
||||
position: absolute;
|
||||
top: 2rpx;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.line-bottom {
|
||||
background: linear-gradient(45deg, rgba(205, 183, 122, 0) 0%, #CDB77A 49.92%, rgba(205, 183, 122, 0) 100%);
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
opacity: 0.9;
|
||||
position: absolute;
|
||||
bottom: 2rpx;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.stat-row {
|
||||
width: 100%;
|
||||
height: 62rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 52rpx;
|
||||
// border: 2rpx solid rgba(209, 184, 125, 0.72);
|
||||
// border-radius: 14rpx;
|
||||
transform: skewX(-12deg);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stat-cell {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: skewX(12deg);
|
||||
}
|
||||
|
||||
.stat-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 582rpx;
|
||||
height: 62rpx;
|
||||
}
|
||||
|
||||
.stat-cell--compare {
|
||||
padding-left: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
position: absolute;
|
||||
top: -30rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120rpx;
|
||||
color: #F3E0B9;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
margin-left: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 34rpx;
|
||||
background: rgba(197, 160, 92, 0.64);
|
||||
transform: skewX(12deg);
|
||||
}
|
||||
|
||||
.stat-equal{
|
||||
width: 30rpx;
|
||||
height: 40rpx;
|
||||
color: #F3E0B9;
|
||||
font-size: 30rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.trend-icon {
|
||||
width: 28rpx;
|
||||
height: 42rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.trend-icon--down {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.stat-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 582rpx;
|
||||
height: 62rpx;
|
||||
}
|
||||
|
||||
.rice-list {
|
||||
width: 160rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rice-icon {
|
||||
width: 36rpx;
|
||||
height: 34rpx;
|
||||
margin-right: 14rpx;
|
||||
}
|
||||
|
||||
.oper-box {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 598rpx;
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 350rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 auto;
|
||||
margin-top: 38rpx;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 70rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
|
||||
.action-item>text {
|
||||
margin-top: 4rpx;
|
||||
color: #FAE6BC;
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.exp-area {
|
||||
width: 100%;
|
||||
margin-top: auto;
|
||||
padding-top: 122rpx;
|
||||
}
|
||||
|
||||
.exp-gain {
|
||||
display: block;
|
||||
margin-bottom: 14rpx;
|
||||
color: #f6e3b2;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.level-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.level-text,
|
||||
.progress-text {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.level-text {
|
||||
min-width: 60rpx;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
min-width: 72rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
flex: 1;
|
||||
height: 10rpx;
|
||||
margin: 0 14rpx;
|
||||
border-radius: 999rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.26);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(90deg, #ff940f 0%, #ffbb33 58%, #fff0a7 100%);
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 70rpx;
|
||||
}
|
||||
|
||||
.result-btn {
|
||||
width: 234rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 18rpx;
|
||||
}
|
||||
|
||||
.result-btn>text {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.result-btn--muted {
|
||||
color: #ffffff;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.result-btn--primary {
|
||||
background: #FED847;
|
||||
color: #151515;
|
||||
}
|
||||
|
||||
.gold-text {
|
||||
color: #fed847;
|
||||
}
|
||||
|
||||
.coach-comment {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.coach-comment>view {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.coach-comment>view>image {
|
||||
width: 420rpx;
|
||||
height: 420rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.coach-suggestion {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,62 @@
|
||||
<script setup>
|
||||
import { nextTick, onMounted } from "vue";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import {
|
||||
trainingHomeFeatured,
|
||||
trainingHomeModes,
|
||||
trainingHomeRadar,
|
||||
trainingHomeStats,
|
||||
trainingHomeWeekSchedule,
|
||||
} from "@/mock/index.js";
|
||||
import { getPersonalTrainingAPI } from "@/apis";
|
||||
|
||||
const checkedIcon = "../../static/training-home/done.png";
|
||||
const missedIcon = "../../static/training-home/missed.png";
|
||||
// 后端训练项目 id 与难度页 mode 参数的映射关系。
|
||||
const trainingModeRouteMap = {
|
||||
base: "basic",
|
||||
endurance: "endurance",
|
||||
precision: "precision",
|
||||
rhythm: "rhythm",
|
||||
strength: "power",
|
||||
};
|
||||
// 训练项目卡片右侧主图标。
|
||||
const trainingModeIconMap = {
|
||||
base_bow: "../../static/training-home/img_22.png",
|
||||
bow: "../../static/training-home/img_3.png",
|
||||
target: "../../static/training-home/img_4.png",
|
||||
wave: "../../static/training-home/img_5.png",
|
||||
muscle: "../../static/training-home/img_6.png",
|
||||
};
|
||||
// 训练项目卡片标题图,按接口 id 映射本地资源。
|
||||
const trainingModeTitleImageMap = {
|
||||
endurance: "../../static/training-home/nailixunlian.png",
|
||||
precision: "../../static/training-home/jingzhunxunlian.png",
|
||||
rhythm: "../../static/training-home/jiezouxunlian.png",
|
||||
strength: "../../static/training-home/liliangxulian.png",
|
||||
};
|
||||
const defaultWeekDays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||
const defaultRadarDimensions = [
|
||||
{ name: "基础", score: 0 },
|
||||
{ name: "精准", score: 0 },
|
||||
{ name: "力量", score: 0 },
|
||||
{ name: "节奏", score: 0 },
|
||||
{ name: "耐力", score: 0 },
|
||||
];
|
||||
|
||||
// 雷达图绘制仍使用原生 number 尺寸,样式展示统一使用 rpx。
|
||||
// 页面始终直接消费接口字段,这里只保留一份兜底结构,避免模板访问空值。
|
||||
const createDefaultTrainingData = () => ({
|
||||
week_days: defaultWeekDays.map((day) => ({ day, status: "cross" })),
|
||||
stats: {
|
||||
total_training_days: 0,
|
||||
total_arrows: 0,
|
||||
hit_rate: 0,
|
||||
endurance_shoot_speed: 0,
|
||||
total_calories: 0,
|
||||
overtake_rate: 0,
|
||||
},
|
||||
radar: {
|
||||
dimensions: defaultRadarDimensions,
|
||||
},
|
||||
training_items: [],
|
||||
});
|
||||
|
||||
const trainingData = ref(createDefaultTrainingData());
|
||||
const pageMounted = ref(false);
|
||||
const trainingRadarCanvasId = "training-home-radar";
|
||||
const radarImageWidth = 225;
|
||||
const radarImageHeight = 224;
|
||||
@@ -24,37 +69,79 @@ const radarCanvasHeight = Math.round(uni.upx2px(radarFigureHeightRpx));
|
||||
const radarScaleX = radarCanvasWidth / radarImageWidth;
|
||||
const radarScaleY = radarCanvasHeight / radarImageHeight;
|
||||
const radarScale = Math.min(radarScaleX, radarScaleY);
|
||||
// Fit from img_19.png so value=10 lands on the actual outer circle.
|
||||
const radarCenterX = 112.0624 * radarScaleX;
|
||||
const radarCenterY = 111.4645 * radarScaleY;
|
||||
const radarStrokeWidth = Math.max(1, 2 * radarScale);
|
||||
const radarPointRadius = Math.max(2.5, 3.5 * radarScale);
|
||||
const radarOuterRadiusX = 110.7089 * radarScaleX;
|
||||
const radarOuterRadiusY = 110.7089 * radarScaleY;
|
||||
const radarMaxValue = 100;
|
||||
const radarFigureStyle = {
|
||||
width: `${radarFigureWidthRpx}rpx`,
|
||||
height: `${radarFigureHeightRpx}rpx`,
|
||||
};
|
||||
|
||||
const getRadarPoint = (centerX, centerY, radiusX, radiusY, angle) => {
|
||||
return {
|
||||
x: centerX + radiusX * Math.cos(angle),
|
||||
y: centerY + radiusY * Math.sin(angle),
|
||||
};
|
||||
const formatValue = (value, digits = 1) => {
|
||||
const numberValue = Number(value);
|
||||
if (!Number.isFinite(numberValue)) return "--";
|
||||
return String(Number(numberValue.toFixed(digits)));
|
||||
};
|
||||
|
||||
const getLevelText = (item) => {
|
||||
if (!item) return "";
|
||||
const level = Number(item.current_level) || 0;
|
||||
return item.is_locked ? `Coming! LV${level}` : `当前进度 LV${level} >`;
|
||||
};
|
||||
|
||||
// 卡路里字段按需求做 K / W 缩写展示。
|
||||
const getCaloriesValue = (value) => {
|
||||
const numberValue = Number(value);
|
||||
if (!Number.isFinite(numberValue)) return "--";
|
||||
if (numberValue >= 10000) return `${formatValue(numberValue / 10000)}W`;
|
||||
if (numberValue >= 1000) return `${formatValue(numberValue / 1000)}K`;
|
||||
return formatValue(numberValue, 0);
|
||||
};
|
||||
|
||||
const getTrainingIcon = (item = {}) =>
|
||||
trainingModeIconMap[item.icon] || trainingModeIconMap.bow;
|
||||
|
||||
const getTrainingTitleImage = (item = {}) =>
|
||||
trainingModeTitleImageMap[item.id] || "";
|
||||
|
||||
const getTrainingMode = (item = {}) =>
|
||||
trainingModeRouteMap[item.id] || item.id || "";
|
||||
|
||||
const getFeaturedItem = () =>
|
||||
trainingData.value.training_items.find((item) => item.id === "base") ||
|
||||
trainingData.value.training_items[0];
|
||||
|
||||
const getRadarPoint = (centerX, centerY, radiusX, radiusY, angle) => ({
|
||||
x: centerX + radiusX * Math.cos(angle),
|
||||
y: centerY + radiusY * Math.sin(angle),
|
||||
});
|
||||
|
||||
// 雷达图直接使用接口的 5 维 score,按 0-100 等比映射到顶点位置。
|
||||
const drawRadar = () => {
|
||||
const dimensions = Array.isArray(trainingData.value.radar?.dimensions)
|
||||
? trainingData.value.radar.dimensions.slice(0, 5)
|
||||
: [];
|
||||
|
||||
if (dimensions.length !== 5) return;
|
||||
|
||||
const ctx = uni.createCanvasContext(trainingRadarCanvasId);
|
||||
const angles = trainingHomeRadar.labels.map(
|
||||
const angles = dimensions.map(
|
||||
(_, index) => (-90 + index * 72) * (Math.PI / 180)
|
||||
);
|
||||
|
||||
ctx.clearRect(0, 0, radarCanvasWidth, radarCanvasHeight);
|
||||
|
||||
// 五边形底图已经由设计切图承载,这里只叠加能力值多边形和节点。
|
||||
const points = trainingHomeRadar.values.map((value, index) => {
|
||||
const normalized = Math.max(0, Math.min(value, trainingHomeRadar.maxValue));
|
||||
const progress = normalized / trainingHomeRadar.maxValue;
|
||||
const points = dimensions.map((item, index) => {
|
||||
const normalized = Math.max(
|
||||
0,
|
||||
Math.min(Number(item.score) || 0, radarMaxValue)
|
||||
);
|
||||
const progress = normalized / radarMaxValue;
|
||||
|
||||
return getRadarPoint(
|
||||
radarCenterX,
|
||||
radarCenterY,
|
||||
@@ -66,11 +153,8 @@ const drawRadar = () => {
|
||||
|
||||
ctx.beginPath();
|
||||
points.forEach((point, index) => {
|
||||
if (index === 0) {
|
||||
ctx.moveTo(point.x, point.y);
|
||||
return;
|
||||
}
|
||||
ctx.lineTo(point.x, point.y);
|
||||
if (index === 0) ctx.moveTo(point.x, point.y);
|
||||
else ctx.lineTo(point.x, point.y);
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.setFillStyle("rgba(255, 209, 154, 0.26)");
|
||||
@@ -93,133 +177,238 @@ const drawRadar = () => {
|
||||
ctx.draw();
|
||||
};
|
||||
|
||||
// 这些入口先保留占位行为,等后续页面接入后再替换成真实跳转。
|
||||
// 小程序 canvas 首次渲染时机不稳定,延后一帧再绘制更稳。
|
||||
const refreshRadar = async () => {
|
||||
await nextTick();
|
||||
setTimeout(() => {
|
||||
drawRadar();
|
||||
}, 30);
|
||||
};
|
||||
|
||||
const loadPersonalTrainingData = async () => {
|
||||
try {
|
||||
const result = await getPersonalTrainingAPI();
|
||||
trainingData.value = {
|
||||
week_days:
|
||||
Array.isArray(result?.week_days) && result.week_days.length
|
||||
? result.week_days
|
||||
: createDefaultTrainingData().week_days,
|
||||
stats: {
|
||||
total_training_days: result?.stats?.total_training_days ?? 0,
|
||||
total_arrows: result?.stats?.total_arrows ?? 0,
|
||||
hit_rate: result?.stats?.hit_rate ?? 0,
|
||||
endurance_shoot_speed: result?.stats?.endurance_shoot_speed ?? 0,
|
||||
total_calories: result?.stats?.total_calories ?? 0,
|
||||
overtake_rate: result?.stats?.overtake_rate ?? 0,
|
||||
},
|
||||
radar: {
|
||||
dimensions:
|
||||
Array.isArray(result?.radar?.dimensions) &&
|
||||
result.radar.dimensions.length === 5
|
||||
? result.radar.dimensions
|
||||
: createDefaultTrainingData().radar.dimensions,
|
||||
},
|
||||
training_items: Array.isArray(result?.training_items)
|
||||
? result.training_items
|
||||
: [],
|
||||
};
|
||||
} catch (error) {
|
||||
console.log("personal training load failed", error);
|
||||
trainingData.value = createDefaultTrainingData();
|
||||
} finally {
|
||||
await refreshRadar();
|
||||
}
|
||||
};
|
||||
|
||||
const openTrainingRecord = () => {
|
||||
uni.showToast({
|
||||
title: "训练记录待接入",
|
||||
icon: "none",
|
||||
});
|
||||
};
|
||||
|
||||
const openFeaturedTraining = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/training/difficulty?mode=basic",
|
||||
url: "/pages/my-growth?tab=2",
|
||||
});
|
||||
};
|
||||
|
||||
const openTrainingMode = (item) => {
|
||||
if (item.disabled) {
|
||||
const openTrainingItem = (item = {}) => {
|
||||
const mode = getTrainingMode(item);
|
||||
if (!mode) return;
|
||||
|
||||
if (item.is_locked) {
|
||||
uni.showToast({
|
||||
title: `${item.title} 暂未开放`,
|
||||
title: `${item.name || "训练"} 暂未开放`,
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/training/difficulty?mode=${item.key}`,
|
||||
url: `/pages/training/difficulty?mode=${mode}`,
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(drawRadar);
|
||||
const openFeaturedTraining = () => {
|
||||
const item = getFeaturedItem();
|
||||
if (item) openTrainingItem(item);
|
||||
};
|
||||
|
||||
// 首次进入页面时拉取数据并完成雷达图初始化。
|
||||
onMounted(async () => {
|
||||
await loadPersonalTrainingData();
|
||||
pageMounted.value = true;
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
nextTick(drawRadar);
|
||||
// 从其他页面返回时刷新训练数据,保持进度与推荐状态最新。
|
||||
onShow(async () => {
|
||||
if (!pageMounted.value) return;
|
||||
await loadPersonalTrainingData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container :showBackToGame="true" :bgType="7" bgColor="#050b19">
|
||||
<view class="training-home">
|
||||
<!-- 周打卡区域 -->
|
||||
<view class="week-grid">
|
||||
<view
|
||||
v-for="item in trainingHomeWeekSchedule"
|
||||
:key="item.key"
|
||||
v-for="item in trainingData.week_days"
|
||||
:key="item.day"
|
||||
class="week-item"
|
||||
>
|
||||
<view class="week-item-bg"></view>
|
||||
<image class="week-item-icon" :src="item.icon" mode="widthFix" />
|
||||
<image
|
||||
class="week-item-icon"
|
||||
:src="item.status === 'checked' ? checkedIcon : missedIcon"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<text
|
||||
class="week-item-label"
|
||||
:class="{ 'week-item-label-active': item.status === 'done' }"
|
||||
:class="{ 'week-item-label-active': item.status === 'checked' }"
|
||||
>
|
||||
{{ item.label }}
|
||||
{{ item.day }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 训练统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<view class="stats-card-bg"></view>
|
||||
<image
|
||||
class="stats-quote stats-quote-left"
|
||||
src="../../static/training-home/slices/img_17.png"
|
||||
src="../../static/training-home/img_17.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<image
|
||||
class="stats-quote stats-quote-right"
|
||||
src="../../static/training-home/slices/img_16.png"
|
||||
src="../../static/training-home/img_16.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<view class="stats-grid">
|
||||
<view
|
||||
v-for="item in trainingHomeStats"
|
||||
:key="item.key"
|
||||
class="stats-item"
|
||||
>
|
||||
<view class="stats-item">
|
||||
<view class="stats-value-row">
|
||||
<view class="stats-value-group">
|
||||
<text class="stats-value">{{ item.value }}</text>
|
||||
<text class="stats-unit">{{ item.unit }}</text>
|
||||
<text class="stats-value">
|
||||
{{ formatValue(trainingData.stats.total_training_days, 0) }}
|
||||
</text>
|
||||
<text class="stats-unit">天</text>
|
||||
<view class="stats-value-decoration"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stats-label">{{ item.label }}</text>
|
||||
<text class="stats-label">共训练</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-item">
|
||||
<view class="stats-value-row">
|
||||
<view class="stats-value-group">
|
||||
<text class="stats-value">
|
||||
{{ formatValue(trainingData.stats.total_arrows, 0) }}
|
||||
</text>
|
||||
<text class="stats-unit">支</text>
|
||||
<view class="stats-value-decoration"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stats-label">累计射箭</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-item">
|
||||
<view class="stats-value-row">
|
||||
<view class="stats-value-group">
|
||||
<text class="stats-value">
|
||||
{{ formatValue(trainingData.stats.hit_rate) }}
|
||||
</text>
|
||||
<text class="stats-unit">%</text>
|
||||
<view class="stats-value-decoration"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stats-label">命中率</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-item">
|
||||
<view class="stats-value-row">
|
||||
<view class="stats-value-group">
|
||||
<text class="stats-value">
|
||||
{{ formatValue(trainingData.stats.endurance_shoot_speed, 0) }}
|
||||
</text>
|
||||
<text class="stats-unit">支/分钟</text>
|
||||
<view class="stats-value-decoration"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stats-label">耐力射击</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-item">
|
||||
<view class="stats-value-row">
|
||||
<view class="stats-value-group">
|
||||
<text class="stats-value">
|
||||
{{ getCaloriesValue(trainingData.stats.total_calories) }}
|
||||
</text>
|
||||
<text class="stats-unit">卡路里</text>
|
||||
<view class="stats-value-decoration"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="stats-label">共消耗</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 雷达图与训练记录入口 -->
|
||||
<view class="radar-section">
|
||||
<view class="record-bubble" @click="openTrainingRecord">
|
||||
<image
|
||||
class="record-bubble-bg"
|
||||
src="../../static/training-home/slices/img_28.png"
|
||||
src="../../static/training-home/img_28.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<view class="record-bubble-copy">
|
||||
<view class="record-main">
|
||||
已超越<text class="record-main-highlight">
|
||||
80%
|
||||
</text>对手
|
||||
已超越<text class="record-main-highlight">{{ formatValue(trainingData.stats.overtake_rate) }}%</text>对手
|
||||
</view>
|
||||
|
||||
<view class="record-sub-row">
|
||||
<text class="record-sub-text">我的训练记录</text>
|
||||
<image class="record-arrow" src="../../static/training-home/slices/img_7.png" mode="widthFix" />
|
||||
<image
|
||||
class="record-arrow"
|
||||
src="../../static/training-home/img_7.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="radar-board">
|
||||
<text class="radar-label radar-label-top">{{ trainingHomeRadar.labels[0] }}</text>
|
||||
<text class="radar-label radar-label-right">{{ trainingHomeRadar.labels[1] }}</text>
|
||||
<text class="radar-label radar-label-top">
|
||||
{{ trainingData.radar.dimensions[0].name }}
|
||||
</text>
|
||||
<text class="radar-label radar-label-right">
|
||||
{{ trainingData.radar.dimensions[1].name }}
|
||||
</text>
|
||||
<text class="radar-label radar-label-bottom-right">
|
||||
{{ trainingHomeRadar.labels[2] }}
|
||||
{{ trainingData.radar.dimensions[2].name }}
|
||||
</text>
|
||||
<text class="radar-label radar-label-bottom-left">
|
||||
{{ trainingHomeRadar.labels[3] }}
|
||||
{{ trainingData.radar.dimensions[3].name }}
|
||||
</text>
|
||||
<text class="radar-label radar-label-left">
|
||||
{{ trainingData.radar.dimensions[4].name }}
|
||||
</text>
|
||||
<text class="radar-label radar-label-left">{{ trainingHomeRadar.labels[4] }}</text>
|
||||
|
||||
<view class="radar-figure" :style="radarFigureStyle">
|
||||
<image
|
||||
class="radar-grid-image"
|
||||
:style="radarFigureStyle"
|
||||
src="../../static/training-home/slices/img_19.png"
|
||||
src="../../static/training-home/img_19.png"
|
||||
/>
|
||||
<canvas
|
||||
:canvas-id="trainingRadarCanvasId"
|
||||
@@ -231,42 +420,49 @@ onShow(() => {
|
||||
/>
|
||||
<image
|
||||
class="radar-mascot"
|
||||
src="../../static/training-home/slices/img_21.png"
|
||||
src="../../static/training-home/img_21.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主推荐训练入口 -->
|
||||
<view class="featured-card" @click="openFeaturedTraining">
|
||||
<image
|
||||
class="featured-card-bg"
|
||||
src="../../static/training-home/slices/img_22.png"
|
||||
src="../../static/training-home/img_22.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<view class="featured-card-copy">
|
||||
<text class="featured-card-title"></text>
|
||||
<text class="featured-card-progress">{{ trainingHomeFeatured.progressText }}</text>
|
||||
<text class="featured-card-progress">
|
||||
{{ getLevelText(getFeaturedItem()) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 四个训练模式入口 -->
|
||||
<view class="mode-grid">
|
||||
<view
|
||||
v-for="item in trainingHomeModes"
|
||||
:key="item.key"
|
||||
v-for="item in trainingData.training_items.filter((item) => item.id !== 'base')"
|
||||
:key="item.id"
|
||||
class="mode-card"
|
||||
@click="openTrainingMode(item)"
|
||||
@click="openTrainingItem(item)"
|
||||
>
|
||||
<view v-if="item.recommended" class="mode-tag">推荐</view>
|
||||
|
||||
<view v-if="item.is_recommended" class="mode-tag">推荐</view>
|
||||
<view class="mode-card-copy">
|
||||
<text class="mode-card-title">{{ item.title }}</text>
|
||||
<text class="mode-card-progress">{{ item.progressText }}</text>
|
||||
<image
|
||||
v-if="getTrainingTitleImage(item)"
|
||||
class="mode-card-title-image"
|
||||
:src="getTrainingTitleImage(item)"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<text v-else class="mode-card-title">{{ item.name }}</text>
|
||||
<text class="mode-card-progress">{{ getLevelText(item) }}</text>
|
||||
</view>
|
||||
|
||||
<image class="mode-card-icon" :src="item.icon" mode="aspectFit" />
|
||||
<image
|
||||
class="mode-card-icon"
|
||||
:src="getTrainingIcon(item)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -278,32 +474,6 @@ onShow(() => {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 18rpx 20rpx 60rpx 20rpx;
|
||||
|
||||
}
|
||||
|
||||
.top-background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.top-background {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.week-grid {
|
||||
@@ -323,7 +493,7 @@ onShow(() => {
|
||||
.week-item-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient( 180deg, #2F2D2B 0%, #252831 100%);
|
||||
background: linear-gradient(180deg, #2f2d2b 0%, #252831 100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -339,7 +509,7 @@ onShow(() => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 10rpx;
|
||||
color: #fff;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
line-height: 28rpx;
|
||||
@@ -360,11 +530,8 @@ onShow(() => {
|
||||
|
||||
.stats-card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient( 180deg, #2F2D2B 0%, #252831 100%);
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, #2f2d2b 0%, #252831 100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -427,7 +594,6 @@ onShow(() => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 6rpx;
|
||||
width: auto;
|
||||
min-width: 72rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 6rpx;
|
||||
@@ -442,9 +608,7 @@ onShow(() => {
|
||||
font-size: 34rpx;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.stats-unit {
|
||||
@@ -452,12 +616,9 @@ onShow(() => {
|
||||
z-index: 1;
|
||||
margin-left: 4rpx;
|
||||
padding-bottom: 8rpx;
|
||||
color: #ffffff;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 400;
|
||||
line-height: 28rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -466,11 +627,7 @@ onShow(() => {
|
||||
margin-top: 6rpx;
|
||||
color: #fcce96;
|
||||
font-size: 20rpx;
|
||||
font-weight: 400;
|
||||
line-height: 28rpx;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
white-space: nowrap;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -496,7 +653,7 @@ onShow(() => {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 18rpx;
|
||||
top: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -525,8 +682,7 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.record-arrow {
|
||||
width: 12rpx;
|
||||
margin-left: 8rpx;
|
||||
width: 24rpx;
|
||||
}
|
||||
|
||||
.radar-board {
|
||||
@@ -614,6 +770,13 @@ onShow(() => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.featured-card-title {
|
||||
color: #895409;
|
||||
font-size: 28rpx;
|
||||
font-family: "AlimamaShuHeiTi-Bold", "PingFang SC", sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
|
||||
.featured-card-progress {
|
||||
margin-left: 18rpx;
|
||||
@@ -632,30 +795,25 @@ onShow(() => {
|
||||
.mode-card {
|
||||
position: relative;
|
||||
height: 150rpx;
|
||||
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: 2rpx solid rgba(235, 184, 123, 0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.mode-card-bg {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mode-tag {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 72rpx;
|
||||
background: linear-gradient( 133deg, #FFD19A 0%, #A17636 100%);
|
||||
height: 34rpx;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
font-size: 20rpx;
|
||||
color: #000;
|
||||
border-bottom-right-radius: 16rpx;
|
||||
background: linear-gradient(133deg, #ffd19a 0%, #a17636 100%);
|
||||
}
|
||||
|
||||
.mode-card-copy {
|
||||
@@ -680,6 +838,11 @@ onShow(() => {
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.mode-card-title-image {
|
||||
display: block;
|
||||
width: 128rpx;
|
||||
}
|
||||
|
||||
.mode-card-progress {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
|
||||
@@ -5,7 +5,7 @@ import Container from "@/components/Container.vue";
|
||||
import ShootProgress from "./components/ShootProgress.vue";
|
||||
import BowTarget from "./components/BowTarget.vue";
|
||||
import ScorePanel2 from "./components/ScorePanel2.vue";
|
||||
import ScoreResult from "@/components/ScoreResult.vue";
|
||||
import ScoreResult from "./components/ScoreResult.vue";
|
||||
import Avatar from "@/components/Avatar.vue";
|
||||
import BowPower from "@/components/BowPower.vue";
|
||||
import TestDistance from "./components/TestDistance.vue";
|
||||
@@ -79,6 +79,15 @@ async function onComplete() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onRetry() {
|
||||
practiseId.value = "";
|
||||
practiseResult.value = {};
|
||||
start.value = false;
|
||||
scores.value = [];
|
||||
const result = await createPractiseAPI(total, 120, targetType.value);
|
||||
if (result) practiseId.value = result.id;
|
||||
}
|
||||
|
||||
const onClickShare = debounce(async () => {
|
||||
await sharePractiseData("shareCanvas", 2, user.value, practiseResult.value);
|
||||
await wxShare("shareCanvas");
|
||||
@@ -175,14 +184,8 @@ onBeforeUnmount(() => {
|
||||
:rowCount="6"
|
||||
:total="total"
|
||||
:onClose="onComplete"
|
||||
:onRetry="onRetry"
|
||||
:result="practiseResult"
|
||||
:tipSrc="`../static/${
|
||||
practiseResult.details.filter(
|
||||
(arrow) => arrow.x !== -30 && arrow.y !== -30
|
||||
).length < total
|
||||
? 'un'
|
||||
: ''
|
||||
}finish-tip.png`"
|
||||
/>
|
||||
<canvas class="share-canvas" id="shareCanvas" type="2d"></canvas>
|
||||
</block>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 838 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
src/static/training-difficulty-design/result-bg.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
src/static/training-difficulty-design/result-c-bg.png
Normal file
|
After Width: | Height: | Size: 849 B |
BIN
src/static/training-difficulty-design/result-icon-1.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/static/training-difficulty-design/result-icon-2.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/static/training-difficulty-design/result-icon-3.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/static/training-difficulty-design/result-rice.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/static/training-difficulty-design/result-t-bg.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
src/static/training-difficulty-design/result-up.png
Normal file
|
After Width: | Height: | Size: 719 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1011 B After Width: | Height: | Size: 1011 B |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
BIN
src/static/training-home/jiezouxunlian.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/training-home/jingzhunxunlian.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/static/training-home/liliangxulian.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
BIN
src/static/training-home/nailixunlian.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |