Browse Source

feat: robot

zhangjie 6 tháng trước cách đây
mục cha
commit
c1a77534e5

+ 22 - 0
src/App.vue

@@ -1,5 +1,27 @@
 <template>
   <div id="app">
     <router-view />
+
+    <RobotHelp v-if="showRobot" />
   </div>
 </template>
+
+<script>
+import RobotHelp from "./components/RobotHelp.vue";
+export default {
+  components: {
+    RobotHelp,
+  },
+  data() {
+    return {
+      robotHelp: false,
+    };
+  },
+  computed: {
+    showRobot() {
+      const whiteRoutes = ["Login"];
+      return !whiteRoutes.includes(this.$route.name);
+    },
+  },
+};
+</script>

BIN
src/assets/images/bg-robot.png


+ 26 - 0
src/assets/styles/pages.scss

@@ -1717,3 +1717,29 @@
     }
   }
 }
+
+// robot-help
+.robot-help {
+  .robot-btn {
+    width: 56px;
+    height: 56px;
+    position: fixed;
+    z-index: 999;
+    background-image: url(../images/bg-robot.png);
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 50%;
+    cursor: pointer;
+  }
+}
+.robot-popover {
+  padding: 5px 8px;
+  text-align: center;
+  min-width: 100px;
+  font-weight: 500;
+  font-size: 14px;
+  color: #3a5ae5;
+  line-height: 22px;
+  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.12);
+}

+ 117 - 0
src/components/RobotHelp.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="robot-help">
+    <el-popover
+      v-if="aiRobotResult.enable"
+      content="点我开始提问"
+      placement="top"
+      effect="light"
+      popper-class="robot-popover"
+      trigger="manual"
+      v-model="tipsShow"
+    >
+      <div
+        slot="reference"
+        class="robot-btn"
+        :style="styles"
+        v-move-ele.prevent.stop="{
+          mouseMove: moveElement,
+          mouseUp: moveElementOver,
+          click: toHelp,
+        }"
+        @mouseenter="mouseenterHandle"
+        @mouseleave="mouseleaveHandle"
+      ></div>
+    </el-popover>
+  </div>
+</template>
+
+<script>
+import MoveEle from "@/directives/move-ele";
+
+export default {
+  name: "robot-help",
+  directives: { MoveEle },
+  data() {
+    return {
+      aiRobotResult: this.$ls.get("user", { aiRobotResult: {} }).aiRobotResult,
+      sizePos: {
+        x: 0,
+        y: 0,
+      },
+      tipsShow: false,
+    };
+  },
+  mounted() {
+    this.sizePos = this.getCachePosition();
+    this.registResizeEvent();
+  },
+  computed: {
+    styles() {
+      return {
+        left: `${this.sizePos.x}px`,
+        top: `${this.sizePos.y}px`,
+      };
+    },
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", () => {
+      this.sizePos = this.getCachePosition();
+    });
+  },
+  methods: {
+    toHelp() {
+      if (!this.aiRobotResult?.url) return;
+      window.open(this.aiRobotResult.url);
+    },
+    mouseenterHandle() {
+      this.tipsShow = true;
+    },
+    mouseleaveHandle() {
+      this.tipsShow = false;
+    },
+    getValidateSize({ x, y }) {
+      return {
+        x: Math.max(Math.min(x, window.innerWidth - 56), 0),
+        y: Math.max(Math.min(y, window.innerHeight - 56), 0),
+      };
+    },
+    moveElement({ left, top }) {
+      this.sizePos.x = left;
+      this.sizePos.y = top;
+      this.tipsShow = false;
+
+      this.sizePos = this.getValidateSize(this.sizePos);
+    },
+    moveElementOver({ left, top }) {
+      this.sizePos.x = left;
+      this.sizePos.y = top;
+      this.sizePos = this.getValidateSize(this.sizePos);
+      this.tipsShow = true;
+
+      this.setCachePosition();
+    },
+    getCachePosition() {
+      const robotPos = this.$ls.get("robotPos", "");
+      if (!robotPos)
+        return { x: window.innerWidth - 92, y: window.innerHeight - 92 };
+
+      return {
+        x: (robotPos.x * window.innerWidth) / robotPos.pw,
+        y: (robotPos.y * window.innerHeight) / robotPos.ph,
+      };
+    },
+    setCachePosition() {
+      this.$ls.set("robotPos", {
+        ...this.sizePos,
+        pw: window.innerWidth,
+        ph: window.innerHeight,
+      });
+    },
+    registResizeEvent() {
+      window.addEventListener("resize", () => {
+        this.sizePos = this.getCachePosition();
+      });
+    },
+  },
+};
+</script>

+ 71 - 0
src/directives/move-ele.js

