123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- <template>
- <view class="luojigou-board" >
- <view class="board" :style="{scale: adaptatio, transformOrigin: '50% 0%'}" >
- <image class="board-img" :src="boardUrl" />
- <view class="board-header" id="game-label">
- <view class="trumpt" @click="emits('playAudio')">
- <image class="dog" :src="staticImg.trumptDog" />
- <image
- class="trumpt-icon"
- :src="props.playLoading ? staticImg.trumptGif : staticImg.trumptPng"
- />
- </view>
- <view class="tip " :key="props.cardDesc">
- <scroll-view
- scroll-y
- :style="{height: '88rpx',
- }"
- >
- {{props.cardDesc ? props.cardDesc : "加載中"}}
- </scroll-view>
- </view>
- </view>
- <view class="card" >
- <view class="ques" ref="quesRef" id="quesRef" >
- <image :src="props.board.quesUrl" alt="" />
- </view>
- <view class="ans" ref="ansRef" id="ansRef">
- <image :src="props.board.ansUrl" alt="" />
- </view>
- </view>
- </view>
- <view
- class="mark-button"
- :style="{
- width: 357 * rate + 'rpx',
- height: 466 * rate + 'rpx',
- top: 102 * rate + 'rpx',
- left: '50%',
- transform: 'translateX(-50%)'
- }"
- >
- <view
- v-for="item in props.board.buttons"
- class="movable-image"
- :style="{
- top: Number(item.y) * rate + 'rpx',
- left: Number(item.x) * rate + 'rpx',
- width: 46 * rate + 'rpx',
- height: 46 * rate + 'rpx',
- backgroundColor: colorMap.get(item.color),
- scale: 0.7,
- borderRadius: '50%'
- }"
- />
- </view>
- <movable-area
- v-if="props.cardType !== undefined"
- class="movable-area"
- id="movableAreaRef"
- :style="{
- width: 357 * rate + 'rpx',
- height: 466 * rate + 'rpx',
- top: 102 * rate + 'rpx',
- left: '50%',
- transform: 'translateX(-50%)'
- }"
- >
- <movable-view
- v-for="item in buttons"
- :key="item.id"
- :disabled="item.disabled"
- :x="item.x"
- :y="item.y"
- damping="100"
- direction="all"
- class="movable-view"
- :style="{
- zIndex: item.zIndex,
- width: 46 * rate + 'rpx',
- height: 46 * rate + 'rpx',
- }"
- @touchend="touchend($event, item)"
- @touchstart="touchStart(item)"
- >
- <image
- :id="`rock-id-${item.id}`"
- :class="`movable-image `"
- :style="{willChange: 'transform', transformOrigin: `center bottom`}"
- :src="item.url"
- />
-
- <image
- v-if="item.ans"
- class="success-flag"
- :src="staticImg.successFlag"
- />
- </movable-view>
- </movable-area>
- </view>
- </template>
- <script setup lang="ts" name="luojigou-board" >
- import { defineProps, reactive, ref, computed, getCurrentInstance, defineEmits } from 'vue'
- import { useStaticImg, useSchedulerOnce, useQueryElInfo, useAdaptationIpadAndPhone } from '@/hooks/index'
- import type { CardModeEnum } from '@/enum/constant';
- import { useCalcQuantityStore } from '@/store/index';
- import { AudioController } from '@/controller/AudioController';
- interface Buttons {
- id: number,
- x: number,
- y: number,
- initX: number,
- initY: number,
- url: string,
- color: API.Color,
- index: number,
- zIndex: number,
- ans: API.Color | null
- disabled: boolean
- }
- interface IProps {
- cardType: 0 | 1, // 0 四钮题卡 | 1 六钮题卡
- board: API.Board,
- mode: CardModeEnum,
- cardDesc: string,
- playLoading: boolean,
- getExpose: (records: any) => void
- }
- const staticImg = useStaticImg()
- const adaptatio = useAdaptationIpadAndPhone()
- const calcQuantityStore = useCalcQuantityStore()
- const props = defineProps<IProps>()
- const emits = defineEmits(['playAudio', 'submit'])
- const state = reactive({
- zIndex: 0, // 暂存zIndex
- disabled: false, // 是否禁用moveview
- correctQuantity: 0, // 正确数量()
- totalQuantity: 0 // 总数量 (移动按钮数量)
- })
- const colorMap = new Map([
- ['red', '#FF0002'],
- ['blue', '#0061B4'],
- ['purple', '#A764AB'],
- ['yellow', '#FFD800'],
- ['green', '#008D46'],
- ['orange', '#FF7F00'],
- ])
- const boardUrl = props.cardType == 1 ? staticImg.boardSix : staticImg.boardFour
- const ansRef = ref<UniApp.NodeInfo>()
- const quesRef = ref<UniApp.NodeInfo>()
- const movableAreaRef = ref<UniApp.NodeInfo>()
- useQueryElInfo('#quesRef', (nodeInfo) => quesRef.value = nodeInfo as UniApp.NodeInfo, getCurrentInstance()!)
- useQueryElInfo('#ansRef', (nodeInfo) => ansRef.value = nodeInfo as UniApp.NodeInfo, getCurrentInstance()!)
- useQueryElInfo('#movableAreaRef', (nodeInfo) => movableAreaRef.value = nodeInfo as UniApp.NodeInfo, getCurrentInstance()!)
- const baseRatio = adaptatio
- const rate = baseRatio * 2
- const { windowWidth, windowHeight } = uni.getSystemInfoSync()
- const unit = windowWidth / windowHeight > 0.6 ? 1 : 0.5
- const ansItemHeight = computed(() => Math.floor(ansRef.value?.height! / copies))
- const TPos = (x: number, y: number) => {
-
- return { x: x - movableAreaRef.value?.left! - 23 * rate * unit , y: y - movableAreaRef.value?.top! - 23 * rate * unit }
-
- }
- const getButtonIndex = (_y: number) => Math.floor(_y / ansItemHeight.value)
- const getButtonPosByIndex = (index: number) => {
- const x = ansRef.value?.width! + quesRef.value?.width!
-
- const y = ansItemHeight.value * index + ansItemHeight.value / 2 - 23 * rate * unit
- return { x, y }
- }
- // 几钮模板
- const copies = props.cardType === 0 ? 4 : 6
- const buttonWidth = 46 * windowWidth / 375 * baseRatio
- const VSpace = props.cardType === 0 ?Math.floor(windowWidth / 375 * 26) * baseRatio + buttonWidth : Math.floor(windowWidth / 375 * 8) * baseRatio + buttonWidth
- const Y = 435 * rate
- const getX = (index: number) => index * VSpace + Math.floor(windowWidth / 375 * 11) * baseRatio
- // 注意一下四钮 用哪几个颜色的按钮
- let buttons = reactive<Buttons[]>([
- {id: 0, ans: null, x: getX(0), y: Y * rate,disabled: false, initX: getX(0), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.red, color: 'red' },
- {id: 1, ans: null, x: getX(1), y: Y * rate,disabled: false, initX: getX(1), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.blue, color: 'blue' },
- {id: 2, ans: null, x: getX(2), y: Y * rate,disabled: false, initX: getX(2), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.green, color: 'green' },
- {id: 3, ans: null, x: getX(3), y: Y * rate,disabled: false, initX: getX(3), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.orange, color: 'orange' },
- {id: 4, ans: null, x: getX(4), y: Y * rate,disabled: false, initX: getX(4), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.yellow, color: "yellow" },
- {id: 5, ans: null, x: getX(5), y: Y * rate,disabled: false, initX: getX(5), initY: Y * rate, zIndex: 0, index: -1, url: staticImg.purple, color: 'purple' },
- ])
- buttons.splice(props.cardType == 1 ? buttons.length : buttons.length - 2, buttons.length)
- // 让按钮回到原位
- const disPatchButtonGoInitPos = (index: number) => {
-
- useSchedulerOnce(() => {
- buttons[index].x = buttons[index].initX + Math.random()
- buttons[index].y = buttons[index].initY + Math.random()
- buttons[index].index = -1
- buttons[index].ans = null
- })
- useSchedulerOnce(() => {
- state.disabled = false
- }, 200)
- }
- const touchStart = (item: Buttons) => {
- useSchedulerOnce(() => {
- state.zIndex ++
- item.zIndex = state.zIndex
- })
- }
- // 按钮脱手
- const touchend = (ev: TouchEvent, item: Buttons) => {
- if (item.disabled) return
- const { x: itemX, y: itemY } = TPos(ev.changedTouches[0].pageX, ev.changedTouches[0].pageY)
- // 返回原点 (没有放在答案区, 按钮回到初始位置)
- if (itemX < quesRef.value?.width! || itemY > ansRef.value?.height! ) {
- disPatchButtonGoInitPos(item.id)
- } else {
-
- const index = getButtonIndex(itemY <= 0 ? 0 : itemY)
- const { x: targetX, y: targetY } = getButtonPosByIndex(index)
- console.log(item.x , targetX , item.y, targetY);
-
- // 直接点击答案区的按钮,答案区按钮回到初始位置
- if (item.x == targetX && item.y == targetY) {
- // disPatchButtonGoInitPos(item.id)
- } else {
- // 两个按钮都在答案区, 直接进行按钮之间的交换
- const isHasIndex = buttons.findIndex(button => button.index == index && button.id !== item.id )
-
- if (isHasIndex >= 0) { // 答案区的目标区域存在按钮
-
- if (item.index == -1) { // 答案区的目标区域存在按钮 操作按钮是从待操作区域移过来的 目标按钮回到原位
- // disPatchButtonGoInitPos(isHasIndex)
- } else { // 答案区的目标区域存在按钮 操作按钮是和目标按钮互换位置
- // useSchedulerOnce(() => {
- // const oldPos = getButtonPosByIndex(item.index)
- // buttons[isHasIndex].x = oldPos.x
- // buttons[isHasIndex].y = oldPos.y
- // buttons[isHasIndex].index = item.index
- // })
- }
- }
- // 将按钮放置到答案区对应的位置
- useSchedulerOnce(() => {
- item.x = targetX
- item.y = targetY
- item.index = index
- })
- checkAns(index, item)
- }
- }
- }
- // 判断答案 (学习计划适用逻辑)
- const checkAns = (index: number, itemData: Buttons) => {
- const targetData = props.board.ansList
- console.log(targetData, index, itemData);
-
- // 移动到了正确位置
- if (targetData[index].ans === itemData.color) {
- itemData.ans = targetData[index].color
-
- calcQuantityStore.correctQuantity++
- AudioController.playCorrect()
- itemData.disabled = true
- } else { // 移动到了錯誤位置
- AudioController.playWrong()
- calcQuantityStore.wrongCount++
- itemData.ans = null
- const rockNode = document.getElementById(`rock-id-${itemData.id}`)
- rockNode?.classList.add('rock-button-ani')
- useSchedulerOnce(() => {
- disPatchButtonGoInitPos(itemData.id)
-
- rockNode?.classList.remove('rock-button-ani')
- }, 1000)
- }
-
- calcQuantityStore.totalQuantity++
- // 全部正确后触发提交答案的emit
- const ansLen = buttons.filter( button => button.ans).length
- if ( ansLen === ( props.cardType === 0 ? 4 : 6 )) {
- emits('submit')
- }
- }
- </script>
- <style lang="less" scoped >
- .luojigou-board {
- width: 714rpx;
- height: 1234rpx;
- position: relative;
- display: flex;
- justify-content: center;
- .board {
- width: 714rpx;
- height: 1234rpx;
- position: relative;
- .board-img {
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1;
- }
- .board-header {
- width: 100%;
- height: 148rpx;
- position: relative;
- top: 7rpx;
- z-index: 12;
- .trumpt {
- width: 96rpx;
- height: 96rpx;
- position: relative;
- top: 44rpx;
- left: 54rpx;
- .dog {
- width: 96rpx;
- height: 96rpx;
- position: absolute;
- z-index: 1;
- }
- .trumpt-icon {
- width: 40rpx;
- height: 40rpx;
- position: absolute;
- z-index: 2;
- right: -12rpx;
- bottom: -6rpx;
- }
- }
- .tip {
- width: 490rpx;
- height: 72rpx;
- font-size: 24rpx;
- font-family: PingFangSC-Medium, PingFang SC;
- font-weight: 500;
- color: #ffffff;
- position: absolute;
- top: 50rpx;
- left: 176rpx;
- }
- }
- .card {
- width: 606rpx;
- height: 772rpx;
- border-radius: 40rpx;
- overflow: hidden;
- display: flex;
- justify-content: space-between;
- position: absolute;
- top: 206rpx;
- left: 26rpx;
- z-index: 2;
- }
- .ques {
- width: 450rpx;
- height: 772rpx;
- border-right: 2rpx solid #006CAA;
- image {
- width: 100%;
- height: 100%;
- display: block;
- }
- }
- .ans {
- width: 170rpx;
- height: 100%;
- display: flex;
- flex-direction: column;
- border-top-right-radius: 40rpx;
- border-bottom-right-radius: 40rpx;
- overflow: hidden;
- background-color: #fff;
- image {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .ans .ans-item:last-child {
- border-bottom: none
- }
- .buttons {
- position: relative;
- top: 200rpx;
- left: 20rpx;
- width: 680rpx;
- height: 1000rpx;
- image {
- position: absolute;
- left: 0;
- top: 0;
- }
- }
- }
- .mark-button {
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0px;
- left: 0px;
- z-index: 29;
- .movable-image {
- position: absolute;
- width: 92rpx;
- height: 92rpx;
- display: block;
- }
- }
-
- .movable-area {
- width: 714rpx;
- height: 984rpx;
- background-color: transparent;
- position: absolute;
- top: 192rpx;
- left: 50%;
- transform: translateX(-50%);
- z-index: 30;
- touch-action: none;
- left: 0px;
- -moz-transform: none;
- -webkit-transform: none;
- -o-transform: none;
- -ms-transform: none;
- transform: none;
- .movable-view {
- width: 92rpx;
- height: 92rpx;
- touch-action: none;
- .movable-image {
- width: 100%;
- height: 100%;
- transition: width 0.2s;
- z-index: 1;
- }
-
- .success-flag {
- width: 36rpx;
- height: 26rpx;
- display: block;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 2;
- }
- }
- }
- .opra {
- width: 100vw;
- height: 1216rpx;
- background: url('../../assets/boardBg.png');
- display: flex;
- justify-content: center;
- padding-top: 46rpx;
- }
- }
- .rock-button-ani {
- animation: rock 1s;
- }
- @keyframes rock {
- 0% {
- rotate: 0deg;
- }
- 25% {
-
- rotate: -10deg;
- }
- 50% {
- rotate: 10deg;
- }
- 75% {
- rotate: -10deg;
- }
- 100% {
- rotate: 0deg;
- }
- }
- /deep/ .uni-scroll-view-content {
- display: flex;
- align-items: center;
- }
- </style>
|