浏览代码

Merge branch 'iteration-20240607-small' into jenkins

lex 1 年之前
父节点
当前提交
53755429d1

+ 262 - 261
src/custom-plugins/guide-page/lessons-guide.tsx

@@ -1,261 +1,262 @@
-import { NButton } from 'naive-ui';
-import {
-  defineComponent,
-  nextTick,
-  onMounted,
-  onUnmounted,
-  reactive,
-  ref,
-  watch
-} from 'vue';
-import styles from './index.module.less';
-import { getImage } from './images';
-import { eventGlobal, px2vw, px2vwH } from '@/utils/index';
-import { getGuidance, setGuidance } from './api';
-export default defineComponent({
-  name: 'lessons-guide',
-  emits: ['close'],
-  setup(props, { emit }) {
-    const data = reactive({
-      box: {
-        height: '0px'
-      } as any,
-      show: false,
-      /**
-       *
-            width:  px2vw(840),
-            height:  px2vw(295)
-       */
-      steps: [
-        {
-          ele: '',
-          eleRect: {} as DOMRect,
-          img: getImage('lessons1.png'),
-          handStyle: {
-            top: '0.91rem'
-          },
-          imgStyle: {
-            top: px2vw(-4),
-            left: px2vw(-50),
-            width: px2vw(420),
-            height: px2vw(228)
-          },
-          btnsStyle: {
-            bottom: px2vw(35),
-            left: px2vw(111)
-          },
-          eleRectPadding: {
-            left: 7,
-            top: 7,
-            width: 14,
-            height: -18
-          }
-        }
-      ],
-      step: 0
-    });
-    const tipShow = ref(false);
-    const guideInfo = ref({} as any);
-    const getAllGuidance = async () => {
-      try {
-        const res = await getGuidance({ guideTag: 'teacher-guideInfo' });
-        if (res.data) {
-          guideInfo.value = JSON.parse(res.data?.guideValue) || null;
-        } else {
-          guideInfo.value = {};
-        }
-        if (guideInfo.value && guideInfo.value.lessonsGuide) {
-          tipShow.value = false;
-        } else {
-          tipShow.value = true;
-        }
-      } catch (e) {
-        console.log(e);
-      }
-      // const guideInfo = localStorage.getItem('teacher-guideInfo');
-    };
-    getAllGuidance();
-    // const guideInfo = localStorage.getItem('teacher-guideInfo');
-    // if (guideInfo && JSON.parse(guideInfo).lessonsGuide) {
-    //   tipShow.value = false;
-    // } else {
-    //   tipShow.value = true;
-    // }
-    const getStepELe = () => {
-      const ele: HTMLElement = document.getElementById(`lessons-${data.step}`)!;
-      if (ele) {
-        const eleRect = ele.getBoundingClientRect();
-
-        const left = data.steps[data.step].eleRectPadding?.left || 0;
-        const top = data.steps[data.step].eleRectPadding?.top || 0;
-        const width = data.steps[data.step].eleRectPadding?.width || 0;
-        const height = data.steps[data.step].eleRectPadding?.height || 0;
-        data.box = {
-          left: eleRect.x - left + 'px',
-          top: eleRect.y - top + 'px',
-          width: eleRect.width + width + 'px',
-          height: eleRect.height + height + 'px'
-        };
-        // console.log(`coai-${data.step}`, data.box);
-      } else {
-        handleNext();
-      }
-    };
-    const onResetGuide = async (name: string) => {
-      try {
-        if (name !== 'prepare-lessons') return;
-        if (!guideInfo.value) {
-          guideInfo.value = { lessonsGuide: false };
-        } else {
-          guideInfo.value.lessonsGuide = false;
-        }
-        try {
-          await setGuidance({
-            guideTag: 'teacher-guideInfo',
-            guideValue: JSON.stringify(guideInfo.value)
-          });
-        } catch (e) {
-          console.log(e);
-        }
-        data.step = 0;
-        getStepELe();
-        tipShow.value = true;
-      } catch {
-        //
-      }
-    };
-    onMounted(() => {
-      // 等所有Mounte之后再加上 防止获取不到id
-      setTimeout(() => {
-        getStepELe();
-      }, 0);
-      window.addEventListener('resize', resetSize);
-      eventGlobal.on('teacher-guideInfo', onResetGuide);
-    });
-    const resetSize = () => {
-      getStepELe();
-    };
-
-    onUnmounted(() => {
-      window.removeEventListener('resize', resetSize);
-      eventGlobal.off('teacher-guideInfo', onResetGuide);
-    });
-
-    const handleNext = () => {
-      if (data.step >= 4) {
-        endGuide();
-        return;
-      }
-      data.step = data.step + 1;
-      getStepELe();
-    };
-
-    const endGuide = async () => {
-      // let guideInfo =
-      //   JSON.parse(localStorage.getItem('teacher-guideInfo')|| '{}') || null;
-      if (!guideInfo.value) {
-        guideInfo.value = { lessonsGuide: true };
-      } else {
-        guideInfo.value.lessonsGuide = true;
-      }
-      try {
-        const res = await setGuidance({
-          guideTag: 'teacher-guideInfo',
-          guideValue: JSON.stringify(guideInfo.value)
-        });
-      } catch (e) {
-        console.log(e);
-      }
-      // localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
-      tipShow.value = false;
-      //  localStorage.setItem('endC')
-    };
-    return () => (
-      <>
-        {tipShow.value ? (
-          <div
-            v-model:show={tipShow.value}
-            class={['n-modal-mask', 'n-modal-mask-guide']}>
-            <div class={styles.content} onClick={() => handleNext()}>
-              <div
-                class={styles.backBtn}
-                onClick={(e: Event) => {
-                  e.stopPropagation();
-                  endGuide();
-                }}>
-                跳过
-              </div>
-              <div
-                class={styles.box}
-                style={{ ...data.box }}
-                id={`modeType-${data.step}`}>
-                {data.steps.map((item: any, index) => (
-                  <div
-                    onClick={(e: Event) => e.stopPropagation()}
-                    class={styles.item}
-                    style={
-                      item.type == 'bottom'
-                        ? {
-                            display: index === data.step ? '' : 'none',
-                            left: `${item.eleRect?.left}px`,
-                            top: `-${item.imgStyle?.height}`
-                          }
-                        : item.type == 'left'
-                        ? {
-                            display: index === data.step ? '' : 'none',
-
-                            top: `${
-                              parseFloat(data.box?.height) / 2 -
-                              (parseFloat(item.imgStyle?.height) * 1920) /
-                                100 /
-                                2 -
-                              14
-                            }px`
-                          }
-                        : {
-                            display: index === data.step ? '' : 'none',
-                            left: `${item.eleRect?.left}px`,
-                            top: `${data.box?.height}`
-                          }
-                    }>
-                    <img
-                      class={styles.img}
-                      style={item.imgStyle}
-                      src={item.img}
-                    />
-                    {/* <img
-                      class={styles.iconHead}
-                      style={item.handStyle}
-                      src={getImage('indexDot.png')}
-                    /> */}
-                    <div class={styles.btns} style={item.btnsStyle}>
-                      {data.step + 1 == data.steps.length ? (
-                        <>
-                          <div class={[styles.btn]} onClick={() => endGuide()}>
-                            完成
-                          </div>
-                          {/* <div
-                            class={styles.nextBtn}
-                            onClick={() => {
-                              data.step = 0;
-                              getStepELe();
-                            }}>
-                            再看一遍
-                          </div> */}
-                        </>
-                      ) : (
-                        <div class={styles.btn} onClick={() => handleNext()}>
-                          下一步 ({data.step + 1}/{data.steps.length})
-                        </div>
-                      )}
-                    </div>
-                  </div>
-                ))}
-              </div>
-            </div>
-          </div>
-        ) : null}
-      </>
-    );
-  }
-});
+import { NButton } from 'naive-ui';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  onUnmounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { getImage } from './images';
+import { eventGlobal, px2vw, px2vwH } from '@/utils/index';
+import { getGuidance, setGuidance } from './api';
+export default defineComponent({
+  name: 'lessons-guide',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const data = reactive({
+      box: {
+        height: '0px'
+      } as any,
+      show: false,
+      /**
+       *
+            width:  px2vw(840),
+            height:  px2vw(295)
+       */
+      steps: [
+        {
+          ele: '',
+          eleRect: {} as DOMRect,
+          img: getImage('lessons1.png'),
+          handStyle: {
+            top: '0.91rem'
+          },
+          imgStyle: {
+            top: px2vw(-4),
+            left: px2vw(-50),
+            width: px2vw(420),
+            height: px2vw(228)
+          },
+          btnsStyle: {
+            bottom: px2vw(35),
+            left: px2vw(111)
+          },
+          eleRectPadding: {
+            left: 7,
+            top: 7,
+            width: 14,
+            height: -9
+          }
+        }
+      ],
+      step: 0
+    });
+    const tipShow = ref(false);
+    const guideInfo = ref({} as any);
+    const getAllGuidance = async () => {
+      try {
+        const res = await getGuidance({ guideTag: 'teacher-guideInfo' });
+        if (res.data) {
+          guideInfo.value = JSON.parse(res.data?.guideValue) || null;
+        } else {
+          guideInfo.value = {};
+        }
+        if (guideInfo.value && guideInfo.value.lessonsGuide) {
+          tipShow.value = false;
+        } else {
+          tipShow.value = true;
+        }
+      } catch (e) {
+        console.log(e);
+      }
+      // const guideInfo = localStorage.getItem('teacher-guideInfo');
+    };
+    getAllGuidance();
+    // const guideInfo = localStorage.getItem('teacher-guideInfo');
+    // if (guideInfo && JSON.parse(guideInfo).lessonsGuide) {
+    //   tipShow.value = false;
+    // } else {
+    //   tipShow.value = true;
+    // }
+    const getStepELe = () => {
+      const ele: HTMLElement = document.getElementById(`lessons-${data.step}`)!;
+      if (ele) {
+        const eleRect = ele.getBoundingClientRect();
+        console.log(ele, eleRect, 'eleRect');
+
+        const left = data.steps[data.step].eleRectPadding?.left || 0;
+        const top = data.steps[data.step].eleRectPadding?.top || 0;
+        const width = data.steps[data.step].eleRectPadding?.width || 0;
+        const height = data.steps[data.step].eleRectPadding?.height || 0;
+        data.box = {
+          left: eleRect.x - left + 'px',
+          top: eleRect.y - top + 'px',
+          width: eleRect.width + width + 'px',
+          height: eleRect.height + height + 'px'
+        };
+        console.log(`coai-${data.step}`, data.box);
+      } else {
+        handleNext();
+      }
+    };
+    const onResetGuide = async (name: string) => {
+      try {
+        if (name !== 'prepare-lessons') return;
+        if (!guideInfo.value) {
+          guideInfo.value = { lessonsGuide: false };
+        } else {
+          guideInfo.value.lessonsGuide = false;
+        }
+        try {
+          await setGuidance({
+            guideTag: 'teacher-guideInfo',
+            guideValue: JSON.stringify(guideInfo.value)
+          });
+        } catch (e) {
+          console.log(e);
+        }
+        data.step = 0;
+        getStepELe();
+        tipShow.value = true;
+      } catch {
+        //
+      }
+    };
+    onMounted(() => {
+      // 等所有Mounte之后再加上 防止获取不到id
+      setTimeout(() => {
+        getStepELe();
+      }, 0);
+      window.addEventListener('resize', resetSize);
+      eventGlobal.on('teacher-guideInfo', onResetGuide);
+    });
+    const resetSize = () => {
+      getStepELe();
+    };
+
+    onUnmounted(() => {
+      window.removeEventListener('resize', resetSize);
+      eventGlobal.off('teacher-guideInfo', onResetGuide);
+    });
+
+    const handleNext = () => {
+      if (data.step >= 4) {
+        endGuide();
+        return;
+      }
+      data.step = data.step + 1;
+      getStepELe();
+    };
+
+    const endGuide = async () => {
+      // let guideInfo =
+      //   JSON.parse(localStorage.getItem('teacher-guideInfo')|| '{}') || null;
+      if (!guideInfo.value) {
+        guideInfo.value = { lessonsGuide: true };
+      } else {
+        guideInfo.value.lessonsGuide = true;
+      }
+      try {
+        const res = await setGuidance({
+          guideTag: 'teacher-guideInfo',
+          guideValue: JSON.stringify(guideInfo.value)
+        });
+      } catch (e) {
+        console.log(e);
+      }
+      // localStorage.setItem('teacher-guideInfo', JSON.stringify(guideInfo));
+      tipShow.value = false;
+      //  localStorage.setItem('endC')
+    };
+    return () => (
+      <>
+        {tipShow.value ? (
+          <div
+            v-model:show={tipShow.value}
+            class={['n-modal-mask', 'n-modal-mask-guide']}>
+            <div class={styles.content} onClick={() => handleNext()}>
+              <div
+                class={styles.backBtn}
+                onClick={(e: Event) => {
+                  e.stopPropagation();
+                  endGuide();
+                }}>
+                跳过
+              </div>
+              <div
+                class={styles.box}
+                style={{ ...data.box }}
+                id={`modeType-${data.step}`}>
+                {data.steps.map((item: any, index) => (
+                  <div
+                    onClick={(e: Event) => e.stopPropagation()}
+                    class={styles.item}
+                    style={
+                      item.type == 'bottom'
+                        ? {
+                            display: index === data.step ? '' : 'none',
+                            left: `${item.eleRect?.left}px`,
+                            top: `-${item.imgStyle?.height}`
+                          }
+                        : item.type == 'left'
+                        ? {
+                            display: index === data.step ? '' : 'none',
+
+                            top: `${
+                              parseFloat(data.box?.height) / 2 -
+                              (parseFloat(item.imgStyle?.height) * 1920) /
+                                100 /
+                                2 -
+                              14
+                            }px`
+                          }
+                        : {
+                            display: index === data.step ? '' : 'none',
+                            left: `${item.eleRect?.left}px`,
+                            top: `${data.box?.height}`
+                          }
+                    }>
+                    <img
+                      class={styles.img}
+                      style={item.imgStyle}
+                      src={item.img}
+                    />
+                    {/* <img
+                      class={styles.iconHead}
+                      style={item.handStyle}
+                      src={getImage('indexDot.png')}
+                    /> */}
+                    <div class={styles.btns} style={item.btnsStyle}>
+                      {data.step + 1 == data.steps.length ? (
+                        <>
+                          <div class={[styles.btn]} onClick={() => endGuide()}>
+                            完成
+                          </div>
+                          {/* <div
+                            class={styles.nextBtn}
+                            onClick={() => {
+                              data.step = 0;
+                              getStepELe();
+                            }}>
+                            再看一遍
+                          </div> */}
+                        </>
+                      ) : (
+                        <div class={styles.btn} onClick={() => handleNext()}>
+                          下一步 ({data.step + 1}/{data.steps.length})
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        ) : null}
+      </>
+    );
+  }
+});

+ 70 - 70
src/hooks/useDrag/dragbom.jsx

@@ -1,70 +1,70 @@
-import { defineComponent, reactive, onMounted, ref } from 'vue';
-import styles from './index.module.less';
-import { setGuidanceShow, guidanceShow } from './useDragGuidance';
-// 底部拖动区域
-export default defineComponent({
-  name: 'dragBom',
-  setup() {
-    const data = reactive({
-      guidePos: 'bottom'
-    });
-    const guidanceShowDela = ref(false);
-    const initGuidePos = () => {
-      const pageHeight =
-        document.documentElement.clientHeight || document.body.clientHeight;
-      const pageWidth =
-        document.documentElement.clientWidth || document.body.clientWidth;
-      const guideHeight =
-        document.querySelector('.bom_guide')?.clientHeight || 0;
-      const guideWidth = document.querySelector('.bom_guide')?.clientWidth || 0;
-      const dragBBox = document
-        .querySelector('.bom_drag')
-        ?.getBoundingClientRect();
-      const dragTop = dragBBox?.top || 0;
-      const dragLeft = dragBBox?.left || 0;
-      // 引导页出现在下边
-      if (pageHeight - dragTop > guideHeight) {
-        data.guidePos = 'bottom';
-      } else {
-        // 引导页出现在左边or右边
-        data.guidePos = dragLeft > guideWidth ? 'left' : 'right';
-      }
-    };
-    onMounted(() => {
-      if (guidanceShow.value) {
-        const _itme = setTimeout(() => {
-          clearTimeout(_itme);
-          initGuidePos();
-          guidanceShowDela.value = true;
-        }, 300);
-      }
-    });
-    return () => (
-      <>
-        <div class={[styles.dragBom, 'bom_drag']}>
-          <div class={styles.box}></div>
-          <div class={styles.box}></div>
-        </div>
-        {guidanceShow.value && (
-          <div
-            onClick={setGuidanceShow}
-            class={[
-              styles.guide,
-              data.guidePos === 'left' && styles.guideLeft,
-              data.guidePos === 'right' && styles.guideRight,
-              !guidanceShowDela.value && styles.hideGuide,
-              'bom_guide'
-            ]}>
-            <div class={styles.guideBg}></div>
-            <div
-              class={styles.guideDone}
-              onClick={e => {
-                e.stopPropagation();
-                setGuidanceShow();
-              }}></div>
-          </div>
-        )}
-      </>
-    );
-  }
-});
+import { defineComponent, reactive, onMounted, ref } from 'vue';
+import styles from './index.module.less';
+import { setGuidanceShow, guidanceShow } from './useDragGuidance';
+// 底部拖动区域
+export default defineComponent({
+  name: 'dragBom',
+  setup() {
+    const data = reactive({
+      guidePos: 'bottom'
+    });
+    const guidanceShowDela = ref(false);
+    const initGuidePos = () => {
+      const pageHeight =
+        document.documentElement.clientHeight || document.body.clientHeight;
+      const pageWidth =
+        document.documentElement.clientWidth || document.body.clientWidth;
+      const guideHeight =
+        document.querySelector('.bom_guide')?.clientHeight || 0;
+      const guideWidth = document.querySelector('.bom_guide')?.clientWidth || 0;
+      const dragBBox = document
+        .querySelector('.bom_drag')
+        ?.getBoundingClientRect();
+      const dragTop = dragBBox?.top || 0;
+      const dragLeft = dragBBox?.left || 0;
+      // 引导页出现在下边
+      if (pageHeight - dragTop > guideHeight) {
+        data.guidePos = 'bottom';
+      } else {
+        // 引导页出现在左边or右边
+        data.guidePos = dragLeft > guideWidth ? 'left' : 'right';
+      }
+    };
+    onMounted(() => {
+      if (guidanceShow.value) {
+        const _itme = setTimeout(() => {
+          clearTimeout(_itme);
+          initGuidePos();
+          guidanceShowDela.value = true;
+        }, 300);
+      }
+    });
+    return () => (
+      <>
+        <div class={[styles.dragBom, 'bom_drag']}>
+          <div class={styles.box}></div>
+          <div class={styles.box}></div>
+        </div>
+        {guidanceShow.value && (
+          <div
+            onClick={setGuidanceShow}
+            class={[
+              styles.guide,
+              data.guidePos === 'left' && styles.guideLeft,
+              data.guidePos === 'right' && styles.guideRight,
+              !guidanceShowDela.value && styles.hideGuide,
+              'bom_guide'
+            ]}>
+            <div class={styles.guideBg}></div>
+            <div
+              class={styles.guideDone}
+              onClick={e => {
+                e.stopPropagation();
+                setGuidanceShow();
+              }}></div>
+          </div>
+        )}
+      </>
+    );
+  }
+});

+ 162 - 162
src/hooks/useDrag/index.ts

@@ -1,162 +1,162 @@
-// 弹窗拖动
-import { ref, Ref, watch, nextTick, computed } from 'vue';
-
-type posType = {
-  top: number;
-  left: number;
-};
-
-/**
- * @params classList  可拖动地方的class值,也为唯一值
- * @params boxClass  容器class值必须为唯一值,这个class和useid拼接 作为缓存主键
- * @params dragShow  弹窗是否显示
- * @params userId    当前用户id
- */
-export default function useDrag(
-  classList: string[],
-  boxClass: string,
-  dragShow: Ref<boolean>,
-  userId: string
-) {
-  const pos = ref<posType>({
-    top: -1, // -1 为初始值 代表没有缓存 默认居中
-    left: -1
-  });
-  const useIdDargClass = userId + boxClass;
-  watch(dragShow, () => {
-    if (dragShow.value) {
-      // 初始化pos值
-      initPos();
-      window.addEventListener('resize', refreshPos);
-      nextTick(() => {
-        const boxClassDom = document.querySelector(
-          `.${boxClass}`
-        ) as HTMLElement;
-        if (!boxClassDom) {
-          return;
-        }
-        classList.map((className: string) => {
-          const classDom = document.querySelector(
-            `.${className}`
-          ) as HTMLElement;
-          if (classDom) {
-            classDom.style.cursor = 'move';
-            drag(classDom, boxClassDom, pos);
-          }
-        });
-      });
-    } else {
-      window.removeEventListener('resize', refreshPos);
-      setCachePos(useIdDargClass, pos.value);
-    }
-  });
-  const styleDrag = computed(() => {
-    // 没有设置拖动的时候保持原本的
-    return pos.value.left === -1 && pos.value.top === -1
-      ? {}
-      : {
-          position: 'fixed',
-          left: `${pos.value.left}px`,
-          top: `${pos.value.top}px`
-        };
-  });
-  function initPos() {
-    const posCache = getCachePos(useIdDargClass);
-    // 有缓存 用缓存的值,没有缓存用默认
-    if (posCache) {
-      pos.value = posCache;
-      nextTick(() => {
-        const _itme = setTimeout(() => {
-          clearTimeout(_itme);
-          refreshPos();
-        }, 300);
-      });
-    }
-  }
-  function refreshPos() {
-    if (pos.value.left === -1 && pos.value.top === -1) {
-      return;
-    }
-    const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
-    if (!boxClassDom) return;
-    const parentElementRect = boxClassDom.getBoundingClientRect();
-    const clientWidth = document.documentElement.clientWidth;
-    const clientHeight = document.documentElement.clientHeight;
-    const { top, left } = pos.value;
-    const maxLeft = clientWidth - parentElementRect.width;
-    const maxTop = clientHeight - parentElementRect.height;
-    let moveX = left;
-    let moveY = top;
-    const minLeft = 0;
-    const minTop = 0;
-    moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
-    moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
-    pos.value = {
-      top: moveY,
-      left: moveX
-    };
-  }
-  return {
-    pos,
-    styleDrag
-  };
-}
-
-// 拖动
-function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
-  function mousedown(e: MouseEvent) {
-    const parentElementRect = parentElement.getBoundingClientRect();
-    const downX = e.clientX;
-    const downY = e.clientY;
-    const clientWidth = document.documentElement.clientWidth;
-    const clientHeight = document.documentElement.clientHeight;
-    const maxLeft = clientWidth - parentElementRect.width;
-    const maxTop = clientHeight - parentElementRect.height;
-    const minLeft = 0;
-    const minTop = 0;
-    function onMousemove(e: MouseEvent) {
-      let moveX = parentElementRect.left + (e.clientX - downX);
-      let moveY = parentElementRect.top + (e.clientY - downY);
-      moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
-      moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
-      pos.value = {
-        top: moveY,
-        left: moveX
-      };
-    }
-    function onMouseup() {
-      document.removeEventListener('mousemove', onMousemove);
-      document.removeEventListener('mouseup', onMouseup);
-    }
-    document.addEventListener('mousemove', onMousemove);
-    document.addEventListener('mouseup', onMouseup);
-  }
-  el.addEventListener('mousedown', mousedown);
-}
-
-// 缓存
-const localStorageName = 'dragCachePos';
-function getCachePos(useIdDargClass: string): null | undefined | posType {
-  const localCachePos = localStorage.getItem(localStorageName);
-  if (localCachePos) {
-    try {
-      return JSON.parse(localCachePos)[useIdDargClass];
-    } catch {
-      return null;
-    }
-  }
-  return null;
-}
-function setCachePos(useIdDargClass: string, pos: posType) {
-  const localCachePos = localStorage.getItem(localStorageName);
-  let cachePosObj: Record<string, any> = {};
-  if (localCachePos) {
-    try {
-      cachePosObj = JSON.parse(localCachePos);
-    } catch {
-      //
-    }
-  }
-  cachePosObj[useIdDargClass] = pos;
-  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
-}
+// 弹窗拖动
+import { ref, Ref, watch, nextTick, computed } from 'vue';
+
+type posType = {
+  top: number;
+  left: number;
+};
+
+/**
+ * @params classList  可拖动地方的class值,也为唯一值
+ * @params boxClass  容器class值必须为唯一值,这个class和useid拼接 作为缓存主键
+ * @params dragShow  弹窗是否显示
+ * @params userId    当前用户id
+ */
+export default function useDrag(
+  classList: string[],
+  boxClass: string,
+  dragShow: Ref<boolean>,
+  userId: string
+) {
+  const pos = ref<posType>({
+    top: -1, // -1 为初始值 代表没有缓存 默认居中
+    left: -1
+  });
+  const useIdDargClass = userId + boxClass;
+  watch(dragShow, () => {
+    if (dragShow.value) {
+      // 初始化pos值
+      initPos();
+      window.addEventListener('resize', refreshPos);
+      nextTick(() => {
+        const boxClassDom = document.querySelector(
+          `.${boxClass}`
+        ) as HTMLElement;
+        if (!boxClassDom) {
+          return;
+        }
+        classList.map((className: string) => {
+          const classDom = document.querySelector(
+            `.${className}`
+          ) as HTMLElement;
+          if (classDom) {
+            classDom.style.cursor = 'move';
+            drag(classDom, boxClassDom, pos);
+          }
+        });
+      });
+    } else {
+      window.removeEventListener('resize', refreshPos);
+      setCachePos(useIdDargClass, pos.value);
+    }
+  });
+  const styleDrag = computed(() => {
+    // 没有设置拖动的时候保持原本的
+    return pos.value.left === -1 && pos.value.top === -1
+      ? {}
+      : {
+        position: 'fixed',
+        left: `${pos.value.left}px`,
+        top: `${pos.value.top}px`
+      };
+  });
+  function initPos() {
+    const posCache = getCachePos(useIdDargClass);
+    // 有缓存 用缓存的值,没有缓存用默认
+    if (posCache) {
+      pos.value = posCache;
+      nextTick(() => {
+        const _itme = setTimeout(() => {
+          clearTimeout(_itme);
+          refreshPos();
+        }, 300);
+      });
+    }
+  }
+  function refreshPos() {
+    if (pos.value.left === -1 && pos.value.top === -1) {
+      return;
+    }
+    const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
+    if (!boxClassDom) return;
+    const parentElementRect = boxClassDom.getBoundingClientRect();
+    const clientWidth = document.documentElement.clientWidth;
+    const clientHeight = document.documentElement.clientHeight;
+    const { top, left } = pos.value;
+    const maxLeft = clientWidth - parentElementRect.width;
+    const maxTop = clientHeight - parentElementRect.height;
+    let moveX = left;
+    let moveY = top;
+    const minLeft = 0;
+    const minTop = 0;
+    moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+    moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+    pos.value = {
+      top: moveY,
+      left: moveX
+    };
+  }
+  return {
+    pos,
+    styleDrag
+  };
+}
+
+// 拖动
+function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
+  function mousedown(e: MouseEvent) {
+    const parentElementRect = parentElement.getBoundingClientRect();
+    const downX = e.clientX;
+    const downY = e.clientY;
+    const clientWidth = document.documentElement.clientWidth;
+    const clientHeight = document.documentElement.clientHeight;
+    const maxLeft = clientWidth - parentElementRect.width;
+    const maxTop = clientHeight - parentElementRect.height;
+    const minLeft = 0;
+    const minTop = 0;
+    function onMousemove(e: MouseEvent) {
+      let moveX = parentElementRect.left + (e.clientX - downX);
+      let moveY = parentElementRect.top + (e.clientY - downY);
+      moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+      moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+      pos.value = {
+        top: moveY,
+        left: moveX
+      };
+    }
+    function onMouseup() {
+      document.removeEventListener('mousemove', onMousemove);
+      document.removeEventListener('mouseup', onMouseup);
+    }
+    document.addEventListener('mousemove', onMousemove);
+    document.addEventListener('mouseup', onMouseup);
+  }
+  el.addEventListener('mousedown', mousedown);
+}
+
+// 缓存
+const localStorageName = 'dragCachePos';
+function getCachePos(useIdDargClass: string): null | undefined | posType {
+  const localCachePos = localStorage.getItem(localStorageName);
+  if (localCachePos) {
+    try {
+      return JSON.parse(localCachePos)[useIdDargClass];
+    } catch {
+      return null;
+    }
+  }
+  return null;
+}
+function setCachePos(useIdDargClass: string, pos: posType) {
+  const localCachePos = localStorage.getItem(localStorageName);
+  let cachePosObj: Record<string, any> = {};
+  if (localCachePos) {
+    try {
+      cachePosObj = JSON.parse(localCachePos);
+    } catch {
+      //
+    }
+  }
+  cachePosObj[useIdDargClass] = pos;
+  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
+}

+ 84 - 84
src/views/classList/classDetail.tsx

@@ -1,84 +1,84 @@
-import { defineComponent, onMounted, ref } from 'vue';
-import styles from './index.module.less';
-import { NTabs, NTabPane } from 'naive-ui';
-import { useRoute, onBeforeRouteUpdate } from 'vue-router';
-import CBreadcrumb from '@/components/CBreadcrumb';
-import ClassStudent from './components/classStudent';
-import AfterWork from './components/afterWork';
-import ClassRecord from './components/classRecord';
-import TestRecode from './components/testRecode';
-import { getTabsCache, setTabsCaches } from '@/hooks/use-async';
-import { classGroupDetail } from './api';
-export default defineComponent({
-  name: 'base-setting',
-  setup() {
-    const classDetailTabs = sessionStorage.getItem('classDetailTabs');
-    const activeTab = ref(classDetailTabs || 'student');
-    sessionStorage.removeItem('classDetailTabs');
-    const route = useRoute();
-    const routerList = ref([
-      { name: '班级管理', path: '/classList' },
-      { name: route.query.name, path: '/classDetail' }
-    ] as any);
-
-    getTabsCache((val: any) => {
-      if (val.form.tabName) {
-        activeTab.value = val.form.tabName;
-      }
-    });
-    const setTabs = (val: any) => {
-      setTabsCaches(val, 'tabName', route);
-    };
-    const upgradeFlag = ref();
-    const getClassGroupDetail = async () => {
-      try {
-        const id = route.query.id;
-        const { data } = await classGroupDetail({ id });
-        upgradeFlag.value = data.upgradeFlag ? 1 : 0; // 是否为历史班
-      } catch {
-        //
-      }
-    };
-    getClassGroupDetail();
-    onMounted(() => {
-      if (classDetailTabs) {
-        activeTab.value = classDetailTabs;
-      }
-    });
-
-    onBeforeRouteUpdate(() => {
-      const classDetailTabs = sessionStorage.getItem('classDetailTabs');
-      if (classDetailTabs) {
-        activeTab.value = classDetailTabs;
-      }
-    });
-    return () => (
-      <div>
-        <CBreadcrumb list={routerList.value}></CBreadcrumb>
-        <div class={[styles.listWrap, styles.infoListWrap]}>
-          <NTabs
-            class={styles.customTabs}
-            v-model:value={activeTab.value}
-            onUpdate:value={(val: any) => setTabs(val)}
-            size="large"
-            animated={false}
-            pane-wrapper-style="margin: 0 -4px"
-            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
-            <NTabPane name="student" tab="学员名单">
-              <ClassStudent upgradeFlag={upgradeFlag.value}></ClassStudent>
-            </NTabPane>
-            <NTabPane name="afterWork" tab="课后作业">
-              <AfterWork upgradeFlag={upgradeFlag.value}></AfterWork>
-            </NTabPane>
-            <NTabPane name="practice" tab="练习记录">
-              <TestRecode></TestRecode>
-            </NTabPane>
-            <NTabPane name="attendclass" tab="上课记录">
-              <ClassRecord />
-            </NTabPane>
-          </NTabs>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, ref } from 'vue';
+import styles from './index.module.less';
+import { NTabs, NTabPane } from 'naive-ui';
+import { useRoute, onBeforeRouteUpdate } from 'vue-router';
+import CBreadcrumb from '@/components/CBreadcrumb';
+import ClassStudent from './components/classStudent';
+import AfterWork from './components/afterWork';
+import ClassRecord from './components/classRecord';
+import TestRecode from './components/testRecode';
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async';
+import { classGroupDetail } from './api';
+export default defineComponent({
+  name: 'base-setting',
+  setup() {
+    const classDetailTabs = sessionStorage.getItem('classDetailTabs');
+    const activeTab = ref(classDetailTabs || 'student');
+    sessionStorage.removeItem('classDetailTabs');
+    const route = useRoute();
+    const routerList = ref([
+      { name: '班级管理', path: '/classList' },
+      { name: route.query.name, path: '/classDetail' }
+    ] as any);
+
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        activeTab.value = val.form.tabName;
+      }
+    });
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route);
+    };
+    const upgradeFlag = ref();
+    const getClassGroupDetail = async () => {
+      try {
+        const id = route.query.id;
+        const { data } = await classGroupDetail({ id });
+        upgradeFlag.value = data.upgradeFlag ? 1 : 0; // 是否为历史班
+      } catch {
+        //
+      }
+    };
+    getClassGroupDetail();
+    onMounted(() => {
+      if (classDetailTabs) {
+        activeTab.value = classDetailTabs;
+      }
+    });
+
+    onBeforeRouteUpdate(() => {
+      const classDetailTabs = sessionStorage.getItem('classDetailTabs');
+      if (classDetailTabs) {
+        activeTab.value = classDetailTabs;
+      }
+    });
+    return () => (
+      <div>
+        <CBreadcrumb list={routerList.value}></CBreadcrumb>
+        <div class={[styles.listWrap, styles.infoListWrap]}>
+          <NTabs
+            class={styles.customTabs}
+            v-model:value={activeTab.value}
+            onUpdate:value={(val: any) => setTabs(val)}
+            size="large"
+            animated={false}
+            pane-wrapper-style="margin: 0 -4px"
+            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
+            <NTabPane name="student" tab="学员名单">
+              <ClassStudent upgradeFlag={upgradeFlag.value}></ClassStudent>
+            </NTabPane>
+            <NTabPane name="afterWork" tab="课后作业">
+              <AfterWork upgradeFlag={upgradeFlag.value}></AfterWork>
+            </NTabPane>
+            <NTabPane name="practice" tab="练习记录">
+              <TestRecode></TestRecode>
+            </NTabPane>
+            <NTabPane name="attendclass" tab="上课记录">
+              <ClassRecord />
+            </NTabPane>
+          </NTabs>
+        </div>
+      </div>
+    );
+  }
+});

