Browse Source

Merge branch 'master' of http://git.dayaedu.com/lex/h5-colexiu

wolyshaw 3 years ago
parent
commit
70b77f6c43
51 changed files with 908 additions and 129 deletions
  1. 3 3
      package-lock.json
  2. BIN
      public/favicon.ico
  3. BIN
      src/common/images/icon_checkbox.png
  4. BIN
      src/common/images/icon_checkbox_default.png
  5. 13 0
      src/components/ColHeader/index.module.less
  6. 97 0
      src/components/ColHeader/index.tsx
  7. 26 0
      src/components/ColProtocol/index.module.less
  8. 84 0
      src/components/ColProtocol/index.tsx
  9. 1 1
      src/components/imgCode/index.module.less
  10. 5 3
      src/components/imgCode/index.tsx
  11. 5 0
      src/helpers/helpState.ts
  12. 8 5
      src/helpers/request.ts
  13. 31 0
      src/helpers/utils.ts
  14. 43 0
      src/helpers/validate.ts
  15. 5 0
      src/router/index-student.ts
  16. 7 2
      src/router/index-teacher.ts
  17. 8 0
      src/router/routes-student.ts
  18. 8 0
      src/router/routes-teacher.ts
  19. 0 0
      src/state.ts
  20. 0 5
      src/student/layout/api.ts
  21. 1 1
      src/student/layout/auth.tsx
  22. 28 0
      src/student/layout/login.module.less
  23. 56 52
      src/student/layout/login.tsx
  24. 0 0
      src/student/practiceClass/index.module.less
  25. 19 0
      src/student/practiceClass/index.tsx
  26. 8 1
      src/styles/index.less
  27. 1 7
      src/teacher/App.vue
  28. 0 5
      src/teacher/layout/api.ts
  29. 2 3
      src/teacher/layout/auth.tsx
  30. BIN
      src/teacher/layout/images/bottom_bg.png
  31. BIN
      src/teacher/layout/images/top_bg.png
  32. 43 0
      src/teacher/layout/login.module.less
  33. 66 41
      src/teacher/layout/login.tsx
  34. 12 0
      src/teacher/main.ts
  35. 58 0
      src/teacher/teacherCert/certOne.module.less
  36. 98 0
      src/teacher/teacherCert/certOne.tsx
  37. BIN
      src/teacher/teacherCert/images/base_active.png
  38. BIN
      src/teacher/teacherCert/images/base_default.png
  39. BIN
      src/teacher/teacherCert/images/base_finish.png
  40. BIN
      src/teacher/teacherCert/images/education_active.png
  41. BIN
      src/teacher/teacherCert/images/education_default.png
  42. BIN
      src/teacher/teacherCert/images/education_finish.png
  43. BIN
      src/teacher/teacherCert/images/icon_horn.png
  44. BIN
      src/teacher/teacherCert/images/name_active.png
  45. BIN
      src/teacher/teacherCert/images/name_default.png
  46. BIN
      src/teacher/teacherCert/images/name_finish.png
  47. 5 0
      src/teacher/teacherCert/index.module.less
  48. 38 0
      src/teacher/teacherCert/index.tsx
  49. 44 0
      src/teacher/teacherCert/steps.module.less
  50. 74 0
      src/teacher/teacherCert/steps.tsx
  51. 11 0
      src/teacher/teacherCert/teacherState.ts

+ 3 - 3
package-lock.json

@@ -2240,9 +2240,9 @@
       }
     },
     "@vant/use": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.3.4.tgz",
