|
@@ -37,21 +37,16 @@
|
|
|
class="config-game-item"
|
|
class="config-game-item"
|
|
|
v-for="(folder, index) in cardJson.game_list"
|
|
v-for="(folder, index) in cardJson.game_list"
|
|
|
:key="index"
|
|
:key="index"
|
|
|
- @dblclick="enterSubFolder(index)"
|
|
|
|
|
|
|
+ @dblclick="enterFolder(index)"
|
|
|
>
|
|
>
|
|
|
<!-- 状态图标 -->
|
|
<!-- 状态图标 -->
|
|
|
<div class="status-icon">
|
|
<div class="status-icon">
|
|
|
- <CheckCircleFilled
|
|
|
|
|
- v-if="validateGameConfig(folder).isValid"
|
|
|
|
|
- class="status-complete"
|
|
|
|
|
- />
|
|
|
|
|
- <ExclamationCircleFilled
|
|
|
|
|
- v-else
|
|
|
|
|
- class="status-incomplete"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <CheckCircleFilled v-if="validateGameConfig(folder).isValid" class="status-complete" />
|
|
|
|
|
+ <ExclamationCircleFilled v-else class="status-incomplete" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
|
|
<img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
|
|
|
|
|
+
|
|
|
<div class="folder-footer">
|
|
<div class="folder-footer">
|
|
|
<div>游戏 {{ index + 1 }}</div>
|
|
<div>游戏 {{ index + 1 }}</div>
|
|
|
<div>
|
|
<div>
|
|
@@ -73,22 +68,57 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</a-col>
|
|
</a-col>
|
|
|
|
|
+ <a-empty style="margin: 0 auto;" v-if="cardJson.game_list.length === 0" description="暂无游戏"> </a-empty>
|
|
|
</a-row>
|
|
</a-row>
|
|
|
|
|
|
|
|
- <!-- <div v-else-if="currentLevel === 1">
|
|
|
|
|
|
|
+ <div v-else-if="currentLevel === 1">
|
|
|
<a-row class="config-game-list" :gutter="[8, 8]">
|
|
<a-row class="config-game-list" :gutter="[8, 8]">
|
|
|
- <a-col :span="12" class="config-game-item" v-for="(item, index) in 4" :key="index" @dblclick="enterCardConfig(index)">
|
|
|
|
|
|
|
+ <a-col
|
|
|
|
|
+ :span="12"
|
|
|
|
|
+ class="config-game-item"
|
|
|
|
|
+ v-for="(item, index) in cardJson.game_list[currentGameIndex].items"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ @dblclick="enterCardConfig(index)"
|
|
|
|
|
+ >
|
|
|
<img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
|
|
<img class="folder-icon" :src="require('@/assets/common/folder.svg')" alt="">
|
|
|
- <div>items {{index + 1}}</div>
|
|
|
|
|
|
|
+ <div>游戏 {{index + 1}}</div>
|
|
|
</a-col>
|
|
</a-col>
|
|
|
</a-row>
|
|
</a-row>
|
|
|
|
|
+ <a-empty style="margin: 0 auto;" v-if="cardJson.game_list[currentGameIndex].items.length === 0" description="暂无游戏"> </a-empty>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- 三级内容:游戏步骤配置 -->
|
|
|
<div v-else-if="currentLevel === 2">
|
|
<div v-else-if="currentLevel === 2">
|
|
|
<div class="card-config-content">
|
|
<div class="card-config-content">
|
|
|
- <h3>卡片配置</h3>
|
|
|
|
|
|
|
+ <a-button type="primary" size="small" @click="createGameStep" style="margin-bottom: 16px;">
|
|
|
|
|
+ <template #icon><plus-outlined /></template>
|
|
|
|
|
+ 新建游戏步骤
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ <draggable
|
|
|
|
|
+ v-if="cardJson.game_list[currentGameIndex]?.items?.[currentSubFolderIndex]?.steps"
|
|
|
|
|
+ class="step-list"
|
|
|
|
|
+ v-model="cardJson.game_list[currentGameIndex].items[currentSubFolderIndex].steps"
|
|
|
|
|
+ item-key="id"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #item="{ element: step, index }">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="step-item"
|
|
|
|
|
+ :class="{ 'step-item-active': activeStepIndex === index }"
|
|
|
|
|
+ @click="selectStep(index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="step-number">步骤 {{ index + 1 }}</div>
|
|
|
|
|
+ <div class="step-circles">
|
|
|
|
|
+ <div class="circle" v-for="(circle, cIndex) in step.circles" :key="cIndex"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="step-actions">
|
|
|
|
|
+ <delete-outlined @click.stop="deleteGameStep(index)" style="color: #ff4d4f;" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </draggable>
|
|
|
|
|
+ <a-empty v-if="cardJson.game_list[currentGameIndex].items.length === 0" description="暂无步骤"> </a-empty>
|
|
|
</div>
|
|
</div>
|
|
|
- </div> -->
|
|
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</modal-pro>
|
|
</modal-pro>
|
|
@@ -209,22 +239,33 @@ import { LeftOutlined, PlusOutlined, SmallDashOutlined, EditOutlined, DeleteOutl
|
|
|
import { reactive, ref } from 'vue'
|
|
import { reactive, ref } from 'vue'
|
|
|
import { message } from 'ant-design-vue'
|
|
import { message } from 'ant-design-vue'
|
|
|
import SelectAudioNew from './select-audio-new.vue'
|
|
import SelectAudioNew from './select-audio-new.vue'
|
|
|
|
|
+import draggable from 'vue-draggable-next'
|
|
|
|
|
|
|
|
// 文件夹数据
|
|
// 文件夹数据
|
|
|
const folders = reactive<{ name: string; rule: string; mainSubject: string }[]>([])
|
|
const folders = reactive<{ name: string; rule: string; mainSubject: string }[]>([])
|
|
|
|
|
|
|
|
-// 一级game的form
|
|
|
|
|
|
|
+// 一级folders的form
|
|
|
const game1FormJson = {
|
|
const game1FormJson = {
|
|
|
- rule: '',
|
|
|
|
|
- mainSubject: { music_name: '', is_break: 1 },
|
|
|
|
|
- ordered_multiple_err: { music_name: '', is_break: 1 },
|
|
|
|
|
- has_click_single: { music_name: '', is_break: 1 },
|
|
|
|
|
- still_have: { music_name: '', is_break: 1 },
|
|
|
|
|
- has_click_group: { music_name: '', is_break: 1 },
|
|
|
|
|
- wait_30s: { music_name: '', is_break: 1 },
|
|
|
|
|
- wait_90s: { music_name: '', is_break: 1 }
|
|
|
|
|
|
|
+ rule: '7',
|
|
|
|
|
+ mainSubject: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ ordered_multiple_err: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ has_click_single: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ still_have: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ has_click_group: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ wait_30s: { music_name: '102.mp3', is_break: 1 },
|
|
|
|
|
+ wait_90s: { music_name: '102.mp3', is_break: 1 }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 当前层级:0表示顶级文件夹,1表示sub文件夹,2表示游戏配置
|
|
|
|
|
+const currentLevel = ref(0)
|
|
|
|
|
+
|
|
|
|
|
+// 点击路径的index
|
|
|
|
|
+const currentFolderIndex = reactive({
|
|
|
|
|
+ folder: 0,
|
|
|
|
|
+ subFolder: 0,
|
|
|
|
|
+ step: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
// 卡片category为21的json
|
|
// 卡片category为21的json
|
|
|
const cardJson = reactive<API.CardJson21>({
|
|
const cardJson = reactive<API.CardJson21>({
|
|
|
game_list: []
|
|
game_list: []
|
|
@@ -253,7 +294,7 @@ const editForm = reactive({
|
|
|
const subItemModalVisible = ref(false)
|
|
const subItemModalVisible = ref(false)
|
|
|
const subItemForm = reactive({
|
|
const subItemForm = reactive({
|
|
|
sub_subject: {
|
|
sub_subject: {
|
|
|
- music_name: '',
|
|
|
|
|
|
|
+ music_name: '102.mp3',
|
|
|
mb: '',
|
|
mb: '',
|
|
|
ok: '',
|
|
ok: '',
|
|
|
ob: '',
|
|
ob: '',
|
|
@@ -268,10 +309,12 @@ const subItemForm = reactive({
|
|
|
|
|
|
|
|
// 当前选中的游戏索引
|
|
// 当前选中的游戏索引
|
|
|
const currentGameIndex = ref(0)
|
|
const currentGameIndex = ref(0)
|
|
|
|
|
+
|
|
|
// 当前选中的子文件夹索引
|
|
// 当前选中的子文件夹索引
|
|
|
const currentSubFolderIndex = ref(0)
|
|
const currentSubFolderIndex = ref(0)
|
|
|
-// 当前层级:0表示顶级文件夹,1表示二级文件夹,2表示卡片配置
|
|
|
|
|
-const currentLevel = ref(0)
|
|
|
|
|
|
|
+
|
|
|
|
|
+// 游戏步骤相关
|
|
|
|
|
+const activeStepIndex = ref<number | null>(null)
|
|
|
|
|
|
|
|
// 获取弹窗标题
|
|
// 获取弹窗标题
|
|
|
const getModalTitle = () => {
|
|
const getModalTitle = () => {
|
|
@@ -326,7 +369,7 @@ const validateGameConfig = (gameConfig: any) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 进入二级文件夹
|
|
// 进入二级文件夹
|
|
|
-const enterSubFolder = (index: number) => {
|
|
|
|
|
|
|
+const enterFolder = (index: number) => {
|
|
|
const gameConfig = cardJson.game_list[index]
|
|
const gameConfig = cardJson.game_list[index]
|
|
|
const validation = validateGameConfig(gameConfig)
|
|
const validation = validateGameConfig(gameConfig)
|
|
|
|
|
|
|
@@ -345,7 +388,8 @@ const enterSubFolder = (index: number) => {
|
|
|
const missingFieldNames = validation.missingFields.map(field => fieldNames[field] || field)
|
|
const missingFieldNames = validation.missingFields.map(field => fieldNames[field] || field)
|
|
|
|
|
|
|
|
// 使用 Ant Design 的 message 组件显示错误信息
|
|
// 使用 Ant Design 的 message 组件显示错误信息
|
|
|
- message.error(`游戏 ${index + 1} 配置不完整,缺少以下字段:${missingFieldNames.join('、')}`)
|
|
|
|
|
|
|
+ message.error(`游戏 ${index + 1} 配置不完整,缺少以下字段:${missingFieldNames.join('、').replaceAll('、', '、\n')}`)
|
|
|
|
|
+
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -354,10 +398,50 @@ const enterSubFolder = (index: number) => {
|
|
|
cardJson.game_list[index].items = []
|
|
cardJson.game_list[index].items = []
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 创建新的 item 对象并添加到 items 数组中
|
|
|
|
|
|
|
+ currentGameIndex.value = index
|
|
|
|
|
+ currentLevel.value = 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const createSubItem = () => {
|
|
|
|
|
+ // 重置表单并打开弹窗
|
|
|
|
|
+ subItemForm.sub_subject.music_name = ''
|
|
|
|
|
+ subItemForm.sub_subject.mb = ''
|
|
|
|
|
+ subItemForm.sub_subject.ok = ''
|
|
|
|
|
+ subItemForm.sub_subject.ob = ''
|
|
|
|
|
+ subItemForm.sub_subject.err = ''
|
|
|
|
|
+ subItemForm.sub_subject.eb = ''
|
|
|
|
|
+ subItemForm.ok_key = []
|
|
|
|
|
+ subItemForm.err_key = []
|
|
|
|
|
+ subItemForm.ok_key_voice = []
|
|
|
|
|
+ subItemForm.err_key_voice = []
|
|
|
|
|
+
|
|
|
|
|
+ subItemModalVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 新建二级文件夹
|
|
|
|
|
+const handleSaveSubItem = () => {
|
|
|
|
|
+ // 基础校验:必须选择子主题音乐
|
|
|
|
|
+ if (!subItemForm.sub_subject.music_name) {
|
|
|
|
|
+ message.warning('请选择子主题音乐')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const index = currentGameIndex.value
|
|
|
|
|
+ const currentGame = cardJson.game_list[index]
|
|
|
|
|
+ if (!currentGame) {
|
|
|
|
|
+ message.error('未选择有效的游戏')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保 items 数组存在
|
|
|
|
|
+ if (!currentGame.items) {
|
|
|
|
|
+ currentGame.items = []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构造 items 下的新对象(遵循 API.CardJson21[game_list].items 的结构)
|
|
|
const newItem = {
|
|
const newItem = {
|
|
|
sub_subject: {
|
|
sub_subject: {
|
|
|
- music_name: '',
|
|
|
|
|
|
|
+ music_name: subItemForm.sub_subject.music_name,
|
|
|
mb: '',
|
|
mb: '',
|
|
|
ok: '',
|
|
ok: '',
|
|
|
ob: '',
|
|
ob: '',
|
|
@@ -370,18 +454,60 @@ const enterSubFolder = (index: number) => {
|
|
|
err_key_voice: []
|
|
err_key_voice: []
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cardJson.game_list[index].items.push(newItem)
|
|
|
|
|
|
|
+ // 追加到 items
|
|
|
|
|
+ currentGame.items.push(newItem)
|
|
|
|
|
|
|
|
- currentGameIndex.value = index
|
|
|
|
|
- currentLevel.value = 1
|
|
|
|
|
|
|
+ // 关闭弹窗并重置关键字段,便于下次新增
|
|
|
|
|
+ subItemModalVisible.value = false
|
|
|
|
|
+ subItemForm.sub_subject.music_name = ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleSaveSubItem = () => {}
|
|
|
|
|
-
|
|
|
|
|
// 进入卡片配置
|
|
// 进入卡片配置
|
|
|
const enterCardConfig = (index: number) => {
|
|
const enterCardConfig = (index: number) => {
|
|
|
currentSubFolderIndex.value = index
|
|
currentSubFolderIndex.value = index
|
|
|
|
|
+ const currentItem = cardJson.game_list[currentGameIndex.value]?.items?.[index]
|
|
|
|
|
+ if (currentItem) {
|
|
|
|
|
+ // 初始化步骤数组(如果不存在)
|
|
|
|
|
+ if (!currentItem.steps) {
|
|
|
|
|
+ currentItem.steps = []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
currentLevel.value = 2
|
|
currentLevel.value = 2
|
|
|
|
|
+ activeStepIndex.value = null // 重置选中的步骤
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 创建游戏步骤
|
|
|
|
|
+const createGameStep = () => {
|
|
|
|
|
+ const rule = cardJson.game_list[currentGameIndex.value].rule
|
|
|
|
|
+ const currentItem = cardJson.game_list[currentGameIndex.value]?.items?.[currentSubFolderIndex.value]
|
|
|
|
|
+ console.log('创建游戏步骤', currentItem)
|
|
|
|
|
+
|
|
|
|
|
+ if (currentItem) {
|
|
|
|
|
+ if (!currentItem.steps) {
|
|
|
|
|
+ currentItem.steps = []
|
|
|
|
|
+ }
|
|
|
|
|
+ const newStep = {
|
|
|
|
|
+ id: Date.now(), // 使用时间戳作为唯一ID
|
|
|
|
|
+ circles: [{}, {}, {}] // 示例:每个步骤包含3个小圆圈
|
|
|
|
|
+ }
|
|
|
|
|
+ currentItem.steps.push(newStep)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除游戏步骤
|
|
|
|
|
+const deleteGameStep = (index: number) => {
|
|
|
|
|
+ const currentItem = cardJson.game_list[currentGameIndex.value]?.items?.[currentSubFolderIndex.value]
|
|
|
|
|
+ if (currentItem?.steps) {
|
|
|
|
|
+ currentItem.steps.splice(index, 1)
|
|
|
|
|
+ if (activeStepIndex.value === index) {
|
|
|
|
|
+ activeStepIndex.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 选中步骤
|
|
|
|
|
+const selectStep = (index: number) => {
|
|
|
|
|
+ activeStepIndex.value = index
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 返回上级文件夹
|
|
// 返回上级文件夹
|
|
@@ -525,4 +651,53 @@ const handleCancelEdit = () => {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.step-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ cursor: grab;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: #40a9ff;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-item-active {
|
|
|
|
|
+ border-color: #1890ff;
|
|
|
|
|
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-number {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ margin-right: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-circles {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-grow: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.circle {
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background-color: #f0f0f0;
|
|
|
|
|
+ border: 1px solid #ccc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-actions {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|