Browse Source

添加交易所管理
添加apikey管理
添加服务器干礼

gepangpang 1 year ago
parent
commit
9b9d87ae4d

+ 46 - 2
package-lock.json

@@ -10,12 +10,15 @@
       "dependencies": {
         "@layui/layui-vue": "^2.13.0",
         "axios": "^1.6.4",
+        "codemirror": "^5.65.16",
+        "codemirror-editor-vue3": "^2.4.1",
         "js-md5": "^0.8.3",
         "pinia": "^2.1.7",
         "vue": "^3.3.11",
         "vue-router": "^4.2.5"
       },
       "devDependencies": {
+        "@types/codemirror": "^5.60.15",
         "@types/node": "^20.10.6",
         "@vitejs/plugin-vue": "^4.5.2",
         "sass": "^1.69.7",
@@ -882,6 +885,15 @@
         "win32"
       ]
     },
+    "node_modules/@types/codemirror": {
+      "version": "5.60.15",
+      "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-5.60.15.tgz",
+      "integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==",
+      "dev": true,
+      "dependencies": {
+        "@types/tern": "*"
+      }
+    },
     "node_modules/@types/eslint": {
       "version": "8.56.1",
       "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.1.tgz",
@@ -908,8 +920,7 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
-      "dev": true,
-      "peer": true
+      "dev": true
     },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
@@ -934,6 +945,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/tern": {
+      "version": "0.23.9",
+      "resolved": "https://registry.npmmirror.com/@types/tern/-/tern-0.23.9.tgz",
+      "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "*"
+      }
+    },
     "node_modules/@umijs/ssr-darkreader": {
       "version": "4.9.45",
       "resolved": "https://registry.npmmirror.com/@umijs/ssr-darkreader/-/ssr-darkreader-4.9.45.tgz",
@@ -1492,6 +1512,25 @@
         "wrap-ansi": "^6.2.0"
       }
     },
+    "node_modules/codemirror": {
+      "version": "5.65.16",
+      "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.16.tgz",
+      "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg=="
+    },
+    "node_modules/codemirror-editor-vue3": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/codemirror-editor-vue3/-/codemirror-editor-vue3-2.4.1.tgz",
+      "integrity": "sha512-XcyLUShIq22D05ySLfXGwLZqYt/HDJk11z1aAc3N/J76dEcXPd1xrUrw+v3pl+fjc8QfTjBzMSU8nKTVccJnUQ==",
+      "dependencies": {
+        "codemirror": "^5.64.0",
+        "diff-match-patch": "^1.0.5"
+      },
+      "peerDependencies": {
+        "codemirror": "^5.x",
+        "diff-match-patch": "^1.0.5",
+        "vue": "^3.x"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
@@ -1567,6 +1606,11 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/diff-match-patch": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+      "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
+    },
     "node_modules/dijkstrajs": {
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",

+ 3 - 0
package.json

@@ -15,12 +15,15 @@
   "dependencies": {
     "@layui/layui-vue": "^2.13.0",
     "axios": "^1.6.4",
+    "codemirror": "^5.65.16",
+    "codemirror-editor-vue3": "^2.4.1",
     "js-md5": "^0.8.3",
     "pinia": "^2.1.7",
     "vue": "^3.3.11",
     "vue-router": "^4.2.5"
   },
   "devDependencies": {
+    "@types/codemirror": "^5.60.15",
     "@types/node": "^20.10.6",
     "@vitejs/plugin-vue": "^4.5.2",
     "sass": "^1.69.7",

+ 183 - 0
src/api/index.ts

@@ -18,6 +18,189 @@ export const get_client_info = (params: any, callback: any) => {
   });
 };
 
