浏览代码

fix: 修复用户端登录/注册页面交互问题

周玉环 1 天之前
父节点
当前提交
139a03af6d

+ 16 - 7
xinkeaboard-web/components/SliderVerify.vue

@@ -1,10 +1,11 @@
 <template>
-  <div class="verify-content">
+  <div class="verify-content" v-loading="loading">
     <slide-verify
       ref="block"
       v-bind="props.slideVerifyOptions"
       :slider-text="sliderText"
       @again="onAgain"
+      @imageLoad="imageLoad"
       @success="onSuccess"
       @fail="onFail"
     ></slide-verify>
@@ -16,12 +17,11 @@
     </div>
   </div>
 </template>
-<script lang="ts" setup>
+<script setup>
 import { ref, computed } from "vue";
-import SlideVerify from "vue3-slide-verify";
-import type { SlideVerifyInstance } from "vue3-slide-verify";
+import SlideVerify from "./vue3-slide-verify/index";
 import { getCurLanguage } from "@/composables/common.js";
-import "vue3-slide-verify/dist/style.css";
+import "./vue3-slide-verify/style.css";
 
 const props = defineProps({
   slideVerifyOptions: {
@@ -31,8 +31,9 @@ const props = defineProps({
 });
 
 const emits = defineEmits(["onSuccess", "onFail"]);
+const loading = ref(true)
 
-const block = ref<SlideVerifyInstance>();
+const block = ref();
 const L = getCurLanguage();
 
 const sliderText = computed(
@@ -44,15 +45,23 @@ const onSuccess = () => {
 };
 
 const refresh = () => {
+  loading.value = true;
   block.value?.refresh();
 };
 
+const imageLoad = () => {
+  loading.value = false;
+}
+
 const onFail = () => {
-  emits("onFail");
+  loading.value = true;
+  block.value?.refresh();
+  emits('onFail')
 };
 
 const onAgain = () => {
   // 刷新
+  loading.value = true;
   block.value?.refresh();
 };
 </script>

+ 35 - 10
xinkeaboard-web/components/register/RegisterAccount.vue

@@ -30,8 +30,10 @@
       <input
         :type="showPwdFlag ? 'text' : 'password'"
         v-model="password"
-        :placeholder="L['请输入6~20位英文、数字、符号']"
+        :placeholder="L['请输入密码']"
         class="input"
+        @focus="checkPwdValidate"
+        @blur="checkPwdValidate"
       />
       <div class="cancel" @click="isShowPwd">
         <span
@@ -48,6 +50,13 @@
         ></span>
       </div>
     </div>
+    <div class="error" v-if="checkPwdErrorMsg">
+      <span
+        style="color: #e1251b; font-size: 14px"
+        class="iconfont icon-jubao"
+      ></span>
+      {{ checkPwdErrorMsg }}
+    </div>
     <div class="item confirm">
       <span style="color: #bbb; font-size: 21px; padding-top: 7px" class="icon"
         ><img src="/register/pwd_confirm.png" alt=""
@@ -145,15 +154,13 @@ const confirmPassword = ref(""); //二次确认密码
 const showPwdFlag = ref(false); //密码是否明文显示,默认密文
 const showConfirmPwdFlag = ref(false); // 二次密码是否明文显示,默认密文
 const joinForFreeLoading = ref(false);
+const checkPwdErrorMsg = ref("");
+const focusPasword = ref(false);
 
 const joinForFreeDisabled = computed(
   () => !name.value || !password.value || !confirmPassword.value
 );
 
-const isPasswordMatched = computed(
-  () => password.value === confirmPassword.value
-);
-
 const clearInputVal = () => {
   name.value = "";
 };
@@ -171,10 +178,14 @@ const isShowConfirmPwd = () => {
 //是否同意用户注册协议
 const agree = () => {
   agreeFlag.value = !agreeFlag.value;
+  if (agreeFlag.value) {
+    agreeErrorMsg.value = "";
+  }
 };
 
 const joinForFree = () => {
-  if (!isPasswordMatched.value) return;
+  if (!checkPwdValidate()) return;
+  if (!checkPwdMatched()) return;
   if (!agreeFlag.value) {
     agreeErrorMsg.value = L["请同意用户注册协议及隐私政策"];
     return false;
@@ -210,9 +221,23 @@ const joinForFree = () => {
 };
 
 const checkPwdMatched = () => {
-  checkErrorMsg.value = isPasswordMatched.value
-    ? ""
-    : L["register"]["两次输入的密码不一致"];
+  if (password.value === confirmPassword.value) {
+    checkErrorMsg.value = "";
+    return true;
+  }
+  checkErrorMsg.value = L["register"]["两次输入的密码不一致"];
+  return false;
+};
+
+const checkPwdValidate = () => {
+  focusPasword.value = false;
+  const regex = /^[A-Za-z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]{6,20}$/;
+  if (regex.test(password.value)) {
+    checkPwdErrorMsg.value = "";
+    return true;
+  }
+  checkPwdErrorMsg.value = L['请输入6~20位英文、数字、符号'];
+  return false;
 };
 
 watch(name, (val) => {
@@ -273,7 +298,7 @@ watch(name, (val) => {
     :before {
       position: absolute;
       top: 9px;
-      left: 14px;
+      // left: 14px;
     }
   }
 

+ 380 - 0
xinkeaboard-web/components/vue3-slide-verify/index.js

@@ -0,0 +1,380 @@
+import { reactive, ref, defineComponent, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, createCommentVNode, toDisplayString } from "vue";
+const PI = Math.PI;
+function sum(x, y) {
+  return x + y;
+}
+function square(x) {
+  return x * x;
+}
+function draw(ctx, x, y, l, r, operation) {
+  ctx.beginPath();
+  ctx.moveTo(x, y);
+  ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
+  ctx.lineTo(x + l, y);
+  ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
+  ctx.lineTo(x + l, y + l);
+  ctx.lineTo(x, y + l);
+  ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
+  ctx.lineTo(x, y);
+  ctx.lineWidth = 2;
+  ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
+  ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
+  ctx.stroke();
+  ctx[operation]();
+  ctx.globalCompositeOperation = "destination-over";
+}
+function createImg(imgs, onload) {
+  const img = document.createElement("img");
+  img.crossOrigin = "Anonymous";
+  img.onload = onload;
+  img.onerror = () => {
+    img.src = getRandomImg(imgs);
+  };
+  img.src = getRandomImg(imgs);
+  return img;
+}
+function getRandomNumberByRange(start, end) {
+  return Math.round(Math.random() * (end - start) + start);
+}
+function getRandomImg(imgs) {
+  const len = imgs.length;
+  return len > 0 ? imgs[getRandomNumberByRange(0, len - 1)] : "https://picsum.photos/300/150?image=" + getRandomNumberByRange(0, 1084);
+}
+function throttle(fn, interval, options = { leading: true, trailing: true }) {
+  const { leading, trailing, resultCallback } = options;
+  let lastTime = 0;
+  let timer = null;
+  const _throttle = function(...args) {
+    return new Promise((resolve, reject) => {
+      const nowTime = new Date().getTime();
+      if (!lastTime && !leading)
+        lastTime = nowTime;
+      const remainTime = interval - (nowTime - lastTime);
+      if (remainTime <= 0) {
+        if (timer) {
+          clearTimeout(timer);
+          timer = null;
+        }
+        const result = fn.apply(this, args);
+        if (resultCallback)
+          resultCallback(result);
+        resolve(result);
+        lastTime = nowTime;
+        return;
+      }
+      if (trailing && !timer) {
+        timer = setTimeout(() => {
+          timer = null;
+          lastTime = !leading ? 0 : new Date().getTime();
+          const result = fn.apply(this, args);
+          if (resultCallback)
+            resultCallback(result);
+          resolve(result);
+        }, remainTime);
+      }
+    });
+  };
+  _throttle.cancel = function() {
+    if (timer)
+      clearTimeout(timer);
+    timer = null;
+    lastTime = 0;
+  };
+  return _throttle;
+}
+function useSlideAction() {
+  const origin = reactive({
+    x: 0,
+    y: 0
+  });
+  const success = ref(false);
+  const isMouseDown = ref(false);
+  const timestamp = ref(0);
+  const trail = ref([]);
+  const start = (e) => {
+    if (success.value)
+      return;
+    if (e instanceof MouseEvent) {
+      origin.x = e.clientX;
+      origin.y = e.clientY;
+    } else {
+      origin.x = e.changedTouches[0].pageX;
+      origin.y = e.changedTouches[0].pageY;
+    }
+    isMouseDown.value = true;
+    timestamp.value = Date.now();
+  };
+  const move = (w, e, cb) => {
+    if (!isMouseDown.value)
+      return false;
+    let moveX = 0;
+    let moveY = 0;
+    if (e instanceof MouseEvent) {
+      moveX = e.clientX - origin.x;
+      moveY = e.clientY - origin.y;
+    } else {
+      moveX = e.changedTouches[0].pageX - origin.x;
+      moveY = e.changedTouches[0].pageY - origin.y;
+    }
+    if (moveX < 0 || moveX + 38 >= w)
+      return false;
+    cb(moveX);
+    trail.value.push(moveY);
+  };
+  const verify = (left, blockX, accuracy) => {
+    const arr = trail.value;
+    const average = arr.reduce(sum) / arr.length;
+    const deviations = arr.map((x) => x - average);
+    const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
+    const leftNum = parseInt(left);
+    accuracy = accuracy <= 1 ? 1 : accuracy > 10 ? 10 : accuracy;
+    return {
+      spliced: Math.abs(leftNum - blockX) <= accuracy,
+      TuringTest: average !== stddev
+    };
+  };
+  const end = (e, cb) => {
+    if (!isMouseDown.value)
+      return false;
+    isMouseDown.value = false;
+    const moveX = e instanceof MouseEvent ? e.clientX : e.changedTouches[0].pageX;
+    if (moveX === origin.x)
+      return false;
+    timestamp.value = Date.now() - timestamp.value;
+    cb(timestamp.value);
+  };
+  return { origin, success, isMouseDown, timestamp, trail, start, move, end, verify };
+}
+var slideVerify_vue_vue_type_style_index_0_scoped_true_lang = "";
+var _export_sfc = (sfc, props) => {
+  const target = sfc.__vccOpts || sfc;
+  for (const [key, val] of props) {
+    target[key] = val;
+  }
+  return target;
+};
+const _sfc_main = defineComponent({
+  name: "SlideVerify",
+  props: {
+    l: {
+      type: Number,
+      default: 42
+    },
+    r: {
+      type: Number,
+      default: 10
+    },
+    w: {
+      type: Number,
+      default: 310
+    },
+    h: {
+      type: Number,
+      default: 155
+    },
+    sliderText: {
+      type: String,
+      default: "Slide filled right"
+    },
+    accuracy: {
+      type: Number,
+      default: 5
+    },
+    show: {
+      type: Boolean,
+      default: true
+    },
+    imgs: {
+      type: Array,
+      default: () => []
+    },
+    interval: {
+      type: Number,
+      default: 50
+    }
+  },
+  emits: ["success", "again", "fail", "refresh", "imageLoad"],
+  setup(props, { emit }) {
+    const { imgs, l, r, w, h, accuracy, interval } = props;
+    const loadBlock = ref(true);
+    const blockX = ref(0);
+    const blockY = ref(0);
+    const containerCls = reactive({
+      containerActive: false,
+      containerSuccess: false,
+      containerFail: false
+    });
+    const sliderBox = reactive({
+      iconCls: "arrow-right",
+      width: "0",
+      left: "0"
+    });
+    const block = ref();
+    const blockCtx = ref();
+    const canvas = ref();
+    const canvasCtx = ref();
+    let img;
+    const { success, start, move, end, verify } = useSlideAction();
+    const reset = () => {
+      var _a, _b;
+      success.value = false;
+      containerCls.containerActive = false;
+      containerCls.containerSuccess = false;
+      containerCls.containerFail = false;
+      sliderBox.iconCls = "arrow-right";
+      sliderBox.left = "0";
+      sliderBox.width = "0";
+      block.value.style.left = "0";
+      (_a = canvasCtx.value) == null ? void 0 : _a.clearRect(0, 0, w, h);
+      (_b = blockCtx.value) == null ? void 0 : _b.clearRect(0, 0, w, h);
+      block.value.width = w;
+      img.src = getRandomImg(imgs);
+    };
+    const refresh = () => {
+      reset();
+      emit("refresh");
+    };
+    function moveCb(moveX) {
+      sliderBox.left = moveX + "px";
+      let blockLeft = (w - 40 - 20) / (w - 40) * moveX;
+      block.value.style.left = blockLeft + "px";
+      containerCls.containerActive = true;
+      sliderBox.width = moveX + "px";
+    }
+    function endCb(timestamp) {
+      const { spliced, TuringTest } = verify(block.value.style.left, blockX.value, accuracy);
+      if (spliced) {
+        if (accuracy === -1) {
+          containerCls.containerSuccess = true;
+          sliderBox.iconCls = "success";
+          success.value = true;
+          emit("success", timestamp);
+          return;
+        }
+        if (TuringTest) {
+          containerCls.containerSuccess = true;
+          sliderBox.iconCls = "success";
+          success.value = true;
+          emit("success", timestamp);
+        } else {
+          containerCls.containerFail = true;
+          sliderBox.iconCls = "fail";
+          emit("again");
+        }
+      } else {
+        containerCls.containerFail = true;
+        sliderBox.iconCls = "fail";
+        emit("fail");
+      }
+    }
+    const touchMoveEvent = throttle((e) => {
+      move(w, e, moveCb);
+    }, interval);
+    const touchEndEvent = (e) => {
+      end(e, endCb);
+    };
+    onMounted(() => {
+      var _a, _b;
+      const _canvasCtx = (_a = canvas.value) == null ? void 0 : _a.getContext("2d");
+      const _blockCtx = (_b = block.value) == null ? void 0 : _b.getContext("2d", { willReadFrequently: true });
+      canvasCtx.value = _canvasCtx;
+      blockCtx.value = _blockCtx;
+      img = createImg(imgs, () => {
+        loadBlock.value = false;
+        const L = l + r * 2 + 3;
+        blockX.value = getRandomNumberByRange(L + 10, w - (L + 10));
+        blockY.value = getRandomNumberByRange(10 + r * 2, h - (L + 10));
+        if (_canvasCtx && _blockCtx) {
+          draw(_canvasCtx, blockX.value, blockY.value, l, r, "fill");
+          draw(_blockCtx, blockX.value, blockY.value, l, r, "clip");
+          _canvasCtx.drawImage(img, 0, 0, w, h);
+          _blockCtx.drawImage(img, 0, 0, w, h);
+          const _y = blockY.value - r * 2 - 1;
+          const imgData = _blockCtx.getImageData(blockX.value, _y, L, L);
+          block.value.width = L;
+          _blockCtx.putImageData(imgData, 0, _y);
+        }
+        emit('imageLoad')
+      });
+      document.addEventListener("mousemove", touchMoveEvent);
+      document.addEventListener("mouseup", touchEndEvent);
+    });
+    onBeforeUnmount(() => {
+      document.removeEventListener("mousemove", touchMoveEvent);
+      document.removeEventListener("mouseup", touchEndEvent);
+    });
+    return {
+      block,
+      canvas,
+      loadBlock,
+      containerCls,
+      sliderBox,
+      refresh,
+      sliderDown: start,
+      touchStartEvent: start,
+      touchMoveEvent,
+      touchEndEvent
+    };
+  }
+});
+const _hoisted_1 = ["width", "height"];
+const _hoisted_2 = ["width", "height"];
+const _hoisted_3 = { class: "slide-verify-slider-text" };
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return openBlock(), createElementBlock("div", {
+    id: "slideVerify",
+    class: "slide-verify",
+    style: normalizeStyle({ width: _ctx.w + "px" }),
+    onselectstart: "return false;"
+  }, [
+    createElementVNode("div", {
+      class: normalizeClass({ "slider-verify-loading": _ctx.loadBlock })
+    }, null, 2),
+    createElementVNode("canvas", {
+      ref: "canvas",
+      width: _ctx.w,
+      height: _ctx.h
+    }, null, 8, _hoisted_1),
+    _ctx.show ? (openBlock(), createElementBlock("div", {
+      key: 0,
+      class: "slide-verify-refresh-icon",
+      onClick: _cache[0] || (_cache[0] = (...args) => _ctx.refresh && _ctx.refresh(...args))
+    }, _cache[5] || (_cache[5] = [
+      createElementVNode("i", { class: "iconfont icon-refresh" }, null, -1)
+    ]))) : createCommentVNode("", true),
+    createElementVNode("canvas", {
+      ref: "block",
+      width: _ctx.w,
+      height: _ctx.h,
+      class: "slide-verify-block"
+    }, null, 8, _hoisted_2),
+    createElementVNode("div", {
+      class: normalizeClass(["slide-verify-slider", {
+        "container-active": _ctx.containerCls.containerActive,
+        "container-success": _ctx.containerCls.containerSuccess,
+        "container-fail": _ctx.containerCls.containerFail
+      }])
+    }, [
+      createElementVNode("div", {
+        class: "slide-verify-slider-mask",
+        style: normalizeStyle({ width: _ctx.sliderBox.width })
+      }, [
+        createElementVNode("div", {
+          class: "slide-verify-slider-mask-item",
+          style: normalizeStyle({ left: _ctx.sliderBox.left }),
+          onMousedown: _cache[1] || (_cache[1] = (...args) => _ctx.sliderDown && _ctx.sliderDown(...args)),
+          onTouchstart: _cache[2] || (_cache[2] = (...args) => _ctx.touchStartEvent && _ctx.touchStartEvent(...args)),
+          onTouchmove: _cache[3] || (_cache[3] = (...args) => _ctx.touchMoveEvent && _ctx.touchMoveEvent(...args)),
+          onTouchend: _cache[4] || (_cache[4] = (...args) => _ctx.touchEndEvent && _ctx.touchEndEvent(...args))
+        }, [
+          createElementVNode("i", {
+            class: normalizeClass(["slide-verify-slider-mask-item-icon", "iconfont", `icon-${_ctx.sliderBox.iconCls}`])
+          }, null, 2)
+        ], 36)
+      ], 4),
+      createElementVNode("span", _hoisted_3, toDisplayString(_ctx.sliderText), 1)
+    ], 2)
+  ], 4);
+}
+var SlideVerify = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-3f647794"]]);
+export { SlideVerify as default };

文件差异内容过多而无法显示
+ 0 - 0
xinkeaboard-web/components/vue3-slide-verify/style.css


+ 1 - 2
xinkeaboard-web/package.json

@@ -28,7 +28,6 @@
     "qrcanvas": "^3.1.2",
     "qs": "^6.11.0",
     "sass": "1.59.3",
-    "vue3-google-map": "^0.15.0",
-    "vue3-slide-verify": "^1.1.7"
+    "vue3-google-map": "^0.15.0"
   }
 }

+ 35 - 10
xinkeaboard-web/pages/login.vue

@@ -46,6 +46,7 @@
                 v-model="name"
                 :placeholder="L['请输入邮箱']"
                 class="input"
+                @blur="validateEmail"
                 autocomplete="off"
               />
               <div
@@ -56,6 +57,13 @@
                 <span style="color: #bbb" class="iconfont icon-cuowu"></span>
               </div>
             </div>
+            <div class="error" v-if="emailErrorMsg">
+              <span
+                style="color: #e1251b; font-size: 14px"
+                class="iconfont icon-jubao"
+              ></span>
+              {{ emailErrorMsg }}
+            </div>
             <div class="item password">
               <span
                 style="color: #bbb; font-size: 21px; padding-top: 7px"
@@ -77,9 +85,8 @@
               </div>
             </div>
 
-            <div class="error">
+            <div class="error" v-if="errorMsg">
               <span
-                v-if="errorMsg"
                 style="color: #e1251b; font-size: 14px"
                 class="iconfont icon-jubao"
               ></span>
@@ -140,6 +147,8 @@ const wxEnable = ref("");
 const pwdCalc = ref();
 const ImgBG = ref("");
 const loginLoding = ref(false);
+const emailCalc = ref();
+const emailErrorMsg = ref('')
 
 const loginDisabled = computed(() => !name.value || !password.value);
 useHead({
@@ -176,17 +185,33 @@ const getBg = () => {
 };
 getBg();
 
+// 校验邮箱
+const validateEmail = () => {
+  //邮箱非空的验证
+  if (!name.value) {
+    emailErrorMsg.value = L["请输入邮箱"];
+    return false;
+  }
+
+  // 邮箱格式验证
+  emailCalc.value = checkEmail(name.value);
+  if (emailCalc.value !== true) {
+    emailErrorMsg.value = emailCalc.value;
+    return false;
+  }
+  emailErrorMsg.value = "";
+
+  return true;
+};
+
 const login = () => {
   let param = {};
   param.username = name.value;
   param.password = password.value;
   param.loginType = loginType.value;
 
-  //账号验证
-  if (!param.username) {
-    errorMsg.value = L["请输入账号"];
-    return false;
-  }
+  //邮箱验证
+  if (!validateEmail()) return;
 
   //密码校验
   if (!param.password) {
@@ -230,14 +255,14 @@ const login = () => {
             //   router.replace({ path: "/member/home" });
             // }
           }
-        }).catch(err => {
-          loginLoding.value = false;
         })
     } else {
       //提示错误
       errorMsg.value = res.msg;
     }
-  });
+  }).finally(() => {
+    loginLoding.value = false;
+  })
 };
 //清空输入框内容
 const clearInputVal = (type) => {

+ 0 - 12
xinkeaboard-web/pnpm-lock.yaml

@@ -47,9 +47,6 @@ importers:
       vue3-google-map:
         specifier: ^0.15.0
         version: 0.15.0(vue@3.5.17(typescript@5.8.3))
-      vue3-slide-verify:
-        specifier: ^1.1.7
-        version: 1.1.7(typescript@5.8.3)
     devDependencies:
       cross-env:
         specifier: ^5.1.1
@@ -3950,9 +3947,6 @@ packages:
     peerDependencies:
       vue: '3'
 
-  vue3-slide-verify@1.1.7:
-    resolution: {integrity: sha512-EP1Ddr5N8wiF5HIEeF6+pKyu3jqWdBkhM0tB1gr/qU6J40IgklcqBBg9YAVaHv7uV4OoRmE5hlhrobPZArvzkw==}
-
   vue@3.5.17:
     resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==}
     peerDependencies:
@@ -8221,12 +8215,6 @@ snapshots:
       fast-deep-equal: 3.1.3
       vue: 3.5.17(typescript@5.8.3)
 
-  vue3-slide-verify@1.1.7(typescript@5.8.3):
-    dependencies:
-      vue: 3.5.17(typescript@5.8.3)
-    transitivePeerDependencies:
-      - typescript
-
   vue@3.5.17(typescript@5.8.3):
     dependencies:
       '@vue/compiler-dom': 3.5.17

部分文件因为文件数量过多而无法显示