Browse Source

feat(system): 新增统计页面及接口支持

- 为赛事、充值和道具使用新增统计查询接口
- 实现赛事统计页面 staticTop/index.vue- 实现充值统计页面 rechargeTop/index.vue
- 实现道具使用统计页面 toolsLog/index.vue
- 修改首页统计卡片点击跳转功能- 调整统计图表配置和数据处理逻辑
-修复道具类型字段命名不一致问题- 添加时间筛选和路由参数传递功能
fugui001 2 months ago
parent
commit
0483c5c03d

+ 12 - 0
src/api/system/business/itemsLog/index.ts

@@ -61,3 +61,15 @@ export const delItemsLog = (id: string | number | Array<string | number>) => {
     method: 'delete'
   });
 };
+
+/**
+ * 流水统计
+ * @param query
+ */
+export const queryStatisticsPageList = (query?: ItemsLogQuery): AxiosPromise<ItemsLogVO[]> => {
+  return request({
+    url: '/business/itemsLog/queryStatisticsPageList',
+    method: 'get',
+    params: query
+  });
+};

+ 15 - 6
src/api/system/business/itemsLog/types.ts

@@ -7,7 +7,7 @@ export interface ItemsLogVO {
   /**
    * 道具ID
    */
-  itermsId: string | number;
+  itemId: string | number;
 
   /**
    * 现在的数量
@@ -47,7 +47,7 @@ export interface ItemsLogVO {
   /**
    * 道具获取类型   充值/奖励/报名/比赛
    */
-  itermsType: string;
+  itemType: string;
 }
 
 export interface ItemsLogForm extends BaseEntity {
@@ -59,7 +59,7 @@ export interface ItemsLogForm extends BaseEntity {
   /**
    * 道具ID
    */
-  itermsId?: string | number;
+  itemId?: string | number;
 
   /**
    * 现在的数量
@@ -99,14 +99,14 @@ export interface ItemsLogForm extends BaseEntity {
   /**
    * 道具获取类型   充值/奖励/报名/比赛
    */
-  itermsType?: string;
+  itemType?: string;
 }
 
 export interface ItemsLogQuery extends PageQuery {
   /**
    * 道具ID
    */
-  itermsId?: string | number;
+  itemId?: string | number;
 
   /**
    * 现在的数量
@@ -141,10 +141,19 @@ export interface ItemsLogQuery extends PageQuery {
   /**
    * 道具获取类型   充值/奖励/报名/比赛
    */
-  itermsType?: string;
+  itemType?: string;
 
   /**
    * 日期范围参数
    */
   params?: any;
+
+  userName?: string;
+
+  loginTimeRange?: string;
+
+  beginTime?: string;
+  endTime?: string;
+  startTime?: string;
+  flagType?: string;
 }

+ 12 - 0
src/api/system/business/order/index.ts

@@ -60,3 +60,15 @@ export const delOrder = (id: string | number | Array<string | number>) => {
     method: 'delete'
   });
 };
+
+/**
+ * 充值统计
+ * @param query
+ */
+export const queryStatisticsPageList = (query?: OrderQuery): AxiosPromise<OrderVO[]> => {
+  return request({
+    url: '/business/order/queryStatisticsPageList',
+    method: 'get',
+    params: query
+  });
+};

+ 2 - 0
src/api/system/business/order/types.ts

@@ -296,4 +296,6 @@ export interface OrderQuery extends PageQuery {
   loginTimeRange?: string;
   beginTime?: string;
   endTime?: string;
+  startTime?: string;
+  flagType?: string;
 }

+ 12 - 0
src/api/system/business/tournaments/index.ts

@@ -124,3 +124,15 @@ export const closeSendTournament = (id: string | number | Array<string | number>
     method: 'GET'
   });
 };
+
+/**
+ * 查询 赛事统计相关
+ * @param query
+ */
+export const getStatisticsPageList = (query?: TournamentsQuery): AxiosPromise<TournamentsVO[]> => {
+  return request({
+    url: '/business/tournaments/getStatisticsPageList',
+    method: 'get',
+    params: query
+  });
+};

+ 6 - 1
src/api/system/business/tournaments/types.ts

