Browse Source

添加页面

lex 2 years ago
parent
commit
8673ae116f
41 changed files with 1532 additions and 18 deletions
  1. BIN
      src/common/images/icon-add.png
  2. 20 0
      src/helpers/constant.ts
  3. 19 0
      src/helpers/utils.ts
  4. 5 0
      src/router/index.ts
  5. 1 1
      src/router/router-root.ts
  6. 24 0
      src/router/routes-common.ts
  7. 35 0
      src/views/activity-record/components/input-timer.tsx
  8. BIN
      src/views/activity-record/images/icon-delete.png
  9. BIN
      src/views/activity-record/images/icon-video.png
  10. 59 0
      src/views/activity-record/index.module.less
  11. 202 0
      src/views/activity-record/index.tsx
  12. 174 0
      src/views/activity-record/operation.module.less
  13. 244 0
      src/views/activity-record/operation.tsx
  14. 79 0
      src/views/activity-record/skeletion-index.modal.tsx
  15. BIN
      src/views/download/images/center.png
  16. 121 0
      src/views/mass-message/components/contacts.tsx
  17. 118 0
      src/views/mass-message/components/group-chat.tsx
  18. BIN
      src/views/mass-message/images/1.png
  19. BIN
      src/views/mass-message/images/10.png
  20. BIN
      src/views/mass-message/images/2.png
  21. BIN
      src/views/mass-message/images/3.png
  22. BIN
      src/views/mass-message/images/4.png
  23. BIN
      src/views/mass-message/images/5.png
  24. BIN
      src/views/mass-message/images/6.png
  25. BIN
      src/views/mass-message/images/7.png
  26. BIN
      src/views/mass-message/images/8.png
  27. BIN
      src/views/mass-message/images/9.png
  28. BIN
      src/views/mass-message/images/badge-1.png
  29. BIN
      src/views/mass-message/images/badge-2.png
  30. BIN
      src/views/mass-message/images/badge-3.png
  31. BIN
      src/views/mass-message/images/badge-4.png
  32. BIN
      src/views/mass-message/images/icon-content.png
  33. BIN
      src/views/mass-message/images/icon-file.png
  34. BIN
      src/views/mass-message/images/icon-uploader.png
  35. BIN
      src/views/mass-message/images/icon-users.png
  36. 118 0
      src/views/mass-message/index.module.less
  37. 183 0
      src/views/mass-message/index.tsx
  38. 118 0
      src/views/mass-message/select-send.tsx
  39. 8 8
      src/views/patrol-evaluation/detail.tsx
  40. 4 8
      src/views/teacher-attendance/detail.tsx
  41. 0 1
      src/views/teacher-attendance/index.tsx

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


+ 20 - 0
src/helpers/constant.ts

@@ -76,3 +76,23 @@ export const coursesStatus = {
   UNDERWAY: '进行中',
   OVER: '已结束'
 } as any;
+
+// 活动类型
+export const activityStatus = {
+  OPEN_DAY: '开放日',
+  PERFORMANCE: '汇演',
+  CONCERT: '音乐会',
+  COMPETITION: '比赛',
+  LECTURE: '讲座',
+  SCHOOL_DAY: '校庆',
+  WELCOME: '迎宾',
+  OTHER: '其他'
+} as any;
+
+// , 可用值: ENSEMBLE, SOLO, REPRISE, UNISON
+export const programType = {
+  ENSEMBLE: '合奏',
+  SOLO: '独奏',
+  REPRISE: '重奏',
+  UNISON: '齐奏'
+} as any;

+ 19 - 0
src/helpers/utils.ts

@@ -56,3 +56,22 @@ export function checkPhone(phone: string) {
     /^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\d{8}$/;
   return phoneRule.test(phone);
 }
+
+/**
+ * @description 格式化日期控件显示内容
+ * @param type
+ * @param option
+ * @returns OBJECT
+ */
+export const formatterDatePicker = (type: any, option: any) => {
+  if (type === 'year') {
+    option.text += '年';
+  }
+  if (type === 'month') {
+    option.text += '月';
+  }
+  if (type === 'day') {
+    option.text += '日';
+  }
+  return option;
+};

+ 5 - 0
src/router/index.ts

@@ -17,6 +17,11 @@ const router: Router = createRouter({
   }
 });
 
+router.beforeEach((to, from, next) => {
+  document.title = (to.meta.title || '学校端') as any;
+  next();
+});
+
 let isOpen = false;
 router.onError(error => {
   if (error instanceof Error) {

+ 1 - 1
src/router/router-root.ts

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

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

@@ -89,6 +89,30 @@ export default [
         meta: {
           title: '待办事项'
         }
+      },
+      {
+        path: '/mass-message',
+        name: 'mass-message',
+        component: () => import('@/views/mass-message'),
+        meta: {
+          title: '群发消息'
+        }
+      },
+      {
+        path: '/activity-record',
+        name: 'activity-record',
+        component: () => import('@/views/activity-record'),
+        meta: {
+          title: '活动记录'
+        }
+      },
+      {
+        path: '/activity-record-operation',
+        name: 'activity-record-operation',
+        component: () => import('@/views/activity-record/operation'),
+        meta: {
+          title: '新增活动'
+        }
       }
     ]
   },

+ 35 - 0
src/views/activity-record/components/input-timer.tsx

@@ -0,0 +1,35 @@
+import { defineComponent, reactive } from 'vue';
+import styles from '../operation.module.less';
+import { Button, Field } from 'vant';
+
+export default defineComponent({
+  name: 'input-timer',
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const forms = reactive({
+      minute: null,
+      secord: null
+    });
+    return () => (
+      <div class={styles.popupContainer}>
+        <h2 class={styles.popupTitle}>请输入节目时长</h2>
+        <div class={styles.popupContent}>
+          <div class={styles.popupTimer}>
+            <Field v-model={forms.minute} type="number" formatter={on} />
+            {/* <input v-model={forms.minute} type="number"></input> */}分
+            {/* <input v-model={forms.secord} type="number"></input> */}秒
+          </div>
+        </div>
+
+        <div class={['btnGroupPopup']}>
+          <Button round onClick={() => emit('close')}>
+            取消
+          </Button>
+          <Button type="primary" round onClick={() => emit('confirm')}>
+            确定
+          </Button>
+        </div>
+      </div>
+    );
+  }
+});

