index.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template >
  2. <div class="luojigou-board" >
  3. <!-- navbar -->
  4. <div class="navbar" >
  5. <div class="" ></div>
  6. </div>
  7. <!-- 操作题卡区域 -->
  8. <div class="opra" >
  9. <div class="board" :style="{background: `url(${boardUrl})`}">
  10. <div class="card" >
  11. <div class="ques" >
  12. <img :src="props.card.quesUrl" alt="">
  13. </div>
  14. <div class="ans" ref="ansEl" >
  15. <div
  16. class="ans-item"
  17. :style="{height: ansItemHeight + 'px'}"
  18. v-for="item in props.card.ans"
  19. :key="item.color"
  20. >
  21. <img :src="item.url" alt="">
  22. </div>
  23. </div>
  24. </div>
  25. <div class="buttons" id="ans" ref="buttonEl" >
  26. <img
  27. :name="item.color"
  28. v-for="(item, index) in buttons"
  29. :key="item.url"
  30. :src="item.url"
  31. :cusAttr="JSON.stringify({
  32. x: VSpace * index,
  33. y: 426,
  34. color: item.color
  35. })"
  36. :style="{
  37. width: '46px',
  38. height: '46px',
  39. transform: `translate(${VSpace * index}px, 426px)`
  40. }"
  41. />
  42. <!-- transform: `translate(${VSpace * index}px, ${426}px)` -->
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </template>
  48. <script lang="ts" setup>
  49. /* eslint-disable vue/require-prop-type-constructor */
  50. import { defineProps, ref, onMounted, reactive } from 'vue'
  51. import { useDrag } from '@/hook/index'
  52. type Colors = 'red' | 'orange' | 'green' | 'purple' | 'blue' | 'yellow'
  53. interface IProps {
  54. platform: 'h5' | 'miniProgram',
  55. formwork: 'four' | 'six',
  56. card: {
  57. quesUrl: string,
  58. ans: {url: string, color: Colors, button: string}[]
  59. buttonsPos: {x: number, y: number, color: Colors}[]
  60. }
  61. }
  62. const initProps: IProps = {
  63. platform: 'h5',
  64. formwork: "six",
  65. card: {
  66. quesUrl: 'https://app-resources-luojigou.luojigou.vip/FqGOmqXFXSp8D-PsxpbrmFTi5O-m',
  67. ans: [
  68. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FjLia9g1_NqKW0dzozRZPA-BOryq', color: 'red' },
  69. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FubYgi_og7RI23MHPVhtm9kl-STW', color: 'green' },
  70. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FlASX6RW3JFb9-FrBzLWPSHYuMGo', color: 'orange' },
  71. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FtBZMPDRLLsIBQrTdTjtjPxI-rma', color: 'purple' },
  72. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FtBZMPDRLLsIBQrTdTjtjPxI-rma', color: "yellow" },
  73. {button: '', url: 'https://app-resources-luojigou.luojigou.vip/FtBZMPDRLLsIBQrTdTjtjPxI-rma', color: 'blue' }
  74. ],
  75. buttonsPos: [{ x: 0, y: 0, color: 'blue' }]
  76. }
  77. }
  78. const staticImg = {
  79. boardBg: require('@/assets/component/LuojigouBoard/board-bg.png'),
  80. boardFour: require('@/assets/component/LuojigouBoard/board-four.png'),
  81. boardSix: require('@/assets/component/LuojigouBoard/board-six.png'),
  82. blue: require('@/assets/component/LuojigouBoard/blue.png'),
  83. green: require('@/assets/component/LuojigouBoard/green.png'),
  84. orange: require('@/assets/component/LuojigouBoard/orange.png'),
  85. red: require('@/assets/component/LuojigouBoard/red.png'),
  86. yellow: require('@/assets/component/LuojigouBoard/yellow.png'),
  87. purple: require('@/assets/component/LuojigouBoard/purple.png')
  88. }
  89. const reg = new RegExp(/(-\d+)|(\d+)/g)
  90. const props = initProps
  91. // 答案区的份数
  92. const copies = props.formwork === 'four' ? 4 : 6
  93. // 按钮横向距离
  94. const VSpace = props.formwork === 'four' ? 27 : 53
  95. // 答案纵向高度
  96. const ansItemHeight = props.formwork === 'four' ? 95 : 64
  97. const buttons = ref([
  98. { x: 0, y: 0, url: staticImg.red, color: 'red' },
  99. { x: 0, y: 200, url: staticImg.blue, color: 'blue' },
  100. { x: 0, y: 200, url: staticImg.green, color: 'green' },
  101. { x: 0, y: 200, url: staticImg.orange, color: 'orange' },
  102. { x: 0, y: 200, url: staticImg.yellow, color: "yellow" },
  103. { x: 0, y: 200, url: staticImg.purple, color: 'purple' },
  104. ])
  105. const boardUrl = props.formwork === 'four' ? staticImg.boardFour : staticImg.boardSix
  106. const ansEl = ref()
  107. const buttonEl = ref()
  108. const state = reactive({
  109. cacheButtonPos: {x: 0, y: 0, copies: 0}
  110. })
  111. const getTransform = (ev: TouchEvent) => {
  112. const target = ev.target as HTMLElement
  113. if (!target) return {x: 0, y: 0}
  114. const [x, y] = target.style.transform.match(reg)!
  115. return {x: Number(x), y: Number(y)}
  116. }
  117. const onDragStart = (ev: TouchEvent) => {
  118. const { x, y }= getTransform(ev)
  119. const curCopies = Math.floor(y / ansItemHeight)
  120. state.cacheButtonPos = {x, y, copies: curCopies >= 6 ? 5 : curCopies}
  121. }
  122. const getEleByColor = (color: Colors): HTMLElement => {
  123. return Array.from(buttonEl.value.children).find( item => item.name == color ) as HTMLElement
  124. }
  125. /**
  126. * 存在五种拖拽情况
  127. * 1. 答案区为空, 按钮直接放在答案区
  128. * 2. 答案区存在按钮 , 两个按钮互换位置
  129. * 3. 直接点击答案区的按钮,答案区按钮回到初始位置
  130. * 4. 没有放在答案区, 按钮回到初始位置
  131. * 5. 两个按钮都在答案区, 直接进行按钮之间的交换
  132. * 6. 将在答案区的按钮直接拖动到另外一个答案区
  133. * 7. 按钮没在答案区, 但是目标区域已经有按钮了
  134. * @param ev
  135. * @param setPos
  136. */
  137. const onDragEnd = (ev: TouchEvent, setPos: any) => {
  138. const target = ev.target as HTMLElement
  139. const { x, y } = getTransform(ev)
  140. console.log(x, y);
  141. const cusAttr = JSON.parse(target.attributes.getNamedItem('cusattr')!.value!)
  142. // 3. 直接点击答案区的按钮,答案区按钮回到初始位置
  143. if ( state.cacheButtonPos.x == x && state.cacheButtonPos.y == y) {
  144. setPos(target, { x: cusAttr.x, y: cusAttr.y }, 0.2)
  145. return
  146. }
  147. let curCopies = Math.floor( y < 0 ? 0 / ansItemHeight : y / ansItemHeight)
  148. console.log(curCopies);
  149. // 按钮放置判断
  150. if (ansEl.value.offsetLeft - 23 < x && curCopies < 6 && curCopies >= 0) {
  151. curCopies = curCopies > 5 ? 5 : curCopies
  152. // 拖拽的按钮已经在答案区了
  153. if ( props.card.ans[curCopies].button ) {
  154. // 5. true 两个按钮都在答案区, 直接进行按钮之间的交换 false 7. 按钮没在答案区, 但是目标区域已经有按钮了
  155. const flag =props.card.ans[state.cacheButtonPos.copies].button
  156. const oldTarget = getEleByColor(props.card.ans[curCopies].button as Colors)
  157. const oldCurAttr = JSON.parse(oldTarget.attributes.getNamedItem('cusattr')!.value!)
  158. let pos = {x: 0, y : 0}
  159. if (flag) {
  160. pos = {x: state.cacheButtonPos.x, y: state.cacheButtonPos.y}
  161. } else {
  162. pos = {x: oldCurAttr.x, y: oldCurAttr.y}
  163. }
  164. setPos(
  165. oldTarget,
  166. pos,
  167. 0.2
  168. )
  169. }
  170. // 按钮直接放在答案区
  171. setPos(
  172. target,
  173. { x: ansEl.value.offsetLeft + ansEl.value.offsetWidth - 23,
  174. y: curCopies * ansItemHeight + ansItemHeight / 2 - 20
  175. },
  176. 0.2
  177. )
  178. props.card.ans[curCopies].button = cusAttr.color
  179. } else {
  180. setPos(target, { x: cusAttr.x, y: cusAttr.y }, 0.2)
  181. }
  182. }
  183. onMounted(() => {
  184. useDrag(buttonEl.value, { onDragEnd: onDragEnd, onDragStart: onDragStart})
  185. })
  186. </script>
  187. <style lang="scss" scoped>
  188. .luojigou-board {
  189. width: 100vw;
  190. height: 100vh;
  191. overflow: hidden;
  192. .navbar {
  193. width: 100vw;
  194. height: 104px;
  195. background: #F9BF4F;
  196. }
  197. .opra {
  198. width: 100vw;
  199. height: 708px;
  200. background: url('../../assets/component/LuojigouBoard/board-bg.png');
  201. display: flex;
  202. justify-content: center;
  203. padding-top: 23px;
  204. .board {
  205. width: 357px;
  206. height: 617px;
  207. position: relative;
  208. .card {
  209. width: 303px;
  210. height: 386px;
  211. border-radius: 20px;
  212. overflow: hidden;
  213. display: flex;
  214. justify-content: space-between;
  215. position: absolute;
  216. top: 103px;
  217. left: 13px;
  218. }
  219. .ques {
  220. width: 225px;
  221. height: 386px;
  222. border-right: 1px solid #006CAA;
  223. // position: absolute;
  224. // top: 103px;
  225. // left: 13px;
  226. img {
  227. width: 100%;
  228. height: 100%;
  229. display: block;
  230. }
  231. }
  232. .ans {
  233. width: 85px;
  234. height: 100%;
  235. display: flex;
  236. flex-direction: column;
  237. border-top-right-radius: 20px;
  238. border-bottom-right-radius: 20px;
  239. overflow: hidden;
  240. background-color: #fff;
  241. .ans-item {
  242. border-bottom: 1px solid #006CAA;
  243. box-sizing: border-box;
  244. img {
  245. width: 100%;
  246. height: 100%;
  247. object-fit: cover;
  248. }
  249. }
  250. }
  251. .ans .ans-item:last-child {
  252. border-bottom: none
  253. }
  254. .buttons {
  255. position: relative;
  256. top: 100px;
  257. left: 10px;
  258. width: 340px;
  259. height: 500px;
  260. img {
  261. position: absolute;
  262. left: 0;
  263. top: 0;
  264. }
  265. }
  266. }
  267. }
  268. }
  269. </style>