Bladeren bron

添加cta机器人页面

DESKTOP-NE65RNK\Citrus_limon 7 maanden geleden
bovenliggende
commit
33b3c16a61
5 gewijzigde bestanden met toevoegingen van 858 en 4 verwijderingen
  1. 38 4
      src/api/index.ts
  2. 6 0
      src/router/routes.ts
  3. 163 0
      src/views/bot/cta/components/Update.vue
  4. 332 0
      src/views/bot/cta/detail.vue
  5. 319 0
      src/views/bot/cta/index.vue

+ 38 - 4
src/api/index.ts

@@ -195,6 +195,44 @@ export const get_arbitrage_robot_detail_balance = (params: any, callback: any) =
   });
 };
 
+// CTA机器人管理
+// 机器人管理-CTA列表
+export const get_cta_robot_list = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 机器人管理-添加CTA
+export const add_cta_robot = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 机器人管理-修改CTA
+export const update_cta_robot = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 机器人管理-删除CTA
+export const delete_cta_robot = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 机器人管理-修改CTA状态
+export const set_cta_robot_status = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/status", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 机器人管理-获取CTA账户余额
+export const refresh_cta_robot_balance = (params: any, callback: any) => {
+  return http.request("/cta/transaction_config/refreshBalance", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
 // 策略管理
 // 策略管理-策略列表
 export const get_strategy_list = (params: any, callback: any) => {
@@ -538,7 +576,6 @@ export const get_as_rank_exchange = (params: any, callback: any) => {
   });
 };
 
-
 // 资金曲线
 // 资金曲线-按用户
 export const get_user_balance = (params: any, callback: any) => {
@@ -552,21 +589,18 @@ export const get_exchange_balance = (params: any, callback: any) => {
     if (data) callback && callback(data);
   });
 };
-
 // 资金曲线-套利
 export const get_arbitrage_balance = (params: any, callback: any) => {
   return http.request("/api/remaining/capitalNingBo", "get", params).then((data) => {
     if (data) callback && callback(data);
   });
 };
-
 // 利润分析查询
 export const get_exchange_pair_profit = (params: any, callback: any) => {
   return http.request("/api/profitAnalysis/getExchangePairProfitPage", "get", params).then((data) => {
     if (data) callback && callback(data);
   });
 };
