index.tsx 23 KB

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