瀏覽代碼

欢迎页&隐私框&手机号验证

Michael Wang 3 年之前
父節點
當前提交
d17c18a2eb

+ 2 - 0
src/constants/constants.ts

@@ -12,6 +12,8 @@ export const DOMAIN = env.DEV
   ? (env.VITE_DEVELOPMENT_DOMAIN as string)
   : domainCandidate;
 
+export const PRIVACY_READ_VERSION_NUMBER = "1";
+
 /** 限流请求的服务器 */
 export const LIMIT_SERVER = "https://tcc.qmth.com.cn";
 

+ 21 - 0
src/features/OnlineExam/OnlineExamHome.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>在线考试</div>
+</template>
+
+<style scoped>
+.home {
+  margin: 20px;
+}
+.welcome-modal {
+  display: flex;
+  margin-top: 26px;
+  margin-left: 20px;
+}
+.smile-png {
+  width: 40px;
+  height: 40px;
+  background: url(./smile-icon.png);
+}
+</style>

+ 2 - 1
src/features/UserLogin/UserLogin.vue

@@ -327,7 +327,8 @@ async function afterLogin(loginRes: any) {
     // 有断点或者异常,停止后续处理
     if (await checkExamInProgress().catch(() => true)) return;
 
-    void router.push({ name: "ChangePassword" });
+    void router.push({ name: "WelcomePage" });
+    $message.destroyAll();
   } catch (error) {
     logger({
       cnl: ["local", "server"],

+ 114 - 0
src/features/WelcomePage/PhoneVerifyForDD.vue

@@ -0,0 +1,114 @@
+<script lang="ts" setup>
+import { DOMAIN } from "@/constants/constants";
+import { httpApp } from "@/plugins/axiosApp";
+import { store } from "@/store/store";
+import { onMounted, onUnmounted } from "vue";
+import { showLogout } from "@/utils/utils";
+
+let phoneModal = $ref(false);
+let code = $ref("");
+let interval = $ref(-1);
+let remainTime = $ref(0);
+let btnText = "发送验证码";
+
+onMounted(() => {
+  if (
+    ["cugr.ecs.qmth.com.cn", "test.cugr.qmth.com.cn"].includes(DOMAIN) &&
+    !localStorage.getItem("phoneVerified")
+  ) {
+    phoneModal = true;
+  }
+});
+onUnmounted(() => clearInterval(interval));
+
+async function getCode() {
+  logger({
+    cnl: ["local", "server"],
+    pgn: "PhoneVerifyForDD",
+    act: "发送验证码按钮",
+  });
+  try {
+    await httpApp.post("/api/ecs_oe_student/sms/sendSmsCodeToStudent");
+  } catch (error) {
+    $message.error("发送验证码失败,请重试!", {
+      duration: 15,
+      closable: true,
+    });
+    return;
+  }
+
+  remainTime = 90;
+  interval = window.setInterval(() => {
+    if (remainTime > 0) {
+      remainTime--;
+    } else {
+      clearInterval(interval);
+    }
+  }, 1000);
+}
+async function verify() {
+  if (code.length < 4) {
+    $message.warning("请输入正确的验证码");
+    return;
+  }
+  logger({
+    cnl: ["local", "server"],
+    pgn: "PhoneVerifyForDD",
+    act: "点击验证按钮",
+  });
+  const { phoneNumber } = store.user;
+  try {
+    await httpApp.post(
+      `/api/ecs_oe_student/sms/checkSmsCode?phoneNumber=${phoneNumber}&code=${code}`,
+      null,
+      { setGlobalMask: true }
+    );
+    phoneModal = false;
+    localStorage.setItem("phoneVerified", "true");
+  } catch (error) {
+    $message.error("验证手机号接口失败,请重试!", {
+      duration: 15,
+      closable: true,
+    });
+    logger({
+      cnl: ["local", "server"],
+      pgn: "PhoneVerifyForDD",
+      dtl: "验证手机号接口失败,请重试!",
+    });
+  }
+}
+</script>
+
+<template>
+  <!-- 手机号验证。For 地质大学 -->
+  <n-modal
+    :show="phoneModal"
+    preset="dialog"
+    title="验证手机号"
+    type="info"
+    :closable="false"
+    negativeText="退出系统"
+    positiveText="确认"
+    :zIndex="2000"
+    @positiveClick="verify"
+    @negativeClick="() => showLogout('未确认手机号')"
+  >
+    <div class="tw-flex">
+      <span class="tw-min-w-max tw-mr-2">预留手机号:</span>
+      <span>{{ store.user.phoneNumber }}</span>
+    </div>
+    <div class="tw-flex tw-items-center tw-mt-2">
+      <div class="tw-min-w-max tw-mr-2">输入验证码:</div>
+      <n-input
+        v-model:value="code"
+        maxlength="4"
+        placeholder="验证码"
+        type="text"
+        class="tw-mr-2"
+      />
+      <n-button type="success" :disabled="remainTime > 0" @click="getCode">
+        {{ btnText }}{{ remainTime > 0 ? "(" + remainTime + "秒)" : "" }}
+      </n-button>
+    </div>
+  </n-modal>
+</template>

+ 177 - 0
src/features/WelcomePage/PrivacyDialog.vue

@@ -0,0 +1,177 @@
+<script setup lang="ts">
+import { PRIVACY_READ_VERSION_NUMBER } from "@/constants/constants";
+import { useTimers } from "@/setups/useTimers";
+import { store } from "@/store/store";
+import { showLogout } from "@/utils/utils";
+
+const { addInterval } = useTimers();
+let showPrivacyModal = $ref(true);
+let remainTime = $ref(0);
+
+const privacyReadVersionUserId = localStorage.getItem(
+  "privacy-read-version-" + store.user.id
+);
+if (privacyReadVersionUserId !== PRIVACY_READ_VERSION_NUMBER) {
+  showPrivacyModal = true;
+
+  remainTime = 15;
+  addInterval(() => {
+    if (remainTime > 0) {
+      remainTime--;
+    }
+  }, 1000);
+}
+
+function agreeTerms() {
+  logger({
+    cnl: ["server"],
+    pgn: "PrivacyDialog",
+    act: "点击同意按钮",
+  });
+  localStorage.setItem(
+    "privacy-read-version-" + store.user.id,
+    PRIVACY_READ_VERSION_NUMBER
+  );
+  showPrivacyModal = false;
+}
+function disagreeTerms() {
+  logger({
+    cnl: ["server"],
+    pgn: "PrivacyDialog",
+    act: "点击拒绝按钮",
+  });
+  showLogout("不同意隐私条款");
+}
+</script>
+
+<template>
+  <n-modal
+    :show="showPrivacyModal"
+    :closable="false"
+    preset="card"
+    title="隐私条款"
+    style="width: 600px"
+    :zIndex="3000"
+  >
+    <div style="font-size: 16px">
+      <div class="privacy-content">
+        <p style="text-indent: 2em">
+          本产品尊重并保护所有服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务,会按照本隐私权政策的规定使用和披露您的个人信息。但将以高度的勤勉、审慎义务对待这些信息。除本隐私权政策规定外,在未征得您事先许可的情况下,本产品不会将这些信息对外披露或向第三方提供(法律法规规定应当披露或提供的除外)。本产品会不时更新本隐私权政策。您在同意本产品服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本产品服务使用协议不可分割的一部分。
+        </p>
+        <p>1.适用范围</p>
+        <p>1) 在您注册使用本产品帐号时,根据本产品要求提供的个人注册信息;</p>
+        <p>
+          2)
+          在您使用本产品网络服务,或访问本产品网页时,本产品自动接收并记录的您的访问设备信息,包括但不限于您的、IP地址、浏览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您访问的网页记录等数据;
+        </p>
+        <p>3) 本产品通过合法途径从商业伙伴处取得的用户个人数据;</p>
+        <p>4) 您在使用本产品平台进行考试身份验证时采集的人脸信息</p>
+        <p>5) 您在使用本产品平台提供的搜索服务时输入的关键字信息;</p>
+        <p>2.信息使用</p>
+        <p>
+          1)
+          本产品不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本产品(含本产品关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。
+        </p>
+        <p>
+          2)
+          本产品亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何本产品平台用户如从事上述活动,一经发现,本产品有权立即终止与该用户的服务协议。
+        </p>
+        <p>
+          3)
+          为服务用户的目的,本产品可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息,或者与本产品合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。
+        </p>
+        <p>3.信息披露</p>
+        <p>
+          在如下情况下,本产品将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息:
+        </p>
+        <p>1) 经您事先同意,向第三方披露;</p>
+        <p>2) 为提供您所要求的产品和服务,而必须和第三方分享您的个人信息;</p>
+        <p>
+          3)
+          根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
+        </p>
+        <p>
+          4)
+          如您出现违反中国有关法律、法规或者本产品服务协议或相关规则的情况,需要向第三方披露;
+        </p>
+        <p>5) 您自行向社会公众公开的个人信息;</p>
+        <p>
+          6)
+          从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。根据法律规定,共享、转让经去标识化处理的个人信息,且确保数据接收方无法复原并重新识别个人信息主体的,不属于个人信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。
+        </p>
+        <p>4.信息存储和交换</p>
+        <p>
+          本产品在中华人民共和国境内收集的有关您的信息和资料将保存在本产品及(或)其关联公司位于中华人民共和国境内的服务器上;针对境外情况,您的个人信息可能会被转移到您使用产品或服务所在国家/地区的境外管辖区,或者受到来自这些管辖区的访问。
+        </p>
+        <p>5.Cookie的使用</p>
+        <p>
+          1)
+          在您未拒绝接受Cookie的情况下,本产品会在您的访问设备上设定或读取Cookie,以便您能登录或使用依赖于Cookie的本产品平台服务或功能。本产品使用Cookie可为您提供更加周到的个性化服务。
+        </p>
+        <p>
+          2)
+          您有权选择接受或拒绝接受Cookie。您可以通过修改浏览器设置的方式拒绝接受Cookie。但如果您选择拒绝接受Cookie,则您可能无法登录或使用依赖于Cookie的本产品网络服务或功能。
+        </p>
+        <p>3) 通过本产品所设Cookie所取得的有关信息,将适用本政策。</p>
+        <p>6.信息安全</p>
+        <p>
+          1)
+          本产品帐号均有安全保护功能,请妥善保管您的用户名及密码信息。本产品会采用符合业界标准的安全防护措施,包括建立合理的制度规范、安全技术来防止您的个人信息遭到未经授权的访问使用、修改,避免数据的损坏或丢失。
+        </p>
+        <p>
+          2)
+          我们会采取一切合理可行的措施,确保未收集无关的个人信息。我们只会在达成本政策所述目的所需的期限内保留您的个人信息,除非需要延长保留期或受到法律的允许。
+        </p>
+        <p>
+          3)
+          互联网并非绝对安全的环境,而且电子邮件、即时通讯、及与其他用户的交流方式并未加密,我们强烈建议您不要通过此类方式发送个人信息。请使用复杂密码,协助我们保证您的账号安全。
+        </p>
+        <p>
+          4)
+          如您发现自己的个人信息泄密,尤其是本应用用户名及密码发生泄露,请您立即联络本应用客服,以便本应用采取相应措施。
+        </p>
+        <p>7.本隐私政策的更改</p>
+        <p>
+          1)如果决定更改隐私政策,我们会在本政策中,以及我们认为适当的位置发布这些更改,以便您了解我们如何收集、使用您的个人信息,哪些人可以访问这些信息,以及在什么情况下我们会透露这些信息。
+        </p>
+        <p>
+          2)我们保留随时修改本政策的权利,因此请经常查看。如对本政策作出重大更改,我们会通过网站通知的形式告知。
+        </p>
+      </div>
+
+      <div class="tw-flex tw-justify-center tw-mt-2">
+        <n-button
+          type="success"
+          style="margin-right: 5px; min-width: 120px"
+          :disabled="remainTime > 0"
+          @click="agreeTerms"
+        >
+          同意{{ remainTime ? `(${remainTime}秒)` : "" }}
+        </n-button>
+        <n-button
+          type="success"
+          style="margin-right: 5px; min-width: 120px"
+          @click="disagreeTerms"
+        >
+          拒绝
+        </n-button>
+      </div>
+    </div>
+  </n-modal>
+</template>
+
+<style scoped>
+.home {
+  margin: 20px;
+}
+.privacy-content {
+  font-size: 14px;
+  width: 100%;
+  height: 400px;
+  overflow-y: scroll;
+  text-align: left;
+}
+.privacy-content p {
+  margin-bottom: 5px;
+}
+</style>

