diff --git a/src/components/Avatar.vue b/src/components/Avatar.vue index dc8290b..470db77 100644 --- a/src/components/Avatar.vue +++ b/src/components/Avatar.vue @@ -1,5 +1,5 @@ + + + + diff --git a/src/pages/training/components/BowTarget.vue b/src/pages/training/components/BowTarget.vue index a85473f..35ce83b 100644 --- a/src/pages/training/components/BowTarget.vue +++ b/src/pages/training/components/BowTarget.vue @@ -1,6 +1,15 @@ @@ -160,6 +450,25 @@ onBeforeUnmount(() => { }} --> + + @@ -169,20 +478,14 @@ onBeforeUnmount(() => { 经验 +1 {{ latestOne.ringX ? "X" : latestOne.ring || "未上靶" }} @@ -193,20 +496,14 @@ onBeforeUnmount(() => { user.id === bluelatestOne.playerId " class="e-value fade-in-out" - :style="{ - left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 20), - top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 40), - }" + :style="getExperienceTipStyle(bluelatestOne)" > 经验 +1 {{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶" }} @@ -217,8 +514,7 @@ onBeforeUnmount(() => { index === scores.length - 1 && latestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000', }" >{{ index + 1 }} { index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: '#1840FF', }" > {{ index + 1 }} - { margin: 10px; width: calc(100% - 20px); height: calc(100% - 20px); - z-index: -1; + z-index: 0; +} +.target-image { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; +} +.target-highlight-layer { + position: absolute; + z-index: 1; + pointer-events: none; } .e-value { position: absolute; @@ -275,7 +583,7 @@ onBeforeUnmount(() => { font-size: 12px; padding: 4px 7px; border-radius: 5px; - z-index: 2; + z-index: 4; width: 50px; text-align: center; } @@ -284,7 +592,7 @@ onBeforeUnmount(() => { color: #fff; font-size: 30px; font-weight: bold; - z-index: 2; + z-index: 4; width: 100px; text-align: center; } @@ -292,31 +600,44 @@ onBeforeUnmount(() => { font-size: 24px; margin-left: 5px; } -.target > image:last-child { - width: 100%; - height: 100%; +@keyframes target-tip-fade-in-out { + 0% { + transform: translate(-50%, -50%) translateY(20px); + opacity: 0; + } + + 30% { + transform: translate(-50%, -50%); + opacity: 1; + } + + 80% { + transform: translate(-50%, -50%); + opacity: 1; + } + + 100% { + transform: translate(-50%, -50%); + opacity: 0; + } +} +.round-tip.fade-in-out, +.e-value.fade-in-out { + animation: target-tip-fade-in-out 1.2s ease forwards; } .hit { position: absolute; border-radius: 50%; - z-index: 1; + z-index: 3; color: #fff; transition: all 0.3s ease; + box-sizing: border-box; } .s-point { - width: 4px; - height: 4px; - min-width: 4px; - min-height: 4px; } .b-point { - width: 10px; - height: 10px; - min-width: 10px; - min-height: 10px; border: 1px solid #fff; - z-index: 1; - box-sizing: border-box; + z-index: 3; display: flex; justify-content: center; align-items: center; @@ -332,6 +653,19 @@ onBeforeUnmount(() => { transform: translate(-50%, -50%);*/ margin-top: 2rpx; } +@keyframes target-pump-in { + from { + transform: translate(-50%, -50%) scale(2); + } + + to { + transform: translate(-50%, -50%) scale(1); + } +} +.hit.pump-in { + animation: target-pump-in 0.3s ease-out forwards; + transform-origin: center center; +} .header { width: 100%; display: flex; @@ -382,7 +716,7 @@ onBeforeUnmount(() => { height: 60px; left: calc(50% - 100px); top: calc(50% - 30px); - z-index: 99; + z-index: 5; font-weight: bold; } .arrow-dir { @@ -391,6 +725,7 @@ onBeforeUnmount(() => { height: 52%; left: 50%; bottom: 50%; + z-index: 4; display: flex; align-items: center; justify-content: center; diff --git a/src/pages/training/components/ScorePanel.vue b/src/pages/training/components/ScorePanel.vue new file mode 100644 index 0000000..b6d3818 --- /dev/null +++ b/src/pages/training/components/ScorePanel.vue @@ -0,0 +1,124 @@ + + + + diff --git a/src/pages/training/components/ShootProgress.vue b/src/pages/training/components/ShootProgress.vue index 4915f21..8cea17c 100644 --- a/src/pages/training/components/ShootProgress.vue +++ b/src/pages/training/components/ShootProgress.vue @@ -206,7 +206,12 @@ onBeforeUnmount(() => { - + {{ displayName }} @@ -271,6 +276,9 @@ onBeforeUnmount(() => { box-sizing: border-box; border-radius: 50%; background: linear-gradient(180deg, rgba(255, 209, 153, 1), rgba(162, 119, 55, 1)); + display: flex; + align-items: center; + justify-content: center; } .progress-card__avatar { diff --git a/src/pages/training/difficulty.vue b/src/pages/training/difficulty.vue index 5e02476..0f21262 100644 --- a/src/pages/training/difficulty.vue +++ b/src/pages/training/difficulty.vue @@ -2,7 +2,6 @@ import { computed, nextTick, ref } from "vue"; import { onLoad, onShow, onUnload } from "@dcloudio/uni-app"; import Container from "@/components/Container.vue"; -import TargetPicker from "@/components/TargetPicker.vue"; import TrainingDifficultyBadge from "./components/TrainingDifficultyBadge.vue"; import TrainingDifficultyPreviewCard from "./components/TrainingDifficultyPreviewCard.vue"; import TrainingDifficultyStartButton from "./components/TrainingDifficultyStartButton.vue"; @@ -40,6 +39,7 @@ const routeModeTypeMap = { precision: "precision", rhythm: "rhythm", }; +const defaultTargetType = 1; const resolveTrainingType = (mode) => { const normalizedMode = String(mode || "").toLowerCase(); @@ -232,7 +232,6 @@ const emptyDifficulty = { const pageConfig = ref(createEmptyModeConfig(defaultTrainingType)); const unlockedDifficultyId = ref(defaultUnlockedDifficultyId); const selectedDifficultyId = ref(defaultUnlockedDifficultyId); -const showTargetPicker = ref(false); const nodesScrollTop = ref(0); const nodesScrollWithAnimation = ref(false); const routeOptions = ref({}); @@ -555,11 +554,63 @@ const initPageState = async (options = {}, refreshOptions = {}) => { } }; -const resolveTargetPaperType = (target) => { - return Number(target) === 1 ? "20厘米全环靶" : "40厘米全环靶"; +const cleanQueryValue = (value) => { + if (value === undefined || value === null || value === "") { + return ""; + } + + if (typeof value === "number" && !Number.isFinite(value)) { + return ""; + } + + return value; }; -const saveTrainingContext = (target) => { +const createPracticeQuery = (difficulty) => { + const trainingType = pageConfig.value.key || defaultTrainingType; + const commonQuery = { + type: trainingType, + difficultyId: difficulty.id, + difficulty: difficulty.level, + recordId: difficulty.recordId, + arrows: toNumber(difficulty.arrows, 12), + time: toNumber(difficulty.time_limit, 120) || 120, + target: defaultTargetType, + }; + const typedQueryMap = { + base: { + hitReq: toNumber(difficulty.hit_req), + }, + endurance: { + totalReq: toNumber(difficulty.total_req), + }, + precision: { + blocks: toNumber(difficulty.blocks), + mode: toNumber(difficulty.mode), + }, + rhythm: { + hitReq: toNumber(difficulty.hit_req), + mode: toNumber(difficulty.mode), + }, + }; + + return { + ...commonQuery, + ...(typedQueryMap[trainingType] || {}), + }; +}; + +const createPracticeUrl = (difficulty) => { + const query = Object.entries(createPracticeQuery(difficulty)) + .map(([key, value]) => [key, cleanQueryValue(value)]) + .filter(([, value]) => value !== "") + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join("&"); + + return `/pages/training/practise-one${query ? `?${query}` : ""}`; +}; + +const saveTrainingContext = () => { const difficulty = selectedDifficulty.value; if (!difficulty.id) { @@ -571,9 +622,8 @@ const saveTrainingContext = (target) => { trainingTitle: pageConfig.value.title, difficultyId: difficulty.id, difficultyLabel: difficulty.label, - targetPaperType: target - ? resolveTargetPaperType(target) - : difficulty.targetPaperType, + targetType: defaultTargetType, + targetPaperType: difficulty.targetPaperType, }); }; @@ -606,30 +656,8 @@ const handleStart = () => { } saveTrainingContext(); - uni.showToast({ - title: `${selectedDifficulty.value.title} 即将开始`, - icon: "none", - }); -}; - -const openTargetPicker = () => { - if (!selectedDifficulty.value.id) { - return; - } - - showTargetPicker.value = true; -}; - -const handleTargetConfirm = (target) => { - showTargetPicker.value = false; - saveTrainingContext(target); - // uni.showToast({ - // title: `${selectedDifficulty.value.title} 即将开始`, - // icon: "none", - // }); - uni.navigateTo({ - url: `/pages/training/practise-one?target=${target}`, + url: createPracticeUrl(selectedDifficulty.value), }); }; @@ -708,15 +736,10 @@ onUnload(() => { - diff --git a/src/pages/training/practise-one.vue b/src/pages/training/practise-one.vue index 8e4e401..653c62b 100644 --- a/src/pages/training/practise-one.vue +++ b/src/pages/training/practise-one.vue @@ -1,5 +1,5 @@ @@ -123,6 +243,7 @@ onBeforeUnmount(() => { @@ -143,7 +264,25 @@ onBeforeUnmount(() => { :totalRound="start ? total / 4 : 0" :currentRound="scores.length % 3" :scores="scores" + :showCrosshair="false" + :highlightAreas="targetHighlightAreas" /> + + + +