View.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import { IconAdd, IconMove } from "@/assets/icons";
  2. import { DesignComp } from "@/modules/editor/objects/DesignTemp/DesignComp";
  3. import { css, cx } from "@linaria/core";
  4. import { IconDelete } from "@queenjs/icons";
  5. import "animate.css";
  6. import {
  7. defineComponent,
  8. inject,
  9. onMounted,
  10. onUnmounted,
  11. reactive,
  12. ref,
  13. } from "vue";
  14. import { bool, string } from "vue-types";
  15. import { useEditor } from "../../..";
  16. import { useCompEditLayerRef, useCompRef } from "./hooks";
  17. let intersectionObserver: any;
  18. export const View = defineComponent({
  19. props: {
  20. compId: string().isRequired,
  21. editlayer: bool().def(true),
  22. showMask: bool().def(false),
  23. meta: bool().def(true),
  24. },
  25. emits: ["dblclick", "click"],
  26. setup(props, { slots, emit, attrs }) {
  27. const { store, actions, helper, controls } = useEditor();
  28. const compRef = useCompRef(props.compId, props.meta);
  29. const editorLayerRef = props.editlayer
  30. ? useCompEditLayerRef(props.compId)
  31. : ref();
  32. const isLongPage = controls.screenCtrl.isLongPage;
  33. const isPreview = inject("isPreview", false);
  34. const state = reactive({
  35. showAnimation: false,
  36. });
  37. function createBgStyles() {
  38. let bgS = {
  39. position: "absolute",
  40. top: 0,
  41. left: 0,
  42. right: 0,
  43. bottom: 0,
  44. zIndex: -1,
  45. };
  46. return bgS;
  47. }
  48. function getAniStyles(comp: any) {
  49. if (!comp.layout.anim) return "";
  50. if (
  51. comp.layout.anim &&
  52. state.showAnimation &&
  53. ((!props.showMask && store.isEditMode) || !store.isEditMode)
  54. ) {
  55. return `animate__animated animate__${comp.layout.anim} animate__delay-0.1s`;
  56. } else {
  57. return "opacity-0";
  58. }
  59. }
  60. onUnmounted(() => {
  61. intersectionObserver.disconnect();
  62. });
  63. onMounted(() => {
  64. intersectionObserver = new IntersectionObserver((entries) => {
  65. if (entries[0].intersectionRatio <= 0) {
  66. if (isLongPage) return;
  67. state.showAnimation = false;
  68. } else {
  69. state.showAnimation = true;
  70. }
  71. });
  72. intersectionObserver.observe(compRef.value);
  73. });
  74. const pageCtrl = controls.pageCtrl;
  75. return () => {
  76. const comp = helper.findComp(props.compId);
  77. if (!comp) return store.isEditMode ? <div>无效组件</div> : null;
  78. const isStreamCard = helper.isStreamCard(props.compId);
  79. const gizmo = controls.selectCtrl.gizmo;
  80. const page = controls.pageCtrl;
  81. const showBgDiv = isPreview && !isLongPage;
  82. const isContiner = comp.compKey == "Container";
  83. let isFocus =
  84. store.isEditMode &&
  85. gizmo.state.transform.selected.length > 1 &&
  86. gizmo.state.lastId == props.compId;
  87. const style = helper.createStyle(comp.layout, comp);
  88. if (comp.compKey == "Container") {
  89. delete style.transform;
  90. style["transform-origin"] = "center center";
  91. }
  92. const cardStyles = helper.createViewStyles(comp.layout, comp);
  93. let bgStyles: any = {};
  94. if (showBgDiv) {
  95. bgStyles = createBgStyles();
  96. bgStyles.background = cardStyles.background;
  97. delete cardStyles.background;
  98. }
  99. if (comp.compKey == "Text") {
  100. cardStyles["overflow"] = "visible";
  101. }
  102. if (isStreamCard) {
  103. style.overflow = "unset";
  104. style.position = "relative";
  105. }
  106. if (store.isPreview) {
  107. style.overflow = "hidden";
  108. }
  109. const aniStyles = getAniStyles(comp);
  110. function RenderPre() {
  111. if (
  112. page.state.currStreamCardId != props.compId ||
  113. !isStreamCard ||
  114. !store.isEditMode
  115. )
  116. return;
  117. let i = pageCtrl.streamCardIds.indexOf(props.compId);
  118. if (i > 0) {
  119. const c = helper.findComp(
  120. pageCtrl.streamCardIds[i - 1]
  121. ) as DesignComp;
  122. const PreComp = controls.compUICtrl.state.components.get(
  123. c.compKey
  124. ) as any;
  125. return (
  126. <div
  127. class="absolute w-full h-150px pointer-events-none"
  128. style={{
  129. bottom: style.height,
  130. }}
  131. >
  132. {/* <div class="absolute w-full h-150px pointer-events-none overflow-hidden">
  133. <PreComp.Component
  134. compId={c.id}
  135. key={c.id}
  136. style={{ position: "absolute", bottom: "0" }}
  137. />
  138. </div> */}
  139. {/* <div class={maskStyleUp}></div> */}
  140. <div class={[divideStyle, "bottom"]}>
  141. {/* <span class="tip">上一页分割线</span> */}
  142. </div>
  143. </div>
  144. );
  145. }
  146. }
  147. function RenderAfter() {
  148. if (
  149. page.state.currStreamCardId != props.compId ||
  150. !isStreamCard ||
  151. !store.isEditMode
  152. )
  153. return;
  154. let i = pageCtrl.streamCardIds.indexOf(props.compId);
  155. if (i < pageCtrl.streamCardIds.length - 1) {
  156. const c = helper.findComp(
  157. pageCtrl.streamCardIds[i + 1]
  158. ) as DesignComp;
  159. const AfterComp = controls.compUICtrl.state.components.get(
  160. c.compKey
  161. ) as any;
  162. return (
  163. <div class="relative pointer-events-none">
  164. {/* <div class="h-150px pointer-events-none overflow-hidden">
  165. <AfterComp.Component compId={c.id} key={c.id} />
  166. </div> */}
  167. {/* <div class={maskStyle}></div> */}
  168. <div class={[divideStyle, "top"]}>
  169. {/* <span class="tip">下一页分割线</span> */}
  170. </div>
  171. </div>
  172. );
  173. }
  174. }
  175. return (
  176. <>
  177. {showBgDiv && isContiner && <div style={bgStyles}></div>}
  178. <div
  179. {...attrs}
  180. ref={compRef}
  181. class={[
  182. comp.compKey,
  183. viewStyle,
  184. page.state.currStreamCardId == props.compId &&
  185. store.isEditMode &&
  186. !isPreview &&
  187. CurrCompStyle,
  188. isFocus && AnchorCompStyle,
  189. isFocus && groupCompCls,
  190. ]}
  191. style={style}
  192. onClick={(e) => {
  193. if (!store.isEditMode) {
  194. e.stopPropagation();
  195. emit("click");
  196. }
  197. }}
  198. onDblclick={() => emit("dblclick")}
  199. >
  200. <div
  201. class={aniStyles}
  202. style={cardStyles}
  203. onMousemove={(e) => {
  204. if (
  205. !store.isEditMode ||
  206. !controls.dragAddCtrl.dragingCompKey ||
  207. !helper.isStreamCard(props.compId)
  208. )
  209. return;
  210. controls.editorCtrl.clickPickComp(props.compId);
  211. }}
  212. >
  213. {showBgDiv && !isContiner && <div style={bgStyles}></div>}
  214. {slots.default?.()}
  215. </div>
  216. {/* {store.isEditMode &&
  217. isStreamCard &&
  218. store.currStreamCardId == props.compId && (
  219. <Hudop compId={props.compId} />
  220. )} */}
  221. {store.isEditMode && props.editlayer && (
  222. <div ref={editorLayerRef} class={editAreaStyle}></div>
  223. )}
  224. {props.showMask && isLongPage && (
  225. <>
  226. {RenderPre()}
  227. {RenderAfter()}
  228. </>
  229. )}
  230. {props.showMask &&
  231. !isLongPage &&
  232. controls.screenCtrl.state.screen.useFor == "mobile" && (
  233. <div
  234. class={cx(
  235. safeAreaStyles,
  236. "absolute left-0 top-1/2 w-1/1 translate -translate-y-1/2 transform pointer-events-none"
  237. )}
  238. style={{
  239. height: `${controls.screenCtrl.state.safeFactor * 100}%`,
  240. }}
  241. >
  242. <div class={[divideStyle, "top"]}>
  243. <span class="tip"> 安全线</span>
  244. </div>
  245. <div class={[divideStyle, "bottom"]}>
  246. <span class="tip"> 安全线</span>
  247. </div>
  248. </div>
  249. )}
  250. </div>
  251. </>
  252. );
  253. };
  254. },
  255. });
  256. export const Hudop = defineComponent({
  257. props: {
  258. compId: string().isRequired,
  259. },
  260. setup(props) {
  261. const { store, actions, helper, controls } = useEditor();
  262. const opref = ref();
  263. onMounted(() => {
  264. opref.value.editable = "hudop";
  265. });
  266. const page = controls.pageCtrl;
  267. return () => (
  268. <div class="hudop shadow" ref={opref}>
  269. {page.streamCardIds.length > 1 && (
  270. <IconMove
  271. class="draganchor"
  272. onMousedown={() => controls.editorCtrl.clickPickComp(props.compId)}
  273. />
  274. )}
  275. {page.streamCardIds.length > 1 && (
  276. <IconDelete
  277. onClick={(e: any) => {
  278. e.stopPropagation();
  279. actions.removeStreamCard(props.compId);
  280. }}
  281. />
  282. )}
  283. <IconAdd
  284. onClick={(e: any) => {
  285. e.stopPropagation();
  286. const index = page.streamCardIds.indexOf(props.compId) + 1;
  287. actions.addCompToDesign("Container", index);
  288. }}
  289. />
  290. </div>
  291. );
  292. },
  293. });
  294. const viewStyle = css`
  295. position: relative;
  296. font-size: 0;
  297. cursor: pointer;
  298. flex-shrink: 0;
  299. > :first-child {
  300. width: 100%;
  301. height: 100%;
  302. }
  303. .hudop {
  304. position: absolute;
  305. top: 0px;
  306. left: -46px;
  307. background-color: white;
  308. flex-direction: column;
  309. color: black;
  310. display: flex;
  311. font-size: 12px;
  312. width: 28px;
  313. align-items: center;
  314. border-radius: 4px;
  315. z-index: 997;
  316. .inficon {
  317. padding: 8px;
  318. }
  319. }
  320. `;
  321. const editCompStyle = css`
  322. &:hover {
  323. outline: 2px dashed @inf-primary-color;
  324. }
  325. `;
  326. const CurrCompStyle = css`
  327. position: relative;
  328. z-index: 998;
  329. box-shadow: 0 0 0 3000px rgba(0, 0, 0, 0.5);
  330. &:before {
  331. content: "";
  332. position: absolute;
  333. left: -1px;
  334. right: -1px;
  335. top: -1px;
  336. bottom: -1px;
  337. z-index: 999;
  338. pointer-events: none;
  339. background-image: repeating-linear-gradient(
  340. to right,
  341. @inf-primary-color 0%,
  342. @inf-primary-color 50%,
  343. transparent 50%,
  344. transparent 100%
  345. ),
  346. repeating-linear-gradient(
  347. to right,
  348. @inf-primary-color 0%,
  349. @inf-primary-color 50%,
  350. transparent 50%,
  351. transparent 100%
  352. ),
  353. repeating-linear-gradient(
  354. to bottom,
  355. @inf-primary-color 0%,
  356. @inf-primary-color 50%,
  357. transparent 50%,
  358. transparent 100%
  359. ),
  360. repeating-linear-gradient(
  361. to bottom,
  362. @inf-primary-color 0%,
  363. @inf-primary-color 50%,
  364. transparent 50%,
  365. transparent 100%
  366. );
  367. background-position: left top, left bottom, left top, right top;
  368. background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
  369. background-size: 8px 1px, 8px 1px, 1px 8px, 1px 8px;
  370. }
  371. `;
  372. const AnchorCompStyle = css`
  373. &:before {
  374. background-size: 8px 2px, 8px 2px, 2px 8px, 2px 8px;
  375. }
  376. `;
  377. const groupCompCls = css`
  378. outline: 2px dashed @inf-primary-color !important;
  379. `;
  380. const editAreaStyle = css`
  381. position: absolute;
  382. top: 0;
  383. left: 0;
  384. width: 100%;
  385. height: 100%;
  386. pointer-events: none;
  387. `;
  388. // const editAreaTestStyle = css`
  389. // position: absolute;
  390. // top: 0;
  391. // left: 0;
  392. // width: 100px;
  393. // height: 100px;
  394. // background-color: red;
  395. // `;
  396. const maskStyle = css`
  397. background: rgba(0, 0, 0, 0.3);
  398. position: absolute;
  399. left: 0;
  400. top: 0;
  401. width: 100%;
  402. height: 100%;
  403. `;
  404. const maskStyleUp = css`
  405. background: rgba(0, 0, 0, 0.3);
  406. position: absolute;
  407. left: 0;
  408. top: 0;
  409. width: 100%;
  410. height: 100%;
  411. `;
  412. const divideStyle = css`
  413. position: absolute;
  414. left: -5%;
  415. width: 110%;
  416. height: 1px;
  417. background-image: linear-gradient(
  418. to right,
  419. #eb684e 0%,
  420. #eb684e 50%,
  421. transparent 50%
  422. );
  423. background-size: 8px 1px;
  424. background-repeat: repeat-x;
  425. &.top {
  426. top: 0;
  427. }
  428. &.bottom {
  429. bottom: 0;
  430. }
  431. .tip {
  432. margin-left: 15px;
  433. font-size: 12px;
  434. position: absolute;
  435. left: 100%;
  436. width: 80px;
  437. top: -10px;
  438. color: #eb684e;
  439. }
  440. `;
  441. const safeAreaStyles = css`
  442. box-shadow: 0 0 0 3000px rgba(0, 0, 0, 0.3);
  443. `;