Sfoglia il codice sorgente

feat: luojigou-board

lvkun996 2 anni fa
parent
commit
322675ae00

+ 5 - 0
package-lock.json

@@ -2312,6 +2312,11 @@
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
       "dev": true
     },
+    "js-base64": {
+      "version": "3.7.5",
+      "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.5.tgz",
+      "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA=="
+    },
     "js-cookie": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.1.tgz",

+ 4 - 1
package.json

@@ -34,7 +34,9 @@
     "build:quickapp-webview": "uni build -p quickapp-webview",
     "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
     "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
-    "type-check": "vue-tsc --noEmit"
+    "type-check": "vue-tsc --noEmit",
+    "upload:pro": "python upload.py /usr/share/nginx/html/luojigou/ luojigou-board pro",
+    "upload:dev": "python upload.py /usr/share/nginx/html/ luojigou-board dev"
   },
   "dependencies": {
     "@dcloudio/uni-app": "3.0.0-alpha-3061620221230002",
@@ -50,6 +52,7 @@
     "@dcloudio/uni-mp-weixin": "3.0.0-alpha-3061620221230002",
     "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3061620221230002",
     "@rive-app/canvas": "^1.0.102",
+    "js-base64": "^3.7.5",
     "less-loader": "^6.2.0",
     "pinia": "^2.0.33",
     "vconsole": "^3.15.0",

+ 2 - 0
src/App.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
 import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
+import { useAppBridge } from '@/hooks/app'
 onLaunch(() => {
   console.log("App Launch");
 });
 onShow(() => {
   console.log("App Show");
+  useAppBridge().getToken()
 });
 onHide(() => {
   console.log("App Hide");

+ 3 - 2
src/api/card.ts

@@ -23,9 +23,10 @@ export const getCardDetailById = (id: string, mode: CardModeEnum) => {
  * @param params 
  * @returns 
  */
-export const submitlearnPortAns = (params: any) => {
+export const submitlearnPortAns = (params: any, url: string) => {
   return request<API.Card>({
-    url: `/app/game-course/data/task2`,
+    url: '',
+    baseURL: url,
     method: 'POST',
     params
   })

+ 1 - 1
src/components/luojigou-board/luojigou-board.vue

@@ -290,7 +290,7 @@ const checkAns = (index: number, itemData: Buttons) => {
   console.log(targetData, index, itemData);
   
   // 移动到了正确位置
-  if (targetData[index].color === itemData.color) {
+  if (targetData[index].ans === itemData.color) {
     itemData.ans = targetData[index].color
     calcQuantityStore.correctQuantity++
   } else { // 移动到了錯誤位置

+ 1 - 0
src/components/navbar/navbar.vue

@@ -43,6 +43,7 @@ const contentStyles = {
   left: navbarInfo.left
 }
 
+
 </script>
 
 

+ 0 - 1
src/components/rive-ani/rive-ani.vue

@@ -159,7 +159,6 @@ const goBezier = () => {
   createAnimationStep(animation, state.cointCount!)
 
   useSchedulerOnce(() => {
-    console.log('触发emit');
     _resolve()
     state.comVisable = false
     state.visible = false

+ 2 - 1
src/enum/constant.ts

@@ -24,5 +24,6 @@ export enum AnsTypeEnum {
 // 游戏模式
 export enum OpraModeEnum {
   'COMPE' = 1,
-  "PRACTICE" = 0
+  "PRACTICE" = 0,
+  'LEARN' = 2
 }

+ 19 - 0
src/global.d.ts

@@ -0,0 +1,19 @@
+declare global {
+  interface Window {
+    navAppPage: {
+      postMessage: (params: string) => void,
+    },
+    popPage: {
+      postMessage: (params?: string) => void,
+    },
+    reloadAppPage: {
+      postMessage: (params?: string) => void,
+    },
+    getToken: {
+      postMessage: (params?: string) => void,
+    },
+    returnToken: (token: string) => void
+  }
+}
+
+export {}

+ 33 - 0
src/hooks/app.ts

@@ -0,0 +1,33 @@
+/**
+ * 与app进行交互的hook
+ */
+
+import { useUserStore } from "@/store"
+
+export const useAppBridge = () => {
+
+  const _reload = (path: string) => window.reloadAppPage.postMessage(JSON.stringify({ route: path }))
+
+  const _push = (path: string) => window.navAppPage.postMessage(JSON.stringify({ route: path }))
+
+  const _back = (path: string) => window.popPage.postMessage(path)
+  
+  const _getToken = () => {
+    window.returnToken = token => useUserStore().token = token
+    try {
+      window.getToken.postMessage(JSON.stringify({}))
+    } catch (error) {
+      
+    }
+    
+  }
+
+  return {
+    reload: _reload,
+    push: _push,
+    back: _back,
+    getToken: _getToken
+  }
+
+}
+

+ 34 - 3
src/hooks/index.ts

@@ -1,6 +1,9 @@
 import type { StaticImg } from '@/utils/static'
-import { inject, onUnmounted, onMounted, type ComponentInternalInstance , ref} from 'vue'
-
+import { onLoad } from '@dcloudio/uni-app'
+import { toRefs } from 'vue'
+import { reactive } from 'vue'
+import { inject, onUnmounted, onMounted, type ComponentInternalInstance, ref} from 'vue'
+import { encode, decode} from 'js-base64'
 
 /**
  * 它从 `inject` 函数返回 `StaticImg` 服务
@@ -10,7 +13,7 @@ import { inject, onUnmounted, onMounted, type ComponentInternalInstance , ref} f
  * const staticImg = useStaticImg()
  * const logo = staticImg.logo
  */
-export const useStaticImg = (): StaticImg  => inject<StaticImg>("staticImg")!
+export const useStaticImg = (): StaticImg => inject<StaticImg>("staticImg")!
 
 /**
  *
@@ -194,3 +197,31 @@ export const useAdaptationIpadAndPhone = () => {
 
 }
 
+export const useQueryParmas = async () => {
+  console.log(1);
+  let state = reactive({})
+  
+  state = await new Promise( (resolve) => {
+    onLoad(query => {
+      console.log(3);
+      resolve(query!)
+    })
+  })
+  
+  return {...toRefs(state)}
+}
+
+
+/**
+ * 这是一个导出对象的TypeScript函数,有两个方法decode和encode,分别将Base64编码的字符串解码为JSON对象,将JSON对象编码为Base64编码的字符串。
+ * @returns 正在返回名为“useBase64”的自定义挂钩。这个钩子有两个函数:`decode` 和 `encode`。 `decode` 函数将字符串作为输入并返回解析后的 JSON 对象。
+ * `encode` 函数将 `API.P` 类型的对象作为输入并返回 base64 编码的字符串。
+ */
+export const useBase64 = () => {
+
+  return {
+    decode: <T>(URI: string): T => JSON.parse(decode(URI)),
+    encode: (records: API.P): string => encode(JSON.stringify(records))
+  }
+
+}

+ 2 - 0
src/main.ts

@@ -5,6 +5,8 @@ import staticImg from '@/utils/static'
 import VConsole from "vconsole"
 
 
+new VConsole()
+
 
 export function createApp() {
   const app = createSSRApp(App);

+ 66 - 22
src/pages/GameView/index.vue

@@ -38,12 +38,13 @@ import { onLoad } from '@dcloudio/uni-app';
 import { reactive, onMounted } from 'vue'
 import { getCardDetailById, submitlearnPortAns } from '@/api/card'
 import { getCollectionDetailById } from '@/api/collection'
-import { useAudioMange, useScheduler } from '@/hooks/index'
+import { useAudioMange, useScheduler, useBase64, useSchedulerOnce } from '@/hooks/index'
 import { usePracticeStore, useOpraRecordStore, useGameCountdownStore, useCalcQuantityStore, useWisdomCoinStore } from '@/store'
 import riveAni from '@/components/rive-ani/rive-ani.vue';
 import { ref } from 'vue';
 
 
+
 /**
  *  README.md 里有详细的参数说明
  */
@@ -58,7 +59,8 @@ interface State extends QueryParams {
   card: Partial<API.Card> | null,
   cardIds: string[],
   playLoading: boolean,
-  userCardAnswerList: any[]
+  userCardAnswerList: any[],
+  queryParams: API.P | null
 }
 
 const atx = useAudioMange()
@@ -73,11 +75,29 @@ const calcQuantityStore = useCalcQuantityStore()
 
 const wisdomCoinStore = useWisdomCoinStore()
 
+const base64 = useBase64()
+
 onLoad(query => {
-  const options = query as QueryParams
-  state.mode = options.mode
+
+  let options
+  if (import.meta.env.MODE !== "development") {
+    state.queryParams = base64.decode<API.P>(query!.p)
+    options = state.queryParams.data
+  } else {
+    state.queryParams = base64.decode<API.P>(query!.p)
+    options = state.queryParams.data
+  }
+  console.log(state.queryParams);
+  
+  state.mode = options.mode || CardModeEnum.PREVIEW
   state.collectionId = options.collectionId
   if (options.cardId) state.cardId = options.cardId
+
+  wisdomCoinStore.total = options.userWisdomCoin
+  wisdomCoinStore.remainderWisdomCoin = options.remainderWisdomCoin
+
+
+
 })
 
 const riveAniRef = ref()
@@ -103,7 +123,8 @@ const state = reactive<State>({
     }
   },
   playLoading: false,
-  userCardAnswerList: []
+  userCardAnswerList: [],
+  queryParams: null
 })
 
 const stx = useScheduler(() => {
@@ -116,7 +137,7 @@ const stx = useScheduler(() => {
 
 // 控制游戏开始
 const gameStart = () => {
-  navbarState.countdownTotal = 60
+  navbarState.countdownTotal = 5
   stx.start()
   playAudio()
 }
@@ -130,25 +151,46 @@ const onSubmitCard = async () => {
   await riveAniRef.value.start(3)
 
   wisdomCoinStore.itemCoin += 3
-
+  console.log({
+    correctQuantity: calcQuantityStore.correctQuantity,
+    totalQuantity: calcQuantityStore.totalQuantity,
+    capability: state.card?.cardAble
+  });
+  
   // 保存做题记录
-  opraRecordStore.push(calcQuantityStore)
+  opraRecordStore.push({
+    ...calcQuantityStore,
+    capability: state.card?.cardAble
+  })
 
   // 保存做题时间
   gameCountdownStore.sum(60 - navbarState.countdownTotal)
 
+  console.log("opraRecordStore:", opraRecordStore, navbarState.progress);
+  
+
   // 触发游戏结束
-  if (opraRecordStore.length == 5) {
-    // 重置一些东西
+  if (navbarState.progress == state.cardIds.length) {
+    console.log('我会触发吗');
+    
+    gameCompleted()
+
+    return
   }
 
   // 切换题卡
-  navbarState.progress++
 
-  state.cardId = state.cardIds[navbarState.progress - 1]
+  if (state.opraMode == OpraModeEnum.PRACTICE) {
 
-  GetCardDetailById()
+    // state.cardId
 
+  } else {
+    navbarState.progress++
+
+    state.cardId = state.cardIds[navbarState.progress - 1]
+  }
+
+  GetCardDetailById()
 }
 
 // 提交题卡
@@ -156,8 +198,8 @@ const gameCompleted = async () => {
   // state
   const $par = {
     "attach": {
-      "itemId": "",
-      "recordId": ""
+      "itemId": state.queryParams?.attach.itemId,
+      "recordId": state.queryParams?.attach.recordId
     },
     "data": {
       "correctQuantity": opraRecordStore.totalCorrectQuantity,
@@ -168,13 +210,17 @@ const gameCompleted = async () => {
     }
   }
 
-  const data = await submitlearnPortAns($par)
+  const data = await submitlearnPortAns($par, state.queryParams?.submitUrl!)
+
+  opraRecordStore.clear()
+  // 如果opraMode 的是learn 则跳转到app-web页面
+  // if (state.opraMode == OpraModeEnum.LEARN) {
+  window.location.href = `https://nginx.test.luojigou.vip/app_web/learn-plan.html#/?from='exercise'`
+  // }
   
 }
 
 const playAudio = () => {
-  console.log('state.card?.audio:', state.card?.audio);
-  
   state.playLoading = true
   atx.play(state.card?.audio!)
   atx.onplayend(() => state.playLoading = false)
@@ -185,6 +231,7 @@ const GetCollectionDetailById = async () => {
   const { data } = await getCollectionDetailById(state.collectionId)
   if ( state.opraMode === OpraModeEnum.PRACTICE) {
     state.cardIds = practiceStore.get(state.collectionId, data.cardIds)
+    state.cardId = practiceStore.nextId(state.cardIds)
   } else {
     state.cardIds = data.cardIds
   }
@@ -200,20 +247,17 @@ const GetCardDetailById = async () => {
   const { data } = await getCardDetailById(state.cardId!, state.mode)
   state.card = data
   if (state.mode === CardModeEnum.OPRA) {
-    gameStart()
+   useSchedulerOnce(gameStart)
   }
 }
 
 
 onMounted(async () => {
 
-  console.log('riveAniRef.value:', riveAniRef.value);
-
   if (state.mode === 'preview') {
     GetCardDetailById()
   } else {
     GetCollectionDetailById()
-    
   }
 })
 

+ 1 - 1
src/pages/Home/index.vue

@@ -56,7 +56,7 @@
 
 <script lang="ts" setup >
 import { reactive } from 'vue'
-import {  } from '@dcloudio/uni-app'
+
 const staticImg = {
   bgi: "https://app-resources-luojigou.luojigou.vip/Fh9jOblR745LCJKFSyxVnzPKy2Fn",
   ava: "https://app-resources-luojigou.luojigou.vip/FqBx1QvL4M2Bs8jIuhBIbue0i3U6",

+ 7 - 2
src/pages/Practice/index.vue

@@ -12,15 +12,20 @@
 </template>
 
 <script lang="ts" setup >
+
+import { CardModeEnum, OpraModeEnum } from '@/enum/constant';
 import { useStaticImg } from '@/hooks/index'
-const staticImg = useStaticImg()
 
+const staticImg = useStaticImg()
 
 const jumpPage = (step: number) => {
-
+    uni.navigateTo({
+      url: `/pages/GameView/index?mode=${CardModeEnum.OPRA}&collectionId=1641627325030051841&opraMode=${OpraModeEnum.PRACTICE}`,
+    });
 }
 
 
+
 </script>
 <style scoped lang="less">
 .pattern-container {

+ 9 - 4
src/service/index.ts

@@ -1,27 +1,32 @@
+import { useUserStore } from "@/store"
 
 const TOKEN = 'TOKEN'
 
 interface Params {
   params?: any,
-  url: string,
+  url?: string,
   method: Request.Methods
   responseType?: '',
   baseURL?: string
 }
 
-const BASEURL = import.meta.env.MODE == 'development' ? 'http://local.luojigou.vip:8888' : "https://open.api.luojigou.vip"
+const BASEURL = import.meta.env.MODE == 'development' ? 'http://local.luojigou.vip:8888' : 'https://open.test.luojigou.vip'
+
+//  "https://open.api.luojigou.vip"
 
 export const request = async <T>(
   params: Params
 ) => {
 
   let data = params.params || {};
+
+  console.log("data:", data);
+  
   let url = (params.baseURL ? params.baseURL: BASEURL) + params.url;
   let method = params.method
   let responseType = params.responseType
 
-  let token = uni.getStorageSync(TOKEN);
-
+  let token = useUserStore().token
 
   const response = await new Promise(resolve => {
      uni.request({

+ 6 - 2
src/store/gameTool.ts

@@ -72,14 +72,18 @@ export const useWisdomCoinStore = defineStore(ConstantStore.WISDOMCOIN, () => {
   // WisdomCoin
   const state = reactive({
     total: 0,  //  总的智慧币
-    itemCoin: 0 // 单次玩的智慧币
+    itemCoin: 0, // 单次玩的智慧币
+    remainderWisdomCoin: 0 // 本次可加的智慧币的上限
   })
 
   const _set = (record: number) => state.total = record
 
+  // 增加智慧币, 按照错误与正确的规则增加
+  const _add = ( ) => {}
+
   return {
     ...state,
-    set: _set,
+    add: _add
   }
 
 })

+ 3 - 3
src/store/opraRecords.ts

@@ -11,14 +11,14 @@ export const useOpraRecordStore = defineStore(ConstantStore.OPRARECORDS, () => {
   })
 
   const _push = (records: any) => setState([...state.value!, records])
-
+  
   const _clear = () => setState([])
 
   const _get = () => state.value
 
-  const _correctQuantityTotal = state.value?.map( item => item.correctQuantity).reduce( (pre, next) => pre + next )
+  const _correctQuantityTotal = state.value?.length && state.value?.map( item => item.correctQuantity).reduce( (pre, next) => pre + next )
 
-  const _totalQuantity =  state.value?.map( item => item.totalQuantity).reduce( (pre, next) => pre + next )
+  const _totalQuantity =  state.value?.length && state.value?.map( item => item.totalQuantity).reduce( (pre, next) => pre + next )
  
   return {
     push: _push,

+ 13 - 1
src/store/practice.ts

@@ -13,6 +13,12 @@ export const usePracticeStore = defineStore(ConstantStoreEnum.PRACTICE, () => {
 
   appPracticeStore = practice.value!
 
+  /**
+   * 获取全部题卡id与已经练过的题卡id的差集
+   * @param collectionId 题集id
+   * @param cardIds 题集下的题卡id数组
+   * @returns 全部题卡id与已经练过的题卡id的差集
+   */
   const get = (collectionId: string, cardIds: string[]) => {
     
     const olds = practice.value![collectionId] || []
@@ -34,6 +40,11 @@ export const usePracticeStore = defineStore(ConstantStoreEnum.PRACTICE, () => {
     practice.value![collectionId].push(cardId)
   }
 
+  const nextId = (cardIds: string[]) => {
+    const randomCount = Math.floor(Math.random() * cardIds.length)
+    return cardIds[randomCount]
+  }
+  
   // get (stage) 获取collectionIds 的请求也在这个里边来请求
 
   // submit stage cardId
@@ -46,7 +57,8 @@ export const usePracticeStore = defineStore(ConstantStoreEnum.PRACTICE, () => {
 
   return {
     get,
-    set
+    set,
+    nextId
   }
 
 })

+ 1 - 0
src/store/user.ts

@@ -24,6 +24,7 @@ export const useUserStore = defineStore(ConstantStore.USER, () => {
     () =>  setUser(appUser)
   )
 
+
   return toRefs(appUser)
 
 })

+ 18 - 3
src/typing.d.ts

@@ -45,7 +45,7 @@ declare namespace API {
     cardName: string // 题卡名称
     cardDesc: string // 题卡题目
     level: Level // 难度
-    able: Able // 能力点
+    cardAble: Able // 能力点
     opraUser: string // 最后操作人
     stage: Stage // 学段
     audio: string
@@ -81,6 +81,23 @@ declare namespace API {
     totalQuantity: number
   }
 
+  interface P {
+    submitUrl: string,
+    data: {
+      mode: CardModeEnum,
+      collectionId: string,
+      cardId?: string,
+      opraMode: OpraModeEnum,
+      remainderWisdomCoin: number,
+      userWisdomCoin: number,
+
+    },
+    attach: {
+      itemId: string,
+      recordId: string
+    }
+  }
+
 }
 
 
@@ -92,5 +109,3 @@ declare namespace DEVICE {
 
 
 
-
-

+ 20 - 2
src/utils/static.ts

@@ -19,6 +19,26 @@ import coinAni from '@/assets/coin-ani.png'
 import successFlag from '@/assets/success-flag.png'
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 export interface StaticImg {
   logo: string
@@ -79,5 +99,3 @@ let staticImg: Partial<StaticImg> = {
 
 
 export default staticImg
-
-coinTotal

+ 2 - 1
tsconfig.json

@@ -7,7 +7,8 @@
       "@/*": ["./src/*"]
     },
     "lib": ["esnext", "dom"],
-    "types": ["@dcloudio/types"]
+    "types": ["@dcloudio/types"],
+    "typeRoots": ["./node_modules/@types", "./src/typing.d.ts"]
   },
   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
 }

+ 101 - 0
upload.py

@@ -0,0 +1,101 @@
+import paramiko
+import os
+import sys
+import time
+import zipfile
+
+hostname=""
+username=""
+password=""
+port=222
+
+if sys.argv[3] == 'pro':
+ hostname="000.luojigou.vip"
+ username="admin"
+ password='FUpnUQ8aLpngnEW'
+else:
+ hostname="local.luojigou.vip"
+ username="root" 
+ password='RDefC4Rh&I0VN1j1'
+ port=22
+############################## 
+############################## 
+############################## 
+
+transport = paramiko.Transport((hostname, port))
+
+transport.connect(username=username, password=password)
+
+############################## 
+############################## 
+############################## 
+
+sftp = paramiko.SFTPClient.from_transport(transport)
+
+# transport.close(),
+############################## 
+############################## 
+############################## 
+
+# 创建SSH对象
+ssh = paramiko.SSHClient()
+
+# 允许连接不在know_hosts文件中的主机
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+# 连接服务器
+ssh.connect(hostname=hostname, port=port, username=username, password=password,allow_agent=False,look_for_keys=False)
+
+############################## 
+##############################
+##############################
+
+print("os.getcwd():",  os.getcwd())
+local_path = os.getcwd() + "/dist/build" + "/h5.zip"
+
+target_file_name = sys.argv[2]
+
+remote_path = sys.argv[1]
+
+remote_file_name = remote_path + target_file_name
+
+print("本地文件地址: " + local_path )
+print("远程目录地址: " + remote_file_name)
+
+srmdir_all_folder = os.getcwd() + '/dist/build/h5'  # 文件夹路径
+
+zip_file_path = srmdir_all_folder + ".zip"  # 压缩文件路径
+
+print("打包中.....🍗")
+def make_zip(source_dir, output_filename):
+    zipf = zipfile.ZipFile(output_filename, 'w')    
+    pre_len = len(os.path.dirname(source_dir))
+    for parent, _, filenames in os.walk(source_dir):
+        for filename in filenames:
+            pathfile = os.path.join(parent, filename)
+            arcname = pathfile[pre_len:].strip(os.path.sep)     #相对路径
+            zipf.write(pathfile, arcname.replace('/h5', ''), zipfile.ZIP_DEFLATED)
+    zipf.close()
+
+make_zip(srmdir_all_folder, zip_file_path)
+
+time.sleep(1)
+print("打包完成.....😎")
+
+print("上传中.....💪")
+sftp.put(local_path,  remote_file_name + "/h5.zip")
+
+time.sleep(1)
+
+cmd1 = "unzip -o -d " + remote_file_name + " " + remote_file_name + "/h5.zip"
+
+stdin, stdout, stderr= ssh.exec_command(cmd1)
+
+result = stdout.read()
+
+print("上传成功🎉🎉🎉🎉")
+
+transport.close()
+
+ssh.close()
+
+