diff --git a/src/api/system/recharge.js b/src/api/system/recharge.js
index e9509f6..9da4992 100644
--- a/src/api/system/recharge.js
+++ b/src/api/system/recharge.js
@@ -79,3 +79,13 @@ export function refundBill(billId, refundAmount) {
     }
   })
 }
+
+
+// 关闭订单
+export function closeBill(data) {
+  return request({
+    url: `/system/bill/close`,
+    method: 'put',
+    data
+  })
+}
diff --git a/src/utils/constants.js b/src/utils/constants.js
index bb60b8b..b703d33 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -110,3 +110,16 @@ export const PayBillStatus = {
   REFUNDING: "5", // 退款中
   REFUNDED: "6", // 已退款
 }
+
+// 充值订单状态
+export const RechargeStatus = {
+  WAIT_PAY: "1", // 未支付
+  PAY_SUCCESS: "2", // 支付成功
+  REFUNDED: "3", // 已退款
+  USER_CANCEL: "4", // 用户取消
+  SYS_CANCEL: "5",  // 系统超时取消
+  PAYING: "6", // 支付中
+  REFUNDING: "7", // 退款中
+  DEPOSIT_WAIT_PAY: "8",  // 押金待支付
+  DEPOSIT_SUCCESS: "9", // 押金已支付
+}
diff --git a/src/views/system/recharge/index.vue b/src/views/system/recharge/index.vue
index 7b869a8..4a8026b 100644
--- a/src/views/system/recharge/index.vue
+++ b/src/views/system/recharge/index.vue
@@ -88,70 +88,60 @@
           v-hasPermi="['system:bill:export']"
         >导出</el-button>
       </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="billList" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" :data="billList" @selection-change="handleSelectionChange" :default-sort="defaultSort"  @sort-change="onSortChange">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="订单ID" align="center" prop="billId" width="80"/>
-      <el-table-column label="时间" align="center" prop="createTime" width="180"/>
-      <el-table-column label="订单编号" align="center" prop="billNo" min-width="130">
-        <recharge-link slot-scope="d" :bill-id="d.row.billId" :text="d.row.billNo"/>
-      </el-table-column>
-      <el-table-column label="充值用户" align="center" prop="userName" >
-        <user-link slot-scope="d" :id="d.row.userId" :name="d.row.userName"/>
-      </el-table-column>
-      <el-table-column label="设备名称/SN" align="center" prop="deviceName" width="180">
-        <device-link slot-scope="d" :id="d.row.deviceId" :text="`${d.row.deviceName ? d.row.deviceName : '--'} (${d.row.deviceNo})`"/>
-      </el-table-column>
-      <el-table-column label="收款用户" align="center" prop="mchName" >
-        <user-link slot-scope="d" :id="d.row.mchId" :name="d.row.mchName"/>
-      </el-table-column>
-      <el-table-column label="交易金额" align="center">
-        <template slot-scope="d">
-          {{d.row.money | money}} 元
-        </template>
-      </el-table-column>
-      <el-table-column label="到账金额" align="center">
-        <template slot-scope="d">
-          {{d.row.arrivalAmount | money}} 元
-        </template>
-      </el-table-column>
-      <el-table-column label="手续费" align="center">
-        <template slot-scope="d">
-          {{d.row.serviceCharge | money | defaultValue}} 元
-        </template>
-      </el-table-column>
-      <el-table-column label="成本" align="center">
-        <template slot-scope="d">
-          {{d.row.channelCost | money}} 元
-        </template>
-      </el-table-column>
-      <el-table-column label="利润" align="center">
-        <template slot-scope="d">
-          {{d.row.serviceCharge - d.row.channelCost | money}} 元
-        </template>
-      </el-table-column>
-      <el-table-column label="收费模式" align="center">
-        <template slot-scope="d">
-          <dict-tag :value="d.row.suitFeeMode" :options="dict.type.suit_fee_mode"/>
-        </template>
-      </el-table-column>
-      <el-table-column label="收费方式" align="center">
-        <template slot-scope="d">
-          <dict-tag :value="d.row.suitFeeType" :options="dict.type.suit_fee_type"/>
-        </template>
-      </el-table-column>
-      <el-table-column label="交易状态" align="center">
-        <template slot-scope="d">
-          <dict-tag :value="d.row.status" :options="dict.type.sm_transaction_bill_status"/>
-        </template>
-      </el-table-column>
-      <el-table-column label="使用中" align="center">
-        <template slot-scope="d">
-          <el-tag :type="d.row.isUsing ? 'success' : 'danger'">{{d.row.isUsing ? '是' : '否'}}</el-tag>
-        </template>
-      </el-table-column>
+      <template v-for="column of showColumns">
+        <el-table-column
+          :key="column.key"
+          :label="column.label"
+          :prop="column.key"
+          :align="column.align"
+          :min-width="column.minWidth"
+          :sort-orders="orderSorts"
+          :sortable="column.sortable"
+          :show-overflow-tooltip="column.overflow"
+          :width="column.width"
+        >
+          <template slot-scope="d">
+            <template v-if="column.key === 'billId'">
+              {{d.row[column.key]}}
+            </template>
+            <template v-else-if="column.key === 'billNo'">
+              <recharge-link :bill-id="d.row.billId" :text="d.row.billNo"/>
+            </template>
+            <template v-else-if="column.key === 'userName'">
+              <user-link :id="d.row.userId" :name="d.row.userName"/>
+            </template>
+            <template v-else-if="column.key === 'deviceName'">
+              <device-link :id="d.row.deviceId" :text="`${d.row.deviceName ? d.row.deviceName : '--'} (${d.row.deviceNo})`"/>
+            </template>
+            <template v-else-if="column.key === 'mchName'">
+              <user-link :id="d.row.mchId" :name="d.row.mchName"/>
+            </template>
+            <template v-else-if="column.key === 'suitFeeMode'">
+              <dict-tag :value="d.row.suitFeeMode" :options="dict.type.suit_fee_mode"/>
+            </template>
+            <template v-else-if="column.key === 'suitFeeType'">
+              <dict-tag :value="d.row.suitFeeType" :options="dict.type.suit_fee_type"/>
+            </template>
+            <template v-else-if="column.key === 'status'">
+              <dict-tag :value="d.row.status" :options="dict.type.sm_transaction_bill_status"/>
+            </template>
+            <template v-else-if="column.key === 'isUsing'">
+              <el-tag :type="d.row.isUsing ? 'success' : 'danger'">{{d.row.isUsing ? '是' : '否'}}</el-tag>
+            </template>
+            <template v-else-if="['money', 'serviceCharge', 'channelCost', 'arrivalAmount'].includes(column.key)">
+              {{d.row.money | money | defaultValue}} 元
+            </template>
+            <template v-else>
+              {{d.row[column.key] | defaultValue}}
+            </template>
+          </template>
+        </el-table-column>
+      </template>
       <el-table-column label="操作" align="center" fixed="right" width="200">
         <template slot-scope="d">
           <el-button