BIN
src/views/activity-record/images/icon-delete.png


BIN
src/views/activity-record/images/icon-video.png


+ 59 - 0
src/views/activity-record/index.module.less

@@ -0,0 +1,59 @@
+.activity-record {
+  .iconAdd {
+    font-size: 24px;
+  }
+
+  :global {
+    .van-dropdown-menu {
+      margin-right: 12px;
+    }
+  }
+
+  .cellGroup {
+    margin-top: 12px;
+  }
+
+  .cellTitle {
+    padding: 15px 12px;
+
+    .tag {
+      font-size: 12px;
+      background-color: #F2FFFC;
+      padding: 3px 6px 2px;
+      border-radius: 3px;
+      margin-right: 6px;
+      flex-shrink: 0;
+    }
+
+    .title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+      line-height: 22px;
+      max-width: 240px;
+    }
+  }
+
+  .cellTimerSkeleton {
+    display: flex;
+    justify-content: flex-end;
+  }
+
+  .cellTimer {
+    :global {
+      .van-cell__title {
+        flex: 0 auto;
+      }
+
+      .van-cell__title,
+      .van-cell__value {
+        color: var(--k-gray-3);
+        font-size: 14px;
+      }
+    }
+
+    .ing {
+      color: var(--k-font-primary);
+    }
+  }
+}

+ 202 - 0
src/views/activity-record/index.tsx

