Эх сурвалжийг харах

feat: 新增用户反馈、赛事关联菜单及App端富文本赛事公告

- 新增用户反馈功能,支持用户提交意见与问题
- 实现赛事与菜单的关联管理,提升赛事内容组织灵活性
- 支持App端富文本格式的赛事公告,增强公告展示效果与信息表达能力
wenkai 3 сар өмнө
parent
commit
ecf1a90865

+ 63 - 0
src/api/system/advice/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { AdviceVO, AdviceForm, AdviceQuery } from '@/api/system/advice/types';
+
+/**
+ * 查询用户反馈建议列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listAdvice = (query?: AdviceQuery): AxiosPromise<AdviceVO[]> => {
+  return request({
+    url: '/system/advice/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询用户反馈建议详细
+ * @param id
+ */
+export const getAdvice = (id: string | number): AxiosPromise<AdviceVO> => {
+  return request({
+    url: '/system/advice/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增用户反馈建议
+ * @param data
+ */
+export const addAdvice = (data: AdviceForm) => {
+  return request({
+    url: '/system/advice',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改用户反馈建议
+ * @param data
+ */
+export const updateAdvice = (data: AdviceForm) => {
+  return request({
+    url: '/system/advice',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除用户反馈建议
+ * @param id
+ */
+export const delAdvice = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/advice/' + id,
+    method: 'delete'
+  });
+};

+ 70 - 0
src/api/system/advice/types.ts

@@ -0,0 +1,70 @@
+export interface AdviceVO {
+  /**
+   *
+   */
+  id: string | number;
+
+  /**
+   * 姓名
+   */
+  name: string;
+
+  /**
+   * 队伍id
+   */
+  teamId: string | number;
+
+  /**
+   * 反馈内容
+   */
+  content: string;
+}
+
+export interface AdviceForm extends BaseEntity {
+  /**
+   *
+   */
+  id?: string | number;
+
+  /**
+   * 姓名
+   */
+  name?: string;
+
+  /**
+   * 队伍id
+   */
+  teamId?: string | number;
+
+  /**
+   * 队伍名称
+   */
+  teamName?: string;
+
+  /**
+   * 反馈内容
+   */
+  content?: string;
+}
+
+export interface AdviceQuery extends PageQuery {
+  /**
+   * 姓名
+   */
+  name?: string;
+
+  /**
+   * 队伍名称
+   */
+  teamName?: string;
+
+  /**
+   * 反馈内容
+   */
+  content?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 85 - 42
src/api/system/common/nav/gameNavigator.ts

@@ -1,58 +1,78 @@
-import request from '@/utils/request'
-import type { ApiResult, PageQuery } from '../sys/types'
+import request from '@/utils/request';
+import type { ApiResult, PageQuery } from '../sys/types';
+import { GameEventMenuVO } from "@/api/system/gameEventMenu/types";
 
 // ============================ 主导航 ============================
 export interface GameNavigatorVo {
-  navId?: number
-  name?: string
-  pic?: string
-  color?: string
-  jumpType?: number
-  jumpPath?: string
-  activityType?: number
-  appId?: string
-  sortNum?: number
-  status?: number
-  title?: string
-  createBy?: string
-  createTime?: string
-  updateBy?: string
-  updateTime?: string
-  remark?: string
+  navId?: number;
+  name?: string;
+  pic?: string;
+  color?: string;
+  jumpType?: number;
+  jumpPath?: string;
+  activityType?: number;
+  appId?: string;
+  sortNum?: number;
+  status?: number;
+  title?: string;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  remark?: string;
 }
 
 export interface GameNavigatorBo {
-  navId?: number
-  name?: string
-  pic?: string
-  color?: string
-  jumpType?: number
-  jumpPath?: string
-  activityType?: number
-  appId?: string
-  sortNum?: number
-  status?: number
-  remark?: string
+  navId?: number;
+  name?: string;
+  pic?: string;
+  color?: string;
+  jumpType?: number;
+  jumpPath?: string;
+  activityType?: number;
+  appId?: string;
+  sortNum?: number;
+  status?: number;
+  remark?: string;
+}
+
+// 赛事关联菜单查询参数
+export interface RelateMenuQuery {
+  eventId?: string | number;
+}
+
+// 编辑赛事关联菜单参数
+export interface EditRelateMenuParams {
+  /**
+   * 赛事id
+   */
+  eventId: string | number;
+  /**
+   * 菜单列表 - 菜单的navId数组
+   */
+  menus: number[];
 }
 
 // 查询底部主导航列表
 export function listNavigator(query: GameNavigatorBo & PageQuery) {
-  return request<ApiResult<{
-    rows: GameNavigatorVo[]
-    total: number
-  }>>({
+  return request<
+    ApiResult<{
+      rows: GameNavigatorVo[];
+      total: number;
+    }>
+  >({
     url: '/system/scenic/navigator/list',
     method: 'get',
     params: query
-  })
+  });
 }
 
 // 查询可用的主导航列表
 export function getEnabledNavigator() {
   return request({
     url: '/system/scenic/navigator/enabled',
-    method: 'get',
-  })
+    method: 'get'
+  });
 }
 
 // 查询底部主导航详细
@@ -60,7 +80,7 @@ export function getNavigator(navId: number) {
   return request<ApiResult<GameNavigatorVo>>({
     url: `/system/scenic/navigator/${navId}`,
     method: 'get'
-  })
+  });
 }
 
 // 新增底部主导航
@@ -69,7 +89,7 @@ export function addNavigator(data: GameNavigatorBo) {
     url: '/system/scenic/navigator',
     method: 'post',
     data: data
-  })
+  });
 }
 
 // 修改底部主导航
@@ -78,7 +98,7 @@ export function updateNavigator(data: GameNavigatorBo) {
     url: '/system/scenic/navigator',
     method: 'put',
     data: data
-  })
+  });
 }
 
 // 删除底部主导航
@@ -86,7 +106,7 @@ export function delNavigator(navId: number | Array<number>) {
   return request<ApiResult<void>>({
     url: `/system/scenic/navigator/${navId}`,
     method: 'delete'
-  })
+  });
 }
 
 // 导出底部主导航
@@ -95,7 +115,7 @@ export function exportNavigator(query: GameNavigatorBo) {
     url: '/system/scenic/navigator/export',
     method: 'post',
     data: query
-  })
+  });
 }
 
 // 金刚区(type=4)固定类型数据
@@ -103,5 +123,28 @@ export function listKingKongNav() {
   return request<ApiResult<GameNavigatorVo[]>>({
     url: '/system/scenic/navigator/byType/4',
     method: 'get'
-  })
+  });
+}
+
+// 查询赛事关联菜单
+export function listRelateMenu(query: RelateMenuQuery & PageQuery) {
+  return request<
+    ApiResult<{
+      rows: GameNavigatorVo[];
+      total: number;
+    }>
+  >({
+    url: '/system/scenic/navigator/list/relate',
+    method: 'get',
+    params: query
+  });
+}
+
+// 编辑赛事关联菜单
+export function editRelate(data: EditRelateMenuParams) {
+  return request({
+    url: '/system/scenic/navigator/edit/relate',
+    method: 'post',
+    data: data
+  });
 }

+ 48 - 0
src/api/system/eventMd/index.ts