-
 // 利润统计数据
 export const get_exchange_pair_profit_statistics = (params: any, callback: any) => {
   return http.request("/api/profitAnalysis/getOpenProfitStatistics", "get", params).then((data) => {

+ 6 - 0
src/router/routes.ts

@@ -46,6 +46,12 @@ const routes: Array<RouteRecordRaw> = [
         component: () => import("@/views/bot/arbitrage/index.vue"),
         meta: { title: "套利机器人管理", keepAlive: true },
       },
+      {
+        path: "/bot/cta",
+        name: "BotCta",
+        component: () => import("@/views/bot/cta/index.vue"),
+        meta: { title: "CTA机器人管理", keepAlive: true },
+      },
       {
         path: "/exchange/manage",
         name: "ExchangeManage",

+ 163 - 0
src/views/bot/cta/components/Update.vue

@@ -0,0 +1,163 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" area="auto" :btn="operator">
+    <div class="width-800 custom-layer" style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-row space="10">
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="名称" prop="name">
+              <lay-input v-model="modelParams.name" placeholder="机器人名称" />
+            </lay-form-item>
+          </lay-col>
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="币对" prop="symbol">
+              <lay-input v-model="modelParams.symbol" placeholder="交易币对" />
+            </lay-form-item>
+          </lay-col>
+          <!-- <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="服务器" prop="serverId">
+              <lay-select v-model="modelParams.serverId" :show-search="true">
+                <lay-select-option v-for="item in serverList" :value="item.id" :label="item.value" placeholder="选择服务器" />
+              </lay-select>
+            </lay-form-item>
+          </lay-col> -->
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="交易所" prop="exchange">
+              <lay-select v-model="modelParams.exchange" :show-search="true">
+                <lay-select-option v-for="(key, value) in INDICATOR_EXCHANG_TYPE" :value="key" :label="value" placeholder="选择交易所" />
+              </lay-select>
+            </lay-form-item>
+          </lay-col>
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="账户" prop="accountId">
+              <lay-select v-model="modelParams.accountId" :show-search="true">
+                <lay-select-option v-for="item in apikeyList" :value="item.value" :label="item.label" placeholder="选择账户" />
+              </lay-select>
+            </lay-form-item>
+          </lay-col>
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="资金比例" prop="fundRatio">
+              <lay-input v-model="modelParams.fundRatio" placeholder="资金比例" />
+            </lay-form-item>
+          </lay-col>
+          <lay-col md="12" sm="12" xs="24">
+            <lay-form-item label="止损" prop="stopLoss">
+              <lay-input v-model="modelParams.stopLoss" placeholder="止损比例" />
+            </lay-form-item>
+          </lay-col>
+        </lay-row>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_cta_robot, update_cta_robot, get_server_select, get_apikey_select } from "@/api";
+import { INDICATOR_EXCHANG_TYPE } from "@/utils/enum";
+
+const { proxy }: any = getCurrentInstance();
+
+const modelFormRef = ref();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  id?: number;
+  name?: string;
+  symbol?: string;
+  fundRatio?: number;
+  exchange?: string;
+  // serverId?: number;
+  accountId?: number;
+  stopLoss?: number;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+let serverList = ref();
+let apikeyList = ref();
+
+const show = async (params?: any) => {
+  console.log(params);
+  const accountId = params.configList?.find((item: any) => item.code == "account")?.val;
+  serverList.value = [];
+  apikeyList.value = [];
+  get_server_list();
+  get_apikey(accountId);
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelParams.value = modelConfig.isUpdate ? { ...params } : {};
+  modelConfig.title = !!params ? "编辑机器人" : "添加机器人";
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+// 获取服务器列表
+const get_server_list = () => {
+  const params = {};
+  get_server_select(params, (data: any) => {
+    serverList.value = data.data;
+  });
+};
+// 获取APIKEY参数下拉
+const get_apikey = (id: any) => {
+  const params = {};
+  get_apikey_select(params, (data: any) => {
+    apikeyList.value = data.data.filter((item: any) => item.inUseNum < 1 || item.id == id).map((item: any) => ({ label: item.value, value: item.id }));
+  });
+};
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelFormRef.value.validate((isValidate: boolean) => {
+        if (isValidate) {
+          modelConfig.loading = true;
+          const params = { ...modelParams.value, symbol: modelParams.value.symbol?.toLocaleUpperCase() };
+          if (modelConfig.isUpdate) {
+            update_cta_robot(params, (data: any) => {
+              modelConfig.loading = false;
+              if (data.code == 200) {
+                proxy.$message("编辑成功!");
+                modelConfig.visible = false;
+                handleResult.resolve(true);
+              }
+            });
+          } else {
+            add_cta_robot(params, (data: any) => {
+              modelConfig.loading = false;
+              if (data.code == 200) {
+                proxy.$message("添加成功!");
+                modelConfig.visible = false;
+                handleResult.resolve(true);
+              }
+            });
+          }
+        }
+      });
+    },
+  },
+  {
+    text: "取消",
+    callback: () => {
+      modelConfig.visible = false;
+      handleResult.resolve(false);
+    },
+  },
+]);
+defineExpose({ show });
+</script>
+
+<style lang="scss" scoped>
+.operator-wp {
+  padding-left: 110px;
+}
+</style>

+ 332 - 0
src/views/bot/cta/detail.vue

