Browse Source

solor移植,部署管理

chulin 2 years ago
parent
commit
fa7830cea6

+ 3 - 1
src/components/UploadFetchFile.vue

@@ -12,7 +12,7 @@
       :show-file-list="false"
       :auto-upload="false"
       :disabled="disabled"
-      style="display:inline-block;margin: 0 10px;"
+      style="display: inline-block; margin: 0 10px"
       ref="UploadComp"
     >
       <el-button type="primary" :disabled="disabled" :loading="loading"
@@ -61,6 +61,8 @@ export default {
         .split(".")
         .pop()
         .toLocaleLowerCase();
+      if (!this.format.length) return true;
+
       return this.format.some(
         item => item.toLocaleLowerCase() === _file_format
       );

+ 13 - 0
src/constants/enumerate.js

@@ -14,3 +14,16 @@ export const ROLE_TYPE = {
   OPS: "运维",
   ADMIN: "管理员"
 };
+
+export const DEPLOY_CONTROL_KEYS = [
+  {
+    name: "过期时间",
+    code: "expireTime",
+    type: "time"
+  },
+  {
+    name: "最大在线用户数",
+    code: "maxOnlineUserCount",
+    type: "number"
+  }
+];

+ 5 - 0
src/constants/navs.js

@@ -86,6 +86,11 @@ export const ROLE_NAV = {
         "app_edit",
         "app_user",
         "app_user_bind",
+        "app_deploy",
+        "app_deploy_add",
+        "app_deploy_edit",
+        "app_deploy_org",
+        "app_deploy_device",
         "app_module",
         "app_module_add",
         "app_module_edit",

+ 87 - 0
src/modules/admin/api.js

@@ -126,3 +126,90 @@ export const appNginxConfig = datas => {
 export const appNginxConfigUpdate = datas => {
   return $postParam("/api/admin/nginx/update", datas);
 };
+
+// app-deploy-manage
+export const appDeployList = datas => {
+  return $postParam("/api/admin/deploy/query", datas);
+};
+export const appDeployModes = () => {
+  return $post("/api/admin/deploy/modes", {});
+};
+export const appDeployInsertOrUpdate = datas => {
+  if (datas.id) {
+    return $post("/api/admin/deploy/update", datas);
+  } else {
+    return $post("/api/admin/deploy/insert", datas);
+  }
+};
+
+// org-manage
+export const orgTypesList = datas => {
+  return $postParam("/api/admin/org/types", datas);
+};
+export const orgQuery = datas => {
+  return $postParam("/api/admin/org/query", datas);
+};
+export const orgInsertOrUpdate = datas => {
+  let formData = new FormData();
+  Object.entries(datas).forEach(([key, val]) => {
+    // if (val === null || val === "null" || val === "") return;
+
+    if (key === "subTypes") {
+      if (val.length) {
+        val.forEach(type => formData.append("subTypes", type));
+      } else {
+        formData.append("subTypes", "");
+      }
+    } else {
+      formData.append(key, datas[key]);
+    }
+  });
+
+  if (datas.id) {
+    return $post("/api/admin/org/update", formData);
+  } else {
+    return $post("/api/admin/org/insert", formData);
+  }
+};
+// 启用/禁用
+export const orgToggle = datas => {
+  return $postParam("/api/admin/org/toggle", datas);
+};
+
+// wechat-app-manage
+/**
+ * @description 微信小程序查询
+ * @params { string } id  // 应用ID
+ * @params { string } nameStartWith  // 名称前缀
+ * @params { string } pageNumber  // 页码
+ * @params { string } pageSize  // 数量
+ */
+export const getWeChatAppList = data => {
+  const params = Object.entries(data).reduce((p, [key, val]) => {
+    if (val) {
+      p[key] = val;
+    }
+    return p;
+  }, {});
+  return $post("/api/admin/wxapp/query", new URLSearchParams(params));
+};
+
+/**
+ * @description 微信小程序新增
+ * @params { string } id  // 应用ID
+ * @params { string } name  // 名称
+ * @params { string } secret  // 密钥
+ */
+export const insertWeChatApp = data => {
+  return $post("/api/admin/wxapp/insert", new URLSearchParams(data));
+};
+
+/**
+ * @description 微信小程序修改
+ * @params { string } id  // 应用ID
+ * @params { string } name  // 名称
+ * @params { string } secret  // 密钥
+ */
+export const updateWeChatApp = data => {
+  return $post("/api/admin/wxapp/update", new URLSearchParams(data));
+};

+ 56 - 0
src/modules/admin/components/AppControlKey.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="app-control-key">
+    <el-input-number
+      v-if="keyType === 'number'"
+      v-model="data"
+      style="width: 100%"
+      :controls="false"
+      placeholder="请输入数字"
+      @change="dataChange"
+    ></el-input-number>
+    <el-date-picker
+      v-if="keyType === 'time'"
+      v-model="data"
+      type="datetime"
+      style="width: 100%"
+      value-format="timestamp"
+      placeholder="选择日期时间"
+      @change="dataChange"
+    >
+    </el-date-picker>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "app-control-key",
+  props: {
+    value: { type: [Number, String] },
+    keyType: {
+      type: String,
+      default: "number",
+      validator: val => ["number", "time"].includes(val)
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.data = val;
+      }
+    }
+  },
+  data() {
+    return {
+      data: null
+    };
+  },
+  methods: {
+    dataChange() {
+      // console.log(this.data);
+      this.$emit("input", this.data);
+      this.$emit("change", this.data);
+    }
+  }
+};
+</script>

+ 186 - 0
src/modules/admin/components/AppDeployManage.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="app-deploy-manage">
+    <el-dialog
+      class="page-dialog"
+      :visible.sync="modalIsShow"
+      title="应用部署管理"
+      top="10px"
+      width="1000px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      @opened="visibleChange"
+    >
+      <div class="part-box part-box-filter part-box-flex">
+        <el-form
+          ref="FilterForm"
+          label-position="left"
+          label-width="80px"
+          inline
+        >
+          <el-form-item label="模糊查询">
+            <el-input
+              v-model="filter.nameStartWith"
+              placeholder="名称前缀"
+            ></el-input>
+          </el-form-item>
+          <el-form-item label-width="0px">
+            <el-button type="primary" icon="ios-search" @click="getList"
+              >查询</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('app_deploy_add')"
+              type="success"
+              icon="md-add"
+              @click="toAdd"
+              >新增</el-button
+            >
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <div class="part-box part-box-pad">
+        <el-table ref="TableList" :data="dataList">
+          <el-table-column prop="id" label="ID" width="80"></el-table-column>
+          <el-table-column prop="name" label="名称"> </el-table-column>
+          <el-table-column prop="modeName" label="部署方式"></el-table-column>
+          <el-table-column label="操作" align="center" width="260">
+            <template slot-scope="scope">
+              <el-button
+                v-if="checkPrivilege('app_deploy_edit')"
+                class="btn-primary"
+                type="text"
+                @click="toEdit(scope.row)"
+                >编辑</el-button
+              >
+              <el-button
+                v-if="checkPrivilege('app_deploy_org')"
+                class="btn-primary"
+                type="text"
+                @click="toBindOrg(scope.row)"
+                >关联机构</el-button
+              >
+              <el-button
+                v-if="checkPrivilege('app_deploy_device')"
+                class="btn-primary"
+                type="text"
+                @click="toBindDevice(scope.row)"
+                >关联设备</el-button
+              >
+            </template>
+          </el-table-column>
+        </el-table>
+        <div class="part-page">
+          <el-pagination
+            background
+            layout="total,prev, pager, next"
+            :current-page="current"
+            :total="total"
+            :page-size="size"
+            @current-change="toPage"
+          >
+          </el-pagination>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- ModifyAppDeploy -->
+    <modify-app-deploy
+      v-if="
+        checkPrivilege('app_deploy_add') || checkPrivilege('app_deploy_edit')
+      "
+      ref="ModifyAppDeploy"
+      :instance="curRow"
+      :deploy-modes="deployModes"
+      @modified="getList"
+    ></modify-app-deploy>
+  </div>
+</template>
+
+<script>
+import { appDeployList, appDeployModes } from "../api";
+import ModifyAppDeploy from "./ModifyAppDeploy.vue";
+
+export default {
+  name: "app-deploy-manage",
+  components: { ModifyAppDeploy },
+  props: {
+    app: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      filter: {
+        nameStartWith: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curRow: {},
+      deployModes: [],
+      deployModeMap: {}
+    };
+  },
+  methods: {
+    async visibleChange() {
+      await this.getDeployModes();
+      this.getList();
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async getDeployModes() {
+      if (this.deployModes.length) return;
+
+      const res = await appDeployModes();
+      this.deployModes = res || [];
+      let deployModeMap = {};
+      this.deployModes.forEach(({ code, name }) => {
+        deployModeMap[code] = name;
+      });
+      this.deployModeMap = deployModeMap;
+    },
+    async getList() {
+      const datas = {
+        ...this.filter,
+        appId: this.app.id,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      const data = await appDeployList(datas);
+      this.dataList = data.records.map(item => {
+        item.modeName = this.deployModeMap[item.type];
+        return item;
+      });
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toAdd() {
+      this.curRow = { appId: this.app.id };
+      this.$refs.ModifyAppDeploy.open();
+    },
+    toEdit(row) {
+      this.curRow = { ...row, appId: this.app.id };
+      this.$refs.ModifyAppDeploy.open();
+    },
+    toBindOrg(row) {
+      this.curRow = { ...row, app: this.app };
+    },
+    toBindDevice(row) {
+      this.curRow = { ...row, app: this.app };
+    }
+  }
+};
+</script>

+ 178 - 0
src/modules/admin/components/ModifyAppDeploy.vue

@@ -0,0 +1,178 @@
+<template>
+  <el-dialog
+    class="modify-app-deploy"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10px"
+    width="540px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @opened="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      label-width="110px"
+    >
+      <el-form-item prop="name" label="名称">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="请输入名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="mode" label="分类">
+        <el-select v-model="modalForm.mode" placeholder="选择分类" clearable>
+          <el-option
+            v-for="item in deployModes"
+            :key="item.code"
+            :value="item.code"
+            :label="item.name"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="IP白名单">
+        <el-input
+          v-model.trim="modalForm.ipAllow"
+          type="textarea"
+          placeholder="请输入访问IP白名单"
+        ></el-input>
+      </el-form-item>
+
+      <template v-for="cont in DEPLOY_CONTROL_KEYS">
+        <el-form-item :key="cont.code" :label="cont.name">
+          <app-control-key
+            v-model="modalForm.control[cont.code]"
+            :key-type="cont.type"
+          ></app-control-key>
+        </el-form-item>
+      </template>
+    </el-form>
+    <div slot="footer">
+      <el-button type="danger" @click="cancel" plain>取消</el-button>
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { DEPLOY_CONTROL_KEYS } from "../../../constants/enumerate";
+import { clearData } from "../../../plugins/utils";
+import { appDeployInsertOrUpdate } from "../api";
+import AppControlKey from "./AppControlKey";
+
+const initModalForm = {
+  id: null,
+  appId: null,
+  name: "",
+  mode: "",
+  ipAllow: "",
+  control: {}
+};
+
+export default {
+  name: "modify-app",
+  components: { AppControlKey },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    deployModes: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "应用部署";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      DEPLOY_CONTROL_KEYS,
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入名称",
+            trigger: "change"
+          }
+        ],
+        mode: [
+          {
+            required: true,
+            message: "请选择分类",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+      const controlVals = val.control || {};
+      let control = {};
+      this.datas.controlKeys.forEach(cont => {
+        const defaultVal = cont.type === "number" ? undefined : null;
+        control[cont.code] =
+          controlVals[cont.code] || controlVals[cont.code] === 0
+            ? controlVals[cont.code]
+            : defaultVal;
+      });
+      this.modalForm.control = control;
+
+      this.$refs.modalFormComp.clearValidate();
+    },
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = { ...this.modalForm };
+      datas.control = clearData(this.modalForm.control);
+      const data = await appDeployInsertOrUpdate(datas).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 234 - 0
src/modules/admin/components/ModifyOrg.vue

@@ -0,0 +1,234 @@
+<template>
+  <el-dialog
+    class="modify-org"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @opened="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :key="modalForm.id"
+      label-width="100px"
+    >
+      <el-form-item prop="name" label="名称">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="请输入名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="code" label="编码">
+        <el-input
+          v-model.trim="modalForm.code"
+          placeholder="请输入编码"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="type" label="一级分类">
+        <el-select
+          v-model="modalForm.type"
+          placeholder="选择一级分类"
+          @change="typeChange"
+        >
+          <el-option
+            v-for="item in datas"
+            :key="item.code"
+            :value="item.code"
+            :label="item.name"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="二级分类">
+        <el-select
+          v-model="modalForm.subTypes"
+          placeholder="选择二级分类"
+          clearable
+          multiple
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in curSubTypes"
+            :key="item.code"
+            :value="item.code"
+            :label="item.name"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item v-if="!isEdit" label="是否启用" required>
+        <el-switch
+          v-model="modalForm.enable"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        >
+        </el-switch>
+      </el-form-item>
+      <el-form-item label="logo">
+        <UploadFetchFile ref="UploadFetchFile" @file-change="logoChange" />
+        <div class="logo-image" v-if="imgSrc">
+          <img class="logo-view" :src="imgSrc" alt="logo" />
+        </div>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="danger" @click="cancel" plain>取消</el-button>
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { orgInsertOrUpdate } from "../api";
+import UploadFetchFile from "../../../components/UploadFetchFile.vue";
+
+const initModalForm = {
+  id: "",
+  name: "",
+  code: "",
+  type: "",
+  subTypes: [],
+  enable: true,
+  logoFile: "",
+  logoMd5: ""
+};
+
+export default {
+  name: "modify-data",
+  components: { UploadFetchFile },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    datas: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "机构";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      curSubTypes: [],
+      modalForm: { ...initModalForm },
+      imgSrc: "",
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入应用名称",
+            trigger: "change"
+          }
+        ],
+        code: [
+          {
+            required: true,
+            message: "请输入应用编码",
+            trigger: "change"
+          }
+        ],
+        type: [
+          {
+            required: true,
+            message: "请选择一级分类",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+        const curType = this.datas.find(
+          item => item.code === this.modalForm.type
+        );
+        this.curSubTypes = curType.subTypes;
+        if (val.logo) {
+          const fileServer = this.$ls.get("user", {
+            fileServer: ""
+          }).fileServer;
+          this.imgSrc = `${fileServer}${val.logo}`;
+          this.$nextTick(() => {
+            this.$refs.UploadFetchFile.setAttachmentName(val.logo);
+          });
+        } else {
+          this.imgSrc = "";
+        }
+      } else {
+        this.imgSrc = "";
+        this.modalForm = { ...initModalForm };
+      }
+      this.$refs.modalFormComp.clearValidate();
+    },
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    typeChange() {
+      const curType = this.datas.find(
+        item => item.code === this.modalForm.type
+      );
+      this.curSubTypes = curType ? curType.subTypes : [];
+      this.modalForm.subTypes = [];
+    },
+    validChange(result) {
+      if (!result.success) {
+        this.$notify.error({ title: "错误提示", message: result.message });
+      }
+    },
+    logoChange({ file, md5 }) {
+      let uRl = window.URL || window.webkitURL;
+      this.imgSrc = uRl.createObjectURL(file);
+      this.modalForm.logoFile = file;
+      this.modalForm.logoMd5 = md5;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      let datas = { ...this.modalForm };
+      const data = await orgInsertOrUpdate(datas).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 20 - 1
src/modules/admin/views/AppManage.vue

@@ -48,7 +48,7 @@
             >
           </template>
         </el-table-column>
-        <el-table-column label="管理" width="320" class-name="action-column">
+        <el-table-column label="管理" width="360" class-name="action-column">
           <template slot-scope="scope">
             <el-button
               v-if="checkPrivilege('app_user')"
@@ -57,6 +57,13 @@
               @click="toEditUser(scope.row)"
               >用户</el-button
             >
+            <el-button
+              v-if="checkPrivilege('app_deploy')"
+              class="btn-primary"
+              type="text"
+              @click="toEditDeploy(scope.row)"
+              >部署</el-button
+            >
             <el-button
               v-if="checkPrivilege('app_module')"
               class="btn-primary"
@@ -122,6 +129,12 @@
       ref="AppUserManage"
       :app="curRow"
     ></app-user-manage>
+    <!-- AppDeployManage -->
+    <app-deploy-manage
+      v-if="checkPrivilege('app_deploy')"
+      ref="AppDeployManage"
+      :app="curRow"
+    ></app-deploy-manage>
     <!-- AppModuleManage -->
     <app-module-manage
       v-if="checkPrivilege('app_module')"
@@ -160,6 +173,7 @@
 import { appQuery } from "../api";
 import ModifyApp from "../components/ModifyApp";
 import AppUserManage from "../components/AppUserManage.vue";
+import AppDeployManage from "../components/AppDeployManage.vue";
 import AppModuleManage from "../components/AppModuleManage.vue";
 import AppVersionManage from "../components/AppVersionManage.vue";
 import AppEnvManage from "../components/AppEnvManage.vue";
@@ -171,6 +185,7 @@ export default {
   components: {
     ModifyApp,
     AppUserManage,
+    AppDeployManage,
     AppModuleManage,
     AppVersionManage,
     AppEnvManage,
@@ -224,6 +239,10 @@ export default {
       this.curRow = row;
       this.$refs.AppUserManage.open();
     },
+    toEditDeploy(row) {
+      this.curRow = row;
+      this.$refs.AppDeployManage.open();
+    },
     toEditModule(row) {
       this.curRow = row;
       this.$refs.AppModuleManage.open();

+ 265 - 0
src/modules/admin/views/OrgManage.vue

@@ -0,0 +1,265 @@
+<template>
+  <div class="org-manage">
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form
+          ref="FilterForm"
+          label-position="left"
+          label-width="80px"
+          inline
+        >
+          <el-form-item label="一级分类">
+            <el-select
+              v-model="filter.type"
+              placeholder="选择一级分类"
+              clearable
+              @change="typeChange"
+            >
+              <el-option
+                v-for="item in orgTypes"
+                :key="item.code"
+                :value="item.code"
+                :label="item.name"
+              >
+              </el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="二级分类">
+            <el-select
+              v-model="filter.subType"
+              placeholder="选择二级分类"
+              clearable
+            >
+              <el-option
+                v-for="item in curSubTypes"
+                :key="item.code"
+                :value="item.code"
+                :label="item.name"
+              >
+              </el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="启用/禁用:">
+            <el-select
+              v-model="filter.enable"
+              style="width: 110px;"
+              placeholder="启用/禁用"
+              clearable
+            >
+              <el-option
+                v-for="(val, key) in ABLE_TYPE"
+                :key="key"
+                :value="key * 1"
+                :label="val"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="编码" label-width="50">
+            <el-input
+              v-model="filter.code"
+              placeholder="编码"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="模糊查询">
+            <el-input
+              v-model="filter.nameStartWith"
+              placeholder="名称前缀"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label-width="0px">
+            <el-button type="primary" icon="ios-search" @click="toPage(1)"
+              >查询</el-button
+            >
+            <el-button type="success" icon="md-add" @click="toAdd"
+              >新增</el-button
+            >
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="TableList" :data="dataList" border>
+        <!-- <el-table-column
+          type="index"
+          label="序号"
+          width="60"
+          align="center"
+          :index="indexMethod"
+        >
+        </el-table-column> -->
+        <el-table-column prop="id" label="ID" width="80"></el-table-column>
+        <el-table-column prop="name" label="名称"> </el-table-column>
+        <el-table-column prop="code" label="编码"> </el-table-column>
+        <el-table-column prop="typeName" label="一级分类" width="90">
+        </el-table-column>
+        <el-table-column prop="subTypesName" label="二级分类">
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="160">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="updateTime" label="修改时间" width="160">
+          <span slot-scope="scope">{{
+            scope.row.updateTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="enable" label="启用/禁用" width="90">
+          <span
+            slot-scope="scope"
+            :class="scope.row.enable ? 'color-success' : 'color-danger'"
+            >{{ scope.row.enable | ableTypeFilter }}</span
+          >
+        </el-table-column>
+        <el-table-column label="密钥" width="130">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="toCopyKey(scope.row)"
+              >复制到剪贴板</el-button
+            >
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="160">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              size="mini"
+              :type="scope.row.enable ? 'danger' : 'success'"
+              :disabled="loading"
+              @click="toEnable(scope.row)"
+              >{{ scope.row.enable ? "禁用" : "启用" }}</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- modify-app -->
+    <ModifyOrg
+      :datas="orgTypes"
+      :instance="curRow"
+      @modified="getList"
+      ref="ModifyOrg"
+    />
+  </div>
+</template>
+
+<script>
+import { orgQuery, orgToggle } from "../api";
+import { ABLE_TYPE } from "@/constants/enumerate";
+import ModifyOrg from "../components/ModifyOrg";
+
+export default {
+  name: "org-manage",
+  components: { ModifyOrg },
+  data() {
+    return {
+      filter: {
+        code: "",
+        nameStartWith: "",
+        type: "",
+        subType: "",
+        enable: null
+      },
+      ABLE_TYPE,
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      orgTypes: this.$ls.get("orgTypes", []),
+      orgTypeMap: this.$ls.get("orgTypeMap", {}),
+      curSubTypes: [],
+      dataList: [],
+      curRow: {},
+      loading: false
+    };
+  },
+  created() {
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.toPage(1);
+    },
+    typeChange() {
+      const curType = this.orgTypes.find(
+        item => item.code === this.filter.type
+      );
+      this.curSubTypes = curType ? curType.subTypes : [];
+      this.filter.subType = "";
+    },
+    indexMethod(index) {
+      return (this.current - 1) * this.size + index + 1;
+    },
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (datas.enable !== null && datas.enable !== "")
+        datas.enable = !!datas.enable;
+      const data = await orgQuery(datas);
+      this.dataList = data.records.map(item => {
+        item.typeName = this.orgTypeMap[item.type].name;
+
+        if (item.subTypes && item.subTypes.length) {
+          item.subTypesName = item.subTypes
+            .map(type => this.orgTypeMap[item.type].subType[type])
+            .join(",");
+        }
+        return item;
+      });
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toCopyKey(row) {
+      const inputDom = document.createElement("textarea");
+      inputDom.value = `accessKey:${row.accessKey}\naccessSecret:${row.accessSecret}`;
+      document.body.appendChild(inputDom);
+      inputDom.select();
+      document.execCommand("copy");
+      inputDom.parentNode.removeChild(inputDom);
+      this.$message.success("复制成功!");
+    },
+    toAdd() {
+      this.curRow = {};
+      this.$refs.ModifyOrg.open();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyOrg.open();
+    },
+    async toEnable(row) {
+      if (this.loading) return;
+      this.loading = true;
+      const res = await orgToggle({
+        id: row.id,
+        enable: !row.enable
+      }).catch(() => {});
+
+      this.loading = false;
+      if (!res) return;
+
+      row.enable = !row.enable;
+    }
+  }
+};
+</script>

+ 210 - 0
src/modules/admin/views/WeChatAppManage.vue

@@ -0,0 +1,210 @@
+<template>
+  <div class="org-manage">
+    <div class="part-filter">
+      <div class="part-filter-form">
+        <el-form
+          ref="FilterForm"
+          label-position="left"
+          label-width="80px"
+          inline
+        >
+          <el-form-item label="应用ID">
+            <el-input
+              v-model="filter.id"
+              placeholder="应用ID"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="模糊查询">
+            <el-input
+              v-model="filter.nameStartWith"
+              placeholder="名称前缀"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <el-form-item label-width="0px">
+            <el-button type="primary" icon="ios-search" @click="toPage(1)"
+              >查询</el-button
+            >
+            <el-button type="success" icon="md-add" @click="toAdd"
+              >新增</el-button
+            >
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="TableList" :data="dataList" border>
+        <el-table-column prop="id" label="ID" width="80"></el-table-column>
+        <el-table-column prop="name" label="名称"> </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="160">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="updateTime" label="修改时间" width="160">
+          <span slot-scope="scope">{{
+            scope.row.updateTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="160">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <el-dialog
+      :visible.sync="visible"
+      append-to-body
+      width="520"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :title="`${modifyType === 'update' ? '修改' : '新增'}小程序`"
+    >
+      <el-form ref="form" :model="modalForm" :rules="rules" label-width="100px">
+        <el-form-item prop="id" label="应用ID">
+          <el-input
+            :disabled="modifyType === 'update'"
+            v-model.trim="modalForm.id"
+            placeholder="请输入应用ID"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="name" label="名称">
+          <el-input
+            v-model.trim="modalForm.name"
+            placeholder="请输入应用名称"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="secret" label="应用密钥">
+          <el-input
+            v-model.trim="modalForm.secret"
+            placeholder="请输入应用密钥"
+            clearable
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button type="danger" @click="cancel" plain>取消</el-button>
+        <el-button type="primary" :disabled="isSubmit" @click="submit"
+          >确认</el-button
+        >
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// eslint-disable-next-line no-unused-vars
+import { getWeChatAppList, insertWeChatApp, updateWeChatApp } from "../api";
+
+export default {
+  name: "we-chat-app-manage",
+  data() {
+    return {
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      filter: {
+        id: "",
+        nameStartWith: ""
+      },
+      modalForm: {
+        id: "",
+        name: "",
+        secret: ""
+      },
+      modifyType: "insert",
+      visible: false,
+      isSubmit: false,
+      rules: {
+        id: [{ required: true, message: "请填写小程序AppId" }],
+        name: [{ required: true, message: "请填写小程序名称" }],
+        secret: [{ required: true, message: "请填写小程序名称" }]
+      }
+    };
+  },
+  watch: {
+    visible(v) {
+      this.$refs.form?.clearValidate();
+    }
+  },
+  created() {
+    this.toPage(1);
+  },
+  methods: {
+    async getList() {
+      try {
+        const data = {
+          ...this.filter,
+          pageNumber: this.current,
+          pageSize: this.size
+        };
+        const result = await getWeChatAppList(data);
+
+        this.dataList = result.records;
+        this.total = result.total;
+      } catch (error) {
+        console.error(error);
+        error.message && this.$message.error(error.message);
+      }
+    },
+    toAdd() {
+      this.modifyType = "insert";
+      this.modalForm = this.$options.data.call(this).modalForm;
+      this.$nextTick(() => {
+        this.visible = true;
+      });
+    },
+    toEdit(row) {
+      this.modifyType = "update";
+      const { id, name, secret } = row;
+      this.modalForm = { id, name, secret };
+      this.visible = true;
+    },
+    cancel() {
+      this.visible = false;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    async submit() {
+      if (this.isSubmit) {
+        return;
+      }
+      this.isSubmit = true;
+      const isUpdate = this.modifyType === "update";
+      const api = isUpdate ? updateWeChatApp : insertWeChatApp;
+      try {
+        await this.$refs.form.validate();
+        await api(this.modalForm);
+        this.getList();
+        this.$message.success(`${isUpdate ? "修改" : "新增"}成功`);
+        this.visible = false;
+      } catch (error) {
+        error.message && this.$message.error(error.message);
+      }
+      this.isSubmit = false;
+    }
+  }
+};
+</script>