Browse Source

feat(文件管理、事件管理): 完善事件管理页的导入默认配置功能、更新文件管理上传方法

fxs 2 days ago
parent
commit
e398d73154

+ 0 - 5
components.d.ts

@@ -12,7 +12,6 @@ declare module 'vue' {
     CustomDialog: typeof import('./src/components/common/CustomDialog.vue')['default']
     CustomFilter: typeof import('./src/components/form/CustomFilter.vue')['default']
     CustomForm: typeof import('./src/components/form/CustomForm.vue')['default']
-    CustomIcon: typeof import('./src/components/common/CustomIcon.vue')['default']
     CustomTable: typeof import('./src/components/table/CustomTable.vue')['default']
     DownloadTipModal: typeof import('./src/components/common/DownloadTipModal.vue')['default']
     DropDownSelection: typeof import('./src/components/dataAnalysis/DropDownSelection.vue')['default']
@@ -21,7 +20,6 @@ declare module 'vue' {
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
-    ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
@@ -35,7 +33,6 @@ declare module 'vue' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElLink: typeof import('element-plus/es')['ElLink']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
@@ -46,7 +43,6 @@ declare module 'vue' {
     ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElResult: typeof import('element-plus/es')['ElResult']
-    ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
@@ -68,7 +64,6 @@ declare module 'vue' {
     IconIcBaselineVisibility: typeof import('~icons/ic/baseline-visibility')['default']
     IconIcBaselineVisibilityOff: typeof import('~icons/ic/baseline-visibility-off')['default']
     IconIconParkGameThree: typeof import('~icons/icon-park/game-three')['default']
-    IconMaterialSymbolsLightLogout: typeof import('~icons/material-symbols-light/logout')['default']
     IconMdiPassword: typeof import('~icons/mdi/password')['default']
     IconTablerPointFilled: typeof import('~icons/tabler/point-filled')['default']
     MyButton: typeof import('./src/components/form/MyButton.vue')['default']

+ 1 - 0
src/hooks/useRequest.ts

@@ -53,6 +53,7 @@ export function useRequest() {
     updateGameAction: `/user/updateGameAction`, // 更新游戏事件
     setGameAction: `/user/setGameAction`, // 新增事件
     copyActionConfig: `/user/copyGameAction`, // 复制事件
+    getDefaultEventConfig: `/user/getDefaultGameAction`, // 获取默认事件配置
 
     // 事件参数
     gameActionOptionList: `/user/gameActionOptionList`, // 获取事件参数列表

+ 186 - 40
src/views/AppManage/EventManageView.vue

@@ -67,6 +67,28 @@ interface DownLoadOption {
   updatedAt: string
 }
 
+interface EventOptionsItem {
+  keyId: string
+  optionId: string
+  optionName: string
+  optionType: string // 比如 "int", "string" 等
+}
+
+/**
+ * 默认事件配置数据结构说明:
+ * - keyId: 唯一标识符(用于el-table的row-key)
+ * - actionId/actionName: 事件ID和名称(必填字段)
+ * - options: 关联选项数组,包含optionId/optionName/optionType
+ */
+interface DefaultEventData {
+  keyId: string
+  actionId: string
+  actionName: string
+  options: EventOptionsItem[] // 注意这里是数组类型
+  remark: string
+  status: number // 1 表示启用,其他值可能表示不同状态
+}
+
 type GetTableReturn<T> = T extends 'all'
   ? {
       allEventTable: Array<DownLoadEvent>
@@ -152,7 +174,7 @@ const configCopyInfo = ref<DialogConfig>({
       name: 'gid',
       cnName: '原游戏',
       type: FormFieldType.SELECT,
-      defaultValue: selectInfo.gid,
+
       otherOptions: {
         options: gameOptions,
         searchSelected: true
@@ -162,7 +184,7 @@ const configCopyInfo = ref<DialogConfig>({
       name: 'newGid',
       cnName: '目标游戏',
       type: FormFieldType.SELECT,
-
+      defaultValue: selectInfo.gid,
       otherOptions: {
         options: gameOptions,
         searchSelected: true
@@ -171,6 +193,17 @@ const configCopyInfo = ref<DialogConfig>({
   ]
 })
 
+// 控制默认事件配置对话框的显示状态
+const defaultEventDialogVisible = ref(false)
+// 存储默认事件配置数据
+const defaultEventsData = ref<DefaultEventData[]>([])
+// 选项类型映射表(字符串/整数/数组)
+const optionTypes = [
+  { name: 'string', label: '字符串', value: 'string' },
+  { name: 'int', label: '整数', value: 'int' },
+  { name: 'array', label: '数组', value: 'array' }
+]
+
 /**
  * 进入详情页,触发headerCard的添加事件,增加一个面包屑导航
  * @param {*} info 传入的信息
@@ -279,9 +312,10 @@ const getTableData = async (
 
 /**
  * 批量请求选项数据
- * @param {*} url 请求的地址
- * @param {*} reqParams 请求的参数列表,主要是actionId
- * @param {*} eventTable 事件列表
+ * 通过事件列表的actionId生成请求参数,批量获取选项数据
+ * @param url 请求地址
+ * @param reqParams 请求参数列表(包含actionId)
+ * @param eventTable 事件列表数据
  */
 const batReqOptionsData = async (
   url: string,
@@ -473,16 +507,11 @@ const uploadOption = async (uploadOptionsInfo: { [key: string]: Array<UploadOpti
 }
 
 /**
- *  当文件添加后,开始进行上传前的处理
- *  首先需要上传事件,如果现有上传的列表中的actionId已经包含在了现有列表中,那么判定为更新,否则为新增
- *
- *  上传完成后,需要首先获取一次新的数据,然后再开始上传选项。
- *
- *  分别在上传和已有的选项列表中,找到对应的actionID的那一组选项,对这一组数据,如果已经存在对应的id,那么就是更新,否则为新增
- *
- *  然后上传,上传完毕后,整体列表刷新,关闭弹框
- *
- * @param data 上传的文件数据,里面包含allEventTable:所有的事件列表,allOptionsInfo:所有选项列表
+ * 处理文件上传成功后的逻辑
+ * 1. 区分新事件和旧事件分别执行新增/更新操作
+ * 2. 事件上传成功后继续上传关联选项
+ * 3. 最终刷新表格并关闭弹窗
+ * @param data 上传的文件数据,包含事件和选项列表
  */
 const uploadSuccess = async (data: {
   allEventTable: Array<uploadEvent>
@@ -519,28 +548,113 @@ const uploadSuccess = async (data: {
   uploadRef.value?.uploadCallback()
   eventTableRef.value?.updateData()
 }
-
+/**
+ * 处理复制配置
+ */
 const handleCopyConfig = () => {
   copyConfigDialogRef.value?.addForm()
 }
 
-const defaultEventDialogVisible = ref(false)
+/**
+ * 提交单个事件配置到后端接口
+ * 1. 添加游戏ID(gid)并清理冗余字段
+ * 2. 通过 Axios 发送 POST 请求
+ * @param data 事件配置数据(包含事件基础信息和关联选项)
+ * @returns boolean 表示请求是否成功
+ */
+const submitEventConfig = async (data: DefaultEventData) => {
+  const url = AllApi.setGameAction
+  const result = { ...data }
+  delete (result as any).keyIdl
+  ;(result as any).gid = selectInfo.gid
+  const res = (await axiosInstance.post(url, result)) as ResponseInfo
+  return res.code === 0
+}
 
-const events = [
-  { id: '1', name: '登录事件', enabled: true },
-  { id: '2', name: '注册事件', enabled: false }
-]
+/**
+ * 表单提交验证逻辑
+ * 1. 检查启用状态下的事件是否填写完整信息
+ * 2. 并行提交所有事件配置
+ * 3. 统计失败数量并反馈结果
+ */
+const submitEventInfo = async () => {
+  try {
+    const pickedEvents = defaultEventsData.value.filter((item) => item.status === 1)
+
+    const hasEmpty = pickedEvents.some((item) => {
+      const eventConfigHasEmpty = item.actionId === '' || item.actionName === ''
+      const optionHasEmpty = item.options.some(
+        (child) => child.optionName === '' || child.optionId === ''
+      )
+      return eventConfigHasEmpty || optionHasEmpty
+    })
+    if (hasEmpty) {
+      ElMessage.warning('请为所有启用事件填写完整信息')
+      return
+    }
+    const submitQueue: Promise<boolean>[] = []
+    pickedEvents.forEach((item) => {
+      submitQueue.push(submitEventConfig(item))
+    })
+    const res = await Promise.allSettled(submitQueue)
+    // 统计失败数量
+    const failedCount = res.filter((result) => result.status === 'rejected').length
+    if (failedCount > 0) {
+      ElMessage.error(`有 ${failedCount} 个事件配置失败`)
+      return
+    }
+    ElMessage.success('事件配置成功')
+    defaultEventDialogVisible.value = false
+    eventTableRef.value?.updateData()
+  } catch (err) {
+    ElMessage.error('部分事件配置失败')
+    console.error(err)
+  }
+}
 
-const submitEventInfo = () => {
-  console.log(events.filter((item) => item.enabled))
-  const hasEmpty = events
-    .filter((item) => item.enabled)
-    .some((item) => item.id === '' || item.name === '')
-  if (hasEmpty) {
-    ElMessage.warning('请为所有启用事件填写完整信息')
-    return
+/**
+ * 导入默认事件配置
+ * 1. 调用接口获取预设事件模板数据
+ * 2. 为事件及选项生成唯一标识符 keyId
+ * 3. 展示事件配置对话框
+ */
+const handleImportDefaultEvent = async () => {
+  try {
+    const res = (await axiosInstance.post(AllApi.getDefaultEventConfig, {
+      gid: selectInfo.gid
+    })) as ResponseInfo
+    if (res.code !== 0) return false
+    defaultEventsData.value = res.data as DefaultEventData[]
+    // 给每行添加keyId
+    defaultEventsData.value.map((item) => {
+      item.keyId = createRowKey(item)
+      item.options.map((child) => {
+        child.keyId = createRowKey(child)
+      })
+    })
+    defaultEventDialogVisible.value = true
+    console.log(defaultEventsData.value)
+  } catch (err) {
+    console.error(err)
+    ElMessage.error('获取默认配置失败,请稍后重试')
+    defaultEventDialogVisible.value = false
   }
 }
+
+/**
+ * 创建row-key
+ */
+const createRowKey = (row: any) => {
+  return row.actionId || row.optionId || `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
+}
+
+/**
+ * 获取rowKey
+ * @param row
+ */
+const getRowKey = (row: any) => {
+  return row.keyId
+}
 </script>
 
 <template>
@@ -556,11 +670,9 @@ const submitEventInfo = () => {
 
     <div class="handleEvent" v-if="nowRouteName === 'EventTable'">
       <div class="fileGroup">
-        <el-button class="fileBtn" @click="handleCopyConfig">复制</el-button>
         <!--        TODO 完善-->
-        <el-button class="fileBtn" @click="defaultEventDialogVisible = true" v-if="false"
-          >导入默认配置
-        </el-button>
+        <el-button class="fileBtn" color="#626aef" @click="handleCopyConfig">复制</el-button>
+        <el-button class="fileBtn" @click="handleImportDefaultEvent">导入默认配置 </el-button>
         <el-button class="fileBtn" color="#626aef" @click="startUpload">上传</el-button>
         <el-button class="fileBtn" @click="throttleStartDownload">下载</el-button>
       </div>
@@ -595,25 +707,59 @@ const submitEventInfo = () => {
     </div>
     <div>
       <el-dialog v-model="defaultEventDialogVisible" title="事件配置">
-        <el-table :data="events" style="width: 100%" table-layout="auto">
+        <el-table
+          :data="defaultEventsData"
+          style="width: 100%"
+          table-layout="auto"
+          :row-key="getRowKey"
+        >
+          <el-table-column type="expand">
+            <template #default="{ row: eventsRows }">
+              <el-table :data="eventsRows.options" :row-key="getRowKey">
+                <el-table-column prop="optionId" label="选项ID">
+                  <template #default="{ row }">
+                    <el-input v-model="row.optionId" />
+                  </template>
+                </el-table-column>
+                <el-table-column prop="optionName" label="选项名">
+                  <template #default="{ row }">
+                    <el-input v-model="row.optionName" />
+                  </template>
+                </el-table-column>
+                <el-table-column prop="optionType" label="选项类型">
+                  <template #default="{ row }">
+                    <el-select v-model="row.optionType" placeholder="Select" size="large">
+                      <el-option
+                        v-for="item in optionTypes"
+                        :key="item.name"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </template>
+          </el-table-column>
+
           <!-- 是否启用列 -->
-          <el-table-column prop="enabled" label="是否启用" align="center">
+          <el-table-column prop="status" label="是否启用" align="center">
             <template #default="{ row }">
-              <el-checkbox v-model="row.enabled" />
+              <el-checkbox v-model="row.status" :true-value="1" :false-value="0" />
             </template>
           </el-table-column>
 
           <!-- 事件ID列 -->
-          <el-table-column align="center" prop="id" label="事件ID">
+          <el-table-column align="center" prop="actionId" label="事件ID">
             <template #default="{ row }">
-              <el-input v-model="row.id" />
+              <el-input v-model="row.actionId" />
             </template>
           </el-table-column>
 
           <!-- 事件名称列 -->
-          <el-table-column align="center" prop="name" label="事件名称">
+          <el-table-column align="center" prop="actionName" label="事件名称">
             <template #default="{ row }">
-              <el-input v-model="row.name" placeholder="事件名称" />
+              <el-input v-model="row.actionName" placeholder="事件名称" />
             </template>
           </el-table-column>
         </el-table>

+ 1 - 1
src/views/AppManage/EventMangeTable.vue

@@ -54,7 +54,7 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
     name: 'id',
     cnName: 'ID',
     isShow: true,
-    needSort: true
+    needSort: false
   },
   {
     name: 'gid',

+ 1 - 1
src/views/FileManage/FileList.vue

@@ -512,7 +512,7 @@ const downloadFiles = async (singleFileDownloadUrl?: string) => {
         })
       )
     })
-    const folderDownloadPaths = await Promise.all(downloadQueue).then((res) => {
+    const folderDownloadPaths = await Promise.allSettled(downloadQueue).then((res) => {
       return res.map((item) => item.data)
     })
 

+ 0 - 1
src/views/Login/LoginView.vue

@@ -110,7 +110,6 @@ const userLogin = async () => {
       setToken(result.token)
       setRefreshToken(result.refreshToken)
       setLoginState(true)
-      // TODO 保存和验证用户信息
       setUserInfo({
         userName: loginInfo.userName,
         isSuper: result.isSuper