@@ -0,0 +1,332 @@
+<template>
+  <div class="container-wp">
+    <div class="robot-info-header">
+      <lay-space>
+        <span class="robot-name">{{ robotDetail.name }}</span>
+        <lay-tag size="sm">
+          <span class="robot-status" v-if="robotDetail.status == 'RUNNING'">
+            <lay-badge type="dot" theme="blue" ripple />
+            <span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
+          </span>
+          <span class="robot-status" v-else-if="robotDetail.status == 'ERROR'">
+            <lay-badge type="dot" ripple />
+            <span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
+          </span>
+          <span class="robot-status" v-else>
+            <lay-badge type="dot" theme="orange" ripple />
+            <span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
+          </span>
+        </lay-tag>
+      </lay-space>
+    </div>
+
+    <lay-card v-show="apiList?.includes('/remaining/list')" class="custom-card">
+      <template v-slot:title>
+        <span class="card-title">净值图</span>
+      </template>
+      <template v-slot:body>
+        <div class="profit-chart" ref="balanceChartRef"></div>
+      </template>
+    </lay-card>
+
+    <lay-card class="custom-card">
+      <template v-slot:title>
+        <span class="card-title">内部数据</span>
+      </template>
+      <template v-slot:extra>
+        <lay-button class="card-button" @click="handleReloadIframe()">{{ robotRunDataIsShow ? "刷新" : "获取" }} </lay-button>
+        <lay-button class="card-button" @click="handleOtherOpen()">站外打开</lay-button>
+      </template>
+      <template v-slot:body>
+        <div :class="{ 'card-body-wp': true, 'height-1200': robotRunDataIsShow }">
+          <iframe v-if="robotRunDataIsShow" :class="{ chart: true, 'height-1200': robotRunDataIsShow }" :src="robotRunData" frameborder="0"></iframe>
+          <div v-else>内部数据,数据可能较大,请勿频繁获取、刷新!</div>
+        </div>
+      </template>
+    </lay-card>
+
+    <lay-card v-if="apiList?.includes('/robot/getRobotLog')" class="custom-card">
+      <template v-slot:title>
+        <span class="card-title">运行日志</span>
+      </template>
+
+      <template v-slot:body>
+        <lay-table :columns="columns" size="sm" resize :data-source="logsList">
+          <template v-slot:text="{ row }">
+            <span class="ellipsis-2" @click="showLog(row.text)">{{ row.text }}</span>
+          </template>
+        </lay-table>
+      </template>
+    </lay-card>
+  </div>
+  <LogText ref="logtextRef" />
+</template>
+<script lang="ts" setup name="BotManageDetail">
+import { ref, reactive, onMounted, onUnmounted, shallowRef } from "vue";
+import { useRoute } from "vue-router";
+import * as echarts from "echarts";
+import LogText from "./components/LogText.vue";
+import { get_robot_detail, get_robot_logs, get_remaining_detail } from "@/api";
+
+const ROBOT_STATUS: any = reactive({
+  STOPPED: "已停止",
+  STOP_PENDING: "停止中",
+  RUNNING: "运行中",
+  START_PENDING: "启动中",
+  RESTART_PENDING: "重启中",
+  DOWNLOADING: "下载中",
+  ERROR: "错误",
+});
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+const balanceChartRef = ref();
+const logtextRef = ref();
+
+const route = useRoute();
+
+interface PageConfig {
+  predictorLoading: boolean;
+  robotLoading: boolean;
+  balanceLoading: boolean;
+  logsLoading: boolean;
+}
+interface Logs {
+  time?: string;
+  text?: string;
+}
+
+let pageConfig: PageConfig = reactive({
+  predictorLoading: false,
+  robotLoading: false,
+  balanceLoading: false,
+  logsLoading: false,
+});
+
+const columns = ref([
+  { title: "时间", key: "time", width: 150 },
+  { title: "日志", key: "text", customSlot: "text" },
+]);
+
+let logsList = ref<Array<Logs>>();
+let robotDetail = ref<any>({});
+let balanceList = ref([]);
+let balanceChart = shallowRef();
+let predictorChart = shallowRef();
+let timer = ref();
+let robotRunData = ref();
+let robotRunDataIsShow = ref(false);
+
+// 获取机器人详情
+const getRobotDetail = () => {
+  const params = { id: route.params.id };
+  pageConfig.robotLoading = true;
+  get_robot_detail(params, (data: any) => {
+    pageConfig.robotLoading = false;
+    if (data.code == 200) {
+      robotDetail.value = data.data;
+      document.title = data.data.name;
+      getBalanceInfo(data.data.accId);
+      robotRunData.value = `https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`;
+    }
+  });
+};
+
+const getBalanceInfo = (id: number) => {
+  const params = { id: id };
+  pageConfig.balanceLoading = true;
+  get_remaining_detail(params, (data: any) => {
+    pageConfig.balanceLoading = false;
+    if (data.code == 200) {
+      balanceList.value = data.data;
+
+      const xData = data.data.map((item: any) => item.creationTime);
+      const sData = data.data.map((item: any) => [item.creationTime, item.afterU, item.changeU, item.pair, item.openNum, item.closeNum]);
+      const yMinData = Math.min(sData.map((item: any) => item[1]));
+
+      !balanceChart.value
+        ? initBalanceChart(data.data)
+        : balanceChart.value.setOption({
+            xAxis: {
+              type: "category",
+              boundaryGap: false,
+              data: xData,
+            },
+            yAxis: {
+              type: "value",
+              boundaryGap: [0, "100%"],
+              min: yMinData,
+              interval: 1,
+            },
+            series: {
+              name: "Balance",
+              type: "line",
+              areaStyle: {},
+              lineStyle: {
+                width: 1,
+              },
+              data: sData,
+            },
+          });
+    }
+  });
+};
+
+// 请求机器人日志
+const getLogsInfo = () => {
+  const params = { id: route.params.id, n: 500 };
+  pageConfig.logsLoading = true;
+  get_robot_logs(params, (data: any) => {
+    pageConfig.logsLoading = false;
+    if (data.code == 200) {
+      logsList.value = handlePageInfo(data.data);
+    }
+  });
+};
+getLogsInfo();
+
+const showLog = (data: any) => {
+  logtextRef.value.show(data);
+};
+
+const handlePageInfo = (data: any) => {
+  let infoList = data;
+  let result = infoList.map((item: string) => {
+    return { time: item.slice(0, 18), text: item.slice(19).trim() };
+  });
+  return result;
+};
+
+const initBalanceChart = (data: any) => {
+  if (balanceChart.value != null && !balanceChart.value.isDisposed()) echarts.dispose(balanceChart.value);
+
+  balanceChart.value = echarts.init(balanceChartRef.value);
+
+  window.removeEventListener("resize", () => balanceChart.value.resize());
+  window.addEventListener("resize", () => balanceChart.value.resize());
+
+  const xData = data.map((item: any) => item.creationTime);
+  const sData = data.map((item: any) => [item.creationTime, item.afterU, item.changeU, item.pair, item.openNum, item.closeNum]);
+  const yMinData = Math.min(sData);
+
+  const balanceChartOption = {
+    tooltip: {
+      trigger: "axis",
+      axisPointer: {
+        type: "cross",
+      },
+      formatter: (params: any) => {
+        let info = params[0];
+        return `${info.marker}${info.seriesName}<br/>时间:${info.value[0]} <br/>余额:${info.value[1]}<br/>收益:${info.value[2]}<br/>币对:${info.value[3]}<br/>开仓:${info.value[4]}<br/>平仓:${info.value[5]}`;
+      },
+    },
+    dataZoom: [
+      {
+        start: 0,
+        end: 100,
+      },
+      {
+        type: "inside",
+        start: 0,
+        end: 100,
+      },
+    ],
+    xAxis: {
+      type: "category",
+      boundaryGap: false,
+      data: xData,
+    },
+    yAxis: {
+      type: "value",
+      boundaryGap: [0, "100%"],
+      min: yMinData,
+      interval: 1,
+    },
+    series: {
+      name: "Balance",
+      type: "line",
+      areaStyle: {},
+      data: sData,
+    },
+  };
+  balanceChart.value.setOption(balanceChartOption);
+};
+
+timer.value = setInterval(() => {
+  getBalanceInfo(robotDetail.value.accId);
+  getLogsInfo();
+}, 5000);
+
+getLogsInfo();
+
+const handleReloadIframe = () => {
+  robotRunDataIsShow.value = true;
+  robotRunData.value = ``;
+  setTimeout(() => {
+    robotRunData.value = `https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`; // 重新赋值
+  }, 0);
+};
+const handleOtherOpen = () => {
+  window.open(`https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`, "_blank");
+};
+
+onMounted(() => {
+  getRobotDetail();
+});
+
+onUnmounted(() => {
+  window.removeEventListener("resize", () => balanceChart.value.resize());
+  clearInterval(timer.value);
+  predictorChart.value.dispose();
+});
+</script>
+
+<style lang="scss" scoped>
+.height-1200 {
+  height: 1200px;
+}
+.card-body-wp {
+  text-align: center;
+  .chart {
+    height: 100%;
+    width: 100%;
+  }
+}
+.card-button {
+  padding: 2px 18px;
+}
+.ellipsis-2 {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  line-break: anywhere;
+  line-clamp: 2; /* 控制显示的行数 */
+  -webkit-line-clamp: 2; /* 控制显示的行数 */
+}
+.container-wp {
+  min-height: 100%;
+  padding: 20px 40px;
+  background-color: rgb(244, 246, 247);
+}
+.profit-chart {
+  height: 300px;
+}
+.predictor-chart {
+  height: 1200px;
+}
+.robot-info-header {
+  background-color: white;
+  padding: 16px 24px;
+  margin-bottom: 20px;
+  .robot-name {
+    font-size: 14px;
+    font-weight: bold;
+  }
+  .robot-status {
+    span {
+      font-size: 12px;
+      padding-left: 4px;
+    }
+  }
+}
+</style>

