Browse Source

feat: 学校数据同步

zhangjie 7 months ago
parent
commit
89d5893dfc

+ 11 - 0
src/constants/enumerate.js

@@ -440,3 +440,14 @@ export const TOOL_TYPE = {
   TEACHCLOUD_CLIENT: "知学知考客户端",
   PICTURE_DOWNLOAD: "图片下载工具",
 };
+export const DATABASE_TYPE = {
+  ORACLE: "Oracle",
+  MYSQL: "MySQL",
+};
+export const DATABASE_SYNC_TYPE = {
+  1: "组织架构",
+  2: "用户数据",
+  3: "课程数据",
+  4: "考生数据",
+  5: "命题任务数据",
+};

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

@@ -132,6 +132,30 @@ export const schoolSetDataBackup = (schoolId) => {
   return $postParam("/api/admin/set/backup", { schoolId });
 };
 
+// 数据同步 school database sync
+export const schoolSetDatabaseSyncBaseinfo = (schoolId) => {
+  return $postParam("/api/admin/set/sync1/save", { schoolId });
+};
+export const schoolSetDatabaseSyncBaseinfoUpdate = (datas) => {
+  return $post("/api/admin/set/sync1/save", datas);
+};
+export const schoolSetDatabaseSyncBaseinfoTest = (datas) => {
+  return $post("/api/admin/set/sync1/save", datas);
+};
+
+export const schoolSetDatabaseSyncList = (datas) => {
+  return $postParam("/api/admin/set/sync1/save", datas);
+};
+export const schoolSetDatabaseSyncUpdate = (datas) => {
+  return $post("/api/admin/set/sync1/save", datas);
+};
+export const schoolSetDatabaseSyncHandleSync = (id) => {
+  return $postParam("/api/admin/set/sync1/save", { id });
+};
+export const schoolSetDatabaseSyncClose = (id) => {
+  return $postParam("/api/admin/set/sync1/save", { id });
+};
+
 export const systemLogExport = () => {
   return $postParam(
     "/api/admin/common/log/download",

+ 8 - 2
src/modules/admin/components/ModifySchoolSet.vue

@@ -20,9 +20,9 @@
       </el-button>
     </div>
 
-    <div v-if="modalIsShow" class="part-box part-box-pad">
+    <template v-if="modalIsShow">
       <component :is="compName" :school="school"></component>
-    </div>
+    </template>
 
     <div slot="footer"></div>
   </el-dialog>
@@ -37,6 +37,7 @@ import SchoolSetRole from "./school/SchoolSetRole.vue";
 import SchoolSetSync from "./school/SchoolSetSync.vue";
 import SchoolSetTarget from "./school/SchoolSetTarget.vue";
 import SchoolSetStdno from "./school/SchoolSetStdno.vue";
+import SchoolSetDatabaseSync from "./school/SchoolSetDatabaseSync.vue";
 
 export default {
   name: "modify-school-set",
@@ -49,6 +50,7 @@ export default {
     SchoolSetSync,
     SchoolSetTarget,
     SchoolSetStdno,
+    SchoolSetDatabaseSync,
   },
   props: {
     school: {
@@ -91,6 +93,10 @@ export default {
           name: "课程目标",
           val: "target",
         },
+        {
+          name: "数据同步",
+          val: "database-sync",
+        },
         {
           name: "数据还原",
           val: "data",

+ 314 - 0
src/modules/admin/components/school/ModifyDatabaseSync.vue

@@ -0,0 +1,314 @@
+<template>
+  <div>
+    <el-dialog
+      class="modify-course"
+      :visible.sync="modalIsShow"
+      :title="title"
+      top="10vh"
+      width="700px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      @open="visibleChange"
+    >
+      <el-form
+        ref="modalFormComp"
+        :model="modalForm"
+        :rules="rules"
+        :key="modalForm.id"
+        label-width="130px"
+      >
+        <el-form-item prop="semesterId" label="学期:">
+          <el-select
+            v-model="modalForm.semesterId"
+            placeholder="学期"
+            filterable
+            @change="semesterChange"
+          >
+            <el-option
+              v-for="item in semesterList"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item prop="examId" label="考试:">
+          <el-select v-model="modalForm.examId" placeholder="考试" filterable>
+            <el-option
+              v-for="item in examList"
+              :key="item.id"
+              :value="item.id"
+              :label="item.name"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item prop="dataType" label="同步数据类型:">
+          <el-checkbox-group v-model="modalForm.dataType">
+            <el-checkbox
+              v-for="(val, key) in DATABASE_SYNC_TYPE"
+              :key="key"
+              :label="key"
+              >{{ val }}</el-checkbox
+            >
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item prop="syncStartTime" label="同步开始时间:">
+          <el-date-picker
+            v-model="modalForm.syncStartTime"
+            type="datetime"
+            value-format="timestamp"
+            placeholder="选择时间"
+            @change="() => timeChange('syncStartTime')"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item prop="syncEndTime" label="同步结束时间:">
+          <el-date-picker
+            v-model="modalForm.syncEndTime"
+            type="datetime"
+            value-format="timestamp"
+            placeholder="选择时间"
+            @change="() => timeChange('syncEndTime')"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item prop="durationTime" label="定时同步时间:">
+          <el-input
+            v-model.trim="modalForm.durationTime"
+            placeholder="请输入定时同步时间"
+            clearable
+          ></el-input>
+          <p class="tips-info">如每天1点开始同步,0 0 1 * * ?</p>
+        </el-form-item>
+        <el-form-item prop="warningPhone" label="同步异常预警:">
+          <el-input
+            v-model.trim="modalForm.warningPhone"
+            placeholder="请输入同步异常预警"
+            clearable
+          ></el-input>
+          <p class="tips-info">支持填写多个手机号,用英文,隔开</p>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button type="primary" :disabled="isSubmit" @click="submit"
+          >确认</el-button
+        >
+        <el-button @click="cancel">取消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { schoolSetDatabaseSyncUpdate } from "../../api";
+import { conditionListSemester, conditionListExam } from "../../../base/api";
+import { DATABASE_SYNC_TYPE } from "@/constants/enumerate";
+
+const initModalForm = {
+  id: null,
+  semesterId: "",
+  examId: "",
+  dataType: "",
+  syncStartTime: "",
+  syncEndTime: "",
+  durationTime: "",
+  warningPhone: "",
+};
+
+export default {
+  name: "modify-database-sync",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    schoolId: {
+      type: String,
+      default: "",
+    },
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "同步记录";
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      DATABASE_SYNC_TYPE,
+      modalForm: { ...initModalForm },
+      rules: {
+        semesterId: [
+          {
+            required: true,
+            message: "请选择学期",
+            trigger: "change",
+          },
+        ],
+        examId: [
+          {
+            required: true,
+            message: "请选择考试",
+            trigger: "change",
+          },
+        ],
+        dataType: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (!value || !value.length) {
+                return callback(new Error(`请选择同步数据类型`));
+              }
+
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+        syncStartTime: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (!value) {
+                return callback(new Error(`请选择同步开始时间`));
+              }
+
+              if (
+                this.modalForm.syncEndTime &&
+                value >= this.modalForm.syncEndTime
+              ) {
+                return callback(new Error(`开始时间必须早于结束时间`));
+              }
+
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+        syncEndTime: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (!value) {
+                return callback(new Error(`请选择同步结束时间`));
+              }
+
+              if (
+                this.modalForm.syncStartTime &&
+                value <= this.modalForm.syncStartTime
+              ) {
+                return callback(new Error(`结束时间必须晚于开始时间`));
+              }
+
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+        durationTime: [
+          {
+            required: true,
+            message: "请输入定时同步时间",
+            trigger: "change",
+          },
+        ],
+        warningPhone: [
+          {
+            required: true,
+            validator: (rule, value, callback) => {
+              if (!value) {
+                return callback(new Error(`请输入同步异常预警`));
+              }
+
+              const vals = value.split(",");
+              const reg = /^1\d{10}$/;
+
+              if (vals.some((val) => !reg.test(val))) {
+                return callback(new Error(`内容中存在不合适的手机号码`));
+              }
+
+              callback();
+            },
+            trigger: "change",
+          },
+        ],
+      },
+      semesterList: [],
+      examList: [],
+    };
+  },
+  mounted() {
+    this.getSemesters();
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+        this.modalForm.dataType = [...val.dataType];
+      } else {
+        this.modalForm = { ...initModalForm };
+        this.modalForm.dataType = [];
+      }
+
+      this.getExams();
+    },
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async getSemesters() {
+      const res = await conditionListSemester(
+        {},
+        { headers: { schoolId: this.schoolId } }
+      );
+      this.semesterList = res || [];
+    },
+    async getExams() {
+      if (!this.modalForm.semesterId) return;
+      const res = await conditionListExam(
+        { semesterId: this.modalForm.semesterId },
+        { headers: { schoolId: this.schoolId } }
+      );
+      this.examList = res || [];
+    },
+    semesterChange() {
+      this.examList = [];
+      this.modalForm.examId = "";
+      this.getExams();
+    },
+    timeChange(field) {
+      const key = field === "syncStartTime" ? "syncEndTime" : "syncStartTime";
+      this.$refs.modalFormComp.validateField(key);
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = { ...this.modalForm };
+      const res = await schoolSetDatabaseSyncUpdate(datas).catch(() => {});
+      this.isSubmit = false;
+
+      if (!res) return;
+      this.$message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 1 - 1
src/modules/admin/components/school/SchoolSetCheck.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-check">
+  <div class="school-set-check part-box part-box-pad">
     <el-form ref="modalFormComp" label-width="240px">
       <el-form-item
         v-for="field in fields"

+ 1 - 1
src/modules/admin/components/school/SchoolSetData.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-data">
+  <div class="school-set-data part-box part-box-pad">
     <el-button type="primary" @click="toClear">清除基础数据</el-button>
     <p class="tips-info tips-error mt-1">
       注意:执行该操作,会将该单位下配置的基础数据全部清除,进入初始状态,请谨慎操作,该操作不可恢复!

+ 344 - 0
src/modules/admin/components/school/SchoolSetDatabaseSync.vue

@@ -0,0 +1,344 @@
+<template>
+  <div class="school-set-database-sync">
+    <div class="part-box part-box-pad">
+      <el-form
+        ref="modalFormComp"
+        :model="modalForm"
+        :rules="rules"
+        label-width="100px"
+        style="max-width: 1000px"
+      >
+        <el-form-item prop="type" label="数据库类型:">
+          <el-radio-group v-model="modalForm.type">
+            <el-radio
+              v-for="(val, key) in DATABASE_TYPE"
+              :key="key"
+              :label="key"
+              >{{ val }}</el-radio
+            >
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item prop="url" label="URL:">
+          <el-input
+            v-model.trim="modalForm.url"
+            placeholder="请输入URL"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item prop="username" label="用户名:">
+              <el-input
+                v-model.trim="modalForm.username"
+                placeholder="请输入用户名"
+                clearable
+              ></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item prop="password" label="密码:">
+              <el-input
+                v-model.trim="modalForm.password"
+                placeholder="请输入密码"
+                clearable
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item>
+          <el-button type="primary" @click="toSave">保存</el-button>
+          <el-button type="primary" @click="toTest">测试链接</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <div class="box-justify mb-2">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="toAdd">新增</el-button>
+        </div>
+      </div>
+
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          prop="semesterName"
+          label="学年学期"
+          min-width="210"
+        ></el-table-column>
+        <el-table-column
+          prop="name"
+          label="考试名称"
+          min-width="160"
+        ></el-table-column>
+        <el-table-column
+          prop="courseName"
+          label="同步数据类型"
+          width="120"
+        ></el-table-column>
+        <el-table-column
+          prop="courseCode"
+          label="同步记录数"
+          width="120"
+        ></el-table-column>
+        <el-table-column prop="createTime" label="同步开始时间" width="170">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column prop="createTime" label="同步结束时间" width="170">
+          <span slot-scope="scope">{{
+            scope.row.createTime | timestampFilter
+          }}</span>
+        </el-table-column>
+        <el-table-column
+          prop="courseCode"
+          label="状态"
+          width="100"
+        ></el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="220"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >设置</el-button
+            >
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toViewLog(scope.row)"
+              >查看日志</el-button
+            >
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toSync(scope.row)"
+              >手动同步</el-button
+            >
+            <el-button
+              class="btn-danger"
+              type="text"
+              @click="toCloseSync(scope.row)"
+              >结束同步</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :pager-count="5"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+          @size-change="pageSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- 日志 -->
+    <el-dialog
+      class="log-dialog"
+      :visible.sync="logModalIsShow"
+      title="日志"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      top="10vh"
+      width="800px"
+    >
+      <div style="min-height: 300px; overflow: auto">
+        <p v-for="(cont, cidx) in logList" :key="cidx">{{ cont }}</p>
+      </div>
+    </el-dialog>
+
+    <!-- ModifyDatabaseSync -->
+    <ModifyDatabaseSync
+      ref="ModifyDatabaseSync"
+      :instance="curRow"
+      :school-id="school.id"
+      @modified="getList"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  schoolSetDatabaseSyncBaseinfo,
+  schoolSetDatabaseSyncBaseinfoUpdate,
+  schoolSetDatabaseSyncBaseinfoTest,
+  schoolSetDatabaseSyncList,
+  schoolSetDatabaseSyncHandleSync,
+  schoolSetDatabaseSyncClose,
+} from "../../api";
+import { DATABASE_TYPE } from "@/constants/enumerate";
+import ModifyDatabaseSync from "./ModifyDatabaseSync.vue";
+
+const initModalForm = {
+  type: "",
+  url: "",
+  username: "",
+  password: "",
+};
+
+export default {
+  name: "school-set-database-sync",
+  components: { ModifyDatabaseSync },
+  props: {
+    school: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalForm: { ...initModalForm },
+      rules: {
+        type: [
+          {
+            required: true,
+            message: "请选择数据库类型",
+            trigger: "change",
+          },
+        ],
+        url: [
+          {
+            required: true,
+            message: "请输入URL",
+            trigger: "change",
+          },
+          {
+            message: "url不能超过999个字符",
+            max: 999,
+            trigger: "change",
+          },
+        ],
+        username: [
+          {
+            required: true,
+            message: "请输入用户名",
+            trigger: "change",
+          },
+          {
+            message: "用户名不能超过50个字符",
+            max: 50,
+            trigger: "change",
+          },
+        ],
+        password: [
+          {
+            required: true,
+            message: "请输入密码",
+            trigger: "change",
+          },
+          {
+            message: "密码不能超过50个字符",
+            max: 50,
+            trigger: "change",
+          },
+        ],
+      },
+      isSubmit: false,
+      dataList: [],
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      curRow: {},
+      DATABASE_TYPE,
+      // log
+      logList: [],
+      logModalIsShow: false,
+    };
+  },
+  methods: {
+    async getBaseInfo() {
+      const res = await schoolSetDatabaseSyncBaseinfo(this.school.id);
+      this.modalForm = this.$objAssign(initModalForm, res || {});
+    },
+    async toSave() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = { ...this.modalForm };
+      const res = await schoolSetDatabaseSyncBaseinfoUpdate(datas).catch(
+        () => {}
+      );
+      this.isSubmit = false;
+
+      if (!res) return;
+      this.$message.success("保存成功!");
+    },
+    async toTest() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = { ...this.modalForm };
+      const res = await schoolSetDatabaseSyncBaseinfoTest(datas).catch(
+        () => {}
+      );
+      this.isSubmit = false;
+
+      if (!res) return;
+      this.$message.success("测试成功!");
+    },
+    async getList() {
+      const datas = {
+        schoolId: this.school.id,
+        pageNumber: this.current,
+        pageSize: this.size,
+      };
+      const data = await schoolSetDatabaseSyncList(datas);
+      this.exams = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyDatabaseSync.open();
+    },
+    toAdd() {
+      this.curRow = {};
+      this.$refs.ModifyDatabaseSync.open();
+    },
+    toViewLog(row) {
+      this.logList = (row.summary || "").split("\n");
+      this.logModalIsShow = true;
+    },
+    async toSync(row) {
+      const res = await schoolSetDatabaseSyncHandleSync(row.id).catch(() => {});
+      if (!res) return;
+      this.$message.success("操作成功!");
+      this.getList();
+    },
+    async toCloseSync(row) {
+      const confirm = await this.$confirm(`确定要结束当前同步记录吗?`, "提示", {
+        type: "warning",
+      }).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await schoolSetDatabaseSyncClose(row.id);
+      this.$message.success("操作成功!");
+      this.getList();
+    },
+  },
+};
+</script>

+ 1 - 1
src/modules/admin/components/school/SchoolSetMenu.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-menu">
+  <div class="school-set-menu part-box part-box-pad">
     <privilege-set
       v-if="menus && menus.length"
       ref="PrivilegeSet"

+ 1 - 1
src/modules/admin/components/school/SchoolSetPaper.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-paper">
+  <div class="school-set-paper part-box part-box-pad">
     <el-form
       ref="modalFormComp"
       :model="modalForm"

+ 1 - 1
src/modules/admin/components/school/SchoolSetRole.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-role">
+  <div class="school-set-role part-box part-box-pad">
     <el-form
       ref="modalFormComp"
       :model="modalForm"

+ 1 - 1
src/modules/admin/components/school/SchoolSetStdno.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-stdno">
+  <div class="school-set-stdno part-box part-box-pad">
     <el-form
       ref="modalFormComp"
       :model="modalForm"

+ 1 - 1
src/modules/admin/components/school/SchoolSetSync.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-sync">
+  <div class="school-set-sync part-box part-box-pad">
     <el-form
       v-if="fields.length"
       ref="modalFormComp"

+ 1 - 1
src/modules/admin/components/school/SchoolSetTarget.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="school-set-target">
+  <div class="school-set-target part-box part-box-pad">
     <el-form
       ref="modalFormComp"
       label-width="100px"

+ 15 - 8
src/modules/base/api.js

@@ -14,17 +14,24 @@ export const getEnums = (type) => {
   return $postParam("/api/admin/common/get_enums", { type });
 };
 // semester
-export const conditionListSemester = (datas) => {
+export const conditionListSemester = (datas, config) => {
   // enable
-  return $postParam("/api/admin/basic/condition/list_semester", datas);
+  return $postParam("/api/admin/basic/condition/list_semester", datas, config);
 };
 // exam
-export const conditionListExam = ({ semesterId, category, enable }) => {
-  return $postParam("/api/admin/basic/condition/list_exam", {
-    semesterId,
-    category,
-    enable,
-  });
+export const conditionListExam = (
+  { semesterId, category, enable },
+  config = {}
+) => {
+  return $postParam(
+    "/api/admin/basic/condition/list_exam",
+    {
+      semesterId,
+      category,
+      enable,
+    },
+    config
+  );
 };
 // print_plan
 export const conditionListPrintPlan = ({ semesterId, examId }) => {

+ 1 - 1
src/plugins/axios.js

@@ -43,7 +43,7 @@ axios.interceptors.request.use(
       };
       Object.entries(ids).forEach(([key, val]) => {
         if (val === null || val === "null" || val === "") return;
-        config.headers[key] = val;
+        if (!config.headers[key]) config.headers[key] = val;
       });
 
       // 新版鉴权