@@ -0,0 +1,48 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { EventMdVO, EventMdForm } from '@/api/system/eventMd/types';
+
+/**
+ * 查询移动端富文本详细
+ * @param id
+ */
+export const getEventMd = (id: string | number): AxiosPromise<EventMdVO> => {
+  return request({
+    url: '/system/markdown/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 根据赛事id和类型获取移动端富文本详细信息
+ * @param id
+ */
+export const getEventMdByEventAndType = (eventId: string | number, type: number): AxiosPromise<EventMdVO> => {
+  return request({
+    url: `/system/markdown/${eventId}/${type}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 编辑移动端富文本
+ * @param data
+ */
+export const editEventMd = (data: EventMdForm) => {
+  return request({
+    url: '/system/markdown',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 删除移动端富文本
+ * @param id
+ */
+export const delEventMd = (eventId: string | number, type: number) => {
+  return request({
+    url: `/system/markdown/${eventId}/${type}`,
+    method: 'delete'
+  });
+};

+ 99 - 0
src/api/system/eventMd/types.ts

@@ -0,0 +1,99 @@
+export interface EventMdVO {
+  /**
+   * 
+   */
+  id: string | number;
+
+  /**
+   * 赛事id
+   */
+  eventId: string | number;
+
+  /**
+   * 标题
+   */
+  title: string;
+
+  /**
+   * 内容
+   */
+  content: string;
+
+  /**
+   * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
+  6:赛事分组 7:运动员号码簿 8:项目场地
+   */
+  type: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface EventMdForm extends BaseEntity {
+  /**
+   * 
+   */
+  id?: string | number;
+
+  /**
+   * 赛事id
+   */
+  eventId?: string | number;
+
+  /**
+   * 标题
+   */
+  title?: string;
+
+  /**
+   * 内容
+   */
+  content?: string;
+
+  /**
+   * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
+  6:赛事分组 7:运动员号码簿 8:项目场地
+   */
+  type?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface EventMdQuery extends PageQuery {
+
+  /**
+   * 赛事id
+   */
+  eventId?: string | number;
+
+  /**
+   * 标题
+   */
+  title?: string;
+
+  /**
+   * 内容
+   */
+  content?: string;
+
+  /**
+   * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
+  6:赛事分组 7:运动员号码簿 8:项目场地
+   */
+  type?: number;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/system/relateMenu/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MenuVO, MenuForm, MenuQuery } from '@/api/system/menu/types';
+
+/**
+ * 查询赛事菜单关联列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => {
+  return request({
+    url: '/system/menu/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询赛事菜单关联详细
+ * @param id
+ */
+export const getMenu = (id: string | number): AxiosPromise<MenuVO> => {
+  return request({
+    url: '/system/menu/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增赛事菜单关联
+ * @param data
+ */
+export const addMenu = (data: MenuForm) => {
+  return request({
+    url: '/system/menu',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改赛事菜单关联
+ * @param data
+ */
+export const updateMenu = (data: MenuForm) => {
+  return request({
+    url: '/system/menu',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除赛事菜单关联
+ * @param id
+ */
+export const delMenu = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/menu/' + id,
+    method: 'delete'
+  });
+};

+ 46 - 0
src/api/system/relateMenu/types.ts

@@ -0,0 +1,46 @@
+export interface MenuVO {
+  /**
+   * 
+   */
+  id: string | number;
+
+  /**
+   * 赛事id
+   */
+  eventId: string | number;
+
+  /**
+   * 菜单列表
+   */
+  menuList: string;
+
+}
+
+export interface MenuForm extends BaseEntity {
+  /**
+   * 
+   */
+  id?: string | number;
+
+  /**
+   * 赛事id
+   */
+  eventId?: string | number;
+
+  /**
+   * 菜单列表
+   */
+  menuList?: string;
+
+}
+
+export interface MenuQuery extends PageQuery {
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 193 - 0
src/views/system/advice/index.vue

@@ -0,0 +1,193 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="姓名" prop="name">
+              <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="队伍名称" prop="teamName">
+              <el-input v-model="queryParams.teamName" placeholder="请输入队伍名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:advice:remove']"
+              >删除
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:advice:export']"> 导出 </el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="adviceList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="id" align="center" prop="id" v-if="true" />
+        <el-table-column label="姓名" align="center" prop="name" />
+        <!--        <el-table-column label="队伍id" align="center" prop="teamId" />-->
+        <el-table-column label="队伍名称" align="center" prop="teamName" />
+        <!--        <el-table-column label="反馈内容" align="center" prop="content" />-->
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="预览" placement="top">
+              <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['system:article:view']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:advice:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <el-dialog title="建议预览" v-model="dialog.visible" width="1200px" append-to-body>
+      <el-form ref="articleFormRef" :model="form" label-width="80px" disabled>
+        <el-form-item label="姓名" prop="name">
+          <el-input v-model="form.name" />
+        </el-form-item>
+        <el-form-item label="队伍名称" prop="teamName">
+          <el-input v-model="form.teamName" />
+        </el-form-item>
+        <el-form-item label="内容">
+          <editor v-model="form.content" :min-height="192" readOnly />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Advice" lang="ts">
+import { listAdvice, delAdvice } from '@/api/system/advice';
+import { AdviceVO, AdviceQuery, AdviceForm } from '@/api/system/advice/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const adviceList = ref<AdviceVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const adviceFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: AdviceForm = {
+  id: undefined,
+  name: undefined,
+  teamId: undefined,
+  content: undefined
+};
+const data = reactive<PageData<AdviceForm, AdviceQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    content: undefined,
+    params: {}
+  },
+  rules: {
+    name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
+    teamId: [{ required: true, message: '队伍id不能为空', trigger: 'change' }],
+    content: [{ required: true, message: '反馈内容不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询用户反馈建议列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listAdvice(queryParams.value);
+  adviceList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  adviceFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: AdviceVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: AdviceVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除用户反馈建议编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delAdvice(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+const handleView = async (row?: AdviceVO) => {
+  Object.assign(form.value, row);
+  dialog.visible = true;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'system/advice/export',
+    {
+      ...queryParams.value
+    },
+    `advice_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 259 - 304
src/views/system/common/file/info/index.vue


+ 157 - 195
src/views/system/common/nav/components/GameNavigator.vue

@@ -4,12 +4,7 @@
     <el-card class="search-card">
       <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="100px">
         <el-form-item label="菜单名称" prop="name">
-          <el-input
-            v-model="queryParams.name"
-            placeholder="请输入菜单名称"
-            clearable
-            style="width: 200px"
-          />
+          <el-input v-model="queryParams.name" placeholder="请输入菜单名称" clearable style="width: 200px" />
         </el-form-item>
         <el-form-item label="链接类型" prop="jumpType">
           <el-select v-model="queryParams.jumpType" placeholder="请选择链接类型" clearable style="width: 200px">
@@ -53,12 +48,12 @@
           <el-button type="primary" icon="Plus" @click="handleAdd"> 添加菜单</el-button>
         </el-col>
         <!-- <el-col :span="1.5">
-          <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" 
+          <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
             >修改
           </el-button>
         </el-col>
         <el-col :span="1.5">
-          <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" 
+          <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
             >删除
           </el-button>
         </el-col> -->
@@ -68,30 +63,25 @@
 
     <!-- 菜单列表 -->
     <el-card class="list-card">
-      <el-table
-        v-loading="loading"
-        :data="navItems"
-        @selection-change="handleSelectionChange"
-        style="width: 100%"
-      >
+      <el-table v-loading="loading" :data="navItems" @selection-change="handleSelectionChange" style="width: 100%">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" width="80" v-if="columns[0].visible">
           <template #default="scope">
             <span class="sort-number">{{ scope.row.sortNum || '0' }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="菜单名称" align="center" prop="name" width="150" v-if="columns[1].visible" /> 
-        <el-table-column label="活动类型" align="center" prop="activityType" width="150" v-if="columns[2].visible"> 
+        <el-table-column label="菜单名称" align="center" prop="name" width="150" v-if="columns[1].visible" />
+        <el-table-column label="活动类型" align="center" prop="activityType" width="150" v-if="columns[2].visible">
           <template #default="scope">
-            <dict-tag :options="game_activity_type" :value="scope.row.activityType"  />
+            <dict-tag :options="game_activity_type" :value="scope.row.activityType" />
           </template>
         </el-table-column>
-        <el-table-column label="图标" align="center" prop="pic" width="150" v-if="columns[3].visible"> 
+        <el-table-column label="图标" align="center" prop="pic" width="150" v-if="columns[3].visible">
           <template #default="scope">
             <img :src="scope.row.pic" style="width: 50px; height: 50px" />
           </template>
         </el-table-column>
-        <el-table-column label="颜色" align="center" prop="color" width="150" v-if="columns[4].visible" /> 
+        <el-table-column label="颜色" align="center" prop="color" width="150" v-if="columns[4].visible" />
         <el-table-column label="链接类别" align="center" prop="jumpType" width="150" v-if="columns[5].visible">
           <template #default="scope">
             {{ getJumpTypeText(scope.row.jumpType) }}
@@ -102,7 +92,7 @@
             {{ scope.row.jumpPath || '-' }}
           </template>
         </el-table-column>
-        
+
         <el-table-column label="创建时间" min-width="200" v-if="columns[7].visible">
           <template #default="scope">
             <div class="create-info">
@@ -125,7 +115,10 @@
             <el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
             <el-dropdown @command="(command) => handleCommand(command, scope.row)">
               <el-button size="small">
-                更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
+                更多
+                <el-icon class="el-icon--right">
+                  <arrow-down />
+                </el-icon>
               </el-button>
               <template #dropdown>
                 <el-dropdown-menu>
@@ -140,18 +133,8 @@
     </el-card>
 
     <!-- 编辑/添加对话框 -->
-    <el-dialog 
-      v-model="dialogVisible" 
-      :title="dialogTitle" 
-      width="720px"
-      class="nav-dialog"
-    >
-      <el-form 
-        ref="formRef" 
-        :model="form" 
-        :rules="rules" 
-        label-width="120px"
-      >
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="720px" class="nav-dialog">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="菜单名称" prop="name">
@@ -169,13 +152,13 @@
           <el-col :span="18">
             <el-form-item label="活动类型" prop="activityType">
               <el-radio-group v-model="form.activityType">
-                <el-radio v-for="dict in game_activity_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+                <el-radio v-for="dict in game_activity_type" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
         </el-row>
-        
-        <el-row :gutter="20"> 
+
+        <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="功能图标" prop="pic">
               <el-upload
@@ -187,7 +170,9 @@
                 :on-success="handleIconSuccess"
               >
                 <img v-if="form.pic" :src="form.pic" class="icon-preview" />
-                <el-icon v-else class="icon-uploader-icon"><Plus /></el-icon>
+                <el-icon v-else class="icon-uploader-icon">
+                  <Plus />
+                </el-icon>
               </el-upload>
               <div class="upload-tip">建议尺寸 100x100</div>
             </el-form-item>
@@ -195,26 +180,13 @@
           <el-col :span="12">
             <el-form-item label="颜色" required>
               <div class="color-input-group">
-                <el-color-picker 
-                  v-model="form.color" 
-                  show-alpha 
-                  size="large"
-                  class="color-picker"
-                  @change="handleColorChange"
-                />
-                <el-input 
-                  v-model="colorInput" 
-                  placeholder="#RRGGBB"
-                  class="color-input"
-                  @input="handleColorInput"
-                  @blur="validateColorInput"
-                />
+                <el-color-picker v-model="form.color" show-alpha size="large" class="color-picker" @change="handleColorChange" />
+                <el-input v-model="colorInput" placeholder="#RRGGBB" class="color-input" @input="handleColorInput" @blur="validateColorInput" />
               </div>
             </el-form-item>
           </el-col>
         </el-row>
-        
-        
+
         <el-form-item label="跳转类型" prop="jumpType">
           <el-radio-group v-model="form.jumpType" @change="handleJumpTypeChange">
             <el-radio :value="1">跳转链接</el-radio>
@@ -227,11 +199,11 @@
             <el-radio :value="8">电话拨号</el-radio>
           </el-radio-group>
         </el-form-item>
-        
+
         <el-form-item label="跳转链接" prop="jumpPath">
           <el-input v-model="form.jumpPath" placeholder="请输入跳转链接" :disabled="form.jumpType === 2" />
         </el-form-item>
-        
+
         <el-form-item label="小程序AppID" prop="appId" v-if="form.jumpType === 4">
           <el-input v-model="form.appId" placeholder="请输入对方小程序的AppID" />
         </el-form-item>
@@ -262,25 +234,23 @@
             </el-form-item>
           </el-col>
         </el-row>
-        
+
         <el-form-item label="状态" prop="status">
           <el-radio-group v-model="form.status">
             <el-radio :value="0">正常</el-radio>
             <el-radio :value="1">停用</el-radio>
           </el-radio-group>
         </el-form-item>
-        
+
         <el-form-item label="备注">
           <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
         </el-form-item>
       </el-form>
-      
+
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="dialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="handleSubmit" :loading="submitting">
-            确定
-          </el-button>
+          <el-button type="primary" @click="handleSubmit" :loading="submitting"> 确定 </el-button>
         </span>
       </template>
     </el-dialog>
@@ -309,7 +279,7 @@
             <span v-else class="no-image-text">暂无图标</span>
           </div>
         </div>
-        <div class="detail-item"> 
+        <div class="detail-item">
           <span class="detail-label">颜色:</span>
           <span class="detail-value">{{ detailData.color }}</span>
         </div>
@@ -354,19 +324,19 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, computed } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { HomeFilled, Plus } from '@element-plus/icons-vue'
-import { 
-  getEnabledNavigator, 
-  getNavigator, 
-  addNavigator, 
-  updateNavigator, 
+import { ref, reactive, onMounted, computed } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { HomeFilled, Plus } from '@element-plus/icons-vue';
+import {
+  getEnabledNavigator,
+  getNavigator,
+  addNavigator,
+  updateNavigator,
   delNavigator,
   type GameNavigatorVo,
   type GameNavigatorBo,
   listNavigator
-} from '@/api/system/common/nav/gameNavigator'
+} from '@/api/system/common/nav/gameNavigator';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { game_activity_type } = toRefs<any>(proxy?.useDict('game_activity_type'));
@@ -388,7 +358,7 @@ const columns = ref<FieldOption[]>([
   { key: 4, label: '颜色', visible: true },
   { key: 5, label: '链接类别', visible: true },
   { key: 6, label: '链接', visible: true },
-  { key: 7, label: '创建时间', visible: true },
+  { key: 7, label: '创建时间', visible: true }
 ]);
 
 // 菜单项接口
@@ -410,11 +380,13 @@ interface NavItem extends GameNavigatorVo {
 
 // 表单数据
 const navItems = ref<NavItem[]>([]);
-const formRef = ref()
-const form = reactive<GameNavigatorBo & {
-  createTime?: string;
-  updateTime?: string;
-}>({
+const formRef = ref();
+const form = reactive<
+  GameNavigatorBo & {
+    createTime?: string;
+    updateTime?: string;
+  }
+>({
   navId: undefined,
   name: '',
   pic: '',
@@ -427,7 +399,7 @@ const form = reactive<GameNavigatorBo & {
   status: 0,
   remark: '',
   createTime: '',
-  updateTime: '',
+  updateTime: ''
 });
 
 // 响应式数据
@@ -450,51 +422,41 @@ const queryParams = reactive({
 
 // 表单验证规则
 const rules = {
-  name: [
-    { required: true, message: '请输入菜单名称', trigger: 'blur' }
-  ],
-  pic: [
-    { required: true, message: '请上传功能图标', trigger: 'change' }
-  ],
-  activityType: [
-    { required: true, message: '请选择活动类型', trigger: 'change' }
-  ],
-  jumpType: [
-    { required: true, message: '请选择跳转方式', trigger: 'change' }
-  ],
-  sortNum: [
-    { required: true, message: '请输入排序', trigger: 'blur' }
-  ]
-}
+  name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
+  pic: [{ required: true, message: '请上传功能图标', trigger: 'change' }],
+  activityType: [{ required: true, message: '请选择活动类型', trigger: 'change' }],
+  jumpType: [{ required: true, message: '请选择跳转方式', trigger: 'change' }],
+  sortNum: [{ required: true, message: '请输入排序', trigger: 'blur' }]
+};
 
 // 添加跳转链接验证函数
 function validateJumpPath(rule: any, value: string, callback: Function) {
   if (value && !/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(value)) {
-    callback(new Error('请输入有效的URL'))
+    callback(new Error('请输入有效的URL'));
   } else {
-    callback()
+    callback();
   }
 }
 
 // 上传相关
 import { globalHeaders } from '@/utils/request';
 
-const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
-const headers = globalHeaders()
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
+const headers = globalHeaders();
 
 // 格式化日期
 function formatDate(date: string | Date | null | undefined): string {
-  if (!date) return '-'
-  const d = new Date(date)
-  if (isNaN(d.getTime())) return '-'
-  return d.toLocaleDateString('zh-CN')
+  if (!date) return '-';
+  const d = new Date(date);
+  if (isNaN(d.getTime())) return '-';
+  return d.toLocaleDateString('zh-CN');
 }
 
 // 格式化日期时间
 function formatDateTime(date: string | Date | null | undefined): string {
-  if (!date) return '-'
-  const d = new Date(date)
-  if (isNaN(d.getTime())) return '-'
+  if (!date) return '-';
+  const d = new Date(date);
+  if (isNaN(d.getTime())) return '-';
   return d.toLocaleString('zh-CN', {
     year: 'numeric',
     month: '2-digit',
@@ -502,12 +464,12 @@ function formatDateTime(date: string | Date | null | undefined): string {
     hour: '2-digit',
     minute: '2-digit',
     second: '2-digit'
-  })
+  });
 }
 
 // 获取跳转类型文本
 function getJumpTypeText(jumpType: number | null | undefined): string {
-  if (!jumpType) return '-'
+  if (!jumpType) return '-';
   const typeMap: Record<number, string> = {
     1: '跳转链接',
     2: '不跳转',
@@ -517,37 +479,37 @@ function getJumpTypeText(jumpType: number | null | undefined): string {
     6: '公众号文章',
     7: '公众号',
     8: '电话拨号'
-  }
-  return typeMap[jumpType] || '-'
+  };
+  return typeMap[jumpType] || '-';
 }
 
 // 获取最大排序值
 function getMaxSortNum(): number {
   if (navItems.value.length === 0) {
-    return 0
+    return 0;
   }
-  const maxSort = Math.max(...navItems.value.map(item => item.sortNum || 0))
-  return maxSort
+  const maxSort = Math.max(...navItems.value.map((item) => item.sortNum || 0));
+  return maxSort;
 }
 
 // 加载菜单数据
 async function loadNavData() {
-  loading.value = true
+  loading.value = true;
   try {
-    const response = await getEnabledNavigator()
+    const response = await getEnabledNavigator();
     if (response.code === 200 && response.data) {
-      navItems.value = response.data as unknown as NavItem[]
+      navItems.value = response.data as unknown as NavItem[];
     } else {
-      navItems.value = []
+      navItems.value = [];
     }
   } catch (error) {
-    console.error('加载数据失败:', error)
-    navItems.value = []
+    console.error('加载数据失败:', error);
+    navItems.value = [];
   } finally {
-    loading.value = false
+    loading.value = false;
     // 数据加载完成后,更新排序值
     if (!form.navId) {
-      form.sortNum = nextSortNum.value
+      form.sortNum = nextSortNum.value;
     }
   }
 }
@@ -571,7 +533,7 @@ function resetQuery() {
 
 /** 多选框选中数据 */
 function handleSelectionChange(selection: NavItem[]) {
-  selectedIds.value = selection.map(item => item.navId!);
+  selectedIds.value = selection.map((item) => item.navId!);
 }
 
 /** 排序变更 */
@@ -579,7 +541,7 @@ async function handleSortChange(row: NavItem) {
   try {
     const response = await updateNavigator({
       navId: row.navId,
-      sortNum: row.sortNum,
+      sortNum: row.sortNum
       // type: '4'
     });
     if (response.code === 200) {
@@ -628,7 +590,7 @@ function handleView(row: NavItem) {
 
 // 处理编辑
 function handleEdit(item: NavItem) {
-  reset()
+  reset();
   const formData = {
     navId: item.navId,
     name: item.name,
@@ -643,20 +605,20 @@ function handleEdit(item: NavItem) {
     remark: item.remark || '',
     createTime: item.createTime,
     updateTime: item.updateTime
-  }
-  Object.assign(form, formData)
-  colorInput.value = item.color || '#409EFF'
-  dialogVisible.value = true
-  dialogTitle.value = '修改菜单'
+  };
+  Object.assign(form, formData);
+  colorInput.value = item.color || '#409EFF';
+  dialogVisible.value = true;
+  dialogTitle.value = '修改菜单';
 }
 
 // 处理添加
 function handleAdd() {
-  reset()
+  reset();
   // 设置排序值为当前最大值+1
-  form.sortNum = nextSortNum.value
-  dialogVisible.value = true
-  dialogTitle.value = '添加菜单'
+  form.sortNum = nextSortNum.value;
+  dialogVisible.value = true;
+  dialogTitle.value = '添加菜单';
 }
 
 // 处理删除
@@ -666,18 +628,18 @@ async function handleDelete(item: NavItem) {
       confirmButtonText: '确定',
       cancelButtonText: '取消',
       type: 'warning'
-    })
-    
-    const response = await delNavigator(item.navId!)
+    });
+
+    const response = await delNavigator(item.navId!);
     if (response.code === 200) {
-      ElMessage.success('删除成功')
-      loadNavData()
+      ElMessage.success('删除成功');
+      loadNavData();
     } else {
-      ElMessage.error(response.msg || '删除失败')
+      ElMessage.error(response.msg || '删除失败');
     }
   } catch (error) {
     if (error !== 'cancel') {
-      ElMessage.error('删除失败')
+      ElMessage.error('删除失败');
     }
   }
 }
@@ -685,9 +647,9 @@ async function handleDelete(item: NavItem) {
 // 处理提交
 async function handleSubmit() {
   try {
-    await formRef.value.validate()
-    submitting.value = true
-    
+    await formRef.value.validate();
+    submitting.value = true;
+
     const payload: any = {
       navId: form.navId,
       name: form.name,
@@ -701,29 +663,29 @@ async function handleSubmit() {
       status: form.status,
       remark: form.remark,
       createTime: form.createTime,
-      updateTime: form.updateTime,
+      updateTime: form.updateTime
       // type: '4' // 菜单固定类型
-    }
-    
-    let response
+    };
+
+    let response;
     if (form.navId) {
-      response = await updateNavigator(payload)
+      response = await updateNavigator(payload);
     } else {
-      response = await addNavigator(payload)
+      response = await addNavigator(payload);
     }
-    
+
     if (response.code === 200) {
-      ElMessage.success(form.navId ? '修改成功' : '添加成功')
-      dialogVisible.value = false
-      loadNavData()
+      ElMessage.success(form.navId ? '修改成功' : '添加成功');
+      dialogVisible.value = false;
+      loadNavData();
     } else {
-      ElMessage.error(response.msg || '操作失败')
+      ElMessage.error(response.msg || '操作失败');
     }
   } catch (error) {
-    console.error('提交失败:', error)
-    ElMessage.error('操作失败')
+    console.error('提交失败:', error);
+    ElMessage.error('操作失败');
   } finally {
-    submitting.value = false
+    submitting.value = false;
   }
 }
 
@@ -743,101 +705,101 @@ function reset() {
     remark: '',
     createTime: '',
     updateTime: ''
-  })
-  colorInput.value = '#409EFF'
-  formRef.value?.clearValidate()
+  });
+  colorInput.value = '#409EFF';
+  formRef.value?.clearValidate();
 }
 
 // 跳转类型变更处理
 function handleJumpTypeChange() {
   if (form.jumpType === 2) {
-    form.jumpPath = '#'
+    form.jumpPath = '#';
   } else if (form.jumpType === 4) {
-    form.jumpPath = ''
+    form.jumpPath = '';
   }
 }
 
 // 图片上传前验证
 function beforeIconUpload(file: File) {
-  const isImage = file.type.startsWith('image/')
-  const isLt2M = file.size / 1024 / 1024 < 2
+  const isImage = file.type.startsWith('image/');
+  const isLt2M = file.size / 1024 / 1024 < 2;
 
   if (!isImage) {
-    ElMessage.error('只能上传图片文件!')
-    return false
+    ElMessage.error('只能上传图片文件!');
+    return false;
   }
   if (!isLt2M) {
-    ElMessage.error('图片大小不能超过 2MB!')
-    return false
+    ElMessage.error('图片大小不能超过 2MB!');
+    return false;
   }
-  return true
+  return true;
 }
 
 // 图片上传成功
 function handleIconSuccess(response: any) {
   if (response.code === 200) {
-    form.pic = response.data.url
-    ElMessage.success('图片上传成功')
+    form.pic = response.data.url;
+    ElMessage.success('图片上传成功');
   } else {
-    ElMessage.error(response.msg || '图片上传失败')
+    ElMessage.error(response.msg || '图片上传失败');
   }
 }
 
 // 颜色输入框的值
-const colorInput = ref('#409EFF')
+const colorInput = ref('#409EFF');
 
 // 将RGB颜色转换为16进制
 const rgbToHex = (rgb: string): string => {
   // 如果是16进制格式,直接返回
   if (rgb.startsWith('#')) {
-    return rgb
+    return rgb;
   }
-  
+
   // 如果是RGB格式,转换为16进制
-  const rgbMatch = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/)
+  const rgbMatch = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
   if (rgbMatch) {
-    const r = parseInt(rgbMatch[1])
-    const g = parseInt(rgbMatch[2])
-    const b = parseInt(rgbMatch[3])
-    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
+    const r = parseInt(rgbMatch[1]);
+    const g = parseInt(rgbMatch[2]);
+    const b = parseInt(rgbMatch[3]);
+    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
   }
-  
+
   // 如果无法解析,返回默认值
-  return '#409EFF'
-}
+  return '#409EFF';
+};
 
 // 处理颜色输入框变化
 const handleColorInput = (value: string) => {
   // 实时同步到颜色选择器
   if (value.startsWith('#')) {
-    form.color = value
+    form.color = value;
   }
-}
+};
 
 // 处理颜色选择器变化
 const handleColorChange = (value: string) => {
   // 当颜色选择器改变时,将16进制值填充到输入框
   if (value) {
-    const hexValue = rgbToHex(value)
-    colorInput.value = hexValue
+    const hexValue = rgbToHex(value);
+    colorInput.value = hexValue;
   }
-}
+};
 
 // 验证颜色输入框
 const validateColorInput = () => {
-  const hexRegex = /^#[0-9A-Fa-f]{6}$/
+  const hexRegex = /^#[0-9A-Fa-f]{6}$/;
   if (!hexRegex.test(colorInput.value)) {
-    ElMessage.warning('请输入正确的16进制颜色格式,如 #FF0000')
-    colorInput.value = form.color
+    ElMessage.warning('请输入正确的16进制颜色格式,如 #FF0000');
+    colorInput.value = form.color;
   }
-}
+};
 
 // 初始化数据
 onMounted(() => {
-  loadNavData()
+  loadNavData();
   // 初始化排序值
-  form.sortNum = nextSortNum.value
-})
+  form.sortNum = nextSortNum.value;
+});
 </script>
 
 <style scoped>
