瀏覽代碼

添加新页面

lex 2 年之前
父節點
當前提交
615949a66b
共有 40 個文件被更改,包括 2788 次插入286 次删除
  1. 二進制
      src/common/images/icon-edit.png
  2. 二進制
      src/common/images/icon-timer.png
  3. 二進制
      src/common/images/icon-warn.png
  4. 27 23
      src/component-ui/k-action-sheet/index.tsx
  5. 8 2
      src/components/m-empty/index.module.less
  6. 1 0
      src/components/m-sticky/index.tsx
  7. 6 0
      src/helpers/constant.ts
  8. 9 8
      src/helpers/request.ts
  9. 8 0
      src/router/router-root.ts
  10. 48 8
      src/router/routes-common.ts
  11. 65 3
      src/styles/index.less
  12. 8 0
      src/views/download/index.tsx
  13. 2 1
      src/views/layout/auth.tsx
  14. 32 18
      src/views/layout/login.tsx
  15. 110 0
      src/views/patrol-evaluation/detail-list.tsx
  16. 255 0
      src/views/patrol-evaluation/detail.module.less
  17. 125 0
      src/views/patrol-evaluation/detail.tsx
  18. 二進制
      src/views/patrol-evaluation/images/icon-face-1.png
  19. 二進制
      src/views/patrol-evaluation/images/icon-face-2.png
  20. 二進制
      src/views/patrol-evaluation/images/icon-face-3.png
  21. 二進制
      src/views/patrol-evaluation/images/icon-face-4.png
  22. 二進制
      src/views/patrol-evaluation/images/icon-upload-img.png
  23. 二進制
      src/views/patrol-evaluation/images/icon-upload-video.png
  24. 90 0
      src/views/patrol-evaluation/index.module.less
  25. 109 0
      src/views/patrol-evaluation/index.tsx
  26. 191 0
      src/views/patrol-evaluation/skeletion-detail.modal.tsx
  27. 181 0
      src/views/patrol-evaluation/skeletion-index.modal.tsx
  28. 85 11
      src/views/school-register/index.tsx
  29. 12 12
      src/views/site-management/index.module.less
  30. 252 172
      src/views/site-management/index.tsx
  31. 122 0
      src/views/site-management/site-settings.module.less
  32. 202 0
      src/views/site-management/site-settings.tsx
  33. 93 0
      src/views/site-management/skeleton-settings-modal.tsx
  34. 160 0
      src/views/teacher-attendance/detail.tsx
  35. 192 0
      src/views/teacher-attendance/index.module.less
  36. 129 0
      src/views/teacher-attendance/index.tsx
  37. 112 0
      src/views/teacher-attendance/skeletion-detail-modal.tsx
  38. 143 0
      src/views/teacher-attendance/skeletion-index-modal.tsx
  39. 5 19
      tsconfig.json
  40. 6 9
      vite.config.ts

二進制
src/common/images/icon-edit.png


二進制
src/common/images/icon-timer.png


二進制
src/common/images/icon-warn.png


+ 27 - 23
src/component-ui/k-action-sheet/index.tsx

@@ -1,12 +1,12 @@
-import { ActionSheet } from 'vant'
-import { defineComponent, PropType, reactive, watch } from 'vue'
-import styles from './index.module.less'
+import { ActionSheet } from 'vant';
+import { defineComponent, PropType, reactive, watch } from 'vue';
+import styles from './index.module.less';
 
 type actionsType = {
-  name: string | number
-  value?: string | number
-  selected?: boolean
-}
+  name: string | number;
+  value?: string | number;
+  selected?: boolean;
+};
 
 export default defineComponent({
   name: 'k-action-sheet',
@@ -33,35 +33,37 @@ export default defineComponent({
   setup(props, { emit }) {
     const form = reactive({
       oPopover: props.show
-    })
+    });
 
     const onSelect = (item: any) => {
-      emit('select', item)
-      emit('update:show', false)
-    }
+      emit('select', item);
+      emit('update:show', false);
+    };
 
     watch(
       () => form.oPopover,
       () => {
-        emit('update:show', form.oPopover)
+        emit('update:show', form.oPopover);
       }
-    )
+    );
 
     watch(
       () => props.show,
       () => {
-        form.oPopover = props.show
+        form.oPopover = props.show;
       }
-    )
+    );
 
     return () => (
       <ActionSheet v-model:show={form.oPopover} teleport={props.teleport}>
         <div class={[styles['k-sheet_content'], 'van-hairline--bottom']}>
           {props.actions.map((item: any) => (
             <div
-              class={['van-sheet-item van-ellipsis', item.selected && 'van-sheet-item-active']}
-              onClick={() => onSelect(item)}
-            >
+              class={[
+                'van-sheet-item van-ellipsis',
+                item.selected && 'van-sheet-item-active'
+              ]}
+              onClick={() => onSelect(item)}>
               {item.name}
             </div>
           ))}
@@ -69,12 +71,14 @@ export default defineComponent({
 
         <button
           type="button"
-          class={['van-action-sheet__cancel', styles['k-action-sheet_bottom__cancel']]}
-          onClick={() => emit('update:show', false)}
-        >
+          class={[
+            'van-action-sheet__cancel',
+            styles['k-action-sheet_bottom__cancel']
+          ]}
+          onClick={() => emit('update:show', false)}>
           {props.cancelText}
         </button>
       </ActionSheet>
-    )
+    );
   }
-})
+});

+ 8 - 2
src/components/m-empty/index.module.less

@@ -2,11 +2,17 @@
   --van-empty-description-color: var(--k-gray-4);
   --van-empty-description-font-size: 16px;
   --van-empty-description-margin-top: 13px;
+
   :global {
     .van-empty__image {
       width: 198px;
       height: 124px;
     }
+
+    .van-empty__bottom {
+      width: 100%;
+      text-align: center;
+    }
   }
 
   .button {
@@ -14,6 +20,6 @@
     min-width: 76px;
     font-size: 13px;
     padding: 0 24px;
-    height: 26px;
+    height: 36px;
   }
-}
+}

+ 1 - 0
src/components/m-sticky/index.tsx

@@ -34,6 +34,7 @@ export default defineComponent({
       default: '--header-height'
     }
   },