+ 96 - 96
src/views/classList/components/classRecord.module.less

@@ -1,96 +1,96 @@
-.tableSpace {
-  gap: 8px 12px;
-}
-
-.tableContainer {
-  background: #F7F9FF;
-  border-radius: 16px;
-  width: 548px;
-  height: 195px;
-}
-
-.header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 20px;
-  font-size: 20px;
-  color: #131415;
-  line-height: 28px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
-
-  .time {
-    font-size: max(20px, 14Px);
-    color: #131415;
-    line-height: 28px;
-  }
-
-  .ntag {
-    color: #2089FF;
-    border-radius: 6px;
-    // border: 1px solid #97C7FF;
-    font-size: max(16px, 12Px);
-    --n-border: 1px solid #97C7FF;
-    padding: 2px 14px;
-    --n-height: max(28px, 24Px) !important;
-  }
-}
-
-.content {
-  padding: 20px;
-  display: flex;
-  align-items: flex-start;
-
-  .navatar {
-    width: 60px;
-    height: 60px;
-    border-radius: 50%;
-    padding: 2px;
-    border: 1px solid #198CFE;
-    margin-right: 15px;
-    flex-shrink: 0;
-    background-color: #fff !important;
-
-    :global {
-      img {
-        border-radius: 50%;
-      }
-    }
-  }
-
-  .userInfo {
-    padding-top: 4px;
-
-    h2 {
-      font-size: max(20px, 14Px);
-      font-weight: 600;
-      color: #131415;
-      line-height: 28px;
-    }
-
-    p {
-      font-size: max(16px, 12Px);
-      color: #777777;
-      line-height: 28px;
-    }
-
-    .subjectName {
-      font-size: max(14px, 11Px);
-      font-weight: 400;
-      color: #FFFFFF;
-      background: #198CFE;
-      border-radius: 5px;
-      padding: 2px 8px 2px;
-      margin-right: 4px;
-    }
-  }
-
-}
-
-.nowEmpty {
-  margin-top: 100px;
-}
-
-// .tableWrap {
-//   min-height: calc(100vh - 400px)
-// }
+.tableSpace {
+  gap: 8px 12px;
+}
+
+.tableContainer {
+  background: #F4F7FD;
+  border-radius: 16px;
+  width: 548px;
+  height: 195px;
+}
+
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  font-size: 20px;
+  color: #131415;
+  line-height: 28px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+
+  .time {
+    font-size: max(20px, 14Px);
+    color: #131415;
+    line-height: 28px;
+  }
+
+  .ntag {
+    color: #2089FF;
+    border-radius: 6px;
+    // border: 1px solid #97C7FF;
+    font-size: max(16px, 12Px);
+    --n-border: 1px solid #97C7FF;
+    padding: 2px 14px;
+    --n-height: max(28px, 24Px) !important;
+  }
+}
+
+.content {
+  padding: 20px;
+  display: flex;
+  align-items: flex-start;
+
+  .navatar {
+    width: 60px;
+    height: 60px;
+    border-radius: 50%;
+    padding: 2px;
+    border: 1px solid #198CFE;
+    margin-right: 15px;
+    flex-shrink: 0;
+    background-color: #fff !important;
+
+    :global {
+      img {
+        border-radius: 50%;
+      }
+    }
+  }
+
+  .userInfo {
+    padding-top: 4px;
+
+    h2 {
+      font-size: max(20px, 14Px);
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+    }
+
+    p {
+      font-size: max(16px, 12Px);
+      color: #777777;
+      line-height: 28px;
+    }
+
+    .subjectName {
+      font-size: max(14px, 11Px);
+      font-weight: 400;
+      color: #FFFFFF;
+      background: #198CFE;
+      border-radius: 5px;
+      padding: 2px 8px 2px;
+      margin-right: 4px;
+    }
+  }
+
+}
+
+.nowmpty {
+  margin-top: 100px;
+}
+
+// .tableWrap {
+//   min-height: calc(100vh - 400px)
+// }

