lex-xin преди 3 години
родител
ревизия
0e17d46f54

+ 3 - 0
.eslintrc.js

@@ -10,6 +10,9 @@ module.exports = {
     '@vue/prettier',
     '@vue/prettier/@typescript-eslint'
   ],
+  rules: {
+    '@typescript-eslint/no-explicit-any': ['off']
+  },
   parserOptions: {
     ecmaVersion: 2020
   }

+ 8 - 0
README.md

@@ -32,6 +32,14 @@ See [Configuration Reference](https://vitejs.dev/config/).
 
 ## Browser adaptation
 
+
+### 目录结构
+```
+/src/student -- 学生端页面目录
+/src/teacher -- 老师端页面目录
+/src/views 两端共用页面,但每个端的路由都需要单独配置
+```
+
 ### Rem Unit (default)
 
 Vant uses `px` unit by default,You can use tools such as `postcss-pxtorem` to transform `px` unit to `rem` unit.

+ 5 - 0
package-lock.json

@@ -4818,6 +4818,11 @@
         "whatwg-fetch": ">=0.10.0"
       }
     },
+    "js-storage": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/js-storage/-/js-storage-1.1.0.tgz",
+      "integrity": "sha512-XwkyTB3cjwBSaaKo+edR/n8ZbmX/mj5lJpW/O753NYvMpClQeurucceIvX3HeF4ZTTY2YRPXTVzgPByK4pA7aQ=="
+    },
     "js-stringify": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/js-stringify/-/js-stringify-1.0.2.tgz",

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
   "dependencies": {
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
+    "js-storage": "^1.1.0",
     "loaders.css": "^0.1.2",
     "mitt": "^3.0.0",
     "normalize.css": "^8.0.1",

BIN
src/common/assets/logo.png


BIN
src/common/images/404.png


BIN
src/common/images/icon_nodata.png


+ 0 - 75
src/components/HelloWorld.vue

@@ -1,75 +0,0 @@
-<template>
-  <h1>{{ msg }}</h1>
-
-  <p>
-    Recommended IDE setup:
-    <a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
-    +
-    <a
-      href="https://marketplace.visualstudio.com/items?itemName=octref.vetur"
-      target="_blank"
-    >
-      Vetur
-    </a>
-    or
-    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
-    (if using
-    <code>&lt;script setup&gt;</code>)
-  </p>
-
-  <p>See <code>README.md</code> for more information.</p>
-
-  <p>
-    <a href="https://vitejs.dev/guide/features.html" target="_blank">
-      Vite Docs
-    </a>
-    |
-    <a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
-  </p>
-
-  <button type="button" @click="count++">count is: {{ count }}</button>
-  <p>
-    Edit
-    <code>components/HelloWorld.vue</code> to test hot module replacement.
-  </p>
-  <van-button type="primary">主要按钮</van-button>
-  <van-button type="success">成功按钮</van-button>
-  <van-button type="default">默认按钮</van-button>
-  <van-button type="warning">警告按钮</van-button>
-  <van-button type="danger">危险按钮</van-button>
-</template>
-
-<script lang="ts">
-import { ref, defineComponent } from 'vue';
-export default defineComponent({
-  name: 'HelloWorld',
-  props: {
-    msg: {
-      type: String,
-      required: true
-    }
-  },
-  setup: () => {
-    const count = ref(0);
-    return { count };
-  }
-});
-</script>
-
-<style scoped>
-a {
-  color: #42b983;
-}
-
-label {
-  margin: 0 0.5em;
-  font-weight: bold;
-}
-
-code {
-  background-color: #eee;
-  padding: 2px 4px;
-  border-radius: 4px;
-  color: #304455;
-}
-</style>

+ 7 - 4
src/helpers/native-message.ts

@@ -1,4 +1,4 @@
-import { browser, getRandomKey } from '/src/helpers/utils';
+import { browser, getRandomKey } from '@/helpers/utils';
 
 export interface IPostMessage {
   api: string;
@@ -26,6 +26,7 @@ window.postMessage = (message: IPostMessage) => {
 
 type CallBack = (evt?: IPostMessage) => void;
 
+// eslint-disable-next-line @typescript-eslint/no-empty-function
 const loop = () => {};
 
 const calls: { [key: string]: CallBack } = {};
@@ -45,7 +46,9 @@ if (browserInfo.isApp) {
         if (data.content) {
           data.content = JSON.parse(data.content);
         }
-      } catch (error) {}
+      } catch (error) {
+        //
+      }
       if (data?.content?.uuid) {
         console.log('data', data);
       }
@@ -59,8 +62,8 @@ if (browserInfo.isApp) {
         }
         return;
       }
-      const callid = data.content?.uuid || data.uuid || data.api + data.uuid;
-      const callback = calls[callid] || loop;
+      const callId = data.content?.uuid || data.uuid || data.api + data.uuid;
+      const callback = calls[callId] || loop;
       callback(data);
     } catch (error) {
       console.error('通信消息解析错误', error);

+ 16 - 18
src/helpers/request.ts

@@ -2,23 +2,15 @@ import { extend } from 'umi-request';
 import { Toast } from 'vant';
 import cleanDeep from 'clean-deep';
 import { browser } from '@/helpers/utils';
+import { setLogout, setLoginError } from '@/student/state'
 import { postMessage } from './native-message';
+import { getAuth } from './storage';
 
 export interface SearchInitParams {
   rows?: string | number;
   page?: string | number;
 }
 
-export interface InitSearchRespones {
-  data: {
-    rows: any[];
-    [key: string]: any
-  };
-  [key: string]: any
-}
-
-const isOpenLogin = false;
-
 const request = extend({
   requestType: 'form',
   timeout: 10000
@@ -35,8 +27,12 @@ const request = extend({
 //   await next();
 // })
 
+// 是否是初始化接口
+let initRequest = false;
+
 request.interceptors.request.use(
   (url, options: any) => {
+    initRequest = options.initRequest || false;
     const Authorization = sessionStorage.getItem('Authorization') || '';
     const authHeaders: any = {};
     if (
@@ -47,7 +43,7 @@ request.interceptors.request.use(
         '/api-auth/code/sendSms'
       ].includes(url)
     ) {
-      authHeaders.Authorization = Authorization
+      authHeaders.Authorization = Authorization;
     }
     return {
       url,
@@ -68,22 +64,24 @@ request.interceptors.response.use(
   async res => {
     if (res.status > 299 || res.status < 200) {
       const msg = '服务器错误,状态码' + res.status;
-      Toast(msg)
+      Toast(msg);
       throw new Error(msg);
     }
     const data = await res.clone().json();
     if (data.code !== 200 && data.errCode !== 0) {
       const msg = data.msg || '处理失败,请重试';
-      // const state: any = store.getState()
-      // if (data.code === 401 && state.user.status === 'login' && url.pathname !== '/api-auth/exit') {
-      //   const { dispatch }: any = store
-      //   dispatch(setLogout())
-      // }
+      if (initRequest) {
+        if (data.code === 403 || data.code === 401) {
+          setLogout();
+        } else {
+          setLoginError();
+        }
+      }
       if (!(data.code === 403 || data.code === 401)) {
         Toast(msg);
       }
       const browserInfo = browser();
-      if (data.code === 403 && browserInfo.isApp && !isOpenLogin) {
+      if (data.code === 403) {
         if (browserInfo.isApp) {
           postMessage({
             api: 'login'

+ 32 - 0
src/helpers/rules.ts

@@ -0,0 +1,32 @@
+import { Toast } from 'vant';
+
+export const phoneRule = /^1(3|4|5|6|7|8|9)\d{9}$/;
+
+export const verifyPhone = (data: any) => {
+  let options = {
+    value: '',
+    hint: true,
+    message: ''
+  };
+  if (typeof data === 'string') {
+    options.value = data;
+  } else if (typeof data === 'object') {
+    options = { ...options, ...data };
+    return false;
+  }
+  const { value = '', hint, message } = options;
+  let result = true;
+  let msg = '';
+  if (!value) {
+    result = false;
+    msg = message || '请输入手机号';
+  }
+  if (value.length !== 11) {
+    result = false;
+    msg = message || '请输入正确的手机号';
+  }
+  if (hint !== false && msg) {
+    Toast(msg);
+  }
+  return result;
+};

+ 26 - 1
src/helpers/utils.ts

@@ -1,3 +1,5 @@
+import { sessionStorage as storage } from 'js-storage';
+
 export const browser = () => {
   const u = navigator.userAgent;
   //   app = navigator.appVersion;
@@ -26,4 +28,27 @@ export const browser = () => {
 export const getRandomKey = () => {
   const key = '' + new Date().getTime() + Math.floor(Math.random() * 1000000);
   return key;
-}
+};
+
+/**
+ * 删除token
+ */
+export const removeAuth = () => {
+  storage.remove('Authorization');
+};
+
+/**
+ * 设置token
+ * @param token
+ * @returns {void}
+ */
+export const setAuth = (token: any) => {
+  storage.set('Authorization', token);
+};
+
+/**
+ * 获取token
+ */
+export const getAuth = () => {
+  storage.get('Authorization');
+};

+ 1 - 2
src/router/index-student.ts

@@ -1,7 +1,6 @@
 import { createRouter, createWebHashHistory, Router } from 'vue-router';
 import routes from './routes-student';
-
-
+console.log(routes);
 const router: Router = createRouter({
   history: createWebHashHistory(),
   routes

+ 26 - 4
src/router/routes-student.ts

@@ -1,13 +1,35 @@
+import Auth from '@/student/layout/auth';
+
+type metaType = {
+  isRegister: boolean;
+};
+
 export default [
   {
     path: '/',
-    component: () => import('@/student/home/index')
+    component: Auth,
+    children: [
+      {
+        path: '/login',
+        name: 'login',
+        component: () => import('@/student/layout/login'),
+        meta: {
+          isRegister: false
+        } as metaType
+      },
+      {
+        path: '/home',
+        name: 'home',
+        component: () => import('@/student/home/index')
+      }
+    ]
   },
   {
-    path: '/home',
-    component: () => import('@/student/home/index'),
+    path: '/:pathMatch(.*)*',
+    component: () => import('@/views/404'),
     meta: {
-      isLogin: false // 是否需要登录
+      title: '404 Not Fund',
+      platform: 'STUDENT'
     }
   }
 ];

+ 35 - 0
src/router/routes-teacher.ts

@@ -0,0 +1,35 @@
+import Auth from '@/teacher/layout/auth';
+
+type metaType = {
+  isRegister: boolean;
+};
+
+export default [
+  {
+    path: '/',
+    component: Auth,
+    children: [
+      {
+        path: '/login',
+        name: 'login',
+        component: () => import('@/teacher/layout/login'),
+        meta: {
+          isRegister: false
+        } as metaType
+      },
+      {
+        path: '/home',
+        name: 'home',
+        component: () => import('@/teacher/home/index')
+      }
+    ]
+  },
+  {
+    path: '/:pathMatch(.*)*',
+    component: () => import('@/views/404'),
+    meta: {
+      title: '404 Not Fund',
+      platform: 'TEACHER'
+    }
+  }
+];

+ 2 - 0
src/shims-vue.d.ts

@@ -6,6 +6,8 @@ declare module '*.vue' {
   export default component;
 }
 
+declare module 'js-storage';
+
 declare module '@vue/runtime-core' {
   export interface ComponentCustomProperties {
     $dayjs: dayjs.Dayjs

+ 5 - 0
src/student/layout/api.ts

@@ -0,0 +1,5 @@
+import request from '@/helpers/request';
+
+export const getUserInfo = () => {
+  return request.post('/api-auth/usernameLogin');
+}

+ 21 - 0
src/student/layout/auth.module.less

@@ -0,0 +1,21 @@
+.error {
+  background-color: #fff;
+  display: flex;
+  padding-top: 20px;
+  flex-direction: column;
+  min-height: 100vh;
+  align-items: center;
+  justify-content: center;
+  .info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 30px;
+
+    span {
+      display: inline-block;
+      margin-left: 10px;
+      color: #58727e;
+      font-size: 18px;
+    }
+  }
+}

+ 84 - 0
src/student/layout/auth.tsx

@@ -0,0 +1,84 @@
+import { defineComponent } from "vue";
+import styles from './auth.module.less';
+import { state, setLogin } from '../state';
+import { browser, setAuth } from "@/helpers/utils";
+import { postMessage } from "@/helpers/native-message";
+import { RouterView } from "vue-router";
+import { Button, Icon } from "vant";
+import request from "@/helpers/request";
+
+export default defineComponent({
+  name: "Auth",
+  data() {
+    return {
+      loading: false as boolean,
+    }
+  },
+  computed: {
+    isNeedView() {
+      return state.user.status === 'login' || this.$route.path === '/login';
+    }
+  },
+  mounted() {
+    console.log(1111)
+    this.setAuth();
+  },
+  methods: {
+    async setAuth() {
+      const { query } = this.$route
+      const token = query.userInfo || query.Authorization
+      if (token) {
+        setAuth(token)
+      }
+      if (this.loading) {
+        return
+      }
+      if ((state.user.status === 'init' || state.user.status === 'error')) {
+        this.loading = true
+        try {
+          let res = await request.get('/api-student/userCashAccount/get', {
+            initRequest: true // 初始化接口
+          })
+          // console.log(res)
+          setLogin(res.data)
+        } catch (e: any) {
+          // console.log(e)
+        }
+        this.loading = false
+      }
+      if (state.user.status === 'logout') {
+        if (browser().isApp) {
+          postMessage({ api: 'login' })
+        } else {
+          try {
+            let route = this.$route
+            let query = {
+              returnUrl: this.$route.path,
+              ...this.$route.query,
+            } as any;
+            if (route.meta.isRegister) {
+              query.isRegister = route.meta.isRegister
+            }
+            this.$router.replace({
+              path: '/login',
+              query: query
+            })
+          } catch (error) { }
+        }
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        {state.user.status === 'error' ? <div class={styles.error}>
+          <div class={styles.info}>
+            <Icon name="clear" size="36" color="#ee0a24" />
+            <span>加载失败,请重新尝试</span>
+          </div>
+          <Button type="primary" round onClick={this.setAuth}>重新加载</Button>
+        </div> : this.isNeedView ? <RouterView></RouterView> : null}
+      </>
+    )
+  }
+})

+ 0 - 0
src/student/layout/login.module.less


+ 145 - 0
src/student/layout/login.tsx

@@ -0,0 +1,145 @@
+import { defineComponent } from "vue";
+import { CellGroup, Field, Button, CountDown } from "vant";
+import request from "@/helpers/request";
+import { setLogin, state } from "@/student/state";
+import { removeAuth, setAuth } from "@/helpers/utils";
+
+type loginType = 'PWD' | 'SMS';
+export default defineComponent({
+  name: 'login',
+  data() {
+    return {
+      loginType: 'PWD' as loginType,
+      username: '',
+      password: '',
+      smsCode: '',
+      countDownStatus: true, // 是否发送验证码
+      countDownTime: 1000 * 120, // 倒计时时间
+      countDownRef: null as any, // 倒计时实例
+    }
+  },
+  computed: {
+    codeDisable() {
+      let status = true;
+      if (this.loginType === 'PWD') {
+        this.username && this.password && (status = false);
+      } else {
+        this.username && this.smsCode && (status = false);
+      }
+      console.log(status, this.loginType)
+      return status;
+    },
+  },
+  mounted() {
+    removeAuth();
+    this.directNext();
+  },
+  methods: {
+    directNext() {
+      if (state.user.status === "login" || state.user.status === "error") {
+        const { returnUrl, isRegister, ...rest } = this.$route.query;
+        this.$router.replace({
+          path: returnUrl as any,
+          query: {
+            ...rest,
+          },
+        });
+      }
+    },
+    async onLogin() {
+      try {
+        let res: any;
+        if(this.loginType === 'PWD') {
+          res = await request.post('/api-auth/usernameLogin', {
+            data: {
+              username: this.username,
+              password: this.password,
+              clientId: 'student',
+              clientSecret: 'student'
+            }
+          });
+        } else {
+          res = await request.post('/api-auth/smsLogin', {
+            data: {
+              clientId: 'student',
+              clientSecret: 'student',
+              phone: this.username,
+              smsCode: this.smsCode,
+              channel: 'H5'
+            }
+          });
+        }
+
+        const { authentication } = res.data;
+        setAuth(authentication.token_type + " " + authentication.access_token);
+
+        let userCash = await request.get('/api-student/userCashAccount/get', {
+          initRequest: true // 初始化接口
+        })
+        setLogin(userCash.data)
+
+        this.directNext();
+      } catch{
+
+      }
+    },
+    onCodeSend() {
+      this.countDownStatus = false;
+      this.countDownRef.start();
+    },
+    onFinished() {
+      this.countDownStatus = true;
+      this.countDownRef.reset();
+    },
+    onChange() {
+      if(this.loginType === 'PWD') {
+        this.loginType = 'SMS'
+      } else if(this.loginType === 'SMS') {
+        this.loginType = 'PWD'
+      }
+    }
+  },
+  render() {
+    return (
+      <div class="login">
+        <CellGroup inset>
+          <Field
+            v-model={this.username}
+            name="手机号"
+            label="手机号"
+            placeholder="手机号"
+            type="tel"
+            maxlength={11}
+          />
+          { this.loginType === 'PWD' ? <Field
+            v-model={this.password}
+            type="password"
+            name="密码"
+            label="密码"
+            placeholder="密码"
+          /> : <Field
+            v-model={this.smsCode}
+            name="验证码"
+            label="验证码"
+            placeholder="验证码"
+            type="tel"
+            maxlength={6}
+            // @ts-ignore
+            vSlots={{
+              button: () => (
+                this.countDownStatus ? <Button size="small" round type="primary" onClick={this.onCodeSend}>发送验证码</Button> :  <CountDown ref={this.countDownRef} auto-start="false" time={this.countDownTime} onFinish={this.onFinished} format="ss秒" />
+              )
+            }}
+          /> }
+
+        </CellGroup>
+        <div style="margin: 16px;">
+          <Button round block type="primary" disabled={this.codeDisable} onClick={this.onLogin}>
+            提交
+          </Button>
+          <Button plain onClick={this.onChange}>{ this.loginType === 'PWD' ? '验证码登录' : '密码登录' }</Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 30 - 0
src/student/state.ts

@@ -0,0 +1,30 @@
+import { reactive } from 'vue';
+
+type status = 'init' | 'login' | 'logout' | 'error';
+
+export const state = reactive({
+  user: {
+    status: 'init' as status,
+    data: null as null | any
+  }
+});
+
+export const setLoginInit = () => {
+  state.user.status = 'init';
+  state.user.data = null;
+};
+
+export const setLogin = (data: any) => {
+  state.user.status = 'login';
+  state.user.data = data;
+};
+
+export const setLogout = () => {
+  state.user.status = 'logout';
+  state.user.data = null;
+};
+
+export const setLoginError = () => {
+  state.user.status = 'error';
+  state.user.data = null;
+};

+ 82 - 1
src/styles/index.less

@@ -1,8 +1,89 @@
+:root {
+  // Color Palette
+  // --van-black: #000;
+  // --van-white: #fff;
+  // --van-gray-1: #f7f8fa;
+  // --van-gray-2: #f2f3f5;
+  // --van-gray-3: #ebedf0;
+  // --van-gray-4: #dcdee0;
+  // --van-gray-5: #c8c9cc;
+  // --van-gray-6: #969799;
+  // --van-gray-7: #646566;
+  // --van-gray-8: #323233;
+  // --van-red: #ee0a24;
+  // --van-blue: #1989fa;
+  --van-primary: #2DC7AA !important;
+  // --van-orange: #ff976a;
+  // --van-orange-dark: #ed6a0c;
+  // --van-orange-light: #fffbe8;
+  // --van-green: #07c160;
+
+  // // Gradient Colors
+  // --van-gradient-red: linear-gradient(to right, #ff6034, #ee0a24);
+  // --van-gradient-orange: linear-gradient(to right, #ffd01e, #ff8917);
+
+  // // Component Colors
+  --van-primary-color: var(--van-primary) !important;
+  // --van-success-color: var(--van-green);
+  // --van-danger-color: var(--van-red);
+  // --van-warning-color: var(--van-orange);
+  // --van-text-color: var(--van-gray-8);
+  // --van-text-color-2: var(--van-gray-6);
+  // --van-text-color-3: var(--van-gray-5);
+  // --van-text-link-color: #576b95;
+  // --van-active-color: var(--van-gray-2);
+  // --van-active-opacity: 0.6;
+  // --van-disabled-opacity: 0.5;
+  // --van-background-color: var(--van-gray-1);
+  // --van-background-color-light: var(--van-white);
+
+  // // Padding
+  // --van-padding-base: 4px;
+  // --van-padding-xs: 8px;
+  // --van-padding-sm: 12px;
+  // --van-padding-md: 16px;
+  // --van-padding-lg: 24px;
+  // --van-padding-xl: 32px;
+
+  // // Font
+  // --van-font-size-xs: 10px;
+  // --van-font-size-sm: 12px;
+  // --van-font-size-md: 14px;
+  // --van-font-size-lg: 16px;
+  // --van-font-weight-bold: 500;
+  // --van-line-height-xs: 14px;
+  // --van-line-height-sm: 18px;
+  // --van-line-height-md: 20px;
+  // --van-line-height-lg: 22px;
+  // --van-base-font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
+  //   Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB',
+  //   'Microsoft Yahei', sans-serif;
+  // --van-price-integer-font-family: Avenir-Heavy, PingFang SC, Helvetica Neue,
+  //   Arial, sans-serif;
+
+  // // Animation
+  // --van-animation-duration-base: 0.3s;
+  // --van-animation-duration-fast: 0.2s;
+  // --van-animation-timing-function-enter: ease-out;
+  // --van-animation-timing-function-leave: ease-in;
+
+  // // Border
+  // --van-border-color: var(--van-gray-3);
+  // --van-border-width-base: 1px;
+  // --van-border-radius-sm: 2px;
+  // --van-border-radius-md: 4px;
+  // --van-border-radius-lg: 8px;
+  // --van-border-radius-max: 999px;
+}
+
 #app {
   font-family: Avenir, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   text-align: center;
   color: #2c3e50;
-  margin-top: 60px;
 }
+
+body {
+  background-color: #f7f7f7;
+}

+ 4 - 0
src/teacher/home/index.module.less

@@ -0,0 +1,4 @@
+.title {
+  font-size: 20px;
+  color: red;
+}

+ 15 - 0
src/teacher/home/index.tsx

@@ -0,0 +1,15 @@
+import { Button } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'home',
+  render() {
+    return (
+      <>
+        <div class={styles.title}>标题</div>
+        <Button>首页</Button>
+      </>
+    )
+  }
+})

+ 5 - 0
src/teacher/layout/api.ts

@@ -0,0 +1,5 @@
+import request from '@/helpers/request';
+
+export const getUserInfo = () => {
+  return request.post('/api-auth/usernameLogin');
+}

+ 21 - 0
src/teacher/layout/auth.module.less

@@ -0,0 +1,21 @@
+.error {
+  background-color: #fff;
+  display: flex;
+  padding-top: 20px;
+  flex-direction: column;
+  min-height: 100vh;
+  align-items: center;
+  justify-content: center;
+  .info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 30px;
+
+    span {
+      display: inline-block;
+      margin-left: 10px;
+      color: #58727e;
+      font-size: 18px;
+    }
+  }
+}

+ 84 - 0
src/teacher/layout/auth.tsx

@@ -0,0 +1,84 @@
+import { defineComponent } from "vue";
+import styles from './auth.module.less';
+import { state, setLogin } from '../../student/state';
+import { browser, setAuth } from "@/helpers/utils";
+import { postMessage } from "@/helpers/native-message";
+import { RouterView } from "vue-router";
+import { Button, Icon } from "vant";
+import request from "@/helpers/request";
+
+export default defineComponent({
+  name: "Auth",
+  data() {
+    return {
+      loading: false as boolean,
+    }
+  },
+  computed: {
+    isNeedView() {
+      return state.user.status === 'login' || this.$route.path === '/login';
+    }
+  },
+  mounted() {
+    this.setAuth();
+  },
+  methods: {
+    async setAuth() {
+      const { query } = this.$route
+      const token = query.userInfo || query.Authorization
+      if (token) {
+        setAuth(token)
+      }
+      if (this.loading) {
+        return
+      }
+      console.log(state)
+      if ((state.user.status === 'init' || state.user.status === 'error')) {
+        this.loading = true
+        try {
+          let res = await request.post('/api-student/userCashAccount/get', {
+            initRequest: true // 初始化接口
+          })
+          // console.log(res)
+          setLogin(res.data)
+        } catch (e: any) {
+          // console.log(e)
+        }
+        this.loading = false
+      }
+      if (state.user.status === 'logout') {
+        if (browser().isApp) {
+          postMessage({ api: 'login' })
+        } else {
+          try {
+            let route = this.$route
+            let query = {
+              returnUrl: this.$route.path,
+              ...this.$route.query,
+            } as any;
+            if (route.meta.isRegister) {
+              query.isRegister = route.meta.isRegister
+            }
+            this.$router.replace({
+              path: '/login',
+              query: query
+            })
+          } catch (error) { }
+        }
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        {state.user.status === 'error' ? <div class={styles.error}>
+          <div class={styles.info}>
+            <Icon name="clear" size="36" color="#ee0a24" />
+            <span>加载失败,请重新尝试</span>
+          </div>
+          <Button type="primary" round onClick={this.setAuth}>重新加载</Button>
+        </div> : this.isNeedView ? <RouterView></RouterView> : null}
+      </>
+    )
+  }
+})

+ 0 - 0
src/teacher/layout/login.module.less


+ 145 - 0
src/teacher/layout/login.tsx

@@ -0,0 +1,145 @@
+import { defineComponent } from "vue";
+import { CellGroup, Field, Button, CountDown } from "vant";
+import request from "@/helpers/request";
+import { setLogin, state } from "@/student/state";
+import { removeAuth, setAuth } from "@/helpers/utils";
+
+type loginType = 'PWD' | 'SMS';
+export default defineComponent({
+  name: 'login',
+  data() {
+    return {
+      loginType: 'PWD' as loginType,
+      username: '',
+      password: '',
+      smsCode: '',
+      countDownStatus: true, // 是否发送验证码
+      countDownTime: 1000 * 120, // 倒计时时间
+      countDownRef: null as any, // 倒计时实例
+    }
+  },
+  computed: {
+    codeDisable() {
+      let status = true;
+      if (this.loginType === 'PWD') {
+        this.username && this.password && (status = false);
+      } else {
+        this.username && this.smsCode && (status = false);
+      }
+      console.log(status, this.loginType)
+      return status;
+    },
+  },
+  mounted() {
+    removeAuth();
+    this.directNext();
+  },
+  methods: {
+    directNext() {
+      if (state.user.status === "login" || state.user.status === "error") {
+        const { returnUrl, isRegister, ...rest } = this.$route.query;
+        this.$router.replace({
+          path: returnUrl as any,
+          query: {
+            ...rest,
+          },
+        });
+      }
+    },
+    async onLogin() {
+      try {
+        let res: any;
+        if(this.loginType === 'PWD') {
+          res = await request.post('/api-auth/usernameLogin', {
+            data: {
+              username: this.username,
+              password: this.password,
+              clientId: 'student',
+              clientSecret: 'student'
+            }
+          });
+        } else {
+          res = await request.post('/api-auth/smsLogin', {
+            data: {
+              clientId: 'student',
+              clientSecret: 'student',
+              phone: this.username,
+              smsCode: this.smsCode,
+              channel: 'H5'
+            }
+          });
+        }
+
+        const { authentication } = res.data;
+        setAuth(authentication.token_type + " " + authentication.access_token);
+
+        let userCash = await request.get('/api-student/userCashAccount/get', {
+          initRequest: true // 初始化接口
+        })
+        setLogin(userCash.data)
+
+        this.directNext();
+      } catch{
+
+      }
+    },
+    onCodeSend() {
+      this.countDownStatus = false;
+      this.countDownRef.start();
+    },
+    onFinished() {
+      this.countDownStatus = true;
+      this.countDownRef.reset();
+    },
+    onChange() {
+      if(this.loginType === 'PWD') {
+        this.loginType = 'SMS'
+      } else if(this.loginType === 'SMS') {
+        this.loginType = 'PWD'
+      }
+    }
+  },
+  render() {
+    return (
+      <div class="login">
+        <CellGroup inset>
+          <Field
+            v-model={this.username}
+            name="手机号"
+            label="手机号"
+            placeholder="手机号"
+            type="tel"
+            maxlength={11}
+          />
+          { this.loginType === 'PWD' ? <Field
+            v-model={this.password}
+            type="password"
+            name="密码"
+            label="密码"
+            placeholder="密码"
+          /> : <Field
+            v-model={this.smsCode}
+            name="验证码"
+            label="验证码"
+            placeholder="验证码"
+            type="tel"
+            maxlength={6}
+            // @ts-ignore
+            vSlots={{
+              button: () => (
+                this.countDownStatus ? <Button size="small" round type="primary" onClick={this.onCodeSend}>发送验证码</Button> :  <CountDown ref={this.countDownRef} auto-start="false" time={this.countDownTime} onFinish={this.onFinished} format="ss秒" />
+              )
+            }}
+          /> }
+
+        </CellGroup>
+        <div style="margin: 16px;">
+          <Button round block type="primary" disabled={this.codeDisable} onClick={this.onLogin}>
+            提交
+          </Button>
+          <Button plain onClick={this.onChange}>{ this.loginType === 'PWD' ? '验证码登录' : '密码登录' }</Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 15 - 0
src/views/404/index.module.less

@@ -0,0 +1,15 @@
+.f404 {
+  min-height: 100vh;
+  :global {
+    .van-image {
+      margin-top: 100px;
+      width: 70%;
+    }
+    .van-button {
+      background-color: transparent;
+      height: 30px;
+      line-height: 28px;
+      padding: 0 25px;
+    }
+  }
+}

+ 17 - 0
src/views/404/index.tsx

@@ -0,0 +1,17 @@
+import { defineComponent } from "vue";
+import styles from './index.module.less';
+import img404 from '@/common/images/404.png';
+import { Button, Image } from "vant";
+
+export default defineComponent({
+  name: "NotFound",
+  render() {
+    return (
+      <div class={styles.f404}>
+        <Image src={img404} />
+        <p>页面找不到了</p>
+        <Button type="primary" plain round onClick={() => this.$router.back()}>返回</Button>
+      </div>
+    )
+  }
+})

+ 16 - 1
vite.config.ts

@@ -11,6 +11,7 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
+const proxyUrl = 'https://mstutest.dayaedu.com/';
 export default defineConfig({
   plugins: [
     vue(),
@@ -41,7 +42,21 @@ export default defineConfig({
   server: {
     host: '0.0.0.0',
     port: 5000,
-    strictPort: true
+    strictPort: true,
+    proxy: {
+      '/api-auth': {
+        target: proxyUrl,
+        changeOrigin: true
+      },
+      '/api-student': {
+        target: proxyUrl,
+        changeOrigin: true
+      },
+      '/api-teacher': {
+        target: proxyUrl,
+        changeOrigin: true
+      }
+    }
   },
   build: {
     rollupOptions: {