+  emits: ['barHeight'],
   setup(props, { slots, emit }) {
     const forms = reactive({
       divStyle: {} as any,

+ 6 - 0
src/helpers/constant.ts

@@ -0,0 +1,6 @@
+export const EShoolStaffType = {
+  SCHOOLMASTER: '校长',
+  ORCHESTRA_LEADER: '乐团领队',
+  SCHOOL_LEADER: '分管领导',
+  TEACHER: '负责老师'
+} as any;

+ 9 - 8
src/helpers/request.ts

@@ -1,7 +1,7 @@
 import { extend } from 'umi-request';
 import cleanDeep from 'clean-deep';
 import { browser } from '@/helpers/utils';
-import { setLogout, setLoginError } from '@/state';
+import { setLogout, setLoginError, state } from '@/state';
 import { postMessage } from './native-message';
 import { showLoadingToast, showToast, closeToast } from 'vant';
 
@@ -38,14 +38,17 @@ request.interceptors.request.use(
     if (
       Authorization &&
       ![
-        '/api-oauth/userlogin',
-        // `${state.platformApi}/user/getUserInfo`,
-        '/api-oauth/open/sendSms'
+        '/api-auth/userlogin',
+        '/api-auth/smsLogin',
+        '/api-auth/open/sendSms'
       ].includes(url)
     ) {
       authHeaders.Authorization = Authorization;
     }
 
+    if (state?.user?.data) {
+      authHeaders.coopId = state?.user?.data.schoolId;
+    }
     return {
       url,
       options: {
@@ -67,7 +70,6 @@ request.interceptors.response.use(
     toast = setTimeout(() => {
       closeToast();
     }, 100);
-
     if (res.status > 299 || res.status < 200) {
       clearTimeout(toast);
       const msg = '服务器错误,状态码' + res.status;
@@ -85,14 +87,13 @@ request.interceptors.response.use(
           setLoginError();
         }
       }
-      console.log(data.code, '5104');
       if (!(data.code === 403 || data.code === 5000)) {
         clearTimeout(toast);
         showToast(msg);
       }
       const browserInfo = browser();
-      if (data.code === 5000) {
-        msg += '5000';
+      if (data.code === 5000 || data.code === 403) {
+        msg += ' authentication ' + data.code;
         if (browserInfo.isApp) {
           postMessage({
             api: 'login'

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

@@ -9,6 +9,14 @@ export default [
     }
   },
   {
+    path: '/download',
+    name: 'download',
+    component: () => import('@/views/download/index'),
+    meta: {
+      title: '下载管乐迷学校端'
+    }
+  },
+  {
     path: '/test',
     name: 'test',
     component: () => import('@/views/test/index'),

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

@@ -25,16 +25,56 @@ export default [
         meta: {
           title: '首页'
         }
+      },
+      {
+        path: '/site-management',
+        name: 'site-management',
+        component: () => import('@/views/site-management/index'),
+        meta: {
+          title: '场地管理'
+        }
+      },
+      {
+        path: '/site-settings',
+        name: 'site-settings',
+        component: () => import('@/views/site-management/site-settings'),
+        meta: {
+          title: '场地设置'
+        }
+      },
+      {
+        path: '/teacher-attendance',
+        name: 'teacher-attendance',
+        component: () => import('@/views/teacher-attendance'),
+        meta: {
+          title: '教师考勤'
+        }
+      },
+      {
+        path: '/teacher-attendance-detail',
+        name: 'teacher-attendance-detail',
+        component: () => import('@/views/teacher-attendance/detail'),
+        meta: {
+          title: '考勤详情'
+        }
+      },
+      {
+        path: '/patrol-evaluation',
+        name: 'patrol-evaluation',
+        component: () => import('@/views/patrol-evaluation'),
+        meta: {
+          title: '巡堂评价'
+        }
+      },
+      {
+        path: '/patrol-evaluation-detail',
+        name: 'patrol-evaluation-detail',
+        component: () => import('@/views/patrol-evaluation/detail'),
+        meta: {
+          title: '巡堂评价'
+        }
       }
     ]
   },
-  {
-    path: '/site-management',
-    name: 'site-management',
-    component: () => import('@/views/site-management/index'),
-    meta: {
-      title: '场地管理'
-    }
-  },
   ...rootRouter
 ];

+ 65 - 3
src/styles/index.less

@@ -32,8 +32,71 @@ body {
   user-select: none;
 }
 
-// 固定底部按钮样式
+.van-cell {
+  padding: 12px;
+}
+
+// tab 选项卡样式
+
+
+// 下拉框样式重置
+.van-dropdown-menu__bar {
+  box-shadow: none;
+  --van-dropdown-menu-title-font-size: 14px;
+  --van-button-normal-font-size: 16px;
+  --van-dropdown-menu-height: 44px;
+}
+
+.van-dropdown-item__content {
+  border-radius: 0px 0px 12px 12px;
+
+  .van-dropdown-item__option {
+    margin: 0 13px;
+    height: 44px;
+    border-radius: 8px;
+    width: auto;
+    ;
+
+    &:first-child {
+      margin-top: 12px;
+    }
+
+    &:last-child {
+      margin-bottom: 12px;
+    }
+
+    &:after {
+      border: none;
+    }
 
+    .van-cell__title {
+      white-space: nowrap;
+      width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      font-size: 16px;
+      color: var(--k-gray-4);
+      text-align: center;
+    }
+
+    .van-cell__value {
+      display: none;
+    }
+  }
+
+
+
+  .van-dropdown-item__option--active {
+    background: #F6F6F6;
+
+    .van-cell__title {
+      font-weight: 600;
+      color: var(--k-gray-1);
+    }
+  }
+}
+
+// 固定底部按钮样式
 .btnGroupFixed {
   padding: 0 25px;
   padding-bottom: calc(20px + constant(safe-area-inset-bottom));
@@ -49,7 +112,6 @@ body {
 
   .van-button {
     font-weight: 400;
-    width: 168px;
+    width: 48%;
   }
-
 }

+ 8 - 0
src/views/download/index.tsx

@@ -0,0 +1,8 @@
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: 'download-page',
+  setup() {
+    return () => <>下载管乐迷学校端</>;
+  }
+});

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

@@ -46,7 +46,7 @@ export default defineComponent({
       if (state.user.status === 'init' || state.user.status === 'error') {
         this.loading = true;
         try {
-          const res = await request.get('/api-school/user/getUserInfo', {
+          const res = await request.get('/api-web/schoolStaff/queryUserInfo', {
             initRequest: true, // 初始化接口
             requestType: 'form',
             hideLoading: true
@@ -99,6 +99,7 @@ export default defineComponent({
               image="network"
               description="加载失败,请稍后重试"
               buttonText="重新加载"
+              showButton
               onClick={this.setAuth}
             />
           </div>

+ 32 - 18
src/views/layout/login.tsx

@@ -54,32 +54,46 @@ export default defineComponent({
       try {
         // let res: any
         const forms: any = {
-          username: this.username,
-          client_id: 'SYSTEM',
-          client_secret: 'SYSTEM'
+          phone: this.username,
+          clientId: 'SYSTEM',
+          clientSecret: 'SYSTEM'
         };
 
         if (this.loginType === 'PWD') {
           forms.password = this.password;
-          forms.loginType = '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.password = this.smsCode;
-          forms.loginType = 'SMS';
-          forms.grant_type = 'password';
+          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 { data } = await request.post('/api-auth/usernameLogin', {
-          requestType: 'form',
-          data: {
-            ...forms
-          }
-        });
 
-        setAuth(data.token_type + ' ' + data.access_token);
-
-        const userCash = await request.get('/api-school/user/getUserInfo', {
-          initRequest: true // 初始化接口
-        });
+        const userCash = await request.get(
+          '/api-web/schoolStaff/queryUserInfo',
+          {
+            initRequest: true // 初始化接口
+          }
+        );
         setLogin(userCash.data);
 
         this.directNext();

+ 110 - 0
src/views/patrol-evaluation/detail-list.tsx

@@ -0,0 +1,110 @@
+import { Cell, CellGroup, Icon, Image, Tag } from 'vant';
+import { defineComponent } from 'vue';
+import styles from './detail.module.less';
+import iconTimer from '@/common/images/icon-timer.png';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconEdit from '@/common/images/icon-edit.png';
+
+import iconFace1 from './images/icon-face-1.png';
+
+export default defineComponent({
+  name: 'detail-list',
+  setup() {
+    return () => (
+      <>
+        <CellGroup inset class={styles.cellGroup}>
+          <Cell center class={styles.timerCell} border={false}>
+            {{
+              icon: () => <Icon name={iconTimer} class={styles.iconTimer} />,
+              title: () => <div class={styles.timer}>2023-03-24 14:00:32</div>,
+              value: () => (
+                <div class={styles.eStatus}>
+                  <Icon name={iconFace1} class={styles.iconFace} />
+                  <span class={[styles.sLevel, styles.success]}>优秀</span>
+                  <Icon name={iconEdit} class={styles.iconEdit} />
+                </div>
+              )
+            }}
+          </Cell>
+          <Cell center class={styles.usernameCell}>
+            {{
+              icon: () => (
+                <Image
+                  src={iconTeacher}
+                  class={styles.iconTeacher}
+                  fit="contain"
+                />
+              ),
+              title: () => (
+                <div>
+                  <div class={styles.classname}>声部课·上低音号音号</div>
+                  <div class={styles.name}>王丹丹</div>
+                </div>
+              ),
+              value: () => (
+                <div class={styles.photoList}>
+                  <div class={styles.photo}>
+                    <Image
+                      src={
+                        'https://daya.ks3-cn-beijing.ksyun.com/202201/SvE7cQl.png'
+                      }
+                      fit="cover"
+                    />
+                  </div>
+                  <div class={styles.photo}>
+                    <Image
+                      src={
+                        'https://daya.ks3-cn-beijing.ksyun.com/202201/SvE7cQl.png'
+                      }
+                      fit="cover"
+                    />
+                  </div>
+                  <div
+                    class={styles.photo}
+                    // onClick={() => {
+                    //   forms.imagePreview = item.signPhoto.split(',') || [];
+                    //   forms.imageShow = true;
+                    //   forms.startPosition = index;
+                    // }}
+                  >
+                    <Image
+                      src={
+                        'https://daya.ks3-cn-beijing.ksyun.com/202201/SvE7cQl.png'
+                      }
+                      fit="cover"
+                    />
+                    <div class={styles.photoMore}>+8</div>
+                  </div>
+                </div>
+              )
+            }}
+          </Cell>
+
+          {/* <Cell center class={styles.resultCell}>
+            <div class={styles.typeGroup}>
+              <Tag type="primary" plain>
+                课堂纪律
+              </Tag>
+              <Tag type="primary" plain>
+                课堂纪律
+              </Tag>
+              <Tag type="primary" plain>
+                课堂纪律
+              </Tag>
+              <Tag type="primary" plain>
+                课堂纪律
+              </Tag>
+            </div>
+            <div class={styles.result}>
+              没有组织学员将乐器箱包放在指定地点。
+            </div>
+          </Cell> */}
+
+          <Cell center class={styles.operationCell}>
+            <div class={styles.operation}></div>
+          </Cell>
+        </CellGroup>
+      </>
+    );
+  }
+});

+ 255 - 0
src/views/patrol-evaluation/detail.module.less

@@ -0,0 +1,255 @@
+.patrol-evaluation-detail {
+  --van-tab-font-size: 16px;
+
+  :global {
+    .van-tab {
+      z-index: 1;
+    }
+
+    .van-tabs__line {
+      bottom: 26px;
+      width: 45px;
+      height: 6px;
+      background: linear-gradient(250deg, rgba(45, 199, 170, 0.22) 0%, #2DC7AA 100%);
+      z-index: 0;
+    }
+  }
+}
+
+.searchContainer {
+  max-height: 400px;
+  overflow-y: auto;
+  box-sizing: border-box;
+  padding-bottom: 16px;
+  background: var(--van-popup-background);
+  transition: var(--van-popup-transition);
+}
+
+
+.searchTitle {
+  padding: 15px 13px 2px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #333333;
+  line-height: 21px;
+  text-align: left;
+}
+
+.searchTypeGroup {
+  display: flex;
+  align-items: center;
+  padding: 0 13px;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+.searchTypeFlex {
+  justify-content: flex-start;
+
+  .searchTypeItem {
+    width: 31%;
+
+    &:nth-child(3n + 1) {
+      margin-right: 2.333%;
+    }
+
+    &:nth-child(3n + 3) {
+      margin-left: 2.333%;
+    }
+  }
+}
+
+.success {
+  color: var(--k-font-primary) !important;
+}
+
+.error {
+  color: #FF5A56 !important;
+}
+
+
+.searchTypeItem {
+  box-sizing: border-box;
+  margin-top: 10px;
+  height: 32px;
+  line-height: 32px;
+  background: #f6f6f6;
+  border: 1px solid #f6f6f6;
+  border-radius: 16px;
+  font-size: 13px;
+  color: #333333;
+  padding: 0 7px;
+  text-align: center;
+
+  &.is-active {
+    background: #F2FFFC;
+    border: 1px solid #9FE2DE;
+    color: var(--k-font-primary);
+  }
+}
+
+.cellGroup {
+  margin-top: 12px;
+}
+
+.timerCell {
+  padding-bottom: 8px;
+
+  :global {
+    .van-cell__title {
+      flex: 0 auto;
+    }
+  }
+
+  .iconTimer {
+    width: 17px;
+    height: 17px;
+    margin-right: 4px;
+  }
+
+  .timer {
+    font-size: 14px;
+    color: var(--k-gray-3);
+  }
+
+  .eStatus {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-size: 14px;
+    font-weight: bold;
+    color: var(--k-gray-3);
+  }
+
+  .iconFace {
+    display: flex;
+    align-items: center;
+    width: 18px;
+    height: 18px;
+    margin-left: 4px;
+  }
+
+  .iconEdit {
+    display: flex;
+    align-items: center;
+    margin-left: 10px;
+    width: 16px;
+    height: 16px;
+  }
+}
+
+.usernameCell {
+  padding: 8px 12px 16px;
+  color: var(--k-gray-3);
+  font-size: 14px;
+  line-height: 20px;
+
+  .iconTeacher {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    margin-right: 8px;
+  }
+
+  .classname {
+    font-size: 16px;
+    font-weight: 600;
+    color: var(--k-gray-1);
+
+    line-height: 22px;
+  }
+
+  :global {
+    .van-cell__value {
+      flex: 0 auto;
+      margin-left: 4px;
+    }
+  }
+}
+
+.photoList {
+  display: flex;
+}
+
+.photo {
+  position: relative;
+  width: 44px;
+  height: 44px;
+
+  &+.photo {
+    margin-left: 4px;
+  }
+
+  :global {
+    .van-image {
+      width: inherit;
+      height: inherit;
+      border-radius: 2px;
+      overflow: hidden;
+    }
+  }
+
+  .photoMore {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    background: rgba(0, 0, 0, 0.5);
+    font-size: 14px;
+    color: #fff;
+    // font-weight: bold;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 2px;
+    overflow: hidden;
+  }
+}
+
+.resultCell {
+  padding-bottom: 15px;
+
+  :global {
+    .van-cell__value {
+      text-align: left;
+    }
+  }
+
+  .result {
+    font-size: 12px;
+    color: var(--k-gray-3);
+  }
+}
+
+.typeGroup {
+  :global {
+    .van-tag {
+      margin-bottom: 8px;
+      padding: 3px 15px;
+      font-size: 12px;
+      border-radius: 4px;
+
+      &+.van-tag {
+        margin-left: 4px;
+      }
+
+      &:nth-child(5n + 5) {
+        margin-left: 0;
+      }
+    }
+
+    .van-tag--default {
+      color: var(--k-gray-1);
+      background: #F6F6F6;
+
+      &::before {
+        color: #F6F6F6;
+      }
+    }
+
+    .van-tag--primary {
+      background: #F2FFFC;
+    }
+  }
+}

+ 125 - 0
src/views/patrol-evaluation/detail.tsx

@@ -0,0 +1,125 @@
+import { defineComponent, reactive, ref } from 'vue';
+import styles from './detail.module.less';
+import MHeader from '@/components/m-header';
+import { Button, DropdownItem, DropdownMenu, Tab, Tabs } from 'vant';
+import MSticky from '@/components/m-sticky';
+import DetailList from './detail-list';
+
+export default defineComponent({
+  name: 'patrol-evaluation-detail',
+  setup() {
+    const dropDownRef = ref();
+    const forms = reactive({
+      heightV: 0,
+      active: 'Evaluated',
+      listState: {
+        loading: true
+      },
+      dropDownValue: {
+        evaluation: '',
+        question: ''
+      },
+      eveluationType: [
+        { text: '全部评价', value: '' },
+        { text: '优秀', value: '1' },
+        { text: '良好', value: '2' },
+        { text: '合格', value: '3' },
+        { text: '不合格', value: '4' }
+      ],
+      questionType: [
+        { text: '全部问题', value: '' },
+        { text: '优秀', value: '1' },
+        { text: '良好', value: '2' },
+        { text: '合格', value: '3' },
+        { text: '不合格', value: '4' }
+      ]
+    });
+
+    const onDropDownClose = (item: any) => {
+      item.value && item.value.toggle();
+    };
+    return () => (
+      <div class={styles['patrol-evaluation-detail']}>
+        <MSticky
+          position="top"
+          onBarHeight={(height: number) => {
+            forms.heightV = height;
+            console.log(height, 'height');
+          }}>
+          <MHeader>
+            {{
+              right: () => (
+                <DropdownMenu
+                  class={styles.patrolDetailDropDown}
+                  closeOnClickOutside={false}>
+                  <DropdownItem
+                    title="筛选"
+                    ref={dropDownRef}
+                    teleport={'body'}>
+                    <div class={styles.searchContainer}>
+                      <div class={styles.searchTitle}>评价类型</div>
+                      <div
+                        class={[styles.searchTypeGroup, styles.searchTypeFlex]}>
+                        {forms.eveluationType.map((item: any) => (
+                          <div
+                            class={[
+                              styles.searchTypeItem,
+                              forms.dropDownValue.evaluation === item.value &&
+                                styles['is-active']
+                            ]}>
+                            {item.text}
+                          </div>
+                        ))}
+                      </div>
+
+                      <div class={styles.searchTitle}>问题类型</div>
+                      <div
+                        class={[styles.searchTypeGroup, styles.searchTypeFlex]}>
+                        {forms.questionType.map((item: any) => (
+                          <div
+                            class={[
+                              styles.searchTypeItem,
+                              forms.dropDownValue.evaluation === item.value &&
+                                styles['is-active']
+                            ]}>
+                            {item.text}
+                          </div>
+                        ))}
+                      </div>
+                    </div>
+                    <div class={['btnGroupPopup', 'van-hairline--top']}>
+                      <Button
+                        round
+                        onClick={() => onDropDownClose(dropDownRef)}>
+                        取消
+                      </Button>
+                      <Button
+                        type="primary"
+                        round
+                        onClick={() => {
+                          onDropDownClose(dropDownRef);
+                        }}>
+                        确定
+                      </Button>
+                    </div>
+                  </DropdownItem>
+                </DropdownMenu>
+              )
+            }}
+          </MHeader>
+        </MSticky>
+
+        <Tabs
+          v-mdoel:active={forms.active}
+          offsetTop={forms.heightV}
+          lazyRender
+          swipeable>
+          <Tab name={'Evaluated'} title="已评价">
+            <DetailList />
+          </Tab>
+          <Tab name={'NotEvaluated'} title="未评价"></Tab>
+        </Tabs>
+      </div>
+    );
+  }
+});

二進制
src/views/patrol-evaluation/images/icon-face-1.png


二進制
src/views/patrol-evaluation/images/icon-face-2.png


二進制
src/views/patrol-evaluation/images/icon-face-3.png


二進制
src/views/patrol-evaluation/images/icon-face-4.png


二進制
src/views/patrol-evaluation/images/icon-upload-img.png


二進制
src/views/patrol-evaluation/images/icon-upload-video.png


+ 90 - 0
src/views/patrol-evaluation/index.module.less

@@ -0,0 +1,90 @@
+.patrolDropDown {
+  --van-dropdown-menu-background: transparent;
+  display: inline-flex;
+
+  :global {
+    .van-dropdown-menu__title {
+      margin: 0 13px;
+      background-color: #fff;
+      border-radius: 30px;
+      font-size: 14px;
+      padding: 3px 21px 3px 7px;
+
+      &::after {
+        right: 10px;
+      }
+    }
+  }
+}
+
+.cellGroup {
+  margin-bottom: 12px;
+
+  .timer {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    color: var(--k-gray-3);
+  }
+
+  .iconTimer {
+    font-size: 17px;
+    margin-right: 6px;
+  }
+}
+
+.patrolContainer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 12px;
+
+  .num {
+    color: var(--k-gray-1);
+
+    span {
+      font-size: 22px;
+      font-weight: bold;
+      line-height: 26px;
+      font-family: DINAlternate-Bold, DINAlternate;
+    }
+  }
+
+  :global {
+    .van-grid {
+      width: 70%;
+    }
+
+    .van-grid-item {
+      &:after {
+        content: ' ';
+        position: absolute;
+        top: 50%;
+        right: 0;
+        margin-top: -16px;
+        width: 1px;
+        height: 34px;
+        background: linear-gradient(to bottom, rgba(226, 230, 230, 0.30), rgba(224, 224, 224, 1), rgba(225, 227, 227, 0.30));
+        border-radius: 1px;
+      }
+
+      &:last-child {
+        &::after {
+          display: none;
+        }
+      }
+    }
+
+    .van-grid-item__text {
+      padding-top: 6px;
+      color: var(--k-gray-3);
+      font-size: 12px;
+    }
+  }
+
+  .btn {
+    font-size: 14px;
+    font-weight: 400;
+    padding: 0 14px;
+  }
+}

+ 109 - 0
src/views/patrol-evaluation/index.tsx

@@ -0,0 +1,109 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import MSticky from '@/components/m-sticky';
+import MHeader from '@/components/m-header';
+import {
+  Button,
+  Cell,
+  CellGroup,
+  DropdownItem,
+  DropdownMenu,
+  Grid,
+  GridItem,
+  Icon
+} from 'vant';
+import iconTimer from '@/common/images/icon-timer.png';
+import SkeletionIndexModal from './skeletion-index.modal';
+import { useRouter } from 'vue-router';
+
+export default defineComponent({
+  name: 'patrol-evaluation',
+  setup() {
+    const router = useRouter();
+    const forms = reactive({
+      listState: {
+        loading: true
+      },
+      statusValue: 'week',
+      statusColumns: [
+        { text: '本周', value: 'week' },
+        { text: '本月', value: 'month' },
+        { text: '本学期', value: 'term' }
+      ]
+    });
+
+    onMounted(() => {
+      setTimeout(() => {
+        forms.listState.loading = false;
+      }, 1000);
+    });
+    return () => (
+      <div class={styles['patrol-evaluation']}>
+        <MSticky position="top">
+          <MHeader />
+
+          <DropdownMenu class={styles.patrolDropDown}>
+            <DropdownItem
+              v-model={forms.statusValue}
+              options={forms.statusColumns as any}></DropdownItem>
+          </DropdownMenu>
+        </MSticky>
+
+        <SkeletionIndexModal v-model:show={forms.listState.loading}>
+          <CellGroup inset class={styles.cellGroup}>
+            <Cell center>
+              {{
+                title: () => (
+                  <div class={styles.timer}>
+                    <Icon name={iconTimer} class={styles.iconTimer} />
+                    2023-03-24
+                  </div>
+                ),
+                label: () => (
+                  <div class={styles.patrolContainer}>
+                    <Grid columnNum={2} border={false}>
+                      <GridItem>
+                        {{
+                          icon: () => (
+                            <div class={styles.num}>
+                              <span>8</span>节
+                            </div>
+                          ),
+                          text: () => (
+                            <div class="van-grid-item__text">已评价</div>
+                          )
+                        }}
+                      </GridItem>
+                      <GridItem>
+                        {{
+                          icon: () => (
+                            <div class={styles.num}>
+                              <span>8</span>节
+                            </div>
+                          ),
+                          text: () => (
+                            <div class="van-grid-item__text">全部课程</div>
+                          )
+                        }}
+                      </GridItem>
+                    </Grid>
+                    <Button
+                      type="primary"
+                      class={styles.btn}
+                      size="small"
+                      round
+                      onClick={() => {
+                        router.push('/patrol-evaluation-detail');
+                      }}>
+                      查看详情
+                    </Button>
+                  </div>
+                )
+              }}
+            </Cell>
+          </CellGroup>
+        </SkeletionIndexModal>
+      </div>
+    );
+  }
+});

+ 191 - 0
src/views/patrol-evaluation/skeletion-detail.modal.tsx

@@ -0,0 +1,191 @@
+import {
+  Cell,
+  CellGroup,
+  Grid,
+  GridItem,
+  Skeleton,
+  SkeletonAvatar,
+  SkeletonImage,
+  SkeletonParagraph
+} from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2, 3, 4, 5, 6]
+    },
+    isLink: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: props.isLink
+                  ? `calc(100vh - var(--header-height))`
+                  : 'auto',
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                // <CellGroup inset class={styles.cellGroup}>
+                //   <Cell center>
+                //     {{
+                //       title: () => (
+                //         <div class={styles.timer}>
+                //           <SkeletonAvatar class={styles.iconTimer} />
+                //           <SkeletonParagraph
+                //             rowWidth={'40%'}
+                //             style={{ marginTop: '0' }}
+                //           />
+                //         </div>
+                //       ),
+                //       label: () => (
+                //         <div class={styles.patrolContainer}>
+                //           <Grid columnNum={2} border={false}>
+                //             <GridItem>
+                //               {{
+                //                 icon: () => (
+                //                   <SkeletonParagraph
+                //                     rowWidth={'40%'}
+                //                     style={{ width: '50px' }}
+                //                     class={styles.num}
+                //                   />
+                //                 ),
+                //                 text: () => (
+                //                   <div class="van-grid-item__text">
+                //                     <SkeletonParagraph
+                //                       rowWidth={'40%'}
+                //                       style={{ width: '50px' }}
+                //                       class={styles.num}
+                //                     />
+                //                   </div>
+                //                 )
+                //               }}
+                //             </GridItem>
+                //             <GridItem>
+                //               {{
+                //                 icon: () => (
+                //                   <SkeletonParagraph
+                //                     rowWidth={'40%'}
+                //                     style={{ width: '50px' }}
+                //                     class={styles.num}
+                //                   />
+                //                 ),
+                //                 text: () => (
+                //                   <div class="van-grid-item__text">
+                //                     <SkeletonParagraph
+                //                       rowWidth={'40%'}
+                //                       style={{ width: '50px' }}
+                //                       class={styles.num}
+                //                     />
+                //                   </div>
+                //                 )
+                //               }}
+                //             </GridItem>
+                //           </Grid>
+
+                //           <SkeletonParagraph
+                //             class={styles.btn}
+                //             rowWidth={'20%'}
+                //           />
+                //         </div>
+                //       )
+                //     }}
+                //   </Cell>
+                // </CellGroup>
+                <div>
+                  <CellGroup inset class={styles.cellGroup}>
+                    <Cell center class={styles.timerCell} border={false}>
+                      {{
+                        icon: () => <SkeletonAvatar class={styles.iconTimer} />,
+                        title: () => (
+                          <div class={styles.timer}>2023-03-24 14:00:32</div>
+                        ),
+                        value: () => (
+                          <div class={styles.eStatus}>
+                            <SkeletonParagraph rowWidth={'50%'} />
+                          </div>
+                        )
+                      }}
+                    </Cell>
+                    <Cell center class={styles.usernameCell}>
+                      {{
+                        icon: () => (
+                          <SkeletonImage class={styles.iconTeacher} />
+                        ),
+                        title: () => (
+                          <div>
+                            <div class={styles.classname}>
+                              声部课·上低音号音号
+                            </div>
+                            <div class={styles.name}>王丹丹</div>
+                          </div>
+                        ),
+                        value: () => (
+                          <div class={styles.photoList}>
+                            <SkeletonImage class={styles.photo} />
+                            <SkeletonImage class={styles.photo} />
+                            <SkeletonImage class={styles.photo} />
+                          </div>
+                        )
+                      }}
+                    </Cell>
+
+                    {/* <Cell center class={styles.resultCell}>
+                      <div class={styles.typeGroup}>
+                        <Tag type="primary" plain>
+                          课堂纪律
+                        </Tag>
+                        <Tag type="primary" plain>
+                          课堂纪律
+                        </Tag>
+                        <Tag type="primary" plain>
+                          课堂纪律
+                        </Tag>
+                        <Tag type="primary" plain>
+                          课堂纪律
+                        </Tag>
+                      </div>
+                      <div class={styles.result}>
+                        没有组织学员将乐器箱包放在指定地点。
+                      </div>
+                    </Cell> */}
+                  </CellGroup>
+                </div>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 181 - 0
src/views/patrol-evaluation/skeletion-index.modal.tsx

@@ -0,0 +1,181 @@
+import {
+  Cell,
+  CellGroup,
+  Grid,
+  GridItem,
+  Skeleton,
+  SkeletonAvatar,
+  SkeletonParagraph
+} from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2, 3, 4, 5, 6]
+    },
+    isLink: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: props.isLink
+                  ? `calc(100vh - var(--header-height))`
+                  : 'auto',
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                <CellGroup inset class={styles.cellGroup}>
+                  <Cell center>
+                    {{
+                      title: () => (
+                        <div class={styles.timer}>
+                          <SkeletonAvatar class={styles.iconTimer} />
+                          <SkeletonParagraph
+                            rowWidth={'40%'}
+                            style={{ marginTop: '0' }}
+                          />
+                        </div>
+                      ),
+                      label: () => (
+                        <div class={styles.patrolContainer}>
+                          <Grid columnNum={2} border={false}>
+                            <GridItem>
+                              {{
+                                icon: () => (
+                                  <SkeletonParagraph
+                                    rowWidth={'40%'}
+                                    style={{ width: '50px' }}
+                                    class={styles.num}
+                                  />
+                                ),
+                                text: () => (
+                                  <div class="van-grid-item__text">
+                                    <SkeletonParagraph
+                                      rowWidth={'40%'}
+                                      style={{ width: '50px' }}
+                                      class={styles.num}
+                                    />
+                                  </div>
+                                )
+                              }}
+                            </GridItem>
+                            <GridItem>
+                              {{
+                                icon: () => (
+                                  <SkeletonParagraph
+                                    rowWidth={'40%'}
+                                    style={{ width: '50px' }}
+                                    class={styles.num}
+                                  />
+                                ),
+                                text: () => (
+                                  <div class="van-grid-item__text">
+                                    <SkeletonParagraph
+                                      rowWidth={'40%'}
+                                      style={{ width: '50px' }}
+                                      class={styles.num}
+                                    />
+                                  </div>
+                                )
+                              }}
+                            </GridItem>
+                          </Grid>
+
+                          <SkeletonParagraph
+                            class={styles.btn}
+                            rowWidth={'20%'}
+                          />
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+                // <div class={styles.cellGroup}>
+                //   <Cell
+                //     center
+                //     isLink={props.isLink}
+                //     clickable={false}
+                //     class={styles.cell}>
+                //     {{
+                //       icon: () => <SkeletonAvatar class={styles.iconTeacher} />,
+
+                //       title: () => (
+                //         <div class={styles.username} style={{ width: '80px' }}>
+                //           <SkeletonParagraph rowWidth={'50%'} />
+                //           <SkeletonParagraph
+                //             style={{
+                //               marginTop: '4px'
+                //             }}
+                //           />
+                //         </div>
+                //       ),
+                //       value: () => (
+                //         <div
+                //           class={[
+                //             styles.attendance,
+                //             !props.isLink ? styles.noLink : ''
+                //           ]}>
+                //           <div class={[styles.attendanceItem]}>
+                //             <SkeletonParagraph
+                //               class={styles.value}
+                //               rowWidth={'40%'}
+                //             />
+                //             <SkeletonParagraph
+                //               class={styles.value}
+                //               rowWidth={'40%'}
+                //             />
+                //           </div>
+                //           <div class={[styles.attendanceItem]}>
+                //             <SkeletonParagraph
+                //               class={styles.value}
+                //               rowWidth={'40%'}
+                //             />
+                //             <SkeletonParagraph
+                //               class={styles.value}
+                //               rowWidth={'40%'}
+                //             />
+                //           </div>
+                //         </div>
+                //       )
+                //     }}
+                //   </Cell>
+                // </div>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 85 - 11
src/views/school-register/index.tsx

@@ -1,36 +1,100 @@
-import { defineComponent, reactive } from 'vue';
+import { defineComponent, onMounted, reactive } from 'vue';
 import styles from './index.module.less';
-import { Button, Cell, CellGroup, Field, Popup } from 'vant';
+import { Button, Cell, CellGroup, Field, Popup, showToast } from 'vant';
 import banner from './images/banner.png';
 import iconSchool from './images/icon-school.png';
 import iconTips from './images/icon-tips.png';
 import bannerPopup from './images/banner-popup.png';
 import MSticky from '@/components/m-sticky';
 import MProtocol from '@/components/m-protocol';
+import { useRoute, useRouter } from 'vue-router';
+import request from '@/helpers/request';
+import { checkPhone } from '@/helpers/utils';
+import { EShoolStaffType } from '@/helpers/constant';
 
 export default defineComponent({
   name: 'school-register',
   setup() {
+    const route = useRoute();
+    const router = useRouter();
     const forms = reactive({
+      id: route.query.id,
+      type: (route.query.type || '') as string,
       username: '',
       phone: '',
       isAgree: false,
-      registerStatus: false
+      registerStatus: false,
+      schoolDetail: {} as any
     });
 
+    const getDetail = async () => {
+      try {
+        const { data } = await request.get('/api-web/open/school/cooperation', {
+          params: {
+            id: forms.id
+          }
+        });
+        forms.schoolDetail = data || {};
+        console.log(data, 'data');
+      } catch {
+        //
+      }
+    };
+
     const onSubmit = async () => {
-      forms.registerStatus = true;
+      if (!forms.username) {
+        showToast('请输入真实姓名');
+        return;
+      }
+      if (!checkPhone(forms.phone)) {
+        showToast('请输入正确的手机号');
+        return;
+      }
+
+      if (!forms.isAgree) {
+        showToast('请阅读并同意注册协议');
+        return;
+      }
+      try {
+        await request.post('/api-web/open/school/staffSave', {
+          hideLoading: false,
+          data: {
+            schoolId: forms.schoolDetail.id,
+            userType: forms.type,
+            username: forms.username,
+            mobile: forms.phone
+          }
+        });
+
+        forms.registerStatus = true;
+      } catch {
+        //
+      }
     };
 
+    const onDownload = () => {
+      forms.registerStatus = false;
+      router.push('/download');
+    };
+
+    onMounted(() => {
+      if (!forms.id) {
+        showToast('链接有误');
+        return;
+      }
+      getDetail();
+    });
     return () => (
       <div class={styles['school-register']}>
         <div class={styles.banner}>
           <img src={banner} alt="banner" />
           <div class={styles.bannerContainer}>
-            <div class={styles.bannerTitle}>乐团领队注册</div>
+            <div class={styles.bannerTitle}>
+              {EShoolStaffType[forms.type]}注册
+            </div>
             <div class={styles.bannerSchool}>
               <img src={iconSchool} class={styles.iconSchool} />
-              <p class={styles.schoolName}>武汉市武昌区中山路小学第一分校</p>
+              <p class={styles.schoolName}>{forms.schoolDetail.name}</p>
             </div>
           </div>
         </div>
@@ -40,7 +104,8 @@ export default defineComponent({
             labelAlign="top"
             class="border"
             v-model={forms.username}
-            placeholder="请填写谷尚居的真实姓名">
+            placeholder="请填写谷尚居的真实姓名"
+            maxlength={8}>
             {{
               label: () => (
                 <>
@@ -52,7 +117,8 @@ export default defineComponent({
           <Field
             labelAlign="top"
             v-model={forms.phone}
-            placeholder="请填写您的手机号码">
+            placeholder="请填写您的手机号码"
+            maxlength={11}>
             {{
               label: () => (
                 <>
@@ -71,7 +137,10 @@ export default defineComponent({
         </CellGroup>
 
         <MSticky position="bottom">
-          <MProtocol style={{ textAlign: 'center' }} />
+          <MProtocol
+            style={{ textAlign: 'center' }}
+            v-model:modelValue={forms.isAgree}
+          />
           <div class={['btnGroupFixed']}>
             <Button round block type="primary" onClick={onSubmit}>
               提交
@@ -87,11 +156,16 @@ export default defineComponent({
             <img src={bannerPopup} class={styles.bannerPopup} />
             <h3>注册成功</h3>
             <div class={styles.popupContent}>
-              恭喜您注册成功为武昌区中山路小学<span>【分管领导】</span>
+              恭喜您注册成功为武昌区中山路小学
+              <span>【{EShoolStaffType[forms.type]}】</span>
               ,请下载管乐迷学校端App进行乐团管理吧~
             </div>
 
-            <Button type="primary" round class={styles.popupBtn}>
+            <Button
+              type="primary"
+              round
+              class={styles.popupBtn}
+              onClick={onDownload}>
               立即下载
             </Button>
           </div>

+ 12 - 12
src/views/site-management/index.module.less

@@ -1,17 +1,13 @@
 .siteManagement {
-  --van-dropdown-menu-title-font-size: 14px;
-  --van-button-normal-font-size: 16px;
-  --van-dropdown-menu-height: 44px;
+  // --van-dropdown-menu-title-font-size: 14px;
+  // --van-button-normal-font-size: 16px;
+  // --van-dropdown-menu-height: 44px;
 
-  :global {
-    .van-dropdown-menu__bar {
-      box-shadow: none;
-    }
-
-    .van-dropdown-item__content {
-      border-radius: 0px 0px 12px 12px;
-    }
-  }
+  // :global {
+  //   .van-dropdown-menu__bar {
+  //     box-shadow: none;
+  //   }
+  // }
 
   .iconSetting {
     font-size: 24px;
@@ -146,6 +142,8 @@
       .van-image {
         width: inherit;
         height: inherit;
+        border-radius: 2px;
+        overflow: hidden;
       }
     }
 
@@ -162,6 +160,8 @@
       display: flex;
       align-items: center;
       justify-content: center;
+      border-radius: 2px;
+      overflow: hidden;
     }
   }
 

+ 252 - 172
src/views/site-management/index.tsx

@@ -1,61 +1,90 @@
 import MHeader from '@/components/m-header';
 import MSticky from '@/components/m-sticky';
-import { Cell, CellGroup, DropdownItem, DropdownMenu, Icon, Image } from 'vant';
+import {
+  ActionBar,
+  Cell,
+  CellGroup,
+  DropdownItem,
+  DropdownMenu,
+  Icon,
+  Image,
+  List
+} from 'vant';
 import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import DropDownModal from './drop-down-modal';
 import iconSetting from '@/common/images/icon-setting.png';
 import iconMusic from '@/common/images/icon-music.png';
 import iconTeacher from '@/common/images/icon-teacher-default.png';
-// import iconEmpty from './images/icon-empty.png';
+import iconEmpty from './images/icon-empty.png';
 import MImagePreview from '@/components/m-image-preview';
 import SkeletonModal from './skeleton-modal';
 import MEmpty from '@/components/m-empty';
 import MFullRefresh from '@/components/m-full-refresh';
+import { useRouter } from 'vue-router';
+import request from '@/helpers/request';
+import KActionSheet from '@/component-ui/k-action-sheet';
+import dayjs from 'dayjs';
 
 export default defineComponent({
   name: 'site-management',
   setup() {
+    const router = useRouter();
     const dropDownItemRef = ref();
     const dropDownItemRef1 = ref();
     const forms = reactive({
-      loading: true,
-      refreshing: false,
-      titleTimeValue: '',
+      isClick: false,
+      titleTimeValue: [],
       titleOrchestraValue: '',
-      timeColumns: [
-        { text: '全部时间', value: '' },
-        { text: '杭州', value: 'Hangzhou' },
-        { text: '宁波', value: 'Ningbo' },
-        { text: '温州', value: 'Wenzhou' },
-        { text: '绍兴', value: 'Shaoxing' },
-        { text: '湖州', value: 'Huzhou' }
-      ],
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: true,
+        finished: false,
+        refreshing: false
+      },
+      params: {
+        startTime: null,
+        endTime: null,
+        musicGroupId: null,
+        page: 1,
+        rows: 20
+      },
+      timeColumns: [],
+      orchestraColumns: [{ text: '全部乐团', value: '' }], //
       imageShow: false,
       startPosition: 0,
-      imagePreview: [
-        'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7108568f10844c1b25db8cebf971caa_mergeImage.png',
-        'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7d77ad6169c4b688cea7183dfabbae2_mergeImage.png',
-        'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/5714fa27180d4115b5bcae6cc2a9ea4f_mergeImage.png'
-      ] as string[]
+      imagePreview: [] as string[],
+      pointCourseStatus: false, // 红点
+      list: []
     });
 
+    const getInitTimer = (week = 4) => {
+      const tempTimer: any = [];
+      for (let i = 0; i < week; i++) {
+        const startDay = dayjs()
+          .subtract(i, 'week')
+          .startOf('week')
+          .add(1, 'day');
+        const endDay = dayjs().subtract(i, 'week').endOf('week').add(1, 'day');
+        tempTimer.push({
+          text:
+            startDay.format('YYYY/MM/DD') + ' - ' + endDay.format('YYYY/MM/DD'),
+          value: [startDay.format('YYYY-MM-DD'), endDay.format('YYYY-MM-DD')]
+        });
+      }
+      console.log(tempTimer, 'tempTimer');
+      forms.timeColumns = tempTimer;
+      forms.titleTimeValue = tempTimer[0].value;
+    };
+
     const onDropDownClose = (item: any) => {
       item.value && item.value.toggle();
     };
 
     const formatName = (type: string) => {
-      if (type === 'time') {
-        let name = '';
-        forms.timeColumns.forEach((item: any) => {
-          if (forms.titleTimeValue === item.value) {
-            name = item.text;
-          }
-        });
-        return name;
-      } else if (type === 'orchestra') {
+      if (type === 'orchestra') {
         let name = '';
-        forms.timeColumns.forEach((item: any) => {
+        forms.orchestraColumns.forEach((item: any) => {
           if (forms.titleOrchestraValue === item.value) {
             name = item.text;
           }
@@ -64,10 +93,65 @@ export default defineComponent({
       }
     };
 
-    onMounted(() => {
-      setTimeout(() => {
-        forms.loading = false;
-      }, 2000);
+    const getList = async () => {
+      try {
+        if (forms.isClick) return;
+        forms.isClick = true;
+        const { data } = await request.post(
+          '/api-web/classGroup/teachingPointCourse',
+          {
+            data: {
+              ...forms.params,
+              startTime: forms.titleTimeValue[0] || null,
+              endTime: forms.titleTimeValue[1] || null
+            }
+          }
+        );
+        const result = data || {};
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.pageNo === 1) {
+          return;
+        }
+        // 判断是否有数据
+        if (forms.listState.refreshing) {
+          forms.list = result.rows || [];
+        } else {
+          forms.list = forms.list.concat(result.rows || []);
+        }
+
+        forms.listState.finished = result.pageNo >= result.pages;
+        forms.params.page = result.pageNo + 1;
+      } catch {
+        forms.listState.finished = true;
+      } finally {
+        forms.listState.dataShow = forms.list.length > 0;
+        forms.listState.refreshing = false;
+        forms.listState.loading = false;
+        forms.isClick = false;
+      }
+    };
+
+    // 红点
+    const teachingPointCourse = async () => {
+      try {
+        const { data } = await request.post(
+          '/api-web/classGroup/teachingPointRemind'
+        );
+        forms.pointCourseStatus = data || false;
+      } catch {
+        //
+      }
+    };
+
+    const onRefresh = () => {
+      forms.params.page = 1;
+      getList();
+    };
+
+    onMounted(async () => {
+      getInitTimer();
+      await getList();
+      teachingPointCourse();
     });
     return () => (
       <div class={styles.siteManagement}>
@@ -75,31 +159,29 @@ export default defineComponent({
           <MHeader>
             {{
               right: () => (
-                <Icon class={styles.iconSetting} name={iconSetting} dot />
+                <Icon
+                  class={styles.iconSetting}
+                  name={iconSetting}
+                  dot={forms.pointCourseStatus}
+                  onClick={() => {
+                    router.push('/site-settings');
+                  }}
+                />
               )
             }}
           </MHeader>
 
           <DropdownMenu>
-            <DropdownItem ref={dropDownItemRef} title={formatName('time')}>
-              <DropDownModal
-                selectValues={forms.titleTimeValue}
-                columns={forms.timeColumns}
-                open={dropDownItemRef.value.state.showPopup}
-                onDropDownClose={() => onDropDownClose(dropDownItemRef)}
-                onDropDownConfirm={(values: any) => {
-                  forms.titleTimeValue = values[0];
-                  console.log(values, 'time');
-                  onDropDownClose(dropDownItemRef);
-                }}
-              />
-            </DropdownItem>
+            <DropdownItem
+              ref={dropDownItemRef}
+              v-model={forms.titleTimeValue}
+              options={forms.timeColumns as any}></DropdownItem>
             <DropdownItem
               ref={dropDownItemRef1}
               title={formatName('orchestra')}>
               <DropDownModal
                 selectValues={forms.titleOrchestraValue}
-                columns={forms.timeColumns}
+                columns={forms.orchestraColumns}
                 open={dropDownItemRef1.value.state.showPopup}
                 onDropDownClose={() => onDropDownClose(dropDownItemRef1)}
                 onDropDownConfirm={(values: any) => {
@@ -112,135 +194,133 @@ export default defineComponent({
           </DropdownMenu>
         </MSticky>
 
-        <MFullRefresh
-          v-model:modelValue={forms.refreshing}
-          onRefresh={() => {
-            setTimeout(() => {
-              forms.refreshing = false;
-            }, 1000);
-          }}
-          style={{
-            minHeight: `calc(100vh - var(--header-height))`
-          }}>
-          <SkeletonModal v-model:show={forms.loading}>
-            {/* <MEmpty
+        <SkeletonModal v-model:show={forms.listState.loading}>
+          <MFullRefresh
+            v-model:modelValue={forms.listState.refreshing}
+            onRefresh={() => onRefresh()}
             style={{
               minHeight: `calc(100vh - var(--header-height))`
-            }}
-            description="暂无数据"
-          /> */}
-            <div class={styles.siteItem}>
-              <CellGroup class={styles.cellGroup} border={false}>
-                <Cell border={false} center>
-                  {{
-                    title: () => (
-                      <div class={styles.orchestraName}>
-                        <img src={iconMusic} class={styles.iconMusic} />
-                        <p class={styles.overhide}>
-                          武汉小学乐团武汉小学乐团武汉小学乐团武汉小学乐团
-                        </p>
-                      </div>
-                    ),
-                    default: () => (
-                      <p class={[styles.address, styles.overhide]}>
-                        1年级1班1年级1班1年级1班
-                      </p>
-                    )
-                  }}
-                </Cell>
-                <Cell center class={styles.username}>
-                  {{
-                    icon: () => (
-                      <Image
-                        src={iconTeacher}
-                        class={styles.iconTeacher}
-                        fit="contain"
-                      />
-                    ),
-                    title: () => (
-                      <div>
-                        <div class={styles.classname}>声部课·上低音号</div>
-                        <div class={styles.name}>王丹丹</div>
+            }}>
+            <List
+              finished={forms.listState.finished}
+              finishedText=" "
+              style={{ overflow: 'hidden' }}
+              onLoad={getList}
+              immediateCheck={false}>
+              {forms.listState.dataShow ? (
+                forms.list.map((item: any) => (
+                  <div class={styles.siteItem}>
+                    <CellGroup class={styles.cellGroup} border={false}>
+                      <Cell border={false} center>
+                        {{
+                          title: () => (
+                            <div class={styles.orchestraName}>
+                              <img src={iconMusic} class={styles.iconMusic} />
+                              <p class={styles.overhide}>
+                                {item.musicGroupName}
+                              </p>
+                            </div>
+                          ),
+                          default: () => (
+                            <p class={[styles.address, styles.overhide]}>
+                              1年级1班1年级1班1年级1班
+                            </p>
+                          )
+                        }}
+                      </Cell>
+                      <Cell center class={styles.username}>
+                        {{
+                          icon: () => (
+                            <Image
+                              src={iconTeacher}
+                              class={styles.iconTeacher}
+                              fit="contain"
+                            />
+                          ),
+                          title: () => (
+                            <div>
+                              <div class={styles.classname}>
+                                {item.courseName}
+                              </div>
+                              <div class={styles.name}>{item.teacherName}</div>
+                            </div>
+                          )
+                        }}
+                      </Cell>
+                    </CellGroup>
+                    <CellGroup class={styles.cellGroup}>
+                      <div class={[styles.photoGroup]}>
+                        <div class={styles.photoUp}>
+                          <h3>
+                            <span class={styles.photoTitle}>课前照片</span>
+                          </h3>
+                          {item.signPhoto ? (
+                            <div class={styles.photoList}>
+                              {(item.signPhoto.split(',') || []).map(
+                                (img: string, index: number) => (
+                                  <div
+                                    class={styles.photo}
+                                    onClick={() => {
+                                      forms.imagePreview =
+                                        item.signPhoto.split(',') || [];
+                                      forms.imageShow = true;
+                                      forms.startPosition = index;
+                                    }}>
+                                    <Image src={img} />
+                                  </div>
+                                )
+                              )}
+                            </div>
+                          ) : (
+                            <div class={styles.photoEmpty}>
+                              <img src={iconEmpty} class={styles.iconEmpty} />
+                              <p>老师未上传照片~</p>
+                            </div>
+                          )}
+                        </div>
+                        <div class={styles.photoDown}>
+                          <h3>
+                            <span class={styles.photoTitle}>课后照片</span>
+                          </h3>
+                          {item.signOutPhoto ? (
+                            <div class={styles.photoList}>
+                              {(item.signOutPhoto.split(',') || []).map(
+                                (img: string, index: number) => (
+                                  <div
+                                    class={styles.photo}
+                                    onClick={() => {
+                                      forms.imagePreview =
+                                        item.signOutPhoto.split(',') || [];
+                                      forms.imageShow = true;
+                                      forms.startPosition = index;
+                                    }}>
+                                    <Image src={img} />
+                                  </div>
+                                )
+                              )}
+                            </div>
+                          ) : (
+                            <div class={styles.photoEmpty}>
+                              <img src={iconEmpty} class={styles.iconEmpty} />
+                              <p>老师未上传照片~</p>
+                            </div>
+                          )}
+                        </div>
                       </div>
-                    )
-                  }}
-                </Cell>
-              </CellGroup>
-              <CellGroup class={styles.cellGroup}>
-                <div class={[styles.photoGroup]}>
-                  <div class={styles.photoUp}>
-                    <h3>
-                      <span class={styles.photoTitle}>课前照片</span>
-                    </h3>
-                    <div class={styles.photoList}>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 0;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7108568f10844c1b25db8cebf971caa_mergeImage.png" />
-                      </div>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 1;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7d77ad6169c4b688cea7183dfabbae2_mergeImage.png" />
-                      </div>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 2;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/5714fa27180d4115b5bcae6cc2a9ea4f_mergeImage.png" />
-                        <div class={styles.photoMore}>+8</div>
-                      </div>
-                    </div>
-                    {/* <div class={styles.photoEmpty}>
-                  <img src={iconEmpty} class={styles.iconEmpty} />
-                  <p>老师未上传照片~</p>
-                </div> */}
+                    </CellGroup>
                   </div>
-                  <div class={styles.photoDown}>
-                    <h3>
-                      <span class={styles.photoTitle}>课后照片</span>
-                    </h3>
-                    <div class={styles.photoList}>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 0;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7108568f10844c1b25db8cebf971caa_mergeImage.png" />
-                      </div>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 1;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/d7d77ad6169c4b688cea7183dfabbae2_mergeImage.png" />
-                      </div>
-                      <div
-                        class={styles.photo}
-                        onClick={() => {
-                          forms.imageShow = true;
-                          forms.startPosition = 2;
-                        }}>
-                        <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/5714fa27180d4115b5bcae6cc2a9ea4f_mergeImage.png" />
-                        <div class={styles.photoMore}>+8</div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </CellGroup>
-            </div>
-          </SkeletonModal>
-        </MFullRefresh>
+                ))
+              ) : (
+                <MEmpty
+                  style={{
+                    minHeight: `calc(100vh - var(--header-height))`
+                  }}
+                  description="暂无数据"
+                />
+              )}
+            </List>
+          </MFullRefresh>
+        </SkeletonModal>
 
         <MImagePreview
           v-model:show={forms.imageShow}

+ 122 - 0
src/views/site-management/site-settings.module.less

@@ -0,0 +1,122 @@
+.searchGroup {
+  padding: 10px 13px;
+}
+
+.searchItem {
+  display: inline-flex;
+  align-items: center;
+  position: relative;
+  height: 26px;
+  background: #fff;
+  border-radius: 13px;
+  padding: 0 21px 0 12px;
+  font-size: 14px;
+  color: var(--k-gray-1);
+
+  &::after {
+    position: absolute;
+    top: 50%;
+    right: 10px;
+    margin-top: -5px;
+    border: 0.08rem solid;
+    border-color: transparent transparent var(--k-gray-4) var(--k-gray-4);
+    transform: rotate(-45deg);
+    content: "";
+  }
+}
+
+.overhide {
+  max-width: 150px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.cellGroup {
+  margin: 0 13px 12px;
+  border-radius: 10px;
+  overflow: hidden;
+
+  :global {
+    .van-cell {
+      padding: 12px;
+    }
+  }
+
+  .orchestraName {
+    display: flex;
+    align-items: center;
+    line-height: 20px;
+    font-size: 14px;
+    color: var(--k-gray-3);
+  }
+
+  .iconMusic {
+    width: 18px;
+    height: 18px;
+    margin-right: 4px;
+  }
+
+  .address {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    color: var(--k-gray-1);
+
+    .overhide {
+      max-width: 120px;
+    }
+
+    .iconEdit {
+      font-size: 16px;
+      margin-left: 4px;
+    }
+
+    .red {
+      color: #FF5A56;
+    }
+  }
+
+  .iconTeacher {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    margin-right: 8px;
+  }
+
+  .username {
+    padding: 8px 12px 16px;
+    color: var(--k-gray-3);
+    font-size: 14px;
+    line-height: 20px;
+
+    .classname {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+
+      line-height: 22px;
+    }
+  }
+}
+
+.popupContainer {
+  width: 315px;
+
+  h2 {
+    text-align: center;
+    padding-top: 20px;
+    padding-bottom: 25px;
+    font-weight: 600;
+    font-size: 18px;
+    color: var(--k-gray-1);
+  }
+
+  .field {
+    width: auto;
+    margin: 0 18px;
+    background: #F6F6F6;
+    height: 47px;
+    border-radius: 6px;
+  }
+}

+ 202 - 0
src/views/site-management/site-settings.tsx

@@ -0,0 +1,202 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './site-settings.module.less';
+import MSticky from '@/components/m-sticky';
+import MHeader from '@/components/m-header';
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Field,
+  Icon,
+  Image,
+  List,
+  Picker,
+  Popup
+} from 'vant';
+import iconMusic from '@/common/images/icon-music.png';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconEdit from '@/common/images/icon-edit.png';
+import SkeletonSettingsModal from './skeleton-settings-modal';
+import MFullRefresh from '@/components/m-full-refresh';
+import request from '@/helpers/request';
+import MEmpty from '@/components/m-empty';
+
+export default defineComponent({
+  name: 'site-setting',
+  setup() {
+    const forms = reactive({
+      showPopup: false,
+      showOrchestraPopup: false,
+      siteName: '',
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: true,
+        finished: false,
+        refreshing: false
+      },
+      params: {
+        musicGroupId: null,
+        page: 1,
+        rows: 20
+      },
+      isClick: false,
+      list: []
+    });
+
+    const onEdit = () => {
+      forms.showPopup = true;
+    };
+
+    const getList = async () => {
+      try {
+        if (forms.isClick) return;
+        forms.isClick = true;
+        const { data } = await request.post(
+          '/api-web/classGroup/teachingPointCourse',
+          {
+            data: forms.params
+          }
+        );
+        const result = data || {};
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.pageNo === 1) {
+          return;
+        }
+        // 判断是否有数据
+        if (forms.listState.refreshing) {
+          forms.list = result.rows || [];
+        } else {
+          forms.list = forms.list.concat(result.rows || []);
+        }
+
+        forms.listState.finished = result.pageNo >= result.pages;
+        forms.params.page = result.pageNo + 1;
+      } catch {
+        forms.listState.finished = true;
+      } finally {
+        forms.listState.dataShow = forms.list.length > 0;
+        forms.listState.refreshing = false;
+        forms.listState.loading = false;
+        forms.isClick = false;
+      }
+    };
+
+    const onRefresh = () => {
+      forms.params.page = 1;
+      getList();
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div class={styles.siteSetting}>
+        <MSticky position="top">
+          <MHeader />
+
+          <div class={styles.searchGroup}>
+            <div
+              class={styles.searchItem}
+              onClick={() => (forms.showOrchestraPopup = true)}>
+              <span>全部乐团</span>
+            </div>
+          </div>
+        </MSticky>
+
+        <SkeletonSettingsModal v-model:show={forms.listState.loading}>
+          <MFullRefresh
+            v-model:modelValue={forms.listState.refreshing}
+            onRefresh={() => onRefresh()}
+            style={{
+              minHeight: `calc(100vh - var(--header-height))`
+            }}>
+            <List
+              finished={forms.listState.finished}
+              finishedText=" "
+              style={{ overflow: 'hidden' }}
+              onLoad={getList}
+              immediateCheck={false}>
+              {forms.listState.dataShow ? (
+                <CellGroup class={styles.cellGroup} border={false}>
+                  <Cell border={false} center>
+                    {{
+                      title: () => (
+                        <div class={styles.orchestraName}>
+                          <img src={iconMusic} class={styles.iconMusic} />
+                          <p class={styles.overhide}>
+                            武汉小学乐团武汉小学乐团武汉小学乐团武汉小学乐团
+                          </p>
+                        </div>
+                      ),
+                      default: () => (
+                        <div class={styles.address} onClick={() => onEdit()}>
+                          <p class={[styles.overhide]}>1年级1班</p>
+                          <Icon name={iconEdit} class={styles.iconEdit} />
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                  <Cell center class={styles.username}>
+                    {{
+                      icon: () => (
+                        <Image
+                          src={iconTeacher}
+                          class={styles.iconTeacher}
+                          fit="contain"
+                        />
+                      ),
+                      title: () => (
+                        <div>
+                          <div class={styles.classname}>声部课·上低音号</div>
+                          <div class={styles.name}>王丹丹</div>
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ) : (
+                <MEmpty
+                  style={{
+                    minHeight: `calc(100vh - var(--header-height))`
+                  }}
+                  description="暂无数据"
+                />
+              )}
+            </List>
+          </MFullRefresh>
+        </SkeletonSettingsModal>
+
+        <Popup v-model:show={forms.showPopup} round>
+          <div class={styles.popupContainer}>
+            <h2>设置场地</h2>
+            <Field
+              placeholder="请输入场地名称"
+              v-model={forms.siteName}
+              border={false}
+              class={styles.field}
+              maxlength={15}
+            />
+            <div class={['btnGroupPopup']}>
+              <Button round onClick={() => (forms.showPopup = false)}>
+                取消
+              </Button>
+              <Button type="primary" round>
+                确定
+              </Button>
+            </div>
+          </div>
+        </Popup>
+
+        <Popup v-model:show={forms.showOrchestraPopup} round position="bottom">
+          <Picker
+            columns={[]}
+            onCancel={() => (forms.showOrchestraPopup = false)}
+            onConfirm={() => {
+              forms.showOrchestraPopup = false;
+            }}
+          />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 93 - 0
src/views/site-management/skeleton-settings-modal.tsx

@@ -0,0 +1,93 @@
+import {
+  Cell,
+  CellGroup,
+  Skeleton,
+  SkeletonAvatar,
+  SkeletonParagraph
+} from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './site-settings.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2, 3, 4, 5, 6]
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: `calc(100vh - var(--header-height))`,
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                <CellGroup class={styles.cellGroup}>
+                  <Cell center>
+                    {{
+                      icon: () => <SkeletonAvatar class={styles.iconMusic} />,
+                      title: () => (
+                        <div
+                          style={{
+                            display: 'flex',
+                            justifyContent: 'space-between'
+                          }}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph
+                            style={{
+                              marginTop: 0
+                            }}
+                            rowWidth={'40%'}
+                          />
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                  <Cell center class={styles.username}>
+                    {{
+                      icon: () => <SkeletonAvatar class={styles.iconTeacher} />,
+                      title: () => (
+                        <div>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph
+                            rowWidth={'40%'}
+                            style={{ marginTop: '4px' }}
+                          />
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 160 - 0
src/views/teacher-attendance/detail.tsx

@@ -0,0 +1,160 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import SkeletionIndexModal from './skeletion-index-modal';
+import { Cell, CellGroup, DropdownItem, DropdownMenu, Icon, Image } from 'vant';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconSuccess from '@/common/images/icon-check-active.png';
+import iconWarn from '@/common/images/icon-warn.png';
+import SkeletionDetailModal from './skeletion-detail-modal';
+import MHeader from '@/components/m-header';
+
+export default defineComponent({
+  name: 'teacher-attendance-detail',
+  setup() {
+    const forms = reactive({
+      listState: {
+        loading: true,
+        loadingList: true
+      },
+      classValue: '',
+      classColumns: [{ text: '全部状态', value: '' }],
+      typeValue: '',
+      typeColumns: [{ text: '全部状态', value: '' }]
+    });
+
+    onMounted(() => {
+      setTimeout(() => {
+        forms.listState.loading = false;
+        forms.listState.loadingList = false;
+      }, 1000);
+    });
+    return () => (
+      <div class={styles.teacherAttendanceDetail}>
+        <MHeader />
+        <SkeletionIndexModal
+          v-model:show={forms.listState.loading}
+          isLink={false}
+          showCount={[1]}>
+          <div class={styles.cellGroup}>
+            <Cell center clickable={false} class={styles.cell}>
+              {{
+                icon: () => (
+                  <Image
+                    src={iconTeacher}
+                    fit="contain"
+                    class={styles.iconTeacher}
+                  />
+                ),
+
+                title: () => (
+                  <div class={styles.username}>
+                    <p class={styles.name}>孙忆枫</p>
+                    <p class={styles.class}>长笛声部班</p>
+                  </div>
+                ),
+                value: () => (
+                  <div class={[styles.attendance, styles.noLink]}>
+                    <div class={[styles.attendanceItem, styles.weekAttendance]}>
+                      <p class={[styles.value, styles.error]}>正常</p>
+                      <p class={styles.title}>本周考勤</p>
+                    </div>
+                    <div
+                      class={[styles.attendanceItem, styles.classAttendance]}>
+                      <p class={[styles.value, styles.success]}>
+                        2<span>课时</span>
+                      </p>
+                      <p class={styles.title}>学期异常</p>
+                    </div>
+                  </div>
+                )
+              }}
+            </Cell>
+          </div>
+        </SkeletionIndexModal>
+
+        <DropdownMenu>
+          <DropdownItem
+            v-model={forms.classValue}
+            options={forms.classColumns as any}></DropdownItem>
+          <DropdownItem
+            v-model={forms.typeValue}
+            options={forms.typeColumns as any}></DropdownItem>
+        </DropdownMenu>
+
+        <SkeletionDetailModal v-model:show={forms.listState.loadingList}>
+          {[1, 2, 3, 4].map(() => (
+            <CellGroup inset class={styles.detailCellGroup}>
+              <Cell center border={false} class={styles.className}>
+                {{
+                  title: () => <div class={styles.class}>集训声部课</div>,
+                  value: () => (
+                    <div class={styles.timer}>2023-03-14 13:00~14:00</div>
+                  )
+                }}
+              </Cell>
+              <Cell center>
+                <div class={styles.detailGroup}>
+                  <div class={styles.detailItem}>
+                    <div class={styles.detailStatus}>
+                      <span class={styles.statusName}>正常</span>
+                      <img src={iconSuccess} class={styles.img} />
+                    </div>
+                    <div class={styles.sign}>
+                      <span class={styles.signTime}>签到地点</span>
+                      <span class={styles.locate}>
+                        查看定位 <Icon name="arrow" class={styles.iconArrow} />
+                      </span>
+                    </div>
+                  </div>
+                  <div class={styles.detailItem}>
+                    <div class={styles.detailStatus}>
+                      <span class={styles.statusName}>正常</span>
+                      <img src={iconSuccess} class={styles.img} />
+                    </div>
+                    <div class={styles.sign}>
+                      <span class={styles.signTime}>签到地点</span>
+                      <span class={styles.locate}>
+                        查看定位
+                        <Icon name="arrow" class={styles.iconArrow} />
+                      </span>
+                    </div>
+                  </div>
+                  <div class={styles.detailItem}>
+                    <div class={styles.detailStatus}>
+                      <span class={[styles.statusName, styles.error]}>
+                        异常签退
+                      </span>
+                      <img src={iconWarn} class={styles.img} />
+                    </div>
+                    <div class={styles.sign}>
+                      <span class={styles.signTime}>签到地点</span>
+                      <span class={styles.locate}>
+                        查看定位
+                        <Icon name="arrow" class={styles.iconArrow} />
+                      </span>
+                    </div>
+                  </div>
+                  <div class={styles.detailItem}>
+                    <div class={styles.detailStatus}>
+                      <span class={[styles.statusName, styles.error]}>
+                        异常
+                      </span>
+                      <img src={iconWarn} class={styles.img} />
+                    </div>
+                    <div class={styles.sign}>
+                      <span class={styles.signTime}>签到地点</span>
+                      <span class={styles.locate}>
+                        查看定位
+                        <Icon name="arrow" class={styles.iconArrow} />
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </Cell>
+            </CellGroup>
+          ))}
+        </SkeletionDetailModal>
+      </div>
+    );
+  }
+});

+ 192 - 0
src/views/teacher-attendance/index.module.less

@@ -0,0 +1,192 @@
+.cellGroup {
+  overflow: hidden;
+  padding: 0 13px;
+}
+
+.cell {
+  margin-top: 12px;
+  padding: 12px;
+  border-radius: 10px;
+
+  :global {
+    .van-cell__title {
+      flex: 0 auto;
+    }
+  }
+
+  .iconTeacher {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    overflow: hidden;
+    margin-right: 8px;
+  }
+
+  .username {
+    .name {
+      font-size: 16px;
+      font-weight: bold;
+    }
+
+    .class {
+      font-size: 13px;
+      color: var(--k-gray-3);
+      line-height: 22px;
+    }
+  }
+
+  .attendance {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-size: 12px;
+    color: var(--k-gray-3);
+  }
+
+  // 处理没有箭头时样式
+  .noLink .attendanceItem {
+    margin-left: 12px;
+    margin-right: 0;
+  }
+
+  .attendanceItem {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    width: 60px;
+    height: 60px;
+    background: #F6F6F6;
+    border-radius: 6px;
+    margin-right: 12px;
+  }
+
+  .weekAttendance {
+    .value {
+      font-size: 14px;
+      font-weight: 600;
+      color: #999999;
+    }
+  }
+
+  .classAttendance {
+    .value {
+      font-size: 18px;
+
+      span {
+        font-size: 12px;
+        color: var(--k-gray-1);
+      }
+    }
+  }
+}
+
+.error {
+  color: #FF5A56 !important;
+}
+
+.success {
+  color: var(--k-font-primary) !important;
+}
+
+.teacherAttendanceDetail {
+  --van-dropdown-menu-background: transparent;
+
+  :global {
+    .van-cell {
+      padding-left: 12px;
+      padding-right: 12px;
+    }
+  }
+
+  .className {
+    padding-top: 15px;
+    padding-bottom: 0;
+
+    :global {
+      .van-cell__title {
+        flex: 0 auto;
+      }
+    }
+
+    .class {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: 600;
+
+      &::before {
+        content: ' ';
+        margin-right: 6px;
+        width: 3px;
+        height: 12px;
+        background: #01C1B5;
+        border-radius: 4px;
+      }
+    }
+
+    .timer {
+      color: var(--k-gray-1);
+      font-size: 14px;
+    }
+  }
+
+  .skeletionTitle {
+    :global {
+      .van-cell__title {
+        flex: 1;
+      }
+    }
+  }
+
+  .detailCellGroup {
+    margin-bottom: 12px;
+  }
+
+  .detailGroup {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    width: 100%;
+  }
+
+  .detailItem {
+    width: 158px;
+    padding: 12px;
+    background: #F6F6F6;
+    border-radius: 6px;
+    margin-bottom: 9px;
+  }
+
+  .detailStatus {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 16px;
+    font-weight: 600;
+
+    .statusName {
+      color: var(--k-gray-1);
+    }
+
+    .img {
+      width: 16px;
+      height: 16px;
+    }
+  }
+
+  .sign {
+    padding-top: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    color: var(--k-gray-1);
+
+    .locate {
+      font-size: 12px;
+      color: #999999;
+    }
+  }
+}

+ 129 - 0
src/views/teacher-attendance/index.tsx

@@ -0,0 +1,129 @@
+import MHeader from '@/components/m-header';
+import MSearch from '@/components/m-search';
+import MSticky from '@/components/m-sticky';
+import { Cell, DropdownItem, DropdownMenu, Image, List } from 'vant';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import DropDownModal from '../site-management/drop-down-modal';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import SkeletionIndexModal from './skeletion-index-modal';
+import { useRouter } from 'vue-router';
+
+export default defineComponent({
+  name: 'teacher-attendance',
+  setup() {
+    const dropDownItemRef = ref();
+    const dropDownItemRef1 = ref();
+    const router = useRouter();
+    const forms = reactive({
+      listState: {
+        loading: true
+      },
+      statusValue: '',
+      statusColumns: [
+        { text: '全部状态', value: '' },
+        { text: '正常', value: 'normal' },
+        { text: '异常', value: 'error' },
+        { text: '无课', value: 'noClass' }
+      ],
+      classValue: '',
+      classColumns: [{ text: '全部班级', value: '' }]
+    });
+
+    const onDropDownClose = (item: any) => {
+      item.value && item.value.toggle();
+    };
+
+    const formatName = (type: string) => {
+      if (type === 'orchestra') {
+        let name = '';
+        forms.classColumns.forEach((item: any) => {
+          if (forms.classValue === item.value) {
+            name = item.text;
+          }
+        });
+        return name;
+      }
+    };
+
+    onMounted(() => {
+      setTimeout(() => {
+        forms.listState.loading = false;
+      }, 2000);
+    });
+    return () => (
+      <div class={styles.teacherAttendance}>
+        <MSticky position="top">
+          <MHeader />
+          <MSearch placeholder="请输入老师姓名" />
+          <DropdownMenu>
+            <DropdownItem
+              ref={dropDownItemRef}
+              v-model={forms.statusValue}
+              options={forms.statusColumns as any}></DropdownItem>
+            <DropdownItem
+              ref={dropDownItemRef1}
+              title={formatName('orchestra')}>
+              <DropDownModal
+                selectValues={forms.classValue}
+                columns={forms.classColumns}
+                open={dropDownItemRef1.value.state.showPopup}
+                onDropDownClose={() => onDropDownClose(dropDownItemRef1)}
+                onDropDownConfirm={(values: any) => {
+                  forms.classValue = values[0];
+                  console.log(values, 'orchestra');
+                  onDropDownClose(dropDownItemRef1);
+                }}
+              />
+            </DropdownItem>
+          </DropdownMenu>
+        </MSticky>
+
+        <SkeletionIndexModal v-model:show={forms.listState.loading}>
+          <List class={styles.cellGroup}>
+            <Cell
+              center
+              isLink
+              clickable={false}
+              class={styles.cell}
+              onClick={() => {
+                router.push('/teacher-attendance-detail');
+              }}>
+              {{
+                icon: () => (
+                  <Image
+                    src={iconTeacher}
+                    fit="contain"
+                    class={styles.iconTeacher}
+                  />
+                ),
+
+                title: () => (
+                  <div class={styles.username}>
+                    <p class={styles.name}>孙忆枫</p>
+                    <p class={styles.class}>长笛声部班</p>
+                  </div>
+                ),
+                value: () => (
+                  <div class={styles.attendance}>
+                    <div class={[styles.attendanceItem, styles.weekAttendance]}>
+                      <p class={[styles.value, styles.error]}>正常</p>
+                      <p class={styles.title}>本周考勤</p>
+                    </div>
+                    <div
+                      class={[styles.attendanceItem, styles.classAttendance]}>
+                      <p class={[styles.value, styles.success]}>
+                        2<span>课时</span>
+                      </p>
+                      <p class={styles.title}>学期异常</p>
+                    </div>
+                  </div>
+                )
+              }}
+            </Cell>
+          </List>
+        </SkeletionIndexModal>
+      </div>
+    );
+  }
+});

+ 112 - 0
src/views/teacher-attendance/skeletion-detail-modal.tsx

@@ -0,0 +1,112 @@
+import {
+  Cell,
+  CellGroup,
+  Skeleton,
+  SkeletonAvatar,
+  SkeletonParagraph
+} from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2]
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: `calc(100vh - var(--header-height))`,
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                <CellGroup inset class={styles.detailCellGroup}>
+                  <Cell
+                    center
+                    border={false}
+                    class={[styles.className, styles.skeletionTitle]}>
+                    {{
+                      title: () => <SkeletonParagraph rowWidth={'50%'} />,
+                      value: () => <SkeletonParagraph />
+                    }}
+                  </Cell>
+                  <Cell center>
+                    <div class={styles.detailGroup}>
+                      <div class={styles.detailItem}>
+                        <div class={styles.detailStatus}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonAvatar class={styles.img} />
+                        </div>
+                        <div class={styles.sign}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph rowWidth={'40%'} />
+                        </div>
+                      </div>
+                      <div class={styles.detailItem}>
+                        <div class={styles.detailStatus}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonAvatar class={styles.img} />
+                        </div>
+                        <div class={styles.sign}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph rowWidth={'40%'} />
+                        </div>
+                      </div>
+                      <div class={styles.detailItem}>
+                        <div class={styles.detailStatus}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonAvatar class={styles.img} />
+                        </div>
+                        <div class={styles.sign}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph rowWidth={'40%'} />
+                        </div>
+                      </div>
+                      <div class={styles.detailItem}>
+                        <div class={styles.detailStatus}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonAvatar class={styles.img} />
+                        </div>
+                        <div class={styles.sign}>
+                          <SkeletonParagraph rowWidth={'40%'} />
+                          <SkeletonParagraph rowWidth={'40%'} />
+                        </div>
+                      </div>
+                    </div>
+                  </Cell>
+                </CellGroup>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 143 - 0
src/views/teacher-attendance/skeletion-index-modal.tsx

@@ -0,0 +1,143 @@
+import { Cell, Skeleton, SkeletonAvatar, SkeletonParagraph } from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2, 3, 4, 5, 6, 5, 6]
+    },
+    isLink: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: props.isLink
+                  ? `calc(100vh - var(--header-height))`
+                  : 'auto',
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                <div class={styles.cellGroup}>
+                  <Cell
+                    center
+                    isLink={props.isLink}
+                    clickable={false}
+                    class={styles.cell}>
+                    {{
+                      icon: () => <SkeletonAvatar class={styles.iconTeacher} />,
+
+                      title: () => (
+                        <div class={styles.username} style={{ width: '80px' }}>
+                          <SkeletonParagraph rowWidth={'50%'} />
+                          <SkeletonParagraph
+                            style={{
+                              marginTop: '4px'
+                            }}
+                          />
+                        </div>
+                      ),
+                      value: () => (
+                        <div
+                          class={[
+                            styles.attendance,
+                            !props.isLink ? styles.noLink : ''
+                          ]}>
+                          <div class={[styles.attendanceItem]}>
+                            <SkeletonParagraph
+                              class={styles.value}
+                              rowWidth={'40%'}
+                            />
+                            <SkeletonParagraph
+                              class={styles.value}
+                              rowWidth={'40%'}
+                            />
+                          </div>
+                          <div class={[styles.attendanceItem]}>
+                            <SkeletonParagraph
+                              class={styles.value}
+                              rowWidth={'40%'}
+                            />
+                            <SkeletonParagraph
+                              class={styles.value}
+                              rowWidth={'40%'}
+                            />
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </div>
+                // <CellGroup class={styles.cellGroup}>
+                //   <Cell center>
+                //     {{
+                //       icon: () => <SkeletonAvatar class={styles.iconMusic} />,
+                //       title: () => (
+                //         <div
+                //           style={{
+                //             display: 'flex',
+                //             justifyContent: 'space-between'
+                //           }}>
+                //           <SkeletonParagraph rowWidth={'40%'} />
+                //           <SkeletonParagraph
+                //             style={{
+                //               marginTop: 0
+                //             }}
+                //             rowWidth={'40%'}
+                //           />
+                //         </div>
+                //       )
+                //     }}
+                //   </Cell>
+                //   <Cell center class={styles.username}>
+                //     {{
+                //       icon: () => <SkeletonAvatar class={styles.iconTeacher} />,
+                //       title: () => (
+                //         <div>
+                //           <SkeletonParagraph rowWidth={'40%'} />
+                //           <SkeletonParagraph
+                //             rowWidth={'40%'}
+                //             style={{ marginTop: '4px' }}
+                //           />
+                //         </div>
+                //       )
+                //     }}
+                //   </Cell>
+                // </CellGroup>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 5 - 19
tsconfig.json

@@ -10,30 +10,16 @@
     "sourceMap": true,
     "resolveJsonModule": true,
     "esModuleInterop": true,
-    "lib": [
-      "esnext",
-      "dom"
-    ],
-    "types": [
-      "vite/client",
-      "node"
-    ],
+    "lib": ["esnext", "dom"],
+    "types": ["vite/client", "node"],
     "paths": {
       "@/*": ["src/*"],
       "@common/*": ["src/common/*"],
       "@components/*": ["src/components/*"],
       "@store/*": ["src/store/*"],
-      "@views/*": ["src/views/*"],
+      "@views/*": ["src/views/*"]
     }
   },
-  "include": [
-    "src/**/*.ts",
-    "src/**/*.d.ts",
-    "src/**/*.tsx",
-    "src/**/*.vue"
-  ],
-  "exclude": [
-    "node_modules",
-    "dist"
-  ]
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "exclude": ["node_modules", "dist"]
 }

+ 6 - 9
vite.config.ts

@@ -1,6 +1,6 @@
 import { defineConfig } from 'vite';
 import vue from '@vitejs/plugin-vue';
-import vueJsx from '@vitejs/plugin-vue-jsx'
+import vueJsx from '@vitejs/plugin-vue-jsx';
 import Components from 'unplugin-vue-components/vite';
 import { VantResolver } from 'unplugin-vue-components/resolvers';
 import viteESLint from 'vite-plugin-eslint';
@@ -12,7 +12,7 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
-const proxyUrl = 'https://test.lexiaoya.cn/';
+const proxyUrl = 'https://test.dayaedu.com/';
 export default defineConfig({
   plugins: [
     vue(),
@@ -36,12 +36,13 @@ export default defineConfig({
     port: 9002,
     strictPort: true,
     cors: true,
+    https: false,
     proxy: {
-      '/api-oauth': {
+      '/api-auth': {
         target: proxyUrl,
         changeOrigin: true
       },
-      '/api-school': {
+      '/api-web': {
         target: proxyUrl,
         changeOrigin: true
       },
@@ -52,11 +53,7 @@ export default defineConfig({
       '/api-student': {
         target: proxyUrl,
         changeOrigin: true
-      },
-      '/api-backend': {
-        target: proxyUrl,
-        changeOrigin: true
       }
     }
-  },
+  }
 });