aim_management_interaction.vue 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. <template>
  2. <div class="maturity-game-page">
  3. <head-component :headinfo="headinfo"></head-component>
  4. <div class="page-content">
  5. <div class="pyramid-container">
  6. <div class="lbline l1"></div>
  7. <div class="lbline l2"></div>
  8. <div class="lbline l3"></div>
  9. <div class="lbline l4"></div>
  10. <div class="lbline l5"></div>
  11. <div class="lbline l6"></div>
  12. <div class="lbline l7"></div>
  13. <div class="lbline l8"></div>
  14. <div class="lbline l9"></div>
  15. <div class="lbline l10"></div>
  16. <div class="lbline l11"></div>
  17. <div class="lbline l12"></div>
  18. <div class="lbline l13"></div>
  19. <div class="lbline l14"></div>
  20. <!-- 长期目标层 -->
  21. <div class="pyramid-layer layer-1">
  22. <div
  23. class="goal-box"
  24. :class="longTermGoal ? 'filled' : 'empty'"
  25. @dragover.prevent
  26. @drop="onDrop('longTerm', $event)"
  27. :data-drop-target="'longTerm'"
  28. >
  29. {{ longTermGoal || "长期目标" }}
  30. </div>
  31. </div>
  32. <!-- 中期目标层 -->
  33. <div class="pyramid-layer layer-2">
  34. <div
  35. v-for="(goal, index) in midTermGoals"
  36. :key="'mid-' + index"
  37. class="goal-box"
  38. :class="goal ? 'filled' : 'empty'"
  39. @dragover.prevent
  40. @drop="onDrop('midTerm' + (index + 1), $event)"
  41. :data-drop-target="'midTerm' + (index + 1)"
  42. >
  43. {{ goal || `中期目标${index + 1}` }}
  44. </div>
  45. </div>
  46. <!-- 短期目标层 -->
  47. <div class="pyramid-layer layer-3">
  48. <div
  49. v-for="(goal, index) in shortTermGoals"
  50. :key="'short-' + index"
  51. class="goal-box"
  52. :class="goal ? 'filled' : 'empty'"
  53. @dragover.prevent
  54. @drop="onDrop('shortTerm' + (index + 1), $event)"
  55. :data-drop-target="'shortTerm' + (index + 1)"
  56. >
  57. {{ goal || `短期目标${index + 1}` }}
  58. </div>
  59. </div>
  60. <!-- 小目标层 -->
  61. <div class="pyramid-layer layer-4">
  62. <div
  63. v-for="(goal, index) in smallGoals"
  64. :key="'small-' + index"
  65. class="goal-box"
  66. :class="goal ? 'filled' : 'empty'"
  67. @dragover.prevent
  68. @drop="onDrop('small' + (index + 1), $event)"
  69. :data-drop-target="'small' + (index + 1)"
  70. >
  71. {{ goal || `小目标${index + 1}` }}
  72. </div>
  73. </div>
  74. </div>
  75. <div
  76. style="
  77. width: 100%;
  78. height: 8px;
  79. background: rgb(173 173 173);
  80. border-top: 3px solid #fff;
  81. "
  82. ></div>
  83. <div class="action-row">
  84. <el-button type="success">返回上一级</el-button>
  85. <p class="instruction">
  86. 请选择一项你想实现的长期目标,并将其拖拽到目标金字塔中相应位置。
  87. </p>
  88. </div>
  89. <div class="goal-options">
  90. <div
  91. v-for="(option, index) in goalOptions"
  92. :key="'goal-option-' + index"
  93. class="goal-btn"
  94. :class="{
  95. dragging: isDragging && currentDragItem === option.content,
  96. }"
  97. draggable="true"
  98. @dragstart="onDragStart($event, option.content)"
  99. @touchstart="onTouchStart($event, option.content, option)"
  100. @touchmove="onTouchMove($event)"
  101. @touchend="onTouchEnd($event, option)"
  102. >
  103. {{ option.content }}
  104. </div>
  105. </div>
  106. <!-- 拖拽时的虚拟图 -->
  107. <div
  108. v-if="isDragging"
  109. class="drag-ghost"
  110. :style="ghostStyle"
  111. ref="dragGhost"
  112. >
  113. {{ currentDragItem }}
  114. </div>
  115. <drag_component></drag_component>
  116. <el-dialog
  117. v-model="centerDialogVisible2"
  118. title=""
  119. width="30%"
  120. align-center
  121. style="padding: 40px; top: 300px; border-radius: 20px"
  122. class="custom-dialog"
  123. :close-on-click-modal="false"
  124. :close-on-press-escape="false"
  125. :show-close="false"
  126. >
  127. <span
  128. >请根据SMART原则将各级有效目标依次拖拽到目标金字塔中的相应位置。</span
  129. >
  130. <template #footer>
  131. <span class="dialog-footer" style="">
  132. <el-button
  133. style="margin-right: 0px !important"
  134. class="button1"
  135. @click="handleTypeError"
  136. >
  137. 我知道了
  138. </el-button>
  139. </span>
  140. </template> </el-dialog
  141. ><el-dialog
  142. v-model="centerDialogVisible1"
  143. title=""
  144. width="30%"
  145. align-center
  146. style="padding: 40px; top: 300px; border-radius: 20px"
  147. class="custom-dialog"
  148. :close-on-click-modal="false"
  149. :close-on-press-escape="false"
  150. :show-close="false"
  151. >
  152. <span
  153. >与上级目标无关,不符合“SMART原则”(一致性R)。请重新进行选择与拖拽!</span
  154. >
  155. <template #footer>
  156. <span class="dialog-footer" style="">
  157. <el-button
  158. style="margin-right: 0px !important"
  159. class="button1"
  160. @click="handleTypeError"
  161. >
  162. 我知道了
  163. </el-button>
  164. </span>
  165. </template> </el-dialog
  166. ><el-dialog
  167. v-model="centerDialogVisible"
  168. title=""
  169. width="30%"
  170. align-center
  171. style="padding: 40px; top: 300px; border-radius: 20px"
  172. class="custom-dialog"
  173. :close-on-click-modal="false"
  174. :close-on-press-escape="false"
  175. :show-close="false"
  176. >
  177. <span>当前活动尚未完成,退出后进度将不会保存,是否确认退出?</span>
  178. <template #footer>
  179. <span class="dialog-footer" style="">
  180. <el-button @click="handleContinue">继续体验</el-button>
  181. <el-button type="primary" @click="handleConfirmExit">
  182. 确定退出
  183. </el-button>
  184. </span>
  185. </template> </el-dialog
  186. ><el-dialog
  187. v-model="centerDialogVisible3"
  188. title=""
  189. width="30%"
  190. align-center
  191. style="padding: 40px; top: 300px; border-radius: 20px"
  192. class="custom-dialog"
  193. :close-on-click-modal="false"
  194. :close-on-press-escape="false"
  195. :show-close="false"
  196. >
  197. <span>恭喜您完成本次探索,收获满满!</span>
  198. <template #footer>
  199. <span class="dialog-footer" style="">
  200. <el-button
  201. style="margin-right: 0px !important"
  202. class="button1"
  203. @click="handleKnowExit"
  204. >
  205. 我知道了
  206. </el-button>
  207. </span>
  208. </template>
  209. </el-dialog>
  210. <el-dialog
  211. v-model="centerDialogVisible4"
  212. title=""
  213. width="30%"
  214. align-center
  215. style="padding: 40px; top: 300px; border-radius: 20px"
  216. class="custom-dialog"
  217. :close-on-click-modal="false"
  218. :close-on-press-escape="false"
  219. :show-close="false"
  220. >
  221. <span>答案错误</span>
  222. <template #footer>
  223. <span class="dialog-footer" style="">
  224. <el-button
  225. style="margin-right: 0px !important"
  226. class="button1"
  227. @click="handleKnowExit"
  228. >
  229. 我知道了
  230. </el-button>
  231. </span>
  232. </template>
  233. </el-dialog>
  234. </div>
  235. </div>
  236. </template>
  237. <script setup>
  238. import headComponent from "@/views/xjc-integratedmachine/components/head_component.vue";
  239. import drag_component from "@/views/xjc-integratedmachine/components/drag_component.vue";
  240. import { onMounted, watch, nextTick } from "vue";
  241. import { getAimManagementMaterial } from "@/api/xjc-integratedmachine/plan/aim.js";
  242. const router = useRouter();
  243. const headinfo = ref({});
  244. const draggedItem = ref(null);
  245. const centerDialogVisible = ref(false);
  246. const centerDialogVisible1 = ref(false);
  247. const centerDialogVisible2 = ref(false);
  248. const centerDialogVisible3 = ref(false);
  249. const centerDialogVisible4 = ref(false);
  250. // 移动端拖拽相关状态
  251. const isDragging = ref(false);
  252. const handleKnowExit = () => {
  253. centerDialogVisible.value = false;
  254. centerDialogVisible1.value = false;
  255. centerDialogVisible2.value = false;
  256. centerDialogVisible3.value = false;
  257. centerDialogVisible4.value = false;
  258. };
  259. const currentDragItem = ref(null);
  260. const dragStartPos = ref({ x: 0, y: 0 });
  261. const currentDropTarget = ref(null);
  262. const dragGhost = ref(null);
  263. const ghostStyle = ref({
  264. position: "fixed",
  265. left: "0px",
  266. top: "0px",
  267. transform: "translate(-50%, -50%)",
  268. zIndex: 9999,
  269. pointerEvents: "none",
  270. });
  271. function dragStart(event) {
  272. draggedItem.value = event.target;
  273. }
  274. function drop(event) {
  275. event.preventDefault(); // 防止默认处理(例如打开链接)
  276. if (event.target !== draggedItem.value) {
  277. const target = event.target; // 获取放置的目标元素
  278. const item = draggedItem.value; // 获取被拖拽的元素
  279. // 交换位置或进行其他操作
  280. item.parentNode.insertBefore(item, target); // 将被拖拽的元素插入到目标元素之前
  281. }
  282. }
  283. // 用于控制页面离开的变量
  284. let pendingRouteCallback = null;
  285. function setHeadinfo() {
  286. headinfo.value = {
  287. title: "目标管理",
  288. user: {
  289. avatar: "头像路径",
  290. nickName: "张三",
  291. },
  292. backUrl: "/xjc-integratedmachine/plan/aim/aim_management_index",
  293. backUrlUse: true,
  294. };
  295. }
  296. // 继续体验按钮处理
  297. const handleContinue = () => {
  298. centerDialogVisible.value = false;
  299. if (pendingRouteCallback) {
  300. pendingRouteCallback(false); // 阻止离开
  301. pendingRouteCallback = null;
  302. }
  303. }; // 确定退出按钮处理
  304. const handleConfirmExit = () => {
  305. centerDialogVisible.value = false;
  306. if (pendingRouteCallback) {
  307. pendingRouteCallback(true); // 允许离开
  308. pendingRouteCallback = null;
  309. }
  310. };
  311. const totalData = ref([]);
  312. onMounted(() => {
  313. setHeadinfo();
  314. getAimManagementMaterial({ pageNum: 100 })
  315. .then((res) => {
  316. console.log(res, 22222);
  317. totalData.value = res;
  318. goalOptions.value = totalData.value.children;
  319. })
  320. .catch((err) => {
  321. console.log("error", err);
  322. //loading.value = false; // 即使出错也要停止加载状态
  323. });
  324. });
  325. onBeforeRouteLeave((to, from, next) => {
  326. // 保存next回调函数
  327. pendingRouteCallback = next;
  328. if (goalOptions.value.length == 15) {
  329. // 任务完成,显示完成对话框
  330. //centerDialogVisible3.value = true;
  331. } else {
  332. // 任务未完成,显示确认对话框
  333. centerDialogVisible.value = true;
  334. }
  335. // 不直接调用next(),等待用户在对话框中选择
  336. });
  337. onBeforeUnmount(() => {
  338. console.log("组件即将卸载,可以清理定时器/事件监听等");
  339. });
  340. function jumpTo(path) {
  341. router.push({
  342. path: path,
  343. query: { name: 123 },
  344. });
  345. }
  346. // lb
  347. const longTermGoal = ref("");
  348. const midTermGoals = ref(["", ""]);
  349. const shortTermGoals = ref(["", "", "", ""]);
  350. const smallGoals = ref(["", "", "", "", "", "", "", ""]);
  351. // 目标选项数据
  352. const goalOptions = ref([]);
  353. watch(
  354. smallGoals,
  355. (newVal) => {
  356. // 判断数组所有项是否都不是空字符串
  357. if (newVal.every((item) => item.trim() !== "")) {
  358. centerDialogVisible3.value = true; // 满足条件时显示对话框
  359. }
  360. },
  361. { deep: true } // 监听数组内部元素变化
  362. );
  363. // 桌面端拖拽
  364. const onDragStart = (event, goalText) => {
  365. event.dataTransfer.setData("text/plain", goalText);
  366. };
  367. // 移动端触摸拖拽
  368. const onTouchStart = (event, goalText, obj) => {
  369. console.log(event, obj, 33);
  370. event.preventDefault();
  371. isDragging.value = true;
  372. currentDragItem.value = goalText;
  373. const touch = event.touches[0];
  374. dragStartPos.value = {
  375. x: touch.clientX,
  376. y: touch.clientY,
  377. };
  378. // 初始化虚拟图位置
  379. ghostStyle.value.left = touch.clientX + "px";
  380. ghostStyle.value.top = touch.clientY + "px";
  381. // 添加视觉反馈
  382. event.currentTarget.style.opacity = "0.5";
  383. event.currentTarget.style.transform = "scale(0.95)";
  384. };
  385. const onTouchMove = (event) => {
  386. if (!isDragging.value) return;
  387. event.preventDefault();
  388. const touch = event.touches[0];
  389. // 更新虚拟图位置
  390. ghostStyle.value.left = touch.clientX + "px";
  391. ghostStyle.value.top = touch.clientY + "px";
  392. // 获取触摸点下的元素
  393. const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
  394. // 清除之前的高亮
  395. document.querySelectorAll(".goal-box.drop-highlight").forEach((el) => {
  396. el.classList.remove("drop-highlight");
  397. });
  398. // 检查是否是有效的放置目标
  399. if (
  400. elementBelow &&
  401. elementBelow.classList.contains("goal-box") &&
  402. elementBelow.dataset.dropTarget
  403. ) {
  404. elementBelow.classList.add("drop-highlight");
  405. currentDropTarget.value = elementBelow.dataset.dropTarget;
  406. } else {
  407. currentDropTarget.value = null;
  408. }
  409. };
  410. const onTouchEnd = (event, obj) => {
  411. console.log(obj.isRight, event, 22);
  412. if (!isDragging.value) return;
  413. event.preventDefault();
  414. // 恢复原始样式
  415. event.currentTarget.style.opacity = "";
  416. event.currentTarget.style.transform = "";
  417. // 清除高亮
  418. document.querySelectorAll(".goal-box.drop-highlight").forEach((el) => {
  419. el.classList.remove("drop-highlight");
  420. });
  421. // 如果有有效的放置目标,执行放置逻辑
  422. if (currentDropTarget.value && currentDragItem.value) {
  423. handleDrop(currentDropTarget.value, currentDragItem.value, obj);
  424. }
  425. // 重置状态
  426. isDragging.value = false;
  427. currentDragItem.value = null;
  428. currentDropTarget.value = null;
  429. };
  430. // 兼容数组 / 单对象 / ref / reactive 的查找
  431. function findNodeById(tree, id) {
  432. // 取出真实根值(兼容 Vue 的 ref/reactive)
  433. const root =
  434. tree && typeof tree === "object" && "value" in tree ? tree.value : tree;
  435. if (!root) return null;
  436. // 统一成待遍历栈
  437. const stack = Array.isArray(root) ? root.slice() : [root];
  438. const targetId = String(id);
  439. while (stack.length) {
  440. const node = stack.pop();
  441. if (node && String(node.id) === targetId) return node;
  442. const children = node && Array.isArray(node.children) ? node.children : [];
  443. // 压入子节点
  444. for (let i = 0; i < children.length; i++) {
  445. stack.push(children[i]);
  446. }
  447. }
  448. return null;
  449. }
  450. function checkArray(arr) {
  451. console.log(arr, 133);
  452. // 判断是否有空项(只包含空字符串或全是空格的也算空)
  453. return !arr.some((item) => item.trim() === "");
  454. }
  455. function steam(target, goalText) {
  456. switch (target) {
  457. case "longTerm":
  458. longTermGoal.value = goalText;
  459. document.querySelector(".l1").style.border = "2px solid #00754d";
  460. document.querySelector(".l2").style.border = "2px solid #00754d";
  461. break;
  462. case "midTerm1":
  463. midTermGoals.value[0] = goalText;
  464. document.querySelector(".l3").style.border = "2px solid #00754d";
  465. document.querySelector(".l4").style.border = "2px solid #00754d";
  466. break;
  467. case "midTerm2":
  468. midTermGoals.value[1] = goalText;
  469. document.querySelector(".l5").style.border = "2px solid #00754d";
  470. document.querySelector(".l6").style.border = "2px solid #00754d";
  471. break;
  472. case "shortTerm1":
  473. shortTermGoals.value[0] = goalText;
  474. document.querySelector(".l7").style.border = "2px solid #00754d";
  475. document.querySelector(".l8").style.border = "2px solid #00754d";
  476. break;
  477. case "shortTerm2":
  478. shortTermGoals.value[1] = goalText;
  479. document.querySelector(".l9").style.border = "2px solid #00754d";
  480. document.querySelector(".l10").style.border = "2px solid #00754d";
  481. break;
  482. case "shortTerm3":
  483. shortTermGoals.value[2] = goalText;
  484. document.querySelector(".l11").style.border = "2px solid #00754d";
  485. document.querySelector(".l12").style.border = "2px solid #00754d";
  486. break;
  487. case "shortTerm4":
  488. shortTermGoals.value[3] = goalText;
  489. document.querySelector(".l13").style.border = "2px solid #00754d";
  490. document.querySelector(".l14").style.border = "2px solid #00754d";
  491. break;
  492. default:
  493. if (target.startsWith("small")) {
  494. const index = parseInt(target.replace("small", "")) - 1;
  495. smallGoals.value[index] = goalText;
  496. }
  497. }
  498. // 输出当前状态
  499. console.log("长期目标:", longTermGoal.value);
  500. console.log("中期目标:", midTermGoals.value);
  501. console.log("短期目标:", shortTermGoals.value);
  502. console.log("小目标:", smallGoals.value);
  503. }
  504. let newArrtotalDeep2 = [];
  505. let newArrtotalDeep3 = [];
  506. let newArrtotalDeep4 = [];
  507. const handleDrop = (target, goalText, obj) => {
  508. console.log(target, goalText, obj, "mobile drop");
  509. if (!obj.isRight) {
  510. centerDialogVisible4.value = true;
  511. return;
  512. }
  513. if (obj.deep == 1) {
  514. if (target != "longTerm") {
  515. centerDialogVisible2.value = true;
  516. return;
  517. }
  518. let newArr = findNodeById(totalData.value, obj.id);
  519. console.log(newArr, 88);
  520. goalOptions.value = newArr.children;
  521. steam(target, goalText);
  522. }
  523. if (obj.deep == 2) {
  524. //判断是否拖进中期目标
  525. if (!(target == "midTerm1" || target == "midTerm2")) {
  526. centerDialogVisible2.value = true;
  527. return;
  528. }
  529. console.log(!checkArray(midTermGoals.value), midTermGoals, 777);
  530. steam(target, goalText);
  531. //判断中期目标是否都已拖拽完毕
  532. if (!checkArray(midTermGoals.value)) {
  533. let newArr = findNodeById(totalData.value, obj.id);
  534. newArrtotalDeep2.push(...newArr.children);
  535. } else {
  536. let newArr = findNodeById(totalData.value, obj.id);
  537. newArrtotalDeep2.push(...newArr.children);
  538. console.log(newArrtotalDeep2, 88);
  539. goalOptions.value = newArrtotalDeep2;
  540. }
  541. }
  542. if (obj.deep == 3) {
  543. //判断是否拖进中期目标
  544. if (
  545. !(
  546. target == "shortTerm1" ||
  547. target == "shortTerm2" ||
  548. target == "shortTerm3" ||
  549. target == "shortTerm4"
  550. )
  551. ) {
  552. centerDialogVisible2.value = true;
  553. return;
  554. }
  555. console.log(!checkArray(shortTermGoals.value), shortTermGoals, 777);
  556. steam(target, goalText);
  557. //判断短期目标是否都已拖拽完毕
  558. if (!checkArray(shortTermGoals.value)) {
  559. let newArr = findNodeById(totalData.value, obj.id);
  560. newArrtotalDeep3.push(...newArr.children);
  561. } else {
  562. let newArr = findNodeById(totalData.value, obj.id);
  563. newArrtotalDeep3.push(...newArr.children);
  564. console.log(newArrtotalDeep3, 88);
  565. goalOptions.value = newArrtotalDeep3;
  566. }
  567. }
  568. if (obj.deep == 4) {
  569. //判断是否拖进中期目标
  570. if (
  571. !(
  572. target == "small1" ||
  573. target == "small2" ||
  574. target == "small3" ||
  575. target == "small4" ||
  576. target == "small5" ||
  577. target == "small6" ||
  578. target == "small7" ||
  579. target == "small8"
  580. )
  581. ) {
  582. centerDialogVisible2.value = true;
  583. return;
  584. }
  585. console.log(!checkArray(smallGoals.value), smallGoals, 777);
  586. steam(target, goalText);
  587. //判断短期目标是否都已拖拽完毕
  588. if (!checkArray(smallGoals.value)) {
  589. } else {
  590. let newArr = findNodeById(totalData.value, obj.id);
  591. console.log(newArr, 88);
  592. goalOptions.value = newArr.children;
  593. }
  594. }
  595. console.log(smallGoals, 7776);
  596. };
  597. const onDrop = (target, event) => {
  598. console.log(target, event, 999);
  599. const goalText = event.dataTransfer.getData("text/plain");
  600. handleDrop(target, goalText);
  601. }; // 我知道了按钮处理
  602. const handleTypeError = () => {
  603. centerDialogVisible2.value = false;
  604. };
  605. // ed
  606. </script>
  607. <style scoped lang="scss">
  608. /* lb */
  609. ::v-deep .dialog-footer button:first-child {
  610. margin-right: 102px !important;
  611. }
  612. ::v-deep .el-dialog__footer {
  613. text-align: center;
  614. }
  615. /* 穿透修改对话框内容 */
  616. ::v-deep .custom-dialog .el-dialog__body span {
  617. margin-bottom: 40px;
  618. font-size: 28px;
  619. display: block;
  620. text-align: left;
  621. }
  622. /* 修改按钮样式 */
  623. ::v-deep .dialog-footer .el-button {
  624. font-size: 18px;
  625. padding: 12px 24px;
  626. }
  627. ::v-deep .dialog-footer .el-button--primary {
  628. background-color: #ff0000 !important;
  629. border-color: #ff0000 !important;
  630. height: 48px;
  631. border-radius: 12px;
  632. }
  633. ::v-deep .dialog-footer .el-button:not(.el-button--primary) {
  634. background-color: #2ac17a !important;
  635. color: #fff !important;
  636. height: 48px;
  637. border-radius: 12px;
  638. }
  639. .action-row {
  640. display: flex;
  641. align-items: center; // 垂直居中
  642. justify-content: flex-start; // 左对齐,可改为 space-between
  643. gap: 20px; // 元素之间的间距
  644. margin: 30px 0;
  645. .el-button {
  646. margin-left: 32px;
  647. padding: 25px 25px;
  648. border-radius: 12px;
  649. font-size: 20px; // 按钮字体变大
  650. }
  651. .instruction {
  652. margin: 0; // 去掉段落默认 margin
  653. font-weight: bold;
  654. margin-left: 410px;
  655. font-size: 22px;
  656. color: #00a524;
  657. }
  658. }
  659. .layer-1 .goal-box.empty {
  660. border: 4px dashed #e8d344;
  661. }
  662. .layer-1 .goal-box.filled {
  663. border: 4px solid #e8d344;
  664. }
  665. .layer-2 .goal-box.empty {
  666. border: 4px dashed #00754d;
  667. }
  668. .layer-2 .goal-box.filled {
  669. border: 4px solid #e8d344;
  670. }
  671. .layer-3 .goal-box.empty {
  672. border: 4px dashed #00754d;
  673. }
  674. .layer-3 .goal-box.filled {
  675. border: 4px solid #e8d344;
  676. }
  677. .layer-4 .goal-box.empty {
  678. border: 4px dashed #00754d;
  679. }
  680. .layer-4 .goal-box.filled {
  681. border: 4px solid #3ff032;
  682. }
  683. // 移动端拖拽相关样式
  684. .goal-box.drop-highlight {
  685. background: rgba(255, 255, 0, 0.3) !important;
  686. border-color: #ffeb3b !important;
  687. transform: scale(1.02);
  688. }
  689. .goal-btn.dragging {
  690. opacity: 0.7;
  691. transform: scale(1.05);
  692. }
  693. // 增强移动端触摸体验
  694. .goal-btn {
  695. touch-action: none; // 防止页面滚动
  696. user-select: none; // 防止文本选择
  697. }
  698. .goal-box {
  699. touch-action: none;
  700. user-select: none;
  701. }
  702. /* 拖拽虚拟图样式 */
  703. .drag-ghost {
  704. padding: 15px 20px;
  705. border: 3px solid #4caf50;
  706. border-radius: 25px;
  707. background: rgba(76, 175, 80, 0.9);
  708. color: white;
  709. font-size: 18px;
  710. font-weight: bold;
  711. box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
  712. backdrop-filter: blur(10px);
  713. animation: ghostPulse 1s ease-in-out infinite alternate;
  714. white-space: nowrap;
  715. max-width: 250px;
  716. text-align: center;
  717. }
  718. @keyframes ghostPulse {
  719. 0% {
  720. transform: translate(-50%, -50%) scale(0.95);
  721. box-shadow: 0 8px 25px rgba(76, 175, 80, 0.4);
  722. }
  723. 100% {
  724. transform: translate(-50%, -50%) scale(1.05);
  725. box-shadow: 0 12px 35px rgba(76, 175, 80, 0.6);
  726. }
  727. }
  728. .lbline {
  729. width: 130px;
  730. border-top: 4px dashed #00754d;
  731. /* transition: revert; */
  732. position: absolute;
  733. }
  734. .l1 {
  735. transform: rotate(-16deg);
  736. top: 103px;
  737. left: 720px;
  738. }
  739. .l2 {
  740. transform: rotate(16deg);
  741. top: 103px;
  742. left: 1050px;
  743. }
  744. .l3 {
  745. transform: rotate(-16deg);
  746. top: 275px;
  747. left: 239px;
  748. }
  749. .l4 {
  750. transform: rotate(16deg);
  751. top: 273px;
  752. left: 640px;
  753. }
  754. .l5 {
  755. transform: rotate(-16deg);
  756. top: 273px;
  757. left: 1140px;
  758. }
  759. .l6 {
  760. transform: rotate(16deg);
  761. top: 273px;
  762. left: 1490px;
  763. }
  764. .l7 {
  765. transform: rotate(-16deg);
  766. top: 445px;
  767. left: 101px;
  768. }
  769. .l8 {
  770. transform: rotate(16deg);
  771. top: 444px;
  772. left: 268px;
  773. }
  774. .l9 {
  775. transform: rotate(-16deg);
  776. top: 444px;
  777. left: 568px;
  778. }
  779. .l10 {
  780. transform: rotate(16deg);
  781. top: 444px;
  782. left: 737px;
  783. }
  784. .l11 {
  785. transform: rotate(-16deg);
  786. top: 444px;
  787. left: 1041px;
  788. }
  789. .l12 {
  790. transform: rotate(16deg);
  791. top: 444px;
  792. left: 1208px;
  793. }
  794. .l13 {
  795. transform: rotate(-16deg);
  796. top: 444px;
  797. left: 1496px;
  798. }
  799. .l14 {
  800. transform: rotate(16deg);
  801. top: 444px;
  802. left: 1673px;
  803. }
  804. * {
  805. margin: 0;
  806. padding: 0;
  807. box-sizing: border-box;
  808. font-family: "Arial", "Microsoft YaHei", sans-serif;
  809. }
  810. * {
  811. margin: 0;
  812. padding: 0;
  813. box-sizing: border-box;
  814. font-family: "Arial", "Microsoft YaHei", sans-serif;
  815. }
  816. body {
  817. background: linear-gradient(135deg, #e0f7fa 0%, #bbdefb 100%);
  818. min-height: 100vh;
  819. display: flex;
  820. justify-content: center;
  821. align-items: center;
  822. padding: 20px;
  823. margin: 0; // 确保没有默认边距
  824. }
  825. // 全局背景覆盖
  826. html,
  827. body {
  828. margin: 0;
  829. padding: 0;
  830. background: linear-gradient(to right, #bef3fc, #3dd3f5);
  831. min-height: 100vh;
  832. }
  833. .container {
  834. width: 100%;
  835. max-width: 900px;
  836. background: white;
  837. border-radius: 20px;
  838. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  839. padding: 30px;
  840. position: relative;
  841. overflow: hidden;
  842. }
  843. .title {
  844. text-align: center;
  845. color: #2c3e50;
  846. margin-bottom: 30px;
  847. font-size: 24px;
  848. font-weight: bold;
  849. }
  850. /* 金字塔容器 */
  851. .pyramid-container {
  852. display: flex;
  853. flex-direction: column;
  854. align-items: center;
  855. margin-bottom: 0px;
  856. position: relative;
  857. }
  858. /* 金字塔层 */
  859. .pyramid-layer {
  860. display: flex;
  861. justify-content: center;
  862. margin-bottom: 32px;
  863. position: relative;
  864. }
  865. /* 长期目标层 - 页面宽度的三分之一 */
  866. .layer-1 .goal-box.empty {
  867. height: 88px;
  868. }
  869. .layer-1 .goal-box.filled {
  870. height: 88px;
  871. }
  872. .layer-1 {
  873. width: 33.33%;
  874. }
  875. /* 中期目标层 - 页面宽度的二分之一 */
  876. .layer-2 {
  877. width: 85%;
  878. justify-content: space-between;
  879. }
  880. /* 短期目标层 - 页面宽度的四分之三 */
  881. .layer-3 {
  882. width: 98%;
  883. justify-content: space-between;
  884. }
  885. /* 小目标层 - 页面宽度的90% */
  886. .layer-4 {
  887. width: 98%;
  888. justify-content: space-between;
  889. }
  890. /* 目标框样式 */
  891. .goal-box {
  892. border-radius: 33px;
  893. background: rgba(255, 255, 255, 0.9);
  894. text-align: center;
  895. cursor: pointer;
  896. transition: all 0.3s;
  897. position: relative;
  898. display: flex;
  899. align-items: center;
  900. justify-content: center;
  901. font-size: 22px;
  902. color: #555;
  903. height: 140px;
  904. z-index: 2;
  905. }
  906. .goal-box:hover {
  907. background: rgba(236, 252, 237, 0.9);
  908. transform: translateY(-2px);
  909. }
  910. .goal-box.empty {
  911. margin: 0px 9px;
  912. color: #95a5a6;
  913. border-style: dashed;
  914. width: 100%;
  915. }
  916. .layer-2 .goal-box.filled {
  917. margin: 0px 9px;
  918. width: 100%;
  919. color: #fff;
  920. border-style: solid;
  921. background: #00d89c;
  922. font-weight: bold;
  923. }
  924. .layer-3 .goal-box.filled {
  925. margin: 0px 9px;
  926. width: 100%;
  927. color: #fff;
  928. border-style: solid;
  929. background: #00d89c;
  930. font-weight: bold;
  931. }
  932. .layer-4 .goal-box.filled {
  933. margin: 0px 9px;
  934. width: 100%;
  935. color: #fff;
  936. border-style: solid;
  937. background: #00d89c;
  938. font-weight: bold;
  939. }
  940. .goal-box.filled {
  941. margin: 0px 9px;
  942. width: 100%;
  943. color: #fff;
  944. border-style: solid;
  945. background: #303030;
  946. font-weight: bold;
  947. }
  948. /* 连接线容器 */
  949. .connections {
  950. position: absolute;
  951. top: 0;
  952. left: 0;
  953. width: 100%;
  954. height: 100%;
  955. z-index: 1;
  956. pointer-events: none;
  957. }
  958. /* 连接线 */
  959. .connection {
  960. position: absolute;
  961. background: transparent;
  962. border-left: 4px dashed #2c8f30;
  963. }
  964. /* 长期目标到中期目标的连线 */
  965. .long-to-mid-1,
  966. .long-to-mid-2 {
  967. height: 60px;
  968. top: 60px;
  969. left: 50%;
  970. }
  971. .long-to-mid-1 {
  972. transform: translateX(-100%) rotate(-30deg);
  973. transform-origin: top center;
  974. }
  975. .long-to-mid-2 {
  976. transform: translateX(0) rotate(30deg);
  977. transform-origin: top center;
  978. }
  979. /* 中期目标到短期目标的连线 */
  980. .mid-to-short-1,
  981. .mid-to-short-2,
  982. .mid-to-short-3,
  983. .mid-to-short-4 {
  984. height: 60px;
  985. top: 180px;
  986. }
  987. .mid-to-short-1 {
  988. left: 25%;
  989. transform: translateX(-150%) rotate(-20deg);
  990. transform-origin: top center;
  991. }
  992. .mid-to-short-2 {
  993. left: 25%;
  994. transform: translateX(-50%) rotate(20deg);
  995. transform-origin: top center;
  996. }
  997. .mid-to-short-3 {
  998. left: 75%;
  999. transform: translateX(-150%) rotate(-20deg);
  1000. transform-origin: top center;
  1001. }
  1002. .mid-to-short-4 {
  1003. left: 75%;
  1004. transform: translateX(-50%) rotate(20deg);
  1005. transform-origin: top center;
  1006. }
  1007. /* 短期目标到小目标的连线 */
  1008. .short-to-small-1,
  1009. .short-to-small-2,
  1010. .short-to-small-3,
  1011. .short-to-small-4,
  1012. .short-to-small-5,
  1013. .short-to-small-6,
  1014. .short-to-small-7,
  1015. .short-to-small-8 {
  1016. height: 60px;
  1017. top: 300px;
  1018. }
  1019. .short-to-small-1 {
  1020. left: 12.5%;
  1021. transform: translateX(-175%) rotate(-15deg);
  1022. transform-origin: top center;
  1023. }
  1024. .short-to-small-2 {
  1025. left: 12.5%;
  1026. transform: translateX(-75%) rotate(15deg);
  1027. transform-origin: top center;
  1028. }
  1029. .short-to-small-3 {
  1030. left: 37.5%;
  1031. transform: translateX(-175%) rotate(-15deg);
  1032. transform-origin: top center;
  1033. }
  1034. .short-to-small-4 {
  1035. left: 37.5%;
  1036. transform: translateX(-75%) rotate(15deg);
  1037. transform-origin: top center;
  1038. }
  1039. .short-to-small-5 {
  1040. left: 62.5%;
  1041. transform: translateX(-175%) rotate(-15deg);
  1042. transform-origin: top center;
  1043. }
  1044. .short-to-small-6 {
  1045. left: 62.5%;
  1046. transform: translateX(-75%) rotate(15deg);
  1047. transform-origin: top center;
  1048. }
  1049. .short-to-small-7 {
  1050. left: 87.5%;
  1051. transform: translateX(-175%) rotate(-15deg);
  1052. transform-origin: top center;
  1053. }
  1054. .short-to-small-8 {
  1055. left: 87.5%;
  1056. transform: translateX(-75%) rotate(15deg);
  1057. transform-origin: top center;
  1058. }
  1059. /* 底部按钮区域 */
  1060. .goal-options {
  1061. display: flex;
  1062. flex-wrap: wrap; // 允许换行
  1063. justify-content: flex-start;
  1064. height: 191px;
  1065. overflow-y: auto;
  1066. gap: 20px;
  1067. margin-top: 40px;
  1068. padding: 20px;
  1069. // 可选:自适应居中
  1070. justify-content: center;
  1071. }
  1072. .goal-btn {
  1073. flex: 0 0 calc(33.33% - 13.33px); // 每行3个,减去gap平均值
  1074. min-width: 200px; // 保持最小宽度
  1075. padding: 10px 25px;
  1076. border: 4px solid #95a5a6;
  1077. border-radius: 40px;
  1078. background: #fff;
  1079. color: #95a5a6;
  1080. font-size: 22px;
  1081. height: 80px;
  1082. text-align: center;
  1083. cursor: grab;
  1084. box-sizing: border-box;
  1085. }
  1086. .goal-btn:hover {
  1087. border: 4px solid #a2d532;
  1088. color: #2c3e50;
  1089. transform: translateY(-3px);
  1090. box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
  1091. }
  1092. .goal-btn:active {
  1093. cursor: grabbing;
  1094. }
  1095. .instruction {
  1096. text-align: center;
  1097. margin: 30px 0;
  1098. color: #00a524;
  1099. font-weight: bold;
  1100. font-size: 22px;
  1101. }
  1102. /* 装饰元素 */
  1103. .decoration {
  1104. position: absolute;
  1105. }
  1106. .cartoon {
  1107. top: 20px;
  1108. left: 20px;
  1109. width: 80px;
  1110. height: 80px;
  1111. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="40" r="30" fill="%23FFD54F"/><circle cx="40" cy="35" r="5" fill="white"/><circle cx="60" cy="35" r="5" fill="white"/><path d="M35 55 Q50 65 65 55" stroke="%23E57373" stroke-width="3" fill="none"/></svg>')
  1112. no-repeat center;
  1113. background-size: contain;
  1114. }
  1115. .robot {
  1116. bottom: 20px;
  1117. right: 20px;
  1118. width: 80px;
  1119. height: 126px;
  1120. background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="30" r="20" fill="%234caf50"/><rect x="30" y="50" width="40" height="40" rx="5" fill="%234caf50"/><circle cx="40" cy="60" r="5" fill="white"/><circle cx="60" cy="60" r="5" fill="white"/></svg>')
  1121. no-repeat center;
  1122. background-size: contain;
  1123. }
  1124. @media (max-width: 768px) {
  1125. .goal-options {
  1126. padding: 15px;
  1127. gap: 15px;
  1128. // 移动端滚动条隐藏
  1129. scrollbar-width: none; // Firefox
  1130. -ms-overflow-style: none; // IE
  1131. &::-webkit-scrollbar {
  1132. display: none; // Chrome/Safari
  1133. }
  1134. }
  1135. .goal-btn {
  1136. width: 30%; // 移动端也保持原来的宽度
  1137. min-width: 200px; // 保持原来的最小宽度
  1138. padding: 40px 20px;
  1139. font-size: 18px;
  1140. }
  1141. .layer-2,
  1142. .layer-3,
  1143. .layer-4 {
  1144. flex-wrap: wrap;
  1145. height: auto;
  1146. justify-content: center;
  1147. }
  1148. .goal-box {
  1149. flex: 1;
  1150. min-width: 120px;
  1151. }
  1152. .connections {
  1153. display: none;
  1154. }
  1155. }
  1156. /*ed*/
  1157. .maturity-game-page {
  1158. // background: url("@/assets/images/wakeup/maturity/maturity-game-bg.png")
  1159. // no-repeat;
  1160. background: linear-gradient(to right, #bef3fc, #3dd3f5);
  1161. background-attachment: fixed;
  1162. z-index: 10;
  1163. width: 100%;
  1164. min-height: 100vh;
  1165. position: relative;
  1166. .page-content * {
  1167. pointer-events: auto; /* 子元素还是能点 */
  1168. }
  1169. .page-content {
  1170. width: 100%;
  1171. position: relative;
  1172. pointer-events: none;
  1173. padding-top: 123px;
  1174. min-height: calc(100vh - 123px);
  1175. padding-bottom: 0px; // 添加底部内边距防止内容贴边
  1176. .top {
  1177. width: 100%;
  1178. height: 212px;
  1179. display: flex;
  1180. justify-content: center;
  1181. margin-top: 104px;
  1182. img {
  1183. width: 864px;
  1184. height: 212px;
  1185. }
  1186. }
  1187. .bottom {
  1188. width: 100%;
  1189. height: 248px;
  1190. display: flex;
  1191. justify-content: space-around;
  1192. margin-top: 186px;
  1193. img {
  1194. width: 398px;
  1195. height: 248px;
  1196. }
  1197. }
  1198. }
  1199. }
  1200. </style>