index.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import { Transition, defineComponent, onMounted, ref, reactive,onUnmounted } from 'vue';
  2. import LayoutSilder from './layoutSilder';
  3. import LayoutTop from './layoutTop';
  4. import styles from './index.module.less';
  5. import { NButton, NImage, NModal, NPopover, NSpace, useDialog } from 'naive-ui';
  6. import Moveable from 'moveable';
  7. import toolStartClass from './images/toolStartClass.png';
  8. import toolbox from './images/toolbox.png';
  9. import setTimeIcon from './images/setTimeIcon.png';
  10. import beatIcon from './images/beatIcon.png';
  11. import toneIcon from './images/toneIcon.png';
  12. import beatImage from './images/beatImage.png';
  13. import toneImage from './images/toneImage.png';
  14. import setTimeImage from './images/setTimeImage.png';
  15. import dragingBoxIcon from './images/dragingBoxIcon.png';
  16. import TimerMeter from '../timerMeter';
  17. import { useRoute, useRouter } from 'vue-router';
  18. import { vaildUrl } from '/src/utils/urlUtils';
  19. import ChioseModal from '/src/views/home/modals/chioseModal';
  20. import { px2vw, px2vwH } from '@/utils/index';
  21. import PlaceholderTone from './modals/placeholderTone';
  22. import { state } from '/src/state';
  23. import PreviewWindow from '/src/views/preview-window';
  24. import {fscreen} from '@/utils/index'
  25. export default defineComponent({
  26. name: 'layoutView',
  27. setup() {
  28. const router = useRouter();
  29. const previewModal = ref(false);
  30. const previewItem = ref({});
  31. const directionType = ref('left');
  32. const showClass = ref(false);
  33. const showModalBeat = ref(false);
  34. const showModalTone = ref(false);
  35. const showModalTime = ref(false);
  36. const showBoxConent = ref(false);
  37. const dialog = useDialog();
  38. const boxBoundaryInfo = reactive({
  39. isBoundary: true,
  40. isBoundaryType: 'right' as any,
  41. mainWidth: '' as any,
  42. mainHeight: '' as any,
  43. subWidth: '' as any,
  44. subHeight: '' as any
  45. });
  46. const classBoundaryInfo = reactive({
  47. isBoundary: true,
  48. isBoundaryType: 'right' as any,
  49. mainWidth: '' as any,
  50. mainHeight: '' as any,
  51. subWidth: '' as any,
  52. subHeight: '' as any
  53. });
  54. const route = useRoute();
  55. const isDragIng = ref(false);
  56. const NPopoverRef = ref()
  57. const initMoveable = async () => {
  58. if (document.querySelector('.wrap')) {
  59. const moveable = new Moveable(document.querySelector('.wrap') as any, {
  60. target: document.querySelector('#moveNPopover') as any,
  61. // If the container is null, the position is fixed. (default: parentElement(document.body))
  62. container: document.querySelector('.wrap') as any,
  63. // snappable: true,
  64. // bounds: {"left":100,"top":100,"right":100,"bottom":100},
  65. draggable: true,
  66. resizable: false,
  67. scalable: false,
  68. rotatable: false,
  69. warpable: false,
  70. pinchable: false, // ["resizable", "scalable", "rotatable"]
  71. origin: false,
  72. keepRatio: false,
  73. // Resize, Scale Events at edges.
  74. edge: false,
  75. throttleDrag: 0,
  76. throttleResize: 0,
  77. throttleScale: 0,
  78. throttleRotate: 0
  79. });
  80. moveable
  81. // .on('dragStart', ({ target, clientX, clientY }) => {
  82. // console.log('dragStart');
  83. // })
  84. .on(
  85. 'drag',
  86. ({
  87. target,
  88. // transform,
  89. left,
  90. top,
  91. right,
  92. bottom
  93. // beforeDelta,
  94. // beforeDist,
  95. // delta,
  96. // dist,
  97. // clientX,
  98. // clientY
  99. }) => {
  100. isDragIng.value = true;
  101. NPopoverRef.value.setShow(false)
  102. const subdEl = document.getElementById(
  103. `moveNPopover`
  104. ) as HTMLDivElement;
  105. // console.log(subdEl, "subdEl", "drag");
  106. const subdElStyle = getComputedStyle(subdEl, null);
  107. const RectInfo = {
  108. left: Number(subdElStyle.left.replace('px', '')),
  109. top: Number(subdElStyle.top.replace('px', '')),
  110. width: Number(subdElStyle.width.replace('px', '')),
  111. height: Number(subdElStyle.height.replace('px', ''))
  112. };
  113. // target.style.transition = ''
  114. const mainWidth =
  115. parseInt(
  116. window.getComputedStyle(
  117. document.querySelector('.wrap') as Element
  118. ).width
  119. ) - RectInfo.width;
  120. const mainHeight =
  121. parseInt(
  122. window.getComputedStyle(
  123. document.querySelector('.wrap') as Element
  124. ).height
  125. ) - RectInfo.height;
  126. subdEl.style.transition = '';
  127. boxBoundaryInfo.isBoundary = false;
  128. boxBoundaryInfo.isBoundaryType = '';
  129. boxBoundaryInfo.mainHeight = mainHeight;
  130. boxBoundaryInfo.mainWidth = mainWidth;
  131. boxBoundaryInfo.subWidth = RectInfo.width;
  132. boxBoundaryInfo.subHeight = RectInfo.height;
  133. if (left < 0) {
  134. left = 2;
  135. boxBoundaryInfo.isBoundary = true;
  136. boxBoundaryInfo.isBoundaryType = 'left';
  137. }
  138. if (top < 0) {
  139. top = 2;
  140. boxBoundaryInfo.isBoundary = true;
  141. boxBoundaryInfo.isBoundaryType = 'top';
  142. }
  143. if (right < 0) {
  144. right = 2;
  145. }
  146. if (bottom < 0) {
  147. bottom = 2;
  148. }
  149. if (left > mainWidth - 2) {
  150. left = mainWidth - 2;
  151. // top = 2;
  152. boxBoundaryInfo.isBoundary = true;
  153. boxBoundaryInfo.isBoundaryType = 'right';
  154. }
  155. if (top > mainHeight - 2) {
  156. top = mainHeight - 2;
  157. boxBoundaryInfo.isBoundary = true;
  158. boxBoundaryInfo.isBoundaryType = 'bottom';
  159. }
  160. target!.style.left = `${left}px`;
  161. target!.style.top = `${top}px`;
  162. }
  163. )
  164. .on(
  165. 'dragEnd',
  166. async ({
  167. target,
  168. // isDrag,
  169. clientX
  170. // clientY
  171. }) => {
  172. if (document.body.clientWidth / 2 - clientX > 0) {
  173. // 往左出
  174. directionType.value = 'right';
  175. } else {
  176. // 往又出
  177. directionType.value = 'left';
  178. }
  179. isDragIng.value = false;
  180. // 在这里进行动画
  181. if (boxBoundaryInfo.isBoundary) {
  182. // 这里说明贴边了
  183. target.style.transition = '.3s';
  184. actionEnd(target, boxBoundaryInfo.isBoundaryType);
  185. }
  186. }
  187. );
  188. }
  189. };
  190. const initMoveableClass = async () => {
  191. if (document.querySelector('.wrap')) {
  192. const moveable = new Moveable(document.querySelector('.wrap') as any, {
  193. target: document.querySelector('#moveNPopover2') as any,
  194. // If the container is null, the position is fixed. (default: parentElement(document.body))
  195. container: document.querySelector('.wrap') as any,
  196. // snappable: true,
  197. // bounds: {"left":100,"top":100,"right":100,"bottom":100},
  198. draggable: true,
  199. resizable: false,
  200. scalable: false,
  201. rotatable: false,
  202. warpable: false,
  203. pinchable: false, // ["resizable", "scalable", "rotatable"]
  204. origin: false,
  205. keepRatio: false,
  206. // Resize, Scale Events at edges.
  207. edge: false,
  208. throttleDrag: 0,
  209. throttleResize: 0,
  210. throttleScale: 0,
  211. throttleRotate: 0
  212. });
  213. moveable
  214. .on(
  215. 'drag',
  216. ({
  217. target,
  218. // transform,
  219. left,
  220. top,
  221. right,
  222. bottom
  223. }) => {
  224. isDragIng.value = true;
  225. const subdEl = document.getElementById(
  226. `moveNPopover2`
  227. ) as HTMLDivElement;
  228. // console.log(subdEl, "subdEl", "drag");
  229. const subdElStyle = getComputedStyle(subdEl, null);
  230. const RectInfo = {
  231. left: Number(subdElStyle.left.replace('px', '')),
  232. top: Number(subdElStyle.top.replace('px', '')),
  233. width: Number(subdElStyle.width.replace('px', '')),
  234. height: Number(subdElStyle.height.replace('px', ''))
  235. };
  236. const mainWidth =
  237. parseInt(
  238. window.getComputedStyle(
  239. document.querySelector('.wrap') as Element
  240. ).width
  241. ) - RectInfo.width;
  242. const mainHeight =
  243. parseInt(
  244. window.getComputedStyle(
  245. document.querySelector('.wrap') as Element
  246. ).height
  247. ) - RectInfo.height;
  248. subdEl.style.transition = '';
  249. classBoundaryInfo.isBoundary = false;
  250. classBoundaryInfo.isBoundaryType = '';
  251. classBoundaryInfo.mainHeight = mainHeight;
  252. classBoundaryInfo.mainWidth = mainWidth;
  253. classBoundaryInfo.subWidth = RectInfo.width;
  254. classBoundaryInfo.subHeight = RectInfo.height;
  255. if (left < 0) {
  256. left = 2;
  257. classBoundaryInfo.isBoundary = true;
  258. classBoundaryInfo.isBoundaryType = 'left';
  259. }
  260. if (top < 0) {
  261. top = 2;
  262. classBoundaryInfo.isBoundary = true;
  263. classBoundaryInfo.isBoundaryType = 'top';
  264. }
  265. if (right < 0) {
  266. right = 2;
  267. }
  268. if (bottom < 0) {
  269. bottom = 2;
  270. }
  271. if (left > mainWidth - 2) {
  272. left = mainWidth - 2;
  273. // top = 2;
  274. classBoundaryInfo.isBoundary = true;
  275. classBoundaryInfo.isBoundaryType = 'right';
  276. }
  277. if (top > mainHeight - 2) {
  278. top = mainHeight - 2;
  279. classBoundaryInfo.isBoundary = true;
  280. classBoundaryInfo.isBoundaryType = 'bottom';
  281. }
  282. target!.style.left = `${left}px`;
  283. target!.style.top = `${top}px`;
  284. }
  285. )
  286. .on(
  287. 'dragEnd',
  288. async ({
  289. target,
  290. // isDrag,
  291. clientX
  292. // clientY
  293. }) => {
  294. if (document.body.clientWidth / 2 - clientX > 0) {
  295. // 往左出
  296. directionType.value = 'right';
  297. } else {
  298. // 往又出
  299. directionType.value = 'left';
  300. }
  301. if (classBoundaryInfo.isBoundary) {
  302. // 这里说明贴边了
  303. target.style.transition = '.3s';
  304. actionEnd(target, classBoundaryInfo.isBoundaryType);
  305. }
  306. isDragIng.value = false;
  307. }
  308. )
  309. .on('click', () => {
  310. showClass.value = true;
  311. });
  312. }
  313. };
  314. onMounted(() => {
  315. initMoveable();
  316. // // initMoveableClass();
  317. const subdEl = document.getElementById(`moveNPopover`) as HTMLDivElement;
  318. // // const classEl = document.getElementById(
  319. // // `moveNPopover2`
  320. // // ) as HTMLDivElement;
  321. initBoxRectInfo(subdEl, boxBoundaryInfo);
  322. // // initBoxRectInfo(classEl, classBoundaryInfo);
  323. initBoundaryWrap(subdEl, boxBoundaryInfo);
  324. // // initBoundaryWrap(classEl, classBoundaryInfo);
  325. window.addEventListener("resize", resetSize);
  326. });
  327. const resetSize = ()=>{
  328. const subdEl = document.getElementById(`moveNPopover`) as HTMLDivElement;
  329. subdEl.style.display = 'none'
  330. // const boxBoundaryInfo = reactive({
  331. // isBoundary: true,
  332. // isBoundaryType: 'right' as any,
  333. // mainWidth: '' as any,
  334. // mainHeight: '' as any,
  335. // subWidth: '' as any,
  336. // subHeight: '' as any
  337. // });
  338. // boxBoundaryInfo.isBoundary = true;
  339. // boxBoundaryInfo.isBoundaryType= 'right'
  340. NPopoverRef.value.setShow(false)
  341. setTimeout(()=>{
  342. subdEl.style.transition = ''
  343. initBoxRectInfo(subdEl, boxBoundaryInfo);
  344. initBoundaryWrap(subdEl, boxBoundaryInfo);
  345. console.log('resize')
  346. subdEl.style.display = 'block'
  347. },100)
  348. }
  349. onUnmounted(()=>{
  350. window.removeEventListener("resize", resetSize);
  351. })
  352. const initBoundaryWrap = (target: any, wrapInfo: any) => {
  353. target.addEventListener('mouseenter', () => {
  354. if (wrapInfo.isBoundary) {
  355. // 如果在边框 就得还原 元素位置 还原完毕后 去除transition
  356. if (wrapInfo.isBoundaryType == 'left') {
  357. target.style.left = '2px';
  358. } else if (wrapInfo.isBoundaryType == 'right') {
  359. target.style.left = `${wrapInfo.mainWidth - 2}px`;
  360. } else if (wrapInfo.isBoundaryType == 'top') {
  361. target.style.top = `${2}px`;
  362. } else if (wrapInfo.isBoundaryType == 'bottom') {
  363. target.style.top = `${wrapInfo.mainHeight - 2}px`;
  364. }
  365. }
  366. rate(target, 0);
  367. });
  368. target.addEventListener('mouseleave', () => {
  369. if (wrapInfo.isBoundary) {
  370. // 如果在边框 就得还原 元素位置 还原完毕后 去除transition
  371. if (wrapInfo.isBoundaryType == 'left') {
  372. actionEnd(target, 'left');
  373. } else if (wrapInfo.isBoundaryType == 'right') {
  374. actionEnd(target, 'right');
  375. } else if (wrapInfo.isBoundaryType == 'top') {
  376. actionEnd(target, 'top');
  377. } else if (wrapInfo.isBoundaryType == 'bottom') {
  378. actionEnd(target, 'bottom');
  379. }
  380. }
  381. // rate(target, 0)
  382. });
  383. target.addEventListener('contextmenu', (event: any) => {
  384. event.preventDefault();
  385. dialog.warning({
  386. title: '提示',
  387. content: '是否收入托盘',
  388. positiveText: '确定',
  389. negativeText: '取消',
  390. onPositiveClick: () => {
  391. console.log('确定')
  392. },
  393. onNegativeClick: () => {
  394. console.log('取消')
  395. }
  396. })
  397. });
  398. actionEnd(target, 'right');
  399. };
  400. const startShowModal = (val: 'setTimeIcon' | 'beatIcon' | 'toneIcon') => {
  401. if (val == 'setTimeIcon') {
  402. showModalTime.value = true;
  403. }
  404. if (val == 'beatIcon') {
  405. showModalBeat.value = true;
  406. }
  407. if (val == 'toneIcon') {
  408. showModalTone.value = true;
  409. }
  410. };
  411. // const moveTargetBoundary = (target: any, type: any) => {
  412. // console.log('moveTargetBoundary', target, type);
  413. // };
  414. // 这里是旋转
  415. const rate = (target: any, rate: any) => {
  416. target.style.transform = ' rotate(' + rate + ')';
  417. };
  418. // 这里是选装的方式
  419. const actionEnd = (target: any, type: any) => {
  420. switch (type) {
  421. case 'left':
  422. rate(target, '90deg');
  423. target!.style.left = `${2 - boxBoundaryInfo.subWidth / 2}px`;
  424. target!.style.top = `${top}px`;
  425. break;
  426. case 'right':
  427. rate(target, '-90deg');
  428. target!.style.left = `${
  429. boxBoundaryInfo.mainWidth - 2 + boxBoundaryInfo.subWidth / 2
  430. }px`;
  431. target!.style.top = `${top}px`;
  432. break;
  433. case 'top':
  434. target!.style.top = `${2 - boxBoundaryInfo.subHeight / 2}px`;
  435. rate(target, '-180deg');
  436. break;
  437. case 'bottom':
  438. target!.style.top = `${
  439. boxBoundaryInfo.mainHeight - 2 + boxBoundaryInfo.subHeight / 2
  440. }px`;
  441. break;
  442. default:
  443. rate(target, '-0');
  444. break;
  445. }
  446. };
  447. const initBoxRectInfo = (target: any, wrapInfo: any) => {
  448. // const subdEl = document.getElementById(`moveNPopover`) as HTMLDivElement;
  449. // console.log(subdEl, "subdEl", "drag");
  450. const subdElStyle = getComputedStyle(target, null);
  451. const RectInfo = {
  452. left: Number(subdElStyle.left.replace('px', '')),
  453. top: Number(subdElStyle.top.replace('px', '')),
  454. width: Number(subdElStyle.width.replace('px', '')),
  455. height: Number(subdElStyle.height.replace('px', ''))
  456. };
  457. // target.style.transition = ''
  458. const mainWidth =
  459. parseInt(
  460. window.getComputedStyle(document.querySelector('.wrap') as Element)
  461. .width
  462. ) - RectInfo.width;
  463. const mainHeight =
  464. parseInt(
  465. window.getComputedStyle(document.querySelector('.wrap') as Element)
  466. .height
  467. ) - RectInfo.height;
  468. // boxBoundaryInfo.isBoundary = false;
  469. // boxBoundaryInfo.isBoundaryType = '';
  470. wrapInfo.mainHeight = mainHeight;
  471. wrapInfo.mainWidth = mainWidth;
  472. wrapInfo.subWidth = RectInfo.width;
  473. wrapInfo.subHeight = RectInfo.height;
  474. target.style.transition = '.3s';
  475. };
  476. return () => (
  477. <div class={[styles.wrap, 'wrap']}>
  478. <div>
  479. <LayoutSilder></LayoutSilder>
  480. </div>
  481. <div class={styles.Wrapcore}>
  482. <LayoutTop></LayoutTop>
  483. <div class={styles.WrapcoreView}>
  484. {/* <div class={styles.WrapcoreViewInfo}> */}
  485. <router-view>
  486. {(obj: any) => (
  487. <Transition name="fade-slide" mode="out-in">
  488. <obj.Component />
  489. </Transition>
  490. )}
  491. </router-view>
  492. {/* </div> */}
  493. </div>
  494. </div>
  495. {/* <img
  496. src={toolStartClass}
  497. id="moveNPopover2"
  498. style={{
  499. display: ['/', '/home', '/classList', '/prepare-lessons'].includes(
  500. route.path
  501. )
  502. ? 'none'
  503. : 'block'
  504. }}
  505. class={[
  506. styles.toolClassImg,
  507. 'moveNPopover2',
  508. isDragIng.value ? styles.isDragIng : ''
  509. ]}
  510. alt=""
  511. /> */}
  512. <NPopover
  513. raw
  514. trigger="click"
  515. ref={NPopoverRef}
  516. show-arrow={false}
  517. placement={directionType.value as 'left' | 'right'}
  518. v-slots={{
  519. trigger: () => (
  520. // 首页不显示工具箱
  521. <img
  522. // src={isDragIng.value ? dragingBoxIcon : toolbox}
  523. src={toolbox}
  524. id="moveNPopover"
  525. style={{
  526. display: ['/', '/home'].includes(route.path)
  527. ? 'none'
  528. : 'block'
  529. }}
  530. class={[
  531. styles.toolboxImg,
  532. 'moveNPopover',
  533. isDragIng.value ? styles.isDragIng : ''
  534. ]}
  535. alt=""
  536. />
  537. )
  538. }}>
  539. <div class={styles.booxToolWrap}>
  540. <div
  541. class={styles.booxToolItem}
  542. onClick={() => startShowModal('beatIcon')}>
  543. <img src={beatIcon} alt="" />
  544. 节拍器
  545. </div>
  546. <div
  547. class={styles.booxToolItem}
  548. onClick={() => startShowModal('toneIcon')}>
  549. <img src={toneIcon} alt="" />
  550. 调音器
  551. </div>
  552. <div
  553. class={styles.booxToolItem}
  554. onClick={() => startShowModal('setTimeIcon')}>
  555. <img src={setTimeIcon} alt="" />
  556. 计时器
  557. </div>
  558. <div class={styles.booxToolItem} onClick={()=>{
  559. showClass.value = true
  560. }} style={{
  561. display: [
  562. '/',
  563. '/home',
  564. '/classList',
  565. '/prepare-lessons'
  566. ].includes(route.path)
  567. ? 'none'
  568. : 'block'
  569. }}>
  570. <img
  571. src={toolStartClass}
  572. style={{
  573. display: [
  574. '/',
  575. '/home',
  576. '/classList',
  577. '/prepare-lessons'
  578. ].includes(route.path)
  579. ? 'none'
  580. : 'block'
  581. }}
  582. class={[
  583. styles.toolClassImg,
  584. ]}
  585. alt=""
  586. />
  587. 开始上课
  588. </div>
  589. </div>
  590. </NPopover>
  591. <NModal
  592. class={['modalTitle background']}
  593. title={'节拍器'}
  594. preset="card"
  595. v-model:show={showModalBeat.value}
  596. style={{ width: '687px' }}>
  597. <div class={styles.modeWrap}>
  598. <iframe
  599. src={`${vaildUrl()}/metronome/?id=${new Date().getTime()}`}
  600. scrolling="no"
  601. frameborder="0"
  602. width="100%"
  603. height={'650px'}></iframe>
  604. </div>
  605. </NModal>
  606. <NModal v-model:show={showModalTone.value} class={['background']}>
  607. {/* <div
  608. onClick={() => {
  609. showModalTone.value = false;
  610. }}>
  611. <NImage
  612. src={toneImage}
  613. previewDisabled
  614. class={styles.beatImage}></NImage>
  615. </div> */}
  616. <div>
  617. <PlaceholderTone
  618. onClose={() => {
  619. showModalTone.value = false;
  620. }}></PlaceholderTone>
  621. </div>
  622. </NModal>
  623. <NModal
  624. v-model:show={showModalTime.value}
  625. class={['modalTitle background']}
  626. title={'计时器'}
  627. preset="card"
  628. style={{ width: px2vw(772) }}>
  629. <div>
  630. <TimerMeter></TimerMeter>
  631. </div>
  632. </NModal>
  633. <NModal
  634. v-model:show={showClass.value}
  635. class={['modalTitle background', styles.showClass]}
  636. preset="card"
  637. title={'开始上课'}>
  638. <ChioseModal
  639. onClose={() => (showClass.value = false)}
  640. onPreview={(item: any) => {
  641. if (window.matchMedia('(display-mode: standalone)').matches) {
  642. previewModal.value = true;
  643. previewItem.value = {
  644. ...item
  645. };
  646. state.application = window.matchMedia('(display-mode: standalone)').matches
  647. fscreen()
  648. } else {
  649. const { href } = router.resolve({
  650. path: '/attend-class',
  651. query: {
  652. ...item
  653. }
  654. });
  655. window.open(href, +new Date() + '');
  656. }
  657. }}
  658. />
  659. </NModal>
  660. {/* 弹窗查看 */}
  661. <PreviewWindow
  662. v-model:show={previewModal.value}
  663. type="attend"
  664. params={previewItem.value}
  665. />
  666. </div>
  667. );
  668. }
  669. });