123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- <!--
- * @Author: fxs bjnsfxs@163.com
- * @Date: 2024-09-02 17:57:15
- * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-10-14 18:35:26
- * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
- * @Description:
- *
- -->
- <script setup lang="ts">
- import type { ResponseInfo } from '@/types/res'
- import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
- import router from '@/router'
- import FileUpload from '@/components/form/FileUpload.vue'
- import axiosInstance from '@/utils/axios/axiosInstance'
- import { ref, reactive, computed } from 'vue'
- import { useRequest } from '@/hooks/useRequest'
- import { useCommonStore } from '@/stores/useCommon'
- import { downLoadData } from '@/utils/table/table'
- import { resetTimeToMidnight } from '@/utils/common'
- import { shouldListenToEvent } from '@/utils/table/table'
- import { throttleFunc } from '@/utils/common'
- interface uploadEvent {
- id?: number // 更新需要传入id
- gid: string
- actionId: string
- actionName: string
- status: number
- remark?: string
- }
- interface UploadOption {
- id?: number // 更新需要传入id
- optionId: string
- optionName: string
- optionType: string
- actionId: number
- status: number
- }
- interface DownLoadEvent {
- actionId: string
- actionName: string
- createdAt: string
- gid: string
- id: number
- remark: string
- status: number
- updatedAt: string
- }
- interface DownLoadOption {
- actionId: number
- createdAt: string
- id: number
- optionId: string
- optionName: string
- optionType: string
- status: number
- updatedAt: string
- }
- type GetTableReturn<T> = T extends 'all'
- ? {
- allEventTable: Array<DownLoadEvent>
- allOptionsInfo: { [key: string]: Array<DownLoadOption> }
- }
- : T extends 'event'
- ? { allEventTable: Array<DownLoadEvent> }
- : T extends 'option'
- ? { allOptionsInfo: { [key: string]: Array<DownLoadOption> } }
- : never
- const { selectInfo } = useCommonStore()
- const { AllApi } = useRequest()
- // 事件表格ref
- const eventTableRef = ref()
- // 头部ref
- const headerCard = ref<typeof HeaderCard>()
- // 上传ref
- const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
- // 上传的信息
- const uploadInfo = reactive({
- dialogTitle: '上传事件信息'
- })
- // 现在的路由路径名称
- const nowRouteName = computed(() => router.currentRoute.value.name)
- /**
- * @description: 进入详情页,触发headercard的添加事件,增加一个面包屑导航
- * @param {*} info 传入的信息
- * @return {*}
- */
- const headerAddPath = (info: any) => {
- const { name, pathName } = info
- headerCard.value?.addPath(name, pathName)
- }
- /**
- * @description: 展示上传之后的反馈信息
- * @param {*} state 这个状态只表示是否全部成功,其余情况皆为false
- * @param {*} name 区分是事件还是选项
- * @return {*}
- */
- const showUploadMsg = (failedCount: number, totalCount: number, name: string): boolean => {
- if (failedCount < 0) throw new Error('Error!FailedCount < 0')
- let state = true
- let duration = 3000
- let title = '上传成功'
- let position: 'top-left' | 'top-right' = 'top-left'
- let message: string = `${name}全部上传成功`
- let type: 'success' | 'warning' | 'error' = 'success'
- // 有上传失败的情况下
- if (failedCount > 0) {
- state = false
- position = 'top-right'
- duration = 8000
- if (failedCount === totalCount) {
- message = `${name}全部上传失败`
- type = 'error'
- } else {
- message = `${name}部分上传失败`
- type = 'warning'
- }
- }
- ElNotification({
- type,
- title,
- message,
- duration,
- position
- })
- return state
- }
- /**
- * @description: 提交所有新上传的事件及选项请求
- * @param {*} reqList 请求列表
- * @param {*} msg 提示信息,用于展示上传之后返回的消息
- * @return {*}
- */
- const submitUpload = async (reqList: Array<Promise<boolean>>, msg: string): Promise<boolean> => {
- const result = await Promise.allSettled(reqList).then((res) => {
- const failedList = res.filter((item: any) => item.value === false)
- const failedCount = failedList.length
- const totalCount = res.length
- let state = showUploadMsg(failedCount, totalCount, msg)
- return state
- })
- return result
- }
- /**
- * @description: 请求表格数据,如果一次请求没有拿到所有的数据,则再请求一次
- * @param {*} url 请求地址
- * @param {*} otherInfo 请求参数
- * @param {*} offset 偏移量
- * @param {*} limit 请求数量
- * @return {*}
- */
- const getTableData = async (
- url: string,
- otherInfo: any,
- offset = 0,
- limit = 10000
- ): Promise<Array<any>> => {
- let total = limit + offset // 目前请求到的总数
- let finalResult = []
- finalResult = await axiosInstance
- .post(url, {
- offset,
- limit,
- ...otherInfo
- })
- .then(async (res) => {
- let resData = JSON.parse(JSON.stringify(res))
- let result = []
- if (!resData.data) return []
- if (resData.count > total) {
- result = await getTableData(url, otherInfo, total, limit)
- }
- return [...resData.data, ...result]
- })
- .catch((err) => {
- console.log(err)
- return []
- })
- return finalResult
- }
- /**
- * @description: 批量请求选项数据
- * @param {*} url 请求的地址
- * @param {*} reqParams 请求的参数列表,主要是actionid
- * @param {*} eventTable 事件列表
- * @return {Promise<Object>} 返回一个最终的结果,其中key是每个事件的actionid,值是选项列表
- */
- const batReqOptionsData = async (
- url: string,
- reqParams: Array<any>,
- eventTable: Array<DownLoadEvent>
- ): Promise<{ [key: string]: Array<DownLoadOption> }> => {
- let reqList: Array<Promise<any>> = []
- let finalResult: {
- [key: string]: Array<DownLoadOption>
- } = {}
- reqParams.map((item) => {
- reqList.push(
- getTableData(url, item)
- .then((res) => {
- let actionId = eventTable.find((i) => i.id === item.actionId)?.actionId
- if (actionId) finalResult[actionId] = res
- })
- .catch((err) => {
- console.log(err)
- })
- )
- })
- await Promise.all(reqList)
- return finalResult
- }
- /**
- * @description: 拿到事件数据和选项数据,首先需要拿到所有的事件列表,然后将他们的actionId抽出来形成一个列表,这个列表去作为optin的查询参数批量查询
- * @return {*} 返回事件数据和选项数据
- */
- const getAllTable = async <T extends 'all' | 'event' | 'option' = 'all'>(
- table: T = 'all' as any
- ): Promise<GetTableReturn<T>> => {
- let allEventTable: Array<DownLoadEvent> = [],
- allOptionsInfo: { [key: string]: Array<DownLoadOption> } = {}
- allEventTable = await getTableData(AllApi.gameActionList, { gid: selectInfo.gid })
- let optionReqList = allEventTable.map((item) => {
- return { actionId: item.id }
- })
- allOptionsInfo = await batReqOptionsData(
- AllApi.gameActionOptionList,
- optionReqList,
- allEventTable
- )
- // 只需要事件列表在这里就返回
- if (table === 'event') {
- return { allEventTable } as GetTableReturn<T>
- }
- // 只需要选项在这里就只返回选项
- if (table === 'option') {
- return { allOptionsInfo } as GetTableReturn<T>
- }
- return { allEventTable, allOptionsInfo } as GetTableReturn<T>
- }
- /**
- * @description: 开始上传
- * @return {*}
- */
- const startUpload = async () => {
- if (uploadRef.value) {
- uploadRef.value.startUpload()
- }
- }
- /**
- * @description: 获取事件列表和选项列表的数据,开始下载
- * @return {*}
- */
- const startDownload = async () => {
- let { allEventTable, allOptionsInfo } = await getAllTable()
- downLoadData(`allevents_${resetTimeToMidnight(new Date())}`, {
- allEventTable,
- allOptionsInfo
- })
- }
- // 把开始下载方法包装一下,节流
- const throttleStartDownload = throttleFunc(startDownload, 1000)
- /**
- * @description: 创建一个上传请求函数
- * @param {*} url 请求地址
- * @param {*} reqParams 请求参数
- * @param {*} name 上传的是事件还是选项
- * @return {*}
- */
- const createUploadReqFunc = async (url: string, reqParams: any, name: string): Promise<boolean> => {
- let result: boolean = false
- try {
- const res: ResponseInfo = await axiosInstance.post<string, ResponseInfo>(url, reqParams)
- if (res.code !== 0) {
- ElNotification({
- type: 'warning',
- title: '事件上传失败',
- dangerouslyUseHTMLString: true,
- message: `${name}上传失败.<br/>${res.msg}`,
- position: 'top-left',
- duration: 8000
- })
- }
- result = res.code === 0
- } catch (err: any) {
- ElNotification({
- type: 'error',
- title: '上传失败',
- message: `${name}上传失败,请检查参数`,
- position: 'top-right',
- duration: 5000
- })
- console.log(err)
- }
- return result
- }
- /**
- * @description: 上传所有事件
- * @param {*} uploadEventTable 上传的事件列表
- * @param {*} allEventTable 获取的事件列表
- * @return {Promise<boolean>} 返回是否上传成功
- */
- const uploadEvent = async (
- uploadEventTable: Array<uploadEvent>,
- allEventTable: Array<DownLoadEvent>
- ): Promise<boolean> => {
- let eventReqUrl = AllApi.setGameAction
- let eventReqList: Array<Promise<boolean>> = []
- uploadEventTable.map((item: uploadEvent) => {
- let { id, ...otherInfo } = item
- if (allEventTable.some((i: DownLoadEvent) => i.actionId === item.actionId)) {
- eventReqUrl = AllApi.updateGameAction
- }
- let eventReq = createUploadReqFunc(eventReqUrl, otherInfo, item.actionName)
- eventReqList.push(eventReq) // 统一放入请求列表中
- })
- return await submitUpload(eventReqList, '事件') // 等待所有的事件请求完成
- }
- /**
- * @description: 上传所有选项
- * @param {*} uploadOptionsInfo 上传的选项列表
- * @return {*}
- */
- const uploadOpiton = async (uploadOptionsInfo: { [key: string]: Array<UploadOption> }) => {
- const { allEventTable, allOptionsInfo } = await getAllTable() // 重新获取所有事件列表和选项列表
- let optionsReqList: Array<Promise<boolean>> = []
- if (Object.keys(uploadOptionsInfo).length) {
- // 开始上传选项
- allEventTable.map((item) => {
- // 在上传的事件列表中,找到有对应的事件的actionid的那一组数据
- let uploadOptionItem = uploadOptionsInfo[item.actionId]
- // 在现有的事件列表中,找到对应事件的actionid的那一组数据
- let nowOptionItem = allOptionsInfo[item.actionId] // 找到所有在已有事件列表中的选项列表
- // 如果有已存在的事件,并且上传的选项列表中也有对应的actionid,则开始上传
- if (uploadOptionItem) {
- // 对找到的那一组数据进行循环,区分出来哪些是已有的,哪些是新上传的
- // 新上传的需要给他加上actionid,然后上传,这个actionid其实是事件列表的id字段
- uploadOptionItem.map((i) => {
- const { id, actionId: originalActionId, ...otherInfo } = i // 上传参数拆分出来
- const isUpdate = nowOptionItem.some((k) => k.id === id) // 判断是否是更新选项
- // 设置请求 URL 和参数
- const optionReqUrl = isUpdate ? AllApi.updateGameActionOption : AllApi.addGameActionOption
- const reqParams = isUpdate
- ? { id, ...otherInfo } // 更新选项
- : { actionId: item.id, ...otherInfo } // 新增选项
- let eventReq = createUploadReqFunc(optionReqUrl, reqParams, item.actionName)
- optionsReqList.push(eventReq) // 统一放入请求列表中
- })
- } else {
- setTimeout(() => {
- ElNotification({
- type: 'warning',
- title: `没有对应事件`,
- message: `没有${item.actionName}事件,为事件添加的选项无效`,
- position: 'top-right',
- duration: 8000
- })
- }, 0)
- }
- })
- await submitUpload(optionsReqList, '选项') // 等待所有选项上传完成
- } else {
- ElNotification({
- type: 'warning',
- title: '没有选项被上传',
- message: `上传选项为空`,
- position: 'top-right',
- duration: 8000
- })
- }
- }
- // 上传选项的时候,选项的键要设置为actionid
- // 需要先上传事件,然后上传完了,用上传的选项中的第一个(如果有)的actionid去找到对应的事件的ID(不是actionid),然后作为选项上传的id
- /**
- * @description: 当文件添加后,开始进行上传前的处理
- * 首先需要上传事件,如果现有上传的列表中的actionid已经包含在了现有列表中,那么判定为更新,否则为新增
- * 上传完成后,需要首先获取一次新的数据,然后再开始上传选项。
- * 分别在上传和已有的选项列表中,找到对应的acionID的那一组选项,对这一组数据,如果已经存在对应的id,那么就是更新,否则为新增
- * 然后上传,上传完毕后,整体列表刷新,关闭弹框
- * @param {*} data 上传的文件数据,里面包含allEventTable:所有的事件列表,allOptionsInfo:所有选项列表
- * @return {*}
- */
- const uploadSuccess = async (data: {
- allEventTable: Array<uploadEvent>
- allOptionsInfo: { [key: string]: Array<UploadOption> }
- }) => {
- let uploadEventTable = data.allEventTable
- let uploadOptionsInfo = data.allOptionsInfo
- let allEventTable: Array<DownLoadEvent> = []
- // 上传的事件列表有值,则开始上传
- ;({ allEventTable } = await getAllTable('event')) // 获取所有事件列表和选项列表
- let eventUploadResult = false
- if (Array.isArray(uploadEventTable) && uploadEventTable.length > 0) {
- // 将新事件和旧事件区分,对于新事件走新增,对于旧事件走更新
- eventUploadResult = await uploadEvent(uploadEventTable, allEventTable)
- // 如果事件上传全部失败,那么就不要再上传选项了
- if (eventUploadResult) {
- uploadOpiton(uploadOptionsInfo)
- }
- } else {
- ElNotification({
- type: 'error',
- title: '上传参数有误',
- message: `上传参数为空,请检查参数`,
- position: 'top-left',
- duration: 5000
- })
- }
- uploadRef.value?.uploadCallback()
- eventTableRef.value?.updateData()
- }
- </script>
- <template>
- <div class="enventManage">
- <div class="breadcrumbBox">
- <HeaderCard
- ref="headerCard"
- :need-breadcrumb="true"
- :title="'事件管理'"
- :need-pf-select="false"
- ></HeaderCard>
- </div>
- <div class="handleEvent" v-if="nowRouteName === 'EventTable'">
- <div class="fileGroup">
- <el-button class="fileBtn" color="#626aef" @click="startUpload">上传</el-button>
- <el-button class="fileBtn" @click="throttleStartDownload">下载</el-button>
- </div>
- </div>
- <div class="eventTableBox">
- <!-- 监听表格的跳转事件 -->
- <router-view v-slot="{ Component, route }">
- <!-- 是eventtable组件就去监听enterdetail事件 -->
- <!-- 注释也不要写到keep-alive里面,会报错 -->
- <component
- :is="Component"
- ref="eventTableRef"
- v-if="shouldListenToEvent(route.name, 'EventTable')"
- @enterEventDetail="headerAddPath"
- />
- <!-- 如果不是正常渲染其他组件 -->
- <component v-if="route.name === 'EventDetail'" :is="Component" />
- <!-- <component v-else :is="Component" /> -->
- </router-view>
- </div>
- <div class="uploadFileBox">
- <FileUpload
- @upload-success="uploadSuccess"
- ref="uploadRef"
- :title="uploadInfo.dialogTitle"
- ></FileUpload>
- </div>
- </div>
- </template>
- <style scoped>
- .enventManage {
- width: 98%;
- margin: 1% auto;
- background-color: white;
- box-sizing: border-box;
- position: relative;
- }
- .breadcrumbBox {
- background-color: white;
- box-sizing: border-box;
- height: 64px;
- font-size: 16px;
- color: #17233d;
- font-weight: 600;
- /* padding: 0 24px; */
- line-height: 64px;
- }
- .eventTableBox {
- box-sizing: border-box;
- padding: 0px 24px;
- }
- .handleEvent {
- position: absolute;
- /* width: 12%; */
- background-color: white;
- box-sizing: border-box;
- /* height: 48px; */
- font-size: 16px;
- font-weight: 600;
- top: 20px;
- right: 24px;
- /* position: relative; */
- /* justify-content: flex-end; */
- }
- .fileGroup {
- width: 100%;
- }
- .fileBtn {
- margin-right: 10px;
- }
- </style>
|