Login.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. <template>
  2. <div class="home">
  3. <header class="header">
  4. <div class="school-logo">
  5. <img
  6. class="logo-size"
  7. v-show="this.logoPath"
  8. :src="this.logoPath"
  9. alt="school logo"
  10. style="background: linear-gradient(to bottom, #38f6f5 0%, #8efdf4 100%);"
  11. @load="e => (e.target.style = '')"
  12. />
  13. </div>
  14. <a
  15. class="close"
  16. style="border-bottom-left-radius: 6px;"
  17. @click="closeApp"
  18. >
  19. 关闭
  20. </a>
  21. </header>
  22. <div class="center">
  23. <div class="content">
  24. <div style="display:flex;">
  25. <a
  26. v-if="allowLoginType.includes('STUDENT_CODE')"
  27. :class="[
  28. 'qm-big-text',
  29. 'login-type',
  30. loginType === 'STUDENT_CODE' && 'active-type',
  31. allowLoginType.length === 1 && 'single-login-type',
  32. ]"
  33. @click="loginType = 'STUDENT_CODE'"
  34. style="border-top-left-radius: 6px"
  35. >
  36. 学号登录
  37. </a>
  38. <a
  39. v-if="allowLoginType.includes('IDENTITY_NUMBER')"
  40. :class="[
  41. 'qm-big-text',
  42. 'login-type',
  43. loginType !== 'STUDENT_CODE' && 'active-type',
  44. allowLoginType.length === 1 && 'single-login-type',
  45. ]"
  46. @click="loginType = 'STUDENT_IDENTITY_NUMBER'"
  47. style="border-top-right-radius: 6px"
  48. >
  49. 身份证号登录
  50. </a>
  51. <a
  52. v-if="allowLoginType.length === 0"
  53. :class="['qm-big-text', 'login-type']"
  54. >loading...</a
  55. >
  56. </div>
  57. <div class="qm-title-text" style="margin: 40px 0 20px 0">
  58. {{ productName }}
  59. </div>
  60. <div style="margin: 0 40px 40px 40px">
  61. <i-form ref="loginForm" :model="loginForm" :rules="loginFormRule">
  62. <i-form-item
  63. prop="accountValue"
  64. style="margin-bottom:35px;height:42px"
  65. >
  66. <i-input
  67. type="text"
  68. size="large"
  69. v-model="loginForm.accountValue"
  70. :placeholder="usernameInputPlaceholder"
  71. >
  72. <i-icon type="ios-person" slot="prepend"></i-icon>
  73. </i-input>
  74. </i-form-item>
  75. <i-form-item prop="password" style="margin-bottom:35px;height:42px">
  76. <i-input
  77. type="password"
  78. size="large"
  79. v-model="loginForm.password"
  80. :placeholder="passwordInputPlaceholder"
  81. @on-enter="login"
  82. >
  83. <i-icon type="ios-lock" slot="prepend"></i-icon>
  84. </i-input>
  85. </i-form-item>
  86. <i-form-item style="position: relative">
  87. <div
  88. v-if="errorInfo !== ''"
  89. style="position: absolute; top: -37px; width: 100%"
  90. >
  91. <i-alert type="error" show-icon>{{ errorInfo }}</i-alert>
  92. </div>
  93. <i-button
  94. size="large"
  95. class="qm-primary-button"
  96. long
  97. :disabled="disableLoginBtn"
  98. :loading="loginBtnLoading"
  99. @click="login"
  100. >
  101. {{ newVersionAvailable ? "点击更新版本" : "登录" }}
  102. </i-button>
  103. </i-form-item>
  104. </i-form>
  105. </div>
  106. </div>
  107. </div>
  108. <footer class="footer">
  109. <div style="position: absolute; right: 20px; bottom: 20px;">
  110. 版本: {{ VUE_APP_GIT_REPO_VERSION }}
  111. </div>
  112. <DevTools />
  113. </footer>
  114. </div>
  115. </template>
  116. <script>
  117. import moment from "moment";
  118. import { mapMutations } from "vuex";
  119. import { FACE_API_MODEL_PATH } from "@/constants/constants";
  120. import DevTools from "./DevTools.vue";
  121. import nativeExe from "@/utils/nativeExe";
  122. /**
  123. * 在任何组件需要强制退出,做以下步骤
  124. * 1. this.$Message.info()
  125. * 2. this.$router.push("/login"+domain);
  126. * 因为在/login里会删除localStorage的token,而在router.beforeEach会检查是否有token,达到退出的目的。
  127. */
  128. export default {
  129. name: "Login",
  130. data() {
  131. return {
  132. // LOGIN_ID_DOMAINS: ["cugr.ecs.qmth.com.cn", "cugbr.ecs.qmth.com.cn"],
  133. domainInUrl: this.$route.params.domain,
  134. QECSConfig: {},
  135. loginType: "STUDENT_CODE",
  136. errorInfo: "",
  137. loginForm: {
  138. accountValue: "",
  139. password: "",
  140. },
  141. loginFormRule: {
  142. accountValue: [
  143. {
  144. required: true,
  145. message: "请填写登录账号",
  146. trigger: "blur",
  147. },
  148. ],
  149. password: [
  150. {
  151. required: true,
  152. message: "请填写密码",
  153. trigger: "blur",
  154. },
  155. ],
  156. },
  157. loginBtnLoading: false,
  158. isElectron: true,
  159. disableLoginBtnBecauseRemoteApp: true,
  160. disableLoginBtnBecauseVCam: true,
  161. disableLoginBtnBecauseNoRemoteAppChecker: false,
  162. disableLoginBtnBecauseRefreshServiceWorker: false,
  163. newVersionAvailable: false,
  164. VUE_APP_GIT_REPO_VERSION: process.env.VUE_APP_GIT_REPO_VERSION,
  165. };
  166. },
  167. async mounted() {
  168. // this.testServiceWorker();
  169. this.examShellStats();
  170. // await this.checkNewVersion();
  171. if (localStorage.getItem("__swReload")) {
  172. localStorage.removeItem("__swReload");
  173. this.$Message.info({
  174. content: "正在更新版本...",
  175. });
  176. this.disableLoginBtnBecauseRefreshServiceWorker = true;
  177. await new Promise(resolve => {
  178. setTimeout(() => {
  179. resolve();
  180. }, 2000);
  181. });
  182. location.reload(true);
  183. }
  184. // manual precache for models
  185. fetch(
  186. FACE_API_MODEL_PATH + "tiny_face_detector_model-weights_manifest.json"
  187. );
  188. fetch(FACE_API_MODEL_PATH + "face_landmark_68_model-weights_manifest.json");
  189. // alread precached
  190. // fetch("/models/tiny_face_detector_model-shard1");
  191. // fetch("/models/face_landmark_68_model-shard1");
  192. // this.checkNewVersionInterval = setInterval(() => {
  193. // if (window.__newSWAvailable) {
  194. // this.newVersionAvailable = true;
  195. // }
  196. // }, 1000);
  197. this.checkNewVersionListener = document.addEventListener(
  198. "__newSWAvailable",
  199. () => {
  200. this.newVersionAvailable = true;
  201. },
  202. { once: true }
  203. );
  204. if (this.isEPCC) {
  205. const redirectUrl = new URLSearchParams(location.search).get(
  206. "redirectUrl"
  207. );
  208. if (redirectUrl) {
  209. sessionStorage.setItem("redirectUrl", redirectUrl);
  210. }
  211. this.login();
  212. }
  213. },
  214. async created() {
  215. if (this.isEPCC) {
  216. this.$Spin.show({
  217. render: () => {
  218. return <div style="font-size: 44px">正在登录...</div>;
  219. },
  220. });
  221. }
  222. this.isElectron = typeof nodeRequire != "undefined";
  223. if (
  224. navigator.userAgent.indexOf("WOW64") != -1 ||
  225. navigator.userAgent.indexOf("Win64") != -1
  226. ) {
  227. window._hmt.push(["_trackEvent", "登录页面", "64Bit OS"]);
  228. } else {
  229. window._hmt.push(["_trackEvent", "登录页面", "非64Bit OS"]);
  230. }
  231. this.$Message.config({
  232. duration: 15,
  233. size: "large",
  234. closable: true, // 没有影响到所有的组件。。。 https://github.com/iview/iview/issues/2962
  235. });
  236. window.sessionStorage.removeItem("token");
  237. window.sessionStorage.clear();
  238. window.localStorage.removeItem("key");
  239. if (localStorage.getItem("user-for-reload")) {
  240. const lsUser = JSON.parse(localStorage.getItem("user-for-reload"));
  241. this.loginForm.accountValue =
  242. lsUser.studentCode ||
  243. (lsUser.studentCodeList && lsUser.studentCodeList[0]);
  244. this.loginForm.password =
  245. process.env.NODE_ENV === "production" ? "" : "180613";
  246. }
  247. if (
  248. [
  249. "xjtu.ecs.qmth.com.cn",
  250. "snnu.ecs.qmth.com.cn",
  251. "cup.ecs.qmth.com.cn",
  252. "swjtu.ecs.qmth.com.cn",
  253. ].includes(this.$route.params.domain)
  254. ) {
  255. if (
  256. typeof nodeRequire == "undefined" ||
  257. !window.nodeRequire("fs").existsSync("multiCamera.exe")
  258. ) {
  259. this.disableLoginBtnBecauseNoRemoteAppChecker = true;
  260. this.$Message.error({
  261. content: "请与学校申请最新的客户端,进行考试!",
  262. duration: 2 * 24 * 60 * 60,
  263. });
  264. }
  265. }
  266. await this.checkElectronConfig();
  267. await this.checkVCam();
  268. if (
  269. this.allowLoginType.length === 1 &&
  270. this.allowLoginType[0] === "IDENTITY_NUMBER"
  271. ) {
  272. this.loginType = "STUDENT_IDENTITY_NUMBER";
  273. }
  274. },
  275. beforeDestroy() {
  276. clearTimeout(this.loginTimeout);
  277. // clearInterval(this.checkNewVersionInterval);
  278. document.removeEventListener(
  279. "__newSWAvailable",
  280. this.checkNewVersionListener
  281. );
  282. },
  283. methods: {
  284. ...mapMutations(["updateUser", "updateTimeDifference", "updateQECSConfig"]),
  285. testServiceWorker() {
  286. if ("serviceWorker" in navigator) {
  287. // Register service worker
  288. navigator.serviceWorker
  289. .register("/service-worker-test.js")
  290. .then(function(reg) {
  291. console.log("Registration OK!. Scope is " + reg.scope);
  292. })
  293. .catch(function(err) {
  294. console.error("Registration FAILED! " + err);
  295. });
  296. }
  297. },
  298. async login() {
  299. // epcc立即登录
  300. if (!this.isEPCC && (this.disableLoginBtn || this.loginBtnLoading)) {
  301. return;
  302. }
  303. this.loginBtnLoading = true;
  304. try {
  305. const hasNewVersion = await this.checkNewVersion();
  306. if (hasNewVersion) return;
  307. } catch (error) {
  308. console.log("检测新版本出错");
  309. }
  310. this.loginTimeout = setTimeout(() => {
  311. this.loginBtnLoading = false;
  312. }, 10 * 1000);
  313. let loginResponse;
  314. if (!this.isEPCC) {
  315. // https://www.cnblogs.com/weiqinl/p/6708993.html
  316. const valid = await this.$refs.loginForm.validate();
  317. if (!valid) {
  318. return;
  319. }
  320. let repPara = this.loginForm;
  321. // 以下网络请求失败,直接报网络异常错误
  322. loginResponse = await this.$http.post("/api/ecs_core/auth/login", {
  323. ...repPara,
  324. accountType: this.loginType,
  325. domain: this.schoolDomain,
  326. alwaysOK: true,
  327. });
  328. } else {
  329. loginResponse = await this.epccLogin();
  330. if (!loginResponse) {
  331. return;
  332. } else {
  333. loginResponse.data = { content: loginResponse.data, code: "200" }; // 和老接口的always ok保持一致
  334. }
  335. }
  336. let data = loginResponse.data;
  337. // if (
  338. // Math.abs() >
  339. // 5 * 60 * 1000
  340. // ) {
  341. // window._hmt.push(["_trackEvent", "登录页面", "本机时间误差过大"]);
  342. // this.$Message.error({
  343. // content: "与服务器时间差异超过5分钟,请校准本机时间之后再重试!",
  344. // duration: 30
  345. // });
  346. // throw "与服务器时间差异超过5分钟,请校准本机时间之后再重试!";
  347. // }
  348. this.updateTimeDifference(
  349. moment(loginResponse.headers.date).diff(moment())
  350. );
  351. if (data.code == "200") {
  352. data = data.content;
  353. this.errorInfo = "";
  354. //缓存用户信息
  355. window.sessionStorage.setItem("token", data.token);
  356. window.localStorage.setItem("key", data.key);
  357. window.localStorage.setItem("domain", this.schoolDomain);
  358. try {
  359. const student = (await this.$http.get(
  360. "/api/ecs_core/student/getStudentInfoBySession"
  361. )).data;
  362. const specialty = (await this.$http.get(
  363. "/api/ecs_exam_work/exam_student/specialtyNameList/"
  364. )).data;
  365. const user = {
  366. ...data,
  367. ...student,
  368. specialty: specialty.join(),
  369. schoolDomain: this.schoolDomain,
  370. };
  371. this.updateUser(user);
  372. window.localStorage.setItem("user-for-reload", JSON.stringify(user));
  373. window._hmt.push([
  374. "_trackEvent",
  375. "登录页面",
  376. "登录",
  377. this.$route.query.LogoutReason,
  378. ]);
  379. const alreadyInExam = await this.checkExamInProgress();
  380. if (alreadyInExam) {
  381. window._hmt.push([
  382. "_trackEvent",
  383. "登录页面",
  384. "断点续考",
  385. "重新登录",
  386. ]);
  387. return;
  388. }
  389. this.$router.push("/online-exam");
  390. window._hmt.push(["_trackEvent", "登录页面", "登录成功"]);
  391. this.$Spin.hide();
  392. } catch (error) {
  393. window._hmt.push([
  394. "_trackEvent",
  395. "登录页面",
  396. "登录失败",
  397. "getStudentInfoBySession失败",
  398. ]);
  399. this.$Message.error({
  400. content: "获取学生信息失败,请重试!",
  401. duration: 15,
  402. closable: true,
  403. });
  404. if (this.isEPCC) {
  405. this.$Spin.hide();
  406. this.logout();
  407. }
  408. }
  409. } else {
  410. window._hmt.push(["_trackEvent", "登录页面", "登录失败", data.desc]);
  411. this.errorInfo = data.desc;
  412. }
  413. },
  414. async checkNewVersion() {
  415. let myHeaders = new Headers();
  416. myHeaders.append("Content-Type", "application/javascript");
  417. myHeaders.append("Cache-Control", "no-cache");
  418. const response = await fetch(
  419. document.scripts[document.scripts.length - 1].src + "?x" + Date.now(),
  420. {
  421. method: process.env.NODE_ENV === "development" ? "GET" : "HEAD",
  422. headers: myHeaders,
  423. }
  424. );
  425. if (!response.ok || this.newVersionAvailable) {
  426. if (
  427. response.ok &&
  428. this.newVersionAvailable &&
  429. localStorage.getItem("__swReload")
  430. ) {
  431. window._hmt.push([
  432. "_trackEvent",
  433. "登录页面",
  434. "service worker刷新失败",
  435. ]);
  436. this.$Message.destroy();
  437. this.$Message.info({
  438. content: "请重新打开程序。",
  439. duration: 2 * 24 * 60 * 60,
  440. });
  441. return true;
  442. }
  443. window._hmt.push([
  444. "_trackEvent",
  445. "登录页面",
  446. "新版本发布后,客户端自动刷新",
  447. ]);
  448. this.$Message.info({
  449. content: "正在获取新版本...",
  450. });
  451. localStorage.setItem("__swReload", "anything");
  452. this.disableLoginBtnBecauseRefreshServiceWorker = true;
  453. await new Promise(resolve => {
  454. setTimeout(() => {
  455. resolve();
  456. }, 1000);
  457. });
  458. location.reload(true);
  459. return true;
  460. }
  461. },
  462. async checkElectronConfig() {
  463. try {
  464. const fileSever =
  465. location.hostname === "ecs.qmth.com.cn"
  466. ? "https://ecs-static.qmth.com.cn"
  467. : "https://ecs-test-static.qmth.com.cn";
  468. const res = await fetch(
  469. fileSever +
  470. "/org_properties/byOrgDomain/" +
  471. this.domainInUrl +
  472. "/studentClientConfig.json" +
  473. "?" +
  474. Date.now() +
  475. Math.random()
  476. );
  477. if (res.ok) {
  478. const json = await res.json();
  479. this.QECSConfig = json;
  480. this.updateQECSConfig(json);
  481. } else {
  482. this.$Message.error({
  483. content: "获取远程配置文件出错,请重新打开程序!",
  484. duration: 15,
  485. closable: true,
  486. });
  487. return;
  488. }
  489. } catch (e) {
  490. this.$Message.error({
  491. content: "获取远程配置文件出错,请重新打开程序!",
  492. duration: 15,
  493. closable: true,
  494. });
  495. return;
  496. }
  497. await this.checkRemoteApp();
  498. },
  499. async checkRemoteApp() {
  500. if (typeof nodeRequire == "undefined") {
  501. return;
  502. }
  503. function checkRemoteAppTxt() {
  504. let applicationNames;
  505. try {
  506. const fs = window.nodeRequire("fs");
  507. applicationNames = fs.readFileSync("remoteApplication.txt", "utf-8");
  508. } catch (error) {
  509. window._hmt.push([
  510. "_trackEvent",
  511. "登录页面",
  512. "读取remoteApplication.txt出错",
  513. ]);
  514. this.$Message.error({
  515. content: "系统检测出错,请退出程序后重试!",
  516. duration: 2 * 24 * 60 * 60,
  517. });
  518. return;
  519. }
  520. if (applicationNames && applicationNames.trim()) {
  521. this.disableLoginBtnBecauseRemoteApp = true;
  522. this.$Message.info({
  523. content:
  524. "在考试期间,请关掉" +
  525. applicationNames.trim() +
  526. "软件,诚信考试。",
  527. duration: 2 * 24 * 60 * 60,
  528. });
  529. } else {
  530. this.disableLoginBtnBecauseRemoteApp = false;
  531. }
  532. }
  533. //如果配置中配置了 DISABLE_REMOTE_ASSISTANCE
  534. if (
  535. this.QECSConfig.PREVENT_CHEATING_CONFIG.includes(
  536. "DISABLE_REMOTE_ASSISTANCE"
  537. )
  538. ) {
  539. await nativeExe("Project1.exe", checkRemoteAppTxt.bind(this));
  540. } else {
  541. this.disableLoginBtnBecauseRemoteApp = false;
  542. }
  543. },
  544. async checkVCam() {
  545. if (typeof nodeRequire == "undefined") {
  546. return;
  547. }
  548. const vcamList = [
  549. "17GuaGua Cam",
  550. "91KBOX",
  551. "ASUS Virtual Camera",
  552. "e2eSoft iVCam",
  553. "e2eSoft VCam",
  554. "FaceRig Virtual Camera",
  555. "Lenovo Virtual Camera",
  556. "MagicCamera Capture",
  557. "MeiSe",
  558. "Virtual Cam",
  559. "YY伴侣",
  560. "WebcamMax Capture",
  561. ];
  562. function checkVCamTxt() {
  563. let applicationNames;
  564. try {
  565. const fs = window.nodeRequire("fs");
  566. applicationNames = fs.readFileSync("CameraInfo.txt", "utf-8");
  567. } catch (error) {
  568. window._hmt.push([
  569. "_trackEvent",
  570. "登录页面",
  571. "读取CameraInfo.txt出错",
  572. ]);
  573. this.$Message.error({
  574. content: "系统检测出错,请退出程序后重试!",
  575. duration: 2 * 24 * 60 * 60,
  576. });
  577. return;
  578. }
  579. this.disableLoginBtnBecauseVCam = false;
  580. if (applicationNames && applicationNames.trim()) {
  581. for (const vc of vcamList) {
  582. if (applicationNames.includes(vc)) {
  583. this.disableLoginBtnBecauseVCam = true;
  584. this.$Message.info({
  585. content:
  586. "在考试期间,请关掉" + "虚拟摄像头" + "软件,诚信考试。",
  587. duration: 2 * 24 * 60 * 60,
  588. });
  589. console.log(applicationNames);
  590. }
  591. }
  592. }
  593. }
  594. //如果配置中配置了 DISABLE_VIRTUAL_CAMERA
  595. if (
  596. this.QECSConfig.PREVENT_CHEATING_CONFIG.includes(
  597. "DISABLE_VIRTUAL_CAMERA"
  598. )
  599. ) {
  600. await nativeExe("multiCamera.exe", checkVCamTxt.bind(this));
  601. } else {
  602. this.disableLoginBtnBecauseVCam = false;
  603. }
  604. },
  605. closeApp() {
  606. window.close();
  607. },
  608. examShellStats() {
  609. const shellVersion = window.navigator.userAgent
  610. .split(" ")
  611. .find(v => v.startsWith("electron-exam-shell/"));
  612. if (shellVersion) {
  613. window._hmt.push([
  614. "_trackEvent",
  615. "登录页面",
  616. "学生端版本",
  617. shellVersion,
  618. ]);
  619. } else {
  620. const chromeVersion = window.navigator.userAgent
  621. .split(" ")
  622. .find(v => v.startsWith("Chrome/"));
  623. if (chromeVersion) {
  624. window._hmt.push([
  625. "_trackEvent",
  626. "登录页面",
  627. "学生端版本/chrome",
  628. shellVersion,
  629. ]);
  630. } else {
  631. window._hmt.push([
  632. "_trackEvent",
  633. "登录页面",
  634. "学生端版本/其他浏览器",
  635. window.navigator.userAgent,
  636. ]);
  637. }
  638. }
  639. },
  640. async epccLogin() {
  641. const {
  642. accountType,
  643. accountValue,
  644. rootOrgId,
  645. appId,
  646. timestamp,
  647. token,
  648. redirectUrl,
  649. } = this.$route.query;
  650. if (
  651. accountType &&
  652. accountValue &&
  653. rootOrgId &&
  654. appId &&
  655. timestamp &&
  656. token &&
  657. redirectUrl
  658. ) {
  659. try {
  660. const response = await this.$http.post(
  661. "/api/ecs_core/auth/thirdPartyStudentAccess" + location.search
  662. );
  663. return response;
  664. } catch (error) {
  665. window._hmt.push(["_trackEvent", "第三方登录页面", "接口出错", ""]);
  666. this.$Spin.hide();
  667. this.logout();
  668. }
  669. } else {
  670. this.$Spin.hide();
  671. this.logout();
  672. }
  673. },
  674. },
  675. computed: {
  676. logoPath() {
  677. return this.QECSConfig.LOGO_FILE_URL
  678. ? this.QECSConfig.LOGO_FILE_URL + "!/progressive/true/format/webp"
  679. : "";
  680. },
  681. productName() {
  682. return this.QECSConfig.OE_STUDENT_SYS_NAME || "远程教育网络考试";
  683. },
  684. allowLoginType() {
  685. return (
  686. (this.QECSConfig.LOGIN_TYPE && this.QECSConfig.LOGIN_TYPE.split(",")) ||
  687. []
  688. );
  689. },
  690. schoolDomain() {
  691. const domain = this.domainInUrl;
  692. if (!domain || !domain.includes("qmth.com.cn")) {
  693. this.$Message.error({
  694. content: "机构地址出错,请关闭程序后再登录。",
  695. duration: 15,
  696. closable: true,
  697. });
  698. }
  699. return domain;
  700. },
  701. isEPCC() {
  702. return this.schoolDomain === "iepcc-ps.qmth.com.cn";
  703. },
  704. usernameInputPlaceholder() {
  705. if (this.loginType === "STUDENT_CODE") {
  706. return "请输入学号";
  707. } else {
  708. return "请输入身份证号";
  709. }
  710. },
  711. passwordInputPlaceholder() {
  712. if (this.loginType === "STUDENT_CODE") {
  713. return "初始密码为身份证号后6位";
  714. } else {
  715. return "初始密码为身份证号后6位";
  716. }
  717. },
  718. disableLoginBtn() {
  719. // console.log(
  720. // this.isElectron,
  721. // this.disableLoginBtnBecauseRemoteApp,
  722. // this.disableLoginBtnBecauseVCam,
  723. // this.disableLoginBtnBecauseNoRemoteAppChecker
  724. // );
  725. return (
  726. (this.isElectron &&
  727. (this.disableLoginBtnBecauseRemoteApp ||
  728. this.disableLoginBtnBecauseVCam ||
  729. this.disableLoginBtnBecauseNoRemoteAppChecker)) ||
  730. this.disableLoginBtnBecauseRefreshServiceWorker
  731. );
  732. },
  733. },
  734. components: {
  735. DevTools,
  736. },
  737. };
  738. </script>
  739. <style scoped>
  740. .home {
  741. display: flex;
  742. flex-direction: column;
  743. height: 100vh;
  744. }
  745. .school-logo {
  746. justify-self: flex-start;
  747. margin-left: 100px;
  748. }
  749. .logo-size {
  750. height: 100px;
  751. width: 400px;
  752. object-fit: cover;
  753. }
  754. .header {
  755. min-height: 120px;
  756. display: grid;
  757. align-items: center;
  758. justify-items: center;
  759. }
  760. .center {
  761. background-image: url("https://cdn.qmth.com.cn/ui/ecs-client-bg.jpg!/progressive/true");
  762. background-position: center;
  763. background-repeat: no-repeat;
  764. background-size: cover;
  765. width: 100vw;
  766. min-height: 600px;
  767. }
  768. .content {
  769. margin-top: 100px;
  770. margin-left: 65%;
  771. width: 300px;
  772. border-radius: 6px;
  773. background-color: white;
  774. display: grid;
  775. grid-template-areas: "";
  776. }
  777. .login-type {
  778. flex: 1;
  779. line-height: 40px;
  780. background-color: #eeeeee;
  781. }
  782. .active-type {
  783. background-color: #ffffff;
  784. }
  785. .single-login-type {
  786. border-top-left-radius: 6px;
  787. border-top-right-radius: 6px;
  788. }
  789. .close {
  790. position: absolute;
  791. top: 0;
  792. right: 0;
  793. background-color: #eeeeee;
  794. color: #999999;
  795. width: 80px;
  796. height: 40px;
  797. line-height: 40px;
  798. }
  799. .close:hover {
  800. color: #444444;
  801. }
  802. .footer {
  803. position: relative;
  804. }
  805. </style>
  806. <style>
  807. .ivu-message-notice-content-text {
  808. font-size: 32px;
  809. }
  810. .ivu-message-notice-content-text i.ivu-icon {
  811. font-size: 32px;
  812. }
  813. </style>