@@ -1020,4 +982,4 @@ onMounted(() => {
   color: #909399;
   margin-top: 8px;
 }
-</style> 
+</style>

+ 168 - 423
src/views/system/gameEvent/edit.vue

@@ -129,119 +129,94 @@
           <div class="menu-config">
             <div class="menu-header">
               <h3>赛事菜单</h3>
-              <el-button type="primary" @click="addMenuItem" icon="Plus">添加菜单项</el-button>
+              <el-button type="primary" @click="showMenuSelectDialog" icon="Plus">添加菜单项</el-button>
             </div>
 
             <el-table :data="menuItems" border style="width: 100%">
-              <el-table-column label="菜单名称" prop="menuName">
+              <el-table-column label="菜单名称" prop="name" width="200">
                 <template #default="scope">
-                  <el-input v-model="scope.row.menuName" placeholder="请输入菜单名称" />
+                  <span>{{ scope.row.name }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="活动类型" prop="activityStatus">
+              <el-table-column label="图标" prop="pic" width="80">
                 <template #default="scope">
-                  <el-radio-group v-model="scope.row.activityStatus">
-                    <el-radio :value="0">赛前</el-radio>
-                    <el-radio :value="1">赛中</el-radio>
-                    <el-radio :value="2">赛后</el-radio>
-                  </el-radio-group>
+                  <el-image v-if="scope.row.pic" :src="scope.row.pic" style="width: 40px; height: 40px" fit="cover" />
+                  <span v-else>-</span>
                 </template>
               </el-table-column>
-              <el-table-column label="图标" prop="icon">
+              <el-table-column label="跳转类型" prop="jumpType" width="100">
                 <template #default="scope">
-                  <el-input v-model="scope.row.icon" placeholder="请输入图标" />
+                  <span>{{ getJumpTypeLabel(scope.row.jumpType) }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="颜色" prop="color">
+              <el-table-column label="颜色" prop="color" width="100">
                 <template #default="scope">
-                  <el-input v-model="scope.row.color" placeholder="请输入颜色" />
+                  <div v-if="scope.row.color" :style="{ backgroundColor: scope.row.color, width: '30px', height: '20px', borderRadius: '4px' }"></div>
                 </template>
               </el-table-column>
-              <el-table-column label="是否站外" prop="isFrame">
+              <el-table-column label="跳转路径" prop="jumpPath">
                 <template #default="scope">
-                  <el-radio-group v-model="scope.row.isFrame">
-                    <el-radio :value="1">站内</el-radio>
-                    <el-radio :value="0">站外</el-radio>
-                  </el-radio-group>
+                  <span>{{ scope.row.jumpPath }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="链接" prop="siteLink">
+              <el-table-column label="排序" prop="sortNum" width="80">
                 <template #default="scope">
-                  <el-input v-model="scope.row.siteLink" placeholder="请输入链接" />
+                  <span>{{ scope.row.sortNum }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="排序" prop="orderNum">
+              <el-table-column label="活动类型" prop="activityType" width="100">
                 <template #default="scope">
-                  <el-input-number v-model="scope.row.orderNum" :min="1" :max="999" />
+                  <span>{{ scope.row.activityType === 0 ? '赛前' : scope.row.activityType === 1 ? '赛中' : '赛后' }}</span>
                 </template>
               </el-table-column>
-              <el-table-column label="操作">
+              <el-table-column label="操作" width="100">
                 <template #default="scope">
                   <el-button type="danger" size="small" @click="removeMenuItem(scope.$index)" icon="Delete">删除 </el-button>
                 </template>
               </el-table-column>
             </el-table>
           </div>
-        </el-tab-pane>
 
-        <!-- 赛事项目标签页 -->
-        <el-tab-pane label="赛事项目" name="projects">
-          <div class="project-config">
-            <div class="project-header">
-              <h3>赛事项目配置</h3>
-              <el-button type="primary" @click="addProjectItem" icon="Plus">添加项目</el-button>
+          <!-- 菜单选择弹框 -->
+          <el-dialog v-model="menuSelectDialogVisible" title="选择菜单" width="60%" :close-on-click-modal="false">
+            <div class="menu-select-content">
+              <div class="search-bar" style="margin-bottom: 16px">
+                <el-input v-model="menuSearchKeyword" placeholder="搜索菜单名称" clearable style="width: 300px" />
+              </div>
+
+              <el-table :data="filteredAvailableMenus" border style="width: 100%" @selection-change="handleMenuSelectionChange" max-height="400">
+                <el-table-column type="selection" width="55" />
+                <el-table-column label="菜单名称" prop="name" width="200" />
+                <el-table-column label="图标" prop="pic" width="80">
+                  <template #default="scope">
+                    <el-image v-if="scope.row.pic" :src="scope.row.pic" style="width: 40px; height: 40px" fit="cover" />
+                  </template>
+                </el-table-column>
+                <el-table-column label="颜色" prop="color" width="100">
+                  <template #default="scope">
+                    <div
+                      v-if="scope.row.color"
+                      :style="{ backgroundColor: scope.row.color, width: '30px', height: '20px', borderRadius: '4px' }"
+                    ></div>
+                  </template>
+                </el-table-column>
+                <el-table-column label="跳转类型" prop="jumpType" width="120">
+                  <template #default="scope">
+                    <span>{{ getJumpTypeLabel(scope.row.jumpType) }}</span>
+                  </template>
+                </el-table-column>
+                <el-table-column label="跳转路径" prop="jumpPath" />
+                <el-table-column label="排序" prop="sortNum" width="80" />
+              </el-table>
             </div>
 
-            <el-table :data="projectItems" border style="width: 100%">
-              <el-table-column label="項目类型" prop="projectType">
-                <template #default="scope">
-                  <el-select v-model="scope.row.projectType" placeholder="请选择項目类型" style="width: 100%">
-                    <el-option v-for="dict in game_project_type" :key="dict.value" :label="dict.label" :value="dict.value" />
-                  </el-select>
-                </template>
-              </el-table-column>
-              <el-table-column label="项目名称" prop="projectName">
-                <template #default="scope">
-                  <el-input v-model="scope.row.projectName" placeholder="请输入项目名称" />
-                </template>
-              </el-table-column>
-              <el-table-column label="项目组别" prop="groupType">
-                <template #default="scope">
-                  <el-select v-model="scope.row.groupType" placeholder="请选择项目组别" style="width: 100%">
-                    <el-option v-for="group in groupItems" :key="group.groupId" :label="group.groupName" :value="group.groupName" />
-                  </el-select>
-                </template>
-              </el-table-column>
-              <el-table-column label="排序" prop="orderType">
-                <template #default="scope">
-                  <el-radio-group v-model="scope.row.orderType">
-                    <el-radio value="0">降序</el-radio>
-                    <el-radio value="1">升序</el-radio>
-                  </el-radio-group>
-                </template>
-              </el-table-column>
-              <el-table-column label="比赛地点" prop="location">
-                <template #default="scope">
-                  <el-input v-model="scope.row.location" placeholder="请输入比赛地点" />
-                </template>
-              </el-table-column>
-              <el-table-column label="录取名次" prop="roundType">
-                <template #default="scope">
-                  <el-input v-model="scope.row.roundType" placeholder="请输入录取名次" />
-                </template>
-              </el-table-column>
-              <el-table-column label="分数" prop="scoreValue">
-                <template #default="scope">
-                  <el-input v-model="scope.row.scoreValue" placeholder="请输入分数" />
-                </template>
-              </el-table-column>
-              <el-table-column label="操作">
-                <template #default="scope">
-                  <el-button type="danger" size="small" @click="removeProjectItem(scope.$index)" icon="Delete">移除 </el-button>
-                </template>
-              </el-table-column>
-            </el-table>
-          </div>
+            <template #footer>
+              <span class="dialog-footer">
+                <el-button @click="menuSelectDialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="confirmAddMenuItems">确认添加</el-button>
+              </span>
+            </template>
+          </el-dialog>
         </el-tab-pane>
 
         <!-- 配置信息标签页 -->
@@ -288,71 +263,6 @@
             </el-table>
           </div>
         </el-tab-pane>
-
-        <!-- 参赛队伍标签页 -->
-        <el-tab-pane label="参赛队伍" name="teams">
-          <div class="team-config">
-            <div class="team-header">
-              <h3>参赛队伍</h3>
-              <el-button type="primary" @click="addTeamItem" icon="Plus">新增队伍</el-button>
-            </div>
-
-            <el-table :data="teamItems" border style="width: 100%">
-              <el-table-column label="单位名称" prop="teamName">
-                <template #default="scope">
-                  <el-input v-model="scope.row.teamName" placeholder="请输入单位名称" />
-                </template>
-              </el-table-column>
-              <el-table-column label="编号" prop="teamCode">
-                <template #default="scope">
-                  <el-input v-model="scope.row.teamCode" placeholder="请输入编号" />
-                </template>
-              </el-table-column>
-              <el-table-column label="团队描述" prop="teamDescribe">
-                <template #default="scope">
-                  <el-input v-model="scope.row.teamDescribe" placeholder="请描述一下你的团队吧~" />
-                </template>
-              </el-table-column>
-              <el-table-column label="操作">
-                <template #default="scope">
-                  <el-button type="danger" size="small" @click="removeTeamItem(scope.$index)" icon="Delete">删除 </el-button>
-                </template>
-              </el-table-column>
-            </el-table>
-          </div>
-        </el-tab-pane>
-
-        <!-- 赛事分组标签页 -->
-        <el-tab-pane label="赛事分组" name="groups">
-          <div class="group-config">
-            <div class="group-header">
-              <h3>赛事分组</h3>
-              <el-button type="primary" @click="addGroupItem" icon="Plus">添加分组</el-button>
-            </div>
-
-            <el-table :data="groupItems" border style="width: 100%">
-              <el-table-column label="分组名称" prop="groupName">
-                <template #default="scope">
-                  <el-input v-model="scope.row.groupName" placeholder="请输入分组名称" />
-                </template>
-              </el-table-column>
-              <el-table-column label="成员性别" prop="memberGender">
-                <template #default="scope">
-                  <el-radio-group v-model="scope.row.memberGender">
-                    <el-radio value="0">不分男女</el-radio>
-                    <el-radio value="1">男</el-radio>
-                    <el-radio value="2">女</el-radio>
-                  </el-radio-group>
-                </template>
-              </el-table-column>
-              <el-table-column label="操作">
-                <template #default="scope">
-                  <el-button type="danger" size="small" @click="removeGroupItem(scope.$index)" icon="Delete">删除 </el-button>
-                </template>
-              </el-table-column>
-            </el-table>
-          </div>
-        </el-tab-pane>
       </el-tabs>
 
       <!-- 底部操作按钮 -->
@@ -368,18 +278,11 @@
 import { getGameEvent, addGameEvent, updateGameEvent, changeEventDefault } from '@/api/system/gameEvent';
 import { GameEventVO, GameEventForm } from '@/api/system/gameEvent/types';
 import { listGameEventConfig, addGameEventConfig, updateGameEventConfig, delGameEventConfig } from '@/api/system/gameEventConfig';
-import { GameEventConfigVO, GameEventConfigForm } from '@/api/system/gameEventConfig/types';
+import { GameEventConfigForm } from '@/api/system/gameEventConfig/types';
 import { listGameEventConfigType } from '@/api/system/gameEventConfigType'; // 添加导入
 import { GameEventConfigTypeVO } from '@/api/system/gameEventConfigType/types'; // 添加导入
 import { useRoute, useRouter } from 'vue-router';
-import { listGameEventMenu, addGameEventMenu, updateGameEventMenu, delGameEventMenu } from '@/api/system/gameEventMenu';
-import { GameEventMenuVO, GameEventMenuForm } from '@/api/system/gameEventMenu/types';
-import { listGameTeam, addGameTeam, updateGameTeam, delGameTeam } from '@/api/system/gameTeam';
-import { GameTeamVO, GameTeamForm } from '@/api/system/gameTeam/types';
-import { listGameEventGroup, addGameEventGroup, updateGameEventGroup, delGameEventGroup } from '@/api/system/gameEventGroup';
-import { GameEventGroupForm, GameEventGroupVO } from '@/api/system/gameEventGroup/types';
-import { addGameEventProject, listGameEventProject, updateGameEventProject, delGameEventProject } from '@/api/system/gameEventProject';
-import { GameEventProjectForm, GameEventProjectVO } from '@/api/system/gameEventProject/types';
+import { listRelateMenu, getEnabledNavigator, editRelate, type EditRelateMenuParams, GameNavigatorVo } from '@/api/system/common/nav/gameNavigator';
 
 const route = useRoute();
 const router = useRouter();
@@ -436,11 +339,14 @@ const basicRules = {
   endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
 };
 
+const currentEventId = ref<number>();
+
 // 初始化页面
 onMounted(async () => {
   const eventId = route.params.id as string;
   if (eventId) {
     isEdit.value = true;
+    currentEventId.value = Number(eventId);
     await loadEventData(eventId);
   } else {
     // 新增模式,加载图片配置模板
@@ -448,7 +354,6 @@ onMounted(async () => {
   }
   // 加载配置类型选项
   await loadConfigTypes();
-  // console.log('Initial image configs:', imageConfigItems.value); // 添加调试信息
 });
 
 // 加载赛事数据
@@ -460,22 +365,16 @@ const loadEventData = async (eventId: string | number) => {
     // 填充基本信息
     Object.assign(basicForm.value, data);
 
-    // 获取有效的赛事ID
-    const effectiveEventId = getEffectiveEventId(eventId);
-
     // 加载图片配置数据
     try {
-      await loadImageConfigData(effectiveEventId);
+      await loadImageConfigData(eventId);
     } catch (error) {
       console.warn('加载图片配置数据失败,继续加载其他数据:', error);
       // 不中断其他数据的加载
     }
 
-    await loadMenuData(effectiveEventId); // 加载菜单数据
-    await loadGroupData(effectiveEventId); // 加载分组数据
-    await loadProjectData(effectiveEventId); // 加载项目数据
-    await loadTeamData(effectiveEventId); // 加载队伍数据
-    await loadConfigData(effectiveEventId); // 加载配置数据
+    await loadMenuData(eventId); // 加载菜单数据
+    await loadConfigData(eventId); // 加载配置数据
   } catch (error) {
     proxy?.$modal.msgError('加载赛事数据失败');
   }
@@ -495,123 +394,125 @@ const handleStatusChange = async (row: GameEventVO) => {
     row.isDefault = row.isDefault === '0' ? '1' : '0';
   }
 };
+// 菜单列表数据
+const menuItems = ref<GameNavigatorVo[]>([]);
+
+// 菜单选择弹框相关数据
+const menuSelectDialogVisible = ref(false);
+const availableMenus = ref<any[]>([]);
+const selectedMenus = ref<any[]>([]);
+const menuSearchKeyword = ref('');
+
+// 过滤可用菜单的计算属性
+const filteredAvailableMenus = computed(() => {
+  if (!menuSearchKeyword.value) {
+    return availableMenus.value;
+  }
+  return availableMenus.value.filter((menu) => menu.name?.toLowerCase().includes(menuSearchKeyword.value.toLowerCase()));
+});
 
-// 赛事分组数据
-const groupItems = ref<GameEventGroupForm[]>([]);
-// 加载项目分组数据
-const loadGroupData = async (eventId: string | number) => {
+// 加载菜单数据
+const loadMenuData = async (eventId: string | number) => {
   try {
-    const res = await listGameEventGroup({
-      eventId: eventId === '' ? '' : eventId,
+    const res = await listRelateMenu({
+      eventId: eventId,
       pageNum: 1,
       pageSize: 1000,
       orderByColumn: '',
       isAsc: ''
     });
-    groupItems.value = Array.isArray(res.rows) ? res.rows : [];
+    menuItems.value = Array.isArray(res.rows) ? res.rows : [];
   } catch (error) {
-    proxy?.$modal.msgError('加载项目分组数据失败');
-  }
-};
-// 添加赛事分组项
-const addGroupItem = () => {
-  groupItems.value.push({
-    groupId: '',
-    eventId: getEffectiveEventId(),
-    groupName: '',
-    memberGender: ''
-  });
-};
-// 删除赛事分组项
-const removeGroupItem = (index: number) => {
-  const groupItem = groupItems.value[index];
-  if (groupItem.groupId) {
-    delGameEventGroup(groupItem.groupId).then(() => {
-      groupItems.value.splice(index, 1);
-    });
-  } else {
-    groupItems.value.splice(index, 1);
+    proxy?.$modal.msgError('加载菜单数据失败');
   }
 };
-// 保存赛事分组数据
-const saveGroupData = async (eventId: string | number) => {
+
+// 显示菜单选择弹框
+const showMenuSelectDialog = async () => {
   try {
-    for (const item of groupItems.value) {
-      // 确保eventId被正确设置
-      const groupData = { ...item, eventId };
-
-      if (item.groupId) {
-        // 更新现有分组
-        await updateGameEventGroup(groupData);
-      } else if (item.groupName) {
-        // 只有有名称的新分组才保存
-        await addGameEventGroup(groupData);
-      }
-    }
+    // 加载可用的导航菜单
+    const res = await getEnabledNavigator();
+    availableMenus.value = Array.isArray(res.data) ? res.data : [];
+    selectedMenus.value = [];
+    menuSearchKeyword.value = '';
+    menuSelectDialogVisible.value = true;
   } catch (error) {
-    console.error('保存赛事分组数据失败:', error);
-    throw new Error('保存赛事分组失败');
+    proxy?.$modal.msgError('加载可用菜单失败');
   }
 };
 
-// 菜单列表数据
-const menuItems = ref<GameEventMenuForm[]>([]);
-// 加载菜单数据
-const loadMenuData = async (eventId: string | number) => {
-  try {
-    const res = await listGameEventMenu({
-      eventId: eventId === '' ? '' : eventId,
-      pageNum: 1,
-      pageSize: 10,
-      orderByColumn: undefined,
-      isAsc: undefined,
-    });
-    menuItems.value = Array.isArray(res.rows) ? res.rows : [];
-  } catch (error) {
-    proxy?.$modal.msgError('加载菜单数据失败');
-  }
+// 处理菜单选择变化
+const handleMenuSelectionChange = (selection: any[]) => {
+  selectedMenus.value = selection;
 };
-// 添加菜单项
-const addMenuItem = () => {
-  menuItems.value.push({
-    menuId: '',
-    parentId: 0,
-    menuName: '',
-    activityStatus: 0,
-    icon: '',
-    color: '',
-    isFrame: 1,
-    siteLink: '',
-    orderNum: menuItems.value.length + 1,
-    eventId: getEffectiveEventId() // 使用有效的赛事ID
+
+// 确认添加菜单项
+const confirmAddMenuItems = () => {
+  console.log('selectedMenus:', selectedMenus.value);
+  if (selectedMenus.value.length === 0) {
+    proxy?.$modal.msgWarning('请选择要添加的菜单');
+    return;
+  }
+
+  // 将选中的菜单添加到菜单列表中
+  selectedMenus.value.forEach((menu) => {
+    // 检查是否已存在相同的菜单(根据menu的navId和当前menuItems的icon字段比较)
+    const exists = menuItems.value.some((item) => item.navId === Number(menu.navId));
+    if (!exists) {
+      menuItems.value.push(menu);
+    }
   });
+  console.log('menuItems:', menuItems.value);
+  menuSelectDialogVisible.value = false;
+  proxy?.$modal.msgSuccess(`成功添加 ${selectedMenus.value.length} 个菜单项`);
+};
+
+// 获取跳转类型标签
+const getJumpTypeLabel = (jumpType: number) => {
+  const typeMap = {
+    1: '跳转链接',
+    2: '不跳转',
+    3: '小程序内链',
+    4: '小程序外链',
+    5: 'H5外链',
+    6: '公众号文章',
+    7: '公众号',
+    8: '电话拨号'
+  };
+  return typeMap[jumpType as keyof typeof typeMap] || '未知类型';
 };
+
 // 删除菜单项
-const removeMenuItem = (index: number) => {
-  const menuItem = menuItems.value[index];
-  if (menuItem.menuId) {
-    delGameEventMenu(menuItem.menuId).then(() => {
-      menuItems.value.splice(index, 1);
-    });
-  } else {
+const removeMenuItem = async (index: number) => {
+  try {
+    // 从本地数组中删除元素
     menuItems.value.splice(index, 1);
+
+    // 获取当前赛事ID
+    const eventId = (route.params.id as string) || '';
+
+    // 调用saveMenuData方法同步到后端
+    await saveMenuData(eventId);
+
+    proxy?.$modal.msgSuccess('删除菜单项成功');
+  } catch (error) {
+    console.error('删除菜单项失败:', error);
+    proxy?.$modal.msgError('删除菜单项失败');
   }
 };
+
 // 保存菜单数据
 const saveMenuData = async (eventId: string | number) => {
   try {
-    for (const item of menuItems.value) {
-      // 确保eventId被正确设置
-      const menuData = { ...item, eventId };
-
-      if (item.menuId) {
-        // 更新现有菜单
-        await updateGameEventMenu(menuData);
-      } else if (item.menuName) {
-        // 只有有名称的新菜单才保存
-        await addGameEventMenu(menuData);
-      }
-    }
+    // 提取所有菜单的navId
+    const menus = menuItems.value.map((item) => Number(item.navId)); // 转换为数字类型
+    // 调用editRelate接口保存赛事关联菜单
+    const relateData: EditRelateMenuParams = {
+      eventId: eventId,
+      menus: menus
+    };
+
+    await editRelate(relateData);
   } catch (error) {
     console.error('保存菜单数据失败:', error);
     throw new Error('保存菜单失败');
@@ -675,35 +576,6 @@ const refreshImageConfigs = async () => {
     // await loadImageConfigTemplates();
   }
 };
-// 加载图片配置模板(用于新增模式)
-// const loadImageConfigTemplates = async () => {
-//   try {
-//     const res = await listGameEventConfig({
-//       eventId: '',
-//       configType: 'IMAGE',
-//       pageNum: 1,
-//       pageSize: 100
-//     });
-
-//     // console.log('Template response (new mode):', res);
-
-//     const imageData = Array.isArray(res.rows) ? res.rows : []; // 修改此处,确保正确访问数组数据
-//     // console.log('Image data (new mode):', imageData);
-
-//     imageConfigItems.value = imageData.map(item => ({
-//       ...item,
-//       configValue: item.configValue || '', // 确保 configValue 初始化正确
-//       isEnabled: item.isEnabled || '0', // 确保 isEnabled 初始化正确
-//       status: item.status || '0' // 确保 status 初始化正确
-//     }));
-
-//     // console.log('Image config items (new mode):', imageConfigItems.value);
-//   } catch (error) {
-//     console.error('加载图片配置模板失败:', error);
-//     proxy?.$modal.msgError('加载图片配置模板失败: ' + (error as Error).message);
-//     imageConfigItems.value = [];
-//   }
-// };
 // 保存图片配置数据
 const saveImageConfigData = async (eventId?: string | number) => {
   try {
@@ -749,68 +621,6 @@ const handleTabClick = () => {
   // 可以在这里添加标签页切换的逻辑
 };
 
-// 项目数据
-const projectItems = ref<GameEventProjectForm[]>([]);
-// 加载项目数据
-const loadProjectData = async (eventId: string | number) => {
-  try {
-    const res = await listGameEventProject({
-      eventId: eventId === '' ? '' : eventId,
-      pageNum: 1,
-      pageSize: 1000,
-      orderByColumn: '',
-      isAsc: ''
-    });
-    projectItems.value = Array.isArray(res.rows) ? res.rows : [];
-  } catch (error) {
-    proxy?.$modal.msgError('加载项目数据失败');
-  }
-};
-// 添加项目项
-const addProjectItem = () => {
-  projectItems.value.push({
-    eventId: getEffectiveEventId(),
-    projectId: '',
-    projectName: '',
-    projectType: '',
-    remark: '',
-    location: '',
-    scoreValue: '',
-    groupType: ''
-  });
-};
-// 删除项目项
-const removeProjectItem = (index: number) => {
-  const projectItem = projectItems.value[index];
-  if (projectItem.projectId) {
-    delGameEventProject(projectItem.projectId).then(() => {
-      projectItems.value.splice(index, 1);
-    });
-  } else {
-    projectItems.value.splice(index, 1);
-  }
-};
-//保存项目数据
-const saveProjectData = async (eventId: string | number) => {
-  try {
-    for (const item of projectItems.value) {
-      // 确保eventId被正确设置
-      const projectData = { ...item, eventId };
-
-      if (item.projectId) {
-        // 更新现有队伍
-        await updateGameEventProject(projectData);
-      } else if (item.projectName) {
-        // 只有有名称的新队伍才保存
-        await addGameEventProject(projectData);
-      }
-    }
-  } catch (error) {
-    console.error('保存赛事项目数据失败:', error);
-    throw new Error('保存赛事项目失败');
-  }
-};
-
 // 配置信息数据
 const configItems = ref<GameEventConfigForm[]>([]);
 // 配置类型选项
@@ -891,65 +701,6 @@ const saveconfigData = async (eventId: string | number) => {
   }
 };
 
-// 参赛队伍数据
-const teamItems = ref<GameTeamForm[]>([]);
-// 加载队伍数据
-const loadTeamData = async (eventId: string | number) => {
-  try {
-    const res = await listGameTeam({
-      eventId: eventId === '' ? '' : eventId,
-      pageNum: 1,
-      pageSize: 1000,
-      orderByColumn: '',
-      isAsc: ''
-    });
-    teamItems.value = Array.isArray(res.rows) ? res.rows : [];
-  } catch (error) {
-    proxy?.$modal.msgError('加载队伍数据失败');
-  }
-};
-// 添加参赛队伍项
-const addTeamItem = () => {
-  teamItems.value.push({
-    teamName: '',
-    teamCode: '',
-    eventId: getEffectiveEventId(),
-    teamDescribe: '',
-    remark: ''
-  });
-};
-// 删除参赛队伍项
-const removeTeamItem = (index: number) => {
-  const teamItem = teamItems.value[index];
-  if (teamItem.teamId) {
-    delGameTeam(teamItem.teamId).then(() => {
-      teamItems.value.splice(index, 1);
-    });
-  } else {
-    teamItems.value.splice(index, 1);
-  }
-};
-// 保存参赛队伍数据
-const saveTeamData = async (eventId: string | number) => {
-  try {
-    for (const item of teamItems.value) {
-      // 确保eventId被正确设置
-      const teamData = { ...item, eventId };
-
-      if (item.teamId) {
-        // 更新现有队伍
-        await updateGameTeam(teamData);
-      } else if (item.teamName) {
-        // 只有有名称的新队伍才保存
-        await addGameTeam(teamData);
-      }
-    }
-  } catch (error) {
-    console.error('保存参赛队伍数据失败:', error);
-    throw new Error('保存参赛队伍失败');
-  }
-};
-
 // 保存赛事信息
 const saveEvent = async () => {
   try {
@@ -974,30 +725,24 @@ const saveEvent = async () => {
     }
 
     // 保存基本信息
-    if (isEdit.value) {
-      await updateGameEvent(formData);
-      savedEventId = route.params.id as string;
-    } else {
+    if (!isEdit.value) {
       const addRes = await addGameEvent(formData);
       // 假设返回的数据中包含新创建的赛事ID
       savedEventId = addRes?.data as string;
+    } else {
+      savedEventId = route.params.id as string;
     }
-
-    // 获取有效的赛事ID用于保存相关数据
-    const effectiveEventId = getEffectiveEventId(savedEventId);
-
     // 保存图片配置数据
-    await saveImageConfigData(effectiveEventId);
+    await saveImageConfigData(savedEventId);
     // 保存赛事配置项数据
-    await saveconfigData(effectiveEventId);
+    await saveconfigData(savedEventId);
     // 保存菜单数据
-    await saveMenuData(effectiveEventId);
-    // 保存参赛队伍数据
-    await saveTeamData(effectiveEventId);
-    // 保存赛事项目分组数据
-    await saveGroupData(effectiveEventId);
-    // 保存赛事项目数据
-    await saveProjectData(effectiveEventId);
+    await saveMenuData(savedEventId);
+
+    // 保存基本信息
+    if (isEdit.value) {
+      await updateGameEvent(formData);
+    }
 
     proxy?.$modal.msgSuccess('保存成功');
 

+ 315 - 2
src/views/system/gameEvent/index.vue

@@ -180,6 +180,16 @@
                 v-hasPermi="['system:gameEvent:gameData']"
               ></el-button>
             </el-tooltip>
+            <!-- 编写文章按钮 -->
+            <el-tooltip content="编写文章" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="EditPen"
+                @click="handleWriteArticle(scope.row)"
+                v-hasPermi="['system:gameEvent:writeArticle']"
+              ></el-button>
+            </el-tooltip>
           </template>
         </el-table-column>
       </el-table>
@@ -192,6 +202,122 @@
     <el-dialog :title="`赛事 ${currentEventId} 排行榜`" v-model="rankingBoardVisible" width="800px" append-to-body>
       <RankingBoard :eventId="currentEventId" v-if="rankingBoardVisible" />
     </el-dialog>
+    <!-- 文章编写对话框 -->
+    <el-dialog v-model="articleDialog.visible" :title="articleDialog.title" width="1200px" append-to-body>
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="竞赛流程" name="competition-process">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.competitionProcess.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.competitionProcess.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.competitionProcess.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="竞赛项目" name="competition-items">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.competitionItems.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.competitionItems.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.competitionItems.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="活动议程" name="activity-agenda">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.activityAgenda.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.activityAgenda.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.activityAgenda.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="项目介绍" name="project-introduction">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.projectIntroduction.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.projectIntroduction.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.projectIntroduction.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="竞赛流程" name="competition-flow">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.competitionFlow.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.competitionFlow.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.competitionFlow.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="赛事分组" name="event-grouping">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.eventGrouping.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.eventGrouping.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.eventGrouping.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="运动员号码簿" name="athlete-handbook">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.athleteHandbook.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.athleteHandbook.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.athleteHandbook.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="项目场地" name="project-venue">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.projectVenue.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.projectVenue.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.projectVenue.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleCloseArticleDialog">取 消</el-button>
+          <el-button type="primary" @click="handleSaveArticle">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
     <!-- 用户导入对话框 -->
     <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
       <el-upload
@@ -233,10 +359,13 @@
 <script setup name="GameEvent" lang="ts">
 import { listGameEvent, changeEventDefault, delGameEvent, addGameEvent, updateGameEvent } from '@/api/system/gameEvent';
 import { GameEventVO, GameEventQuery, GameEventForm } from '@/api/system/gameEvent/types';
+import { getEventMdByEventAndType, editEventMd } from '@/api/system/eventMd';
+import { EventMdVO, EventMdForm } from '@/api/system/eventMd/types';
 import { useRouter } from 'vue-router';
 import { ref } from 'vue';
 import RefereeForm from '@/views/system/gameEvent/RefereeForm.vue';
 import RankingBoard from './RankingBoard.vue';
+import Editor from '@/components/Editor/index.vue';
 import { useTagsViewStore } from '@/store/modules/tagsView';
 import { globalHeaders } from '@/utils/request';
 
@@ -301,9 +430,9 @@ const upload = reactive<ImportOption>({
   // 是否更新已经存在的用户数据
   updateSupport: 0,
   // 设置上传的请求头部
-  headers: globalHeaders()
+  headers: globalHeaders(),
   // 上传的地址
-  // url: import.meta.env.VITE_APP_BASE_API + '/system/enroll/importData'
+  url: import.meta.env.VITE_APP_BASE_API + '/system/enroll/importData'
 });
 
 const initFormData: GameEventForm = {
@@ -567,6 +696,38 @@ const handleGameData = (row: GameEventVO) => {
 const rankingBoardVisible = ref(false);
 const currentEventId = ref<string | undefined>();
 
+// 文章编写相关数据
+const articleDialog = reactive({
+  visible: false,
+  title: '',
+  currentEventId: undefined as string | number | undefined
+});
+
+const activeTab = ref('competition-process');
+
+// 标签页与类型值的映射关系
+const tabTypeMapping: Record<string, number> = {
+  'competition-process': 1, // 竞赛流程
+  'competition-items': 2,   // 竞赛项目
+  'activity-agenda': 3,     // 活动议程
+  'project-introduction': 4, // 项目介绍
+  'competition-flow': 5,    // 竞赛流程
+  'event-grouping': 6,      // 赛事分组
+  'athlete-handbook': 7,    // 运动员号码簿
+  'project-venue': 8        // 项目场地
+};
+
+const articleData = reactive({
+  competitionProcess: { id: undefined, title: '', content: '', remark: '' },
+  competitionItems: { id: undefined, title: '', content: '', remark: '' },
+  activityAgenda: { id: undefined, title: '', content: '', remark: '' },
+  projectIntroduction: { id: undefined, title: '', content: '', remark: '' },
+  competitionFlow: { id: undefined, title: '', content: '', remark: '' },
+  eventGrouping: { id: undefined, title: '', content: '', remark: '' },
+  athleteHandbook: { id: undefined, title: '', content: '', remark: '' },
+  projectVenue: { id: undefined, title: '', content: '', remark: '' }
+});
+
 // 打开排行榜组件并传递赛事ID
 const openRankingBoard = (eventId: string) => {
   currentEventId.value = eventId;
@@ -591,6 +752,135 @@ const handleStatusChange = async (row: GameEventVO) => {
   }
 };
 
+/** 编写文章按钮操作 */
+const handleWriteArticle = async (row: GameEventVO) => {
+  articleDialog.title = `编写文章 - ${row.eventName}`;
+  articleDialog.currentEventId = row.eventId;
+  articleDialog.visible = true;
+  activeTab.value = 'competition-process';
+  
+  // 加载默认标签页(竞赛流程)的数据
+  await loadTabData('competition-process');
+};
+
+/** 加载指定标签页的数据 */
+const loadTabData = async (tabName: string) => {
+  const type = tabTypeMapping[tabName];
+  
+  if (articleDialog.currentEventId && type) {
+    try {
+      const response = await getEventMdByEventAndType(articleDialog.currentEventId, type);
+      const eventMd = response.data;
+      
+      const dataKey = getDataKeyByTabName(tabName);
+      if (dataKey && articleData[dataKey]) {
+        if (eventMd) {
+          articleData[dataKey].id = eventMd.id;
+          articleData[dataKey].title = eventMd.title || '';
+          articleData[dataKey].content = eventMd.content || '';
+          articleData[dataKey].remark = eventMd.remark || '';
+        } else {
+          articleData[dataKey].id = undefined;
+          articleData[dataKey].title = '';
+          articleData[dataKey].content = '';
+          articleData[dataKey].remark = '';
+        }
+      }
+    } catch (error) {
+      console.log('查询文章数据失败:', error);
+      const dataKey = getDataKeyByTabName(tabName);
+      if (dataKey && articleData[dataKey]) {
+        articleData[dataKey].id = undefined;
+        articleData[dataKey].title = '';
+        articleData[dataKey].content = '';
+        articleData[dataKey].remark = '';
+      }
+    }
+  }
+};
+
+/** 标签页点击事件 */
+const handleTabClick = async (tab: any) => {
+  const tabName = tab.props.name;
+  await loadTabData(tabName);
+};
+
+/** 根据标签页名称获取数据键名 */
+const getDataKeyByTabName = (tabName: string): keyof typeof articleData | null => {
+  const mapping: Record<string, keyof typeof articleData> = {
+    'competition-process': 'competitionProcess',
+    'competition-items': 'competitionItems',
+    'activity-agenda': 'activityAgenda',
+    'project-introduction': 'projectIntroduction',
+    'competition-flow': 'competitionFlow',
+    'event-grouping': 'eventGrouping',
+    'athlete-handbook': 'athleteHandbook',
+    'project-venue': 'projectVenue'
+  };
+  return mapping[tabName] || null;
+};
+
+/** 关闭文章编写对话框 */
+const handleCloseArticleDialog = () => {
+  // 清空所有文章数据
+  Object.keys(articleData).forEach(key => {
+    const dataKey = key as keyof typeof articleData;
+    articleData[dataKey].id = undefined;
+    articleData[dataKey].title = '';
+    articleData[dataKey].content = '';
+    articleData[dataKey].remark = '';
+  });
+  
+  // 重置对话框状态
+  articleDialog.visible = false;
+  articleDialog.currentEventId = undefined;
+  activeTab.value = 'competition-process';
+};
+
+/** 保存文章 */
+const handleSaveArticle = async () => {
+  if (!articleDialog.currentEventId) {
+    proxy?.$modal.msgError('赛事ID不能为空');
+    return;
+  }
+
+  const currentTabName = activeTab.value;
+  const type = tabTypeMapping[currentTabName];
+  const dataKey = getDataKeyByTabName(currentTabName);
+  
+  if (!dataKey || !articleData[dataKey]) {
+    proxy?.$modal.msgError('获取当前标签页数据失败');
+    return;
+  }
+
+  const currentData = articleData[dataKey];
+  
+  if (!currentData.title?.trim()) {
+    proxy?.$modal.msgError('标题不能为空');
+    return;
+  }
+
+  try {
+    const formData: EventMdForm = {
+      id: currentData.id,
+      eventId: articleDialog.currentEventId,
+      title: currentData.title,
+      content: currentData.content || '',
+      type: type,
+      remark: currentData.remark || ''
+    };
+
+    await editEventMd(formData);
+    proxy?.$modal.msgSuccess('文章保存成功');
+    
+    // 重新加载当前标签页数据以获取最新的ID
+    await loadTabData(currentTabName);
+  } catch (error) {
+    console.error('保存文章失败:', error);
+    proxy?.$modal.msgError('保存文章失败');
+  }
+};
+
 onMounted(() => {
   getList();
 });
@@ -607,3 +897,26 @@ onActivated(() => {
   }
 });
 </script>
+
+<style scoped>
+.article-form {
+  padding: 20px 0;
+}
+
+.article-form .el-form-item {
+  margin-bottom: 20px;
+}
+
+.article-form .el-form-item__label {
+  font-weight: 600;
+  color: #303133;
+}
+
+.article-form .el-input {
+  width: 100%;
+}
+
+.dialog-footer {
+  text-align: right;
+}
+</style>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно