Ver Fonte

fix: 优化

lvkun996 há 5 meses atrás
pai
commit
2832895037

+ 2 - 1
README.md

@@ -1,4 +1,5 @@
-# flicker-admin
+## swagger地址
+  https://open.api.luojigou.vip/xiaodou-ai-admin/doc.html
 
 
 

+ 7 - 0
src/api/media.ts

@@ -9,3 +9,10 @@ export const getAudioList = (params: {key: string, page: number}) => {
     }
   })
 }
+
+export const getAudioUrlByName = (name: string) => {
+  return request<{records: API.Audio}>({
+    url: '/getAudioUrl/' + name,
+    method: 'GET'
+  })
+}

+ 13 - 4
src/controller/CardController.ts

@@ -9,8 +9,16 @@ export class CardController {
   ]
 
   static ruleList = [
-    { title: '单点单播', value: 7, desc: '无err_key, 只加ok_key' }, // 点一下播放一个语音,此时语音放在ok_key里
-    { title: '无序多点', value: 9, desc: '单点操作,多个答案的无序单选' }, // 点完所有答案播放语音,语音放在ok_key_voice里 按照步骤
+    { title: '单点单播', value: 7, desc: '无err_key, 只加ok_key' },
+    /**
+     *  rule 7 点一下播放一个语音,此时语音放在ok_key里
+     * sub_subject 字段 音频绑定到ok上
+     */
+    { title: '无序多点', value: 9, desc: '单点操作,多个答案的无序单选' },
+    /**
+     *  rule 9 点完所有答案播放语音,语音放在ok_key_voice里 按照步骤
+     * sub_subject 字段 音频绑定到music_name 上
+     */
     { title: '有序单点', value: 10, desc: '单点操作,多个正确答案的有序单选' }, // 点完所有答案播放语音,语音放在ok_key_voice里 按照步骤
     // { title: '双点', value: 11, desc: '' },
     // { title: '无序多选(有一个固定按钮)', value: 12, desc: '' },
@@ -68,6 +76,7 @@ export class CardController {
 
   static async add (data: API.CardJson) {
     const _data = data
+    console.log('添加卡片数据:', _data)
     if (data.header.card_type === 5) {
       CardController.completeCard5Json(_data)
     }
@@ -98,14 +107,13 @@ export class CardController {
     }
 
     const dataJson = data as any
-    console.log('卡片数据详情:', dataJson)
 
     if (dataJson.header.card_type === 5) {
       return CardController.card5JsonById(dataJson)
     } else if (dataJson.header.card_type === 21) {
       return CardController.card21JsonById(dataJson)
     } else {
-      throw new Error('不支持的卡片类型')
+      return { data: {}, cardType: '' }
     }
   }
 
@@ -225,6 +233,7 @@ export class CardController {
           music_name: '',
           is_break: 0
         },
+        card_knob: { music_name: '', is_break: 1 },
         card_insert: { music_name: '', is_break: 1 },
         card_remove: { music_name: '', is_break: 1 },
         ack_ok: { music_name: '', is_break: 1 },

+ 9 - 2
src/controller/MediaController.ts

@@ -1,5 +1,5 @@
-import { getAudioList } from '@/api/media'
-
+import { getAudioList, getAudioUrlByName } from '@/api/media'
+import { instance } from '@/service/request'
 export class MediaController {
   static async getAudioList (params: {key: string, page: number}) {
     const { data } = await getAudioList(params)
@@ -12,4 +12,11 @@ export class MediaController {
       })
     }
   }
+
+  static async getAudioUrlByName (name: string) {
+    const { data } = await getAudioUrlByName(name)
+    return {
+      data: data
+    }
+  }
 }

+ 2 - 1
src/pages/card/components/card-default.vue

@@ -80,7 +80,8 @@ const formState = reactive<{default: FormState[]}>({
     { remind_button: '', keyName: 'remind_button', music_name: '', is_break: 1, label: '提示钮' },
     { remind_not_ack: '', keyName: 'remind_not_ack', music_name: '', is_break: 1, label: '点击过后5秒未答题' },
     { wait_30s: '', keyName: 'wait_30s', music_name: '', is_break: 1, label: '30秒未操作' },
-    { wait_90s: '', keyName: 'wait_90s', music_name: '', is_break: 1, label: '90秒未操作' }
+    { wait_90s: '', keyName: 'wait_90s', music_name: '', is_break: 1, label: '90秒未操作' },
+    { card_knob: '', keyName: 'card_knob', music_name: '', is_break: 1, label: '按钮归位语音' }
   ]
 })
 