@@ -183,7 +183,11 @@ export interface TournamentsQuery extends PageQuery {
    * 赛事名称
    */
   name?: string;
-
+  /**
+   * 比赛结束时间
+   */
+  endTime?: string;
+  flagType?: string;
   /**
    * 比赛开始时间
    */
@@ -228,6 +232,7 @@ export interface TournamentsQuery extends PageQuery {
   // 新增排序字段
   sortBy?: string; // 排序字段名,例如 "startTime"
   isAsc?: boolean; // 是否升序,true = 升序,false = 降序
+  loginTimeRange?: string;
 }
 
 export interface TournamentsBindStructuresQuery extends PageQuery {

+ 92 - 14
src/views/index.vue

@@ -1,6 +1,5 @@
 <template>
   <div class="app-container home">
-    <!-- 时间筛选区域 -->
     <!-- 时间筛选区域 -->
     <div class="time-filter">
       <el-button size="small" :type="timeFilter === 'day' ? 'primary' : 'default'" @click="handleTimeFilter('day')"> 今日 </el-button>
@@ -18,23 +17,27 @@
 
     <!-- 统计卡片区域 -->
     <div class="statistic-cards">
-      <div class="stat-card">
+      <div class="stat-card" @click="navigateToStaticTop('match')">
         <div class="card-header">比赛场次</div>
         <div class="card-value">{{ matchCount }}</div>
+        <!-- 添加点击反馈图标 -->
+        <div class="click-indicator">
+          <i class="el-icon el-icon-arrow-right"></i>
+        </div>
       </div>
-      <div class="stat-card">
+      <div class="stat-card" @click="navigateToStaticTop('rechargeCount')">
         <div class="card-header">充值数</div>
         <div class="card-value">{{ rechargeCount }}</div>
       </div>
-      <div class="stat-card">
+      <div class="stat-card" @click="navigateToStaticTop('rechargeAmount')">
         <div class="card-header">充值金额</div>
         <div class="card-value">{{ rechargeAmount }}</div>
       </div>
-      <div class="stat-card">
+      <div class="stat-card" @click="navigateToStaticTop('qualificationIssued')">
         <div class="card-header">三湘杯参赛资格发放数</div>
         <div class="card-value">{{ qualificationIssued }}</div>
       </div>
-      <div class="stat-card">
+      <div class="stat-card" @click="navigateToStaticTop('qualificationUsed')">
         <div class="card-header">三湘杯参赛资格核销数</div>
         <div class="card-value">{{ qualificationUsed }}</div>
       </div>
@@ -68,7 +71,7 @@ const rechargeCount = ref(0);
 const rechargeAmount = ref('0');
 const qualificationIssued = ref(0);
 const qualificationUsed = ref(0);
-
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 // 图表相关
 const chartRef = ref<HTMLDivElement | null>(null);
 let chartInstance: any;
@@ -149,7 +152,6 @@ const updateChart = (data: any) => {
       {
         name: '充值数',
         type: 'line',
-        stack: 'Total',
         areaStyle: {},
         emphasis: {
           focus: 'series'
@@ -159,7 +161,6 @@ const updateChart = (data: any) => {
       {
         name: '充值金额',
         type: 'line',
-        stack: 'Total',
         areaStyle: {},
         emphasis: {
           focus: 'series'
@@ -176,7 +177,6 @@ const updateChart = (data: any) => {
       {
         name: '三湘杯参赛资格发放数',
         type: 'line',
-        stack: 'Total',
         areaStyle: {},
         emphasis: {
           focus: 'series'
@@ -186,7 +186,6 @@ const updateChart = (data: any) => {
       {
         name: '三湘杯参赛资格核销数',
         type: 'line',
-        stack: 'Total',
         areaStyle: {},
         emphasis: {
           focus: 'series'
@@ -200,7 +199,6 @@ const updateChart = (data: any) => {
   chartInstance.setOption(option);
 };
 
-
 // 初始化图表
 const initChart = () => {
   if (!chartInstance && chartRef.value) {
@@ -209,7 +207,7 @@ const initChart = () => {
     // 初始化时设置基本配置
     const option = {
       title: {
-       /* text: '',*/
+        /* text: '',*/
         left: 'center'
       },
       tooltip: {
@@ -254,7 +252,7 @@ const fetchStatisticsData = async () => {
       const data = res.data;
       matchCount.value = data.tournamentCount || 0;
       rechargeCount.value = data.payOrderCount || 0;
-      rechargeAmount.value = data.payOrderAmount ? data.payOrderAmount.toFixed(0) : '0';
+      rechargeAmount.value = data.payOrderAmount || 0;
       qualificationIssued.value = data.sanXiangCardCount || 0;
       qualificationUsed.value = data.selectCheckRecordSanXiangCount || 0;
     }
@@ -275,6 +273,59 @@ onMounted(() => {
   fetchStatisticsData();
   fetchGraphData(); // 获取图表数据
 });
+
+// 跳转到 staticTop 页面并携带参数
+const navigateToStaticTop = (cardType: string) => {
+  // 根据 cardType 的值跳转到不同的路由
+  switch (cardType) {
+    case 'match':
+      proxy?.$router.push({
+        path: '/service/tournamentTop',
+        query: {
+          cardType: cardType,
+          timeFilter: timeFilter.value,
+          startTime: startTime.value,
+          endTime: endTime.value
+        }
+      });
+      break;
+    case 'rechargeCount':
+    case 'rechargeAmount':
+      proxy?.$router.push({
+        path: '/service/rechargeTop',
+        query: {
+          cardType: cardType,
+          timeFilter: timeFilter.value,
+          startTime: startTime.value,
+          endTime: endTime.value
+        }
+      });
+      break;
+    case 'qualificationIssued':
+    case 'qualificationUsed':
+      proxy?.$router.push({
+        path: '/service/toolsTop',
+        query: {
+          cardType: cardType,
+          timeFilter: timeFilter.value,
+          startTime: startTime.value,
+          endTime: endTime.value
+        }
+      });
+      break;
+    default:
+      // 默认跳转到 tournamentTop 页面
+      proxy?.$router.push({
+        path: '/service/tournamentTop',
+        query: {
+          cardType: cardType,
+          timeFilter: timeFilter.value,
+          startTime: startTime.value,
+          endTime: endTime.value
+        }
+      });
+  }
+};
 </script>
 
 <style lang="scss" scoped>
@@ -345,4 +396,31 @@ onMounted(() => {
   width: 100%;
   height: 400px;
 }
+
+.stat-card {
+  background: #f5f5f5;
+  border-radius: 8px;
+  padding: 20px;
+  text-align: center;
+  cursor: pointer; /* 添加小手光标 */
+  transition: all 0.3s ease; /* 添加过渡效果 */
+
+  &:hover {
+    background: #e6f7ff; /* 悬停时背景变色 */
+    transform: translateY(-2px); /* 轻微上移效果 */
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* 添加阴影 */
+  }
+
+  .card-header {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 10px;
+  }
+
+  .card-value {
+    font-size: 36px;
+    font-weight: bold;
+    color: #333;
+  }
+}
 </style>

+ 237 - 0
src/views/system/business/rechargeTop/index.vue

@@ -0,0 +1,237 @@
+<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-width="100px" label="充值商品名" prop="outTradeNo">
+              <el-input v-model="queryParams.subject" placeholder="请输入充值商品名" clearable @keyup.enter="handleQuery" style="width: 200px" />
+            </el-form-item>
+            <el-form-item label-width="100px" label="用户名/手机号" prop="subject">
+              <el-input v-model="queryParams.userIds" placeholder="请输入用户名/手机号" clearable @keyup.enter="handleQuery" style="width: 200px" />
+            </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="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:order:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:order:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:order:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:order: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="orderList">
+        <el-table-column label="编号" width="60" align="center">
+          <template #default="{ $index }">
+            {{ $index + 1 + (queryParams.pageNum - 1) * queryParams.pageSize }}
+          </template>
+        </el-table-column>
+        <el-table-column label="充值商品名" align="center" prop="subject" />
+        <el-table-column label="价格" align="center" prop="totalAmount" />
+        <el-table-column label="说明" align="center" prop="productText" />
+        <el-table-column label="关联道具" align="center" prop="relatedItemIdText" />
+        <el-table-column label="用户ID" align="center" prop="userId" />
+        <el-table-column label="用户手机号" align="center" prop="phone" />
+        <el-table-column label="用户名" align="center" prop="userName" />
+        <el-table-column label="充值时间" align="center" prop="payTime" />
+        <el-table-column label="充值状态" align="center" prop="status">
+          <template #default="{ row }">
+            <el-tag :type="row.status === 'success' ? 'success' : 'info'" size="small">
+              {{ row.status === 'success' ? '充值成功' : row.status }}
+            </el-tag>
+          </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>
+  </div>
+</template>
+
+<script setup name="Order" lang="ts">
+import { queryStatisticsPageList } from '@/api/system/business/order';
+import { OrderVO, OrderQuery, OrderForm } from '@/api/system/business/order/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const orderList = ref<OrderVO[]>([]);
+const buttonLoading = ref(false);
+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 orderFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: OrderForm = {
+  id: undefined,
+  outTradeNo: undefined,
+  subject: undefined,
+  body: undefined,
+  totalAmount: undefined,
+  currency: undefined,
+  userId: undefined,
+  bizType: undefined,
+  bizId: undefined,
+  tradeNo: undefined,
+  appId: undefined,
+  sellerId: undefined,
+  status: undefined,
+  payTime: undefined,
+  notifyTime: undefined,
+  notifyType: undefined,
+  tradeStatus: undefined,
+  productId: undefined,
+  relatedItemId: undefined
+};
+const data = reactive<PageData<OrderForm, OrderQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    outTradeNo: undefined,
+    subject: undefined,
+    body: undefined,
+    totalAmount: undefined,
+    currency: undefined,
+    userId: undefined,
+    bizType: undefined,
+    bizId: undefined,
+    tradeNo: undefined,
+    appId: undefined,
+    sellerId: undefined,
+    status: undefined,
+    payTime: undefined,
+    notifyTime: undefined,
+    notifyType: undefined,
+    tradeStatus: undefined,
+    productId: undefined,
+    relatedItemId: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
+    outTradeNo: [{ required: true, message: '商户订单号不能为空', trigger: 'blur' }],
+    subject: [{ required: true, message: '商品标题不能为空', trigger: 'blur' }],
+    totalAmount: [{ required: true, message: '订单金额不能为空', trigger: 'blur' }],
+    userId: [{ required: true, message: '用户ID不能为空', trigger: 'blur' }],
+    bizType: [{ required: true, message: '业务类型不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询支付订单列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await queryStatisticsPageList(queryParams.value);
+  orderList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  orderFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  //queryParams.value.flagType = '';
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'system/order/export',
+    {
+      ...queryParams.value
+    },
+    `order_${new Date().getTime()}.xlsx`
+  );
+};
+import { useRoute } from 'vue-router';
+const route = useRoute();
+onMounted(() => {
+  const flagType = route.query.timeFilter;
+  const startTime = route.query.startTime;
+  const endTime = route.query.endTime;
+  // 根据参数设置查询条件
+  if (startTime) {
+    queryParams.value.startTime = startTime as string; // 直接赋值字符串
+  } else {
+  }
+  // 处理结束时间(如果需要)
+  if (endTime) {
+    queryParams.value.endTime = endTime as string;
+  }
+  if (flagType) {
+    queryParams.value.flagType = flagType as string;
+  }
+  getList();
+});
+
+// 监听路由参数变化
+watch(
+  () => route.query,
+  (newQuery, oldQuery) => {
+    const flagType = route.query.timeFilter;
+    const startTime = route.query.startTime;
+    const endTime = route.query.endTime;
+    // 根据参数设置查询条件
+    if (startTime) {
+      queryParams.value.startTime = startTime as string; // 直接赋值字符串
+    } else {
+    }
+    // 处理结束时间(如果需要)
+    if (endTime) {
+      queryParams.value.endTime = endTime as string;
+    }
+    if (flagType) {
+      queryParams.value.flagType = flagType as string;
+    }
+    getList();
+  },
+  { immediate: true } // 立即执行一次,确保初始参数被处理
+);
+</script>

+ 278 - 0
src/views/system/business/staticTop/index.vue

@@ -0,0 +1,278 @@
+<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="时间">
+              <el-date-picker
+                v-model="data.queryParams.loginTimeRange"
+                type="datetimerange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                format="YYYY-MM-DD HH:mm:ss"
+                style="width: 100%"
+              />
+            </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">
+
+&lt;!&ndash;          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:tournaments:export']">导出</el-button>
+          </el-col>&ndash;&gt;
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>-->
+
+      <el-table v-loading="loading" border :data="tournamentsList">
+        <!-- 序号列 -->
+        <el-table-column label="编号" width="60" align="center">
+          <template #default="{ $index }">
+            {{ $index + 1 + (queryParams.pageNum - 1) * queryParams.pageSize }}
+          </template>
+        </el-table-column>
+        <el-table-column label="赛事ID" align="center" prop="id" v-if="true" />
+        <el-table-column label="赛事名称" align="center" prop="name" />
+        <el-table-column label="开始时间" align="center" prop="startTime" width="150" sortable="custom"> </el-table-column>
+        <el-table-column label="结束时间" align="center" prop="endTime" width="150"> </el-table-column>
+        <el-table-column label="报名人数" align="center" prop="signNum" />
+        <el-table-column label="报名总手数" align="center" prop="totalSignup" />
+        <el-table-column label="报名条件" align="center" prop="tournamentCondition" />
+        <el-table-column label="条件数" align="center" prop="tournamentConditionValue" />
+        <el-table-column label="奖励人数" align="center" prop="rewardPlayers" />
+        <el-table-column label="总报名价值" align="center" prop="signValue" />
+        <el-table-column label="奖励" align="center">
+          <template #default="scope">
+            <div v-if="scope.row.itemsPrizeList && scope.row.itemsPrizeList.length > 0">
+              <!-- 显示第一名奖励 -->
+              <div>
+                第{{ scope.row.itemsPrizeList[0].ranking }}名:{{ scope.row.itemsPrizeList[0].quantity }} {{ scope.row.itemsPrizeList[0].itemsName }}
+              </div>
+
+              <!-- 如果有更多奖励,用 tooltip 显示 -->
+              <el-tooltip v-if="scope.row.itemsPrizeList.length > 1" :content="getRewardTooltipContent(scope.row.itemsPrizeList)" placement="top">
+                <span class="more-rewards">更多</span>
+              </el-tooltip>
+            </div>
+            <span v-else>—</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="奖励名次" align="center" prop="rewardClaimsCount" />
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+  </div>
+</template>
+
+<script setup name="Tournaments" lang="ts">
+import { getStatisticsPageList, getTournaments, delTournaments, addTournaments, updateTournaments } from '@/api/system/business/tournaments';
+import { TournamentsVO, TournamentsQuery, TournamentsForm } from '@/api/system/business/tournaments/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const tournamentsList = ref<TournamentsVO[]>([]);
+const buttonLoading = ref(false);
+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 tournamentsFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: TournamentsForm = {
+  id: undefined,
+  name: undefined,
+  startTime: undefined,
+  gameType: undefined,
+  startingChips: undefined,
+  levelDuration: undefined,
+  lateRegistrationLevel: undefined,
+  maxPlayers: undefined,
+  status: undefined,
+  createdAt: undefined,
+  updatedAt: undefined,
+  signTime: undefined,
+  competitionIcon: undefined
+};
+const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    startTime: undefined,
+    gameType: undefined,
+    startingChips: undefined,
+    levelDuration: undefined,
+    lateRegistrationLevel: undefined,
+    maxPlayers: undefined,
+    status: undefined,
+    createdAt: undefined,
+    updatedAt: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '赛事名称不能为空', trigger: 'blur' }],
+    startTime: [{ required: true, message: '比赛开始时间不能为空', trigger: 'blur' }],
+    gameType: [{ required: true, message: '游戏类型不能为空', trigger: 'change' }],
+    startingChips: [{ required: true, message: '起始记分牌数量不能为空', trigger: 'blur' }],
+    levelDuration: [{ required: true, message: '级别持续时间不能为空', trigger: 'blur' }],
+    lateRegistrationLevel: [{ required: true, message: '截止报名级别不能为空', trigger: 'blur' }],
+    maxPlayers: [{ required: true, message: '最大参赛人数不能为空', trigger: 'blur' }],
+    status: [{ required: true, message: '赛事状态 0 未开始 1 进行中 2 同步发牌 3 普通暂停 4 完成不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询【请填写功能名称】列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await getStatisticsPageList(queryParams.value);
+  tournamentsList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  tournamentsFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  //queryParams.value.flagType = '';
+  const timeRange = data.queryParams.loginTimeRange;
+  if (timeRange && timeRange.length === 2) {
+    data.queryParams.startTime = timeRange[0];
+    data.queryParams.endTime = timeRange[1];
+  } else {
+    data.queryParams.startTime = null;
+    data.queryParams.endTime = null;
+  }
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 设置默认开始时间为今天
+  const today = new Date();
+  const yyyy = today.getFullYear();
+  const mm = String(today.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以+1
+  const dd = String(today.getDate()).padStart(2, '0');
+  queryParams.value.startTime = `${yyyy}-${mm}-${dd}`;
+  handleQuery();
+};
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: TournamentsVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'system/tournaments/export',
+    {
+      ...queryParams.value
+    },
+    `tournaments_${new Date().getTime()}.xlsx`
+  );
+};
+// 获取奖励提示内容
+const getRewardTooltipContent = (rewards: any[]) => {
+  return rewards.map((prize) => `第${prize.ranking}名:${prize.quantity} ${prize.itemsName}`).join('\n');
+};
+
+import { useRoute } from 'vue-router';
+
+const route = useRoute();
+
+onMounted(() => {
+  // 设置默认开始时间为今天
+  const today = new Date();
+  const yyyy = today.getFullYear();
+  const mm = String(today.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以+1
+  const dd = String(today.getDate()).padStart(2, '0');
+  const flagType = route.query.timeFilter;
+  const startTime = route.query.startTime;
+  const endTime = route.query.endTime;
+  // 根据参数设置查询条件
+  if (startTime) {
+    queryParams.value.startTime = startTime as string; // 直接赋值字符串
+  } else {
+    /* queryParams.value.startTime = `${yyyy}-${mm}-${dd}`;*/
+  }
+  // 处理结束时间(如果需要)
+  if (endTime) {
+    queryParams.value.endTime = endTime as string;
+  }
+  if (flagType) {
+    queryParams.value.flagType = flagType as string;
+  }
+  getList();
+});
+
+// 监听路由参数变化
+watch(
+  () => route.query,
+  (newQuery, oldQuery) => {
+    const flagType = route.query.timeFilter;
+    const startTime = route.query.startTime;
+    const endTime = route.query.endTime;
+    // 根据参数设置查询条件
+    if (startTime) {
+      queryParams.value.startTime = startTime as string; // 直接赋值字符串
+    } else {
+    }
+    // 处理结束时间(如果需要)
+    if (endTime) {
+      queryParams.value.endTime = endTime as string;
+    }
+    if (flagType) {
+      queryParams.value.flagType = flagType as string;
+    }
+    getList();
+  },
+  { immediate: true } // 立即执行一次,确保初始参数被处理
+);
+</script>
+<style scoped>
+.more-rewards {
+  cursor: pointer;
+  color: #409eff;
+  margin-left: 5px;
+}
+</style>

+ 275 - 0
src/views/system/business/toolsLog/index.vue

@@ -0,0 +1,275 @@
+<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-row :gutter="10" class="mb8">
+              <!-- 第一行:道具和方式 -->
+              <el-form-item label="道具" prop="itermsId">
+                <el-select v-model="queryParams.itemId" placeholder="请选择道具" style="flex: 1">
+                  <el-option label="所有" value="" />
+                  <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="方式" prop="gameType">
+                <el-select aria-required="true" v-model="queryParams.itemType" placeholder="请选择">
+                  <el-option label="所有" value="" />
+                  <el-option v-for="dict in every_tools_type" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="用户名" prop="userName">
+                <el-input v-model="queryParams.userName" placeholder="请输入用户名" clearable @keyup.enter="handleQuery" style="width: 100%" />
+              </el-form-item>
+            </el-row>
+            <el-row :gutter="10" class="mb8">
+              <el-form-item label="时间">
+                <el-date-picker
+                  v-model="data.queryParams.loginTimeRange"
+                  type="datetimerange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  format="YYYY-MM-DD HH:mm:ss"
+                  style="width: 100%"
+                />
+              </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-row>
+          </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="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:itemsLog:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:itemsLog:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:itemsLog:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:itemsLog: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="itemsLogList" @selection-change="handleSelectionChange">
+        <el-table-column label="编号" width="60" align="center">
+          <template #default="{ $index }">
+            {{ $index + 1 + (queryParams.pageNum - 1) * queryParams.pageSize }}
+          </template>
+        </el-table-column>
+        <el-table-column label="道具名" align="center" prop="itemName" />
+        <el-table-column label="道具数量" align="center" prop="scoreNum" />
+        <el-table-column label="方式" align="center" prop="itermTypeWay" />
+        <el-table-column label="事项" align="center" prop="remark" />
+        <el-table-column label="时间" align="center" prop="createdAt" width="150"> </el-table-column>
+        <el-table-column label="关联用户ID" align="center" prop="userId" />
+        <el-table-column label="关联用户手机号" align="center" prop="phone" />
+        <el-table-column label="关联用户名" align="center" prop="userName" />
+        <el-table-column label="状态" align="center" prop="statusText" />
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+  </div>
+</template>
+
+<script setup name="ItemsLog" lang="ts">
+import { queryStatisticsPageList } from '@/api/system/business/itemsLog';
+import { ItemsLogVO, ItemsLogQuery, ItemsLogForm } from '@/api/system/business/itemsLog/types';
+import { selectItemsSelList } from '@/api/system/business/items';
+import { useRoute } from 'vue-router';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { every_tools_type } = toRefs<any>(proxy?.useDict('every_tools_type'));
+
+const itemsLogList = ref<ItemsLogVO[]>([]);
+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 itemsLogFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ItemsLogForm = {
+  id: undefined,
+  itemId: undefined,
+  scoreNum: undefined,
+  afterNum: undefined,
+  createdAt: undefined,
+  updatedAt: undefined,
+  userId: undefined,
+  type: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<ItemsLogForm, ItemsLogQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    scoreNum: undefined,
+    afterNum: undefined,
+    createdAt: undefined,
+    updatedAt: undefined,
+    userId: undefined,
+    type: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询道具使用记录列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await queryStatisticsPageList(queryParams.value);
+  itemsLogList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  itemsLogFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  const timeRange = data.queryParams.loginTimeRange;
+  if (timeRange && timeRange.length === 2) {
+    data.queryParams.startTime = timeRange[0];
+    data.queryParams.endTime = timeRange[1];
+  } else {
+    data.queryParams.startTime = null;
+    data.queryParams.endTime = null;
+  }
+  queryParams.value.flagType = '';
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ItemsLogVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'system/itemsLog/export',
+    {
+      ...queryParams.value
+    },
+    `itemsLog_${new Date().getTime()}.xlsx`
+  );
+};
+// 下拉选项数据 selectBlingStructuresInfo
+const itemOptions = ref<{ id: number; label: string }[]>([]);
+
+// 加载报名条件选项
+const loadItemOptions = async () => {
+  try {
+    const res = await selectItemsSelList();
+    if (res.code === 200) {
+      // 类型断言
+      const data = res.data as unknown as { id: number; name: string }[];
+
+      // 过滤掉 id === 2 的项,并映射为 { id, label }
+      itemOptions.value = data
+        .filter((item) => item.id !== 2) // ✅ 过滤 id 为 2 的
+        .map((item) => ({
+          id: item.id,
+          label: item.name
+        }));
+    } else {
+      ElMessage.error('加载失败:' + res.msg);
+    }
+  } catch (error) {
+    console.error('请求出错:', error);
+    ElMessage.error('请求失败,请检查网络');
+  }
+};
+const route = useRoute();
+onMounted(() => {
+  const flagType = route.query.timeFilter;
+  const startTime = route.query.startTime;
+  const endTime = route.query.endTime;
+  // 根据参数设置查询条件
+  if (startTime) {
+    queryParams.value.startTime = startTime as string; // 直接赋值字符串
+  } else {
+    /* queryParams.value.startTime = `${yyyy}-${mm}-${dd}`;*/
+  }
+  // 处理结束时间(如果需要)
+  if (endTime) {
+    queryParams.value.endTime = endTime as string;
+  }
+  if (flagType) {
+    queryParams.value.flagType = flagType as string;
+  }
+  getList();
+  loadItemOptions();
+});
+// 监听路由参数变化
+watch(
+  () => route.query,
+  (newQuery, oldQuery) => {
+    const flagType = route.query.timeFilter;
+    const startTime = route.query.startTime;
+    const endTime = route.query.endTime;
+    // 根据参数设置查询条件
+    if (startTime) {
+      queryParams.value.startTime = startTime as string; // 直接赋值字符串
+    } else {
+    }
+    // 处理结束时间(如果需要)
+    if (endTime) {
+      queryParams.value.endTime = endTime as string;
+    }
+    if (flagType) {
+      queryParams.value.flagType = flagType as string;
+    }
+    getList();
+  },
+  { immediate: true } // 立即执行一次,确保初始参数被处理
+);
+</script>