-      "integrity": "sha512-XvZkPCjcmEBhD+T3vB68thOG6P9jazld6aBTMenhbAQd4FT/x9AiKIWPJx4MvhYoSIWt7fju6K01XTJldWs1hw=="
+      "version": "1.3.6",
+      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.3.6.tgz",
+      "integrity": "sha512-3z+nywPaV2F5BdJO7RQxWlgfzJeEOmViD2yHMb7Tg+R4NR/7iQskqW8v2Cnv9FWSJgTOSHlcr7UzeLpiTAP4HA=="
     },
     "@vitejs/plugin-legacy": {
       "version": "1.7.1",

BIN
public/favicon.ico


BIN
src/common/images/icon_checkbox.png


BIN
src/common/images/icon_checkbox_default.png


+ 13 - 0
src/components/ColHeader/index.module.less

@@ -0,0 +1,13 @@
+.colHeader {
+  &.green {
+    background-color: var(--van-primary);
+    :global {
+      .van-nav-bar__title, .van-icon {
+        color: #fff;
+      }
+      .van-nav-bar__content {
+        height: inherit;
+      }
+    }
+  }
+}

+ 97 - 0
src/components/ColHeader/index.tsx

@@ -0,0 +1,97 @@
+import { postMessage } from "@/helpers/native-message";
+import { NavBar } from "vant";
+import { defineComponent } from "vue";
+import styles from './index.module.less'
+
+type backIconColor = 'black' | 'white';
+type backgroundType = 'white' | 'green';
+
+export default defineComponent({
+  name: "col-header",
+  props: {
+    title: String,
+    isBack: {
+      type: Boolean,
+      default: false
+    },
+    isFixed: {
+      type: Boolean,
+      default: true
+    },
+    styleName: {
+      type: Object,
+      default: () => ({})
+    },
+    titleClass: String,
+    background: {
+      type: String,
+      default: 'white' as backgroundType
+    },
+    rightText: String,
+    onClickRight: {
+      type: Function,
+      default: () => { }
+    }
+  },
+  data() {
+    return {
+      headerTitle: null as any,
+      navBarHeight: 0, // 顶部导航栏高度
+      titleHeight: 44, // 顶部导航高度(默认44px)
+    }
+  },
+  mounted() {
+    this.headerTitle = this.title || this.$route.meta.title;
+    this.navBarInit();
+  },
+  methods: {
+    navBarInit() {
+      // 设置是否显示导航栏 0 显示 1 不显示
+      postMessage({ api: 'setBarStatus', content: { status: 0 } })
+      // 设置返回按钮颜色
+      postMessage({ api: 'backIconChange', content: { iconStyle: 'black' as backIconColor } })
+
+      let sNavHeight = sessionStorage.getItem('navHeight')
+      let sTitleHeight = sessionStorage.getItem('titleHeight')
+      if (sNavHeight && sTitleHeight) {
+        this.navBarHeight = Number(sNavHeight)
+      } else {
+        postMessage({ api: 'getNavHeight' }, (res) => {
+          const { content } = res as any
+          const dpi = content.dpi || 2
+          if (content.navHeight) {
+            const navHeight = content.navHeight / dpi
+            sessionStorage.setItem('navHeight', String(navHeight))
+            this.navBarHeight = navHeight
+          }
+          if (content.titleHeight) { // 导航栏的高度
+            const titleHeight = content.titleHeight / dpi
+            sessionStorage.setItem('titleHeight', String(titleHeight))
+            this.titleHeight = titleHeight
+          }
+        })
+      }
+    },
+    onClickLeft() {
+      this.$router.back();
+    },
+    clickRight() {
+      this.onClickRight && this.onClickRight();
+    }
+  },
+  render() {
+    return (
+      <>
+        <NavBar title={this.headerTitle}
+          style={{ paddingTop: `${this.navBarHeight}px`, height: this.titleHeight + 'px', lineHeight: this.titleHeight + 'px' }}
+          class={[this.background === 'green' ? styles.green : null, styles.colHeader]}
+          left-arrow={this.isBack}
+          rightText={this.rightText}
+          fixed={this.isFixed}
+          onClick-right={this.clickRight}
+          onClick-left={this.onClickLeft}></NavBar>
+          { this.$slots.default ? this.$slots.default() : null }
+      </>
+    )
+  }
+})

+ 26 - 0
src/components/ColProtocol/index.module.less

@@ -0,0 +1,26 @@
+.colProtocol {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+  padding: 15px 14px;
+  color: #999;
+  .protocolText {
+    color: var(--van-primary);
+  }
+
+  .boxStyle {
+    background: transparent;
+    width: 15px;
+    height: 15px;
+    border: transparent;
+  }
+  :global {
+    .van-checkbox__icon {
+      height: 20px;
+      line-height: 15px;
+      display: flex;
+      align-items: center;
+    }
+  }
+}
+

+ 84 - 0
src/components/ColProtocol/index.tsx

@@ -0,0 +1,84 @@
+import { Checkbox, Icon, Popup } from "vant";
+import { defineComponent } from "vue";
+import styles from './index.module.less';
+import activeButtonIcon from '@common/images/icon_checkbox.png';
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png';
+
+export default defineComponent({
+  name: 'protocol',
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      checked: false,
+      popupStatus: false,
+      protocolHTML: '',
+      protocolPopup: null as any,
+    }
+  },
+  mounted() {
+    window.addEventListener('hashchange', this.onHash, false)
+  },
+  unmounted() {
+    window.removeEventListener('hashchange', this.onHash, false)
+  },
+  methods: {
+    onHash() {
+      this.popupStatus = false;
+    },
+    onPopupClose() {
+      // 判断是否有协议内容
+      // if (!this.protocolHTML) {
+      //   return
+      // }
+      this.popupStatus = !this.popupStatus
+
+      // 打开弹窗
+      if (this.popupStatus) {
+        const route = this.$route
+        let times = 0;
+        for (let i in route.query) {
+          times += 1
+        }
+        const origin = window.location.href
+        const url = times > 0 ? '&pto=' + (+new Date()) : '?pto=' + (+new Date())
+        history.pushState("", "", `${origin}${url}`)
+      } else {
+        window.history.go(-1)
+      }
+      if (this.protocolPopup) {
+        (this.protocolPopup as any).scrollTop = 0
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.colProtocol}>
+        <Checkbox v-model={this.checked}
+          // @ts-ignore
+          vSlots={{
+            icon: (props: any) => (
+              <Icon class={styles.boxStyle} name={props.checked ? activeButtonIcon : inactiveButtonIcon} size="15" />
+            )
+          }}
+        >
+          我已阅读并同意
+        </Checkbox>
+        <span onClick={this.onPopupClose} class={styles.protocolText}>《酷乐秀平台服务协议》</span>
+
+        <Popup ref={this.protocolPopup} show={this.popupStatus} position="bottom" style={{ height: '100%' }}>
+          <div class={styles.protocolContent}>
+            <div class={styles.protocolTitle}>酷乐秀平台服务协议</div>
+            <div class={styles.protocolContent}>
+              呆头呆脑的协议内容
+            </div>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

+ 1 - 1
src/components/imgCode/index.module.less

@@ -25,7 +25,7 @@
 
   .field {
     background: #F4F4F4;
-    padding: 10px 12px;
+    padding: 10px 12px !important;
   }
 }
 

+ 5 - 3
src/components/imgCode/index.tsx

@@ -10,7 +10,8 @@ export default defineComponent({
     phone: [String, Number],
     onClose: {
       type: Function,
-      default: () => {},
+      // (...args: any[]) => any) | undefined
+      default: () => {}
     },
     onSendCode: {
       type: Function,
@@ -48,13 +49,13 @@ export default defineComponent({
           if((this as any).code.length < 4) {
               return
           }
-          await request('/api-student/code/verifyLoginImage', {
+          await request.post('/api-student/code/verifyLoginImage', {
             data: {
               phone: this.phone,
               code: this.code
             }
           })
-          await request('/api-student/code/sendSms', {
+          await request.post('/api-student/code/sendSms', {
             data: {
               mobile: this.phone
             }
@@ -69,6 +70,7 @@ export default defineComponent({
   },
   render() {
     return (
+      // @ts-ignore
       <Popup show={this.showStatus} class={styles.imgCodePopup} closeOnClickOverlay={false} onClose={this.onClose} closeable closeIcon="close">
         <div class={styles.imgCode}>
           <p class={styles.codeTitle}>输入图形验证码</p>

+ 5 - 0
src/helpers/helpState.ts

@@ -0,0 +1,5 @@
+import { reactive } from 'vue';
+
+export const state = reactive({
+  loadingCount: 0 as number // 加载次数
+})

+ 8 - 5
src/helpers/request.ts

@@ -1,10 +1,9 @@
 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 { browser, openLoading, closeLoading } from '@/helpers/utils';
+import { setLogout, setLoginError } from '@/state';
 import { postMessage } from './native-message';
-import { getAuth } from './storage';
+import { Toast } from 'vant';
 
 export interface SearchInitParams {
   rows?: string | number;
@@ -32,6 +31,7 @@ let initRequest = false;
 
 request.interceptors.request.use(
   (url, options: any) => {
+    openLoading();
     initRequest = options.initRequest || false;
     const Authorization = sessionStorage.getItem('Authorization') || '';
     const authHeaders: any = {};
@@ -55,13 +55,16 @@ request.interceptors.request.use(
           ...authHeaders
         }
       }
-    }
+    };
   },
   { global: false }
 );
 
 request.interceptors.response.use(
   async res => {
+    setTimeout(() => {
+      closeLoading();
+    }, 100);
     if (res.status > 299 || res.status < 200) {
       const msg = '服务器错误,状态码' + res.status;
       Toast(msg);

+ 31 - 0
src/helpers/utils.ts

@@ -1,4 +1,6 @@
 import { sessionStorage as storage } from 'js-storage';
+import { Toast } from 'vant';
+import { state as helpState } from './helpState';
 
 export const browser = () => {
   const u = navigator.userAgent;
@@ -52,3 +54,32 @@ export const setAuth = (token: any) => {
 export const getAuth = () => {
   storage.get('Authorization');
 };
+
+/**
+ * 开始加载
+ */
+export const openLoading = () => {
+  if (helpState.loadingCount === 0) {
+    helpState.loadingCount++;
+    Toast.loading({
+      message: '加载中...',
+      forbidClick: true,
+      loadingType: 'spinner',
+      duration: 0
+    });
+  }
+};
+
+/**
+ * 关闭加载
+ */
+export const closeLoading = () => {
+  console.log(helpState.loadingCount, +new Date());
+  if (helpState.loadingCount <= 0) return;
+  setTimeout(() => {
+    helpState.loadingCount--;
+    if (helpState.loadingCount === 0) {
+      Toast.clear();
+    }
+  }, 200);
+};

+ 43 - 0
src/helpers/validate.ts

@@ -17,3 +17,46 @@ export function vaildStudentUrl() {
   }
   return returnUrl;
 }
+
+export function checkPhone(phone: string) {
+  const phoneRule = /^1[3456789]\d{9}$/;
+  return phoneRule.test(phone);
+}
+
+// 身份证号验证
+export function checkIDCard(idCardNo: string) {
+  let result = true;
+  //
+  const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
+  if (idCardReg.test(idCardNo) === false) {
+    result = false;
+  }
+  return result;
+}
+
+// 港澳居民来往内地通行证(回乡证)
+export function checkPassport(idCardNo: string) {
+  // 港澳居民来往内地通行证
+  // 规则: H/M + 10位或6位数字
+  // 样本: H1234567890
+  let result = true;
+  // let idReg = /^[mMhH]\\d{10}|[mMhH]\\d{8}$/
+  const idReg = /^([A-Z]\d{6,10}(\(\w{1}\))?)$/;
+  if (idReg.test(idCardNo) === false) {
+    result = false;
+  }
+  return result;
+}
+
+// 台湾居民来往大陆通行证(台胞证)
+export function checkPassportTaiwan(idCardNo: string) {
+  // 台湾居民来往大陆通行证
+  // 规则: 新版8位或18位数字, 旧版10位数字 + 英文字母
+  // 样本: 12345678 或 1234567890B
+  let result = true
+  const idReg = /(^\\d{8}$)|(^[a-zA-Z0-9]{10}$)|(^\\d{18}$)/;
+  if (idReg.test(idCardNo) === false) {
+    result = false;
+  }
+  return result;
+}

+ 5 - 0
src/router/index-student.ts

@@ -5,4 +5,9 @@ const router: Router = createRouter({
   routes
 });
 
+router.beforeEach((to, from, next) => {
+  document.title = (to.meta.title || '酷乐秀') as any;
+  next();
+});
+
 export default router;

+ 7 - 2
src/router/index-teacher.ts

@@ -1,8 +1,13 @@
 import { createRouter, createWebHashHistory, Router } from 'vue-router';
-
+import routes from './routes-teacher';
 const router: Router = createRouter({
   history: createWebHashHistory(),
-  routes: []
+  routes
+});
+
+router.beforeEach((to, from, next) => {
+  document.title = (to.meta.title || '酷乐秀') as any;
+  next();
 });
 
 export default router;

+ 8 - 0
src/router/routes-student.ts

@@ -21,6 +21,14 @@ export default [
         path: '/home',
         name: 'home',
         component: () => import('@/student/home/index')
+      },
+      {
+        path: '/practiceClass',
+        name: 'practiceClass',
+        component: () => import('@/student/practiceClass/index'),
+        meta: {
+          title: '陪练课'
+        }
       }
     ]
   },

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

@@ -21,6 +21,14 @@ export default [
         path: '/home',
         name: 'home',
         component: () => import('@/teacher/home/index')
+      },
+      {
+        path: 'teacherCert',
+        name: 'teacherCert',
+        component: () => import('@/teacher/teacherCert/index'),
+        meta: {
+          title: '老师认证'
+        }
       }
     ]
   },

+ 0 - 0
src/student/state.ts → src/state.ts


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

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

+ 1 - 1
src/student/layout/auth.tsx

@@ -1,6 +1,6 @@
 import { defineComponent } from "vue";
 import styles from './auth.module.less';
-import { state, setLogin } from '../state';
+import { state, setLogin } from '@/state';
 import { browser, setAuth } from "@/helpers/utils";
 import { postMessage } from "@/helpers/native-message";
 import { RouterView } from "vue-router";

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

@@ -12,4 +12,32 @@
     line-height: 37px;
     font-weight: 500;
   }
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .margin34 {
+    margin: 0 34px;
+  }
+
+  .formTitle {
+    font-size: 18px;
+    color: #000;
+    font-weight: 500;
+  }
+
+  :global {
+    .van-cell-group {
+      margin-bottom: 35px;
+    }
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button + .van-button {
+      margin-top: 20px;
+      color: #000 !important;
+    }
+  }
 }

+ 56 - 52
src/student/layout/login.tsx

@@ -1,8 +1,9 @@
 import { defineComponent } from "vue";
-import { CellGroup, Field, Button, CountDown } from "vant";
-import ImgCode from "@/components/imgCode";
+import { CellGroup, Field, Button, CountDown, Row, Col, Toast } from "vant";
+import ImgCode from "@/components/ImgCode";
+import { checkPhone } from "@/helpers/validate";
 import request from "@/helpers/request";
-import { setLogin, state } from "@/student/state";
+import { setLogin, state } from "@/state";
 import { removeAuth, setAuth } from "@/helpers/utils";
 import styles from "./login.module.less";
 
@@ -51,7 +52,7 @@ export default defineComponent({
     async onLogin() {
       try {
         let res: any;
-        if(this.loginType === 'PWD') {
+        if (this.loginType === 'PWD') {
           res = await request.post('/api-auth/usernameLogin', {
             data: {
               username: this.username,
@@ -81,14 +82,14 @@ export default defineComponent({
         setLogin(userCash.data)
 
         this.directNext();
-      } catch{
+      } catch {
 
       }
     },
     async onSendCode() { // 发送验证码
-      // if(!checkPhone(this.phoneNumber)) {
-      //     return
-      // }
+      if(!checkPhone(this.username)) {
+        return Toast('请输入正确的手机号码');
+      }
       this.imgCodeStatus = true
     },
     onCodeSend() {
@@ -100,9 +101,9 @@ export default defineComponent({
       this.countDownRef.reset();
     },
     onChange() {
-      if(this.loginType === 'PWD') {
+      if (this.loginType === 'PWD') {
         this.loginType = 'SMS'
-      } else if(this.loginType === 'SMS') {
+      } else if (this.loginType === 'SMS') {
         this.loginType = 'PWD'
       }
     },
@@ -111,55 +112,58 @@ export default defineComponent({
     return (
       <div class={styles.login}>
         <div class={styles.loginTitle}>您好,<br /> 欢迎使用酷乐秀</div>
-        <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.onSendCode}
-                  >发送验证码</Button>
-                ) : (
-                  <CountDown ref={this.countDownRef} auto-start="false" time={this.countDownTime} onFinish={this.onFinished} format="ss秒" />
-                )
-              )
-            }}
-          /> }
+        <CellGroup class={styles.margin34} border={false}>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>手机号</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.username}
+                name="手机号"
+                placeholder="请输入您的手机号"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+
+          {this.loginType === 'PWD' ? <Row>
+            <Col span={24} class={styles.formTitle}>密码</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.password}
+                type="password"
+                name="密码"
+                placeholder="请输入密码"
+              />
+            </Col>
+          </Row> : <Row>
+            <Col span={24} class={styles.formTitle}>密码</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.smsCode}
+                name="验证码"
+                placeholder="请输入验证码"
+                type="tel"
+                maxlength={6}
+                // @ts-ignore
+                vSlots={{
+                  button: () => (
+                    this.countDownStatus ? <span class={styles.codeText} onClick={this.onSendCode}>获取验证码</span> : <CountDown ref={this.countDownRef} auto-start={false} time={this.countDownTime} onFinish={this.onFinished} format="ss秒" />
+                  )
+                }}
+              />
+            </Col>
+          </Row>}
 
         </CellGroup>
-        <div style="margin: 16px;">
+        <div class={styles.margin34}>
           <Button round block type="primary" disabled={this.codeDisable} onClick={this.onLogin}>
             提交
           </Button>
-          <Button plain onClick={this.onChange}>{ this.loginType === 'PWD' ? '验证码登录' : '密码登录' }</Button>
+          <Button block round color="#F5F7FB" onClick={this.onChange}>{this.loginType === 'PWD' ? '验证码登录' : '密码登录'}</Button>
         </div>
 
-        { this.imgCodeStatus ? (
-          <ImgCode value={this.imgCodeStatus} phone={this.username} onClose={() => { this.imgCodeStatus = false }} onSendCode={this.onCodeSend} />
-        ) : null }
+        {this.imgCodeStatus ? <ImgCode v-model:value={this.imgCodeStatus} phone={this.username} onClose={() => { this.imgCodeStatus = false }} onSendCode={this.onCodeSend} /> : null}
       </div>
     )
   }

+ 0 - 0
src/student/practiceClass/index.module.less


+ 19 - 0
src/student/practiceClass/index.tsx

@@ -0,0 +1,19 @@
+import { defineComponent } from "vue";
+import styles from './index.module.less';
+import ColHeader from '@/components/ColHeader';
+import { Sticky } from "vant";
+
+export default defineComponent({
+  name: "practiceClass",
+  render() {
+    return (
+      <>
+        <Sticky>
+          <ColHeader title="陪练课" isFixed={false} isBack background="green">
+            显示
+          </ColHeader>
+        </Sticky>
+      </>
+    )
+  }
+})

+ 8 - 1
src/styles/index.less

@@ -13,6 +13,7 @@
   // --van-red: #ee0a24;
   // --van-blue: #1989fa;
   --van-primary: #2DC7AA !important;
+  --van-picker-confirm-action-color: #2DC7AA !important;
   // --van-orange: #ff976a;
   // --van-orange-dark: #ed6a0c;
   // --van-orange-light: #fffbe8;
@@ -76,6 +77,12 @@
   // --van-border-radius-max: 999px;
 }
 
+* {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
 #app {
   font-family: Avenir, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
@@ -84,5 +91,5 @@
 }
 
 body {
-  background-color: #f7f7f7;
+  background-color: #F6F8F9;
 }

+ 1 - 7
src/teacher/App.vue

@@ -1,17 +1,11 @@
 <template>
-  <!-- <img alt="Vue logo" src="./common/assets/logo.png" /> -->
-  <!-- <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" /> -->
-  <div>12321</div>
+  <router-view></router-view>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-// import HelloWorld from '@components/HelloWorld.vue';
 
 export default defineComponent({
   name: 'App'
-  // components: {
-  //   HelloWorld
-  // }
 });
 </script>

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

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

+ 2 - 3
src/teacher/layout/auth.tsx

@@ -1,6 +1,6 @@
 import { defineComponent } from "vue";
 import styles from './auth.module.less';
-import { state, setLogin } from '../../student/state';
+import { state, setLogin } from '@/state';
 import { browser, setAuth } from "@/helpers/utils";
 import { postMessage } from "@/helpers/native-message";
 import { RouterView } from "vue-router";
@@ -32,11 +32,10 @@ export default defineComponent({
       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', {
+          let res = await request.get('/api-student/userCashAccount/get', {
             initRequest: true // 初始化接口
           })
           // console.log(res)

BIN
src/teacher/layout/images/bottom_bg.png


BIN
src/teacher/layout/images/top_bg.png


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

@@ -0,0 +1,43 @@
+.login {
+  min-height: 100vh;
+  background: url('./images/top_bg.png') no-repeat top center, url('./images/bottom_bg.png') no-repeat bottom center;
+  background-color: #fff;
+  background-size: contain;
+
+  .loginTitle {
+    padding-top: 100px;
+    font-size: 26px;
+    padding-left: 35px;
+    padding-bottom: 70px;
+    line-height: 37px;
+    font-weight: 500;
+  }
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .margin34 {
+    margin: 0 34px;
+  }
+
+  .formTitle {
+    font-size: 18px;
+    color: #000;
+    font-weight: 500;
+  }
+
+  :global {
+    .van-cell-group {
+      margin-bottom: 35px;
+    }
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button + .van-button {
+      margin-top: 20px;
+      color: #000 !important;
+    }
+  }
+}

+ 66 - 41
src/teacher/layout/login.tsx

@@ -1,21 +1,25 @@
 import { defineComponent } from "vue";
-import { CellGroup, Field, Button, CountDown } from "vant";
+import { CellGroup, Field, Button, CountDown, Row, Col, Toast } from "vant";
+import ImgCode from "@/components/ImgCode";
+import { checkPhone } from "@/helpers/validate";
 import request from "@/helpers/request";
-import { setLogin, state } from "@/student/state";
+import { setLogin, state } from "@/state";
 import { removeAuth, setAuth } from "@/helpers/utils";
+import styles from "./login.module.less";
 
 type loginType = 'PWD' | 'SMS';
 export default defineComponent({
   name: 'login',
   data() {
     return {
-      loginType: 'PWD' as loginType,
+      loginType: 'SMS' as loginType,
       username: '',
       password: '',
       smsCode: '',
       countDownStatus: true, // 是否发送验证码
       countDownTime: 1000 * 120, // 倒计时时间
       countDownRef: null as any, // 倒计时实例
+      imgCodeStatus: false,
     }
   },
   computed: {
@@ -26,7 +30,6 @@ export default defineComponent({
       } else {
         this.username && this.smsCode && (status = false);
       }
-      console.log(status, this.loginType)
       return status;
     },
   },
@@ -49,7 +52,7 @@ export default defineComponent({
     async onLogin() {
       try {
         let res: any;
-        if(this.loginType === 'PWD') {
+        if (this.loginType === 'PWD') {
           res = await request.post('/api-auth/usernameLogin', {
             data: {
               username: this.username,
@@ -79,10 +82,16 @@ export default defineComponent({
         setLogin(userCash.data)
 
         this.directNext();
-      } catch{
+      } catch {
 
       }
     },
+    async onSendCode() { // 发送验证码
+      if(!checkPhone(this.username)) {
+        return Toast('请输入正确的手机号码');
+      }
+      this.imgCodeStatus = true
+    },
     onCodeSend() {
       this.countDownStatus = false;
       this.countDownRef.start();
@@ -92,53 +101,69 @@ export default defineComponent({
       this.countDownRef.reset();
     },
     onChange() {
-      if(this.loginType === 'PWD') {
+      if (this.loginType === 'PWD') {
         this.loginType = 'SMS'
-      } else if(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秒" />
-              )
-            }}
-          /> }
+      <div class={styles.login}>
+        <div class={styles.loginTitle}>您好,<br /> 欢迎使用酷乐秀</div>
+        <CellGroup class={styles.margin34} border={false}>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>手机号</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.username}
+                name="手机号"
+                placeholder="请输入您的手机号"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+
+          {this.loginType === 'PWD' ? <Row>
+            <Col span={24} class={styles.formTitle}>密码</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.password}
+                type="password"
+                name="密码"
+                placeholder="请输入密码"
+              />
+            </Col>
+          </Row> : <Row>
+            <Col span={24} class={styles.formTitle}>密码</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.smsCode}
+                name="验证码"
+                placeholder="请输入验证码"
+                type="tel"
+                maxlength={6}
+                // @ts-ignore
+                vSlots={{
+                  button: () => (
+                    this.countDownStatus ? <span class={styles.codeText} onClick={this.onSendCode}>获取验证码</span> : <CountDown ref={this.countDownRef} auto-start={false} time={this.countDownTime} onFinish={this.onFinished} format="ss秒" />
+                  )
+                }}
+              />
+            </Col>
+          </Row>}
 
         </CellGroup>
-        <div style="margin: 16px;">
+        <div class={styles.margin34}>
           <Button round block type="primary" disabled={this.codeDisable} onClick={this.onLogin}>
             提交
           </Button>
-          <Button plain onClick={this.onChange}>{ this.loginType === 'PWD' ? '验证码登录' : '密码登录' }</Button>
+          <Button block round color="#F5F7FB" onClick={this.onChange}>{this.loginType === 'PWD' ? '验证码登录' : '密码登录'}</Button>
         </div>
+
+        {this.imgCodeStatus ? <ImgCode v-model:value={this.imgCodeStatus} phone={this.username} onClose={() => { this.imgCodeStatus = false }} onSendCode={this.onCodeSend} /> : null}
       </div>
     )
   }

+ 12 - 0
src/teacher/main.ts

@@ -10,6 +10,18 @@ import '../styles/index.less';
 
 const app = createApp(App);
 
+// 将selects全局混入当前vue实例中
+// import activeButtonIcon from '@/common/images/icon_check.png';
+// import inactiveButtonIcon from '@/common/images/icon_default.png';
+// app.mixin({
+//   data() {
+//     return {
+//       activeButtonIcon: activeButtonIcon,
+//       inactiveButtonIcon: inactiveButtonIcon,
+//     };
+//   }
+// });
+
 dayjs.locale('zh-ch');
 app.config.globalProperties.$dayjs = dayjs;
 app.use(router);

+ 58 - 0
src/teacher/teacherCert/certOne.module.less

@@ -0,0 +1,58 @@
+.certOne {
+  margin: 0 14px;
+  border-radius: 10px;
+  overflow: hidden;
+  background-color: #fff;
+  padding: 20px;
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .formTitle {
+    font-size: 17px;
+    color: #000;
+    line-height: 24px;
+    &::before {
+      content: '*';
+      color: #FF4E19;
+      font-size: 17px;
+    }
+  }
+
+  :global {
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button + .van-button {
+      margin-top: 20px;
+      color: #000 !important;
+    }
+  }
+
+  .radioGroup {
+    padding-top: 14px;
+    display: flex;
+  }
+  .radio {
+    width: 94px;
+    height: 28px;
+    line-height: 28px;
+    background: #F8F8F8;
+    border: 1px solid #F8F8F8;
+    border-radius: 5px;
+    font-size: 16px;
+    color: #C0C0C0;
+    text-align: center;
+    & + .radio {
+      margin-left: 5px;
+    }
+
+    &.active {
+      background: #E9FFF8;
+      border: 1px solid var(--van-primary);
+      color: var(--van-primary);
+    }
+  }
+}

+ 98 - 0
src/teacher/teacherCert/certOne.tsx

@@ -0,0 +1,98 @@
+import { Cell, CellGroup, Col, DatetimePicker, Field, Popup, Row } from "vant"
+import { defineComponent } from "vue";
+import dayjs from "dayjs";
+import { teacherState } from "./teacherState";
+import styles from './certOne.module.less';
+
+export default defineComponent({
+  name: 'certOne',
+  data() {
+    return {
+      maxDate: new Date(),
+      popupShow: false,
+      popupDate: new Date(),
+    }
+  },
+  methods: {
+    onConfirm(_date: any) {
+      teacherState.teacherCert.birthday = dayjs(this.popupDate).format('YYYY-MM-DD')
+      this.popupShow = false;
+    },
+    formatter(type: any, val: any) {
+      if (type === 'year') {
+        return `${val}年`;
+      }
+      if (type === 'month') {
+        return `${val}月`;
+      }
+      if (type === 'day') {
+        return `${val}日`;
+      }
+      return val;
+    }
+  },
+  render() {
+    return (
+      <div class={styles.certOne}>
+        <CellGroup border={false}>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>真实姓名</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={teacherState.teacherCert.username}
+                name="真实姓名"
+                placeholder="请输入您的真实姓名"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>身份证号</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={teacherState.teacherCert.idCard}
+                name="身份证号"
+                placeholder="请输入您的身份证号码"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>性别</Col>
+            <Col span={24} class={styles.radioGroup}>
+              <div onClick={() => teacherState.teacherCert.sex = 1 } class={[styles.radio, teacherState.teacherCert.sex === 1 ? styles.active : null]}>男</div>
+              <div onClick={() => teacherState.teacherCert.sex = 0 } class={[styles.radio,teacherState.teacherCert.sex === 0 ? styles.active : null]}>女</div>
+            </Col>
+          </Row>
+          <Row>
+            <Col span={24} class={styles.formTitle}>出生日期</Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={teacherState.teacherCert.birthday}
+                name="出生日期"
+                onClick-input={() => this.popupShow = true}
+                readonly
+                isLink
+                placeholder="请选择您的出生日期"
+              />
+            </Col>
+          </Row>
+        </CellGroup>
+
+        <Popup show={this.popupShow} position="bottom" round>
+          <DatetimePicker
+            v-model={this.popupDate}
+            type="date"
+            close-on-popstate={true}
+            maxDate={this.maxDate}
+            onCancel={() => this.popupShow = false}
+            onConfirm={this.onConfirm}
+            formatter={this.formatter}>
+          </DatetimePicker>
+        </Popup>
+      </div>
+    )
+  }
+})

BIN
src/teacher/teacherCert/images/base_active.png


BIN
src/teacher/teacherCert/images/base_default.png


BIN
src/teacher/teacherCert/images/base_finish.png


BIN
src/teacher/teacherCert/images/education_active.png


BIN
src/teacher/teacherCert/images/education_default.png


BIN
src/teacher/teacherCert/images/education_finish.png


BIN
src/teacher/teacherCert/images/icon_horn.png


BIN
src/teacher/teacherCert/images/name_active.png


BIN
src/teacher/teacherCert/images/name_default.png


BIN
src/teacher/teacherCert/images/name_finish.png


+ 5 - 0
src/teacher/teacherCert/index.module.less

@@ -0,0 +1,5 @@
+.teacher-cert {
+  .btnGroup {
+    padding: 0 14px;
+  }
+}

+ 38 - 0
src/teacher/teacherCert/index.tsx

@@ -0,0 +1,38 @@
+
+import { defineComponent } from "vue";
+import styles from './index.module.less';
+import ColProtocol from "@/components/ColProtocol";
+import Steps from './steps';
+import CertOne from './certOne'
+import { Button, Toast } from "vant";
+
+
+export default defineComponent({
+  name: 'teacherCert',
+  data() {
+    return {
+    }
+  },
+  methods: {
+    next() {
+      const checked = (this as any).$refs.eleRef.checked;
+      if (!checked) {
+        Toast('请阅读并同意协议');
+        return
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles['teacher-cert']}>
+        <Steps style={{ marginBottom: '12px' }} />
+
+        <CertOne />
+        <div class={styles.btnGroup}>
+          <ColProtocol ref='eleRef' style={{ paddingLeft: 0, paddingRight: 0 }} />
+          <Button block round onClick={this.next} type="primary" text="下一步" />
+        </div>
+      </div>
+    )
+  }
+})

+ 44 - 0
src/teacher/teacherCert/steps.module.less

@@ -0,0 +1,44 @@
+.steps {
+  background-color: #fff;
+  margin: 12px 14px 0;
+  padding: 20px;
+  border-radius: 10px;
+  &.paddingBottom12 {
+    padding-bottom: 12px;
+  }
+  .stepContent {
+    display: flex;
+    justify-content: space-between;
+  }
+  .stepItem {
+    text-align: center;
+    p {
+      font-size: 12px;
+      color: #B4B4B4;
+      &.active {
+        color: var(--van-primary);
+      }
+    }
+  }
+  .line {
+    display: block;
+    margin-top: 19px;
+    width: 54px;
+    height: 2px;
+    background-color: #E3E3E3;
+    &.lineActive {
+      background-color: #8BE1D1;
+    }
+  }
+
+  .stepTips {
+    margin-top: 20px;
+    padding: 7px;
+    background: linear-gradient(139deg, #FFF6EE 0%, #FFECDD 100%);
+    border-radius: 6px;
+    font-size: 12px;
+    color: #E0945A;
+    display: flex;
+    align-items: center;
+  }
+}

+ 74 - 0
src/teacher/teacherCert/steps.tsx

@@ -0,0 +1,74 @@
+import { defineComponent } from "vue";
+import { Icon } from 'vant';
+import { teacherState } from './teacherState'
+import styles from './steps.module.less';
+import baseDefault from './images/base_default.png';
+import baseActive from './images/base_active.png';
+import baseFinish from './images/base_finish.png';
+import nameDefault from './images/name_default.png';
+import nameActive from './images/name_active.png';
+import nameFinish from './images/name_finish.png';
+import educationDefault from './images/education_default.png';
+import educationActive from './images/education_active.png';
+import educationFinish from './images/education_finish.png';
+
+import horn from './images/icon_horn.png'
+
+export default defineComponent({
+  name: "steps",
+  computed: {
+    stepOne() {
+      if (teacherState.active === 1) {
+        return baseActive
+      } else if (teacherState.active > 1) {
+        return baseFinish
+      } else {
+        return baseDefault
+      }
+    },
+    stepTwo() {
+      if (teacherState.active === 2) {
+        return nameActive
+      } else if (teacherState.active > 2) {
+        return nameFinish
+      } else {
+        return nameDefault
+      }
+    },
+    stepThree() {
+      if (teacherState.active === 3) {
+        return educationActive
+      } else if (teacherState.active > 3) {
+        return educationFinish
+      } else {
+        return educationDefault
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={[styles.steps, teacherState.active == 3 ? styles.paddingBottom12 : null]}>
+        <div class={styles.stepContent}>
+          <div class={[styles.stepItem]}>
+            <Icon name={this.stepOne} size="38" />
+            <p class={this.stepOne != baseDefault ? styles.active : null}>实名认证</p>
+          </div>
+          <div class={[styles.stepItem, styles.line, this.stepTwo != nameDefault ? styles.lineActive : null]}></div>
+          <div class={[styles.stepItem]}>
+            <Icon name={this.stepTwo} size="38" />
+            <p class={this.stepTwo != nameDefault ? styles.active : null}>基本信息</p>
+          </div>
+          <div class={[styles.stepItem, styles.line, this.stepThree != educationDefault ? styles.lineActive : null]}></div>
+          <div class={[styles.stepItem]}>
+            <Icon name={this.stepThree} size="38" />
+            <p class={this.stepThree != educationDefault ? styles.active : null}>学历信息</p>
+          </div>
+        </div>
+        { teacherState.active == 3 ? <div class={styles.stepTips}>
+          <Icon name={horn} size="20" style={{ marginRight: '6px' }} />
+          完整填写学历信息有助于您更快的通过认证审核
+        </div> : null }
+      </div>
+    )
+  }
+})

+ 11 - 0
src/teacher/teacherCert/teacherState.ts

@@ -0,0 +1,11 @@
+import { reactive } from 'vue';
+
+export const teacherState = reactive({
+  active: 1,
+  teacherCert: {
+    username: null,
+    idCard: null,
+    sex: 1,
+    birthday: null as any
+  }
+});