config-game.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. <template>
  2. <div
  3. :style="{
  4. height: 'calc(100vh - 120px)',
  5. backgroundColor: 'red',
  6. position: 'fixed',
  7. top: '0px',
  8. right: '0px',
  9. zIndex: 1000,
  10. }"
  11. >
  12. <a-drawer
  13. :label="getModalTitle()"
  14. size="600px"
  15. :open="open"
  16. :mask="false"
  17. :closable="false"
  18. placement="right"
  19. :get-container="false"
  20. :style="{ position: 'absolute', top: '0px', right: '0px', width: '600px' }"
  21. >
  22. <!-- -->
  23. <template #title>
  24. <div style="display: flex;justify-content: space-between;" >
  25. <div>
  26. <a-space>
  27. <div>游戏配置 {{gameStateComputed ? `: ${gameStateComputed}` : gameStateComputed}}</div>
  28. <a-tooltip placement="bottom" :getPopupContainer="(node) => node.offsetParent">
  29. <template #title>
  30. <span v-html="operationTip"></span>
  31. </template>
  32. <InfoCircleOutlined />
  33. </a-tooltip>
  34. </a-space>
  35. </div>
  36. </div>
  37. </template>
  38. <template #extra >
  39. <a-space>
  40. <a-button v-if="currentLevel === 0" type="primary" @click="createGameOne">
  41. <template #icon><plus-outlined /></template>
  42. 新增
  43. </a-button>
  44. <a-button @click="emits('update:open', false)">取消</a-button>
  45. <a-button type="primary" @click="saveGameConfig" :loading="saveLoading" ghost>保存</a-button>
  46. </a-space>
  47. </template>
  48. <div class="config-game-page">
  49. <div class="game-modal-content">
  50. <!-- 显示返回按钮和新增按钮,仅在非顶级目录显示 -->
  51. <div v-if="currentLevel > 0" class="back-button-row">
  52. <a-button type="link" @click="goBack">
  53. <template #icon><left-outlined /></template>
  54. 返回上级
  55. </a-button>
  56. <a-button v-if="currentLevel === 1" type="primary" size="small" @click="createSubItem">
  57. <template #icon><plus-outlined /></template>
  58. 新增
  59. </a-button>
  60. </div>
  61. <!-- 顶级文件夹内容 -->
  62. <a-row v-if="currentLevel === 0" justify="flex-start" :gutter="[8, 8]" class="config-game-list">
  63. <a-col
  64. :span="7"
  65. style="height: 120px;"
  66. class="config-game-item"
  67. v-for="(folder, index) in cardJson.game_list"
  68. :key="index"
  69. @dblclick="enterFolder(index)"
  70. >
  71. <!-- 状态图标 -->
  72. <div class="status-icon">
  73. <CheckCircleFilled v-if="validateGameConfig(folder).isValid" class="status-complete" />
  74. <ExclamationCircleFilled v-else class="status-incomplete" />
  75. </div>
  76. <img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
  77. <div class="folder-footer">
  78. <div>游戏 {{ index + 1 }}</div>
  79. <div>
  80. <a-dropdown :trigger="['hover']">
  81. <SmallDashOutlined class="action-icon" />
  82. <template #overlay>
  83. <a-menu>
  84. <a-menu-item key="edit" @click="handleEdit(index)">
  85. <EditOutlined />
  86. <span style="margin-left: 8px;">编辑</span>
  87. </a-menu-item>
  88. <a-menu-item key="delete" @click="handleDelete(index)">
  89. <DeleteOutlined style="color: #ff4d4f;" />
  90. <span style="margin-left: 8px;">删除</span>
  91. </a-menu-item>
  92. </a-menu>
  93. </template>
  94. </a-dropdown>
  95. </div>
  96. </div>
  97. </a-col>
  98. <a-empty style="margin: 0 auto;" v-if="cardJson.game_list.length === 0" description="暂无游戏"> </a-empty>
  99. </a-row>
  100. <div v-else-if="currentLevel === 1">
  101. <a-row :gutter="[8, 8]">
  102. <VueDraggableNext
  103. :list="cardJson.game_list[currentGameIndex].items"
  104. group="people"
  105. item-key="id"
  106. :animation="300"
  107. class="config-game-list"
  108. >
  109. <a-col
  110. style="height: 120px;"
  111. :span="8"
  112. class="config-game-item"
  113. v-for="(item, index) in cardJson.game_list[currentGameIndex].items"
  114. :key="index"
  115. @dblclick="enterCardConfig(index)"
  116. >
  117. <img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
  118. <div class="folder-footer">
  119. <a-space>
  120. <div>游戏 {{ index + 1 }}</div>
  121. <div class="folder-info">
  122. <div v-if="item.sub_subject?.music_name ? item.sub_subject.music_name : item.sub_subject.ok" class="music-badge">
  123. <sound-outlined />
  124. <span class="music-name">{{ item.sub_subject?.music_name ? item.sub_subject.music_name : item.sub_subject.ok }}</span>
  125. </div>
  126. </div>
  127. </a-space>
  128. <div>
  129. <a-dropdown :trigger="['hover']">
  130. <SmallDashOutlined class="action-icon" />
  131. <template #overlay>
  132. <a-menu>
  133. <a-menu-item key="edit" @click="handleEditSubItem(index)">
  134. <EditOutlined />
  135. <span style="margin-left: 8px;">编辑</span>
  136. </a-menu-item>
  137. <a-menu-item key="delete" @click="handleDeleteSubItem(index)">
  138. <DeleteOutlined style="color: #ff4d4f;" />
  139. <span style="margin-left: 8px;">删除</span>
  140. </a-menu-item>
  141. </a-menu>
  142. </template>
  143. </a-dropdown>
  144. </div>
  145. </div>
  146. </a-col>
  147. </VueDraggableNext>
  148. </a-row>
  149. <a-empty style="margin: 0 auto;" v-if="cardJson.game_list[currentGameIndex].items.length === 0" description="暂无游戏"> </a-empty>
  150. </div>
  151. <!-- 三级内容:游戏步骤配置 -->
  152. <div v-else-if="currentLevel === 2">
  153. <div class="card-config-content">
  154. <gameStage3
  155. v-if="cardJson.game_list[currentGameIndex]?.items?.[currentSubFolderIndex]"
  156. v-model="cardJson.game_list[currentGameIndex].items[currentSubFolderIndex]"
  157. :gameIndex="currentSubFolderIndex"
  158. v-model:touch_key="cardJson.game_list[currentGameIndex].touch_key"
  159. :rule="cardJson.game_list[currentGameIndex].rule"
  160. @update:modelValue="changedSteps"
  161. @step-selected="emits('step-selected', $event)"
  162. />
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. </a-drawer>
  168. <!-- 新增子项目弹窗 -->
  169. <a-modal
  170. v-model:open="subItemModalVisible"
  171. title="新增子项目"
  172. width="400px"
  173. @ok="handleSaveSubItem"
  174. >
  175. <a-form :model="subItemForm" layout="vertical">
  176. <a-form-item label="子主题音乐">
  177. <SelectAudioNew
  178. v-model="subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']"
  179. placeholder="请选择子主题音乐"
  180. />
  181. <!-- 已选择的音乐名称展示区域 -->
  182. <div v-if="subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']" class="selected-music-display">
  183. <div class="music-info">
  184. <sound-outlined />
  185. <span class="music-name">{{ subItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name'] }}</span>
  186. </div>
  187. </div>
  188. </a-form-item>
  189. </a-form>
  190. </a-modal>
  191. <!-- 编辑子项目弹窗 -->
  192. <a-modal
  193. v-model:open="editSubItemModalVisible"
  194. title="编辑子项目"
  195. width="400px"
  196. @ok="handleSaveSubItemEdit"
  197. @cancel="handleCancelSubItemEdit"
  198. >
  199. <a-form :model="editSubItemForm" layout="vertical">
  200. <a-form-item label="子主题音乐">
  201. <SelectAudioNew
  202. v-model="editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']"
  203. placeholder="请选择子主题音乐"
  204. />
  205. <!-- 已选择的音乐名称展示区域 -->
  206. <div v-if="editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name']" class="selected-music-display">
  207. <div class="music-info">
  208. <sound-outlined />
  209. <span class="music-name">{{ editSubItemForm.sub_subject[editForm.rule == '7' ? 'ok' : 'music_name'] }}</span>
  210. </div>
  211. </div>
  212. </a-form-item>
  213. </a-form>
  214. </a-modal>
  215. <!-- 编辑游戏弹窗 -->
  216. <a-modal
  217. v-model:open="editModalVisible"
  218. title="编辑游戏配置"
  219. width="600px"
  220. @ok="handleSaveEdit"
  221. @cancel="handleCancelEdit"
  222. >
  223. <a-form :model="editForm" layout="vertical">
  224. <!-- 规则单独一行 -->
  225. <a-form-item label="规则">
  226. <a-select v-model:value="editForm.rule" placeholder="请选择规则">
  227. <a-select-option v-for="rule in ruleOptions" :key="rule.value" :value="rule.value">
  228. {{ rule.title }}
  229. </a-select-option>
  230. </a-select>
  231. </a-form-item>
  232. <!-- 音频选择器两列布局 -->
  233. <a-row :gutter="16">
  234. <a-col :span="12">
  235. <a-form-item label="主题音乐:">
  236. <SelectAudioNew
  237. v-model="editForm.main_subject.music_name"
  238. placeholder="请选择主题音乐"
  239. />
  240. </a-form-item>
  241. </a-col>
  242. <!-- <a-col :span="12">
  243. <a-form-item label="顺序多选错误音乐:">
  244. <SelectAudioNew
  245. v-model="editForm.ordered_multiple_err.music_name"
  246. placeholder="请选择顺序多选错误音乐"
  247. />
  248. </a-form-item>
  249. </a-col> -->
  250. </a-row>
  251. <!-- <a-row :gutter="16">
  252. <a-col :span="12">
  253. <a-form-item label="重复单击音乐:">
  254. <SelectAudioNew
  255. v-model="editForm.has_click_single.music_name"
  256. placeholder="请选择重复点击音乐"
  257. />
  258. </a-form-item>
  259. </a-col>
  260. <a-col :span="12">
  261. <a-form-item label="再次点击音乐:">
  262. <SelectAudioNew
  263. v-model="editForm.still_have.music_name"
  264. placeholder="请选择再次点击音乐"
  265. />
  266. </a-form-item>
  267. </a-col>
  268. </a-row>
  269. <a-row :gutter="16">
  270. <a-col :span="12">
  271. <a-form-item label="重复组点击音乐:">
  272. <SelectAudioNew
  273. v-model="editForm.has_click_group.music_name"
  274. placeholder="请选择重复组点击音乐乐"
  275. />
  276. </a-form-item>
  277. </a-col>
  278. <a-col :span="12">
  279. <a-form-item label="等待30秒音乐:">
  280. <SelectAudioNew
  281. v-model="editForm.wait_30s.music_name"
  282. placeholder="请选择等待30秒音乐"
  283. />
  284. </a-form-item>
  285. </a-col>
  286. </a-row>
  287. <a-row :gutter="16">
  288. <a-col :span="12">
  289. <a-form-item label="等待90秒音乐:">
  290. <SelectAudioNew
  291. v-model="editForm.wait_90s.music_name"
  292. placeholder="请选择等待90秒音乐"
  293. />
  294. </a-form-item>
  295. </a-col>
  296. <a-col :span="12">
  297. </a-col>
  298. </a-row> -->
  299. </a-form>
  300. </a-modal>
  301. </div>
  302. </template>
  303. <script lang='ts' setup>
  304. import { CardController } from '@/controller'
  305. import {
  306. LeftOutlined,
  307. PlusOutlined,
  308. SmallDashOutlined,
  309. EditOutlined,
  310. DeleteOutlined,
  311. CheckCircleFilled,
  312. ExclamationCircleFilled,
  313. SoundOutlined,
  314. InfoCircleOutlined
  315. } from '@ant-design/icons-vue'
  316. import { reactive, ref, PropType, computed } from 'vue'
  317. import { message } from 'ant-design-vue'
  318. import SelectAudioNew from './select-audio-new.vue'
  319. import gameStage3 from './game-stage-3.vue'
  320. import { VueDraggableNext } from 'vue-draggable-next'
  321. const operationTip = `
  322. 1. 双击文件进入下一级<br>
  323. 2. 拖拽游戏文件可以调整顺序<br>
  324. 3. 单点单播和有序单点无错误选项<br>
  325. `
  326. const props = defineProps({
  327. open: {
  328. type: Boolean,
  329. default: false
  330. },
  331. config: {
  332. type: Object as PropType<API.CardJson21>,
  333. default: () => ({})
  334. }
  335. })
  336. const saveLoading = ref(false)
  337. const gameStateComputed = computed(() => {
  338. if (cardJson.value.game_list.length === 0) return ''
  339. const rule = cardJson.value.game_list[currentGameIndex.value].rule
  340. console.log('CardController.ruleList.toMap:', rule)
  341. if (currentLevel.value === 0) return ''
  342. return CardController.ruleList.toMap('value', 'title').get(rule) || ''
  343. })
  344. const emits = defineEmits(['update:open', 'step-selected', 'update:config'])
  345. // 卡片category为21的json
  346. const cardJson = computed({
  347. get: () => props.config,
  348. set: (value) => emits('update:config', value)
  349. })
  350. // 一级folders的form
  351. const game1FormJson = {
  352. rule: '',
  353. main_subject: { music_name: '', is_break: 1 },
  354. ordered_multiple_err: { music_name: '', is_break: 1 },
  355. has_click_single: { music_name: '', is_break: 1 },
  356. still_have: { music_name: '', is_break: 1 },
  357. has_click_group: { music_name: '', is_break: 1 },
  358. wait_30s: { music_name: '', is_break: 1 },
  359. wait_90s: { music_name: '', is_break: 1 }
  360. }
  361. // 当前层级:0表示顶级文件夹,1表示sub文件夹,2表示游戏配置
  362. const currentLevel = ref(0)
  363. // 规则下拉选项
  364. const ruleOptions = CardController.ruleList
  365. // 编辑弹窗相关
  366. const editModalVisible = ref(false)
  367. const editingIndex = ref(-1)
  368. const editForm = reactive({
  369. rule: '',
  370. main_subject: { music_name: '', is_break: 1 },
  371. ordered_multiple_err: { music_name: '', is_break: 1 },
  372. has_click_single: { music_name: '', is_break: 1 },
  373. still_have: { music_name: '', is_break: 1 },
  374. has_click_group: { music_name: '', is_break: 1 },
  375. wait_30s: { music_name: '', is_break: 1 },
  376. wait_90s: { music_name: '', is_break: 1 }
  377. })
  378. // 编辑子项目弹窗相关
  379. const editSubItemModalVisible = ref(false)
  380. const editingSubItemIndex = ref(-1)
  381. const editSubItemForm = reactive({
  382. sub_subject: {
  383. music_name: '',
  384. mb: '',
  385. ok: '',
  386. ob: '',
  387. err: '',
  388. eb: ''
  389. },
  390. ok_key: [],
  391. err_key: [],
  392. ok_key_voice: {},
  393. err_key_voice: {}
  394. })
  395. // 子项目弹窗相关
  396. const subItemModalVisible = ref(false)
  397. const subItemForm = reactive({
  398. sub_subject: {
  399. music_name: '',
  400. mb: '',
  401. ok: '',
  402. ob: '',
  403. err: '',
  404. eb: ''
  405. },
  406. ok_key: [],
  407. err_key: [],
  408. ok_key_voice: {},
  409. err_key_voice: {}
  410. })
  411. // 当前选中的游戏索引
  412. const currentGameIndex = ref(0)
  413. // 当前选中的子文件夹索引
  414. const currentSubFolderIndex = ref(0)
  415. // 获取弹窗标题
  416. const getModalTitle = () => {
  417. switch (currentLevel.value) {
  418. case 0:
  419. return '游戏配置'
  420. case 1:
  421. return '游戏配置'
  422. case 2:
  423. return '游戏配置'
  424. default:
  425. return '游戏配置'
  426. }
  427. }
  428. const createGameOne = () => {
  429. cardJson.value.game_list.push(JSON.parse(JSON.stringify(game1FormJson)))
  430. }
  431. // 校验游戏配置是否完整
  432. const validateGameConfig = (gameConfig: any) => {
  433. const requiredFields = [
  434. 'rule',
  435. 'main_subject.music_name'
  436. // 'ordered_multiple_err.music_name',
  437. // 'has_click_single.music_name',
  438. // 'still_have.music_name',
  439. // 'has_click_group.music_name',
  440. // 'wait_30s.music_name',
  441. // 'wait_90s.music_name'
  442. ]
  443. const missingFields: string[] = []
  444. requiredFields.forEach(field => {
  445. const fieldParts = field.split('.')
  446. let value = gameConfig
  447. for (const part of fieldParts) {
  448. value = value?.[part]
  449. }
  450. if (!value || value === '') {
  451. missingFields.push(field)
  452. }
  453. })
  454. return {
  455. isValid: missingFields.length === 0,
  456. missingFields
  457. }
  458. }
  459. // 进入二级文件夹
  460. const enterFolder = (index: number) => {
  461. const gameConfig = cardJson.value.game_list[index]
  462. const validation = validateGameConfig(gameConfig)
  463. // if (!validation.isValid) {
  464. // const fieldNames = {
  465. // rule: '规则',
  466. // 'main_subject.music_name': '主题音乐',
  467. // 'ordered_multiple_err.music_name': '顺序多选错误音乐',
  468. // 'has_click_single.music_name': '重复单击音乐',
  469. // 'still_have.music_name': '再次点击音乐',
  470. // 'has_click_group.music_name': '重复组点击音乐',
  471. // 'wait_30s.music_name': '等待30秒音乐',
  472. // 'wait_90s.music_name': '等待90秒音乐'
  473. // }
  474. // const missingFieldNames = validation.missingFields.map(field => fieldNames[field] || field)
  475. // message.error(`游戏 ${index + 1} 配置不完整,缺少以下字段:${missingFieldNames.join('、').replaceAll('、', '、\n')}`)
  476. // return
  477. // }
  478. // 验证通过,创建 items 数组(如果不存在)
  479. if (!cardJson.value.game_list[index].items) {
  480. cardJson.value.game_list[index].items = []
  481. }
  482. currentGameIndex.value = index
  483. currentLevel.value = 1
  484. }
  485. const changedSteps = (steps: any) => {
  486. console.log('changedSteps:', cardJson.value.game_list[currentGameIndex.value].items)
  487. }
  488. const createSubItem = () => {
  489. // 重置表单并打开弹窗
  490. subItemForm.sub_subject.music_name = ''
  491. subItemForm.sub_subject.mb = ''
  492. subItemForm.sub_subject.ok = ''
  493. subItemForm.sub_subject.ob = ''
  494. subItemForm.sub_subject.err = ''
  495. subItemForm.sub_subject.eb = ''
  496. subItemForm.ok_key = []
  497. subItemForm.err_key = []
  498. subItemForm.ok_key_voice = {}
  499. subItemForm.err_key_voice = {}
  500. subItemModalVisible.value = true
  501. }
  502. // 新建二级文件夹
  503. const handleSaveSubItem = () => {
  504. // 基础校验:必须选择子主题音乐
  505. // if (!subItemForm.sub_subject.music_name) {
  506. // message.warning('请选择子主题音乐')
  507. // return
  508. // }
  509. const index = currentGameIndex.value
  510. const currentGame = cardJson.value.game_list[index]
  511. if (!currentGame) {
  512. message.error('未选择有效的游戏')
  513. return
  514. }
  515. // 确保 items 数组存在
  516. if (!currentGame.items) {
  517. currentGame.items = []
  518. }
  519. // 构造 items 下的新对象(遵循 API.CardJson21[game_list].items 的结构)
  520. const newItem = {
  521. sub_subject: {
  522. music_name: subItemForm.sub_subject.music_name,
  523. mb: 1,
  524. ok: '',
  525. ob: 1,
  526. err: '',
  527. eb: 1
  528. },
  529. ok_key: [],
  530. err_key: [],
  531. ok_key_voice: {},
  532. err_key_voice: {}
  533. }
  534. // 追加到 items
  535. currentGame.items.push(newItem)
  536. console.log('游戏类容', cardJson.value.game_list)
  537. // 关闭弹窗并重置关键字段,便于下次新增
  538. subItemModalVisible.value = false
  539. subItemForm.sub_subject.music_name = ''
  540. }
  541. // 进入卡片配置
  542. const enterCardConfig = (index: number) => {
  543. currentSubFolderIndex.value = index
  544. const currentSubGame = cardJson.value.game_list[currentGameIndex.value]
  545. const currentItem = currentSubGame?.items?.[index]
  546. if (currentItem) {
  547. if (!currentItem.steps) {
  548. currentItem.steps = []
  549. }
  550. }
  551. if (currentSubGame.touch_key[2].length <= index + 1) {
  552. currentSubGame.touch_key[2].push({
  553. is_break: 1,
  554. music_name: '',
  555. value: 2
  556. })
  557. }
  558. currentLevel.value = 2
  559. }
  560. // 返回上级文件夹
  561. const goBack = () => {
  562. if (currentLevel.value > 0) {
  563. currentLevel.value--
  564. }
  565. }
  566. // 处理编辑游戏
  567. const handleEdit = (index: number) => {
  568. editingIndex.value = index
  569. // 将当前游戏数据复制到编辑表单
  570. Object.assign(editForm, JSON.parse(JSON.stringify(cardJson.value.game_list[index])))
  571. editModalVisible.value = true
  572. }
  573. // 处理删除游戏
  574. const handleDelete = (index: number) => {
  575. // 从游戏列表中删除指定索引的游戏
  576. cardJson.value.game_list.splice(index, 1)
  577. }
  578. // 处理编辑子项目
  579. const handleEditSubItem = (index: number) => {
  580. editingSubItemIndex.value = index
  581. // 将当前子项目数据复制到编辑表单
  582. const currentItem = cardJson.value.game_list[currentGameIndex.value].items[index]
  583. editSubItemForm.sub_subject.music_name = currentItem.sub_subject.music_name
  584. editSubItemForm.sub_subject.ok = currentItem.sub_subject.ok
  585. editSubItemForm.sub_subject.err = currentItem.sub_subject.err
  586. editSubItemModalVisible.value = true
  587. console.log(editForm)
  588. }
  589. // 处理删除子项目
  590. const handleDeleteSubItem = (index: number) => {
  591. // 从子项目列表中删除指定索引的项目
  592. cardJson.value.game_list[currentGameIndex.value].items.splice(index, 1)
  593. }
  594. // 保存子项目编辑
  595. const handleSaveSubItemEdit = () => {
  596. if (editingSubItemIndex.value >= 0) {
  597. // 将编辑表单的数据保存到子项目列表
  598. const currentItem = cardJson.value.game_list[currentGameIndex.value].items[editingSubItemIndex.value]
  599. currentItem.sub_subject.music_name = editSubItemForm.sub_subject.music_name || ''
  600. currentItem.sub_subject.ok = editSubItemForm.sub_subject.ok || ''
  601. currentItem.sub_subject.err = editSubItemForm.sub_subject.err || ''
  602. }
  603. editSubItemModalVisible.value = false
  604. editingSubItemIndex.value = -1
  605. }
  606. // 取消子项目编辑
  607. const handleCancelSubItemEdit = () => {
  608. editSubItemModalVisible.value = false
  609. editingSubItemIndex.value = -1
  610. }
  611. // 保存编辑
  612. const handleSaveEdit = () => {
  613. if (editingIndex.value >= 0) {
  614. // 将编辑表单的数据保存到游戏列表
  615. Object.assign(cardJson.value.game_list[editingIndex.value], JSON.parse(JSON.stringify(editForm)))
  616. if (!cardJson.value.game_list[editingIndex.value].touch_key) {
  617. cardJson.value.game_list[editingIndex.value].touch_key = [
  618. [{ value: 0, music_name: '', is_break: 1 }],
  619. [{ value: 1, music_name: '', is_break: 1 }],
  620. [{ value: 2, music_name: '', is_break: 1 }]
  621. ]
  622. }
  623. }
  624. editModalVisible.value = false
  625. editingIndex.value = -1
  626. }
  627. // 取消编辑
  628. const handleCancelEdit = () => {
  629. editModalVisible.value = false
  630. editingIndex.value = -1
  631. }
  632. // 保存游戏配置
  633. const saveGameConfig = async () => {
  634. saveLoading.value = true
  635. // const defaultJson = await Car dController.getDefaultJson()
  636. await CardController.add({
  637. header: cardJson.value.header,
  638. game_list: cardJson.value.game_list
  639. })
  640. saveLoading.value = false
  641. }
  642. </script>
  643. <style lang='less' scoped>
  644. .create-folder-form {
  645. padding: 16px;
  646. background-color: #fff;
  647. border: 1px solid #d9d9d9;
  648. border-radius: 4px;
  649. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  650. }
  651. .config-game-page {
  652. width: 100%;
  653. height: 100%;
  654. }
  655. .back-button-row {
  656. display: flex;
  657. justify-content: space-between;
  658. align-items: center;
  659. margin-bottom: 16px;
  660. }
  661. .game-modal-content {
  662. padding: 16px;
  663. .config-game-list {
  664. width: 100%;
  665. display: flex;
  666. flex-wrap: wrap;
  667. justify-content: flex-start;
  668. gap: 16px;
  669. height: 100%;
  670. }
  671. .config-game-item {
  672. display: flex;
  673. flex-direction: column;
  674. justify-content: space-between;
  675. align-items: center;
  676. cursor: pointer;
  677. border-radius: 8px;
  678. // transition: all 0.3s;
  679. background-color: #f5f5f5;
  680. padding: 0px !important;
  681. border: 1px solid #e9ecf1;
  682. box-sizing: border-box !important;
  683. margin-right: 16px;
  684. margin-bottom: 16px;
  685. position: relative;
  686. overflow: hidden;
  687. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  688. .status-icon {
  689. position: absolute;
  690. top: 8px;
  691. left: 8px;
  692. z-index: 10;
  693. font-size: 16px;
  694. .status-complete {
  695. color: #52c41a;
  696. }
  697. .status-incomplete {
  698. color: #ff4d4f;
  699. }
  700. }
  701. .folder-icon {
  702. width: 86px;
  703. height: 86px;
  704. }
  705. .folder-footer {
  706. width: 100%;
  707. height: 32px;
  708. display: flex;
  709. align-items: center;
  710. justify-content: space-between;
  711. border-top: 1px solid #e9ecf1;
  712. background-color: #fff;
  713. box-sizing: border-box;
  714. padding: 0 12px;
  715. position: absolute;
  716. bottom: 0;
  717. left: 0;
  718. right: 0;
  719. .action-icon {
  720. cursor: pointer;
  721. opacity: 0;
  722. transition: opacity 0.3s;
  723. font-size: 16px;
  724. color: #8c8c8c;
  725. &:hover {
  726. color: #1890ff;
  727. }
  728. }
  729. }
  730. .folder-info {
  731. width: 100%;
  732. display: flex;
  733. flex-direction: column;
  734. align-items: center;
  735. // padding: 12px 0;
  736. // margin-bottom: 32px;
  737. .folder-title {
  738. font-weight: 500;
  739. margin-bottom: 8px;
  740. font-size: 14px;
  741. color: #333;
  742. }
  743. .music-badge {
  744. display: flex;
  745. align-items: center;
  746. gap: 4px;
  747. border-radius: 12px;
  748. font-size: 12px;
  749. .anticon {
  750. color: #1890ff;
  751. font-size: 12px;
  752. }
  753. .music-name {
  754. color: #1890ff;
  755. white-space: nowrap;
  756. overflow: hidden;
  757. text-overflow: ellipsis;
  758. max-width: 80px;
  759. }
  760. }
  761. }
  762. &:hover {
  763. scale: 1.02;
  764. .folder-footer .action-icon {
  765. opacity: 1;
  766. }
  767. }
  768. &:hover {
  769. scale: 1.02;
  770. }
  771. }
  772. }
  773. .card-config-content {
  774. height: 700px;
  775. overflow: hidden;
  776. overflow-y: scroll;
  777. padding: 16px;
  778. h3 {
  779. margin-bottom: 24px;
  780. text-align: center;
  781. }
  782. }
  783. .step-list {
  784. display: flex;
  785. flex-direction: column;
  786. gap: 12px;
  787. }
  788. .step-item {
  789. display: flex;
  790. align-items: center;
  791. padding: 12px;
  792. border: 1px solid #d9d9d9;
  793. border-radius: 4px;
  794. cursor: grab;
  795. background-color: #fff;
  796. transition: all 0.3s;
  797. &:hover {
  798. border-color: #40a9ff;
  799. }
  800. }
  801. .step-item-active {
  802. border-color: #1890ff;
  803. box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
  804. }
  805. .step-number {
  806. font-weight: 500;
  807. margin-right: 16px;
  808. }
  809. .step-circles {
  810. display: flex;
  811. gap: 8px;
  812. flex-grow: 1;
  813. }
  814. .circle {
  815. width: 20px;
  816. height: 20px;
  817. border-radius: 50%;
  818. background-color: #f0f0f0;
  819. border: 1px solid #ccc;
  820. }
  821. .step-actions {
  822. cursor: pointer;
  823. }
  824. .selected-music-display {
  825. margin-top: 12px;
  826. padding: 10px 12px;
  827. background-color: #f6f8fa;
  828. border-radius: 6px;
  829. border: 1px solid #e1e4e8;
  830. transition: all 0.3s;
  831. }
  832. .music-info {
  833. display: flex;
  834. align-items: center;
  835. gap: 8px;
  836. .anticon {
  837. color: #1890ff;
  838. font-size: 16px;
  839. }
  840. .music-name {
  841. color: #333;
  842. font-size: 14px;
  843. white-space: nowrap;
  844. overflow: hidden;
  845. text-overflow: ellipsis;
  846. max-width: 300px;
  847. }
  848. }
  849. </style>