+ 49 - 0
src/features/WelcomePage/WelcomePage.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import router from "@/router";
+import { store } from "@/store/store";
+import PhoneVerifyForDD from "./PhoneVerifyForDD.vue";
+import PrivacyDialog from "./PrivacyDialog.vue";
+
+function goOnlineExam() {
+  void router.push({ name: "OnlineExam" });
+}
+</script>
+
+<template>
+  <n-modal :show="true" preset="card" title="欢迎" style="width: 400px">
+    <div class="welcome-modal tw-mb-6">
+      <div class="smile-png" />
+      <div class="tw-ml-5">
+        <div class="tw-mb-4">欢迎使用考试系统,祝顺利完成考试!</div>
+        <div class="em-text">
+          姓名:{{ store.user.name }} - {{ " " }}
+          {{ store.user.studentCodeList.join(",") }}
+        </div>
+        <div class="em-text">专业:{{ store.user.specialty }}</div>
+      </div>
+    </div>
+
+    <n-button class="tw-float-right" type="success" @click="goOnlineExam">
+      确定
+    </n-button>
+  </n-modal>
+  <PhoneVerifyForDD />
+  <PrivacyDialog />
+</template>
+
+<style scoped>
+.welcome-modal {
+  display: flex;
+  margin-left: 20px;
+}
+.smile-png {
+  width: 40px;
+  height: 40px;
+  background: url(./smile-icon.png);
+}
+
+.em-text {
+  font-weight: bold;
+  line-height: 25px;
+}
+</style>

二進制
src/features/WelcomePage/smile-icon.png


+ 12 - 0
src/router/index.ts

@@ -1,6 +1,8 @@
 import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
 import UserLogin from "@/features/UserLogin/UserLogin.vue";
 import MainLayout from "@/components/MainLayout/MainLayout.vue";
+import OnlineExam from "@/features/OnlineExam/OnlineExamHome.vue";
+import WelcomePage from "@/features/WelcomePage/WelcomePage.vue";
 import ChangePassword from "@/features/ChangePassword/ChangePassword.vue";
 import SiteMessage from "@/features/SiteMessage/SiteMessage.vue";
 import SiteMessageDetail from "@/features/SiteMessage/SiteMessageDetail.vue";
@@ -14,6 +16,16 @@ const routes: RouteRecordRaw[] = [
     path: "/",
     component: MainLayout,
     children: [
+      {
+        path: "welcome",
+        component: WelcomePage,
+        name: "WelcomePage",
+      },
+      {
+        path: "online-exam",
+        component: OnlineExam,
+        name: "OnlineExam",
+      },
       {
         path: "password",
         component: ChangePassword,