+ 233 - 233
src/views/classList/components/classRecord.tsx

@@ -1,233 +1,233 @@
-import { defineComponent, onMounted, reactive } from 'vue';
-import { NAvatar, NButton, NForm, NFormItem, NSpace, NTag } from 'naive-ui';
-import Pagination from '@/components/pagination';
-import { courseSchedulePage } from '../api';
-import { useRoute } from 'vue-router';
-import CDatePicker from '/src/components/CDatePicker';
-import styles from './classRecord.module.less';
-import teacherIcon from '@components/layout/images/teacherIcon.png';
-import dayjs from 'dayjs';
-import { getTimes } from '/src/utils';
-import TheEmpty from '/src/components/TheEmpty';
-import CSelect from '/src/components/CSelect';
-import { api_teacherPage } from '../../setting/api';
-import { useUserStore } from '/src/store/modules/users';
-import { initCache, setCache } from '/src/hooks/use-async';
-export default defineComponent({
-  name: 'class-record',
-  setup() {
-    const user = useUserStore();
-    const route = useRoute();
-
-    const formatDefaultTime = () => {
-      const gradeYear: any = route.query.gradeYear;
-      if (gradeYear) {
-        const yearStr = `${gradeYear}-9-1`;
-        const yearStrEnd = `${Number(gradeYear) + 1}-8-31`;
-        return [dayjs(yearStr).valueOf(), dayjs(yearStrEnd).valueOf()];
-      } else {
-        const nowTime = dayjs().format('YYYY-MM-DD');
-        return [
-          dayjs(nowTime).subtract(1, 'month').valueOf(),
-          dayjs(nowTime).valueOf()
-        ];
-      }
-    };
-
-    const state = reactive({
-      searchForm: {
-        teacherId: '',
-        createTimer: formatDefaultTime() as any
-      },
-      teacherList: [],
-      loading: false,
-      pagination: {
-        page: 1,
-        rows: 12,
-        pageTotal: 0
-      },
-      tableList: [] as any
-    });
-    const search = () => {
-      state.pagination.page = 1;
-      getList();
-      setCache({
-        current: state.searchForm,
-        saveKey: 'classDetailRecord'
-      });
-    };
-
-    const onReset = () => {
-      state.searchForm = {
-        teacherId: '',
-        createTimer: formatDefaultTime() as any
-      };
-      search();
-      setCache({
-        current: state.searchForm,
-        saveKey: 'classDetailRecord'
-      });
-    };
-
-    initCache({
-      current: state.searchForm,
-      saveKey: 'classDetailRecord',
-      callBack: (active: any) => {
-        state.searchForm = active;
-      }
-    });
-    const getList = async () => {
-      state.loading = true;
-      try {
-        const { data } = await courseSchedulePage({
-          classGroupId: route.query.id,
-          teacherId: state.searchForm.teacherId,
-          ...getTimes(
-            state.searchForm.createTimer,
-            ['startTime', 'endTime'],
-            'YYYY-MM-DD'
-          ),
-          ...state.pagination
-        });
-        const rows = data.rows || [];
-        rows.forEach((row: any) => {
-          row.lessonCourseware = row.lessonCoursewareJson
-            ? JSON.parse(row.lessonCoursewareJson)
-            : {};
-        });
-        state.tableList = rows;
-        state.pagination.pageTotal = data.total;
-        state.loading = false;
-      } catch (e) {
-        state.loading = false;
-      }
-    };
-
-    const getTeachers = async () => {
-      try {
-        const { data } = await api_teacherPage({
-          schoolId: user.info.schoolInfos?.[0]?.id,
-          page: 1,
-          rows: 1000
-        });
-        const tempList = data.rows || [];
-        const tempT = [] as any;
-        tempList.forEach((item: any) => {
-          tempT.push({
-            label: item.nickname,
-            value: item.id
-          });
-        });
-        state.teacherList = tempT;
-      } catch {
-        //
-      }
-    };
-    onMounted(() => {
-      getTeachers();
-      getList();
-    });
-    return () => (
-      <div>
-        <div class={styles.searchList}>
-          <NForm label-placement="left" inline>
-            <NFormItem>
-              <CSelect
-                {...({
-                  options: [
-                    {
-                      label: '全部老师',
-                      value: ''
-                    },
-                    ...state.teacherList
-                  ],
-                  placeholder: '全部老师',
-                  clearable: true,
-                  inline: true
-                } as any)}
-                v-model:value={state.searchForm.teacherId}></CSelect>
-            </NFormItem>
-
-            <NFormItem>
-              <CDatePicker
-                v-model:value={state.searchForm.createTimer}
-                timerValue={state.searchForm.createTimer}
-                separator={'至'}
-                type="daterange"></CDatePicker>
-            </NFormItem>
-
-            <NFormItem>
-              <NSpace justify="end">
-                <NButton type="primary" class="searchBtn" onClick={search}>
-                  搜索
-                </NButton>
-                <NButton
-                  type="primary"
-                  ghost
-                  class="resetBtn"
-                  onClick={onReset}>
-                  重置
-                </NButton>
-              </NSpace>
-            </NFormItem>
-          </NForm>
-        </div>
-        <div class={styles.tableWrap}>
-          <NSpace class={styles.tableSpace}>
-            {state.tableList.map((item: any) => (
-              <div class={styles.tableContainer}>
-                <div class={styles.header}>
-                  <div class={styles.time}>
-                    {dayjs(item.startTime).format('YYYY-MM-DD HH:mm')}
-                  </div>
-                  {route.query.name && (
-                    <NTag type="primary" class={styles.ntag} strong>
-                      {route.query.name}
-                    </NTag>
-                  )}
-                </div>
-                <div class={styles.content}>
-                  <NAvatar
-                    class={styles.navatar}
-                    round
-                    src={item.teacherAvatar || teacherIcon}
-                  />
-                  <div class={styles.userInfo}>
-                    <h2>{item.teacherName}</h2>
-                    <p>
-                      {item.subjectName && (
-                        <span class={styles.subjectName}>
-                          {item.subjectName}
-                        </span>
-                      )}
-                      {item.lessonCourseware.lessonCoursewareName} |{' '}
-                      {item.lessonCourseware.lessonCoursewareDetailName}|{' '}
-                      {
-                        item.lessonCourseware
-                          .lessonCoursewareKnowledgeDetailName
-                      }
-                      | {item.useChapterLessonCoursewareName}
-                    </p>
-                  </div>
-                </div>
-              </div>
-            ))}
-          </NSpace>
-          {state.tableList.length > 0 && (
-            <Pagination
-              v-model:page={state.pagination.page}
-              v-model:pageSize={state.pagination.rows}
-              v-model:pageTotal={state.pagination.pageTotal}
-              onList={getList}
-              pageSizes={[12, 24, 36, 48]}
-              sync
-              saveKey="classRecord-key"
-            />
-          )}
-
-          {state.tableList.length <= 0 && <TheEmpty class={styles.nowEmpty} />}
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, reactive } from 'vue';
+import { NAvatar, NButton, NForm, NFormItem, NSpace, NTag } from 'naive-ui';
+import Pagination from '@/components/pagination';
+import { courseSchedulePage } from '../api';
+import { useRoute } from 'vue-router';
+import CDatePicker from '/src/components/CDatePicker';
+import styles from './classRecord.module.less';
+import teacherIcon from '@components/layout/images/teacherIcon.png';
+import dayjs from 'dayjs';
+import { getTimes } from '/src/utils';
+import TheEmpty from '/src/components/TheEmpty';
+import CSelect from '/src/components/CSelect';
+import { api_teacherPage } from '../../setting/api';
+import { useUserStore } from '/src/store/modules/users';
+import { initCache, setCache } from '/src/hooks/use-async';
+export default defineComponent({
+  name: 'class-record',
+  setup() {
+    const user = useUserStore();
+    const route = useRoute();
+
+    const formatDefaultTime = () => {
+      const gradeYear: any = route.query.gradeYear;
+      if (gradeYear) {
+        const yearStr = `${gradeYear}-9-1`;
+        const yearStrEnd = `${Number(gradeYear) + 1}-8-31`;
+        return [dayjs(yearStr).valueOf(), dayjs(yearStrEnd).valueOf()];
+      } else {
+        const nowTime = dayjs().format('YYYY-MM-DD');
+        return [
+          dayjs(nowTime).subtract(1, 'month').valueOf(),
+          dayjs(nowTime).valueOf()
+        ];
+      }
+    };
+
+    const state = reactive({
+      searchForm: {
+        teacherId: '',
+        createTimer: formatDefaultTime() as any
+      },
+      teacherList: [],
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 12,
+        pageTotal: 0
+      },
+      tableList: [] as any
+    });
+    const search = () => {
+      state.pagination.page = 1;
+      getList();
+      setCache({
+        current: state.searchForm,
+        saveKey: 'classDetailRecord'
+      });
+    };
+
+    const onReset = () => {
+      state.searchForm = {
+        teacherId: '',
+        createTimer: formatDefaultTime() as any
+      };
+      search();
+      setCache({
+        current: state.searchForm,
+        saveKey: 'classDetailRecord'
+      });
+    };
+
+    initCache({
+      current: state.searchForm,
+      saveKey: 'classDetailRecord',
+      callBack: (active: any) => {
+        state.searchForm = active;
+      }
+    });
+    const getList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await courseSchedulePage({
+          classGroupId: route.query.id,
+          teacherId: state.searchForm.teacherId,
+          ...getTimes(
+            state.searchForm.createTimer,
+            ['startTime', 'endTime'],
+            'YYYY-MM-DD'
+          ),
+          ...state.pagination
+        });
+        const rows = data.rows || [];
+        rows.forEach((row: any) => {
+          row.lessonCourseware = row.lessonCoursewareJson
+            ? JSON.parse(row.lessonCoursewareJson)
+            : {};
+        });
+        state.tableList = rows;
+        state.pagination.pageTotal = data.total;
+        state.loading = false;
+      } catch (e) {
+        state.loading = false;
+      }
+    };
+
+    const getTeachers = async () => {
+      try {
+        const { data } = await api_teacherPage({
+          schoolId: user.info.schoolInfos?.[0]?.id,
+          page: 1,
+          rows: 1000
+        });
+        const tempList = data.rows || [];
+        const tempT = [] as any;
+        tempList.forEach((item: any) => {
+          tempT.push({
+            label: item.nickname,
+            value: item.id
+          });
+        });
+        state.teacherList = tempT;
+      } catch {
+        //
+      }
+    };
+    onMounted(() => {
+      getTeachers();
+      getList();
+    });
+    return () => (
+      <div>
+        <div class={styles.searchList}>
+          <NForm label-placement="left" inline>
+            <NFormItem>
+              <CSelect
+                {...({
+                  options: [
+                    {
+                      label: '全部老师',
+                      value: ''
+                    },
+                    ...state.teacherList
+                  ],
+                  placeholder: '全部老师',
+                  clearable: true,
+                  inline: true
+                } as any)}
+                v-model:value={state.searchForm.teacherId}></CSelect>
+            </NFormItem>
+
+            <NFormItem>
+              <CDatePicker
+                v-model:value={state.searchForm.createTimer}
+                timerValue={state.searchForm.createTimer}
+                separator={'至'}
+                type="daterange"></CDatePicker>
+            </NFormItem>
+
+            <NFormItem>
+              <NSpace justify="end">
+                <NButton type="primary" class="searchBtn" onClick={search}>
+                  搜索
+                </NButton>
+                <NButton
+                  type="primary"
+                  ghost
+                  class="resetBtn"
+                  onClick={onReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </NForm>
+        </div>
+        <div class={styles.tableWrap}>
+          <NSpace class={styles.tableSpace}>
+            {state.tableList.map((item: any) => (
+              <div class={styles.tableContainer}>
+                <div class={styles.header}>
+                  <div class={styles.time}>
+                    {dayjs(item.startTime).format('YYYY-MM-DD HH:mm')}
+                  </div>
+                  {route.query.name && (
+                    <NTag type="primary" class={styles.ntag} strong>
+                      {route.query.name}
+                    </NTag>
+                  )}
+                </div>
+                <div class={styles.content}>
+                  <NAvatar
+                    class={styles.navatar}
+                    round
+                    src={item.teacherAvatar || teacherIcon}
+                  />
+                  <div class={styles.userInfo}>
+                    <h2>{item.teacherName}</h2>
+                    <p>
+                      {item.subjectName && (
+                        <span class={styles.subjectName}>
+                          {item.subjectName}
+                        </span>
+                      )}
+                      {item.lessonCourseware.lessonCoursewareName} |{' '}
+                      {item.lessonCourseware.lessonCoursewareDetailName}|{' '}
+                      {
+                        item.lessonCourseware
+                          .lessonCoursewareKnowledgeDetailName
+                      }
+                      | {item.useChapterLessonCoursewareName}
+                    </p>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </NSpace>
+          {state.tableList.length > 0 && (
+            <Pagination
+              v-model:page={state.pagination.page}
+              v-model:pageSize={state.pagination.rows}
+              v-model:pageTotal={state.pagination.pageTotal}
+              onList={getList}
+              pageSizes={[12, 24, 36, 48]}
+              sync
+              saveKey="classRecord-key"
+            />
+          )}
+
+          {state.tableList.length <= 0 && <TheEmpty class={styles.nowEmpty} />}
+        </div>
+      </div>
+    );
+  }
+});

二进制
src/views/prepare-lessons/components/directory-main/images/icon-delete.png


二进制
src/views/prepare-lessons/components/directory-main/images/icon-edit.png


+ 244 - 235
src/views/prepare-lessons/components/directory-main/select-lessonware/index.module.less

@@ -1,235 +1,244 @@
-.attendClassSearch {
-  width: 100%;
-  display: flex;
-  align-items: center;
-  gap: 0 24px;
-  margin-bottom: 6px;
-  padding: 32px 40px 0;
-
-  :global {
-
-    .n-base-selection,
-    .n-input {
-      height: 52px;
-      min-height: 52px;
-      --n-height: 52px !important;
-      font-size: 18px;
-      border-radius: 8px !important;
-    }
-
-    .n-cascader {
-      min-width: 240px;
-    }
-  }
-
-  .iconSearch {
-    width: 16px;
-    height: 17px;
-  }
-}
-
-.classList {
-  min-height: 60vh;
-  max-height: 60vh;
-
-  .content {
-    min-height: 60vh;
-    padding: 22px 40px;
-  }
-}
-
-.item {
-  position: relative;
-  width: 214px;
-  cursor: pointer;
-  transition: all .3s;
-
-  .cover {
-    position: relative;
-    overflow: hidden;
-
-    &::before {
-      content: '';
-      position: absolute;
-      top: 107px;
-      left: 0;
-      width: 214px;
-      height: 214px;
-      background: #DDF2FF;
-      border-radius: 50%;
-    }
-  }
-
-  .iconCustom {
-    position: absolute;
-    top: -8px;
-    left: -7px;
-    width: 80px;
-    height: 81px;
-    background: url('../images/icon-custom.svg') no-repeat center;
-    background-size: contain;
-    z-index: 9;
-  }
-
-  .editContainer {
-    opacity: 0;
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background-color: rgba(0, 0, 0, 0.5);
-    z-index: 8;
-    display: none;
-    align-items: flex-end;
-    justify-content: center;
-
-    .editBtnGroup {
-      padding-bottom: 7px;
-    }
-
-    .delBtn,
-    .editBtn {
-      height: 30px;
-      font-size: 16px;
-      font-weight: 600;
-      border: none;
-      border-radius: 4px;
-    }
-
-    .delBtn {
-      color: #fff;
-      background: #EA4132;
-      border: 1px solid #EA4132;
-      margin-right: 9px;
-    }
-
-    .editBtn {
-      color: #131415;
-    }
-  }
-
-  &:hover {
-    transform: scale(1.05);
-
-    .editContainer {
-      display: flex;
-      opacity: 1;
-    }
-  }
-
-  .itemImg {
-    position: relative;
-    width: 158px;
-    height: 223px;
-    margin: auto;
-    background-color: #EDEFF2;
-    background-image: url('../images/icon_default.svg');
-    background-repeat: no-repeat;
-    background-position: center center;
-    margin-top: 10px;
-
-    &::before {
-      content: '';
-      position: absolute;
-      top: 4px;
-      right: -4px;
-      width: 4px;
-      height: calc(100% - 8px);
-      background-color: #C5C5C5;
-      z-index: 1;
-    }
-
-    &::after {
-      content: '';
-      position: absolute;
-      top: 2px;
-      right: -2px;
-      width: 4px;
-      height: calc(100% - 4px);
-      background-color: #E7E7E7;
-      z-index: 2;
-    }
-
-    .itemBg {
-      position: absolute;
-      top: 0;
-      left: 0;
-      width: 100%;
-      height: 100%;
-      z-index: 4;
-      background-repeat: no-repeat;
-      background-image: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0.08) 0%, transparent 0.5%), linear-gradient(to right, rgba(0, 0, 0, 0.1) 0.3%, rgba(255, 255, 255, 0.09) 1.1%, transparent 1.3%);
-      background-size: 50% 100%, 50% 100%;
-      background-position: 0% top, 9% top;
-    }
-
-    :global {
-      .n-image {
-        position: relative;
-        width: 100%;
-        height: 100%;
-        z-index: 3;
-      }
-    }
-
-    img {
-      transition: opacity .3s;
-      opacity: 0;
-    }
-
-    img[data-loaded="true"] {
-      opacity: 1;
-    }
-
-
-  }
-
-  .itemName {
-    margin-top: 16px;
-    font-size: 16px;
-    font-weight: 600;
-    color: #131415;
-    text-align: center;
-  }
-}
-
-.teachingModal {
-  width: 1100px;
-}
-
-.removeVisiable {
-  width: 432px;
-
-  :global {
-    .n-card-header {
-      font-size: max(22px, 16Px);
-    }
-  }
-
-  .studentRemove {
-    padding: 20px 40px 0;
-
-    p {
-      font-size: max(18px, 14Px);
-      color: #777777;
-      line-height: 30px;
-      text-align: center;
-
-      span {
-        color: #EA4132;
-      }
-    }
-  }
-
-  .btnGroup {
-    padding: 32px 0;
-
-    :global {
-      .n-button {
-        height: 47px;
-        min-width: 156px;
-      }
-    }
-  }
-}
+.attendClassSearch {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  gap: 0 24px;
+  margin-bottom: 6px;
+  padding: 32px 40px 0;
+
+  :global {
+
+    .n-base-selection,
+    .n-input {
+      height: 52px;
+      min-height: 52px;
+      --n-height: 52px !important;
+      font-size: 18px;
+      border-radius: 8px !important;
+    }
+
+    .n-cascader {
+      min-width: 240px;
+    }
+  }
+
+  .iconSearch {
+    width: 16px;
+    height: 17px;
+  }
+}
+
+.classList {
+  min-height: 60vh;
+  max-height: 60vh;
+
+  .content {
+    min-height: 60vh;
+    padding: 22px 40px;
+  }
+}
+
+.item {
+  position: relative;
+  width: 214px;
+  cursor: pointer;
+  transition: all .3s;
+
+  .cover {
+    position: relative;
+    overflow: hidden;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 107px;
+      left: 0;
+      width: 214px;
+      height: 214px;
+      background: #DDF2FF;
+      border-radius: 50%;
+    }
+  }
+
+  .iconCustom {
+    position: absolute;
+    top: -8px;
+    left: -7px;
+    width: 80px;
+    height: 81px;
+    background: url('../images/icon-custom.svg') no-repeat center;
+    background-size: contain;
+    z-index: 9;
+  }
+
+  .editContainer {
+    // opacity: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    // background-color: rgba(0, 0, 0, 0.5);
+    z-index: 8;
+    display: flex;
+    align-items: flex-end;
+    justify-content: flex-end;
+
+    .editBtnGroup {
+      padding-bottom: 7px;
+      line-height: 0;
+    }
+
+    .imgBtn {
+      width: 32px;
+      height: 32px;
+      cursor: pointer;
+      opacity: 1;
+      margin-right: 10px;
+    }
+
+    // .delBtn,
+    // .editBtn {
+    //   height: 30px;
+    //   font-size: 16px;
+    //   font-weight: 600;
+    //   border: none;
+    //   border-radius: 4px;
+    // }
+
+    // .delBtn {
+    //   color: #fff;
+    //   background: #EA4132;
+    //   border: 1px solid #EA4132;
+    //   margin-right: 9px;
+    // }
+
+    // .editBtn {
+    //   color: #131415;
+    // }
+  }
+
+  &:hover {
+    transform: scale(1.05);
+
+    // .editContainer {
+    //   display: flex;
+    //   opacity: 1;
+    // }
+  }
+
+  .itemImg {
+    position: relative;
+    width: 158px;
+    height: 223px;
+    margin: auto;
+    background-color: #EDEFF2;
+    background-image: url('../images/icon_default.svg');
+    background-repeat: no-repeat;
+    background-position: center center;
+    margin-top: 10px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 4px;
+      right: -4px;
+      width: 4px;
+      height: calc(100% - 8px);
+      background-color: #C5C5C5;
+      z-index: 1;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 2px;
+      right: -2px;
+      width: 4px;
+      height: calc(100% - 4px);
+      background-color: #E7E7E7;
+      z-index: 2;
+    }
+
+    .itemBg {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 4;
+      background-repeat: no-repeat;
+      background-image: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0.08) 0%, transparent 0.5%), linear-gradient(to right, rgba(0, 0, 0, 0.1) 0.3%, rgba(255, 255, 255, 0.09) 1.1%, transparent 1.3%);
+      background-size: 50% 100%, 50% 100%;
+      background-position: 0% top, 9% top;
+    }
+
+    :global {
+      .n-image {
+        position: relative;
+        width: 100%;
+        height: 100%;
+        z-index: 3;
+      }
+    }
+
+    img {
+      transition: opacity .3s;
+      opacity: 0;
+    }
+
+    img[data-loaded="true"] {
+      opacity: 1;
+    }
+
+
+  }
+
+  .itemName {
+    margin-top: 16px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #131415;
+    text-align: center;
+  }
+}
+
+.teachingModal {
+  width: 1100px;
+}
+
+.removeVisiable {
+  width: 432px;
+
+  :global {
+    .n-card-header {
+      font-size: max(22px, 16Px);
+    }
+  }
+
+  .studentRemove {
+    padding: 20px 40px 0;
+
+    p {
+      font-size: max(18px, 14Px);
+      color: #777777;
+      line-height: 30px;
+      text-align: center;
+
+      span {
+        color: #EA4132;
+      }
+    }
+  }
+
+  .btnGroup {
+    padding: 32px 0;
+
+    :global {
+      .n-button {
+        height: 47px;
+        min-width: 156px;
+      }
+    }
+  }
+}