+ 1 - 1
src/pages/card/components/card-template-21.vue

@@ -123,7 +123,7 @@ function centerImage () {
   if (!canvas) return
   const imgW = img.width * state.scale
   const imgH = img.height * state.scale
-  state.offsetX = (canvas.width - imgW) / 2 - window.innerWidth / 4// 额外偏移量
+  state.offsetX = (canvas.width - imgW) / 2
   state.offsetY = (canvas.height - imgH) / 2
 }
 

+ 2 - 27
src/pages/card/components/config-card.vue

@@ -59,8 +59,7 @@
 
 </template>
 <script lang='ts'  setup >
-import { reactive, watch, onMounted, ref, Ref, defineExpose, computed } from 'vue'
-import { MediaController } from '@/controller/index'
+import { reactive, watch, ref, Ref, defineExpose, computed } from 'vue'
 import SelectAudioNew from './select-audio-new.vue'
 import SelectBreak from './select-break.vue'
 
@@ -68,7 +67,7 @@ const props = defineProps<{
   config: API.CardJson
 }>()
 
-const emits = defineEmits(['delete', 'finish', 'finishFailed', 'verify'])
+const emits = defineEmits(['delete'])
 
 const collapseKeys = ref({
   touch: '1'
@@ -80,9 +79,6 @@ const dynamicValidateForm = reactive<{default: API.CardJson['touch_key']}>({
 
 const formDom = ref()
 
-// 当前表单的验证状态 为true的时候 父组件校验通过
-const isValid = ref(false)
-
 const touchKeyIdsComputed = computed(() => props.config.touch_key.filter(item => item.selected).map(x => x.id) || [])
 
 watch(
@@ -122,7 +118,6 @@ const isEmptyComputed = ref(true)
 const initConfigJson = () => {
   dynamicValidateForm.default = props.config.touch_key
   isEmptyComputed.value = dynamicValidateForm.default.findIndex(item => item.selected) === -1
-  console.log('dynamicValidateForm', dynamicValidateForm)
 }
 
 watch(
@@ -148,29 +143,9 @@ const generateConfigfCardJson = () => {
       data: dynamicValidateForm.default,
       verify: true
     })
-    // formDom.value.validate().then(res => {
-    //   isValid.value = true
-    //   emits('verify', true)
-    //   resolve({
-    //     status: 200,
-    //     data: dynamicValidateForm.default,
-    //     verify: true
-    //   })
-    // }).catch(err => {
-    //   console.log('catch', err)
-    //   emits('verify', false)
-    //   resolve({
-    //     status: 0,
-    //     data: dynamicValidateForm.default,
-    //     verify: false
-    //   })
-    // })
   })
 }
 
-onMounted(() => {
-})
-
 defineExpose({
   generateConfigfCardJson
 })

+ 76 - 59
src/pages/card/components/config-game.vue

@@ -1,13 +1,26 @@
 <template>
-  <modal-pro
+  <div
+   :style="{
+      height: 'calc(100vh - 120px)',
+      backgroundColor: 'red',
+      position: 'fixed',
+      top: '0px',
+      right: '0px',
+      zIndex: 1000,
+    }"
+  >
+
+  <a-drawer
     :label="getModalTitle()"
+    size="600px"
     :open="open"
     :mask="false"
-    width="640px"
-    @close="emits('update:open', false)"
-    @confirm="saveGameConfig"
-    styles="position: fixed;top: 60px;right: 90px; pointer-events: none;"
+    :closable="false"
+    placement="right"
+    :get-container="false"
+    :style="{ position: 'absolute',  top: '0px', right: '0px', width: '600px' }"
   >
+  <!--     -->
     <template #title>
       <div  style="display: flex;justify-content: space-between;" >
       <div>
@@ -21,13 +34,19 @@
           </a-tooltip>
         </a-space>
       </div>
-      <a-button v-if="currentLevel === 0" type="primary" size="small" @click="createGameOne">
-        <template #icon><plus-outlined /></template>
-        新增
-      </a-button>
+
       </div>
     </template>
-
+    <template #extra  >
+      <a-space>
+        <a-button v-if="currentLevel === 0" type="primary"  @click="createGameOne">
+          <template #icon><plus-outlined /></template>
+          新增
+        </a-button>
+        <a-button @click="emits('update:open', false)">取消</a-button>
+        <a-button type="primary" @click="saveGameConfig" :loading="saveLoading"  ghost>保存</a-button>
+      </a-space>
+    </template>
     <div class="config-game-page">
       <div class="game-modal-content">
         <!-- 显示返回按钮和新增按钮,仅在非顶级目录显示 -->
@@ -107,9 +126,9 @@
                 <a-space>
                   <div>游戏 {{ index + 1 }}</div>
                   <div class="folder-info">
-                    <div v-if="item.sub_subject?.ok" class="music-badge">
+                    <div v-if="item.sub_subject?.music_name ? item.sub_subject.music_name : item.sub_subject.ok" class="music-badge">
                       <sound-outlined />
-                      <span class="music-name">{{ item.sub_subject.ok }}</span>
+                      <span class="music-name">{{ item.sub_subject?.music_name ? item.sub_subject.music_name : item.sub_subject.ok }}</span>
                     </div>
                   </div>
                 </a-space>
@@ -143,6 +162,7 @@
             <gameStage3
               v-if="cardJson.game_list[currentGameIndex]?.items?.[currentSubFolderIndex]"
               v-model="cardJson.game_list[currentGameIndex].items[currentSubFolderIndex]"
+              :gameIndex="currentSubFolderIndex"
               v-model:touch_key="cardJson.game_list[currentGameIndex].touch_key"
               :rule="cardJson.game_list[currentGameIndex].rule"
               @update:modelValue="changedSteps"
@@ -152,7 +172,7 @@
         </div>
       </div>
     </div>
-  </modal-pro>
+  </a-drawer>
 
   <!-- 新增子项目弹窗 -->
   <a-modal
@@ -164,15 +184,15 @@
     <a-form :model="subItemForm" layout="vertical">
       <a-form-item label="子主题音乐">
         <SelectAudioNew
-          v-model="subItemForm.sub_subject.ok"
+          v-model="subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']"
           placeholder="请选择子主题音乐"
         />
 
         <!-- 已选择的音乐名称展示区域 -->
-        <div v-if="subItemForm.sub_subject.ok" class="selected-music-display">
+        <div v-if="subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']" class="selected-music-display">
           <div class="music-info">
             <sound-outlined />
-            <span class="music-name">{{ subItemForm.sub_subject.ok }}</span>
+            <span class="music-name">{{ subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name'] }}</span>
           </div>
         </div>
       </a-form-item>
@@ -190,15 +210,15 @@
     <a-form :model="editSubItemForm" layout="vertical">
       <a-form-item label="子主题音乐">
         <SelectAudioNew
-          v-model="editSubItemForm.sub_subject.ok"
+          v-model="editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']"
           placeholder="请选择子主题音乐"
         />
 
         <!-- 已选择的音乐名称展示区域 -->
-        <div v-if="editSubItemForm.sub_subject.ok" class="selected-music-display">
+        <div v-if="editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']" class="selected-music-display">
           <div class="music-info">
             <sound-outlined />
-            <span class="music-name">{{ editSubItemForm.sub_subject.ok }}</span>
+            <span class="music-name">{{ editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name'] }}</span>
           </div>
         </div>
       </a-form-item>
@@ -296,6 +316,8 @@
       </a-row> -->
     </a-form>
   </a-modal>
+
+ </div>
 </template>
 
 <script lang='ts' setup>
@@ -334,6 +356,8 @@ const props = defineProps({
   }
 })
 
+const saveLoading = ref(false)
+
 const gameStateComputed = computed(() => {
   if (cardJson.value.game_list.length === 0) return ''
   const rule = cardJson.value.game_list[currentGameIndex.value].rule
@@ -445,13 +469,13 @@ const createGameOne = () => {
 const validateGameConfig = (gameConfig: any) => {
   const requiredFields = [
     'rule',
-    'main_subject.music_name',
-    'ordered_multiple_err.music_name',
-    'has_click_single.music_name',
-    'still_have.music_name',
-    'has_click_group.music_name',
-    'wait_30s.music_name',
-    'wait_90s.music_name'
+    'main_subject.music_name'
+    // 'ordered_multiple_err.music_name',
+    // 'has_click_single.music_name',
+    // 'still_have.music_name',
+    // 'has_click_group.music_name',
+    // 'wait_30s.music_name',
+    // 'wait_90s.music_name'
   ]
 
   const missingFields: string[] = []
@@ -483,13 +507,13 @@ const enterFolder = (index: number) => {
   if (!validation.isValid) {
     const fieldNames = {
       rule: '规则',
-      'main_subject.music_name': '主题音乐',
-      'ordered_multiple_err.music_name': '顺序多选错误音乐',
-      'has_click_single.music_name': '重复单击音乐',
-      'still_have.music_name': '再次点击音乐',
-      'has_click_group.music_name': '重复组点击音乐',
-      'wait_30s.music_name': '等待30秒音乐',
-      'wait_90s.music_name': '等待90秒音乐'
+      'main_subject.music_name': '主题音乐'
+      // 'ordered_multiple_err.music_name': '顺序多选错误音乐',
+      // 'has_click_single.music_name': '重复单击音乐',
+      // 'still_have.music_name': '再次点击音乐',
+      // 'has_click_group.music_name': '重复组点击音乐',
+      // 'wait_30s.music_name': '等待30秒音乐',
+      // 'wait_90s.music_name': '等待90秒音乐'
     }
 
     const missingFieldNames = validation.missingFields.map(field => fieldNames[field] || field)
@@ -532,10 +556,10 @@ const createSubItem = () => {
 // 新建二级文件夹
 const handleSaveSubItem = () => {
   // 基础校验:必须选择子主题音乐
-  if (!subItemForm.sub_subject.music_name) {
-    message.warning('请选择子主题音乐')
-    return
-  }
+  // if (!subItemForm.sub_subject.music_name) {
+  //   message.warning('请选择子主题音乐')
+  //   return
+  // }
 
   const index = currentGameIndex.value
   const currentGame = cardJson.value.game_list[index]
@@ -577,12 +601,21 @@ const handleSaveSubItem = () => {
 // 进入卡片配置
 const enterCardConfig = (index: number) => {
   currentSubFolderIndex.value = index
-  const currentItem = cardJson.value.game_list[currentGameIndex.value]?.items?.[index]
+  const currentSubGame = cardJson.value.game_list[currentGameIndex.value]
+  const currentItem = currentSubGame?.items?.[index]
   if (currentItem) {
     if (!currentItem.steps) {
       currentItem.steps = []
     }
   }
+
+  if (currentSubGame.touch_key[2].length <= index + 1) {
+    currentSubGame.touch_key[2].push({
+      is_break: 1,
+      music_name: '',
+      value: 2
+    })
+  }
   currentLevel.value = 2
 }
 
@@ -613,12 +646,10 @@ const handleEditSubItem = (index: number) => {
   // 将当前子项目数据复制到编辑表单
   const currentItem = cardJson.value.game_list[currentGameIndex.value].items[index]
   editSubItemForm.sub_subject.music_name = currentItem.sub_subject.music_name
-  editSubItemForm.sub_subject.mb = currentItem.sub_subject.mb
   editSubItemForm.sub_subject.ok = currentItem.sub_subject.ok
-  editSubItemForm.sub_subject.ob = currentItem.sub_subject.ob
   editSubItemForm.sub_subject.err = currentItem.sub_subject.err
-  editSubItemForm.sub_subject.eb = currentItem.sub_subject.eb
   editSubItemModalVisible.value = true
+  console.log(editForm)
 }
 
 // 处理删除子项目
@@ -630,25 +661,11 @@ const handleDeleteSubItem = (index: number) => {
 // 保存子项目编辑
 const handleSaveSubItemEdit = () => {
   if (editingSubItemIndex.value >= 0) {
-    // 基础校验:必须选择子主题音乐
-    if (!editSubItemForm.sub_subject.music_name) {
-      message.warning('请选择子主题音乐')
-      return
-    }
-
     // 将编辑表单的数据保存到子项目列表
     const currentItem = cardJson.value.game_list[currentGameIndex.value].items[editingSubItemIndex.value]
-    currentItem.sub_subject.music_name = editSubItemForm.sub_subject.music_name
-    currentItem.sub_subject.mb = editSubItemForm.sub_subject.mb || 1
+    currentItem.sub_subject.music_name = editSubItemForm.sub_subject.music_name || ''
     currentItem.sub_subject.ok = editSubItemForm.sub_subject.ok || ''
-    currentItem.sub_subject.ob = editSubItemForm.sub_subject.ob || 1
     currentItem.sub_subject.err = editSubItemForm.sub_subject.err || ''
-    currentItem.sub_subject.eb = editSubItemForm.sub_subject.eb || 1
-
-    // if (!currentItem.ok_key) currentItem.ok_key = []
-    // if (!currentItem.err_key) currentItem.err_key = []
-    // if (!currentItem.ok_key_voice) currentItem.ok_key_voice = { value: 0, music_name: '', is_break: 1 }
-    // if (!currentItem.err_key_voice) currentItem.err_key_voice = { value: 0, music_name: '', is_break: 1 }
   }
   editSubItemModalVisible.value = false
   editingSubItemIndex.value = -1
@@ -685,14 +702,14 @@ const handleCancelEdit = () => {
 }
 
 // 保存游戏配置
-const saveGameConfig = async ({ confirmStart, confirmed }) => {
-  confirmStart()
+const saveGameConfig = async () => {
+  saveLoading.value = true
   // const defaultJson = await Car dController.getDefaultJson()
   await CardController.add({
     header: cardJson.value.header,
     game_list: cardJson.value.game_list
   })
-  confirmed()
+  saveLoading.value = false
 }
 
 </script>

+ 3 - 3
src/pages/card/components/game-stage-3.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="game-stage-3">
-    <div class="top-config">
+    <div class="top-config" v-if="rule != 7">
       <a-collapse :bordered="false" v-model:activeKey="collapseKeys.top">
         <a-collapse-panel key="1">
           <template #header>
@@ -39,11 +39,10 @@
               <div class="form-label">
                 <a-space>
                   <div>提示按钮音频:</div>
-                  <!-- <select-break :bordered="false" v-model:value="touch_keys[2][0].is_break" class="break-select-inline" /> -->
                 </a-space>
               </div>
                <SelectAudioNew
-                  v-model="touch_keys[2][0].music_name"
+                  v-model="touch_keys[2][gameIndex + 1].music_name"
                   placeholder="刷新按钮音频"
                   class="audio-select"
                 />
@@ -259,6 +258,7 @@ interface Props {
   modelValue: API.CardJson21Item,
   touch_key: API.CardJson21TouchKey
   rule: API.Rule
+  gameIndex: number
 }
 
 watch(

+ 34 - 38
src/pages/card/components/select-audio-new.vue

@@ -114,6 +114,7 @@ import {
 import { MediaController } from '@/controller'
 import { audioManager } from '@/utils/AudioManaer'
 import AudioDataManager from '@/utils/AudioDataManager'
+import { message } from 'ant-design-vue'
 
 interface Props {
   modelValue?: string
@@ -153,29 +154,26 @@ const audioDataManager = AudioDataManager.getInstance()
 
 // 获取音频列表
 const getAudioList = async () => {
-  try {
-    // 如果没有搜索关键字,使用缓存的数据
-    if (!searchKey.value) {
-      const cachedData = await audioDataManager.getAudioList({ page: 1, key: '' })
-      audioList.value = cachedData as API.Audio[]
-    } else {
-      // 有搜索关键字时,直接调用API
-      const { data } = await MediaController.getAudioList({ page: 1, key: searchKey.value })
-      audioList.value = data as API.Audio[]
-    }
-
-    // 获取音频列表后,开始预加载音频
-    if (audioList.value.length > 0) {
-      const audioUrls = audioList.value.map(audio => audio.url).filter(url => url)
+  // try {
 
-      // 异步预加载,不阻塞UI
-      audioManager.preloadAudioList(audioUrls).catch(error => {
-        console.error('预加载音频失败:', error)
-      })
-    }
-  } catch (error) {
-    console.error('获取音频列表失败:', error)
+  if (!searchKey.value) {
+    const cachedData = await audioDataManager.getAudioList({ page: 1, key: '' })
+    audioList.value = cachedData as API.Audio[]
+  } else {
+    const { data } = await MediaController.getAudioList({ page: 1, key: searchKey.value })
+    audioList.value = data as API.Audio[]
   }
+
+  //   if (audioList.value.length > 0) {
+  //     const audioUrls = audioList.value.map(audio => audio.url).filter(url => url)
+
+  //     audioManager.preloadAudioList(audioUrls).catch(error => {
+  //       console.error('预加载音频失败:', error)
+  //     })
+  //   }
+  // } catch (error) {
+  //   console.error('获取音频列表失败:', error)
+  // }
 }
 
 // 设置所有音频状态
@@ -194,25 +192,22 @@ const selectAudio = (audio: API.Audio) => {
 
 // 播放选中的音频
 const playSelectedAudio = async () => {
-  console.log('selectedValue.value:', selectedValue.value)
-
   if (!selectedValue.value) return
 
-  const audio = audioList.value.find(item => item.title === selectedValue.value)
-  console.log('selectedValue.value:', audioList.value)
-  if (audio) {
-    await playAudio(new Event('click'), audio)
-    currentPlayingAudio.value = selectedValue.value
-    isPlaying.value = true
-  } else {
-    const { data } = await MediaController.getAudioList({ page: 1, key: selectedValue.value })
-    const audio1 = data.find(item => item.title === selectedValue.value)
-    if (audio1) {
-      await playAudio(new Event('click'), audio1)
-      currentPlayingAudio.value = selectedValue.value
-      isPlaying.value = true
-    }
+  const { data } = await MediaController.getAudioUrlByName(selectedValue.value)
+
+  if (!data) {
+    message.error('音频不存在')
+    return
   }
+
+  await playAudio(new Event('click'), {
+    title: selectedValue.value,
+    url: data,
+    state: 'stopped'
+  })
+  currentPlayingAudio.value = selectedValue.value
+  isPlaying.value = true
 }
 
 // 暂停当前音频
@@ -231,7 +226,7 @@ const pauseCurrentAudio = () => {
 const playAudio = async (e: Event, audio: API.Audio) => {
   e.stopPropagation()
   e.preventDefault()
-
+  audioManager.stop()
   try {
     // 先设置所有音频为停止状态
     setAudioListState('stopped')
@@ -295,6 +290,7 @@ const handleSearch = () => {
 
 // 处理弹窗取消
 const handleModalCancel = () => {
+  audioManager.stop()
   searchKey.value = ''
   getAudioList()
   emits('blur')

+ 30 - 18
src/pages/card/index.vue

@@ -25,9 +25,9 @@
       </a-space>
     </div>
     <a-space>
-      <a-button v-if="Number(cardType) === 5" @click="openDefaultConfig(5)">基础配置</a-button>
-      <!-- <a-button v-if="Number(cardType) === 21"  @click="openDefaultConfig(21)">基础配置</a-button> -->
-      <a-button v-if="Number(cardType) === 21"  @click="openState.jsonPanelOpen = !openState.jsonPanelOpen">JSON面板</a-button>
+      <a-button   @click="openState.jsonPanelOpen = !openState.jsonPanelOpen">JSON面板</a-button>
+      <!-- <a-button v-if="Number(cardType) === 5" @click="openDefaultConfig(5)">基础配置</a-button>
+      <a-button v-if="Number(cardType) === 21"  @click="openDefaultConfig(21)">基础配置</a-button> -->
       <a-button v-if="Number(cardType) === 5" type="primary" @click="openConfigCard(5)">打开配置板</a-button>
       <a-button v-if="Number(cardType) === 21" type="primary" @click="openConfigCard(21)">打开配置板</a-button>
     </a-space>
@@ -72,7 +72,6 @@
     <div style="height: 12px;"></div>
     <ConfigCard
       :config="cardJson"
-      @verify="value => handleVerify('cardIsValid', value)"
       @delete="handleCardTemplateDelete"
       ref="cardConfigDom"
     />
@@ -163,8 +162,9 @@
   </a-modal>
 
   <CardJsonPanel
+    v-if="openState.jsonPanelOpen"
     :open="openState.jsonPanelOpen"
-    :jsonData="cardJson21"
+    :jsonData="jsonData"
     @close="openState.jsonPanelOpen = false"
   />
 </template>
@@ -172,7 +172,7 @@
 import { InfoCircleOutlined } from '@ant-design/icons-vue'
 import CardTemplate from './components/card-template.vue'
 import CardTemplate21 from './components/card-template-21.vue'
-import { ref, onMounted, reactive } from 'vue'
+import { ref, onMounted, reactive, computed, watch } from 'vue'
 import ConfigCard from './components/config-card.vue'
 import CardDefault from './components/card-default.vue'
 import CardDefault21 from './components/card-default-21.vue'
@@ -189,8 +189,6 @@ const operationTip = `
   3. 按住shift可以框选多个按钮位
 `
 
-const [messageApi, contextHolder] = message.useMessage()
-
 const cardInfo = useRoute().query as unknown as API.Card
 
 const router = useRouter()
@@ -198,6 +196,8 @@ const router = useRouter()
 const cardType = ref<5 | 21>(5)
 const selectedCardType = ref<5 | 21>(5)
 
+const jsonData = ref()
+
 const openState = reactive({
   configCardOpen: false,
   configGameOpen: false,
@@ -230,6 +230,17 @@ const cardJson21 = reactive<API.CardJson21>({
   game_list: [] as any
 })
 
+watch(
+  () => cardType.value,
+  (newVal) => {
+    jsonData.value = cardType.value === 5 ? cardJson : cardJson21
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
 const cardTemplateDom = ref()
 
 const cardTemplateDom21 = ref()
@@ -400,7 +411,6 @@ const openChangeCardType = () => {
 
 const getCardJsonById = async () => {
   const { data, cardType: type } = await CardController.cardJsonById(cardInfo.id!)
-  console.log('getCardJsonById:', type)
 
   // 需要让用户选择题卡类型 然后生成对应的数据模板
   if (type === '') {
@@ -408,31 +418,33 @@ const getCardJsonById = async () => {
     openState.cardTypeOpen = true
     return
   }
-  cardType.value = type
+  cardType.value = type as 5 | 21
   console.log('卡片类型:', type)
 
   if (type === 5) {
-    cardJson.header = data.header
-    cardJson.slide_knob = data.slide_knob
-    cardJson.touch_key = data.touch_key
+    const _data = data as API.CardJson
+    cardJson.header = _data.header
+    cardJson.slide_knob = _data.slide_knob
+    cardJson.touch_key = _data.touch_key
 
     isValids.buttonIsValid = true
     isValids.cardIsValid = true
 
-    Object.keys(data.slide_knob!).forEach(key => {
-      if (key.startsWith('score') && !data.slide_knob![key]) {
+    Object.keys(_data.slide_knob!).forEach(key => {
+      if (key.startsWith('score') && !_data.slide_knob![key]) {
         isValids.buttonIsValid = false
       }
     })
 
-    data.touch_key!.forEach(item => {
+    _data.touch_key!.forEach(item => {
       if (item.selected && !item.music_name) {
         isValids.cardIsValid = false
       }
     })
   } else {
-    cardJson21.header = data.header as API.CardJson21.header
-    cardJson21.game_list = data.game_list as any
+    const _data = data as API.CardJson21
+    cardJson21.header = _data.header
+    cardJson21.game_list = _data.game_list
   }
 }
 

+ 4 - 0
src/typeing.d.ts

@@ -92,6 +92,10 @@ declare namespace API {
         'music_name': string,
         'is_break': number
       },
+      'card_knob': {
+        'music_name': string,
+        'is_break': number
+      },
       'card_remove': {
           'music_name': string,
           'is_break': number

+ 62 - 0
卡片类型对应值.txt

@@ -0,0 +1,62 @@
+card_type:
+5 对应:  滑钮卡-分类能力
+8 对应:  点触卡-分类能力
+10对应:点触卡-数学能力
+11对应:点触卡-生活常识
+12对应:点触卡-PK对战互动
+13对应:点触卡-视听与记忆(数学能力也能用)
+14对应:熏听卡
+15对应:情景卡
+16对应:点触卡-数学能力									
+17对应:点触卡-观察能力
+18对应:点触卡-比较能力
+	卡片点触按键字段意思:
+		po值:正序编排;
+		na值:反序编排;
+		attr值:1,最大;2,最小;3,大到小排序;4,小到大排序;
+		order:排序序号
+19对应:点触卡-空间能力
+		value:按键键值(单点,重复的按键也可以)
+		order:答题序号(只允许增加)
+20对应:情景卡V2
+21对应:分组类型,包含所有类型
+		rule类别:
+			1.表示单点→(单点操作,单选逻辑,只有一个正确的答案值;);
+			2.表示多点→(单点操作,多个答案的无序单选,此类答案不需要按照顺序作答,将所有按
+				键点完即可。);
+			3.表示有序单点→(单点操作,多个正确答案的有序单选,答案需要有序一个一个按照
+
+
+				顺序作答,如果不按照顺序作答则认为错误,点错则需要重新开始作答;):
+			4.同时点击(双点)→双点操作,同时点击逻辑,只有一组正确的答案值;
+			5.无序多选(随机同时点击)→双点操作,同时点击逻辑(每小题可能是一组或多组)答案
+				随机一组一组作答;
+			6.有序多选(同时点击,双点)→表示有序;双点操作,同时点击逻辑,两个答案为一个
+				按键值的有序多选逻辑,答案需要有序一组一组按照顺序作答,如果不按照顺序作答则认为
+				错误,点错则需要重新开始作答;
+			7.单点单播,无对错;
+			8.新脚本,单点无序;
+			9.新脚本,表示多点→(单点操作,多个答案的无序单选,此类答案不需要按照顺序作答,将所有按
+				键点完即可。);
+			10.新脚本,表示有序单点→(单点操作,多个正确答案的有序单选,答案需要有序一个一个按照
+				顺序作答,如果不按照顺序作答则认为错误,点错则需要重新开始作答;):
+			11.新脚本,同时点击(双点)→双点操作,同时点击逻辑,每组有一个按键固定+(另两个按键,点这两个按键之一即可);
+			12.新脚本,无序多选(随机同时点击,双点,每组有一个按键固定+(另两个按键,点这两个按键之一即可))
+				→双点操作,同时点击逻辑(每小题可能是一组或多组)答案随机一组一组作答;
+			13.新脚本,有序多选(同时点击,双点,每组有一个按键固定+(另两个按键,点这两个按键之一即可))
+				→表示有序;双点操作,同时点击逻辑,两个答案为一个按键值的有序多选逻辑,答案需要有序一组一组
+				按照顺序作答,如果不按照顺序作答  则认为错误,点错则需要重新开始作答;
+			14.新脚本,无序多选(随机同时点击,双点,同时点击逻辑,只有一组正确的答案值,所有组点完即可)
+				→双点操作,同时点击逻辑(每小题可能是一组或多组)答案随机一组一组作答;
+			15.新脚本,有序多选(同时点击,双点,同时点击逻辑,只有一组正确的答案值,所有组点完即可)
+				→表示有序;双点操作,同时点击逻辑,两个答案为一个按键值的有序多选逻辑,答案需要有序一组一组
+				按照顺序作答,如果不按照顺序作答则认为错误,点错则需要重新开始作答;
+			16.pk对战;  // 
+			17.新脚本,有序单点和无序单点混搭;
+                        18.新手指引A面点触方式;
+			注意:脚本部分的切题,不自动切题也不手动切题音频字段填false.mp3
+需求文档						
+https://docs.qq.com/doc/DRmlNRVlEbFpqR0du
+
+
+