|
@@ -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>
|