Jelajahi Sumber

Merge branch 'dev' into wk-dev

wenkai 1 Minggu lalu
induk
melakukan
9069e309ab

+ 107 - 0
src/api/system/common/nav/gameNavigator.ts

@@ -0,0 +1,107 @@
+import request from '@/utils/request'
+import type { ApiResult, PageQuery } from '../sys/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
+}
+
+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
+}
+
+// 查询底部主导航列表
+export function listNavigator(query: GameNavigatorBo & PageQuery) {
+  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',
+  })
+}
+
+// 查询底部主导航详细
+export function getNavigator(navId: number) {
+  return request<ApiResult<GameNavigatorVo>>({
+    url: `/system/scenic/navigator/${navId}`,
+    method: 'get'
+  })
+}
+
+// 新增底部主导航
+export function addNavigator(data: GameNavigatorBo) {
+  return request<ApiResult<void>>({
+    url: '/system/scenic/navigator',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改底部主导航
+export function updateNavigator(data: GameNavigatorBo) {
+  return request<ApiResult<void>>({
+    url: '/system/scenic/navigator',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除底部主导航
+export function delNavigator(navId: number | Array<number>) {
+  return request<ApiResult<void>>({
+    url: `/system/scenic/navigator/${navId}`,
+    method: 'delete'
+  })
+}
+
+// 导出底部主导航
+export function exportNavigator(query: GameNavigatorBo) {
+  return request({
+    url: '/system/scenic/navigator/export',
+    method: 'post',
+    data: query
+  })
+}
+
+// 金刚区(type=4)固定类型数据
+export function listKingKongNav() {
+  return request<ApiResult<GameNavigatorVo[]>>({
+    url: '/system/scenic/navigator/byType/4',
+    method: 'get'
+  })
+}

+ 2 - 2
src/components/ImageSelector/index.vue

@@ -297,14 +297,14 @@ const previewContainerStyle = computed(() => ({
   height: `${sizeForm.value.height}px`,
   maxWidth: '100%',
   maxHeight: '400px',
-  position: 'relative',
+  position: 'relative' as const,
   overflow: 'hidden',
   border: '1px solid #d9d9d9',
   borderRadius: '6px'
 }));
 
 const cropBoxStyle = computed(() => ({
-  position: 'absolute',
+  position: 'absolute' as const,
   left: `${cropForm.value.x}px`,
   top: `${cropForm.value.y}px`,
   width: `${cropForm.value.width}px`,

+ 1023 - 0
src/views/system/common/nav/components/GameNavigator.vue

@@ -0,0 +1,1023 @@
+<template>
+  <div class="bottom-main-nav">
+    <!-- 搜索条件区域 -->
+    <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-form-item>
+        <el-form-item label="链接类型" prop="jumpType">
+          <el-select v-model="queryParams.jumpType" placeholder="请选择链接类型" clearable style="width: 200px">
+            <el-option label="跳转链接" :value="1" />
+            <el-option label="不跳转" :value="2" />
+            <el-option label="小程序内链" :value="3" />
+            <el-option label="小程序外链" :value="4" />
+            <el-option label="H5外链" :value="5" />
+            <el-option label="公众号文章" :value="6" />
+            <el-option label="公众号" :value="7" />
+            <el-option label="电话拨号" :value="8" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
+            <el-option label="正常" :value="0" />
+            <el-option label="停用" :value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="dateRange"
+            type="daterange"
+            range-separator="-"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 240px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">Q 搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 操作按钮区域 -->
+    <el-card class="action-card">
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <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>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" 
+            >删除
+          </el-button>
+        </el-col> -->
+        <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="loadNavData"></right-toolbar>
+      </el-row>
+    </el-card>
+
+    <!-- 菜单列表 -->
+    <el-card class="list-card">
+      <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"> 
+          <template #default="scope">
+            <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"> 
+          <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="jumpType" width="150" v-if="columns[5].visible">
+          <template #default="scope">
+            {{ getJumpTypeText(scope.row.jumpType) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="链接" align="center" prop="jumpPath" width="150" v-if="columns[6].visible">
+          <template #default="scope">
+            {{ 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">
+              <div class="status">
+                <span class="label">使用状态:</span>
+                <el-tag :type="scope.row.status === 0 ? 'success' : 'danger'" size="small">
+                  {{ scope.row.status === 0 ? '正常' : '停用' }}
+                </el-tag>
+              </div>
+              <div class="create-time">
+                <span class="label">创建时间:</span>
+                <span class="value">{{ formatDate(scope.row.createTime) }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" align="center" width="180">
+          <template #default="scope">
+            <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-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item command="delete">删除</el-dropdown-item>
+                  <el-dropdown-item command="view">查看详情</el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </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-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="菜单名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入菜单名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="排序" prop="sortNum">
+              <el-input-number v-model="form.sortNum" :min="1" :max="999" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <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-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
+        <el-row :gutter="20"> 
+          <el-col :span="12">
+            <el-form-item label="功能图标" prop="pic">
+              <el-upload
+                class="icon-uploader"
+                :action="uploadUrl"
+                :headers="headers"
+                :show-file-list="false"
+                :before-upload="beforeIconUpload"
+                :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-upload>
+              <div class="upload-tip">建议尺寸 100x100</div>
+            </el-form-item>
+          </el-col>
+          <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"
+                />
+              </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>
+            <el-radio :value="2">不跳转</el-radio>
+            <el-radio :value="3">小程序内链</el-radio>
+            <el-radio :value="4">小程序外链</el-radio>
+            <el-radio :value="5">H5外链</el-radio>
+            <el-radio :value="6">公众号文章</el-radio>
+            <el-radio :value="7">公众号</el-radio>
+            <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>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="form.createTime"
+                type="datetime"
+                placeholder="请选择创建时间"
+                style="width: 100%"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="更新时间">
+              <el-date-picker
+                v-model="form.updateTime"
+                type="datetime"
+                placeholder="请选择更新时间"
+                style="width: 100%"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss"
+              />
+            </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>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 详情对话框 -->
+    <el-dialog title="菜单详情" v-model="detailOpen" width="600px" append-to-body>
+      <div class="detail-content">
+        <div class="detail-item">
+          <span class="detail-label">菜单名称:</span>
+          <span class="detail-value">{{ detailData.name || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">排序:</span>
+          <span class="detail-value">{{ detailData.sortNum || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">活动类型:</span>
+          <span class="detail-value">
+            <dict-tag :options="game_activity_type" :value="detailData.activityType" />
+          </span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">图标:</span>
+          <div class="detail-image">
+            <img v-if="detailData.pic" :src="detailData.pic" :alt="detailData.pic" />
+            <span v-else class="no-image-text">暂无图标</span>
+          </div>
+        </div>
+        <div class="detail-item"> 
+          <span class="detail-label">颜色:</span>
+          <span class="detail-value">{{ detailData.color }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">跳转类型:</span>
+          <span class="detail-value">{{ getJumpTypeText(detailData.jumpType) }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">跳转链接:</span>
+          <span class="detail-value">{{ detailData.jumpPath || '-' }}</span>
+        </div>
+        <div class="detail-item" v-if="detailData.appId">
+          <span class="detail-label">小程序AppID:</span>
+          <span class="detail-value">{{ detailData.appId }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">状态:</span>
+          <el-tag :type="detailData.status === 0 ? 'success' : 'danger'" size="small">
+            {{ detailData.status === 0 ? '正常' : '停用' }}
+          </el-tag>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">创建时间:</span>
+          <span class="detail-value">{{ formatDateTime(detailData.createTime) }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">更新时间:</span>
+          <span class="detail-value">{{ formatDateTime(detailData.updateTime) }}</span>
+        </div>
+        <div class="detail-item" v-if="detailData.remark">
+          <span class="detail-label">备注:</span>
+          <span class="detail-value">{{ detailData.remark }}</span>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="detailOpen = false">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</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, 
+  delNavigator,
+  type GameNavigatorVo,
+  type GameNavigatorBo,
+  listNavigator
+} from '@/api/system/common/nav/gameNavigator'
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { game_activity_type } = toRefs<any>(proxy?.useDict('game_activity_type'));
+
+// 计算下一个排序值
+const nextSortNum = computed(() => getMaxSortNum() + 1);
+
+const showSearch = ref(true);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+// 列显隐数据
+const columns = ref<FieldOption[]>([
+  { key: 0, label: '序号', visible: true },
+  { key: 1, label: '菜单名称', visible: true },
+  { key: 2, label: '活动类型', visible: true },
+  { key: 3, label: '图标', visible: true },
+  { key: 4, label: '颜色', visible: true },
+  { key: 5, label: '链接类别', visible: true },
+  { key: 6, label: '链接', visible: true },
+  { key: 7, label: '创建时间', visible: true },
+]);
+
+// 菜单项接口
+interface NavItem extends GameNavigatorVo {
+  navId?: number;
+  name?: string;
+  pic?: string;
+  color?: string;
+  jumpType?: number;
+  jumpPath?: string;
+  activityType?: number;
+  sortNum?: number;
+  status?: number;
+  remark?: string;
+  createTime?: string;
+  updateTime?: string;
+  appId?: string;
+}
+
+// 表单数据
+const navItems = ref<NavItem[]>([]);
+const formRef = ref()
+const form = reactive<GameNavigatorBo & {
+  createTime?: string;
+  updateTime?: string;
+}>({
+  navId: undefined,
+  name: '',
+  pic: '',
+  color: '#409EFF',
+  jumpType: 1,
+  jumpPath: '',
+  activityType: 1,
+  appId: '',
+  sortNum: 1, // 初始值,会在使用时动态更新
+  status: 0,
+  remark: '',
+  createTime: '',
+  updateTime: '',
+});
+
+// 响应式数据
+const loading = ref(false);
+const dialogVisible = ref(false);
+const detailOpen = ref(false);
+const dialogTitle = ref('');
+const submitting = ref(false);
+const selectedIds = ref<number[]>([]);
+const dateRange = ref<[Date, Date] | null>(null);
+const detailData = ref<NavItem>({} as NavItem);
+
+// 查询参数
+const queryParams = reactive({
+  name: '',
+  jumpType: undefined as number | undefined,
+  status: undefined as number | undefined,
+  createTime: ''
+});
+
+// 表单验证规则
+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' }
+  ]
+}
+
+// 添加跳转链接验证函数
+function validateJumpPath(rule: any, value: string, callback: Function) {
+  if (value && !/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(value)) {
+    callback(new Error('请输入有效的URL'))
+  } else {
+    callback()
+  }
+}
+
+// 上传相关
+import { globalHeaders } from '@/utils/request';
+
+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')
+}
+
+// 格式化日期时间
+function formatDateTime(date: string | Date | null | undefined): string {
+  if (!date) return '-'
+  const d = new Date(date)
+  if (isNaN(d.getTime())) return '-'
+  return d.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit',
+    second: '2-digit'
+  })
+}
+
+// 获取跳转类型文本
+function getJumpTypeText(jumpType: number | null | undefined): string {
+  if (!jumpType) return '-'
+  const typeMap: Record<number, string> = {
+    1: '跳转链接',
+    2: '不跳转',
+    3: '小程序内链',
+    4: '小程序外链',
+    5: 'H5外链',
+    6: '公众号文章',
+    7: '公众号',
+    8: '电话拨号'
+  }
+  return typeMap[jumpType] || '-'
+}
+
+// 获取最大排序值
+function getMaxSortNum(): number {
+  if (navItems.value.length === 0) {
+    return 0
+  }
+  const maxSort = Math.max(...navItems.value.map(item => item.sortNum || 0))
+  return maxSort
+}
+
+// 加载菜单数据
+async function loadNavData() {
+  loading.value = true
+  try {
+    const response = await getEnabledNavigator()
+    if (response.code === 200 && response.data) {
+      navItems.value = response.data as unknown as NavItem[]
+    } else {
+      navItems.value = []
+    }
+  } catch (error) {
+    console.error('加载数据失败:', error)
+    navItems.value = []
+  } finally {
+    loading.value = false
+    // 数据加载完成后,更新排序值
+    if (!form.navId) {
+      form.sortNum = nextSortNum.value
+    }
+  }
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  loadNavData();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  dateRange.value = null;
+  Object.assign(queryParams, {
+    name: '',
+    jumpType: undefined,
+    status: undefined,
+    createTime: ''
+  });
+  loadNavData();
+}
+
+/** 多选框选中数据 */
+function handleSelectionChange(selection: NavItem[]) {
+  selectedIds.value = selection.map(item => item.navId!);
+}
+
+/** 排序变更 */
+async function handleSortChange(row: NavItem) {
+  try {
+    const response = await updateNavigator({
+      navId: row.navId,
+      sortNum: row.sortNum,
+      // type: '4'
+    });
+    if (response.code === 200) {
+      ElMessage.success('排序更新成功');
+    } else {
+      ElMessage.error(response.msg || '排序更新失败');
+    }
+  } catch (error) {
+    ElMessage.error('排序更新失败');
+  }
+}
+
+/** 批量操作 */
+async function handleCommand(command: string, row?: NavItem) {
+  if (command === 'delete') {
+    if (row) {
+      await handleDelete(row);
+    } else if (selectedIds.value.length > 0) {
+      await handleBatchDelete();
+    }
+  } else if (command === 'export') {
+    handleExport();
+  } else if (command === 'view') {
+    handleView(row!);
+  }
+}
+
+/** 批量删除 */
+async function handleBatchDelete() {
+  await ElMessageBox.confirm(`是否确认删除选中的${selectedIds.value.length}个菜单?`, '提示');
+  // 这里需要实现批量删除接口
+  ElMessage.success('批量删除成功');
+  loadNavData();
+}
+
+/** 导出 */
+function handleExport() {
+  ElMessage.info('导出功能待实现');
+}
+
+/** 查看详情 */
+function handleView(row: NavItem) {
+  detailData.value = { ...row };
+  detailOpen.value = true;
+}
+
+// 处理编辑
+function handleEdit(item: NavItem) {
+  reset()
+  const formData = {
+    navId: item.navId,
+    name: item.name,
+    pic: item.pic,
+    color: item.color || '#409EFF',
+    jumpType: item.jumpType || 1,
+    jumpPath: item.jumpType === 2 ? item.jumpPath || '#' : item.jumpPath,
+    activityType: item.activityType || 1,
+    appId: (item as any).appId,
+    sortNum: item.sortNum,
+    status: item.status,
+    remark: item.remark || '',
+    createTime: item.createTime,
+    updateTime: item.updateTime
+  }
+  Object.assign(form, formData)
+  colorInput.value = item.color || '#409EFF'
+  dialogVisible.value = true
+  dialogTitle.value = '修改菜单'
+}
+
+// 处理添加
+function handleAdd() {
+  reset()
+  // 设置排序值为当前最大值+1
+  form.sortNum = nextSortNum.value
+  dialogVisible.value = true
+  dialogTitle.value = '添加菜单'
+}
+
+// 处理删除
+async function handleDelete(item: NavItem) {
+  try {
+    await ElMessageBox.confirm('确认删除该菜单吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    
+    const response = await delNavigator(item.navId!)
+    if (response.code === 200) {
+      ElMessage.success('删除成功')
+      loadNavData()
+    } else {
+      ElMessage.error(response.msg || '删除失败')
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      ElMessage.error('删除失败')
+    }
+  }
+}
+
+// 处理提交
+async function handleSubmit() {
+  try {
+    await formRef.value.validate()
+    submitting.value = true
+    
+    const payload: any = {
+      navId: form.navId,
+      name: form.name,
+      pic: form.pic,
+      color: form.color,
+      jumpType: form.jumpType,
+      jumpPath: form.jumpType === 2 ? form.jumpPath || '#' : form.jumpPath,
+      activityType: form.activityType,
+      appId: form.appId,
+      sortNum: form.sortNum,
+      status: form.status,
+      remark: form.remark,
+      createTime: form.createTime,
+      updateTime: form.updateTime,
+      // type: '4' // 菜单固定类型
+    }
+    
+    let response
+    if (form.navId) {
+      response = await updateNavigator(payload)
+    } else {
+      response = await addNavigator(payload)
+    }
+    
+    if (response.code === 200) {
+      ElMessage.success(form.navId ? '修改成功' : '添加成功')
+      dialogVisible.value = false
+      loadNavData()
+    } else {
+      ElMessage.error(response.msg || '操作失败')
+    }
+  } catch (error) {
+    console.error('提交失败:', error)
+    ElMessage.error('操作失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+// 重置表单
+function reset() {
+  Object.assign(form, {
+    navId: undefined,
+    name: '',
+    pic: '',
+    color: '#409EFF',
+    jumpType: 1,
+    jumpPath: '',
+    activityType: 1,
+    appId: '',
+    sortNum: nextSortNum.value,
+    status: 0,
+    remark: '',
+    createTime: '',
+    updateTime: ''
+  })
+  colorInput.value = '#409EFF'
+  formRef.value?.clearValidate()
+}
+
+// 跳转类型变更处理
+function handleJumpTypeChange() {
+  if (form.jumpType === 2) {
+    form.jumpPath = '#'
+  } else if (form.jumpType === 4) {
+    form.jumpPath = ''
+  }
+}
+
+// 图片上传前验证
+function beforeIconUpload(file: File) {
+  const isImage = file.type.startsWith('image/')
+  const isLt2M = file.size / 1024 / 1024 < 2
+
+  if (!isImage) {
+    ElMessage.error('只能上传图片文件!')
+    return false
+  }
+  if (!isLt2M) {
+    ElMessage.error('图片大小不能超过 2MB!')
+    return false
+  }
+  return true
+}
+
+// 图片上传成功
+function handleIconSuccess(response: any) {
+  if (response.code === 200) {
+    form.pic = response.data.url
+    ElMessage.success('图片上传成功')
+  } else {
+    ElMessage.error(response.msg || '图片上传失败')
+  }
+}
+
+// 颜色输入框的值
+const colorInput = ref('#409EFF')
+
+// 将RGB颜色转换为16进制
+const rgbToHex = (rgb: string): string => {
+  // 如果是16进制格式,直接返回
+  if (rgb.startsWith('#')) {
+    return rgb
+  }
+  
+  // 如果是RGB格式,转换为16进制
+  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')}`
+  }
+  
+  // 如果无法解析,返回默认值
+  return '#409EFF'
+}
+
+// 处理颜色输入框变化
+const handleColorInput = (value: string) => {
+  // 实时同步到颜色选择器
+  if (value.startsWith('#')) {
+    form.color = value
+  }
+}
+
+// 处理颜色选择器变化
+const handleColorChange = (value: string) => {
+  // 当颜色选择器改变时,将16进制值填充到输入框
+  if (value) {
+    const hexValue = rgbToHex(value)
+    colorInput.value = hexValue
+  }
+}
+
+// 验证颜色输入框
+const validateColorInput = () => {
+  const hexRegex = /^#[0-9A-Fa-f]{6}$/
+  if (!hexRegex.test(colorInput.value)) {
+    ElMessage.warning('请输入正确的16进制颜色格式,如 #FF0000')
+    colorInput.value = form.color
+  }
+}
+
+// 初始化数据
+onMounted(() => {
+  loadNavData()
+  // 初始化排序值
+  form.sortNum = nextSortNum.value
+})
+</script>
+
+<style scoped>
+.bottom-main-nav {
+  padding: 20px;
+}
+
+.search-card {
+  margin-bottom: 20px;
+}
+
+.action-card {
+  margin-bottom: 20px;
+}
+
+.list-card {
+  margin-bottom: 20px;
+}
+
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.nav-info {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 8px 0;
+}
+
+.nav-image {
+  width: 60px;
+  height: 60px;
+  border-radius: 6px;
+  overflow: hidden;
+  background: #f5f7fa;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.nav-image img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.no-image {
+  color: #c0c4cc;
+  font-size: 20px;
+}
+
+.nav-details {
+  flex: 1;
+  min-width: 0;
+}
+
+.nav-title,
+.nav-module {
+  margin-bottom: 6px;
+  font-size: 13px;
+  line-height: 1.4;
+}
+
+.label {
+  color: #909399;
+  margin-right: 6px;
+  font-weight: normal;
+}
+
+.value {
+  color: #333;
+  font-weight: 500;
+}
+
+.link-info,
+.create-info {
+  font-size: 13px;
+  line-height: 1.5;
+  padding: 8px 0;
+}
+
+.link-type,
+.link-address,
+.status,
+.create-time {
+  margin-bottom: 6px;
+}
+
+.status {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.create-time {
+  margin-top: 4px;
+}
+
+.sort-number {
+  font-size: 14px;
+  font-weight: bold;
+  color: #333;
+}
+
+.detail-content {
+  padding: 20px;
+}
+
+.detail-item {
+  display: flex;
+  margin-bottom: 16px;
+  align-items: flex-start;
+}
+
+.detail-label {
+  width: 120px;
+  font-weight: 600;
+  color: #606266;
+  flex-shrink: 0;
+}
+
+.detail-value {
+  flex: 1;
+  color: #333;
+  word-break: break-all;
+}
+
+.detail-image {
+  flex: 1;
+}
+
+.detail-image img {
+  max-width: 200px;
+  max-height: 120px;
+  border-radius: 6px;
+  object-fit: cover;
+}
+
+.no-image-text {
+  color: #909399;
+  font-style: italic;
+}
+
+.dialog-footer {
+  text-align: right;
+}
+
+.icon-uploader {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  width: 100px;
+  height: 100px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.icon-uploader:hover {
+  border-color: #409eff;
+}
+
+.icon-preview {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.icon-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 8px;
+}
+</style> 

+ 0 - 1
src/views/system/gameAthlete/index.vue

@@ -274,7 +274,6 @@ const initFormData: GameAthleteForm = {
   location: undefined,
   tshirtSize: undefined,
   groupType: undefined,
-  number: undefined,
   projectValue: undefined,
   selectedProjects: [], // 添加已选项目列表
   status: undefined,

+ 3 - 3
src/views/system/gameEvent/edit.vue

@@ -562,9 +562,9 @@ const loadMenuData = async (eventId: string | number) => {
     const res = await listGameEventMenu({
       eventId: eventId === '' ? '' : eventId,
       pageNum: 1,
-      pageSize: 1000,
-      orderByColumn: '',
-      isAsc: ''
+      pageSize: 10,
+      orderByColumn: undefined,
+      isAsc: undefined,
     });
     menuItems.value = Array.isArray(res.rows) ? res.rows : [];
   } catch (error) {