Переглянути джерело

feat(firm-account): 实现企业资金流水及余额变更功能

- 新增企业资金流水API,包括分页查询和批量删除接口
- 企业信息API新增上账(充值)、下账(扣款)及余额变更接口
- 企业资金流水列表页新增,支持收支类型、变动类型筛选和自定义数据展示
- 企业管理页新增余额、累计充值、当前累计盈利相关字段及显示模板
- 新增上账及下账弹窗,支持金额输入及备注,联动后台接口
- 用户企业列表新增余额、累计消费、欠费字段及排序功能
- CURD组件PageContent支持排序事件传递
- 统一新增金额格式化函数,用于多处货币显示样式优化
- 优化列表列宽及部分交互逻辑,增强用户体验
SheepHy 15 годин тому
батько
коміт
035ada73a3

+ 74 - 0
src/api/toBManage/firm-account-log-api.ts

@@ -0,0 +1,74 @@
+import request from "@/utils/request";
+
+const FIRM_ACCOUNT_LOG_BASE_URL = "/api/v1/firmAccountLog";
+
+const FirmAccountLogAPI = {
+  /** 获取企业资金流水分页数据 */
+  getPage(queryParams?: FirmAccountLogPageQuery) {
+    return request<any, PageResult<FirmAccountLogPageVO[]>>({
+      url: `${FIRM_ACCOUNT_LOG_BASE_URL}/page`,
+      method: "get",
+      params: queryParams,
+    });
+  },
+
+  /**
+   * 批量删除企业资金流水,多个以英文逗号(,)分割
+   *
+   * @param ids 企业资金流水ID字符串,多个以英文逗号(,)分割
+   */
+  deleteByIds(ids: string) {
+    return request({
+      url: `${FIRM_ACCOUNT_LOG_BASE_URL}/${ids}`,
+      method: "delete",
+    });
+  },
+};
+
+export default FirmAccountLogAPI;
+
+/** 企业资金流水分页查询参数 */
+export interface FirmAccountLogPageQuery extends PageQuery {
+  /** 企业ID */
+  firmId?: number;
+  /** 流水号 */
+  serialNo?: string;
+  /** 交易名称 */
+  name?: string;
+  /** 收支类型(1-转入 2-转出) */
+  incomeType?: number;
+  /** 变动类型 */
+  changeType?: number;
+}
+
+/** 企业资金流水分页对象 */
+export interface FirmAccountLogPageVO {
+  /** 主键ID */
+  id?: number;
+  /** 企业ID */
+  firmId?: number;
+  /** 企业名称 */
+  firmName?: string;
+  /** 项目名称 */
+  projectName?: string;
+  /** 交易名称 */
+  name?: string;
+  /** 流水号 */
+  serialNo?: string;
+  /** 收支类型(1-转入 2-转出) */
+  incomeType?: number;
+  /** 变动前金额 */
+  beforeChange?: number;
+  /** 变动后金额 */
+  afterChange?: number;
+  /** 变动金额 */
+  moneyChange?: number;
+  /** 变动类型 */
+  changeType?: number;
+  /** 关联ID */
+  childId?: number;
+  /** 备注 */
+  remark?: string;
+  /** 创建时间 */
+  createTime?: Date;
+}

+ 71 - 0
src/api/toBManage/firm-info-api.ts

@@ -62,6 +62,49 @@ const FirmInfoAPI = {
       method: "delete",
     });
   },