+ 309 - 312
src/views/prepare-lessons/components/directory-main/select-lessonware/index.tsx

@@ -1,312 +1,309 @@
-import { defineComponent, onMounted, reactive } from 'vue';
-import styles from './index.module.less';
-import {
-  NButton,
-  NCascader,
-  NImage,
-  NInput,
-  NModal,
-  NScrollbar,
-  NSelect,
-  NSpace,
-  NSpin,
-  useDialog,
-  useMessage
-} from 'naive-ui';
-import AddTeaching, {
-  BOOK_DATA
-} from '/src/views/natural-resources/model/add-teaching';
-import {
-  lessonCoursewarePage,
-  lessonCoursewareRemove,
-  bookVersionPage
-} from '../../../api';
-import iconUploadBg from '../images/icon-upload-bg.png';
-import { useCatchStore } from '/src/store/modules/catchData';
-import { useThrottleFn } from '@vueuse/core';
-
-export default defineComponent({
-  name: 'select-lessonware',
-  emits: ['close', 'confirm'],
-  setup(props, { emit }) {
-    const catchStore = useCatchStore();
-    const dialog = useDialog();
-    const message = useMessage();
-    const forms = reactive({
-      loading: false,
-      list: [] as any[],
-      teachingStatus: false,
-      musicTagList: [] as any[],
-      selectItem: {} as any,
-      bookVersionId: null,
-      keyword: null,
-      currentGradeNum: null,
-      instrumentId: null,
-      removeVisiable: false,
-      removeRow: {} as any
-      // bookType: null
-    });
-
-    const getLessonCourseware = async () => {
-      forms.loading = true;
-      try {
-        const { data } = await lessonCoursewarePage({
-          page: 1,
-          rows: 99,
-          type: 'COURSEWARE',
-          enableFlag: 1,
-          bookVersionId: forms.bookVersionId,
-          keyword: forms.keyword,
-          currentGradeNum: forms.currentGradeNum,
-          instrumentId: forms.instrumentId
-          // bookType: forms.bookType
-        });
-
-        forms.list = data.rows;
-      } catch {
-        //
-      }
-      forms.loading = false;
-    };
-
-    // 删除教材
-    const onDelete = async (item: any) => {
-      try {
-        await lessonCoursewareRemove({ id: forms.removeRow.id });
-        message.success('删除成功');
-        getLessonCourseware();
-        forms.removeVisiable = false;
-      } catch (e) {
-        console.log(e);
-      }
-    };
-
-    const throttledFn = useThrottleFn(() => getLessonCourseware(), 500);
-
-    const onDetail = (item: any) => {
-      emit('confirm', item);
-      emit('close');
-    };
-
-    onMounted(async () => {
-      // 获取教材分类列表
-      try {
-        await catchStore.getSubjects();
-        const { data } = await bookVersionPage({
-          page: 1,
-          rows: 99,
-          type: 'COURSEWARE'
-        });
-        const temp = data.rows || [];
-        temp.forEach((item: any) => {
-          forms.musicTagList.push({
-            id: item.id,
-            name: item.name
-          });
-        });
-      } catch {
-        //
-      }
-      getLessonCourseware();
-    });
-    return () => (
-      <div class={styles.selectLessonware}>
-        <div class={styles.attendClassSearch}>
-          <NInput
-            placeholder="请输入教材名称"
-            clearable
-            v-model:value={forms.keyword}
-            onKeyup={(e: KeyboardEvent) => {
-              if (e.code === 'Enter') {
-                throttledFn();
-              }
-            }}
-            onClear={() => {
-              forms.keyword = null;
-              throttledFn();
-            }}>
-            {{
-              prefix: () => (
-                <span
-                  class="icon-search-input"
-                  onClick={() => throttledFn()}></span>
-              )
-            }}
-          </NInput>
-          <NCascader
-            placeholder="请选择乐器"
-            v-model:value={forms.instrumentId}
-            onUpdate:value={() => throttledFn()}
-            options={
-              [
-                { name: '全部乐器', id: null },
-                ...catchStore.getSubjectList
-              ] as any
-            }
-            checkStrategy="child"
-            showPath={false}
-            childrenField="instruments"
-            expandTrigger="hover"
-            labelField="name"
-            valueField="id"
-            clearable
-            filterable
-            style={{ width: '400px' }}
-          />
-          <NSelect
-            placeholder="全部版本"
-            clearable
-            options={[{ id: null, name: '全部版本' }, ...forms.musicTagList]}
-            labelField="name"
-            valueField="id"
-            v-model:value={forms.bookVersionId}
-            onUpdate:value={() => throttledFn()}
-          />
-          <NSelect
-            placeholder="全部年级"
-            options={
-              [{ label: '全部年级', value: null }, ...BOOK_DATA.grades] as any
-            }
-            v-model:value={forms.currentGradeNum}
-            clearable
-            filterable
-            onUpdate:value={() => throttledFn()}
-          />
-          {/* <NSelect
-            placeholder="全部册别"
-            options={
-              [
-                { label: '全部册别', value: null },
-                ...BOOK_DATA.bookTypes
-              ] as any
-            }
-            v-model:value={forms.bookType}
-            clearable
-            onUpdate:value={() => throttledFn()}
-          /> */}
-        </div>
-        <NScrollbar class={styles.classList}>
-          <NSpin show={forms.loading} class={styles.content}>
-            <NSpace size={[50, 40]}>
-              <div
-                class={styles.item}
-                key={`item--1`}
-                onClick={() => {
-                  forms.selectItem = {};
-                  forms.teachingStatus = true;
-                }}>
-                <div class={styles.cover}>
-                  <div class={styles.itemImg}>
-                    <div class={styles.itemBg}></div>
-                    <NImage
-                      objectFit="cover"
-                      src={iconUploadBg}
-                      lazy
-                      previewDisabled={true}
-                      onLoad={e => {
-                        (e.target as any).dataset.loaded = 'true';
-                      }}
-                    />
-                  </div>
-                </div>
-              </div>
-              {forms.list.map((item: any, index: number) => {
-                return (
-                  <div
-                    class={styles.item}
-                    key={`item-${index}`}
-                    onClick={() => onDetail(item)}>
-                    <div class={styles.cover}>
-                      <div class={styles.itemImg}>
-                        <div class={styles.itemBg}></div>
-                        <NImage
-                          objectFit="cover"
-                          src={item.coverImg}
-                          lazy
-                          previewDisabled={true}
-                          onLoad={e => {
-                            (e.target as any).dataset.loaded = 'true';
-                          }}
-                        />
-                        {item.customFlag && (
-                          <>
-                            <div class={styles.iconCustom}></div>
-                            <div class={styles.editContainer}>
-                              <div class={styles.editBtnGroup}>
-                                <NButton
-                                  size="small"
-                                  class={styles.delBtn}
-                                  color="#EA4132"
-                                  onClick={(e: MouseEvent) => {
-                                    e.stopPropagation();
-                                    // onDelete(item);
-                                    forms.removeVisiable = true;
-                                    forms.removeRow = item;
-                                  }}>
-                                  删除
-                                </NButton>
-                                <NButton
-                                  size="small"
-                                  class={styles.editBtn}
-                                  color="#FFFFFF"
-                                  onClick={(e: MouseEvent) => {
-                                    e.stopPropagation();
-                                    forms.selectItem = item;
-                                    forms.teachingStatus = true;
-                                  }}>
-                                  编辑
-                                </NButton>
-                              </div>
-                            </div>
-                          </>
-                        )}
-                      </div>
-                    </div>
-                    <div class={styles.itemName}>{item.name}</div>
-                  </div>
-                );
-              })}
-            </NSpace>
-          </NSpin>
-        </NScrollbar>
-
-        {/* 添加自定义教材 */}
-        <NModal
-          v-model:show={forms.teachingStatus}
-          preset="card"
-          showIcon={false}
-          class={['modalTitle background', styles.teachingModal]}
-          title={'自定义教材'}
-          blockScroll={false}>
-          <AddTeaching
-            item={forms.selectItem}
-            onClose={() => (forms.teachingStatus = false)}
-            onConfirm={() => {
-              getLessonCourseware();
-              forms.selectItem = {};
-            }}
-          />
-        </NModal>
-
-        <NModal
-          v-model:show={forms.removeVisiable}
-          preset="card"
-          class={['modalTitle', styles.removeVisiable]}
-          title={'删除教材'}>
-          <div class={styles.studentRemove}>
-            <p>是否删除该教材</p>
-
-            <NSpace class={styles.btnGroup} justify="center">
-              <NButton round type="primary" onClick={onDelete}>
-                确定
-              </NButton>
-              <NButton round onClick={() => (forms.removeVisiable = false)}>
-                取消
-              </NButton>
-            </NSpace>
-          </div>
-        </NModal>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import {
+  NButton,
+  NCascader,
+  NImage,
+  NInput,
+  NModal,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin,
+  useDialog,
+  useMessage
+} from 'naive-ui';
+import AddTeaching, {
+  BOOK_DATA
+} from '/src/views/natural-resources/model/add-teaching';
+import {
+  lessonCoursewarePage,
+  lessonCoursewareRemove,
+  bookVersionPage
+} from '../../../api';
+import iconUploadBg from '../images/icon-upload-bg.png';
+import { useCatchStore } from '/src/store/modules/catchData';
+import { useThrottleFn } from '@vueuse/core';
+import iconEdit from '../images/icon-edit.png';
+import iconDelete from '../images/icon-delete.png';
+
+export default defineComponent({
+  name: 'select-lessonware',
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
+    const dialog = useDialog();
+    const message = useMessage();
+    const forms = reactive({
+      loading: false,
+      list: [] as any[],
+      teachingStatus: false,
+      musicTagList: [] as any[],
+      selectItem: {} as any,
+      bookVersionId: null,
+      keyword: null,
+      currentGradeNum: null,
+      instrumentId: null,
+      removeVisiable: false,
+      removeRow: {} as any
+      // bookType: null
+    });
+
+    const getLessonCourseware = async () => {
+      forms.loading = true;
+      try {
+        const { data } = await lessonCoursewarePage({
+          page: 1,
+          rows: 99,
+          type: 'COURSEWARE',
+          enableFlag: 1,
+          bookVersionId: forms.bookVersionId,
+          keyword: forms.keyword,
+          currentGradeNum: forms.currentGradeNum,
+          instrumentId: forms.instrumentId
+          // bookType: forms.bookType
+        });
+
+        forms.list = data.rows;
+      } catch {
+        //
+      }
+      forms.loading = false;
+    };
+
+    // 删除教材
+    const onDelete = async (item: any) => {
+      try {
+        await lessonCoursewareRemove({ id: forms.removeRow.id });
+        message.success('删除成功');
+        getLessonCourseware();
+        forms.removeVisiable = false;
+      } catch (e) {
+        console.log(e);
+      }
+    };
+
+    const throttledFn = useThrottleFn(() => getLessonCourseware(), 500);
+
+    const onDetail = (item: any) => {
+      emit('confirm', item);
+      emit('close');
+    };
+
+    onMounted(async () => {
+      // 获取教材分类列表
+      try {
+        await catchStore.getSubjects();
+        const { data } = await bookVersionPage({
+          page: 1,
+          rows: 99,
+          type: 'COURSEWARE'
+        });
+        const temp = data.rows || [];
+        temp.forEach((item: any) => {
+          forms.musicTagList.push({
+            id: item.id,
+            name: item.name
+          });
+        });
+      } catch {
+        //
+      }
+      getLessonCourseware();
+    });
+    return () => (
+      <div class={styles.selectLessonware}>
+        <div class={styles.attendClassSearch}>
+          <NInput
+            placeholder="请输入教材名称"
+            clearable
+            v-model:value={forms.keyword}
+            onKeyup={(e: KeyboardEvent) => {
+              if (e.code === 'Enter') {
+                throttledFn();
+              }
+            }}
+            onClear={() => {
+              forms.keyword = null;
+              throttledFn();
+            }}>
+            {{
+              prefix: () => (
+                <span
+                  class="icon-search-input"
+                  onClick={() => throttledFn()}></span>
+              )
+            }}
+          </NInput>
+          <NCascader
+            placeholder="请选择乐器"
+            v-model:value={forms.instrumentId}
+            onUpdate:value={() => throttledFn()}
+            options={
+              [
+                { name: '全部乐器', id: null },
+                ...catchStore.getSubjectList
+              ] as any
+            }
+            checkStrategy="child"
+            showPath={false}
+            childrenField="instruments"
+            expandTrigger="hover"
+            labelField="name"
+            valueField="id"
+            clearable
+            filterable
+            style={{ width: '400px' }}
+          />
+          <NSelect
+            placeholder="全部版本"
+            clearable
+            options={[{ id: null, name: '全部版本' }, ...forms.musicTagList]}
+            labelField="name"
+            valueField="id"
+            v-model:value={forms.bookVersionId}
+            onUpdate:value={() => throttledFn()}
+          />
+          <NSelect
+            placeholder="全部年级"
+            options={
+              [{ label: '全部年级', value: null }, ...BOOK_DATA.grades] as any
+            }
+            v-model:value={forms.currentGradeNum}
+            clearable
+            filterable
+            onUpdate:value={() => throttledFn()}
+          />
+          {/* <NSelect
+            placeholder="全部册别"
+            options={
+              [
+                { label: '全部册别', value: null },
+                ...BOOK_DATA.bookTypes
+              ] as any
+            }
+            v-model:value={forms.bookType}
+            clearable
+            onUpdate:value={() => throttledFn()}
+          /> */}
+        </div>
+        <NScrollbar class={styles.classList}>
+          <NSpin show={forms.loading} class={styles.content}>
+            <NSpace size={[50, 40]}>
+              <div
+                class={styles.item}
+                key={`item--1`}
+                onClick={() => {
+                  forms.selectItem = {};
+                  forms.teachingStatus = true;
+                }}>
+                <div class={styles.cover}>
+                  <div class={styles.itemImg}>
+                    <div class={styles.itemBg}></div>
+                    <NImage
+                      objectFit="cover"
+                      src={iconUploadBg}
+                      lazy
+                      previewDisabled={true}
+                      onLoad={e => {
+                        (e.target as any).dataset.loaded = 'true';
+                      }}
+                    />
+                  </div>
+                </div>
+              </div>
+              {forms.list.map((item: any, index: number) => {
+                return (
+                  <div
+                    class={styles.item}
+                    key={`item-${index}`}
+                    onClick={() => onDetail(item)}>
+                    <div class={styles.cover}>
+                      <div class={styles.itemImg}>
+                        <div class={styles.itemBg}></div>
+                        <NImage
+                          objectFit="cover"
+                          src={item.coverImg}
+                          lazy
+                          previewDisabled={true}
+                          onLoad={e => {
+                            (e.target as any).dataset.loaded = 'true';
+                          }}
+                        />
+                        {item.customFlag && (
+                          <>
+                            <div class={styles.iconCustom}></div>
+                            <div class={styles.editContainer}>
+                              <div class={styles.editBtnGroup}>
+                                <img
+                                  src={iconEdit}
+                                  class={styles.imgBtn}
+                                  onClick={(e: MouseEvent) => {
+                                    e.stopPropagation();
+                                    forms.selectItem = item;
+                                    forms.teachingStatus = true;
+                                  }}
+                                />
+                                <img
+                                  src={iconDelete}
+                                  class={styles.imgBtn}
+                                  onClick={(e: MouseEvent) => {
+                                    e.stopPropagation();
+                                    forms.removeVisiable = true;
+                                    forms.removeRow = item;
+                                  }}
+                                />
+                              </div>
+                            </div>
+                          </>
+                        )}
+                      </div>
+                    </div>
+                    <div class={styles.itemName}>{item.name}</div>
+                  </div>
+                );
+              })}
+            </NSpace>
+          </NSpin>
+        </NScrollbar>
+
+        {/* 添加自定义教材 */}
+        <NModal
+          v-model:show={forms.teachingStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.teachingModal]}
+          title={'自定义教材'}
+          blockScroll={false}>
+          <AddTeaching
+            item={forms.selectItem}
+            onClose={() => (forms.teachingStatus = false)}
+            onConfirm={() => {
+              getLessonCourseware();
+              forms.selectItem = {};
+            }}
+          />
+        </NModal>
+
+        <NModal
+          v-model:show={forms.removeVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable]}
+          title={'删除教材'}>
+          <div class={styles.studentRemove}>
+            <p>是否删除该教材</p>
+
+            <NSpace class={styles.btnGroup} justify="center">
+              <NButton round type="primary" onClick={onDelete}>
+                确定
+              </NButton>
+              <NButton round onClick={() => (forms.removeVisiable = false)}>
+                取消
+              </NButton>
+            </NSpace>
+          </div>
+        </NModal>
+      </div>
+    );
+  }
+});