@@ -0,0 +1,202 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import MSticky from '@/components/m-sticky';
+import MHeader from '@/components/m-header';
+import MSearch from '@/components/m-search';
+import {
+  Cell,
+  CellGroup,
+  DropdownItem,
+  DropdownMenu,
+  Icon,
+  List,
+  Tag
+} from 'vant';
+import DropDownModal from '../site-management/drop-down-modal';
+import { activityStatus } from '@/helpers/constant';
+import SkeletionIndexModal from './skeletion-index.modal';
+import MFullRefresh from '@/components/m-full-refresh';
+import MEmpty from '@/components/m-empty';
+import request from '@/helpers/request';
+import iconAdd from '@/common/images/icon-add.png';
+import { useRouter } from 'vue-router';
+
+export default defineComponent({
+  name: 'activtiy-record',
+  setup() {
+    const router = useRouter();
+    const dropDownRef = ref();
+    const forms = reactive({
+      activeValue: '',
+      activeColumns: [{ text: '全部活动', value: '' }], //
+      isClick: false,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: true,
+        finished: false,
+        refreshing: false
+      },
+      params: {
+        page: 1,
+        rows: 20
+      },
+      list: []
+    });
+    const onDropDownClose = (item: any) => {
+      item.value && item.value.toggle();
+    };
+    const formatName = () => {
+      let name = '';
+      forms.activeColumns.forEach((item: any) => {
+        if (forms.activeValue === item.value) {
+          name = item.text;
+        }
+      });
+      return name;
+    };
+
+    const getList = async () => {
+      try {
+        if (forms.isClick) return;
+        forms.isClick = true;
+        const { data } = await request.post('/api-web/schoolActivity/page', {
+          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.totalPage;
+        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(() => {
+      for (const key in activityStatus) {
+        if (Object.prototype.hasOwnProperty.call(activityStatus, key)) {
+          forms.activeColumns.push({
+            text: activityStatus[key],
+            value: key
+          });
+        }
+      }
+
+      getList();
+    });
+    return () => (
+      <div class={styles['activity-record']}>
+        <MSticky position="top">
+          <MHeader>
+            {{
+              right: () => (
+                <Icon
+                  name={iconAdd}
+                  class={styles.iconAdd}
+                  onClick={() => {
+                    router.push('activity-record-operation');
+                  }}
+                />
+              )
+            }}
+          </MHeader>
+          <MSearch placeholder="请输入活动名称">
+            {{
+              left: () => (
+                <DropdownMenu
+                  class={styles.patrolDetailDropDown}
+                  closeOnClickOutside={false}>
+                  <DropdownItem title={formatName()} ref={dropDownRef}>
+                    <DropDownModal
+                      selectValues={forms.activeValue}
+                      columns={forms.activeColumns}
+                      open={dropDownRef.value.state.showPopup}
+                      onDropDownClose={() => onDropDownClose(dropDownRef)}
+                      onDropDownConfirm={(values: any) => {
+                        forms.activeValue = values[0];
+                        onDropDownClose(dropDownRef);
+                      }}
+                    />
+                  </DropdownItem>
+                </DropdownMenu>
+              )
+            }}
+          </MSearch>
+        </MSticky>
+
+        <SkeletionIndexModal 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 ? (
+                forms.list.map(() => (
+                  <CellGroup inset class={styles.cellGroup}>
+                    <Cell
+                      center
+                      isLink
+                      class={styles.cellTitle}
+                      clickable={false}>
+                      {{
+                        icon: () => (
+                          <Tag plain type="primary" class={styles.tag}>
+                            泥泞
+                          </Tag>
+                        ),
+                        title: () => (
+                          <div class={[styles.title, 'van-ellipsis']}>
+                            武汉小学23年元旦晚会武汉小学23年元旦晚会武汉小学23年元旦晚会
+                          </div>
+                        )
+                      }}
+                    </Cell>
+                    <Cell
+                      class={styles.cellTimer}
+                      center
+                      title={'活动日期:2022年12月10日'}
+                      value={'进行中'}
+                      valueClass={styles.ing}></Cell>
+                  </CellGroup>
+                ))
+              ) : (
+                <MEmpty
+                  style={{
+                    minHeight: `calc(100vh - var(--header-height))`
+                  }}
+                  description="暂无数据"
+                />
+              )}
+            </List>
+          </MFullRefresh>
+        </SkeletionIndexModal>
+      </div>
+    );
+  }
+});

+ 174 - 0
src/views/activity-record/operation.module.less

@@ -0,0 +1,174 @@
+.topCellGroup {
+  margin-top: 12px;
+
+  :global {
+    .van-cell {
+      padding: 18px 12px;
+      font-size: 15px;
+    }
+
+    .van-field__label,
+    .van-field__control {
+      font-size: 15px;
+    }
+  }
+}
+
+.programType {
+  padding-top: 14px !important;
+
+  :global {
+    .van-field__label {
+      padding-top: 6px;
+    }
+  }
+}
+
+.searchTypeFlex {
+  margin-top: 6px;
+
+  &.small {
+    :global {
+      .van-tag {
+        margin-bottom: 8px;
+        width: 52px;
+        height: 24px;
+        font-size: 12px;
+        justify-content: center;
+
+        &+.van-tag {
+          margin-left: 4px;
+        }
+
+        &:nth-child(5n + 5) {
+          margin-left: 0;
+        }
+      }
+    }
+  }
+
+  :global {
+    .van-tag {
+      margin-bottom: 8px;
+      width: 78px;
+      height: 32px;
+      font-size: 14px;
+      justify-content: center;
+
+      &+.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 {
+      font-weight: 600;
+    }
+
+    .van-radio {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 0;
+      bottom: 0;
+      width: 100%;
+      opacity: 0;
+    }
+  }
+}
+
+.iconImg {
+  font-size: 18px;
+}
+
+.topTitle {
+  vertical-align: middle;
+  font-size: 16px;
+  margin-left: 8px;
+  font-weight: 600;
+}
+
+.programTimer {
+  font-size: 15px;
+  color: var(--k-gray-2);
+
+  span {
+    display: inline-block;
+    min-width: 48px;
+    line-height: 31px;
+    background: #F6F6F6;
+    border-radius: 4px;
+    padding: 0 12px;
+    margin: 0 10px;
+    text-align: center;
+    font-size: 14px;
+    color: var(--k-gray-1);
+    font-weight: 600;
+  }
+}
+
+.addButtonGroup {
+  margin: 12px 13px;
+
+  .addButton {
+    font-size: 16px;
+    background: #E5F8F7;
+    border-radius: 6px;
+    --van-button-default-height: 40px;
+  }
+}
+
+.bottonGroup {
+  padding-top: 20px;
+  background-color: #fff;
+  border-top: 1px solid #F2F2F2;
+}
+
+
+.popupContainer {
+  .popupTitle {
+    padding-top: 20px;
+    padding-bottom: 25px;
+    font-size: 18px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 25px;
+    text-align: center;
+  }
+
+  .popupTimer {
+    text-align: center;
+    font-size: 16px;
+    color: var(--k-gray-2);
+    margin-bottom: 10px;
+
+    input {
+      display: inline-block;
+      line-height: 47px;
+      width: 60px;
+      padding: 0 12px;
+      border-radius: 6px;
+      background: #F6F6F6;
+      font-size: 18px;
+      font-weight: bold;
+      margin-right: 20px;
+      text-align: center;
+
+      &:last-child {
+        margin-left: 20px;
+      }
+    }
+  }
+}

+ 244 - 0
src/views/activity-record/operation.tsx

@@ -0,0 +1,244 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './operation.module.less';
+import MHeader from '@/components/m-header';
+import {
+  Button,
+  Cell,
+  CellGroup,
+  DatePicker,
+  Field,
+  Icon,
+  Picker,
+  Popup,
+  Radio,
+  RadioGroup,
+  Tag
+} from 'vant';
+import { activityStatus, programType } from '@/helpers/constant';
+import MUploader from '@/components/m-uploader';
+import MUploaderInside from '@/components/m-uploader/inside';
+import iconVideo from './images/icon-video.png';
+import iconDelete from './images/icon-delete.png';
+import iconUploadImg from '../patrol-evaluation/images/icon-upload-img.png';
+import iconUploadVideo from '../patrol-evaluation/images/icon-upload-video.png';
+import MSticky from '@/components/m-sticky';
+import { formatterDatePicker } from '@/helpers/utils';
+import request from '@/helpers/request';
+import InputTimer from './components/input-timer';
+
+export default defineComponent({
+  name: 'operation-page',
+  setup() {
+    const forms = reactive({
+      timerStatus: false,
+      submitEvaluateStatus: null,
+      currentDate: [],
+      orchestraStatus: false, // 乐团列表状态
+      orchestraColumns: [] as any,
+      programType: '',
+      programTimerStatus: true // 节目时长状态
+    });
+
+    // 乐团列表
+    const musicGroupPage = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/cooperationOrgan/musicGroupPage'
+        );
+        (data || []).forEach((item: any) => {
+          forms.orchestraColumns.push({
+            text: item.name,
+            value: item.id
+          });
+        });
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      musicGroupPage();
+    });
+    return () => (
+      <div class={styles.operation}>
+        <MHeader />
+
+        <CellGroup inset class={styles.topCellGroup}>
+          <Field
+            isLink
+            label="活动日期"
+            inputAlign="right"
+            readonly
+            clearable={false}
+            onClick={() => (forms.timerStatus = true)}
+            placeholder="请选择活动日期"></Field>
+          <Field
+            label="活动名称"
+            inputAlign="right"
+            placeholder="请填写活动名称"
+            maxlength={25}
+          />
+          <Field label="活动名称" labelAlign="top">
+            {{
+              input: () => (
+                <RadioGroup
+                  class={styles.searchTypeFlex}
+                  v-model={forms.submitEvaluateStatus}>
+                  {Object.keys(activityStatus).map((item: string) => (
+                    <Tag
+                      type={
+                        forms.submitEvaluateStatus === item
+                          ? 'primary'
+                          : 'default'
+                      }
+                      round>
+                      <Radio name={item} />
+                      {activityStatus[item]}
+                    </Tag>
+                  ))}
+                </RadioGroup>
+              )
+            }}
+          </Field>
+        </CellGroup>
+
+        <CellGroup inset class={styles.topCellGroup}>
+          <Cell center>
+            {{
+              icon: () => <Icon name={iconVideo} class={styles.iconImg} />,
+              title: () => <div class={styles.topTitle}>节目二</div>,
+              value: () => <Icon name={iconDelete} class={styles.iconImg} />
+            }}
+          </Cell>
+          <Field
+            label="节目名称"
+            inputAlign="right"
+            placeholder="请填写节目名称"
+            maxlength={25}
+          />
+          <Field
+            label="节目类型"
+            inputAlign="right"
+            placeholder="请填写节目"
+            class={styles.programType}>
+            {{
+              input: () => (
+                <RadioGroup
+                  class={[styles.searchTypeFlex, styles.small]}
+                  v-model={forms.programType}>
+                  {Object.keys(programType).map((item: string) => (
+                    <Tag
+                      type={forms.programType === item ? 'primary' : 'default'}
+                      round>
+                      <Radio name={item} />
+                      {programType[item]}
+                    </Tag>
+                  ))}
+                </RadioGroup>
+              )
+            }}
+          </Field>
+          <Field
+            isLink
+            label="表演乐团"
+            inputAlign="right"
+            readonly
+            clearable={false}
+            onClick={() => (forms.orchestraStatus = true)}
+            placeholder="请选择表演乐团"
+          />
+          <Field
+            isLink
+            label="表演团队"
+            inputAlign="right"
+            readonly
+            clearable={false}
+            placeholder="请选择表演团队"
+          />
+          <Field
+            isLink
+            label="演员"
+            inputAlign="right"
+            readonly
+            clearable={false}
+            placeholder="请选择演员"
+          />
+          <Field
+            label="节目时长"
+            inputAlign="right"
+            onClick={() => (forms.programTimerStatus = true)}
+            placeholder="请选择节目时长">
+            {{
+              input: () => (
+                <div class={styles.programTimer}>
+                  <span>3</span>分<span>24</span>秒
+                </div>
+              )
+            }}
+          </Field>
+          <Field label="上传附件" labelAlign="top">
+            {{
+              input: () => (
+                <MUploader
+                  uploadIcon={iconUploadImg}
+                  maxCount={5}
+                  style={{ marginTop: '4px' }}>
+                  <MUploaderInside
+                    uploadIcon={iconUploadVideo}
+                    uploadType="VIDEO"
+                    accept=".mp4"
+                    maxCount={3}
+                  />
+                </MUploader>
+              )
+            }}
+          </Field>
+        </CellGroup>
+
+        <div class={styles.addButtonGroup}>
+          <Button
+            icon="plus"
+            block
+            type="primary"
+            plain
+            class={styles.addButton}>
+            添加节目
+          </Button>
+        </div>
+
+        <MSticky position="bottom">
+          <div class={['btnGroupFixed', styles.bottonGroup]}>
+            <Button type="primary" round block>
+              确认
+            </Button>
+          </div>
+        </MSticky>
+
+        {/* 时间 */}
+        <Popup v-model:show={forms.timerStatus} round position="bottom">
+          <DatePicker
+            v-model={forms.currentDate}
+            formatter={formatterDatePicker}
+            onCancel={() => (forms.timerStatus = false)}
+          />
+        </Popup>
+
+        {/* 乐团 */}
+        <Popup v-model:show={forms.orchestraStatus} round position="bottom">
+          <Picker
+            columns={forms.orchestraColumns}
+            onCancel={() => (forms.orchestraStatus = false)}
+          />
+        </Popup>
+
+        {/* 节目时长 */}
+        <Popup
+          v-model:show={forms.programTimerStatus}
+          round
+          style={{ width: '82%' }}>
+          <InputTimer />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 79 - 0
src/views/activity-record/skeletion-index.modal.tsx

@@ -0,0 +1,79 @@
+import { Cell, CellGroup, Skeleton, 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
+                    isLink
+                    class={styles.cellTitle}
+                    clickable={false}>
+                    {{
+                      title: () => (
+                        <SkeletonParagraph
+                          rowWidth={'80%'}
+                          class={styles.title}
+                        />
+                      )
+                    }}
+                  </Cell>
+                  <Cell center valueClass={styles.cellTimerSkeleton}>
+                    {{
+                      title: () => <SkeletonParagraph rowWidth={'80%'} />,
+                      value: () => <SkeletonParagraph rowWidth={'50%'} />
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

BIN
src/views/download/images/center.png


+ 121 - 0
src/views/mass-message/components/contacts.tsx

@@ -0,0 +1,121 @@
+import { Cell, Checkbox, CheckboxGroup, Icon, Image, Sticky } from 'vant';
+import { defineComponent, reactive, ref, watch } from 'vue';
+import styles from '../index.module.less';
+import badge1 from '../images/badge-1.png';
+import class1 from '../images/1.png';
+import MSearch from '@/components/m-search';
+import activeButtonIcon from '@/common/images/icon-check-active.png';
+import inactiveButtonIcon from '@/common/images/icon-check.png';
+export default defineComponent({
+  name: 'contacts-modal',
+  props: {
+    height: {
+      type: [Number],
+      default: 0
+    },
+    bottomHeight: {
+      type: [String, Number],
+      default: 0
+    },
+    headerHeight: {
+      type: [Number],
+      default: 0
+    },
+    selectItem: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['update:selectItem'],
+  setup(props, { emit }) {
+    const checkboxRefs = ref([] as any);
+    const forms = reactive({
+      height: props.height,
+      list: [],
+      check: [] as any
+    });
+
+    const onToggle = (index: number) => {
+      //
+      checkboxRefs.value[index].toggle();
+
+      const list: any = [];
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.nickname,
+            avatar: item.avatar
+          });
+        }
+      });
+      emit('update:selectItem', list);
+    };
+
+    watch(
+      () => props.height,
+      () => {
+        forms.height = props.height;
+        console.log(forms.height);
+      }
+    );
+    return () => (
+      <div
+        style={{
+          'min-height': `calc(100vh - ${props.headerHeight}px - ${forms.height}px - ${props.bottomHeight}px )`
+        }}>
+        <Sticky
+          position="top"
+          offsetTop={props.headerHeight + forms.height}
+          style={{ width: '100%' }}>
+          <MSearch placeholder="请输入群聊名称" />
+        </Sticky>
+        <CheckboxGroup v-model={forms.check}>
+          {[1, 2, 3, 4, 5, 6, 7, 8].map((item: any, index: number) => (
+            <Cell
+              center
+              onClick={() => onToggle(index)}
+              class={styles.popupCell}>
+              {{
+                icon: () => (
+                  <Image src={class1} class={styles.imgLogo} fit="contain" />
+                ),
+                title: () => (
+                  <div class={styles.infos}>
+                    <div class={styles.infoTitle}>
+                      长笛班 <img src={badge1} class={styles.iconBadge} />
+                    </div>
+                    <div class={styles.infoContent}>
+                      武汉洪山区第二小学第一乐团
+                    </div>
+                  </div>
+                ),
+                'right-icon': () => (
+                  <Checkbox
+                    name={item}
+                    ref={e => (checkboxRefs.value[index] = e)}
+                    onClick={(e: MouseEvent) => {
+                      e.stopPropagation();
+                    }}
+                    v-slots={{
+                      icon: (props: any) => (
+                        <Icon
+                          class={styles.boxStyle}
+                          name={
+                            props.checked
+                              ? activeButtonIcon
+                              : inactiveButtonIcon
+                          }
+                        />
+                      )
+                    }}
+                  />
+                )
+              }}
+            </Cell>
+          ))}
+        </CheckboxGroup>
+      </div>
+    );
+  }
+});

+ 118 - 0
src/views/mass-message/components/group-chat.tsx

@@ -0,0 +1,118 @@
+import { Cell, Checkbox, CheckboxGroup, Icon, Image, Sticky } from 'vant';
+import { defineComponent, reactive, ref, watch } from 'vue';
+import styles from '../index.module.less';
+import class1 from '../images/1.png';
+import MSearch from '@/components/m-search';
+import activeButtonIcon from '@/common/images/icon-check-active.png';
+import inactiveButtonIcon from '@/common/images/icon-check.png';
+export default defineComponent({
+  name: 'group-chat',
+  props: {
+    height: {
+      type: [Number],
+      default: 0
+    },
+    bottomHeight: {
+      type: [String, Number],
+      default: 0
+    },
+    headerHeight: {
+      type: [Number],
+      default: 0
+    },
+    selectItem: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['update:selectItem'],
+  setup(props, { emit }) {
+    const checkboxRefs = ref([] as any);
+    const forms = reactive({
+      height: props.height,
+      list: [],
+      check: [] as any
+    });
+
+    const onToggle = (index: number) => {
+      //
+      checkboxRefs.value[index].toggle();
+
+      const list: any = [];
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.nickname,
+            avatar: item.avatar
+          });
+        }
+      });
+      emit('update:selectItem', list);
+    };
+
+    watch(
+      () => props.height,
+      () => {
+        forms.height = props.height;
+        console.log(forms.height);
+      }
+    );
+    return () => (
+      <div
+        style={{
+          'min-height': `calc(100vh - ${props.headerHeight}px - ${forms.height}px - ${props.bottomHeight}px )`
+        }}>
+        <Sticky
+          position="top"
+          offsetTop={props.headerHeight + forms.height}
+          style={{ width: '100%' }}>
+          <MSearch placeholder="请输入群聊名称" />
+        </Sticky>
+        <CheckboxGroup v-model={forms.check}>
+          {[1, 2, 3, 4, 5, 6, 7, 8].map((item: any, index: number) => (
+            <Cell
+              center
+              onClick={() => onToggle(index)}
+              class={styles.popupCell}>
+              {{
+                icon: () => (
+                  <Image src={class1} class={styles.imgLogo} fit="contain" />
+                ),
+                title: () => (
+                  <div class={styles.infos}>
+                    <div class={styles.infoTitle}>长笛班</div>
+                    <div class={styles.infoContent}>
+                      武汉洪山区第二小学第一乐团
+                    </div>
+                  </div>
+                ),
+                'right-icon': () => (
+                  <Checkbox
+                    name={item}
+                    ref={e => (checkboxRefs.value[index] = e)}
+                    onClick={(e: MouseEvent) => {
+                      e.stopPropagation();
+                    }}
+                    v-slots={{
+                      icon: (props: any) => (
+                        <Icon
+                          class={styles.boxStyle}
+                          name={
+                            props.checked
+                              ? activeButtonIcon
+                              : inactiveButtonIcon
+                          }
+                        />
+                      )
+                    }}
+                  />
+                )
+              }}
+            </Cell>
+          ))}
+        </CheckboxGroup>
+      </div>
+    );
+  }
+});

