lex 1 éve
szülő
commit
da04c1954a
43 módosított fájl, 1898 hozzáadás és 146 törlés
  1. BIN
      src/common/images/icon-student.png
  2. BIN
      src/common/images/icon-teacher.png
  3. BIN
      src/common/images/wx-no-bg.png
  4. BIN
      src/common/images/wx-no-top.png
  5. 42 0
      src/components/m-wx-tip/index.module.less
  6. 47 0
      src/components/m-wx-tip/index.tsx
  7. 0 23
      src/helpers/utils.ts
  8. 5 6
      src/main.ts
  9. 1 1
      src/router/index.ts
  10. 16 0
      src/router/router-root.ts
  11. 8 0
      src/router/routes-common.ts
  12. 1 2
      src/state.ts
  13. 2 2
      src/views/adapay/pay-define/index.tsx
  14. 2 2
      src/views/adapay/pay-result/index.tsx
  15. 4 2
      src/views/layout/auth.tsx
  16. 23 44
      src/views/layout/login.tsx
  17. BIN
      src/views/member-center/images/bg.png
  18. BIN
      src/views/member-center/images/discount_bg.png
  19. BIN
      src/views/member-center/images/icon-question.png
  20. BIN
      src/views/member-center/images/member-1.png
  21. BIN
      src/views/member-center/images/member-2.png
  22. BIN
      src/views/member-center/images/member_logo.png
  23. BIN
      src/views/member-center/images/record_bg.png
  24. BIN
      src/views/member-center/images/vip_bg.png
  25. 261 0
      src/views/member-center/index.module.less
  26. 292 0
      src/views/member-center/index.tsx
  27. 44 0
      src/views/preview-protocol/cash-protocol.tsx
  28. 27 0
      src/views/preview-protocol/index.module.less
  29. 49 0
      src/views/preview-protocol/index.tsx
  30. 66 0
      src/views/preview-protocol/privacy.module.less
  31. 473 0
      src/views/preview-protocol/privacy.tsx
  32. 66 12
      src/views/student-register/index.tsx
  33. 1 1
      src/views/student-register/order-detail.tsx
  34. 5 4
      src/views/student-register/shop-mall/components/goods/index.tsx
  35. 20 0
      src/views/student-register/shop-mall/components/tab-list/index.tsx
  36. 9 2
      src/views/student-register/shop-mall/goods-detail/index.module.less
  37. 33 20
      src/views/student-register/shop-mall/goods-detail/index.tsx
  38. 1 3
      src/views/student-register/shop-mall/goods-list/index.tsx
  39. BIN
      src/views/student-register/shop-mall/images/icon-add-cart.png
  40. 131 0
      src/views/student-register/shop-mall/modal/add-goods-cart/index.module.less
  41. 258 0
      src/views/student-register/shop-mall/modal/add-goods-cart/index.tsx
  42. 3 1
      src/views/student-register/shop-mall/modal/goods-filter-list/index.tsx
  43. 8 21
      src/views/student-register/shop-mall/shop-mall.ts

BIN
src/common/images/icon-student.png


BIN
src/common/images/icon-teacher.png


BIN
src/common/images/wx-no-bg.png


BIN
src/common/images/wx-no-top.png


+ 42 - 0
src/components/m-wx-tip/index.module.less

@@ -0,0 +1,42 @@
+.wxPopupDialog {
+  // position: relative;
+  overflow: initial;
+
+  // margin-top: -160px;
+  &::before {
+    position: absolute;
+    content: ' ';
+    top: -73px;
+    left: 50%;
+    margin-left: -86px;
+    display: inline-block;
+    background: url('../../common/images/wx-no-top.png') no-repeat top center;
+    background-size: contain;
+    width: 172px;
+    height: 154px;
+  }
+}
+
+.popupContainer {
+  background: url('../../common/images/wx-no-bg.png') no-repeat top center;
+  background-size: cover;
+  border-radius: 20px;
+  overflow: hidden;
+
+  .title1 {
+    padding-top: 57px;
+    text-align: center;
+    font-size: 18px;
+    font-weight: 500;
+    color: #3b2300;
+  }
+
+  .popupTips {
+    padding-top: 12px;
+    padding-bottom: 47px;
+    text-align: center;
+    font-size: 15px;
+    color: #777777;
+    line-height: 21px;
+  }
+}

+ 47 - 0
src/components/m-wx-tip/index.tsx

@@ -0,0 +1,47 @@
+import { Popup } from 'vant';
+import { defineComponent, onMounted, ref } from 'vue';
+import styles from './index.module.less';
+import { browser } from '@/helpers/utils';
+
+export default defineComponent({
+  name: 'm-wx-tip',
+  props: {
+    // 是否显示微信弹窗
+    show: {
+      type: Boolean,
+      default: true
+    },
+    title: {
+      type: String,
+      default: '温馨提示'
+    },
+    message: {
+      type: String,
+      default: '请使用微信打开'
+    }
+  },
+  setup(props) {
+    const showPopup = ref(false);
+    onMounted(() => {
+      if (!browser().weixin && props.show) {
+        showPopup.value = true;
+        return;
+      }
+    });
+    return () => (
+      <>
+        <Popup
+          v-model:show={showPopup.value}
+          round
+          style={{ width: '88%' }}
+          closeOnClickOverlay={false}
+          class={styles.wxPopupDialog}>
+          <div class={styles.popupContainer}>
+            <p class={styles.title1}>{props.title}</p>
+            <p class={styles.popupTips}>{props.message}</p>
+          </div>
+        </Popup>
+      </>
+    );
+  }
+});

+ 0 - 23
src/helpers/utils.ts

@@ -47,29 +47,6 @@ export const getRandomKey = () => {
   return key;
 };
 
-/**
- * 删除token
- */
-export const removeAuth = () => {
-  sessionStorage.removeItem('Authorization');
-};
-
-/**
- * 设置token
- * @param token
- * @returns {void}
- */
-export const setAuth = (token: any) => {
-  sessionStorage.setItem('Authorization', token);
-};
-
-/**
- * 获取token
- */
-export const getAuth = () => {
-  sessionStorage.getItem('Authorization');
-};
-
 export function checkPhone(phone: string) {
   const phoneRule =
     /^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\d{8}$/;

+ 5 - 6
src/main.ts

@@ -9,14 +9,15 @@ import './component-ui/index.less';
 import './styles/index.less';
 import { promisefiyPostMessage, postMessage } from './helpers/native-message';
 import { state } from './state';
-import { setAuth } from './helpers/utils';
+import { storage } from '@/helpers/storage';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
 import { setupStore } from './store';
 
 // 获取token
 promisefiyPostMessage({ api: 'getToken' }).then((res: any) => {
   const content = res.content;
   if (content?.accessToken) {
-    setAuth(content.tokenType + ' ' + content.accessToken);
+    storage.set(ACCESS_TOKEN, content.tokenType + ' ' + content.accessToken);
   }
 });
 
@@ -31,13 +32,11 @@ postMessage({ api: 'getNavHeight' }, (res: any) => {
   }
 });
 
-import Vconsole from 'vconsole';
-const vconsole = new Vconsole();
+// import Vconsole from 'vconsole';
+// const vconsole = new Vconsole();
 
 const app = createApp(App);
 
-console.log(import.meta.env.DEV);
-
 // store plugin: pinia
 setupStore(app);
 

+ 1 - 1
src/router/index.ts

@@ -18,7 +18,7 @@ const router: Router = createRouter({
 });
 
 router.beforeEach((to, from, next) => {
-  document.title = (to.meta.title || '学端') as any;
+  document.title = (to.meta.title || '学端') as any;
   next();
 });
 

+ 16 - 0
src/router/router-root.ts

@@ -84,6 +84,22 @@ export default [
     }
   },
   {
+    path: '/preview-protocol',
+    name: 'preview-protocol',
+    component: () => import('@/views/preview-protocol/index'),
+    meta: {
+      title: '课堂乐器注册协议'
+    }
+  },
+  {
+    path: '/privacy-protocol',
+    name: 'privacy-protocol',
+    component: () => import('@/views/preview-protocol/privacy'),
+    meta: {
+      title: '课堂乐器隐私政策'
+    }
+  },
+  {
     path: '/:pathMatch(.*)*',
     component: () => import('@/views/404'),
     meta: {

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

@@ -41,6 +41,14 @@ export default [
         meta: {
           title: '收货地址'
         }
+      },
+      {
+        path: '/member-center',
+        name: 'member-center',
+        component: () => import('@/views/member-center/index'),
+        meta: {
+          title: '会员中心'
+        }
       }
     ]
   },

+ 1 - 2
src/state.ts

@@ -72,7 +72,6 @@ export const goWechatAuth = (wxAppId: string, urlString?: string) => {
     window.location.replace(replaceUrl);
   }
 
