update:代码备份
This commit is contained in:
@@ -1,6 +1,15 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, computed } from "vue";
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import PointSwitcher from "@/components/PointSwitcher.vue";
|
||||
import TargetCanvas from "@/components/TargetCanvas.vue";
|
||||
|
||||
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
||||
import { simulShootAPI } from "@/apis";
|
||||
@@ -8,6 +17,7 @@ import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
const store = useStore();
|
||||
const { user, device } = storeToRefs(store);
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
currentRound: {
|
||||
@@ -34,6 +44,39 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
coordinateRadius: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
hitRadiusPx: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
zoomHitRadiusPx: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
showCrosshair: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showQuadrantLabels: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
quadrantLabels: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
1: "1",
|
||||
2: "2",
|
||||
3: "3",
|
||||
4: "4",
|
||||
}),
|
||||
},
|
||||
highlightAreas: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const pMode = ref(true);
|
||||
@@ -45,6 +88,100 @@ const timer = ref(null);
|
||||
const dirTimer = ref(null);
|
||||
const angle = ref(null);
|
||||
const circleColor = ref("");
|
||||
const ROUND_TIP_OFFSET_Y = -32;
|
||||
const EXPERIENCE_TIP_OFFSET_Y = -68;
|
||||
|
||||
const getNumber = (value, fallback = 0) => {
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : fallback;
|
||||
};
|
||||
|
||||
const safeTargetRadius = computed(() => {
|
||||
return Math.max(getNumber(props.coordinateRadius, 20), 1);
|
||||
});
|
||||
|
||||
const currentHitRadiusPx = computed(() => {
|
||||
const radius = Number(
|
||||
pMode.value ? props.zoomHitRadiusPx : props.hitRadiusPx
|
||||
);
|
||||
return Number.isFinite(radius) && radius >= 0 ? radius : 0;
|
||||
});
|
||||
|
||||
function getShotPoint(shot, fallbackCenter = false) {
|
||||
const x = Number(shot?.x);
|
||||
const y = Number(shot?.y);
|
||||
if (Number.isFinite(x) && Number.isFinite(y)) return { x, y };
|
||||
return fallbackCenter ? { x: 0, y: 0 } : null;
|
||||
}
|
||||
|
||||
function getPointDirection(point) {
|
||||
if (!point) return null;
|
||||
const distance = Math.sqrt(point.x * point.x + point.y * point.y);
|
||||
if (distance === 0) return null;
|
||||
|
||||
return {
|
||||
x: point.x / distance,
|
||||
y: point.y / distance,
|
||||
};
|
||||
}
|
||||
|
||||
function formatPxOffset(value) {
|
||||
if (!value) return "";
|
||||
const operator = value > 0 ? "+" : "-";
|
||||
return ` ${operator} ${Math.abs(value)}px`;
|
||||
}
|
||||
|
||||
function formatTargetPosition(percent, offset) {
|
||||
const pxOffset = formatPxOffset(offset);
|
||||
return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`;
|
||||
}
|
||||
|
||||
function getTargetPositionStyle(point, offsetPx = 0, extraOffset = {}) {
|
||||
if (!point) return { display: "none" };
|
||||
|
||||
const radius = safeTargetRadius.value;
|
||||
const diameter = radius * 2;
|
||||
const direction = getPointDirection(point);
|
||||
const xOffset = (direction ? direction.x * offsetPx : 0) + (extraOffset.x || 0);
|
||||
const yOffset = (direction ? -direction.y * offsetPx : 0) + (extraOffset.y || 0);
|
||||
const leftPercent = ((point.x + radius) / diameter) * 100;
|
||||
const topPercent = ((radius - point.y) / diameter) * 100;
|
||||
|
||||
return {
|
||||
left: formatTargetPosition(leftPercent, xOffset),
|
||||
top: formatTargetPosition(topPercent, yOffset),
|
||||
transform: "translate(-50%, -50%)",
|
||||
};
|
||||
}
|
||||
|
||||
function getHitStyle(shot) {
|
||||
const radius = currentHitRadiusPx.value;
|
||||
const point = getShotPoint(shot);
|
||||
|
||||
return {
|
||||
...getTargetPositionStyle(point, radius),
|
||||
width: `${radius * 2}px`,
|
||||
height: `${radius * 2}px`,
|
||||
};
|
||||
}
|
||||
|
||||
function getRoundTipStyle(shot) {
|
||||
const point = getShotPoint(shot, true);
|
||||
return getTargetPositionStyle(
|
||||
point,
|
||||
shot?.ring ? currentHitRadiusPx.value : 0,
|
||||
{ y: ROUND_TIP_OFFSET_Y }
|
||||
);
|
||||
}
|
||||
|
||||
function getExperienceTipStyle(shot) {
|
||||
const point = getShotPoint(shot, true);
|
||||
return getTargetPositionStyle(
|
||||
point,
|
||||
shot?.ring ? currentHitRadiusPx.value : 0,
|
||||
{ y: EXPERIENCE_TIP_OFFSET_Y }
|
||||
);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.scores,
|
||||
@@ -80,14 +217,6 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
function calcRealX(num, offset = 3.4) {
|
||||
const len = 20.4 + num;
|
||||
return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
|
||||
}
|
||||
function calcRealY(num, offset = 3.4) {
|
||||
const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num;
|
||||
return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
|
||||
}
|
||||
const simulShoot = async () => {
|
||||
if (device.value.deviceId) await simulShootAPI(device.value.deviceId);
|
||||
};
|
||||
@@ -111,6 +240,160 @@ const arrowStyle = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const currentArrowIndex = computed(() => {
|
||||
return props.scores.length + props.blueScores.length + 1;
|
||||
});
|
||||
|
||||
const getHighlightArrowIndex = (area = {}) => {
|
||||
const arrowIndex = Number(area.arrowIndex ?? area.arrowNo ?? area.arrow);
|
||||
return Number.isInteger(arrowIndex) && arrowIndex > 0 ? arrowIndex : null;
|
||||
};
|
||||
|
||||
const currentHighlightAreas = computed(() => {
|
||||
if (!Array.isArray(props.highlightAreas) || props.highlightAreas.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hasExplicitArrowIndex = props.highlightAreas.some((area = {}) => {
|
||||
return getHighlightArrowIndex(area) !== null;
|
||||
});
|
||||
|
||||
const matchedAreas = props.highlightAreas.filter((area = {}) => {
|
||||
return getHighlightArrowIndex(area) === currentArrowIndex.value;
|
||||
});
|
||||
|
||||
if (hasExplicitArrowIndex) {
|
||||
return matchedAreas;
|
||||
}
|
||||
|
||||
if (props.highlightAreas.length === 1) {
|
||||
return props.highlightAreas.slice(0, 1);
|
||||
}
|
||||
|
||||
const currentArea = props.highlightAreas[currentArrowIndex.value - 1];
|
||||
return currentArea ? [currentArea] : [];
|
||||
});
|
||||
|
||||
const showHighlightCanvas = computed(() => {
|
||||
return props.totalRound > 0 && currentHighlightAreas.value.length > 0;
|
||||
});
|
||||
|
||||
const targetImageNaturalSize = ref({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
const targetLayerRect = ref({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
ready: false,
|
||||
});
|
||||
|
||||
const targetHighlightLayerStyle = computed(() => ({
|
||||
left: `${targetLayerRect.value.left}px`,
|
||||
top: `${targetLayerRect.value.top}px`,
|
||||
width: `${targetLayerRect.value.width}px`,
|
||||
height: `${targetLayerRect.value.height}px`,
|
||||
}));
|
||||
|
||||
const getRectNumber = (value, fallback = 0) => {
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : fallback;
|
||||
};
|
||||
|
||||
const getImageContentRect = (imageRect) => {
|
||||
const naturalWidth = getRectNumber(targetImageNaturalSize.value.width);
|
||||
const naturalHeight = getRectNumber(targetImageNaturalSize.value.height);
|
||||
const imageWidth = getRectNumber(imageRect?.width);
|
||||
const imageHeight = getRectNumber(imageRect?.height);
|
||||
|
||||
if (naturalWidth <= 0 || naturalHeight <= 0 || imageWidth <= 0 || imageHeight <= 0) {
|
||||
return imageRect;
|
||||
}
|
||||
|
||||
const naturalRatio = naturalWidth / naturalHeight;
|
||||
const boxRatio = imageWidth / imageHeight;
|
||||
|
||||
if (naturalRatio > boxRatio) {
|
||||
const contentHeight = imageWidth / naturalRatio;
|
||||
return {
|
||||
left: imageRect.left,
|
||||
top: imageRect.top + (imageHeight - contentHeight) / 2,
|
||||
width: imageWidth,
|
||||
height: contentHeight,
|
||||
};
|
||||
}
|
||||
|
||||
const contentWidth = imageHeight * naturalRatio;
|
||||
return {
|
||||
left: imageRect.left + (imageWidth - contentWidth) / 2,
|
||||
top: imageRect.top,
|
||||
width: contentWidth,
|
||||
height: imageHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const updateTargetLayerRect = async () => {
|
||||
await nextTick();
|
||||
|
||||
const query = uni.createSelectorQuery().in(instance?.proxy);
|
||||
query.select(".target").boundingClientRect();
|
||||
query.select(".target-image").boundingClientRect();
|
||||
query.exec((rects = []) => {
|
||||
const stageRect = rects[0];
|
||||
const imageRect = rects[1];
|
||||
|
||||
if (!stageRect || !imageRect || !imageRect.width || !imageRect.height) {
|
||||
targetLayerRect.value = {
|
||||
...targetLayerRect.value,
|
||||
ready: false,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const contentRect = getImageContentRect(imageRect);
|
||||
const width = Math.round(getRectNumber(contentRect?.width));
|
||||
const height = Math.round(getRectNumber(contentRect?.height));
|
||||
|
||||
targetLayerRect.value = {
|
||||
left: getRectNumber(contentRect?.left) - getRectNumber(stageRect.left),
|
||||
top: getRectNumber(contentRect?.top) - getRectNumber(stageRect.top),
|
||||
width,
|
||||
height,
|
||||
ready: width > 0 && height > 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const onTargetImageLoad = (event) => {
|
||||
const width = getRectNumber(event?.detail?.width);
|
||||
const height = getRectNumber(event?.detail?.height);
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
targetImageNaturalSize.value = {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
updateTargetLayerRect();
|
||||
};
|
||||
|
||||
const onWindowResize = () => {
|
||||
updateTargetLayerRect();
|
||||
};
|
||||
|
||||
watch(
|
||||
showHighlightCanvas,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
updateTargetLayerRect();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function onReceiveMessage(message) {
|
||||
if (Array.isArray(message)) return;
|
||||
if (message.type === MESSAGETYPESV2.ShootResult && message.shootData) {
|
||||
@@ -135,6 +418,10 @@ async function onReceiveMessage(message) {
|
||||
|
||||
onMounted(() => {
|
||||
uni.$on("socket-inbox", onReceiveMessage);
|
||||
setTimeout(updateTargetLayerRect, 30);
|
||||
if (uni.onWindowResize) {
|
||||
uni.onWindowResize(onWindowResize);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -147,6 +434,9 @@ onBeforeUnmount(() => {
|
||||
dirTimer.value = null;
|
||||
}
|
||||
uni.$off("socket-inbox", onReceiveMessage);
|
||||
if (uni.offWindowResize) {
|
||||
uni.offWindowResize(onWindowResize);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -160,6 +450,25 @@ onBeforeUnmount(() => {
|
||||
}}</text>
|
||||
</view> -->
|
||||
<view class="target">
|
||||
<image
|
||||
class="target-image"
|
||||
src="../../../static/bow-target.png"
|
||||
mode="aspectFit"
|
||||
@load="onTargetImageLoad"
|
||||
/>
|
||||
<TargetCanvas
|
||||
v-if="showHighlightCanvas && targetLayerRect.ready"
|
||||
class="target-highlight-layer"
|
||||
:style="targetHighlightLayerStyle"
|
||||
:canvasWidth="targetLayerRect.width"
|
||||
:canvasHeight="targetLayerRect.height"
|
||||
:coordinateRadius="coordinateRadius"
|
||||
:showCrosshair="false"
|
||||
:showQuadrantLabels="false"
|
||||
:showRingLabels="false"
|
||||
:highlightOnly="true"
|
||||
:highlightAreas="currentHighlightAreas"
|
||||
/>
|
||||
<view v-if="angle !== null" class="arrow-dir" :style="arrowStyle">
|
||||
<view :style="{ background: circleColor }">
|
||||
<image src="../../../static/dot-circle.png" mode="widthFix" />
|
||||
@@ -169,20 +478,14 @@ onBeforeUnmount(() => {
|
||||
<view
|
||||
v-if="latestOne && latestOne.ring && user.id === latestOne.playerId"
|
||||
class="e-value fade-in-out"
|
||||
:style="{
|
||||
left: calcRealX(latestOne.ring ? latestOne.x : 0, 20),
|
||||
top: calcRealY(latestOne.ring ? latestOne.y : 0, 40),
|
||||
}"
|
||||
:style="getExperienceTipStyle(latestOne)"
|
||||
>
|
||||
经验 +1
|
||||
</view>
|
||||
<view
|
||||
v-if="latestOne"
|
||||
class="round-tip fade-in-out"
|
||||
:style="{
|
||||
left: calcRealX(latestOne.ring ? latestOne.x : 0, 28),
|
||||
top: calcRealY(latestOne.ring ? latestOne.y : 0, 28),
|
||||
}"
|
||||
:style="getRoundTipStyle(latestOne)"
|
||||
>{{ latestOne.ringX ? "X" : latestOne.ring || "未上靶"
|
||||
}}<text v-if="latestOne.ring">环</text>
|
||||
</view>
|
||||
@@ -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
|
||||
</view>
|
||||
<view
|
||||
v-if="bluelatestOne"
|
||||
class="round-tip fade-in-out"
|
||||
:style="{
|
||||
left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 28),
|
||||
top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 28),
|
||||
}"
|
||||
:style="getRoundTipStyle(bluelatestOne)"
|
||||
>{{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶"
|
||||
}}<text v-if="bluelatestOne.ring">环</text></view
|
||||
>
|
||||
@@ -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',
|
||||
}"
|
||||
><text v-if="pMode">{{ index + 1 }}</text></view
|
||||
@@ -231,15 +527,13 @@ onBeforeUnmount(() => {
|
||||
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',
|
||||
}"
|
||||
>
|
||||
<text v-if="pMode">{{ index + 1 }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<image src="../../../static/bow-target.png" mode="widthFix" />
|
||||
</view>
|
||||
<view class="footer">
|
||||
<PointSwitcher
|
||||
@@ -266,7 +560,21 @@ onBeforeUnmount(() => {
|
||||
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;
|
||||
|
||||
124
src/pages/training/components/ScorePanel.vue
Normal file
124
src/pages/training/components/ScorePanel.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
const props = defineProps({
|
||||
rowCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
arrows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 25,
|
||||
},
|
||||
completeEffect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
const items = ref(new Array(props.total).fill(9));
|
||||
const width = ref(92);
|
||||
const itemWidth = ref(0);
|
||||
const bgImages = [
|
||||
"../static/complete-light1.png",
|
||||
"../static/complete-light2.png",
|
||||
];
|
||||
const bgIndex = ref(0);
|
||||
watch(
|
||||
() => props.total,
|
||||
(newValue) => {
|
||||
items.value = new Array(newValue).fill(9);
|
||||
}
|
||||
);
|
||||
const timer = ref(null);
|
||||
onMounted(() => {
|
||||
timer.value = setInterval(() => {
|
||||
bgIndex.value = bgIndex.value === 0 ? 1 : 0;
|
||||
}, 200);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<view class="container">
|
||||
<image
|
||||
v-if="total > 0 && arrows.length === total && completeEffect"
|
||||
:src="bgImages[bgIndex]"
|
||||
class="complete-light"
|
||||
:style="{
|
||||
width: `calc(${(100 / (rowCount + 2)) * rowCount}vw + ${
|
||||
(100 / (total * 2)) * (rowCount * 2 + (total === 12 ? 8 : 24))
|
||||
}px)`,
|
||||
height: `calc(${(100 / (rowCount + 2)) * (total / rowCount)}vw + ${
|
||||
(100 / (total * 2)) *
|
||||
((total / rowCount) * 2 + (total === 12 ? 7 : 24))
|
||||
}px)`,
|
||||
top: `${total === 12 ? -2 : -3}vw`,
|
||||
}"
|
||||
/>
|
||||
<view
|
||||
v-for="(_, index) in items"
|
||||
:key="index"
|
||||
class="score-item"
|
||||
:style="{
|
||||
width: 100 / (rowCount + 2) + 'vw',
|
||||
height: 100 / (rowCount + 2) + 'vw',
|
||||
lineHeight: 100 / (rowCount + 2) + 'vw',
|
||||
fontSize: fontSize + 'px',
|
||||
margin: 100 / (total * 2) + 'px',
|
||||
}"
|
||||
>
|
||||
<image src="/static/score-bg.png" mode="widthFix" />
|
||||
<text
|
||||
:style="{ fontWeight: arrows[index] !== undefined ? 'bold' : 'normal' }"
|
||||
>{{
|
||||
!arrows[index] ? "-" : arrows[index].ringX ? "X" : arrows[index].ring
|
||||
}}</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 92vw;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin: 0 4vw;
|
||||
position: relative;
|
||||
padding: 1vw 0;
|
||||
}
|
||||
.score-item {
|
||||
/* background-image: url("../static/score-bg.png");
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center; */
|
||||
color: #fed847;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.score-item > image {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 5%;
|
||||
}
|
||||
.score-item > text {
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.complete-light {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@@ -206,7 +206,12 @@ onBeforeUnmount(() => {
|
||||
<view class="progress-card__header">
|
||||
<view class="progress-card__profile">
|
||||
<view class="progress-card__avatar-shell">
|
||||
<Avatar :src="user.avatar" :size="40" />
|
||||
<Avatar
|
||||
:src="avatarSrc"
|
||||
:size="80"
|
||||
size-unit="rpx"
|
||||
image-mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<text class="progress-card__name">{{ displayName }}</text>
|
||||
</view>
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user