+export const client_upload = (params: any, callback: any) => {
+  return http.request("/client/upload", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// 策略管理
+// 策略管理-策略列表
+export const get_strategy_list = (params: any, callback: any) => {
+  return http.request("/strategy/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-策略列表(不分页)
+export const get_strategy_list_all = (params: any, callback: any) => {
+  return http.request("/strategy/getList", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-添加策略
+export const add_strategy = (params: any, callback: any) => {
+  return http.request("/strategy/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-修改策略
+export const update_strategy = (params: any, callback: any) => {
+  return http.request("/strategy/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-删除策略
+export const delete_strategy = (params: any, callback: any) => {
+  return http.request("/strategy/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-版本列表
+export const get_strategy_program_list = (params: any, callback: any) => {
+  return http.request("/strategyProgram/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-添加版本
+export const add_strategy_program = (params: any, callback: any) => {
+  return http.request("/strategyProgram/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-编辑版本
+export const update_strategy_program = (params: any, callback: any) => {
+  return http.request("/strategyProgram/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-设置版本状态
+export const set_strategy_program_status = (params: any, callback: any) => {
+  return http.request("/strategyProgram/setStatus", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 策略管理-删除版本
+export const delete_strategy_program = (params: any, callback: any) => {
+  return http.request("/strategyProgram/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// 交易所
+// 交易所管理-交易所列表
+export const get_exchange_list = (params: any, callback: any) => {
+  return http.request("/exchange/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 交易所管理-添加交易所
+export const add_exchange = (params: any, callback: any) => {
+  return http.request("/exchange/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 交易所管理-修改交易所
+export const update_exchange = (params: any, callback: any) => {
+  return http.request("/exchange/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 交易所管理-设置交易所状态
+export const set_exchange_status = (params: any, callback: any) => {
+  return http.request("/exchange/setStatus", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 交易所管理-删除交易所
+export const delete_exchange = (params: any, callback: any) => {
+  return http.request("/exchange/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// ApiKey管理-Apikey列表
+export const get_apikey_list = (params: any, callback: any) => {
+  return http.request("/user-exchange-proof/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// ApiKey管理-添加Apikey
+export const add_apikey = (params: any, callback: any) => {
+  return http.request("/user-exchange-proof/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// ApiKey管理-修改Apikey
+export const update_apikey = (params: any, callback: any) => {
+  return http.request("/user-exchange-proof/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// ApiKey管理-删除Apikey
+export const delete_apikey = (params: any, callback: any) => {
+  return http.request("/user-exchange-proof/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// 服务器
+// 服务器管理-服务器列表
+export const get_server_list = (params: any, callback: any) => {
+  return http.request("/user-server/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 服务器管理-添加服务器
+export const add_server = (params: any, callback: any) => {
+  return http.request("/user-server/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 服务器管理-编辑服务器
+export const update_server = (params: any, callback: any) => {
+  return http.request("/user-server/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 服务器管理-删除服务器
+export const delete_server = (params: any, callback: any) => {
+  return http.request("/user-server/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// 指令管理-指令列表
+export const get_instruct_list = (params: any, callback: any) => {
+  return http.request("/server-instruct/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 指令管理-添加指令
+export const add_instruct = (params: any, callback: any) => {
+  return http.request("/server-instruct/save", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 指令管理-编辑指令
+export const update_instruct = (params: any, callback: any) => {
+  return http.request("/server-instruct/update", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+// 指令管理-删除指令
+export const delete_instruct = (params: any, callback: any) => {
+  return http.request("/server-instruct/delete", "post", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
+// 指令记录-记录列表
+export const get_instruct_log_list = (params: any, callback: any) => {
+  return http.request("/instruct-log/getPage", "get", params).then((data) => {
+    if (data) callback && callback(data);
+  });
+};
+
 // 系统管理
 // 用户管理-用户列表
 export const get_user_list = (params: any, callback: any) => {

+ 28 - 0
src/components/DragUpload.vue

@@ -0,0 +1,28 @@
+<template>
+  <lay-upload :url="uploadUrl" :drag="true" :headers="headers" :beforeUpload="handleUpload" @done="handleUploadDone" />
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+const uploadUrl = `${import.meta.env.VITE_API_BASE_URL}/client/upload`;
+const headers = {
+  auth: "4L",
+  Token: window.sessionStorage.getItem("_4L_TOKEN") || "",
+};
+const uploadValue = ref();
+
+
+const props = defineProps({
+  modelValue: String,
+});
+uploadValue.value = props.modelValue;
+
+const handleUpload = (file: any) => {
+  console.log(file);
+};
+const handleUploadDone = (e: any) => {
+  console.log(JSON.parse(e.data).data);
+  uploadValue.value = JSON.parse(e.data).data;
+  // defineEmits({"change",JSON.parse(e.data).data;});
+};
+</script>

+ 36 - 0
src/router/routes.ts

@@ -16,6 +16,42 @@ const routes: Array<RouteRecordRaw> = [
         component: () => import("@/views/dashboard/index.vue"),
         meta: { title: "控制中心", keepAlive: true },
       },
+      {
+        path: "/quant/manage",
+        name: "QuantManage",
+        component: () => import("@/views/quant/manage/index.vue"),
+        meta: { title: "策略管理", keepAlive: true },
+      },
+      {
+        path: "/exchange/manage",
+        name: "ExchangeManage",
+        component: () => import("@/views/exchange/manage/index.vue"),
+        meta: { title: "交易所管理", keepAlive: true },
+      },
+      {
+        path: "/exchange/apikey",
+        name: "ExchangeApikey",
+        component: () => import("@/views/exchange/apikey/index.vue"),
+        meta: { title: "ApiKey管理", keepAlive: true },
+      },
+      {
+        path: "/server/manage",
+        name: "ServerManage",
+        component: () => import("@/views/server/manage/index.vue"),
+        meta: { title: "服务器管理", keepAlive: true },
+      },
+      {
+        path: "/server/command",
+        name: "ServerCommand",
+        component: () => import("@/views/server/command/index.vue"),
+        meta: { title: "指令管理", keepAlive: true },
+      },
+      {
+        path: "/server/command_record",
+        name: "ServerCommandRecord",
+        component: () => import("@/views/server/command_record/index.vue"),
+        meta: { title: "指令记录", keepAlive: true },
+      },
       {
         path: "/system/user",
         name: "SystemUser",

+ 79 - 0
src/views/exchange/apikey/components/Batch.vue

@@ -0,0 +1,79 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['1000px', '550px']">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef">
+        <lay-form-item prop="remark">
+          <lay-textarea placeholder="交易所  ApiKey名称  Api  Key  Pass" v-model="modelParams.info" rows="12" />
+        </lay-form-item>
+      </lay-form>
+      <div class="tips_wp">
+        <div class="tips">一行一个,空格分割,格式:交易所 名称 Api Key Pass</div>
+        <div class="tips">推荐用XLS表格弄好直接复制进去~</div>
+        <div class="tips danger">交易所请注意大小写!!支持列表:Binance,OKEx,CoinEx,Gate,KucoinFutures,KucoinSpot,Bitget,Bybit,MXC,Huobi</div>
+      </div>
+      <div class="run_wp">
+        <lay-button type="primary">运 行</lay-button>
+      </div>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_exchange } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  info?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = () => {
+  modelConfig.visible = true;
+  modelConfig.title = "批量添加ApiKey";
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const handleInfo = (value: string) => {
+  const list = value.split(/\n+/);
+  let result = list.map((item: any) => {
+    const data = item.split(/\s+/);
+    return {
+      exchange: data[0],
+      name: data[1],
+      api: data[2],
+      key: data[3],
+      pass: data[4],
+    };
+  });
+  return result;
+};
+defineExpose({ show });
+</script>
+<style lang="scss" scoped>
+.tips_wp {
+  .tips {
+    color: var(--button-normal-color);
+    &.danger {
+      color: var(--button-danger-color);
+    }
+  }
+}
+.run_wp {
+  padding: 10px 0;
+}
+</style>

+ 100 - 0
src/views/exchange/apikey/components/Update.vue

@@ -0,0 +1,100 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['500px', '600px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="交易所名称" prop="exchangeId">
+          <lay-input v-model="modelParams.exchangeId" />
+        </lay-form-item>
+        <lay-form-item label="ApiKey名称" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="Api" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="Key" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="Pass" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="备注" prop="remark">
+          <lay-textarea placeholder="请输入备注" v-model="modelParams.remark" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_exchange, update_exchange} from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  exchangeId?: string;
+  name?: string;
+  code?: string;
+  remark?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelConfig.title = modelConfig.isUpdate ? "编辑交易所" : "添加交易所";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : { status: 1 };
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_exchange(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_exchange(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>

+ 135 - 0
src/views/exchange/apikey/index.vue

@@ -0,0 +1,135 @@
+<template>
+  <lay-card class="custom-card">
+    <template v-slot:title>
+      <span class="card-title">ApiKey管理</span>
+    </template>
+    <template v-slot:extra>
+      <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleBatch()">批量添加ApiKey</lay-button>
+      <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">添加ApiKey</lay-button>
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline">
+          <lay-form-item label="名称" prop="name">
+            <lay-input v-model="pageParams.name" />
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton v-if="apiList?.includes('/user/update')" text="编辑" @click="handleUpdate(row)" />
+              <TableButton v-if="apiList?.includes('/user/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </template>
+  </lay-card>
+  <Update ref="updateRef" />
+  <Batch ref="batchRef" />
+</template>
+
+<script lang="ts" setup name="ExchangeApikey">
+import { ref, reactive, getCurrentInstance } from "vue";
+import Update from "./components/Update.vue";
+import Batch from "./components/Batch.vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_apikey_list, delete_apikey } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const updateRef = ref();
+const batchRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  name?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "名称", width: "80px", key: "name", sort: "desc" },
+  { title: "ApiKey", width: "80px", key: "account", sort: "desc" },
+  { title: "API", width: "80px", key: "account", sort: "desc" },
+  { title: "KEY", width: "80px", key: "account", sort: "desc" },
+  { title: "PASS", width: "80px", key: "account", sort: "desc" },
+  { title: "备注", width: "80px", key: "remark", ellipsisTooltip: true },
+  { title: "更新时间", width: "80px", key: "account", sort: "desc" },
+  { title: "创建时间", width: "80px", key: "account", sort: "desc" },
+  {
+    title: "操作",
+    width: "150px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+// 请求ApiKey列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  pageConfig.loading = true;
+  get_apikey_list(pageParams, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+getPageInfo();
+
+const handleUpdate = async (value?: any) => {
+  const result = await updateRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+const handleBatch = async () => {
+  const result = await batchRef.value.show();
+  if (result) getPageInfo();
+};
+
+// 删除ApiKey
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该ApiKey?");
+  if (!result) return;
+  let params = [value.userId];
+  pageConfig.loading = true;
+  delete_apikey(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`删除成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+</script>

+ 87 - 0
src/views/exchange/manage/components/Update.vue

@@ -0,0 +1,87 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['500px', '450px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="交易所名称" prop="name">
+          <lay-input v-model="modelParams.name" />
+        </lay-form-item>
+        <lay-form-item label="交易所编码" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_exchange, update_exchange} from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  exchangeId?: string;
+  name?: string;
+  code?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelConfig.title = modelConfig.isUpdate ? "编辑交易所" : "添加交易所";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : { status: 1 };
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_exchange(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_exchange(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>

+ 145 - 0
src/views/exchange/manage/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <lay-card class="custom-card">
+    <template v-slot:title>
+      <span class="card-title">交易所管理</span>
+    </template>
+    <template v-slot:extra>
+      <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">添加交易所</lay-button>
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline">
+          <lay-form-item label="名称" prop="name">
+            <lay-input v-model="pageParams.name" />
+          </lay-form-item>
+          <lay-form-item label="编码" prop="code">
+            <lay-input v-model="pageParams.code" />
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:status="{ row }">
+            {{ row.status ? "启用" : "禁用" }}
+          </template>
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton v-if="apiList?.includes('/user/setStatus')" :text="row.status == 0 ? '启用' : '禁用'" @click="handleStatus(row)" />
+              <TableButton v-if="apiList?.includes('/user/update')" text="编辑" @click="handleUpdate(row)" />
+              <TableButton v-if="apiList?.includes('/user/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </template>
+  </lay-card>
+  <Update ref="updateRef" />
+</template>
+
+<script lang="ts" setup name="ExchangeManage">
+import { ref, reactive, getCurrentInstance } from "vue";
+import Update from "./components/Update.vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_exchange_list, delete_exchange, set_exchange_status } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const updateRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  name?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "名称", width: "80px", key: "name", sort: "desc" },
+  { title: "编码", width: "80px", key: "code", sort: "desc" },
+  { title: "状态", width: "80px", key: "status", customSlot: "status" },
+  {
+    title: "操作",
+    width: "150px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+// 请求交易所列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  pageConfig.loading = true;
+  get_exchange_list(pageParams, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+getPageInfo();
+
+const handleUpdate = async (value?: any) => {
+  const result = await updateRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+// 删除交易所
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该交易所?");
+  if (!result) return;
+  let params = [value.exchangeId];
+  pageConfig.loading = true;
+  delete_exchange(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`删除成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 修改交易所状态
+const handleStatus = async (value: any) => {
+  let result = await proxy.$waitingConfirm(`是否确认${value.status == 0 ? "启用" : "禁用"}该交易所?`);
+  if (!result) return;
+  let params = {
+    ids: [value.exchangeId],
+    status: value.status == 0 ? 1 : 0,
+  };
+  pageConfig.loading = true;
+  set_exchange_status(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`修改成功!`);
+      getPageInfo();
+    }
+  });
+};
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+</script>

+ 87 - 0
src/views/quant/manage/components/Update.vue

@@ -0,0 +1,87 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['500px', '450px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="策略名称" prop="name">
+          <lay-input v-model="modelParams.name" />
+        </lay-form-item>
+        <lay-form-item label="备注" prop="remark">
+          <lay-textarea placeholder="请输入备注" v-model="modelParams.remark" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_strategy, update_strategy} from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  id?: string;
+  name?: string;
+  remark?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelConfig.title = modelConfig.isUpdate ? "编辑策略" : "添加策略";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : { status: 1 };
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_strategy(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_strategy(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>

+ 92 - 0
src/views/quant/manage/components/UpdateVersion.vue

@@ -0,0 +1,92 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['500px', '550px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="版本名称" prop="name">
+          <lay-input v-model="modelParams.name" />
+        </lay-form-item>
+        <lay-form-item label="版本文件" prop="path">
+          <DragUpload v-model="modelParams.path" />
+        </lay-form-item>
+        <lay-form-item label="备注" prop="remark">
+          <lay-textarea placeholder="请输入备注" v-model="modelParams.remark" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_strategy, update_strategy } from "@/api";
+import DragUpload from "@/components/DragUpload.vue";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  id?: string;
+  name?: string;
+  path?: string;
+  remark?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params: any, version?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!version;
+  modelConfig.title = modelConfig.isUpdate ? "编辑版本" : "添加版本";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : { status: 1 };
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      console.log(modelParams.value)
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_strategy(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_strategy(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>

+ 155 - 0
src/views/quant/manage/components/Version.vue

@@ -0,0 +1,155 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['1200px', '600px']">
+    <lay-card class="custom-card">
+      <div class="operator-wp">
+        <lay-button class="custom-button-primary" v-if="apiList?.includes('/menu/save')" @click="handleUpdate()">添加版本</lay-button>
+      </div>
+      <div class="table-wp">
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="modelConfig.loading" @change="handleCurrentChange">
+          <template v-slot:status="{ row }">
+            {{ row.status ? "启用" : "禁用" }}
+          </template>
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton v-if="apiList?.includes('/user/setStatus')" :text="row.status == 0 ? '启用' : '禁用'" @click="handleStatus(row)" />
+              <TableButton v-if="apiList?.includes('/menu/update')" text="编辑" @click="handleUpdate(row)" />
+              <TableButton v-if="apiList?.includes('/menu/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </lay-card>
+  </lay-layer>
+  <UpdateVersion ref="updateVersionRef" />
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import UpdateVersion from "./UpdateVersion.vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_strategy_program_list, set_strategy_program_status, delete_strategy_program } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const updateVersionRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  id?: number;
+}
+let modelParams: ModelParams = reactive({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  strategyId?: number;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "版本名称", key: "name", sort: "desc" },
+  { title: "存储路径", key: "path", sort: "desc" },
+  { title: "状态", key: "status", customSlot: "status", sort: "desc" },
+  { title: "备注", key: "remark" },
+  {
+    title: "操作",
+    width: "300px",
+    customSlot: "operator",
+    key: "operator",
+    fixed: "right",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+const show = (params: ModelParams) => {
+  modelConfig.visible = true;
+  modelConfig.title = "版本管理";
+  modelParams = { ...params };
+  getPageInfo();
+};
+
+// 请求操作列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  modelConfig.loading = true;
+  pageParams.strategyId = modelParams.id;
+  get_strategy_program_list(pageParams, (data: any) => {
+    modelConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+
+// 添加/编辑操作
+const handleUpdate = async (value?: any) => {
+  const result = await updateVersionRef.value.show(modelParams, value);
+  if (result) getPageInfo();
+};
+
+// 修改版本状态
+const handleStatus = async (value: any) => {
+  let result = await proxy.$waitingConfirm(`是否确认${value.status == 0 ? "启用" : "禁用"}该版本?`);
+  if (!result) return;
+  let params = {
+    ids: [value.id],
+    status: value.status == 0 ? 1 : 0,
+  };
+  modelConfig.loading = true;
+  set_strategy_program_status(params, (data: any) => {
+    modelConfig.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];
+  modelConfig.loading = true;
+  delete_strategy_program(params, (data: any) => {
+    modelConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message("刪除成功!");
+      getPageInfo();
+    }
+  });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+defineExpose({ show });
+</script>
+
+<style lang="scss" scoped>
+.custom-card {
+  height: 100%;
+}
+.operator-wp {
+  display: flex;
+  justify-content: right;
+  padding-bottom: 20px;
+}
+</style>

+ 133 - 0
src/views/quant/manage/index.vue

@@ -0,0 +1,133 @@
+<template>
+  <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="handleUpdate()">添加策略</lay-button>
+      <!-- <lay-button class="card-button" v-if="apiList?.includes('/strategy/save')" @click="handleUpdate()">添加策略</lay-button> -->
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline">
+          <lay-form-item label="名称" prop="name">
+            <lay-input v-model="pageParams.name" />
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton text="版本管理" @click="showVersion(row)" />
+              <TableButton text="编辑" @click="handleUpdate(row)" />
+              <TableButton type="danger" text="删除" @click="handleDelete(row)" />
+              <!-- <TableButton v-if="apiList?.includes('/strategy/setStatus')" text="版本管理" @click="handleStatus(row)" />
+              <TableButton v-if="apiList?.includes('/strategy/update')" text="编辑" @click="handleUpdate(row)" />
+              <TableButton v-if="apiList?.includes('/strategy/delete')" type="danger" text="删除" @click="handleDelete(row)" /> -->
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </template>
+  </lay-card>
+  <Update ref="updateRef" />
+  <Version ref="versionRef" />
+</template>
+
+<script lang="ts" setup name="QuantManage">
+import { ref, reactive, getCurrentInstance } from "vue";
+import Update from "./components/Update.vue";
+import Version from "./components/Version.vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_strategy_list, delete_strategy } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const updateRef = ref();
+const versionRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  name?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "策略名称", width: "80px", key: "name", sort: "desc" },
+  { title: "备注", width: "80px", key: "remark", ellipsisTooltip: true },
+  {
+    title: "操作",
+    width: "150px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+// 请求策略列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  pageConfig.loading = true;
+  get_strategy_list(pageParams, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+getPageInfo();
+
+const handleUpdate = async (value?: any) => {
+  const result = await updateRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+const showVersion = async (value?: any) => {
+  const result = await versionRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+// 删除策略
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该策略?");
+  if (!result) return;
+  let params = [value.id];
+  pageConfig.loading = true;
+  delete_strategy(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`删除成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+</script>

+ 95 - 0
src/views/server/command/components/Update.vue

@@ -0,0 +1,95 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['600px', '650px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="名称" prop="title">
+          <lay-input v-model="modelParams.title" />
+        </lay-form-item>
+        <lay-form-item label="系统类型" prop="typeName">
+          <lay-input v-model="modelParams.typeName" />
+        </lay-form-item>
+        <lay-form-item label="Code" prop="code">
+          <lay-textarea v-model="modelParams.code" rows="10" />
+        </lay-form-item>
+        <lay-form-item label="备注" prop="remark">
+          <lay-textarea v-model="modelParams.remark" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_instruct, update_instruct } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  instructId?: string;
+  typeName?: string;
+  title?: string;
+  remark?: string;
+  code?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelConfig.title = modelConfig.isUpdate ? "编辑指令" : "添加指令";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : {};
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_instruct(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_instruct(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>

+ 123 - 0
src/views/server/command/index.vue

@@ -0,0 +1,123 @@
+<template>
+  <lay-card class="custom-card">
+    <template v-slot:title>
+      <span class="card-title">指令管理</span>
+    </template>
+    <template v-slot:extra>
+      <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">添加指令</lay-button>
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline">
+          <lay-form-item label="名称" prop="title">
+            <lay-input v-model="pageParams.title" />
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton v-if="apiList?.includes('/user/update')" text="编辑" @click="handleUpdate(row)" />
+              <TableButton v-if="apiList?.includes('/user/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </template>
+  </lay-card>
+  <Update ref="updateRef" />
+</template>
+
+<script lang="ts" setup name="ServerCommand">
+import { ref, reactive, getCurrentInstance } from "vue";
+import Update from "./components/Update.vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_instruct_list, delete_instruct } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const updateRef = ref();
+
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  title?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "名称", width: "80px", key: "title", sort: "desc" },
+  { title: "CODE", width: "80px", key: "code", sort: "desc" },
+  { title: "系统类型", width: "80px", key: "typeName", sort: "desc" },
+  { title: "描述", width: "80px", key: "remark", ellipsisTooltip: true },
+  { title: "更新时间", width: "80px", key: "updateTime", sort: "desc" },
+  {
+    title: "操作",
+    width: "150px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+// 请求指令列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  pageConfig.loading = true;
+  get_instruct_list(pageParams, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+getPageInfo();
+
+const handleUpdate = async (value?: any) => {
+  const result = await updateRef.value.show(value);
+  if (result) getPageInfo();
+};
+
+// 删除指令
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该指令?");
+  if (!result) return;
+  let params = [value.instructId];
+  pageConfig.loading = true;
+  delete_instruct(params, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      proxy.$message(`删除成功!`);
+      getPageInfo();
+    }
+  });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+</script>

+ 115 - 0
src/views/server/command_record/index.vue

@@ -0,0 +1,115 @@
+<template>
+  <lay-card class="custom-card">
+    <template v-slot:title>
+      <span class="card-title">指令记录</span>
+    </template>
+
+    <template v-slot:body>
+      <div class="custom-form-layout">
+        <lay-form class="form-wp" :model="pageParams" mode="inline">
+          <lay-form-item label="名称" prop="name">
+            <lay-input v-model="pageParams.name" />
+          </lay-form-item>
+          <div class="form-button-wp">
+            <lay-button @click="getPageInfo(true)">搜索</lay-button>
+          </div>
+        </lay-form>
+      </div>
+      <div>
+        <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+          <template v-slot:status="{ row }">
+            {{ row.status ? "成功" : "失败" }}
+          </template>
+          <template v-slot:operator="{ row }">
+            <lay-space>
+              <TableButton v-if="apiList?.includes('/user/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+            </lay-space>
+          </template>
+        </lay-table>
+      </div>
+    </template>
+  </lay-card>
+</template>
+
+<script lang="ts" setup name="ServerCommandRecord">
+import { ref, reactive, getCurrentInstance } from "vue";
+import TableButton from "@/components/TableButton.vue";
+import { get_instruct_log_list } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+
+interface PageConfig {
+  loading: boolean;
+}
+
+let pageConfig: PageConfig = reactive({
+  loading: false,
+});
+
+interface FormItem {
+  pageNum?: Number;
+  pageSize?: Number;
+  name?: String;
+}
+const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+
+interface TablePage {
+  current: number;
+  limit: number;
+  total: number;
+}
+const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+const columns = ref([
+  { title: "名称", width: "80px", key: "name", sort: "desc" },
+  { title: "服务器", width: "80px", key: "account", sort: "desc" },
+  { title: "状态", width: "80px", key: "status", customSlot: "status" },
+  { title: "执行CODE", width: "80px", key: "account", sort: "desc" },
+  { title: "系统类型", width: "80px", key: "account", sort: "desc" },
+  { title: "执行时间", width: "80px", key: "account", sort: "desc" },
+  { title: "执行人", width: "80px", key: "account", sort: "desc" },
+  {
+    title: "操作",
+    width: "150px",
+    customSlot: "operator",
+    key: "operator",
+    ignoreExport: true,
+  },
+]);
+let dataSource = ref([]);
+
+// 请求指令记录列表
+const getPageInfo = (isSearch?: boolean) => {
+  if (isSearch) pageParams.pageNum = 1;
+  pageConfig.loading = true;
+  get_instruct_log_list(pageParams, (data: any) => {
+    pageConfig.loading = false;
+    if (data.code == 200) {
+      dataSource.value = data.data.list;
+      tablePage.total = data.data.total;
+    }
+  });
+};
+getPageInfo();
+
+// 删除指令记录
+const handleDelete = async (value: any) => {
+  let result = await proxy.$waitingConfirm("是否确认删除该指令记录?");
+  if (!result) return;
+  let params = [value.userId];
+  // pageConfig.loading = true;
+  // delete_user(params, (data: any) => {
+  //   pageConfig.loading = false;
+  //   if (data.code == 200) {
+  //     proxy.$message(`删除成功!`);
+  //     getPageInfo();
+  //   }
+  // });
+};
+
+// 分页设置
+const handleCurrentChange = (val: any) => {
+  pageParams.pageNum = val.current;
+  getPageInfo();
+};
+</script>

+ 106 - 0
src/views/server/manage/components/Update.vue

@@ -0,0 +1,106 @@
+<template>
+  <lay-layer :title="modelConfig.title" v-model="modelConfig.visible" :area="['500px', '450px']" :btn="operator">
+    <div style="padding: 20px">
+      <lay-form :model="modelParams" ref="modelFormRef" required>
+        <lay-form-item label="名称" prop="name">
+          <lay-input v-model="modelParams.name" />
+        </lay-form-item>
+        <lay-form-item label="IP" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="端口号" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="登录方式" prop="code">
+          <lay-radio v-model="modelParams.code" name="action" :value="1" label="账号密码" />
+          <lay-radio v-model="modelParams.code" name="action" :value="2" label="PEM" />
+        </lay-form-item>
+        <lay-form-item label="账号" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="密码" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="PEM" prop="code">
+          <lay-input v-model="modelParams.code" />
+        </lay-form-item>
+        <lay-form-item label="备注" prop="textarea">
+          <lay-textarea v-model="modelParams.code" />
+        </lay-form-item>
+      </lay-form>
+    </div>
+  </lay-layer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, getCurrentInstance } from "vue";
+import { add_exchange, update_exchange } from "@/api";
+
+const { proxy }: any = getCurrentInstance();
+
+interface ModelConfig {
+  title: string;
+  visible: boolean;
+  isUpdate: boolean;
+  loading: boolean;
+}
+interface ModelParams {
+  exchangeId?: string;
+  name?: string;
+  code?: string;
+}
+
+let modelParams = ref<ModelParams>({});
+let modelConfig: ModelConfig = reactive({ title: "", visible: false, isUpdate: false, loading: false });
+
+let handleResult = reactive<{ resolve?: any; reject?: any }>({});
+
+const show = (params?: any) => {
+  modelConfig.visible = true;
+  modelConfig.isUpdate = !!params;
+  modelConfig.title = modelConfig.isUpdate ? "编辑交易所" : "添加交易所";
+  modelParams.value = modelConfig.isUpdate ? { ...params } : { status: 1 };
+  return new Promise(async (resolve, reject) => {
+    handleResult.resolve = resolve;
+    handleResult.reject = reject;
+  });
+};
+
+const operator = reactive([
+  {
+    text: "确认",
+    callback: () => {
+      modelConfig.loading = true;
+      if (modelConfig.isUpdate) {
+        const params = { ...modelParams.value };
+        update_exchange(params, (data: any) => {
+          modelConfig.loading = false;
+          if (data.code == 200) {
+            proxy.$message("编辑成功!");
+            modelConfig.visible = false;
+            handleResult.resolve(true);
+          }
+        });
+      } else {
+        const params = { ...modelParams.value };
+        add_exchange(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>

+ 131 - 0
src/views/server/manage/index.vue

@@ -0,0 +1,131 @@
+<template>
+    <lay-card class="custom-card">
+      <template v-slot:title>
+        <span class="card-title">服务器管理</span>
+      </template>
+      <template v-slot:extra>
+        <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">PEM管理</lay-button>
+        <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">批量添加服务器</lay-button>
+        <lay-button class="card-button" v-if="apiList?.includes('/user/save')" @click="handleUpdate()">添加服务器</lay-button>
+      </template>
+  
+      <template v-slot:body>
+        <div class="custom-form-layout">
+          <lay-form class="form-wp" :model="pageParams" mode="inline">
+            <lay-form-item label="名称" prop="name">
+              <lay-input v-model="pageParams.name" />
+            </lay-form-item>
+            <div class="form-button-wp">
+              <lay-button @click="getPageInfo(true)">搜索</lay-button>
+            </div>
+          </lay-form>
+        </div>
+        <div>
+          <lay-table :page="tablePage" :columns="columns" resize :data-source="dataSource" :loading="pageConfig.loading" @change="handleCurrentChange">
+            <template v-slot:status="{ row }">
+              {{ row.status ? "启用" : "禁用" }}
+            </template>
+            <template v-slot:operator="{ row }">
+              <lay-space>
+                <TableButton v-if="apiList?.includes('/user/setStatus')" text="测试连接" @click="handleStatus(row)" />
+                <TableButton v-if="apiList?.includes('/user/update')" text="编辑" @click="handleUpdate(row)" />
+                <TableButton v-if="apiList?.includes('/user/update')" text="复制" @click="handleUpdate(row)" />
+                <TableButton v-if="apiList?.includes('/user/delete')" type="danger" text="删除" @click="handleDelete(row)" />
+              </lay-space>
+            </template>
+          </lay-table>
+        </div>
+      </template>
+    </lay-card>
+    <Update ref="updateRef" />
+  </template>
+  
+  <script lang="ts" setup name="ServerManage">
+  import { ref, reactive, getCurrentInstance } from "vue";
+  import Update from "./components/Update.vue";
+  import TableButton from "@/components/TableButton.vue";
+  import { get_server_list, delete_server } from "@/api";
+  
+  const { proxy }: any = getCurrentInstance();
+  const updateRef = ref();
+  
+  const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
+  
+  interface PageConfig {
+    loading: boolean;
+  }
+  
+  let pageConfig: PageConfig = reactive({
+    loading: false,
+  });
+  
+  interface FormItem {
+    pageNum?: Number;
+    pageSize?: Number;
+    name?: String;
+  }
+  const pageParams: FormItem = reactive({ pageNum: 1, pageSize: 10 });
+  
+  interface TablePage {
+    current: number;
+    limit: number;
+    total: number;
+  }
+  const tablePage: TablePage = reactive({ current: 1, limit: 10, total: 0 });
+  const columns = ref([
+    { title: "名称", width: "80px", key: "name", sort: "desc" },
+    { title: "IP", width: "80px", key: "account", sort: "desc" },
+    { title: "端口号", width: "80px", key: "account", sort: "desc" },
+    { title: "状态", width: "80px", key: "status", customSlot: "status" },
+    { title: "备注", width: "80px", key: "remark", ellipsisTooltip: true },
+    { title: "更新时间", width: "80px", key: "account", sort: "desc" },
+    {
+      title: "操作",
+      width: "150px",
+      customSlot: "operator",
+      key: "operator",
+      ignoreExport: true,
+    },
+  ]);
+  let dataSource = ref([]);
+  
+  // 请求交易所列表
+  const getPageInfo = (isSearch?: boolean) => {
+    if (isSearch) pageParams.pageNum = 1;
+    pageConfig.loading = true;
+    get_server_list(pageParams, (data: any) => {
+      pageConfig.loading = false;
+      if (data.code == 200) {
+        dataSource.value = data.data.list;
+        tablePage.total = data.data.total;
+      }
+    });
+  };
+  getPageInfo();
+  
+  const handleUpdate = async (value?: any) => {
+    const result = await updateRef.value.show(value);
+    if (result) getPageInfo();
+  };
+  
+  // 删除交易所
+  const handleDelete = async (value: any) => {
+    let result = await proxy.$waitingConfirm("是否确认删除该用户?");
+    if (!result) return;
+    let params = [value.userId];
+    pageConfig.loading = true;
+    delete_server(params, (data: any) => {
+      pageConfig.loading = false;
+      if (data.code == 200) {
+        proxy.$message(`删除成功!`);
+        getPageInfo();
+      }
+    });
+  };
+  // 分页设置
+  const handleCurrentChange = (val: any) => {
+    pageParams.pageNum = val.current;
+    getPageInfo();
+  };
+  </script>
+