+ 319 - 0
src/views/bot/cta/index.vue

@@ -0,0 +1,319 @@
+<template>
+  <lay-card class="custom-card">
+    <template v-slot:title>
+      <span class="card-title">CTA机器人管理</span>
+    </template>
+    <template v-slot:extra>
+      <lay-button class="card-button" @click="handleUpdate(0)">添加</lay-button>
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline" size="sm">
+          <lay-form-item label="名称" prop="name">
+            <lay-input v-model="pageParams.name" />
+          </lay-form-item>
+          <lay-form-item label="币对" prop="symbol">
+            <lay-input v-model="pageParams.symbol" />
+          </lay-form-item>
+          <lay-form-item label="状态" prop="robotState">
+            <lay-select v-model="pageParams.robotState" :show-search="true" allowClear>
+              <lay-select-option v-for="(value, key) of ROBOT_STATUS" :value="key" :label="value" />
+            </lay-select>
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div class="custom-operator-wp padding-bottom-10">
+        <!-- <div class="custom-group collect-wp" v-html="collectInfo" /> -->
+        <div class="custom-group">
+          <lay-space>
+            <!-- <lay-button v-if="apiList?.includes('/robot/submitStatus')" :border="'green'" size="xs" @click="handleStatus(selectedKeys, 'RUN')">开机</lay-button> -->
+            <!-- <lay-button v-if="apiList?.includes('/robot/submitStatus')" :border="'red'" size="xs" @click="handleStatus(selectedKeys, 'STOP')">停机</lay-button> -->
+            <!-- <lay-button v-if="apiList?.includes('/robot/resetCapital')" :border="'green'" size="xs" @click="handleResetCapital(selectedKeys)">复位本金</lay-button> -->
+          </lay-space>
+        </div>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize id="id" :data-source="dataSource" v-model:selected-keys="selectedKeys" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:name="{ row }">
+            <span v-if="apiList?.includes('/robot/findById')" class="normal-color" @click="jumpDetail(row)">{{ row.name }}</span>
+            <span v-else>{{ row.name }}</span>
+          </template>
+          <template v-slot:earningRate="{ row }">
+            <span :class="{ 'primary-color': row.nowAmount - row.startAmount > 0, 'danger-color': row.nowAmount - row.startAmount < 0 }">
+              {{ (((row.nowAmount - row.startAmount) / row.startAmount) * 100).toFixed(2) || 0 }}%
+            </span>
+          </template>
+          <template v-slot:configs="{ row }">
+            <lay-tooltip :content="`资金比例:${row.fundRatio}%;止损:${row.stopLoss}%;`">
+              <div class="ellipsis-2" @click="handleUpdate(row)">
+                {{ `资金比例:${row.fundRatio}%;止损:${row.stopLoss}%;` }}
+              </div>
+            </lay-tooltip>
+          </template>
+          <template v-slot:status="{ row }">
+            <lay-space v-if="row.status">
+              <lay-badge type="dot" theme="blue" ripple />
+              <span>{{ ROBOT_STATUS[row.status] }}</span>
+            </lay-space>
+            <lay-space v-else-if="!row.status">
+              <lay-badge type="dot" />
+              <span>{{ ROBOT_STATUS[row.status] }}</span>
+            </lay-space>
+          </template>
+          <template v-slot:updateTime="{ row }">
+            <span>{{ timeConverts(row.updateTime) }}</span>
+          </template>
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton :text="row.status ? '关机' : '开机'" :type="row.status ? 'danger' : 'primary'" @click="handleStatus(row)" />
+              <TableButton text="编辑" @click="handleUpdate(row)" />
+              <TableButton text="更新余额" @click="refreshBalance(row)" />
+              <TableButton v-if="row.status" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+      <!-- <div class="custom-operator-wp padding-top-10">
+        <div class="custom-group collect-wp" v-html="collectInfo" />
+        <div class="custom-group">
+          <lay-space>
+            <lay-button v-if="apiList?.includes('/robot/submitStatus')" :border="'green'" size="xs" @click="handleStatus(selectedKeys, 'RUN')">开机</lay-button>
+            <lay-button v-if="apiList?.includes('/robot/submitStatus')" :border="'red'" size="xs" @click="handleStatus(selectedKeys, 'STOP')">停机</lay-button>
+            <lay-button v-if="apiList?.includes('/robot/resetCapital')" :border="'green'" size="xs" @click="handleResetCapital(selectedKeys)">复位本金</lay-button>
+          </lay-space>
+        </div>
+      </div> -->
+    </template>
+  </lay-card>
+  <Update ref="updateRef" />
+</template>
+
+<script lang="ts" setup name="BotManage">
+import { ref, reactive, getCurrentInstance, onActivated, onDeactivated } from "vue";
+import Update from "./components/Update.vue";
+import TableButton from "@/components/TableButton.vue";
+import { timeConverts } from "@/utils/index";
+import { get_cta_robot_list, delete_cta_robot, set_cta_robot_status, refresh_cta_robot_balance } from "@/api";
+
+const ROBOT_STATUS: any = reactive({
+  0: "已停止",
+  1: "运行中",
+});
+
+const { proxy }: any = getCurrentInstance();
+const updateRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+  requestLoading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+  requestLoading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  name?: String;
+  symbol?: String;
+  robotState?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 50 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 50, total: 0, limits: [20, 50, 100, 200, 500] });
+const columns = ref([
+  { title: "选项", width: "44px", type: "checkbox" },
+  { title: "ID", width: "50px", key: "id" },
+  { title: "名称", key: "name", ellipsisTooltip: true },
+  { title: "账户", key: "accountName", ellipsisTooltip: true },
+  { title: "币对", key: "symbol", align: "center", ellipsisTooltip: true },
+  { title: "初始余额", width: "100px", key: "startAmount", align: "center" },
+  { title: "当前余额", width: "100px", key: "nowAmount", align: "center" },
+  { title: "收益", width: "70px", key: "earningRate", customSlot: "earningRate", align: "center" },
+  { title: "配置信息", width: "200px", key: "configs", customSlot: "configs" },
+  { title: "状态", width: "90px", key: "status", customSlot: "status", align: "center" },
+  { title: "IP", width: "120px", key: "serverName", ellipsisTooltip: true },
+  // { title: "持仓信息", width: "80px", key: "posNum", customSlot: "posNum", align: "center", ellipsisTooltip: true },
+  { title: "修改时间", width: "90px", key: "updateTime", customSlot: "updateTime", align: "center" },
+  // { title: "所属人", width: "80px", key: "userName", align: "center", ellipsisTooltip: true },
+  {
+    title: "操作",
+    width: "200px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+
+let dataSource = ref([]);
+let selectedKeys = ref([]);
+// let collectInfo = ref(`[0/0] 利润:<span class="primary-color">0(0%)</span> 初始:<span class="primary-color">0</span> 现有:<span class="primary-color">0</span>`);
+// let exchangeList = ref();
+let refreshAsBotInterval = ref();
+
+// 请求机器人列表
+const getPageInfo = (isSearch?: boolean, isRefresh?: boolean) => {
+  if (isSearch) {
+    pageParams.pageNum = 1;
+    selectedKeys.value = [];
+    pageConfig.requestLoading = false;
+  }
+  if (pageConfig.requestLoading) return;
+
+  pageConfig.requestLoading = true;
+  pageConfig.loading = true && !isRefresh;
+  get_cta_robot_list(pageParams, (data: any) => {
+    pageConfig.requestLoading = false;
+    pageConfig.loading = false && !isRefresh;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+      // handleShowInfo(data.data);
+    }
+  });
+};
+getPageInfo();
+
+const jumpDetail = (info: any) => {
+  window.open(`/bot/as/detail/${info.id}`);
+};
+
+// const handleShowInfo = (info: any) => {
+//   document.title = `[${info.runNum || 0}/${info.total || 0}] 利润:${info.income || 0}(${info.incomeRate || 0}%)
+//     初始:${info.startAmount || 0} 现有:${info.nowAmount || 0}`;
+//   collectInfo.value = `[${info.runNum || 0}/${info.total || 0}] 利润:<span class="${info.income >= 0 ? "primary-color" : "danger-color"}">${info.income || 0}(${info.incomeRate || 0}%)</span>
+//     初始:<span class="primary-color">${info.startAmount || 0}</span> 现有:<span class="primary-color">${info.nowAmount || 0}</span>`;
+// };
+
+const handleStatus = async (value: any) => {
+  let result = await proxy.$waitingConfirm(`是否确认${value.status ? "关机" : "开机"}?`);
+  if (!result) return;
+  let params = {
+    id: value.id,
+    status: value.status ? 0 : 1,
+  };
+  pageConfig.loading = true;
+  set_cta_robot_status(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`修改成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+const handleUpdate = async (value?: any) => {
+  const result = await updateRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+// 更新余额
+const refreshBalance = async (value?: any) => {
+  let result = await proxy.$waitingConfirm("是否确认更新该机器人余额?");
+  if (!result) return;
+  let params = { id: value.id };
+  pageConfig.loading = true;
+  await refresh_cta_robot_balance(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`更新成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 删除机器人
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该机器人?");
+  if (!result) return;
+  let params = [value.id];
+  pageConfig.loading = true;
+  delete_cta_robot(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`删除成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  pageParams.pageSize = val.limit;
+  getPageInfo();
+};
+
+const startInterval = () => {
+  clearInterval(refreshAsBotInterval.value);
+  refreshAsBotInterval.value = setInterval(() => {
+    getPageInfo(false, true);
+  }, 10000);
+};
+startInterval();
+const closeInterval = () => {
+  clearInterval(refreshAsBotInterval.value);
+};
+onActivated(() => {
+  startInterval();
+});
+onDeactivated(() => {
+  closeInterval();
+  document.title = "4L CAPITAL";
+});
+</script>
+
+<style lang="scss" scoped>
+.custom-operator-wp {
+  .custom-group {
+    margin-right: 14px;
+    display: inline-block;
+    vertical-align: middle;
+  }
+  .collect-wp {
+    line-height: 24px;
+    padding: 0 4px;
+    border: 1px solid var(--normal-color);
+    color: var(--normal-color);
+    :deep(.primary-color) {
+      color: var(--primary-color);
+    }
+    :deep(.danger-color) {
+      color: var(--danger-color);
+    }
+  }
+}
+.ellipsis-2 {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  line-break: anywhere;
+  line-clamp: 2; /* 控制显示的行数 */
+  -webkit-line-clamp: 2; /* 控制显示的行数 */
+}
+.custom-form-layout {
+  font-size: 12px !important;
+}
+.primary-color {
+  color: var(--primary-color);
+}
+.normal-color {
+  color: var(--normal-color);
+}
+.danger-color {
+  color: var(--danger-color);
+}
+</style>