@@ -0,0 +1,71 @@
+export default {
+  inserted(el, { value, modifiers }) {
+    let [_x, _y] = [0, 0];
+    // 当前拖动事务开始前元素的left,top
+    let [oleft, otop] = [0, 0];
+    // 元素移动后的left,top
+    let [left, top] = [0, 0];
+    let isDrag = false;
+
+    let moveHandle = function (e) {
+      isDrag = true;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      const mLeft = e.pageX - _x;
+      const mTop = e.pageY - _y;
+      left = oleft + mLeft;
+      top = otop + mTop;
+
+      // console.log(left, top);
+
+      if (value && value.mouseMove) {
+        value.mouseMove({ left, top, mLeft, mTop });
+      } else {
+        el.style.left = left + "px";
+        el.style.top = top + "px";
+      }
+    };
+
+    let upHandle = function (e) {
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      const mLeft = e.pageX - _x;
+      const mTop = e.pageY - _y;
+      left = oleft + mLeft;
+      top = otop + mTop;
+
+      if (value && value.mouseUp) value.mouseUp({ left, top, mLeft, mTop });
+      if (value && value.click && !isDrag) value.click(e);
+
+      document.removeEventListener("mousemove", moveHandle);
+      document.removeEventListener("mouseup", upHandle);
+
+      isDrag = false;
+    };
+
+    el.addEventListener("mousedown", function (e) {
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      _x = e.pageX;
+      _y = e.pageY;
+      oleft = el.offsetLeft;
+      otop = el.offsetTop;
+      if (value && value.mouseDown) value.mouseDown({ oleft, otop }, e);
+
+      document.addEventListener("mousemove", moveHandle);
+      document.addEventListener("mouseup", upHandle);
+    });
+  },
+};

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

@@ -168,6 +168,14 @@ export const schoolSetDatabaseSyncEnable = ({ schoolId, examId, enable }) => {
   });
 };
 
+// 机器人配置
+export const schoolSetRobotInfo = (schoolId) => {
+  return $postParam("/api/admin/set/ai/robot/select", { schoolId });
+};
+export const schoolSetRobotUpdate = (datas) => {
+  return $post("/api/admin/set/ai/robot/save", datas);
+};
+
 // log
 export const systemLogExport = () => {
   return $postParam(

+ 6 - 0
src/modules/admin/components/ModifySchoolSet.vue

@@ -38,6 +38,7 @@ import SchoolSetSync from "./school/SchoolSetSync.vue";
 import SchoolSetTarget from "./school/SchoolSetTarget.vue";
 import SchoolSetStdno from "./school/SchoolSetStdno.vue";
 import SchoolSetDatabaseSync from "./school/SchoolSetDatabaseSync.vue";
+import SchoolSetRobot from "./school/SchoolSetRobot.vue";
 
 export default {
   name: "modify-school-set",
@@ -51,6 +52,7 @@ export default {
     SchoolSetTarget,
     SchoolSetStdno,
     SchoolSetDatabaseSync,
+    SchoolSetRobot,
   },
   props: {
     school: {
@@ -101,6 +103,10 @@ export default {
           name: "数据还原",
           val: "data",
         },
+        {
+          name: "机器人配置",
+          val: "robot",
+        },
       ],
     };
   },

+ 84 - 0
src/modules/admin/components/school/SchoolSetRobot.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="school-set-robot part-box part-box-pad">
+    <el-form ref="modalFormComp" label-width="240px">
+      <el-form-item
+        v-for="field in fields"
+        :key="field.code"
+        :prop="field.code"
+        :label="field.name + ':'"
+      >
+        <el-radio-group v-if="field.isBoolean" v-model="field.value">
+          <el-radio
+            v-for="item in OPEN_STATUS"
+            :key="item.value"
+            :label="item.value"
+            >{{ item.label }}</el-radio
+          >
+        </el-radio-group>
+        <el-input
+          v-else
+          v-model.trim="field.value"
+          :placeholder="`请输入${field.name}`"
+          clearable
+        >
+        </el-input>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" :loading="loading" @click="confirm"
+          >保存</el-button
+        >
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { OPEN_STATUS } from "../../../../constants/enumerate";
+import { schoolSetRobotInfo, schoolSetRobotUpdate } from "../../api";
+
+export default {
+  name: "school-set-robot",
+  props: {
+    school: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      loading: false,
+      fields: [],
+      OPEN_STATUS,
+    };
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    async initData() {
+      const data = await schoolSetRobotInfo(this.school.id);
+      this.fields = data.result || [];
+      this.fields.forEach((field) => {
+        field.isBoolean =
+          typeof field.value === "boolean" ||
+          ["true", "false"].includes(field.value);
+        if (field.isBoolean && ["true", "false"].includes(field.value))
+          field.value = field.value === "true";
+      });
+    },
+    async confirm() {
+      if (this.loading) return;
+      this.loading = true;
+      const datas = { param: this.fields, schoolId: this.school.id };
+      const res = await schoolSetRobotUpdate(datas).catch(() => {});
+      this.loading = false;
+      if (!res) return;
+
+      this.$message.success("修改成功!");
+      this.initData();
+    },
+  },
+};
+</script>