Bladeren bron

feat(business): 添加核销记录功能并优化用户和订单页面- 新增核销记录相关 API 和页面组件
- 优化用户页面的道具发放功能
- 更新订单页面的支付时间显示
- 添加核销记录相关的类型定义

fugui001 4 maanden geleden
bovenliggende
commit
8207af6c2d

+ 1 - 0
src/api/system/business/apiUsers/types.ts

@@ -372,4 +372,5 @@ export interface GrantForm extends BaseEntity {
   quantity?: number;
   phone?: string;
   userId?: number;
+  type?: number;
 }

+ 75 - 0
src/api/system/business/checkRecord/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { CheckRecordVO, CheckRecordForm, CheckRecordQuery, UserItemVO } from '@/api/system/business/checkRecord/types';
+
+/**
+ * 查询用户核销记录列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listCheckRecord = (query?: CheckRecordQuery): AxiosPromise<CheckRecordVO[]> => {
+  return request({
+    url: '/business/checkRecord/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询用户核销记录详细
+ * @param id
+ */
+export const getCheckRecord = (id: string | number): AxiosPromise<CheckRecordVO> => {
+  return request({
+    url: '/business/checkRecord/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增用户核销记录
+ * @param data
+ */
+export const addCheckRecord = (data: CheckRecordForm) => {
+  return request({
+    url: '/business/checkRecord',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改用户核销记录
+ * @param data
+ */
+export const updateCheckRecord = (data: CheckRecordForm) => {
+  return request({
+    url: '/business/checkRecord',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除用户核销记录
+ * @param id
+ */
+export const delCheckRecord = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/business/checkRecord/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 根据用户名/手机号查询用户道具列表
+ * @param searchUserKeyword 用户名或手机号
+ */
+export const selectPlayerItemsListByUser = (searchUserKeyword: string): AxiosPromise<UserItemVO[]> => {
+  return request({
+    url: '/business/checkRecord/selectPlayerItemsListByUser',
+    method: 'get',
+    params: { searchUserKeyword } // ✅ 查询参数用 params
+  });
+};

+ 139 - 0
src/api/system/business/checkRecord/types.ts

@@ -0,0 +1,139 @@
+export interface CheckRecordVO {
+  /**
+   *
+   */
+  id: string | number;
+
+  /**
+   * 被核销用户ID
+   */
+  userId: string | number;
+
+  /**
+   * 核销数量
+   */
+  num: number;
+
+  /**
+   * 核销道具
+   */
+  itemId: string | number;
+
+  /**
+   * 创建时间
+   */
+  createdAt: string;
+
+  /**
+   * 更新时间
+   */
+  updatedAt: string;
+
+  /**
+   * 创建人
+   */
+  createUserId: string | number;
+
+  /**
+   * 创建人名称
+   */
+  createUserName: string;
+}
+
+export interface CheckRecordForm extends BaseEntity {
+  /**
+   *
+   */
+  id?: string | number;
+
+  /**
+   * 被核销用户ID
+   */
+  userId?: string | number;
+
+  /**
+   * 核销数量
+   */
+  num?: number;
+
+  /**
+   * 核销道具
+   */
+  itemId?: string | number;
+
+  /**
+   * 创建时间
+   */
+  createdAt?: string;
+
+  /**
+   * 更新时间
+   */
+  updatedAt?: string;
+
+  /**
+   * 创建人
+   */
+  createUserId?: string | number;
+
+  /**
+   * 创建人名称
+   */
+  createUserName?: string;
+}
+
+export interface CheckRecordQuery extends PageQuery {
+  /**
+   * 被核销用户ID
+   */
+  userId?: string | number;
+
+  /**
+   * 核销数量
+   */
+  num?: number;
+
+  /**
+   * 核销道具
+   */
+  itemId?: string | number;
+
+  /**
+   * 创建时间
+   */
+  createdAt?: string;
+
+  /**
+   * 更新时间
+   */
+  updatedAt?: string;
+
+  /**
+   * 创建人
+   */
+  createUserId?: string | number;
+
+  /**
+   * 创建人名称
+   */
+  createUserName?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+
+  /**
+   * 用户名 手机号
+   */
+  userIds?: string;
+}
+
+export interface UserItemVO {
+  itemId: number;
+  itemName: string;
+  quantity: number;
+  phone: string;
+  nickName: string;
+  userId: number;
+}

+ 13 - 5
src/views/system/business/apiUsers/index.vue

@@ -20,9 +20,10 @@
               <el-date-picker
                 clearable
                 v-model="queryParams.lastLoginTime"
-                type="date"
-                value-format="YYYY-MM-DD HH:MM:SS"
+                type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss"
                 placeholder="请选择上一次登录时间"
+                format="YYYY-MM-DD HH:mm:ss"
               />
             </el-form-item>
 
@@ -106,9 +107,9 @@
                 <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:user:remove']">删除</el-button>
               </el-tooltip>
 
-              <el-tooltip content="发放道具" placement="top">
+              <el-tooltip content="编辑道具" placement="top">
                 <el-button link type="primary" icon="Tools" @click="openGrantDialog(scope.row)" v-hasPermi="['business:user:remove']"
-                  >发放道具</el-button
+                  >编辑道具</el-button
                 >
               </el-tooltip>
             </div>
@@ -208,6 +209,12 @@
                 <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="itemId">
+              <el-select v-model="grantForm.type" placeholder="请操作类型" style="width: 120px; margin-right: 8px">
+                <el-option label="增加" :value="1" />
+                <el-option label="扣除" :value="2" />
+              </el-select>
+            </el-form-item>
             <el-form-item label="数量" prop="quantity">
               <el-input-number v-model="grantForm.quantity" :min="1" :max="999999" />
             </el-form-item>
@@ -519,7 +526,8 @@ const grantForm = ref({
   itemId: null,
   quantity: 1,
   phone: null,
-  userId: null
+  userId: null,
+  type: 1
 });
 
 // 表单校验规则

+ 432 - 0
src/views/system/business/checkRecord/index.vue

@@ -0,0 +1,432 @@
+<template>
+  <div class="p-2">
+    <el-row :gutter="20">
+      <!-- 左侧操作区 -->
+      <el-col :span="6">
+        <el-card shadow="hover" class="mb-[10px]">
+          <template #header>
+            <span class="font-bold">道具核销操作</span>
+          </template>
+
+          <el-form label-width="80px" size="default">
+            <el-form-item label="选择商品">
+              <el-radio-group v-model="data2.leftForm.selectedItem">
+                <el-radio label="1001">三湘杯资格卡</el-radio>
+              </el-radio-group>
+            </el-form-item>
+
+            <el-form-item label="核销数量">
+              <el-input-number v-model="data2.leftForm.num" :min="1" placeholder="请输入数量" class="w-full" />
+            </el-form-item>
+
+            <el-form-item label="搜索用户">
+              <el-input v-model="data2.leftForm.searchUserKeyword" placeholder="用户名/手机号" clearable>
+                <template #append>
+                  <el-button icon="Search" @click="handleSearchUser()" />
+                </template>
+              </el-input>
+            </el-form-item>
+
+            <el-table :data="data2.userItemsList" border fit highlight-current-row style="width: 100%; margin-top: 10px">
+              <el-table-column prop="itemName" label="道具名称" align="center" />
+              <el-table-column prop="quantity" label="持有数量" align="center" />
+              <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template #default="scope">
+                  <el-tooltip content="核销" placement="top">
+                    <el-button link type="danger" icon="Edit" @click="dealDedution(scope.row)" v-hasPermi="['business:checkRecord:add']"
+                      >核销</el-button
+                    >
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-form>
+        </el-card>
+      </el-col>
+
+      <!-- 右侧数据展示区 -->
+      <el-col :span="18">
+        <!-- 原来的搜索 + 表格区域 -->
+        <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="data.queryParams" :inline="true">
+                <el-form-item label="" prop="userId">
+                  <el-input v-model="data.queryParams.userIds" placeholder="请输入用户名/手机号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="核销时间" prop="createdAt">
+                  <el-date-picker clearable v-model="data.queryParams.createdAt" type="date" value-format="YYYY-MM-DD" placeholder="请选择创建时间" />
+                </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">
+          <el-table v-loading="loading" border :data="checkRecordList" @selection-change="handleSelectionChange">
+            <el-table-column label="序号" width="60" align="center">
+              <template #default="{ $index }">
+                {{ $index + 1 + (data.queryParams.pageNum - 1) * data.queryParams.pageSize }}
+              </template>
+            </el-table-column>
+            <el-table-column label="用户名" align="center" prop="nickName" />
+            <el-table-column label="用户手机号" align="center" prop="phone" />
+            <el-table-column label="道具名" align="center" prop="itemsName" />
+            <el-table-column label="核销数量" align="center" prop="num" />
+            <el-table-column label="核销时间" align="center" prop="createdAt" width="180">
+              <template #default="scope">
+                <span>{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="data.queryParams.pageNum"
+            v-model:limit="data.queryParams.pageSize"
+            @pagination="getList"
+          />
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 弹窗等其他内容保持不变 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <!-- 表单内容 -->
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="CheckRecord" lang="ts">
+import {
+  listCheckRecord,
+  getCheckRecord,
+  delCheckRecord,
+  addCheckRecord,
+  updateCheckRecord,
+  selectPlayerItemsListByUser
+} from '@/api/system/business/checkRecord';
+import { CheckRecordVO, CheckRecordQuery, CheckRecordForm } from '@/api/system/business/checkRecord/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const checkRecordList = ref<CheckRecordVO[]>([]);
+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 checkRecordFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: CheckRecordForm = {
+  id: undefined,
+  userId: undefined,
+  num: undefined,
+  itemId: undefined,
+  createdAt: undefined,
+  updatedAt: undefined,
+  createUserId: undefined,
+  createUserName: undefined
+};
+const data = reactive<PageData<CheckRecordForm, CheckRecordQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    userId: undefined,
+    num: undefined,
+    itemId: undefined,
+    createdAt: undefined,
+    updatedAt: undefined,
+    createUserId: undefined,
+    createUserName: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询用户核销记录列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listCheckRecord(queryParams.value);
+  checkRecordList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  checkRecordFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: CheckRecordVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加用户核销记录';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: CheckRecordVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getCheckRecord(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改用户核销记录';
+};
+/** 提交按钮 */
+const submitForm = () => {
+  checkRecordFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateCheckRecord(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addCheckRecord(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: CheckRecordVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除用户核销记录编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delCheckRecord(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'business/checkRecord/export',
+    {
+      ...queryParams.value
+    },
+    `checkRecord_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+
+// 类型定义(建议补充)
+interface LeftSideForm {
+  selectedItem: '1001'; // 👈 设置默认选中 '1001'(即“三湘杯资格卡”)
+  num: number | null; // 核销数量
+  searchUserKeyword: string; // 搜索用户关键词
+}
+
+interface UserItemVO {
+  itemId: number;
+  itemName: string;
+  quantity: number;
+  phone: string;
+  nickName: string;
+  userId: number;
+}
+
+const initLeftFormData: LeftSideForm = {
+  selectedItem: '1001', // ✅ 直接赋值
+  num: null,
+  searchUserKeyword: ''
+};
+
+// 扩展 data 类型
+const data2 = reactive<
+  PageData<CheckRecordForm, CheckRecordQuery> & {
+    leftForm: LeftSideForm;
+    userItemsList: UserItemVO[];
+  }
+>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    userId: undefined,
+    num: undefined,
+    itemId: undefined,
+    createdAt: undefined,
+    updatedAt: undefined,
+    createUserId: undefined,
+    createUserName: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: 'ID不能为空', trigger: 'blur' }]
+  },
+  // 左侧新增字段
+  leftForm: { ...initLeftFormData },
+  userItemsList: [] // 用户拥有的道具列表
+});
+
+// 搜索用户并加载其道具列表
+const handleSearchUser = async () => {
+  data2.userItemsList = []; // 清空列表
+  const keyword = data2.leftForm.searchUserKeyword;
+  if (!keyword.trim()) {
+    ElMessage.warning('请输入用户名或手机号');
+    return;
+  }
+
+  try {
+    const res = await selectPlayerItemsListByUser(keyword);
+    data2.userItemsList = res.data; // ✅ 注意:AxiosPromise<R<T>> 返回的是 { data: T },res.data 就是 PlayerItemsVo[]
+    if (res.data.length <= 0) {
+      ElMessage.error('暂无数据');
+    }
+  } catch (error) {
+    ElMessage.error('查询失败');
+    data2.userItemsList = []; // 清空列表
+  }
+};
+const dealDedution = async (row?: UserItemVO) => {
+  if (!row) {
+    ElMessage.warning('请选择一条记录');
+    return;
+  }
+  // ✅ 校验核销数量
+  const num = data2.leftForm.num;
+
+  if (!num) {
+    ElMessage.warning('请先输入核销数量,才能核销');
+    return;
+  }
+
+  if (!Number.isInteger(Number(num)) || Number(num) <= 0) {
+    ElMessage.warning('核销数量必须是正整数');
+    return;
+  }
+  const { phone, nickName, itemName, userId, itemId } = row;
+  // 构建提示信息
+  const message = `
+    <div style="text-align: center;">
+      <p>是否扣除用户:<strong>${nickName} (${phone})</strong></p>
+      <p>[${itemName}] X ${data2.leftForm.num ?? 0}</p>
+      <p style="color: #999; margin-top: 10px;">本次操作不可逆!请确认后继续操作!</p>
+    </div>
+  `;
+
+  try {
+    // 弹出确认框
+    await ElMessageBox.confirm(message, '确认核销', {
+      confirmButtonText: '确认核销',
+      cancelButtonText: '取消',
+      type: 'warning',
+      dangerouslyUseHTMLString: true,
+      center: true,
+      customClass: 'custom-message-box' // 自定义类名,用于样式调整
+    });
+
+    // 构造核销记录数据
+    const recordData: CheckRecordForm = {
+      userId,
+      itemId,
+      num: data2.leftForm.num
+    };
+
+    // 填入 form.value 并提交
+    Object.assign(form.value, recordData);
+    buttonLoading.value = true;
+    try {
+      await addCheckRecord(form.value);
+      ElMessage.success('核销成功');
+      dialog.visible = false;
+      // 刷新核销记录列表
+      await getList();
+      // 可选:刷新左侧用户道具列表
+      handleSearchUser();
+    } catch (error) {
+      //ElMessage.error('核销失败,请重试');
+    } finally {
+      buttonLoading.value = false;
+    }
+  } catch (cancel) {
+    // 用户点击取消,不做任何事
+    ElMessage.info('已取消核销');
+  }
+};
+</script>
+<style>
+/* 在全局样式文件或当前组件的 <style> 中添加 */
+.custom-message-box {
+  .el-message-box__header {
+    border-bottom: 1px solid #ebeef5;
+  }
+
+  .el-message-box__title {
+    font-size: 16px;
+    color: #303133;
+  }
+
+  .el-message-box__content {
+    padding: 20px;
+  }
+
+  .el-message-box__btns {
+    text-align: center;
+  }
+
+  .el-button--primary {
+    background-color: #409eff;
+    border-color: #409eff;
+    color: #fff;
+  }
+
+  .el-button--default {
+    background-color: #fff;
+    border-color: #dcdfe6;
+    color: #606266;
+  }
+}
+</style>

+ 2 - 2
src/views/system/business/order/index.vue

@@ -17,7 +17,7 @@
               <el-input v-model="queryParams.tradeNo" placeholder="请输入支付宝交易号" clearable @keyup.enter="handleQuery" />
             </el-form-item>-->
             <el-form-item label="支付时间" prop="payTime">
-              <el-date-picker clearable v-model="queryParams.payTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择支付成功时间" />
+              <el-date-picker clearable v-model="queryParams.payTime" type="date" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择支付时间" />
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -75,7 +75,7 @@
         </el-table-column>
         <el-table-column label="支付时间" align="center" prop="payTime" width="180">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.payTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.payTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <!--        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">