@@ -171,18 +161,26 @@
           <el-button
             size="small"
             type="text"
-            icon="el-icon-refresh"
+            icon="el-icon-s-promotion"
             @click="rechargeDevice(d.row.billId)"
             v-if="canRechargeDevice(d.row)"
           >手动设备充值</el-button>
           <el-button
             size="small"
             type="text"
-            icon="el-icon-refresh"
+            icon="el-icon-bank-card"
             @click="handleRefund(d.row)"
             v-has-permi="['system:bill:refund']"
             v-show="canRefund(d.row)"
           >订单退款</el-button>
+          <el-button
+            size="small"
+            type="text"
+            icon="el-icon-document-delete"
+            @click="handleClose(d.row)"
+            v-has-permi="['system:bill:close']"
+            v-show="canClose(d.row)"
+          >结束订单</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -231,19 +229,47 @@ import {
   updateBill,
   refreshPayResult,
   rechargeDevice,
-  refundBill
+  refundBill, closeBill
 } from '@/api/system/recharge'
 import UserLink from '@/components/Business/SmUser/UserLink.vue'
 import DeviceLink from '@/components/Business/Device/DeviceLink.vue'
 import RechargeLink from '@/components/Business/Transaction/RechargeLink.vue'
-import { SuitFeeType } from '@/utils/constants'
+import { RechargeStatus, SuitFeeType } from '@/utils/constants'
+import { $showColumns } from '@/utils/mixins'
+
+// 默认排序字段
+const defaultSort = {
+  prop: "createTime",
+  order: "descending"
+}
 
 export default {
   name: "Bill",
+  mixins: [$showColumns],
   components: { RechargeLink, DeviceLink, UserLink },
   dicts: ['channel_type','sm_transaction_bill_status', 'sm_transaction_bill_device_recharge_status', 'suit_fee_mode', 'suit_fee_type'],
   data() {
     return {
+      defaultSort,
+      // 排序方式
+      orderSorts: ['ascending', 'descending', null],
+      // 字段列表
+      columns: [
+        {key: 'billId', visible: false, label: '订单ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
+        {key: 'createTime', visible: true, label: '时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
+        {key: 'billNo', visible: true, label: '订单编号', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
+        {key: 'userName', visible: true, label: '充值用户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'deviceName', visible: true, label: '设备名称/SN', minWidth: null, sortable: true, overflow: false, align: 'center', width: "120"},
+        {key: 'mchName', visible: true, label: '商户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'money', visible: true, label: '交易金额', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'arrivalAmount', visible: true, label: '到账金额', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'serviceCharge', visible: true, label: '手续费', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'channelCost', visible: true, label: '成本', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'suitFeeMode', visible: true, label: '收费模式', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'suitFeeType', visible: true, label: '收费方式', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'status', visible: true, label: '交易状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+        {key: 'isUsing', visible: true, label: '使用中', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
+      ],
       // 遮罩层
       loading: true,
       // 选中数组
@@ -266,6 +292,8 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
+        orderByColumn: defaultSort.prop,
+        isAsc: defaultSort.order,
         userName: null,
         deviceName: null,
         landlordName: null,
@@ -301,12 +329,43 @@ export default {
       return (row) => {
         return row.status === '2';
       }
+    },
+    canClose() {
+      return (row) => {
+        return [RechargeStatus.PAY_SUCCESS, RechargeStatus.DEPOSIT_SUCCESS].includes(row.status) && !row.isFinished;
+      }
     }
   },
   created() {
     this.getList();
   },
   methods: {
+    // 结束订单
+    handleClose(row) {
+      this.$confirm(`确定结束订单【${row.billNo}】吗?`, {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        closeBill({billId: row.billId, totalEle: null}).then(res => {
+          if (res.code === 200) {
+            this.$message.success("操作成功");
+            this.getList();
+          }
+        })
+      })
+    },
+    /** 当排序按钮被点击时触发 **/
+    onSortChange(column) {
+      if (column.order == null) {
+        this.queryParams.orderByColumn = defaultSort.prop;
+        this.queryParams.isAsc = defaultSort.order;
+      } else {
+        this.queryParams.orderByColumn = column.prop;
+        this.queryParams.isAsc = column.order;
+      }
+      this.getList();
+    },
     submitRefund() {
       this.refundLoading = true;
       refundBill(this.refundForm.billId, this.refundForm.refundAmount).then(res => {