BIN
src/views/mass-message/images/1.png


BIN
src/views/mass-message/images/10.png


BIN
src/views/mass-message/images/2.png


BIN
src/views/mass-message/images/3.png


BIN
src/views/mass-message/images/4.png


BIN
src/views/mass-message/images/5.png


BIN
src/views/mass-message/images/6.png


BIN
src/views/mass-message/images/7.png


BIN
src/views/mass-message/images/8.png


BIN
src/views/mass-message/images/9.png


BIN
src/views/mass-message/images/badge-1.png


BIN
src/views/mass-message/images/badge-2.png


BIN
src/views/mass-message/images/badge-3.png


BIN
src/views/mass-message/images/badge-4.png


BIN
src/views/mass-message/images/icon-content.png


BIN
src/views/mass-message/images/icon-file.png


BIN
src/views/mass-message/images/icon-uploader.png


BIN
src/views/mass-message/images/icon-users.png


+ 118 - 0
src/views/mass-message/index.module.less

@@ -0,0 +1,118 @@
+.mass-message {
+  .cellGroup {
+    margin: 12px 13px;
+  }
+
+  :global {
+    .van-uploader {
+      --upload-file-size: 100px !important;
+    }
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    font-weight: 500;
+    color: var(--k-gray-1);
+    line-height: 22px;
+    padding-bottom: 8px;
+
+    .iconImg {
+      font-size: 22px;
+      margin-right: 6px;
+      margin-top: 2px;
+    }
+  }
+
+  .imgLogo {
+    width: 42px;
+    height: 42px;
+    overflow: hidden;
+    border-radius: 50%;
+    margin-right: 8px;
+    flex-shrink: 0;
+  }
+
+  .infos {
+    .infoTitle {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+      line-height: 20px;
+    }
+
+    .infoContent {
+      font-size: 12px;
+      font-weight: var(--k-gray-3);
+      color: #777777;
+    }
+
+    .iconBadge {
+      width: 56px;
+      height: 18px;
+      margin-left: 4px;
+    }
+  }
+
+  .iconClose {
+    font-size: 18px;
+  }
+
+  .bottonGroup {
+    padding-top: 20px;
+    background-color: #fff;
+    border-top: 1px solid #F2F2F2;
+  }
+}
+
+.select-send {
+  --van-tab-font-size: 16px;
+
+  :global {
+    .van-tab {
+      z-index: 1;
+    }
+
+    .van-tabs__line {
+      bottom: 26px;
+      width: 32px !important;
+      height: 6px !important;
+      background: linear-gradient(250deg, rgba(45, 199, 170, 0.22) 0%, #2DC7AA 100%);
+      z-index: 0;
+    }
+  }
+}
+
+.popupCell {
+  padding: 15px 20px;
+
+  .boxStyle {
+    background: transparent !important;
+    width: 18px;
+    height: 18px;
+    border: transparent !important;
+  }
+
+  :global {
+    .van-checkbox {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+
+    .van-checkbox__icon {
+      height: 18px;
+      line-height: 18px;
+      display: inline-block;
+      vertical-align: sub;
+    }
+
+    .van-checkbox__label {
+      line-height: 18px;
+      color: var(--k-gray-4);
+    }
+  }
+}

+ 183 - 0
src/views/mass-message/index.tsx

@@ -0,0 +1,183 @@
+import { defineComponent, reactive } from 'vue';
+import styles from './index.module.less';
+import MHeader from '@/components/m-header';
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Field,
+  Icon,
+  Image,
+  showConfirmDialog
+} from 'vant';
+import MUploader from '@/components/m-uploader';
+import MPopup from '@/components/m-popup';
+import SelectSend from './select-send';
+import iconUploader from './images/icon-uploader.png';
+import iconClose from '@/common/images/icon-upload-close.png';
+import iconContent from './images/icon-content.png';
+import iconFile from './images/icon-file.png';
+import iconUsers from './images/icon-users.png';
+
+import badge1 from './images/badge-1.png';
+import class1 from './images/1.png';
+import MSticky from '@/components/m-sticky';
+
+export default defineComponent({
+  name: 'mass-message',
+  setup() {
+    const forms = reactive({
+      textMessage: '',
+      attachments: [],
+      selectStatus: false,
+      selectObject: []
+    });
+
+    const onClose = () => {
+      showConfirmDialog({
+        title: '提示',
+        message: '您是否删除该数据'
+      }).then(() => {
+        //
+      });
+    };
+
+    const onSubmit = () => {
+      console.log('onSubmit');
+    };
+
+    return () => (
+      <div class={styles['mass-message']}>
+        <MHeader />
+
+        <CellGroup inset class={styles.cellGroup}>
+          <Cell title="群发内容">
+            {{
+              title: () => (
+                <div class={styles.title}>
+                  <Icon name={iconContent} class={styles.iconImg} />
+                  群发内容
+                </div>
+              ),
+              label: () => (
+                <Field
+                  style={{ padding: '0', marginTop: '12px' }}
+                  placeholder="请输入您的消息内容"
+                  v-model={forms.textMessage}
+                  type="textarea"
+                  rows={3}
+                  showWordLimit
+                  maxlength={150}
+                />
+              )
+            }}
+          </Cell>
+        </CellGroup>
+
+        <CellGroup inset class={styles.cellGroup}>
+          <Cell title="上传附件">
+            {{
+              title: () => (
+                <div class={styles.title}>
+                  <Icon name={iconFile} class={styles.iconImg} />
+                  上传附件
+                </div>
+              ),
+              label: () => (
+                <MUploader
+                  v-model:modelValue={forms.attachments}
+                  maxCount={5}
+                  uploadIcon={iconUploader}
+                />
+              )
+            }}
+          </Cell>
+        </CellGroup>
+        <CellGroup inset class={styles.cellGroup}>
+          <Field
+            label="发送对象"
+            readonly
+            inputAlign="right"
+            class={styles.sendObjPlaceholder}
+            placeholder={'请选择发送对象'}
+            isLink
+            border={false}
+            onClick={() => {
+              forms.selectStatus = true;
+            }}>
+            {{
+              label: () => (
+                <div class={styles.title} style={{ paddingBottom: '0' }}>
+                  <Icon name={iconUsers} class={styles.iconImg} />
+                  发送对象
+                </div>
+              )
+            }}
+          </Field>
+
+          <Cell center border={false}>
+            {{
+              icon: () => <Image src={class1} class={styles.imgLogo} />,
+              title: () => (
+                <div class={styles.infos}>
+                  <div class={styles.infoTitle}>长笛班</div>
+                  <div class={styles.infoContent}>
+                    武汉洪山区第二小学第一乐团
+                  </div>
+                </div>
+              ),
+              'right-icon': () => (
+                <Icon
+                  name={iconClose}
+                  class={styles.infoClose}
+                  onClick={onClose}
+                />
+              )
+            }}
+          </Cell>
+          {[1, 2, 3, 4, 5, 6].map(() => (
+            <Cell center border={false}>
+              {{
+                icon: () => (
+                  <Image src={class1} class={styles.imgLogo} fit="contain" />
+                ),
+                title: () => (
+                  <div class={styles.infos}>
+                    <div class={styles.infoTitle}>
+                      长笛班 <img src={badge1} class={styles.iconBadge} />
+                    </div>
+                    <div class={styles.infoContent}>
+                      武汉洪山区第二小学第一乐团
+                    </div>
+                  </div>
+                ),
+                'right-icon': () => (
+                  <Icon
+                    name={iconClose}
+                    class={styles.infoClose}
+                    onClick={onClose}
+                  />
+                )
+              }}
+            </Cell>
+          ))}
+        </CellGroup>
+
+        <MSticky position="bottom">
+          <div class={['btnGroupFixed', styles.bottonGroup]}>
+            <Button type="primary" round block onClick={onSubmit}>
+              发送
+            </Button>
+          </div>
+        </MSticky>
+
+        <MPopup v-model:modelValue={forms.selectStatus}>
+          <SelectSend
+            v-model:selectObject={forms.selectObject}
+            onClose={() => (forms.selectStatus = false)}
+          />
+        </MPopup>
+      </div>
+    );
+  }
+});

+ 118 - 0
src/views/mass-message/select-send.tsx

@@ -0,0 +1,118 @@
+import MSticky from '@/components/m-sticky';
+import { useRect } from '@vant/use';
+import { Button, Tab, Tabs } from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+import MHeader from '@/components/m-header';
+import GroupChat from './components/group-chat';
+import Contacts from './components/contacts';
+
+export default defineComponent({
+  name: 'select-send',
+  props: {
+    selectObject: {
+      type: Object,
+      default: () => ({})
+    },
+    selectStatus: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['close', 'update:selectObject', 'confirm'],
+  setup(props, { emit }) {
+    const state = reactive({
+      headerHeight: 0,
+      height: 0,
+      bottomHeight: 0,
+      tabValue: 'groupChat',
+      selectGroupChat: [] as any,
+      selectContacts: [] as any
+    });
+
+    const onSubmit = async () => {
+      // const params = {
+      //   class: state.selectClass,
+      //   student: state.selectStudent,
+      //   teacher: state.selectTeacher,
+      //   school: state.selectManage
+      // };
+      // emit('close');
+      // emit('update:selectObject', params);
+      // emit('confirm', params);
+    };
+
+    watch(
+      () => props.selectObject,
+      () => {
+        console.log('watch', props.selectObject);
+        resetSelectItems();
+      },
+      { deep: true }
+    );
+
+    const resetSelectItems = () => {
+      // const list = props.selectObject || {};
+      // state.selectClass = list.class || [];
+      // state.selectTeacher = list.teacher || [];
+      // state.selectManage = list.school || [];
+      // state.selectStudent = list.student || [];
+    };
+
+    onMounted(() => {
+      const { height } = useRect(
+        document.querySelector('.van-tab') as HTMLElement
+      );
+      state.height = height;
+
+      resetSelectItems();
+      console.log(state, 'select');
+    });
+
+    return () => (
+      <div class={styles['select-send']}>
+        <MSticky
+          onBarHeight={(height: number) => {
+            state.headerHeight = height;
+          }}>
+          <MHeader title="发送对象" />
+        </MSticky>
+        <Tabs
+          sticky
+          lineWidth={20}
+          lineHeight={4}
+          v-model:active={state.tabValue}
+          offsetTop={state.headerHeight}>
+          <Tab title="群聊" name="groupChat">
+            <GroupChat
+              height={state.height}
+              headerHeight={state.headerHeight}
+              bottomHeight={state.bottomHeight}
+              v-model:selectItem={state.selectGroupChat}
+            />
+          </Tab>
+          <Tab title="联系人" name="contacts">
+            <Contacts
+              height={state.height}
+              headerHeight={state.headerHeight}
+              bottomHeight={state.bottomHeight}
+              v-model:selectItem={state.selectContacts}
+            />
+          </Tab>
+        </Tabs>
+
+        <MSticky
+          position="bottom"
+          onBarHeight={(height: any) => {
+            state.bottomHeight = height;
+          }}>
+          <div class={'btnGroupFixed'}>
+            <Button round block type="primary" onClick={onSubmit}>
+              确认
+            </Button>
+          </div>
+        </MSticky>
+      </div>
+    );
+  }
+});

+ 8 - 8
src/views/patrol-evaluation/detail.tsx

@@ -12,7 +12,7 @@ export default defineComponent({
     const dropDownRef = ref();
     const forms = reactive({
       heightV: 0,
-      active: 'Evaluated',
+      active: 'NotEvaluated',
       listState: {
         loading: true
       },
@@ -220,13 +220,6 @@ export default defineComponent({
           sticky
           lazyRender
           swipeable>
-          <Tab name={'Evaluated'} title="已评价">
-            <DetailList
-              type="Evaluated"
-              evaluateStatus={forms.dropDownValue.evaluateStatus}
-              problemType={forms.dropDownValue.problemType}
-            />
-          </Tab>
           <Tab name={'NotEvaluated'} title="未评价">
             <DetailList
               type="NotEvaluated"
@@ -234,6 +227,13 @@ export default defineComponent({
               courseType={forms.dropDownValue.courseType}
             />
           </Tab>
+          <Tab name={'Evaluated'} title="已评价">
+            <DetailList
+              type="Evaluated"
+              evaluateStatus={forms.dropDownValue.evaluateStatus}
+              problemType={forms.dropDownValue.problemType}
+            />
+          </Tab>
         </Tabs>
       </div>
     );

+ 4 - 8
src/views/teacher-attendance/detail.tsx

@@ -80,10 +80,10 @@ export default defineComponent({
 
     const getList = async () => {
       try {
-        const { data } = await request.post(
-          '/api-web/schoolTeacherAttendance/queryTeacherAttendance',
+        const { data } = await request.get(
+          '/api-web/schoolTeacherAttendance/getClassTeacherAttendance',
           {
-            data: {
+            params: {
               ...forms.params
             }
           }
@@ -100,10 +100,6 @@ export default defineComponent({
     };
 
     onMounted(() => {
-      // setTimeout(() => {
-      //   forms.listState.loading = false;
-      //   forms.listState.loadingList = false;
-      // }, 1000);
       for (const key in coursesType) {
         if (Object.prototype.hasOwnProperty.call(coursesType, key)) {
           const text = coursesType[key];
@@ -114,7 +110,7 @@ export default defineComponent({
         }
       }
 
-      getTeacherInfo();
+      // getTeacherInfo();
       getList();
     });
     return () => (

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

@@ -65,7 +65,6 @@ export default defineComponent({
         const { data } = await request.post(
           '/api-web/schoolTeacherAttendance/queryTeacherAttendance',
           {
-            requestType: 'form',
             data: {
               coopId: 3,
               ...forms.params