-  console.log(import.meta.env.PROD, ':是否为测试环境');
   // 生产环境
   if (import.meta.env.PROD) {
     goAuth(wxAppId, urlString);
@@ -82,7 +81,7 @@ export const goWechatAuth = (wxAppId: string, urlString?: string) => {
 const goAuth = (wxAppId: string, urlString?: string) => {
   // 用户授权
   const urlNow = encodeURIComponent(urlString || window.location.href);
-  console.log(urlNow, 'urlNow');
+  // console.log(urlNow, 'urlNow');
   const scope = 'snsapi_base'; //snsapi_userinfo   //静默授权 用户无感知
   const appid = wxAppId || 'wx8654c671631cfade';
   const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${urlNow}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`;

+ 2 - 2
src/views/adapay/pay-define/index.tsx

@@ -118,7 +118,7 @@ export default defineComponent({
           ) {
             window.location.replace(
               location.origin +
-                '/orchestra-student/#/payment-result?orderNo=' +
+                '/classroom-app/#/payment-result?orderNo=' +
                 state.orderNo
             );
           } else {
@@ -127,7 +127,7 @@ export default defineComponent({
             // alert('支付成功')
             window.location.replace(
               location.origin +
-                '/orchestra-student/#/payment-result?orderNo=' +
+                '/classroom-app/#/payment-result?orderNo=' +
                 state.orderNo
             );
           }

+ 2 - 2
src/views/adapay/pay-result/index.tsx

@@ -161,7 +161,7 @@ export default defineComponent({
           ) {
             window.location.replace(
               location.origin +
-                '/orchestra-student/#/payment-result?orderNo=' +
+                '/classroom-app/#/payment-result?orderNo=' +
                 state.orderNo
             );
           } else {
@@ -170,7 +170,7 @@ export default defineComponent({
             // alert('支付成功')
             window.location.replace(
               location.origin +
-                '/orchestra-student/#/payment-result?orderNo=' +
+                '/classroom-app/#/payment-result?orderNo=' +
                 state.orderNo
             );
           }

+ 4 - 2
src/views/layout/auth.tsx

@@ -1,7 +1,9 @@
 import { defineComponent } from 'vue';
 import styles from './auth.module.less';
 import { state, setLogin, setLogout, setLoginError } from '@/state';
-import { browser, setAuth } from '@/helpers/utils';
+import { browser } from '@/helpers/utils';
+import { storage } from '@/helpers/storage';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
 import { postMessage } from '@/helpers/native-message';
 import { RouterView } from 'vue-router';
 import request from '@/helpers/request';
@@ -38,7 +40,7 @@ export default defineComponent({
       const token = query.userInfo || query.Authorization;
 
       if (token) {
-        setAuth(token);
+        storage.set(ACCESS_TOKEN, token);
       }
       if (this.loading) {
         return;

+ 23 - 44
src/views/layout/login.tsx

@@ -3,9 +3,10 @@ import { CellGroup, Field, Button, CountDown, showToast } from 'vant';
 import ImgCode from '@/components/m-img-code';
 import request from '@/helpers/request';
 import { setLogin, state } from '@/state';
-import { checkPhone, removeAuth, setAuth } from '@/helpers/utils';
+import { checkPhone } from '@/helpers/utils';
+import { storage } from '@/helpers/storage';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
 import styles from './login.module.less';
-import logo from '@/common/images/logo.png';
 
 type loginType = 'PWD' | 'SMS';
 export default defineComponent({
@@ -34,7 +35,7 @@ export default defineComponent({
     }
   },
   mounted() {
-    removeAuth();
+    storage.remove(ACCESS_TOKEN);
     this.directNext();
   },
   methods: {
@@ -54,51 +55,31 @@ export default defineComponent({
       try {
         // let res: any
         const forms: any = {
-          phone: this.username,
-          clientId: 'EDUCATION',
-          clientSecret: 'EDUCATION'
+          username: this.username,
+          client_id: 'cooleshow-student',
+          client_secret: 'cooleshow-student',
+          password: this.loginType === 'PWD' ? this.password : this.smsCode,
+          grant_type: 'password',
+          loginType: this.loginType === 'PWD' ? 'PASSWORD' : 'SMS'
         };
 
-        if (this.loginType === 'PWD') {
-          forms.password = this.password;
-          forms.grant_type = 'password';
-          const { data } = await request.post('/api-auth/usernameLogin', {
-            requestType: 'form',
-            data: {
-              ...forms
-            }
-          });
-          setAuth(
-            data.authentication.token_type +
-              ' ' +
-              data.authentication.access_token
-          );
-        } else {
-          forms.smsCode = this.smsCode;
-          const { data } = await request.post('/api-auth/smsLogin', {
-            requestType: 'form',
-            data: {
-              ...forms
-            }
-          });
-          setAuth(
-            data.authentication.token_type +
-              ' ' +
-              data.authentication.access_token
-          );
-        }
-
-        const userCash = await request.get(
-          '/api-web/schoolStaff/queryUserInfo',
-          {
-            initRequest: true // 初始化接口
+        const { data } = await request.post('/edu-oauth/userlogin', {
+          requestType: 'form',
+          data: {
+            ...forms
           }
-        );
+        });
+        storage.set(ACCESS_TOKEN, data.token_type + ' ' + data.access_token);
+
+        const userCash = await request.get('/edu-app/user/getUserInfo', {
+          initRequest: true // 初始化接口
+        });
         setLogin(userCash.data);
 
         this.directNext();
-      } catch {
+      } catch (e: any) {
         //
+        console.log(e);
       }
     },
     async onSendCode() {
@@ -129,9 +110,7 @@ export default defineComponent({
   render() {
     return (
       <div class={[styles.login]}>
-        <div class={styles.logo}>
-          {/* <img src={logo} /> */}
-        </div>
+        <div class={styles.logo}>{/* <img src={logo} /> */}</div>
         <CellGroup class={styles.container} border={false}>
           <Field
             v-model={this.username}

BIN
src/views/member-center/images/bg.png


BIN
src/views/member-center/images/discount_bg.png


BIN
src/views/member-center/images/icon-question.png


BIN
src/views/member-center/images/member-1.png


BIN
src/views/member-center/images/member-2.png


BIN
src/views/member-center/images/member_logo.png


BIN
src/views/member-center/images/record_bg.png


BIN
src/views/member-center/images/vip_bg.png


+ 261 - 0
src/views/member-center/index.module.less

@@ -0,0 +1,261 @@
+.member-center {
+  background: url('./images/bg.png') top center no-repeat;
+  background-size: contain;
+  min-height: 100vh;
+  position: relative;
+
+  :global {
+    .van-nav-bar {
+      background-color: transparent;
+    }
+  }
+
+  .member_container {
+    padding: 0 5px;
+
+    // .title {
+    //   display: flex;
+    //   align-items: center;
+    //   font-size: 16px;
+    //   line-height: 28px;
+    //   font-weight: 500;
+    //   color: #333333;
+
+    //   &::before {
+    //     content: ' ';
+    //     width: 4px;
+    //     height: 17px;
+    //     background: var(--van-primary-color);
+    //     display: inline-block;
+    //     margin-right: 7px;
+    //     border-radius: 8px;
+    //   }
+    // }
+  }
+
+
+  .level {
+    width: 18px;
+    height: 16px;
+  }
+
+  .userMember {
+    width: auto;
+    border-radius: 19px;
+    background-color: transparent;
+
+    .userImgSection {
+      border: 2px solid #fff;
+      background-color: transparent;
+      margin-right: 12px;
+      border-radius: 50%;
+    }
+
+    .userImg {
+      width: 52px;
+      height: 52px;
+      border-radius: 50%;
+      vertical-align: middle;
+      overflow: hidden;
+    }
+
+    .userInfo {
+      display: flex;
+      align-items: center;
+      color: #1E464F;
+      padding-bottom: 5px;
+
+      .name {
+        font-size: 18px;
+        padding-right: 5px;
+        max-width: 100px;
+        font-weight: bold;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      .phone {
+        font-size: 14px;
+      }
+    }
+
+    .timeRemaining {
+      margin-top: 0;
+      font-size: 14px;
+      color: #c0c0c0;
+
+      .remaining {
+        color: #f7b500;
+        padding: 0 5px;
+      }
+    }
+
+    .member_time {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      color: #1E464F;
+    }
+  }
+
+  .memberImgs {
+    img {
+      margin-bottom: 6px;
+      width: 100%;
+    }
+  }
+
+  // .intro {
+  //   background: url('./images/tip_bg.png') no-repeat center;
+  //   background-size: contain;
+  //   height: 142px;
+  //   font-size: 14px;
+  //   color: #bb6e3a;
+
+  //   p {
+  //     padding: 45px 25px 0;
+  //     text-align: justify;
+  //     line-height: 22px;
+  //   }
+  // }
+
+  .memberContainer {
+    position: relative;
+    padding: 0 14px 75px;
+    z-index: 99;
+  }
+
+  .memberItem {
+    display: flex;
+    flex-direction: column;
+    min-height: 131px;
+    box-sizing: border-box;
+    border-radius: 12px;
+    background: url('./images/vip_bg.png') no-repeat center;
+    background-size: contain;
+
+    .title {
+      padding: 25px 17px 0;
+      font-size: 14px;
+      color: #FFFFFF;
+
+      span {
+        margin-left: 6px;
+        background: rgba(255, 255, 255, 0.73);
+        border-radius: 4px;
+        font-size: 12px;
+        font-weight: 600;
+        color: #1F82ED;
+        line-height: 18px;
+        padding: 1px 3px;
+      }
+    }
+
+    .priceGroup {
+      display: flex;
+      align-items: baseline;
+      padding: 25px 12px 9px;
+    }
+
+    .price {
+      font-size: 30px;
+      font-weight: 500;
+      color: #fff;
+      font-family: DINAlternate-Bold, DINAlternate;
+
+      span {
+        font-size: 22px;
+      }
+    }
+
+    .originalPrice {
+      margin-left: 8px;
+      font-size: 14px;
+      color: #fff !important;
+      font-family: DINAlternate-Bold, DINAlternate;
+      line-height: 16px;
+      font-weight: 300;
+    }
+  }
+
+
+  .btnGroup {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 100;
+    background-color: #fff;
+    display: flex;
+    align-items: center;
+    padding: 12px 16px;
+    justify-content: space-between;
+    border-top: 1px solid #f0f0f0;
+
+    .btn {
+      line-height: 38px;
+      height: 38px;
+      background: linear-gradient(220deg, #F5CBAB 0%, #FFCFC0 100%);
+      padding: 0 17px;
+      font-size: 16px;
+      color: #772A27;
+    }
+
+    .priceSection {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      color: #1a1a1a;
+
+      .price {
+        font-size: 18px;
+        font-weight: bold;
+        color: #ff3535;
+        font-family: DINAlternate-Bold, DINAlternate;
+
+        .priceUnit {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
+
+.memberDiscount {
+  margin-top: 16px;
+  position: relative;
+  background: url('./images/discount_bg.png') no-repeat center;
+  background-size: contain;
+  display: flex;
+  align-items: center;
+  height: 44px;
+  font-size: 16px;
+  color: #ff7100;
+  line-height: 18px;
+
+  .discountAvatar {
+    margin-left: 15px;
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    overflow: hidden;
+    border: 1px solid #ffaf59;
+  }
+
+  .discountName {
+    padding-left: 30px;
+    max-width: 200px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .discountGift {
+    position: absolute;
+    right: 26px;
+    top: 7px;
+    width: 29px;
+    height: 29px;
+  }
+}

+ 292 - 0
src/views/member-center/index.tsx

@@ -0,0 +1,292 @@
+import { Button, Cell, Icon, Image, showConfirmDialog } from 'vant';
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import request from '@/helpers/request';
+import iconStudent from '@common/images/icon-student.png';
+import iconMemberLogo from './images/member_logo.png';
+import { moneyFormat } from '@/helpers/utils';
+import OHeader from '@/components/m-header';
+import member1 from './images/member-1.png';
+import member2 from './images/member-2.png';
+// import iconQuestion from './images/icon-question.png';
+import ODialog from '@/components/m-dialog';
+import { useEventListener, useWindowScroll } from '@vueuse/core';
+
+export default defineComponent({
+  name: 'MemberCenter',
+  data() {
+    const query = this.$route.query;
+    return {
+      functionList: [] as any,
+      selectMember: {} as any,
+      users: {} as any,
+      memberStatus: false,
+      background: 'transparent',
+      color: '#fff'
+    };
+  },
+  computed: {
+    userInfo() {
+      const users: any = this.users;
+      console.log(this.users, {
+        username: users?.nickname || '',
+        phone: users?.phone || '',
+        avatar: users?.avatar,
+        id: users?.id,
+        isVip: users?.vipMember,
+        membershipDays: users?.membershipDays,
+        membershipEndTime: users?.membershipEndTime
+      });
+      return {
+        username: users?.nickname || '',
+        phone: users?.phone || '',
+        avatar: users?.avatar,
+        id: users?.id,
+        isVip: users?.vipMember,
+        membershipDays: users?.membershipDays,
+        membershipEndTime: users?.membershipEndTime
+      };
+    }
+  },
+  async mounted() {
+    useEventListener(document, 'scroll', () => {
+      const { y } = useWindowScroll();
+      if (y.value > 52) {
+        this.background = '#fff';
+        this.color = '#323333';
+      } else {
+        this.background = 'transparent';
+        this.color = '#fff';
+      }
+    });
+    try {
+      const userInfo = await request.get('/edu-app/student/member');
+      this.users = userInfo.data || {};
+
+      const { data } = await request.post(`/edu-app/cityFeeSetting/member`);
+      this.selectMember = data;
+
+      this.paymentOrderUnpaid();
+    } catch {
+      //
+    }
+    //
+  },
+  methods: {
+    // 查询未支付订单
+    async paymentOrderUnpaid() {
+      try {
+        const { data } = await request.get('/edu-app/userPaymentOrder/unpaid', {
+          requestType: 'form',
+          params: {
+            paymentType: 'VIP'
+          }
+        });
+        // 判断是否有待支付订单
+        if (data.id) {
+          showConfirmDialog({
+            message: '您有待支付的订单,是否继续支付',
+            cancelButtonText: '取消订单',
+            confirmButtonText: '继续支付'
+          })
+            .then(() => {
+              const paymentConfig = data.paymentConfig;
+              this.$router.push({
+                path: '/order-detail',
+                query: {
+                  config: JSON.stringify(paymentConfig.paymentConfig),
+                  orderNo: paymentConfig.orderNo
+                }
+              });
+            })
+            .catch(async () => {
+              try {
+                await request.post(
+                  '/edu-app/userPaymentOrder/cancelPayment/' + data.orderNo
+                );
+              } catch {
+                //
+              }
+            });
+        }
+      } catch {
+        //
+      }
+    },
+    calcSalePrice(item: any) {
+      // discount
+      if (item.discount === 1) {
+        const tempPrice = Number(
+          (item.salePrice - item.discountPrice).toFixed(2)
+        );
+        return tempPrice >= 0 ? tempPrice : 0;
+      }
+      return item.salePrice;
+    },
+    // 购买
+    async onSubmit() {
+      try {
+        const selectMember = this.selectMember;
+        const params: any = [
+          {
+            goodsId: selectMember.id,
+            goodsNum: 1,
+            goodsType: 'VIP',
+            paymentCashAmount: selectMember.salePrice, // 现金支付金额
+            paymentCouponAmount: 0 // 优惠券金额
+          }
+        ]; // 支付参数
+
+        // 创建订单
+        const { data } = await request.post(
+          '/edu-app/userPaymentOrder/executeOrder',
+          {
+            data: {
+              orderType: 'VIP',
+              paymentCashAmount: this.selectMember.salePrice || 0,
+              paymentCouponAmount: 0,
+              goodsInfos: params,
+              orderName: '数字化乐器学练工具',
+              orderDesc: '数字化乐器学练工具'
+            }
+          }
+        );
+
+        console.log(data);
+        const res = await request.get(
+          '/edu-app/userPaymentOrder/detail/' + data.orderNo
+        );
+        if (res.data.status !== 'WAIT_PAY' && res.data.status !== 'PAYING') {
+          this.$router.push({
+            path: '/payment-result',
+            query: {
+              orderNo: data.orderNo
+            }
+          });
+        } else {
+          this.$router.push({
+            path: '/orderDetail',
+            query: {
+              config: JSON.stringify(data.paymentConfig),
+              orderNo: data.orderNo
+            }
+          });
+        }
+      } catch (e: any) {
+        //
+        console.log(e);
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles['member-center']}>
+        <OHeader
+          background={this.background}
+          color={this.color}
+          border={false}
+        />
+        <div class={styles.member_container}>
+          <Cell
+            class={[styles.userMember]}
+            labelClass={styles.timeRemaining}
+            center
+            v-slots={{
+              icon: () => (
+                <div class={styles.userImgSection}>
+                  <Image
+                    class={styles.userImg}
+                    src={this.userInfo.avatar || iconStudent}
+                    fit="cover"
+                  />
+                </div>
+              ),
+              title: () => (
+                <div class={styles.userInfo}>
+                  <span class={styles.name}>{this.userInfo.username}</span>
+                  {!!this.userInfo.isVip && (
+                    <Image
+                      class={styles.level}
+                      src="https://daya.ks3-cn-beijing.ksyun.com/202107/ScSTL1D.png"
+                    />
+                  )}
+                  {this.userInfo.phone && (
+                    <span
+                      class={styles.phone}
+                      v-html={`(${this.userInfo.phone})`}></span>
+                  )}
+                </div>
+              ),
+              label: () => (
+                <div class={styles.member_time}>
+                  <>
+                    {this.userInfo.isVip ? (
+                      <div>
+                        使用有效期剩余
+                        <span class={styles.remaining}>
+                          {this.userInfo.membershipDays}
+                        </span>
+                        天
+                      </div>
+                    ) : (
+                      <div>亲,您还不是会员哟</div>
+                    )}
+                  </>
+                </div>
+              )
+            }}></Cell>
+        </div>
+
+        <div class={[styles.memberContainer]}>
+          <div class={styles.memberItem}>
+            <p class={[styles.title]}>
+              <strong>数字化</strong>乐器学练工具
+              <span>12个月</span>
+            </p>
+            <div class={styles.priceGroup}>
+              <p class={styles.price}>
+                <span>¥</span>
+                {moneyFormat(this.selectMember.salePrice)}
+              </p>
+              <del class={styles.originalPrice}>
+                ¥{moneyFormat(this.selectMember.originalPrice)}
+              </del>
+            </div>
+          </div>
+          <div class={styles.memberImgs}>
+            <img src={member1} />
+            <img src={member2} />
+          </div>
+        </div>
+
+        <div class={styles.btnGroup}>
+          <div class={styles.priceSection}>
+            支付金额:
+            <div class={styles.price}>
+              <span class={styles.priceUnit}>¥</span>
+              <span class={styles.priceNum}>
+                {moneyFormat(this.calcSalePrice(this.selectMember) || 0)}
+              </span>
+            </div>
+          </div>
+          {this.userInfo.id ? (
+            <Button round class={styles.btn} onClick={this.onSubmit}>
+              立即领取
+            </Button>
+          ) : (
+            ''
+          )}
+        </div>
+
+        <ODialog
+          v-model:show={this.memberStatus}
+          title="待激活团练宝"
+          message="为让团员有效使用乐团学习工具,首次加入乐团且购买团练宝的团员,团练宝的生效时间为乐团首次训练之日,具体训练时间可查看课表。"
+          messageAlign="left"
+          dialogMarginTop="env(safe-area-inset-top)"
+          confirmButtonText="我知道了"
+        />
+      </div>
+    );
+  }
+});

+ 44 - 0
src/views/preview-protocol/cash-protocol.tsx

@@ -0,0 +1,44 @@
+import OHeader from '@/components/o-header'
+import request from '@/helpers/request'
+import { browser } from '@/helpers/utils'
+import { state } from '@/state'
+import { defineComponent } from 'vue'
+
+// 预览协议 - 原生实名认证使用
+export default defineComponent({
+  name: 'preview-protocol',
+  data() {
+    return {
+      protocolHTML: '' as any
+    }
+  },
+  async mounted() {
+    try {
+      // 判断是否有协议内容
+      if (!this.protocolHTML) {
+        const { data } = await request.get(
+          state.platformApi + '/open/userContractRecord/queryLatestContractTemplate',
+          {
+            params: {
+              contractType: 'WITHDRAW'
+            }
+          }
+        )
+        this.protocolHTML = data.contractTemplateContent || ''
+      }
+    } catch {
+      //
+    }
+  },
+  render() {
+    return (
+      <div id="mProtocol">
+        {browser().isApp && <OHeader />}
+        <div
+          style="font-size: 14px;padding: 12px;color: #333;line-height: 1.4;"
+          v-html={this.protocolHTML}
+        ></div>
+      </div>
+    )
+  }
+})

+ 27 - 0
src/views/preview-protocol/index.module.less

@@ -0,0 +1,27 @@
+.mProtocol {
+  // font-size: 14px;padding: 12px;color: #333;line-height: 1.4;
+  padding: 0 12px 12px;
+  font-size: 14px;
+  line-height: 1.5;
+  text-align: justify;
+  word-break: break-all;
+
+  strong {
+    display: block;
+    font-size: 16px;
+    font-weight: bold;
+    padding-top: 12px;
+  }
+
+  p:last-child {
+    padding-top: 12px;
+    text-align: right;
+  }
+}
+
+.LABOR_TEACHER {
+  p:last-child {
+    padding-top: 12px;
+    text-align: left;
+  }
+}

+ 49 - 0
src/views/preview-protocol/index.tsx

@@ -0,0 +1,49 @@
+import MHeader from '@/components/m-header';
+import request from '@/helpers/request';
+import { browser } from '@/helpers/utils';
+import { state } from '@/state';
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+
+// 预览协议 - 原生实名认证使用
+export default defineComponent({
+  name: 'preview-protocol',
+  data() {
+    return {
+      protocolHTML: '' as any
+    };
+  },
+  async mounted() {
+    let type = 'REGISTER';
+
+    try {
+      // 判断是否有协议内容
+      if (!this.protocolHTML) {
+        const { data } = await request.get(
+          '/edu-app/open/userContractRecord/queryLatestContractTemplate',
+          {
+            params: {
+              contractType: type || 'REGISTER'
+            }
+          }
+        );
+        this.protocolHTML = data.contractTemplateContent || '';
+      }
+    } catch {
+      //
+    }
+  },
+  render() {
+    return (
+      <div id="mProtocol">
+        {browser().isApp && <MHeader />}
+        <div
+          class={[
+            styles.mProtocol,
+            this.$route.query.type ? styles[this.$route.query.type as any] : ''
+          ]}
+          v-html={this.protocolHTML}></div>
+      </div>
+    );
+  }
+});

+ 66 - 0
src/views/preview-protocol/privacy.module.less

@@ -0,0 +1,66 @@
+.container {
+  padding: 22px 12px 30px;
+  font-size: 14px;
+  line-height: 1.5;
+  text-align: justify;
+  word-break: break-all;
+  h1 {
+    font-size: 16px;
+    text-align: center;
+  }
+
+  h2 {
+    font-size: 16px;
+    font-weight: bold;
+    // padding-top: 15px;
+  }
+  h3 {
+    font-size: 14px;
+    font-weight: bold;
+  }
+
+  .signature {
+    display: flex;
+    padding-top: 5px;
+    .sign {
+      flex: 1;
+      position: relative;
+    }
+    span {
+      display: block;
+      padding-left: 20px;
+    }
+
+    .cachet {
+      position: absolute;
+      top: -60px;
+      left: 0;
+      width: 15px;
+      height: 15px;
+    }
+  }
+}
+
+.iInfo {
+  display: flex;
+  span {
+    flex: 1;
+  }
+}
+
+.btnback {
+  display: inline-block;
+  font-size: 18px;
+  color: #fff;
+  background: #f1111b;
+  border-radius: 4px;
+  -webkit-box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
+  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
+  padding: 8px 0;
+  margin-top: 80px;
+  width: 100%;
+  text-align: center;
+}
+.line {
+  border-bottom: 1px solid #000;
+}

+ 473 - 0
src/views/preview-protocol/privacy.tsx

@@ -0,0 +1,473 @@
+import OHeader from '@/components/m-header';
+import { browser } from '@/helpers/utils';
+import { defineComponent } from 'vue';
+import styles from './privacy.module.less';
+
+export default defineComponent({
+  name: 'privacy-protocol',
+  data() {
+    const query = this.$route.query;
+    return {
+      showHeader: query.showHeader || '0',
+      type: query.type || '',
+      name: '课堂乐器'
+    };
+  },
+  mounted() {
+    document.title = this.name + '隐私协议';
+  },
+  render() {
+    return (
+      <>
+        {browser().isApp && <OHeader />}
+        <div class={styles.container}>
+          <h2 style="font-size: 16px;font-weight: bold; ">
+            《{this.name}隐私协议》
+          </h2>
+          引言
+          <br />
+          {this.name}是由武汉乐小雅网络科技有限公司(以下简称“我们”)提供给学校
+          {this.name}
+          使用的乐团双师训练平台。我们十分重视用户的个人信息和数据。您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。本《隐私政策》与您所使用的
+          {this.name}
+          功能息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。
+          <br />
+          您登陆使用或缴费使用我们的付费功能,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。
+          <br />
+          <br />
+          <h2>我们如何收集信息</h2>
+          您在使用我们的产品与/或功能时,我们需要/可能需要收集和使用您的一些个人信息,我们收集和使用的您的个人信息类型包括两种: 第一种:我们产品与/或功能必需使用/呈现您的信息:此类信息为产品与/或功能正常运行的必备信息,您须授权我们收集。如您拒绝提供,您将无法正常使用我们的产品与/或功能 第二种:我们开展线下乐团活动时,因乐团的呈现或宣传必须使用/呈现您的信息。如您拒绝提供,不影响我们的产品/或功能的正常使用。
+          <br />
+          我们对您的信息承担保密义务,除以下情形外,未经您同意,我们以及我们的关联公司不会与任何第三方分享您的个人信息:
+          <br />
+          1.极光推送 SDK(https://www.jiguang.cn/) <br />
+          提供方名称:极光 <br />
+          场景描述:针对App的业务实现推送功能 <br />
+          收集方式:调用系统相关接口自动采集
+          <br />
+          个人信息类型:设备信息、网络信息与位置信息、应用信息
+          <br />
+          个人信息字段范围:(最终用户的硬件型号、硬件序列号操作系统版本、设备配置、唯一设备标识符、国际移动设备身份码IMEI/MEID、SIM卡信息IMSI、iOS系统广告标识符(IDFA)、安卓系统广告标识符(OAID)、Android
+          Id、设备Mac地址)、设备位置信息(通过GPS、蓝牙或Wi-Fi信号获得的位置信息)以及设备状态信息(如设备应用安装列表)、应用信息(应用崩溃信息、通知开关状态、软件安装列表、运行中的进程、传感器信息等相关信息)
+          <br />
+          用途或目的:用于统计详细崩溃日志以及行为分析。
+          <br />
+          网络信息:IP地址、WiFi信息、基站信息等相关信息
+          <br />
+          用途或目的:消息推送服务 <br />
+          是否为必要信息:是
+          <br />
+          信息处理方式:采用去标识化方式对个人信息进行脱敏展示 <br />
+          隐私政策: https://www.jiguang.cn/license/privacy
+          <br />
+          我们的产品集成极光SDK,极光SDK需要采集设备标识符(IMEI/Mac/android
+          ID/IDFA/OPENUDID/GUID/SIM 卡 IMSI
+          信息/ICCID),用于唯一标识设备,以便向目标设备推送消息,实现精准推送,提供推送统计和分类,反作弊服务。
+          <br />
+          2.腾讯Bugly SDK
+          <br />
+          提供方名称:腾讯
+          <br />
+          场景描述:针对App的各维度的统计功能
+          <br />
+          收集方式:调用系统相关接口自动采集
+          <br />
+          个人信息类型:设备信息、设备连接信息、日志信息、粗略位置信息
+          <br />
+          个人信息字段范围:(最终用户的硬件型号、硬件序列号操作系统版本、设备配置、唯一设备标识符、国际移动设备身份码IMEI/MEID、SIM卡信息IMSI、iOS系统广告标识符(IDFA)、安卓系统广告标识符(OAID)、Android
+          Id、设备Mac地址)、设备位置信息(通过GPS、蓝牙或Wi-Fi信号获得的位置信息)以及设备状态信息(如设备应用安装列表)、应用信息(应用崩溃信息、通知开关状态、软件安装列表、运行中的进程、传感器信息等相关信息)、设备参数及系统信息(设备类型、设备型号、操作系统及硬件相关信息)
+          <br />
+          网络信息:IP地址、WiFi信息、基站信息等相关信息
+          <br />
+          用途或目的:用于统计详细崩溃日志以及行为分析。
+          <br />
+          是否为必要信息:是
+          <br />
+          信息处理方式:采用去标识化方式对个人信息进行脱敏展示
+          <br />
+          隐私政策:
+          https://privacy.qq.com/document/priview/fbd2c3f898df4c1c869925dd49d57827
+          <br />
+          3.支付宝SDK
+          <br />
+          提供方:支付宝(中国)网络技术有限公司
+          <br />
+          场景描述:登录、支付。
+          <br />
+          收集方式:调用系统相关接口自动采集
+          <br />
+          个人信息类型:设备信息、位置信息、网络信息、个人通信信息
+          <br />
+          个人信息字段:设备信息:设备标识符(IMEI、IDFA、Android
+          ID、MAC、OAID等相关信息)、应用信息(应用崩溃信息、通知开关状态、软件列表等相关信息)、设备参数及系统信息(设备类型、设备型号、操作系统及硬件相关信息)
+          <br />
+          网络信息:IP地址,WiFi信息,基站信息等相关信息
+          <br />
+          用途或目的:为了让您更安全、便捷地登录本应用,可以选择使用支付宝第三方账号授权登录本应用,方便购买本应用中的付费服务。
+          <br />
+          是否为必要信息:是
+          <br />
+          信息处理方式:采用去标识化方式对个人信息进行脱敏展示
+          <br />
+          隐私政策: https://render.alipay.com/p/c/k2cx0tg8
+          <br />
+          4.微信SDK
+          <br />
+          提供方:腾讯
+          <br />
+          场景描述:登录、支付。
+          <br />
+          收集方式:调用系统相关接口自动采集
+          <br />
+          个人信息类型:个人常用设备信息、位置信息、网络信息、个人通信信息
+          <br />
+          个人信息字段:设备信息:设备标识符(IMEI、IDFA、Android
+          ID、MAC、OAID等相关信息)、应用信息(应用崩溃信息、通知开关状态、软件列表等相关信息)
+          <br />
+          网络信息:IP地址,WiFi信息,基站信息等相关信息
+          <br />
+          用途或目的:为了让您更安全、便捷地登录本应用,可以选择使用支付宝第三方账号授权登录本应用,方便购买本应用中的付费服务。
+          <br />
+          是否为必要信息:是
+          <br />
+          信息处理方式:采用去标识化方式对个人信息进行脱敏展示
+          <br />
+          隐私政策:
+          https://open.weixin.qq.com/cgi-bin/frame?t=news/protocol_developer_tmpl
+          <br />
+          5.友盟SDK
+          <br />
+          服务类型:统计分析及分享
+          <br /> 收集个人信息类型:设备信息(IMEI/MAC/Android
+          ID/IDFA/OpenUDID/GUID/SIM卡IMSI/地理位置等)
+          <br />
+          隐私政策:https://www.umeng.com/page/policy <br />
+          用于唯一标识设备,以便向目标设备分享消息。采集地理位置甄别分享通道,提供反作弊服务。
+          <br />
+          (一)账号注册/登录功能
+          <br />
+          当您使用账号注册功能时,我们会收集由您主动提供给我们的一些单独或者结合识别您实名身份的信息,包含但不限于:姓名、性别、学校、年级、所在地、手机号码、验证码匹配结果等,并创建密码。您的密码将以加密形式进行自动存储、传输、验证,我们不会以明文方式存储、传输、验证您的密码。您在保管、输入、使用您的密码时,应当对物理环境、电子环境审慎评估,以防密码外泄。我们收集这些信息是用以完成注册程序、为您持续稳定提供专属于注册用户的产品与/或功能,并保护您的账号安全。您应知悉,手机号码和验证码匹配结果属于您的个人敏感信息,我们收集该类信息是为了满足相关法律法规的要求,如您拒绝提供可能导致您无法使用我们的此功能,请您谨慎考虑后再提供。
+          <br />
+          需要说明的是,我们的一些产品或功能支持您使用第三方平台的账号(包含但不限于微信、支付宝等)进行登录,如您使用第三方平台的账号登录的,我们将根据您的授权获取该第三方账号下的相关信息(包括:昵称、头像、所在地等,具体以您的授权内容为准)以及身份验证信息(个人敏感信息)。我们收集这些信息是用于为您提供账号登录使用、账号功能以及保障您的账号安全,防范安全风险。如您拒绝授权此类信息的,您将无法使用第三方平台的账号登录我们平台,但不影响我们为您提供的其他产品和功能的正常使用。
+          <br />
+          (二)内容展示/浏览/播放/储存/信息查阅/通讯/练习等功能
+          <br />
+          我们的基础功能使用权益:查看课表、查看课后作业要求、阶段自测、请假、通讯、相册、乐团资讯(不含收费功能-团练宝的使用权限)。
+          <br />
+          收费功能(包含但不限于团练宝)其使用权益:五线谱跟播、演奏指法跟播、电子节拍器/校音、曲目选段练习、原音/伴奏一键切换、演奏速度调整、训练声部/合奏电子曲谱、云教程回放、作业中的知识点与相关内容的观看和使用、单元测与测评报告、智能练习测评与报告、即时练习数据统计、练习成长数据报告、练习PK排行榜、测评音视频云储存功能等。
+          <br />
+          我们的产品与/或功能为您提供内容展示、浏览、播放、储存、信息查阅、通讯等功能,在此过程中,我们会根据您使用我们产品与/或功能的具体操作收集您的一些信息,包括如下个人信息:
+          <br />
+          设备信息:包括设备MAC地址、唯一设备识别码、登录IP地址、设备型号、设备名称、设备标识、浏览器类型和设置、语言设置、操作系统和应用程序版本、接入网络的方式、网络质量数据、移动网络信息(包括运营商名称)、产品版本号、设备所在位置相关信息(包括您授权我们获取的地理位置信息)。为了收集上述基本的个人设备信息,我们将会申请访问您的设备信息的权限并根据您的授权获取相关信息。
+          <br />
+          在您观看视频时,为给您提供良好的观看体验,我们会调用您的加速度、方向传感器,通过您的传感器信息(单独的传感器信息非个人信息)检测设备屏幕方向变化,进行视频播放中的横竖屏切换。当您使用全景视频与沉浸模式时,我们会调用您的磁场、陀螺仪传感器,检测您的手机移动和旋转行为,定位视频画面的区域。
+          <br />
+          投屏播放:当您使用投屏播放视频内容时,投屏功能是使用手机设备本身的镜像功能或第三方软件实现投屏,我们需要进行查找并连接到本地网络上设备,收集您的网络信息、WLAN接入点(如SSID、BSSID)及TV端硬件设备名称、厂商、设备型号,以确认您的移动客户端硬件设备与TV端硬件设备在同一局域网内,实现硬件设备互联互通及完成效果适配。
+          <br />
+          日志信息:当您使用我们的产品与/或功能时,我们会自动收集您的个人上网记录,并作为有关操作日志、服务日志保存,包括您的浏览记录、练习记录、点赞/分享/评论/互动等记录、完成自测/测评音视频云储存记录、播放记录、播放时长、访问日期和时间等。
+          <br />
+          获取运行中的进程:当您使用我们的产品与/或功能时,我们会不定期获取当前设备正在运行中的进程,以用来判定本App是否在前台运行,从而使App可以正常开展业务逻辑。获取的进程信息,仅用来做App是否在前台运行的判定,不会对该信息进行存储、上传、转发操作。
+          <br />
+          位置信息:附近的人,您通过具有定位功能的移动设备使用我们的功能时,通过GPS或WiFi方式收集的您的地理位置信息;您或其他用户提供的包含您所处地理位置的实时信息,例如您提供的账户信息中包含的您所在地区信息,您或其他人上传的显示您当前或曾经所处地理位置的共享信息,您或其他人共享的照片包含的地理标记信息;您可以通过关闭定位功能,停止对您的地理位置信息的收集。
+          <br />
+          录音信息:当前App的核心功能是通过录音信息分析来获取频率信息,所以我们需要获取您的设备录音(麦克风)的相关权限,以用来完成当前声音频率的识别。如果您不允许App使用录音权限,该App将无法正常使用。App获取录音信息后仅用于声音分析,不会对录音信息进行存储、上传操作。
+          <br />
+          软件安装列表:近期出现不法分子通过反编译、套壳一些非法手段生成盗版的“
+          {this.name}
+          ”App,可能会导致您的个人信息泄露,个人权益受损。获取软件安装列表是为了从安装列表中获取到“
+          {this.name}
+          ”App的安装包名,包体大小,以此来判定app是否已被盗版攻击,以更好的保护您的个人权益和隐私。
+          <br />
+          我们收集这些信息是为了向您提供我们双师乐团训练的内容展示/播放/管理/训练与课后练习等功能,如您拒绝提供上述信息和/或权限将可能导致您无法使用我们的产品与服务。请您知悉,单独的设备信息、日志信息无法识别您的身份信息。
+          <br />
+          (三)信息统计、消息推送、分享、支付
+          <br />
+          为了完成支付、统计、推送、地图,分享功能,我们还集成了其他的SDK,如您在我们平台上使用这类由第三方提供的服务时,您同意将由其直接收集和处理您的信息(如以嵌入代码、插件形式)。目前我们产品中包含的第三方SDK服务以及隐私政策详情请参阅《
+          {this.name}第三方SDK目录及其隐私政策》。 <br />
+          小米推送 SDK
+          <br />
+          涉及的个人信息类型:设备标识符(如 Android ID、OAID、GAID)、设备信息
+          <br />
+          使用目的:推送消息 使用场景:在小米手机终端推送消息时使用
+          <br />
+          第三方主体:北京小米移动软件有限公司
+          数据处理方式:通过去标识化、加密传输及其他安全方式
+          <br />
+          官网链接:https://dev.mi.com/console/appservice/push.html
+          <br />
+          隐私政策:https://dev.mi.com/console/doc/detail?pId=1822
+          <br />
+          华为 HMS SDK <br />
+          涉及的个人信息类型:应用基本信息、应用内设备标识符、设备的硬件信息、系统基本信息和系统设置信息
+          <br />
+          使用目的:推送消息 <br />
+          使用场景:在华为手机终端推送消息时使用
+          第三方主体:华为软件技术有限公司 <br />
+          数据处理方式:通过去标识化、加密传输及其他安全方式 <br />
+          官网链接:https://developer.huawei.com/consumer/cn/ <br />
+          隐私政策:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/sdk-data-security-0000001050042177{' '}
+          <br />
+          OPPO 推送 SDK
+          <br />
+          涉及的个人信息类型:设备标识符(如 IMEI、ICCID、IMSI、Android
+          ID、GAID)、应用信息(如应用包名、版本号和运行状态)、网络信息(如 IP
+          或域名连接结果,当前网络类型)
+          <br />
+          使用目的:推送消息
+          <br />
+          使用场景:在 OPPO 手机终端推送消息时使用
+          <br />
+          第三方主体:广东欢太科技有限公司
+          <br />
+          数据处理方式:通过加密传输和处理的安全处理方式
+          <br />
+          官网链接:https://open.oppomobile.com/new/introduction?page_name=oppopush
+          <br />
+          隐私政策:https://open.oppomobile.com/wiki/doc#id=10288
+          <br />
+          vivo 推送 SDK
+          <br />
+          涉及的个人信息类型:设备信息
+          <br />
+          使用目的:推送消息
+          <br />
+          使用场景:在 vivo 手机终端推送消息时使用
+          <br />
+          第三方主体:广东天宸网络科技有限公司及将来受让运营 vivo 开放平台的公司
+          <br />
+          数据处理方式:通过去标识化、加密传输及其他安全方式
+          <br />
+          官网链接:https://dev.vivo.com.cn/promote/pushNews
+          <br />
+          隐私政策:https://www.vivo.com.cn/about-vivo/privacy-policy
+          <br />
+          新浪微博
+          <br />
+          使用目的: 登录、分享
+          <br />
+          数据类型: 存储的个人文件、网络信息
+          <br />
+          官网链接: https://weibo.com/signup/v5/privacy
+          <br />
+          高德开放平台SDK
+          <br />
+          使用目的: 实现定位/展现地图/导航
+          <br />
+          数据类型: 存储的个人文件、位置信息、读取手机状态和身份、网络信息
+          <br />
+          官网链接:
+          https://lbs.amap.com/compliance-center/check-and-reference/compliance
+          <br />
+          (四)信息制作、发布、上传、交流互动功能
+          <br />
+          当您在我们的部分产品与/或功能中上传、发布等平台内交流互动、评论、分享、点赞等功能时,除注册登录账户之外,您可能会主动提供相关图文/视频内容、互动信息(包括但不限于帖子、点赞/评论/分享/交流等互动信息)。我们会自动收集您的上述信息,并展示您的昵称、头像、学校、年级、声部、所在地等发布的信息内容。请您知悉,您发布的信息中可能包含他人的个人信息,请您务必取得他人的合法授权,避免非法泄露他人的个人信息。如您不同意我们收集上述信息,您将无法使用我们的相关的信息发布或练习成长数据报告、练习PK排行榜功能,但不影响您使用我们为您提供的其他产品和功能。
+          <br />
+          摄像头权限:当您需要上传个人信息:包含但不限于头像、图片、视频时,拍照功能需要调取您的摄像头权限,如果您拒绝授权,可能会影响您上传个人信息:头像、图片以及视频等,由此带来的不便请您理解。
+          <br />
+          (五)下单与交付
+          <br />
+          当您在我们的产品与/或功能中购买需要收费的商品或功能的,我们需要根据商品或功能类型收集如下部分或全部的个人信息(务必确保真实性、有效性),包括:交易商品、功能信息、收货人信息(包含但不限收货人姓名、邮编、收货地址、联系电话、发票信息的。若收件人非您本人的,您应当确保已经获得收货人相关授权)(个人敏感信息)、交易金额、下单时间、订单商户、订单编号、订单状态、支付机构、支付方式、支付账号、支付状态(个人敏感信息)、银行卡绑定与核算(如相关功能需提供的,务必与您的实名信息一致的银行卡信息)等,我们收集这些信息是为了帮助您顺利完成交易、配送,保障您的交易安全、查询订单信息、提供客户服务。
+          <br />
+          (六)相册使用
+          <br />
+          您上传/下载照片或视频时,我们将会申请访问您的设备信息的权限(相机、相册等相关的图片信息)并根据您的授权获取相关信息。
+          <br />
+          (七)训练与练习
+          <br />
+          乐团训练与练习会使用到相关课件或云教程或团练宝(包含但不限于视频、曲目播放及练习等功能)时,我们将会申请访问您的设备信息的权限(麦克风、摄像头等)并根据您的授权获取相关信息。
+          <br />
+          (八)消息通知
+          <br />
+          我们会通过您的设备的系统通知(PUSH通知),向您推送系统更新、乐团状态、乐团资讯、练习信息、练习功能使用、课程信息及管理老师、乐团学员、伴学指导用户动态和您可能感兴趣的资讯等。为了实现上述目的所必需,确保应用处于关闭或后台运行状态下正常接收到系统通知(PUSH通知),我们可能会在合理频率范围内自启动。对于您在使用过程中提供的您的联系方式(包含但不限于联系电话),我们在运营中可能会向其中的一种或多种发送多类通知,用于用户消息告知、身份验证、安全验证、用户使用体验调研等用途。
+          <br />
+          (九)除本政策外,在特定场景下,我们将以及时告知的方式(包括但不限于更新本政策、重新签署文件、页面提示、弹窗、系统通知、系统消息、邮件、系统公告或其他便于您获知的方式)另行向您详细说明对应信息的处理目的、方式、范围等规则,并在征得您的授权同意后处理(如适用)。
+          <br />
+          (十)客服、其他用户响应功能
+          <br />
+          当您与我们的客服互动时或使用其他用户响应功能时(包括:在线提交意见反馈、与在线/人工客服沟通、提出我们的产品与/或功能的售后申请、行使您的相关个人信息控制权、其他客户投诉和需求),为了您的账号与系统安全,我们可能需要您先行提供账号信息,并与您之前的个人信息相匹配以验证您的用户身份。在您使用客服或其他用户响应功能时,我们可能还会需要收集您的如下个人敏感信息:联系方式(您与我们联系时使用的电话号码/电子邮箱或您向我们主动提供的其他联系方式)、您与我们的沟通信息(包括文字/图片/音视频/通话记录形式)、与您需求相关联的其他必要信息。我们收集这些信息是为了调查事实与帮助您解决问题,如您拒绝提供可能导致您无法使用我们的客服用户响应机制。
+          <br />
+          (十一)产品安全保障功能
+          <br />
+          我们需要收集您的一些信息来保障您使用我们的产品与/或功能时的账号与系统安全,并协助提升我们的产品与/功能的安全性和可靠性,以防产生任何危害用户、
+          {this.name}
+          、社会的行为,包括您的如下个人信息:账号登录地、个人常用设备信息(例如:硬件型号、设备MAC地址、IMEI、IMSI)、登录IP地址、产品版本号、语言模式、浏览记录、网络使用习惯、服务故障信息,以及个人敏感信息:交易信息、会员实名认证信息,日志信息(包括:正在安装的应用信息或正在运行的进程信息、应用程序的总体运行、使用情况与频率、应用崩溃情况、总体安装使用情况、性能数据、应用来源)等。我们会根据上述信息来综合判断您账号、账户及交易风险、进行身份验证、客户服务、检测及防范安全事件、诈骗监测、存档和备份用途,并依法采取必要的记录、审计、分析、处置措施,一旦我们检测出存在或疑似存在账号安全风险时,我们会使用相关信息进行安全验证与风险排除,确保我们向您提供的产品和服务的安全性,以用来保障您的权益不受侵害。同时,当发生账号或系统安全问题时,我们会收集这些信息来优化我们的产品和服务。
+          <br />
+          此外,为确保您设备操作环境的安全以及提供我们的产品与/或功能所必需,防止恶意程序和反作弊,我们会在您同意本《隐私政策》后获取您设备上已安装或正在运行的必要的应用/软件列表信息(包括应用/软件来源、应用/软件总体运行情况、崩溃情况、使用频率)。请您知悉,单独的应用/软件列表信息无法识别您的特定身份。
+          <br />
+          例外情形,另外,您充分理解并同意,我们在以下情况下收集、使用您的个人信息无需您的授权同意:
+          <br />
+          与我们履行法律法规规定的义务相关的; 与国家安全、国防安全直接相关的;
+          <br />
+          与公共安全、公共卫生、重大公共利益直接相关的;
+          <br />
+          与犯罪侦查、起诉、审判和判决执行直接相关的;
+          <br />
+          出于维护您或其他个人的生命、财产重大合法权益但又很难得到您本人同意的;
+          <br />
+          所收集的信息是您自行向社会公开的或者是从合法公开的渠道(如合法的新闻报道、政府信息公开渠道)中收集到的;
+          <br />
+          根据与您签订和履行相关协议或其他书面文件所必需的;
+          <br />
+          用于维护我们的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或功能的故障
+          <br />
+          有权机关的要求、法律法规规定的其他情形。
+          <br />
+          <br />
+          <h2>我们如何使用Cookie和同类技术</h2>
+          (一)关于Cookie和同类技术
+          <br />
+          Cookie是包含字符串的小文件,在您登入和使用网站或其他网络内容时发送、存放在您的计算机、移动设备或其他装置内(通常经过加密)。Cookie同类技术是可用于与Cookie类似用途的其他技术,例如:Web
+          Beacon、Proxy、嵌入式脚本。
+          <br />
+          目前,我们主要使用Cookie收集您的个人信息。您知悉并同意,随着技术的发展和我们产品和服务的进一步完善,我们也可能会使用Cookie同类技术
+          <br />
+          (二)我们如何使用Cookie和同类技术
+          <br />
+          在您使用我们的产品与/或功能时,我们可能会使用Cookie和同类技术收集您的一些个人信息,包括:您访问网站的习惯、您的浏览信息、您的登录信息
+          <br />
+          如果您的浏览器允许,您可以通过您的浏览器的设置以管理、(部分/全部)拒绝Cookie与/或同类技术;或删除已经储存在您的计算机、移动设备或其他装置内的Cookie与/或同类技术,从而实现我们无法全部或部分追踪您的个人信息。您如需详细了解如何更改浏览器设置,请具体查看您使用的浏览器的相关设置页面。您理解并知悉:我们的某些产品/功能只能通过使用Cookie或同类技术才可得到实现,如您拒绝使用或删除的,您可能将无法正常使用我们的相关产品与/或服务或无法通过我们的产品与/或服务获得最佳的服务体验,同时也可能会对您的信息保护和账号安全性造成一定的影响。
+          <br />
+          我们如何共享、转让、公开披露您的个人信息
+          <br />
+          除以下情形外,未经您同意,我们以及我们的关联公司不会与任何第三方分享您的个人信息:
+          <br />
+          我们以及我们的关联公司,可能将您的个人信息与我们的关联公司、合作伙伴及第三方服务供应商、承包商及代理(例如代表我们发出电子邮件或推送通知的通讯服务提供商、为我们提供位置数据的地图服务供应商)分享(他们可能并非位于您所在的法域),用作下列用途:
+          <br />
+          向您提供我们的功能使用权限; 实现“我们可能如何使用信息”部分所述目的;
+          <br />
+          履行我们在《{this.name}
+          使用协议》或本《隐私政策》中的义务和行使我们的权利;
+          <br />
+          理解、维护和改善我们的服务。
+          <br />
+          如我们或我们的关联公司与任何上述第三方分享您的个人信息,我们将努力确保该第三方在使用您的个人信息时遵守本《隐私政策》及我们要求其遵守的其他适当的保密和安全措施。
+          <br />
+          随着我们业务的持续发展,我们以及我们的关联公司有可能进行合并、收购、资产转让或类似的交易,您的个人信息有可能作为此类交易的一部分而被转移。我们将在转移前通知您。
+          <br />
+          我们或我们的关联公司还可能为以下需要而保留、保存或披露您的个人信息:
+          遵守适用的法律法规;
+          <br />
+          遵守法院命令或其他法律程序的规定; 遵守相关政府机关的要求;
+          <br />
+          为遵守适用的法律法规、维护社会公共利益,或保护我们的客户、我们或我们的集团公司、其他用户或雇员的人身和财产安全或合法权益所合理必需的用途。
+          <br />
+          <br />
+          <h2>您对个人信息享有的控制权</h2>
+          您对我们产品与/或您使用功能中,您的个人信息享有多种方式的控制权,包括:您可以访问、更正/修改、删除您的个人信息,也可以撤回之前作出的对您个人信息的同意,同时您也可以注销您的账号。为便于您行使您的上述控制权,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以自行进行操作,如您在操作过程中有疑惑或困难的可以通过文末的方式联系我们来进行控制,我们会及时为您处理。
+          <br />
+          (一)访问权
+          您可以在我们的产品与/或您在使用功能中查询或访问您的相关个人信息,包括:
+          <br />
+          账号信息:您可以通过相关产品页面随时登录您的个人账号,随时查询或访问您的账号中的个人资料信息,包括:头像、昵称、性别、个性签名等。
+          <br />
+          使用信息:您可以通过相关产品页面随时访问您的使用信息,包括:观看历史、作业信息、练习记录、订单信息等。
+          <br />
+          其他信息:如您在此访问过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容,您可通过文末提供的方式联系我们,我们将在核实您的身份后在合理期限内向您提供,但法律法规另有规定的或本政策另有约定的除外。
+          <br />
+          (二)更正/修改权
+          <br />
+          您可以在我们的产品与/或您在使用功能中更正/修改您的相关个人信息。为便于您行使您的上述权利,我们为您提供了在线自行更正/修改和向我们提出更正/修改申请两种方式。
+          <br />
+          对于您的部分个人信息,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以直接进行更正/修改,例如:“头像/昵称/性别/个性签名”信息在“手机端APP”中的更正/修改路径为:我的—设置;
+          <br />
+          对于您在行使上述权利过程中遇到的困难,或其他可能未/无法向您提供在线自行更正/修改权限的,
+          <br />
+          经对您的身份进行验证,且更正不影响信息的客观性和准确性的情况下,您有权对错误或不完整的信息作出更正或修改,或在特定情况下,尤其是数据错误时,通过我们公布的反馈与报错措施将您的更正/修改申请提交给我们,要求我们更正或修改您的数据,但法律法规另有规定的除外。但出于安全性和身份识别的考虑,您可能无法修改注册时提交的某些初始注册信息。
+          <br />
+          (三)注销权
+          <br />
+          我们为您提供账号注销的多种途径,您可以通过在线申请注销或联系我们的客服或通过其他我们公示的方式申请注销您的账号。在您注销账号后,您将无法再以此账号登录和使用
+          {this.name}旗下的相关产品与功能;该账号在{this.name}
+          的产品与服务使用期间已产生的但未消耗完毕的权益及未来的预期利益全部权益将被清除;该账号下的内容、信息、数据、记录将会被删除或匿名化处理(但法律法规另有规定或监管部门另有要求的除外);同时,账号一旦注销超过一定时间,将无法恢复。
+          <br />
+          (四)提前获知产品与/或服务停止运营权
+          <br />
+          若因特殊原因导致我们的部分或全部产品与/或功能被迫停止运营,我们将提前在显著位置或向您发送推送消息或以其他方式通知您,并将停止对您个人信息的收集,同时在超出法律法规规定的必需且最短期限后,我们将会对所持有的您的个人信息进行删除或匿名化处理。
+          <br />
+          (五)帮助反馈权 我们为您提供了反馈渠道:可联系在线客服,帮助反馈。
+          <br />
+          <br />
+          <h2>我们如何存储和保护您的个人信息</h2>
+          (一)个人信息的存储
+          <br />
+          存储地点:我们依照法律法规的规定,将您的个人信息存储于中华人民共和国境内。目前我们不存在跨境存储您的个人信息或向境外提供个人信息的场景。如需跨境存储或向境外提供个人信息的,我们会单独向您明确告知(包括出境的目的、接收方、使用方式与范围、使用内容、安全保障措施、安全风险)并再次征得您的授权同意,并严格要求接收方按照本《隐私政策》以及法律法规相关要求来处理您的个人信息;
+          <br />
+          存储期限:我们在为提供我们的产品或功能之目的所必需且最短的期间内保留您的个人信息,例如:当您使用我们的注册登录及付费功能时,我们需要收集您的手机号码,且在您提供后并在您使用该功能期间,我们需要持续为您保留您的手机号码,以向您正常提供该功能、保障您的账号和系统安全。在超出上述存储期限后,我们会对您的个人信息进行删除或匿名化处理。但您行使删除权、注销账号的或法律法规另有规定的除外(例如:《电子商务法》规定:商品和服务信息、交易信息保存时间自交易完成之日起不少于三年)。
+          <br />
+          (二)个人信息的保护措施
+          <br />
+          我们一直都极为重视保护用户的个人信息安全,为此我们采用了符合行业标准的安全技术措施及组织和管理措施保护措施以最大程度降低您的信息被泄露、毁损、误用、非授权访问、非授权披露和更改的风险。
+          <br />
+          <br />
+          <h2>未成年人保护</h2>
+          {this.name}
+          一直非常注重对未成年人的保护,致力于践行我们的企业社会责任。
+          <br />
+          如未成年人需要使用的,应首先取得其监护人的同意(包括本政策),在监护人同意后和指导下进行使用、提交个人信息;我们希望监护人亦能积极的教育和引导未成年人增强个人信息保护意识和能力,保护未成年人个人信息安全。
+          {this.name}
+          会严格履行法律规定的未成年人保护义务与责任,我们只会在法律允许或监护人同意或保护未成年人所必要的情况下或乐团所需收集、使用、共享、转让或披露未成年人个人信息,如果您是14周岁以下儿童的,在您使用我们的功能前,请您在监护人的授权同意后,在监护人的指导下使用我们的产品与/或功能。
+          <br />
+          <br />
+          <h2>本《隐私政策》的更新</h2>
+          我们鼓励您在每次使用我们的产品或功能时都查阅我们的《隐私政策》。为了让您更好的使用我们的产品或功能,我们会根据产品或功能或乐团的需求更新情况及法律法规的相关要求更新本《隐私政策》的条款,该更新构成本《隐私政策》的一部分。如该更新造成您在本《隐私政策》下权利的实质减少或重大变更,我们将在本政策生效前通过系统提示或系统信息或向您发送推送消息或以其他方式通知您,若您继续使用我们的服务,即表示您充分阅读、理解并同意受经修订的《隐私政策》的约束。为保障您的合法权益,我们建议您可以定期在我们平台的设置页面中查看本政策。
+          <br />
+          上述的“重大变更”包括但不限于:
+          <br />
+          我们的功能或模式发生重大变化。如处理个人信息的目的、处理的个人信息的类型、个人信息的使用方式;
+          <br />
+          我们在所有权结构、组织架构方面发生重大变化。如业务调整、破产并购引起的所有者变更;
+          <br />
+          个人信息共享、转让或公开披露的主要对象发生变化;
+          <br />
+          您参与个人信息处理方面的权利及其行使方式发生重大变化;
+          <br />
+          我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化时;
+          <br />
+          个人信息安全影响评估报告表明存在高风险时;
+          <br />
+          其他重要的或可能严重影响您的个人权益的情况发生时。
+          <br />
+          <br />
+          <h2>如何联系我们</h2>
+          (一)如您对本《隐私政策》的执行或使用我们的服务时遇到的与隐私保护相关的事宜有任何问题(包括问题咨询、投诉),我们专门为您提供了反馈通道,希望为您提供满意的解决方案:
+          <br />
+          在线客服/其他在线意见反馈通道:您可与我们平台上产品功能页面的在线客服联系或者在线提交意见反馈;
+          <br />
+          (二)我们会在收到您的意见及建议后尽快向您回复,一般情况下,我们不会因此对您收取服务费。但是,在以下情形下,您理解并知悉,我们将无法响应您的请求:
+          <br />
+          与我们履行法律法规规定的义务相关的; 与国家安全、国防安全直接相关的;
+          <br />
+          与公共安全、公共卫生、重大公共利益直接相关的;
+          与犯罪侦查、起诉和审判有关的;
+          <br />
+          有充分证据表明您存在主观恶意或滥用权利的;
+          <br />
+          出于维护您或其他个人的生命、财产重大权益但又难得到本人授权同意的;
+          <br />
+          响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
+          涉及商业秘密的;
+          <br />
+          法律法规规定的其他情形。
+          <br />
+          <br />
+          <h2>其他</h2>
+          (一)本《隐私政策》的解释及争议解决均应适用中华人民共和国大陆地区法律。如就本政策的签订、履行发生任何争议的,双方应尽量友好协商解决;协商不成时,您同意将纠纷或争议提交武汉仲裁委员会仲裁。
+          <br />
+          (二)本《隐私政策》的标题仅为方便及阅读而设,并不影响正文其中任何规定的含义或解释。
+          <br />
+          <div
+            style={{
+              textAlign: 'right',
+              paddingTop: '18px'
+            }}>
+            武汉乐小雅网络科技有限公司
+          </div>
+          <br />
+          <strong>发布日期:2023-02-28</strong>
+          <br />
+          <strong>最新更新日期:2021-05-06</strong>
+        </div>
+      </>
+    );
+  }
+});

+ 66 - 12
src/views/student-register/index.tsx

@@ -8,7 +8,8 @@ import {
   Icon,
   Popup,
   showConfirmDialog,
-  showToast
+  showToast,
+  closeToast
 } from 'vant';
 import { computed, defineComponent, onMounted, reactive } from 'vue';
 import styles from './index.module.less';
@@ -20,9 +21,16 @@ import { useRoute, useRouter } from 'vue-router';
 import RegisterModal from './register-modal';
 import { useStudentRegisterStore } from '@/store/modules/student-register-store';
 import request from '@/helpers/request';
-import { moneyFormat } from '@/helpers/utils';
+import {
+  // browser,
+  // getUrlCode,
+  moneyFormat
+} from '@/helpers/utils';
 import deepClone from '@/helpers/deep-clone';
 import MDialog from '@/components/m-dialog';
+import OWxTip from '@/components/m-wx-tip';
+// import { goWechatAuth } from '@/state';
+// import qs from 'query-string';
 
 export default defineComponent({
   name: 'student-register',
@@ -43,7 +51,8 @@ export default defineComponent({
       submitLoading: false,
       dialogStatus: false,
       dialogMessage: '',
-      dialogConfig: {} as any
+      dialogConfig: {} as any,
+      code: '' as any
     });
 
     // 查询未支付订单
@@ -55,7 +64,6 @@ export default defineComponent({
           forms.dialogMessage = '您有待支付的订单,是否继续支付';
           forms.dialogStatus = true;
           forms.dialogConfig = data;
-          console.log(data, 'data');
         }
       } catch {
         //
@@ -70,7 +78,6 @@ export default defineComponent({
             noAuthorization: true // 是否请求接口的时候添加toekn
           }
         );
-
         // 默认选中商品
         studentRegisterStore.setVip(data.details || []);
         forms.details = deepClone(data.details || []);
@@ -78,6 +85,8 @@ export default defineComponent({
         forms.bugGoods = data.bugGoods;
         forms.schoolType = data.schoolType;
         forms.gradeYear = data.gradeYear;
+
+        console.log(studentRegisterStore.getGoods);
       } catch {}
     };
 
@@ -93,8 +102,8 @@ export default defineComponent({
 
       const goodsList: any[] = studentRegisterStore.getGoods;
       goodsList.forEach((good: any) => {
-        amount += Number(good.price) * good.count;
-        originAmount += Number(good.originalPrice) * good.count;
+        amount += Number(good.price) * good.quantity;
+        originAmount += Number(good.originalPrice) * good.quantity;
       });
       return {
         amount,
@@ -152,11 +161,12 @@ export default defineComponent({
 
         goodsList.forEach((goods: any) => {
           params.push({
-            goodsId: goods.id,
-            goodsNum: goods.count,
+            goodsId: goods.productId,
+            goodsNum: goods.quantity,
             goodsType: 'INSTRUMENTS',
             paymentCashAmount: goods.price, // 现金支付金额
-            paymentCouponAmount: 0 // 优惠券金额
+            paymentCouponAmount: 0, // 优惠券金额
+            goodsSkuId: goods.productSkuId
           });
         });
 
@@ -194,9 +204,44 @@ export default defineComponent({
       }
     };
 
+    // const getAppIdAndCode = async (url?: string) => {
+    //   try {
+    //     const { data } = await request.get(
+    //       '/edu-app/open/paramConfig/wechatAppId'
+    //     );
+    //     // 判断是否有微信appId
+    //     if (data) {
+    //       closeToast();
+    //       goWechatAuth(data, url);
+    //     }
+    //   } catch {
+    //     //
+    //   }
+    // };
+
     onMounted(() => {
       getRegisterGoods();
     });
+
+    // if (browser().weixin) {
+    //   //授权
+    //   const code = getUrlCode();
+    //   if (!code) {
+    //     const newUrl =
+    //       window.location.origin +
+    //       window.location.pathname +
+    //       '#' +
+    //       route.path +
+    //       '?' +
+    //       qs.stringify({
+    //         ...route.query
+    //       });
+    //     getAppIdAndCode(newUrl);
+    //     return '';
+    //   } else {
+    //     forms.code = code;
+    //   }
+    // }
     return () => (
       <div class={styles['student-register']}>
         <div class={styles.studentSection} style={{ marginTop: '18px' }}>
@@ -326,10 +371,16 @@ export default defineComponent({
                                   {goods.brandName}
                                 </Tag>
                               </h2>
-
+                              <p class={[styles.model]}>
+                                规格:{goods.spDataJson}
+                              </p>
                               <p class={[styles.model]}>{goods.productSn}</p>
 
-                              <Stepper min={1} max={99} v-model={goods.count} />
+                              <Stepper
+                                min={1}
+                                max={99}
+                                v-model={goods.quantity}
+                              />
                             </div>
 
                             <i
@@ -455,6 +506,9 @@ export default defineComponent({
             }
           }}
         />
+
+        {/* 是否在微信中打开 */}
+        <OWxTip />
       </div>
     );
   }

+ 1 - 1
src/views/student-register/order-detail.tsx

@@ -212,7 +212,7 @@ export default defineComponent({
       try {
         state.submitStatus = true;
         const { data } = await request.get(
-          '/api-student/userPaymentOrder/detail/' + state.orderNo,
+          '/edu-app/userPaymentOrder/detail/' + state.orderNo,
           {
             hideLoading: false
           }

+ 5 - 4
src/views/student-register/shop-mall/components/goods/index.tsx

@@ -3,6 +3,7 @@ import { defineComponent } from 'vue';
 import styles from './index.module.less';
 
 import iconSellOut from '../../images/icon-sell-out.png';
+import iconAddCart from '../../images/icon-add-cart.png';
 import { moneyFormat } from '@/helpers/utils';
 
 export default defineComponent({
@@ -48,7 +49,7 @@ export default defineComponent({
               {moneyFormat(item.price)}
             </p>
 
-            {/* {this.showAdd && (
+            {this.showAdd && (
               <Icon
                 class={[
                   styles.addCart,
@@ -57,11 +58,11 @@ export default defineComponent({
                 name={iconAddCart}
                 size={22}
                 onClick={(e: MouseEvent) => {
-                  e.stopPropagation()
-                  item.stock > 0 && this.onBuyClick(item)
+                  e.stopPropagation();
+                  item.stock > 0 && this.onBuyClick(item);
                 }}
               />
-            )} */}
+            )}
           </div>
         </div>
       </div>

+ 20 - 0
src/views/student-register/shop-mall/components/tab-list/index.tsx

@@ -4,6 +4,7 @@ import { List, Popup } from 'vant';
 import { defineComponent } from 'vue';
 import Goods from '../goods';
 import styles from './index.module.less';
+import AddGoodsCart from '../../modal/add-goods-cart';
 
 export default defineComponent({
   name: 'tab-list',
@@ -84,6 +85,7 @@ export default defineComponent({
         params.sort = this.sort ? this.sort : undefined;
         params.keyword = this.keyword ? this.keyword : undefined;
         const res = await request.post('/edu-app/open/mall/productSearch', {
+          noAuthorization: true,
           data: {
             ...params
           }
@@ -120,6 +122,7 @@ export default defineComponent({
       });
     },
     onBuyClick(item: any) {
+      console.log(item, 'item');
       this.selectGoodsItem = item;
       this.addGoodsShow = true;
     }
@@ -149,6 +152,23 @@ export default defineComponent({
           )) ||
             null}
         </List>
+
+        <Popup
+          show={this.addGoodsShow}
+          closeable
+          position="bottom"
+          round
+          onClose={() => {
+            this.addGoodsShow = false;
+          }}>
+          <AddGoodsCart
+            show={this.addGoodsShow}
+            onClose={() => {
+              this.addGoodsShow = false;
+            }}
+            item={this.selectGoodsItem}
+          />
+        </Popup>
       </div>
     );
   }

+ 9 - 2
src/views/student-register/shop-mall/goods-detail/index.module.less

@@ -107,14 +107,20 @@
 
     .van-tag--default {
       color: #999999;
+      background-color: #F7F8F9;
     }
 
     .van-tag--primary {
-      background-color: #f7f8f9;
+      background-color: #FFFAF4;
+      background: #FFFAF4;
+      border-radius: 6px;
+      border: 1px solid #FFCF7C;
+      color: #F39F11;
     }
 
     .van-radio__label {
       margin-left: 0;
+      width: 100%;
     }
   }
 }
@@ -218,6 +224,7 @@
   color: #5B2C03;
   width: 148px;
   height: 40px;
+  border: 0;
 }
 
 
@@ -320,4 +327,4 @@
       top: 40px;
     }
   }
-}
+}

+ 33 - 20
src/views/student-register/shop-mall/goods-detail/index.tsx

@@ -15,12 +15,12 @@ import {
   Badge,
   SubmitBar,
   showImagePreview,
-  showToast
+  showToast,
+  Popup
 } from 'vant';
 import { defineComponent } from 'vue';
 import styles from './index.module.less';
-import { useStudentRegisterStore } from '@/store/modules/student-register-store';
-const studentRegisterStore = useStudentRegisterStore();
+import AddGoodsCart from '../modal/add-goods-cart';
 export default defineComponent({
   name: 'goods-detail',
   data() {
@@ -32,7 +32,10 @@ export default defineComponent({
       radio: 0,
       skuStockListTemp: [],
       detailMobileHtml: '',
-      loading: false
+      loading: false,
+      addGoodsShow: false,
+      selectGoodsItem: {},
+      cartCount: 0
     };
   },
   computed: {
@@ -87,7 +90,10 @@ export default defineComponent({
     try {
       this.loading = true;
       const res = await request.get(
-        `/edu-app/open/mall/product/detail/${this.id}`
+        `/edu-app/open/mall/product/detail/${this.id}`,
+        {
+          noAuthorization: true
+        }
       );
       this.loading = false;
       const result = res.data || {};
@@ -141,7 +147,7 @@ export default defineComponent({
       });
     },
     onShowCart() {
-      const selectGoodsItem = {
+      this.selectGoodsItem = {
         price: this.product.price,
         originalPrice: this.product.originalPrice,
         pic: this.product.pic,
@@ -154,22 +160,10 @@ export default defineComponent({
         name: this.product.name,
         productSn: this.product.productSn,
         productSubTitle: this.product.subTitle,
-        count: 1,
         id: this.product.id
       };
-      let isExist = false; // 是否存在
-      studentRegisterStore.getGoods.forEach((goods: any) => {
-        if (goods.id === this.product.id) {
-          isExist = true;
-          goods.count += 1;
-        }
-      });
-
-      if (!isExist) {
-        studentRegisterStore.setGoods(selectGoodsItem);
-      }
-
-      showToast('添加成功');
+      // 打开购物弹框
+      this.addGoodsShow = true;
     }
   },
   render() {
@@ -309,6 +303,25 @@ export default defineComponent({
               }}></SubmitBar>
           </>
         )}
+
+        <Popup
+          show={this.addGoodsShow}
+          closeable
+          position="bottom"
+          round
+          onClose={() => {
+            this.addGoodsShow = false;
+          }}>
+          <AddGoodsCart
+            show={this.addGoodsShow}
+            item={this.selectGoodsItem}
+            onClose={() => {
+              this.addGoodsShow = false;
+            }}
+            defaultRadio={this.radio}
+            showType={'cart'}
+          />
+        </Popup>
       </div>
     );
   }

+ 1 - 3
src/views/student-register/shop-mall/goods-list/index.tsx

@@ -6,7 +6,6 @@ import iconFilter from '../images/icon-filter.png';
 import iconSearchY from '../images/icon-search-y.png';
 import GoodsFilterList from '../modal/goods-filter-list';
 import ColSearch from '@/components/m-search';
-import request from '@/helpers/request';
 import MSticky from '@/components/m-sticky';
 export default defineComponent({
   name: 'goods-list',
@@ -243,8 +242,7 @@ export default defineComponent({
             style={{
               height: 'calc(100vh - var(--header-height))'
             }}
-            showAdd={false}
-            // typeId={Number(20)}
+            showAdd={true}
             productAttributeCategoryId={this.productAttributeCategory.id}
             brandId={this.brand.id}
             sort={this.filterActive}

BIN
src/views/student-register/shop-mall/images/icon-add-cart.png


+ 131 - 0
src/views/student-register/shop-mall/modal/add-goods-cart/index.module.less

@@ -0,0 +1,131 @@
+.addGoodsCart {
+  padding-top: 12px;
+  --k-font-primary: #FFCB75;
+  --van-primary: var(--k-font-primary);
+  --van-stepper-button-round-theme-color: var(--van-primary);
+
+  :global {
+    .van-stepper__input {
+      background: #f7f8f9 !important;
+      border-radius: 6px;
+      margin: 0 8px;
+    }
+
+    .van-stepper__minus--disabled {
+      opacity: 0.6 !important;
+      color: #333 !important;
+      background: #f7f8f9 !important;
+      border-color: #f7f8f9 !important;
+    }
+  }
+
+  .addCartBtn {
+    background: linear-gradient(121deg, #FFD892 0%, #FFCB75 100%);
+    border-radius: 12px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #5B2C03;
+    border: 0;
+  }
+}
+
+.goodsSection {
+  position: relative;
+  width: 100px;
+  height: 100px;
+  border-radius: 8px;
+  overflow: hidden;
+
+  .sellOut {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.2);
+    padding: 20px;
+  }
+}
+
+.goodsImg {
+  width: 100px;
+  height: 100px;
+  background: linear-gradient(180deg, #f0f0f0 0%, #d7d7d7 100%);
+
+  overflow: hidden;
+}
+
+.goodsPrice {
+  padding-top: 8px;
+  font-size: 18px;
+  color: #ff4e19;
+  line-height: 22px;
+
+  span {
+    font-size: 16px;
+  }
+}
+
+.goodsStore {
+  font-size: 14px;
+  color: #999999;
+  line-height: 20px;
+}
+
+.title {
+  font-size: 16px;
+  color: #333333;
+  line-height: 22px;
+}
+
+.radio-group {
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 14px;
+}
+
+.radio {
+  margin-right: 10px;
+  margin-bottom: 8px;
+  min-width: 60px;
+
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+
+    .van-tag--large {
+      height: 32px;
+      font-size: 16px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .van-tag {
+      box-sizing: border-box;
+    }
+
+    .van-tag--default {
+      color: #999999;
+      background-color: #F7F8F9;
+    }
+
+    .van-tag--primary {
+      background-color: #FFFAF4;
+      background: #FFFAF4;
+      border-radius: 6px;
+      border: 1px solid #FFCF7C;
+      color: #F39F11;
+    }
+
+    .van-radio__label {
+      margin-left: 0;
+      width: 100%;
+    }
+  }
+}

+ 258 - 0
src/views/student-register/shop-mall/modal/add-goods-cart/index.tsx

@@ -0,0 +1,258 @@
+import { moneyFormat } from '@/helpers/utils';
+import {
+  Button,
+  Cell,
+  Image,
+  Radio,
+  RadioGroup,
+  Stepper,
+  Tag,
+  showSuccessToast,
+  showToast
+} from 'vant';
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import iconSellOut from '../../images/icon-sell-out.png';
+import { useStudentRegisterStore } from '@/store/modules/student-register-store';
+const studentRegisterStore = useStudentRegisterStore();
+
+export default defineComponent({
+  name: 'add-goods-cart',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    item: {
+      type: Object,
+      default: {}
+    },
+    defaultRadio: {
+      type: Number,
+      default: 0
+    },
+    showType: {
+      type: String,
+      default: 'cart'
+    },
+    onClose: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  watch: {
+    show(val) {
+      // 添加购物车显示
+      if (val) {
+        this.totalData = {};
+        this.total = 1;
+        this.radio = '';
+        this.setList();
+      }
+    }
+  },
+  data() {
+    return {
+      radio: '',
+      total: 1,
+      totalData: {} as any,
+      skuStockList: []
+    };
+  },
+  computed: {
+    selectItem() {
+      const radio = this.radio;
+      const select = this.skuStockList.find((n: any) => n.id == radio) as any;
+      if (select) {
+        let stock: number = select.stock - select.lockStock; //- select.cartNum
+        return {
+          ...select,
+          stock
+        };
+      }
+      return {
+        stock: 0
+      };
+    }
+  },
+  mounted() {
+    this.setList();
+  },
+  methods: {
+    setList() {
+      // 处理规格
+      let skuStockList = [] as any;
+      const item = JSON.parse(JSON.stringify(this.item));
+      if (Array.isArray(item.skuStockList)) {
+        skuStockList = item.skuStockList.map((n: any) => {
+          n.pic = n.pic || item.pic;
+          n.cartNum = 0;
+          if (n.spData) {
+            const spData = JSON.parse(n.spData);
+            let str = '';
+            spData.forEach((sp: any) => {
+              str += `${sp.value}`;
+            });
+            n.spDataJson = str;
+          } else {
+            n.spDataJson = '默认';
+          }
+          n.lockStock = n.lockStock > 0 ? n.lockStock : 0;
+          return {
+            ...n
+          };
+        });
+      }
+      if (!skuStockList.length) return skuStockList;
+      // 处理默认显示
+      let index = 0;
+      if (this.defaultRadio) {
+        let i = skuStockList.findIndex((n: any) => n.id == this.defaultRadio);
+        index = i > -1 ? i : 0;
+      }
+      this.radio = skuStockList[index].id;
+      this.skuStockList = skuStockList;
+    },
+    async onAddCart() {
+      const selectItem = this.selectItem;
+      const item = this.item;
+      const body = {
+        pic: item.pic,
+        name: item.name,
+        brandName: item.brandName,
+        productSn: item.productSn,
+        price: selectItem.price, //添加到购物车的价格
+        originalPrice: item.originalPrice, // 原价
+        productSkuId: selectItem.id,
+        stock: selectItem.stock,
+        quantity: this.total, // 数量
+        productId: item.id,
+        spDataJson: selectItem.spDataJson, // 规格
+        hidden: this.showType === 'cart' ? 0 : 1,
+        promoterId: this.$route.query.promoterId
+          ? this.$route.query.promoterId
+          : undefined
+      };
+      try {
+        let isExist = false; // 是否存在 是否是同一个商品,同一个sku
+        studentRegisterStore.getGoods.forEach((goods: any) => {
+          if (
+            goods.id === body.productId &&
+            goods.productSkuId === body.productSkuId
+          ) {
+            isExist = true;
+            goods.quantity += body.quantity;
+          }
+        });
+
+        if (!isExist) {
+          studentRegisterStore.setGoods(body);
+        }
+
+        showSuccessToast('添加成功');
+        this.onClose();
+      } catch (error) {}
+    },
+
+    // 更新产品规格的库存
+    setProductStock(n: number) {
+      // 根据当前用户的购物车,当前产品规格的数量,限制库存
+      for (let i = 0; i < this.skuStockList.length; i++) {
+        if ((this.skuStockList[i] as any).id === this.radio) {
+          (this.skuStockList[i] as any).cartNum = n;
+        }
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.addGoodsCart}>
+        <Cell
+          titleStyle={{ paddingLeft: '12px' }}
+          v-slots={{
+            icon: () => (
+              <div class={styles.goodsSection}>
+                <Image
+                  src={this.selectItem.pic}
+                  class={styles.goodsImg}
+                  fit="cover"
+                />
+                {this.selectItem.stock <= 0 && (
+                  <div class={styles.sellOut}>
+                    <Image
+                      src={iconSellOut}
+                      fit="cover"
+                      class={styles.sellOutImg}
+                    />
+                  </div>
+                )}
+              </div>
+            ),
+            title: () => (
+              <div class={styles.goodsInfo}>
+                <p class={styles.goodsPrice}>
+                  <span>¥</span>
+                  {moneyFormat(this.selectItem.price)}
+                </p>
+                <p class={styles.goodsStore}>库存:{this.selectItem.stock}</p>
+              </div>
+            )
+          }}
+        />
+        <Cell
+          v-slots={{
+            title: () => <div class={styles.title}>规格</div>,
+            label: () => (
+              <RadioGroup class={styles['radio-group']} modelValue={this.radio}>
+                {this.skuStockList.map((item: any) => {
+                  const isActive = item.id === this.radio;
+                  const type = isActive ? 'primary' : 'default';
+                  return (
+                    <Radio
+                      class={styles.radio}
+                      name={item.id}
+                      onClick={() => {
+                        if (this.radio == item.id) return;
+                        this.radio = item.id;
+                      }}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {item.spDataJson}
+                      </Tag>
+                    </Radio>
+                  );
+                })}
+              </RadioGroup>
+            )
+          }}
+        />
+        <Cell
+          title="购买数量"
+          style={{ margin: '12px 0' }}
+          border={false}
+          titleClass={styles.title}
+          center>
+          <Stepper
+            v-model={this.total}
+            inputWidth="50px"
+            theme="round"
+            buttonSize="24px"
+            max={this.selectItem.stock > 200 ? 200 : this.selectItem.stock}
+            min={1}
+            disabled={this.selectItem.stock <= 0}
+            integer
+          />
+        </Cell>
+        <div class={['btnGroup']} style={{ marginBottom: '8px' }}>
+          <Button
+            block
+            type="primary"
+            text="确定"
+            class={styles.addCartBtn}
+            disabled={this.selectItem.stock <= 0}
+            onClick={() => this.onAddCart()}
+          />
+        </div>
+      </div>
+    );
+  }
+});

+ 3 - 1
src/views/student-register/shop-mall/modal/goods-filter-list/index.tsx

@@ -47,7 +47,9 @@ export default defineComponent({
     async getFilterList() {
       this.loading = true;
       try {
-        const res = await request.get('/edu-app/open/mall/search/condition');
+        const res = await request.get('/edu-app/open/mall/search/condition', {
+          noAuthorization: true
+        });
         this.dataShow = res.code === 200;
         const { brandList = [], productAttributeCategoryList = [] } =
           res.data || {};

+ 8 - 21
src/views/student-register/shop-mall/shop-mall.ts

@@ -1,22 +1,9 @@
-import request from "@/helpers/request"
-import { ref } from "vue"
-
 export const orderState = {
-    0: '待支付',
-    1: '待发货',
-    2: '已发货',
-    3: '已完成',
-    4: '已关闭',
-    5: '无效订单',
-    6: '待支付'
-  }
-
-export const cartCount = ref(0)
-export const getCartCount = async () => {
-  try {
-    let {data: {count}} = await request.get('/api-mall-portal/home/content', {hideLoading: true})
-    cartCount.value = count
-  } catch (error) {
-    
-  }
-}
+  0: '待支付',
+  1: '待发货',
+  2: '已发货',
+  3: '已完成',
+  4: '已关闭',
+  5: '无效订单',
+  6: '待支付'
+};