+
+  /**
+   * 企业上账(充值)
+   *
+   * @param firmId 企业ID
+   * @param amount 上账金额
+   * @param remark 备注
+   */
+  recharge(firmId: number, amount: number, remark?: string) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}/recharge`,
+      method: "post",
+      params: { firmId, amount, remark },
+    });
+  },
+
+  /**
+   * 企业下账(扣款)
+   *
+   * @param firmId 企业ID
+   * @param amount 下账金额
+   * @param remark 备注
+   */
+  deduct(firmId: number, amount: number, remark?: string) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}/deduct`,
+      method: "post",
+      params: { firmId, amount, remark },
+    });
+  },
+
+  /**
+   * 企业余额变更(通用)
+   *
+   * @param data 余额变更表单数据
+   */
+  changeBalance(data: FirmBalanceChangeForm) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}/changeBalance`,
+      method: "post",
+      data,
+    });
+  },
 };
 
 export default FirmInfoAPI;
@@ -76,6 +119,8 @@ export interface FirmInfoPageQuery extends PageQuery {
 
 /** 企业信息表单对象 */
 export interface FirmInfoForm {
+  /** 主键ID */
+  id?: number;
   /** 部门id */
   deptId?: number;
   /** B端企业名称 */
@@ -86,10 +131,36 @@ export interface FirmInfoForm {
 
 /** 企业信息分页对象 */
 export interface FirmInfoPageVO {
+  /** 主键ID */
+  id?: number;
   /** B端企业名称 */
   name?: string;
+  /** 员工数 */
+  empNum?: number;
   /** 状态 0 已下线  1 上线中 */
   status?: number;
+  /** 当前余额 */
+  balance?: number;
+  /** 累计充值 */
+  totalRecharge?: number;
+  /** 当前盈利 */
+  currentProfit?: number;
+  /** 累计盈利 */
+  totalProfit?: number;
   /** 创建时间 */
   createTime?: Date;
 }
+
+/** 企业余额变更表单 */
+export interface FirmBalanceChangeForm {
+  /** 企业ID */
+  firmId: number;
+  /** 变更金额(正数) */
+  amount: number;
+  /** 收支类型(1-上账/转入 2-下账/转出) */
+  incomeType: number;
+  /** 改变类型(1-充值 2-提现 3-其他) */
+  changeType?: number;
+  /** 备注 */
+  remark?: string;
+}

+ 18 - 0
src/api/toBManage/user-firm-api.ts

@@ -82,6 +82,10 @@ export interface UserFirmPageQuery extends PageQuery {
   phone?: string;
   /** 身份 1 管理员 2普通员工 */
   type?: number;
+  /** 排序字段 */
+  sortField?: string;
+  /** 排序方式: ascending/descending */
+  sortOrder?: string;
 }
 
 /** 企业与用户关系表单对象 */
@@ -96,10 +100,24 @@ export interface UserFirmForm {
 
 /** 企业与用户关系分页对象 */
 export interface UserFirmPageVO {
+  /** 主键ID */
+  id?: number;
   /** 用户id */
   userId?: number;
   /** 手机号 */
   phone?: string;
+  /** 企业ID */
+  firmId?: number;
+  /** 企业名称 */
+  firmName?: string;
   /** 身份 1 管理员 2普通员工 */
   type?: number;
+  /** 当前余额 */
+  balance?: number;
+  /** 累计消费 */
+  totalConsumption?: number;
+  /** 欠费金额 */
+  amountOwed?: number;
+  /** 创建时间 */
+  createTime?: Date;
 }

+ 7 - 0
src/components/CURD/PageContent.vue

@@ -50,6 +50,7 @@
       class="flex-1"
       @selection-change="handleSelectionChange"
       @filter-change="handleFilterChange"
+      @sort-change="handleSortChange"
     >
       <template v-for="col in cols" :key="col.prop">
         <el-table-column v-if="col.show" v-bind="col">
@@ -362,6 +363,7 @@ const emit = defineEmits<{
   editClick: [row: IObject];
   filterChange: [data: IObject];
   operateClick: [data: IOperateData];
+  sortChange: [data: { prop: string; order: string | null }];
 }>();
 
 // 表格工具栏按钮配置
@@ -867,6 +869,11 @@ function getFilterParams() {
   return filterParams;
 }
 
+// 排序处理
+function handleSortChange({ prop, order }: { prop: string; order: string | null }) {
+  emit("sortChange", { prop, order });
+}
+
 // 获取分页数据
 let lastFormData = {};
 function fetchPageData(formData: IObject = {}, isRestart = false) {

+ 226 - 0
src/views/toBManage/firm-account-log/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="app-container h-full flex flex-1 flex-col">
+    <!-- 搜索 -->
+    <page-search
+      ref="searchRef"
+      :search-config="searchConfig"
+      @query-click="handleQueryClick"
+      @reset-click="handleResetClick"
+    ></page-search>
+
+    <!-- 列表 -->
+    <page-content
+      ref="contentRef"
+      :content-config="contentConfig"
+      @search-click="handleSearchClick"
+      @toolbar-click="handleToolbarClick"
+      @filter-change="handleFilterChange"
+    >
+      <template #incomeType="scope">
+        <el-tag :type="scope.row.incomeType === 1 ? 'success' : 'danger'">
+          {{ scope.row.incomeType === 1 ? "转入" : "转出" }}
+        </el-tag>
+      </template>
+      <template #moneyChange="scope">
+        <span
+          :style="{ color: scope.row.incomeType === 1 ? '#67c23a' : '#f56c6c', fontWeight: 'bold' }"
+        >
+          {{ scope.row.incomeType === 1 ? "+" : "-"
+          }}{{ scope.row.moneyChange?.toFixed(2) || "0.00" }}
+        </span>
+      </template>
+      <template #beforeChange="scope">
+        <span>¥ {{ scope.row.beforeChange?.toFixed(2) || "0.00" }}</span>
+      </template>
+      <template #afterChange="scope">
+        <span style="color: #409eff; font-weight: bold">
+          ¥ {{ scope.row.afterChange?.toFixed(2) || "0.00" }}
+        </span>
+      </template>
+      <template #changeType="scope">
+        <el-tag type="info">{{ getChangeTypeLabel(scope.row.changeType) }}</el-tag>
+      </template>
+    </page-content>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({ name: "FirmAccountLog" });
+import FirmAccountLogAPI, { FirmAccountLogPageQuery } from "@/api/toBManage/firm-account-log-api";
+import type { IObject, IContentConfig, ISearchConfig } from "@/components/CURD/types";
+import usePage from "@/components/CURD/usePage";
+
+// 组合式 CRUD
+const {
+  searchRef,
+  contentRef,
+  handleQueryClick,
+  handleResetClick,
+  handleSearchClick,
+  handleFilterChange,
+} = usePage();
+
+// 变动类型映射
+const changeTypeMap: Record<number, string> = {
+  1: "平台充值",
+  2: "平台扣款",
+  3: "用户充电消费",
+  4: "充电订单退款",
+  5: "提现",
+  6: "分成收益",
+  7: "其他",
+};
+
+const getChangeTypeLabel = (type: number | undefined) => {
+  return type ? changeTypeMap[type] || "未知" : "未知";
+};
+
+// 搜索配置
+const searchConfig: ISearchConfig = reactive({
+  permPrefix: "business:firm-account-log",
+  formItems: [
+    {
+      type: "input",
+      label: "流水号",
+      prop: "serialNo",
+      attrs: {
+        placeholder: "流水号",
+        clearable: true,
+        style: { width: "200px" },
+      },
+    },
+    {
+      type: "input",
+      label: "交易名称",
+      prop: "name",
+      attrs: {
+        placeholder: "交易名称",
+        clearable: true,
+        style: { width: "200px" },
+      },
+    },
+    {
+      type: "select",
+      label: "收支类型",
+      prop: "incomeType",
+      attrs: {
+        placeholder: "请选择收支类型",
+        clearable: true,
+        style: { width: "150px" },
+      },
+      options: [
+        { label: "转入", value: 1 },
+        { label: "转出", value: 2 },
+      ],
+    },
+    {
+      type: "select",
+      label: "变动类型",
+      prop: "changeType",
+      attrs: {
+        placeholder: "请选择变动类型",
+        clearable: true,
+        style: { width: "150px" },
+      },
+      options: [
+        { label: "平台充值", value: 1 },
+        { label: "平台扣款", value: 2 },
+        { label: "用户充电消费", value: 3 },
+        { label: "充电订单退款", value: 4 },
+        { label: "提现", value: 5 },
+        { label: "分成收益", value: 6 },
+        { label: "其他", value: 7 },
+      ],
+    },
+  ],
+});
+
+// 列表配置
+const contentConfig: IContentConfig<FirmAccountLogPageQuery> = reactive({
+  // 权限前缀
+  permPrefix: "business:firm-account-log",
+  table: {
+    border: true,
+    highlightCurrentRow: true,
+  },
+  // 主键
+  pk: "id",
+  // 列表查询接口
+  indexAction: FirmAccountLogAPI.getPage,
+  // 删除接口
+  deleteAction: FirmAccountLogAPI.deleteByIds,
+  // 数据解析函数
+  parseData(res: any) {
+    return {
+      total: res.total,
+      list: res.list,
+    };
+  },
+  // 分页配置
+  pagination: {
+    background: true,
+    layout: "total, sizes, prev, pager, next, jumper",
+    pageSize: 20,
+    pageSizes: [10, 20, 30, 50],
+  },
+  // 工具栏配置
+  toolbar: ["delete"],
+  defaultToolbar: ["refresh", "filter"],
+  // 表格列配置
+  cols: [
+    { type: "selection", width: 55, align: "center" },
+    { label: "企业名称", prop: "firmName", width: 150 },
+    { label: "交易名称", prop: "name", width: 150 },
+    { label: "流水号", prop: "serialNo", width: 180 },
+    {
+      label: "收支类型",
+      prop: "incomeType",
+      width: 100,
+      templet: "custom",
+      slotName: "incomeType",
+    },
+    {
+      label: "变动金额",
+      prop: "moneyChange",
+      width: 120,
+      templet: "custom",
+      slotName: "moneyChange",
+    },
+    {
+      label: "变动前金额",
+      prop: "beforeChange",
+      width: 120,
+      templet: "custom",
+      slotName: "beforeChange",
+    },
+    {
+      label: "变动后金额",
+      prop: "afterChange",
+      width: 120,
+      templet: "custom",
+      slotName: "afterChange",
+    },
+    {
+      label: "变动类型",
+      prop: "changeType",
+      width: 120,
+      templet: "custom",
+      slotName: "changeType",
+    },
+    { label: "备注", prop: "remark", minWidth: 150 },
+    { label: "创建时间", prop: "createTime", width: 170 },
+    {
+      label: "操作",
+      prop: "operation",
+      width: 100,
+      templet: "tool",
+      operat: ["delete"],
+    },
+  ],
+});
+
+// 处理工具栏按钮点击
+const handleToolbarClick = (name: string) => {
+  console.log(name);
+};
+</script>

+ 179 - 4
src/views/toBManage/firm-info/index.vue

@@ -24,6 +24,22 @@
           {{ scope.row.status == 0 ? "已下线" : "上线中" }}
         </el-tag>
       </template>
+      <template #balance="scope">
+        <span class="money-text primary">¥ {{ formatMoney(scope.row.balance) }}</span>
+      </template>
+      <template #totalRecharge="scope">
+        <span class="money-text success">¥ {{ formatMoney(scope.row.totalRecharge) }}</span>
+      </template>
+      <template #currentProfit="scope">
+        <span :class="['money-text', scope.row.currentProfit >= 0 ? 'success' : 'danger']">
+          {{ scope.row.currentProfit >= 0 ? '+' : '' }}¥ {{ formatMoney(scope.row.currentProfit) }}
+        </span>
+      </template>
+      <template #totalProfit="scope">
+        <span :class="['money-text', scope.row.totalProfit >= 0 ? 'success' : 'danger']">
+          {{ scope.row.totalProfit >= 0 ? '+' : '' }}¥ {{ formatMoney(scope.row.totalProfit) }}
+        </span>
+      </template>
     </page-content>
 
     <!-- 新增 -->
@@ -58,6 +74,30 @@
         <el-button @click="dialogTableVisible = false">取消</el-button>
       </template>
     </el-dialog>
+
+    <!-- 上账/下账弹窗 -->
+    <el-dialog v-model="balanceDialogVisible" :title="balanceDialogType === 1 ? '上账(充值)' : '下账(扣款)'" width="500px">
+      <el-form ref="balanceFormRef" :model="balanceForm" :rules="balanceFormRules" label-width="100px">
+        <el-form-item label="企业名称">
+          <el-input v-model="balanceForm.firmName" disabled />
+        </el-form-item>
+        <el-form-item label="当前余额">
+          <el-input v-model="balanceForm.currentBalance" disabled>
+            <template #suffix>元</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item :label="balanceDialogType === 1 ? '上账金额' : '下账金额'" prop="amount">
+          <el-input-number v-model="balanceForm.amount" :min="0.01" :precision="2" :step="100" style="width: 100%" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="balanceForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="balanceDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="balanceLoading" @click="handleBalanceSubmit">确定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -87,6 +127,15 @@ const {
   handleFilterChange,
 } = usePage();
 
+// 金额格式化函数
+const formatMoney = (value: number | undefined | null): string => {
+  if (value === null || value === undefined) return "0.00";
+  return Math.abs(value).toLocaleString("zh-CN", {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2,
+  });
+};
+
 // 搜索配置
 const searchConfig: ISearchConfig = reactive({
   permPrefix: "business:firm-info",
@@ -153,23 +202,69 @@ const contentConfig: IContentConfig<FirmInfoPageQuery> = reactive({
   cols: [
     { type: "selection", width: 55, align: "center" },
     { label: "企业名称", prop: "name" },
-    { label: "员工数", prop: "empNum" },
+    { label: "员工数", prop: "empNum", width: 80 },
+    {
+      label: "当前余额",
+      prop: "balance",
+      width: 120,
+      templet: "custom",
+      slotName: "balance",
+    },
+    {
+      label: "累计充值",
+      prop: "totalRecharge",
+      width: 120,
+      templet: "custom",
+      slotName: "totalRecharge",
+    },
+    {
+      label: "当前盈利",
+      prop: "currentProfit",
+      width: 120,
+      templet: "custom",
+      slotName: "currentProfit",
+    },
+    {
+      label: "累计盈利",
+      prop: "totalProfit",
+      width: 120,
+      templet: "custom",
+      slotName: "totalProfit",
+    },
     {
       label: "状态", // 0 已下线  1 上线中
       prop: "status",
+      width: 90,
       templet: "custom",
     },
-    { label: "创建时间", prop: "createTime" },
+    { label: "创建时间", prop: "createTime", width: 170 },
     {
       label: "操作",
       prop: "operation",
+      width: 280,
       templet: "tool",
       operat: [
         {
-          name: "qrCode",
-          text: "查看邀请码",
+          name: "recharge",
+          text: "上账",
           attrs: {
             type: "success",
+            icon: "Plus",
+          },
+        },
+        {
+          name: "deduct",
+          text: "下账",
+          attrs: {
+            type: "warning",
+            icon: "Minus",
+          },
+        },
+        {
+          name: "qrCode",
+          text: "邀请码",
+          attrs: {
+            type: "primary",
             icon: "Setting",
           },
         },
@@ -247,6 +342,23 @@ const editModalConfig: IModalConfig<FirmInfoForm> = reactive({
 const dialogTableVisible = ref(false);
 const firmId = ref();
 const firmName= ref();
+
+// 上账/下账相关
+const balanceDialogVisible = ref(false);
+const balanceDialogType = ref(1); // 1-上账 2-下账
+const balanceLoading = ref(false);
+const balanceFormRef = ref();
+const balanceForm = reactive({
+  firmId: 0,
+  firmName: "",
+  currentBalance: 0,
+  amount: 0,
+  remark: "",
+});
+const balanceFormRules = {
+  amount: [{ required: true, message: "请输入金额", trigger: "blur" }],
+};
+
 const handleOperateClick = (data: IObject) => {
   if (data.name === "edit") {
     handleEditClick(data.row, async () => {
@@ -258,6 +370,50 @@ const handleOperateClick = (data: IObject) => {
     firmId.value = parseInt(data.row.id);
     firmName.value = data.row.name;
   }
+  if (data.name === "recharge") {
+    balanceDialogType.value = 1;
+    balanceForm.firmId = data.row.id;
+    balanceForm.firmName = data.row.name;
+    balanceForm.currentBalance = data.row.balance || 0;
+    balanceForm.amount = 0;
+    balanceForm.remark = "";
+    balanceDialogVisible.value = true;
+  }
+  if (data.name === "deduct") {
+    balanceDialogType.value = 2;
+    balanceForm.firmId = data.row.id;
+    balanceForm.firmName = data.row.name;
+    balanceForm.currentBalance = data.row.balance || 0;
+    balanceForm.amount = 0;
+    balanceForm.remark = "";
+    balanceDialogVisible.value = true;
+  }
+};
+
+// 提交上账/下账
+const handleBalanceSubmit = async () => {
+  if (!balanceFormRef.value) return;
+  await balanceFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      balanceLoading.value = true;
+      try {
+        if (balanceDialogType.value === 1) {
+          await FirmInfoAPI.recharge(balanceForm.firmId, balanceForm.amount, balanceForm.remark);
+          ElMessage.success("上账成功");
+        } else {
+          await FirmInfoAPI.deduct(balanceForm.firmId, balanceForm.amount, balanceForm.remark);
+          ElMessage.success("下账成功");
+        }
+        balanceDialogVisible.value = false;
+        // 刷新列表
+        handleQueryClick();
+      } catch (error) {
+        console.error("操作失败:", error);
+      } finally {
+        balanceLoading.value = false;
+      }
+    }
+  });
 };
 
 // 二维码海报下载
@@ -291,3 +447,22 @@ const handleToolbarClick = (name: string) => {
   console.log(name);
 };
 </script>
+
+<style scoped>
+.money-text {
+  font-weight: bold;
+  font-size: 13px;
+}
+.money-text.primary {
+  color: #409eff;
+}
+.money-text.success {
+  color: #67c23a;
+}
+.money-text.danger {
+  color: #f56c6c;
+}
+.money-text.warning {
+  color: #e6a23c;
+}
+</style>

+ 86 - 5
src/views/toBManage/user-firm/index.vue

@@ -18,12 +18,24 @@
       @toolbar-click="handleToolbarClick"
       @operate-click="handleOperateClick"
       @filter-change="handleFilterChange"
+      @sort-change="handleSortChange"
     >
       <template #type="scope">
         <el-tag :type="scope.row.type == 1 ? 'warning' : 'success'">
           {{ scope.row.type == 1 ? "管理员" : "普通员工" }}
         </el-tag>
       </template>
+      <template #balance="scope">
+        <span class="money-text primary">¥ {{ formatMoney(scope.row.balance) }}</span>
+      </template>
+      <template #totalConsumption="scope">
+        <span class="money-text warning">¥ {{ formatMoney(scope.row.totalConsumption) }}</span>
+      </template>
+      <template #amountOwed="scope">
+        <span :class="['money-text', scope.row.amountOwed > 0 ? 'danger' : 'success']">
+          ¥ {{ formatMoney(scope.row.amountOwed) }}
+        </span>
+      </template>
     </page-content>
 
     <!-- 新增 -->
@@ -77,6 +89,15 @@ const {
   handleFilterChange,
 } = usePage();
 
+// 金额格式化函数
+const formatMoney = (value: number | undefined | null): string => {
+  if (value === null || value === undefined) return "0.00";
+  return Math.abs(value).toLocaleString("zh-CN", {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2,
+  });
+};
+
 // 搜索配置
 const searchConfig: ISearchConfig = reactive({
   permPrefix: "business:user-firm",
@@ -147,18 +168,45 @@ const contentConfig: IContentConfig<UserFirmPageQuery> = reactive({
   defaultToolbar: ["refresh", "filter"],
   // 表格列配置
   cols: [
-    { type: "selection", width: 55, align: "center" },
-    { label: "用户id", prop: "userId" },
-    { label: "手机号", prop: "phone" },
+    { type: "selection", width: 50, align: "center" },
+    { label: "用户ID", prop: "userId", width: 80 },
+    { label: "手机号", prop: "phone", width: 120 },
+    { label: "所属企业", prop: "firmName" },
     {
-      label: "身份", // 1 管理员 2普通员工
+      label: "身份",
       prop: "type",
+      width: 120,
       templet: "custom",
     },
+    {
+      label: "当前余额",
+      prop: "balance",
+      width: 110,
+      sortable: "custom",
+      templet: "custom",
+      slotName: "balance",
+    },
+    {
+      label: "累计消费",
+      prop: "totalConsumption",
+      width: 110,
+      sortable: "custom",
+      templet: "custom",
+      slotName: "totalConsumption",
+    },
+    {
+      label: "欠费情况",
+      prop: "amountOwed",
+      width: 110,
+      sortable: "custom",
+      templet: "custom",
+      slotName: "amountOwed",
+    },
+    { label: "创建时间", prop: "createTime", width: 160 },
     {
       label: "操作",
       prop: "operation",
-      width: 220,
+      width: 120,
       templet: "tool",
       operat: ["edit", "delete"],
     },
@@ -311,4 +359,37 @@ const handleOperateClick = (data: IObject) => {
 const handleToolbarClick = (name: string) => {
   console.log(name);
 };
+
+// 排序参数
+const sortParams = reactive({
+  sortField: "",
+  sortOrder: "",
+});
+
+// 处理排序变化
+const handleSortChange = (data: { prop: string; order: string | null }) => {
+  sortParams.sortField = data.prop || "";
+  sortParams.sortOrder = data.order || "";
+  // 带着排序参数重新查询
+  contentRef.value?.fetchPageData({ ...sortParams });
+};
 </script>
+
+<style scoped>
+.money-text {
+  font-weight: bold;
+  font-size: 13px;
+}
+.money-text.primary {
+  color: #409eff;
+}
+.money-text.success {
+  color: #67c23a;
+}
+.money-text.danger {
+  color: #f56c6c;
+}
+.money-text.warning {
+  color: #e6a23c;
+}
+</style>

+ 0 - 6
vite.config.ts

@@ -11,10 +11,6 @@ import UnoCSS from "unocss/vite";
 import { resolve } from "path";
 import { name, version, engines, dependencies, devDependencies } from "./package.json";
 
-// MCP 插件:为项目开启 MCP Server(仅开发环境)
-// 使用前请先安装:pnpm add -D vite-plugin-vue-mcp
-import { VueMcp } from "vite-plugin-vue-mcp";
-
 // 平台的名称、版本、运行所需的 node 版本、依赖、构建时间的类型提示
 const __APP_INFO__ = {
   pkg: { name, version, engines, dependencies, devDependencies },
@@ -60,8 +56,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     plugins: [
       vue(),
       ...(env.VITE_MOCK_DEV_SERVER === "true" ? [mockDevServerPlugin()] : []),
-      // MCP 插件:仅在开发环境启用,用于 AI 工具集成,让 Cursor AI 能够读取应用运行时的 Store 状态,帮助调试和理解代码
-      ...(!isProduction ? [VueMcp()] : []),
       UnoCSS(),
       // API 自动导入
       AutoImport({