index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. <template>
  2. <Container
  3. :upload-resourse="uploadResourse"
  4. v-model:full="full"
  5. v-if="dataLoaded"
  6. :ref="(d: any) => initDraw(d?.draw)"
  7. >
  8. <template #header>
  9. <Header
  10. v-if="draw"
  11. :title="$t('tabulation.name1')"
  12. @screenshot="(val) => (needScreenshot = val)"
  13. />
  14. </template>
  15. <template #slide>
  16. <Slide v-if="draw" @update-map-image="setMapHandler" />
  17. </template>
  18. </Container>
  19. <Preview
  20. @change-origin="(origin) => setOrigin && setOrigin(origin)"
  21. :showLabelLine="coverSetting?.showViewportLabelLine"
  22. :cover-scale="coverSetting?.scale"
  23. :paperKey="tabulationData?.paperKey"
  24. :pixelRatio="needScreenshot ? needScreenshot : pixelRatio"
  25. :not-debounce="!!needScreenshot"
  26. :data="overviewData?.store"
  27. :tab-draw="draw"
  28. :scale="draw?.viewerConfig.scaleX"
  29. @change-config="(config) => (originConfig = config)"
  30. v-if="overviewData?.store.config?.proportion.scale"
  31. />
  32. <Dialog />
  33. </template>
  34. <script lang="ts" setup>
  35. import Header from "./header.vue";
  36. import Slide from "./slide.vue";
  37. import Container from "../../../components/container/container.vue";
  38. import {
  39. computed,
  40. nextTick,
  41. onUnmounted,
  42. reactive,
  43. ref,
  44. shallowRef,
  45. watch,
  46. watchEffect,
  47. } from "vue";
  48. import { Draw } from "../../../components/container/use-draw";
  49. import Dialog from "../../../dialog/dialog.vue";
  50. import {
  51. tabulationData,
  52. refreshTabulationData,
  53. tableCoverKey,
  54. tableCoverScaleKey,
  55. tableCompassKey,
  56. overviewData,
  57. refreshOverviewData,
  58. } from "../../store";
  59. import { ImageData } from "@/core/components/image";
  60. import { TextData } from "@/core/components/text";
  61. import { genTabulationData, getRealPixel, repTabulationStore } from "./gen-tab";
  62. import { IconData } from "@/core/components/icon";
  63. import { Transform } from "konva/lib/Util";
  64. import { MathUtils } from "three";
  65. import { components } from "@/core/components";
  66. import { ShapeType } from "@/index";
  67. import { copy, mergeFuns, round } from "@/utils/shared";
  68. import { getImageSize } from "@/utils/shape";
  69. import { tabCustomStyle } from "../defStyle";
  70. import { defaultLayer } from "@/constant";
  71. import { Size } from "@/utils/math";
  72. import Preview from "./overview-viewport.vue";
  73. import { Image } from "konva/lib/shapes/Image";
  74. import { Group } from "konva/lib/Group";
  75. import { getBaseItem } from "@/core/components/util";
  76. import {
  77. getPaperConfig,
  78. paperConfigs,
  79. PaperKey,
  80. } from "@/example/components/slide/actions";
  81. import { getFixPosition } from "@/utils/bound";
  82. import { listener } from "@/utils/event";
  83. import { router } from "../../router";
  84. import { params } from "@/example/env";
  85. import { tableSerialTableKey } from "@/example/constant";
  86. import { addTable, getCurrentNdxRaw, syncTable } from "@/core/components/serial";
  87. import { ui18n } from "@/lang";
  88. const uploadResourse = window.platform.uploadResourse;
  89. const full = ref(false);
  90. const draw = ref<Draw>();
  91. const pixelRatio = ref(window.devicePixelRatio);
  92. const setMap = async (config: { url: string; size: Size }) => {
  93. const store = tabulationData.value.store;
  94. const paperKey = tabulationData.value.paperKey;
  95. const compass = 0;
  96. if (config.url && config.size.height && config.size.width) {
  97. const size = await getImageSize(window.platform.getResource(config.url));
  98. const cover = {
  99. url: config.url,
  100. ...size,
  101. proportion: {
  102. scale: (config.size.width / size.width) * 1000,
  103. unit: "mm",
  104. },
  105. };
  106. if (!tabulationData.value.store.config) {
  107. const layer = await genTabulationData(paperKey, compass, cover);
  108. tabulationData.value.store.layers[defaultLayer] = layer;
  109. } else {
  110. await repTabulationStore(paperKey, compass, cover, store);
  111. }
  112. }
  113. };
  114. const setMapHandler = async (config: { url: string; size: Size }) => {
  115. const size = await getImageSize(window.platform.getResource(config.url));
  116. const cover = {
  117. url: config.url,
  118. ...size,
  119. proportion: {
  120. scale: (config.size.width / size.width) * 1000,
  121. unit: "mm",
  122. },
  123. };
  124. const data = await genTabulationData(tabulationData.value.paperKey, undefined, cover);
  125. const store = draw.value!.store;
  126. const img = data.image?.find((item) => item.key === tableCoverKey);
  127. const text = data.text?.find((item) => item.key === tableCoverScaleKey);
  128. if (img && text) {
  129. const sImage = store.getTypeItems("image").find((item) => item.key === tableCoverKey);
  130. const sText = store
  131. .getTypeItems("text")
  132. .find((item) => item.key === tableCoverScaleKey);
  133. draw.value!.history.onceTrack(() => {
  134. if (sImage) {
  135. store.setItem("image", {
  136. id: sImage.id,
  137. value: { ...sImage, ...img, id: sImage.id },
  138. });
  139. } else {
  140. store.addItem("image", img);
  141. }
  142. if (sText) {
  143. store.setItem("text", {
  144. id: text.id,
  145. value: { ...sText, ...text, id: sText.id },
  146. });
  147. } else {
  148. store.addItem("text", text);
  149. }
  150. });
  151. }
  152. };
  153. // 序号处理
  154. {
  155. const serialTable = computed(() => {
  156. const tables = draw.value?.store.getTypeItems("table");
  157. console.log("update");
  158. return tables && tables.find((table) => table.key === tableSerialTableKey);
  159. });
  160. watchEffect((onCleanup) => {
  161. if (!draw.value || !serialTable.value) return;
  162. const un = draw.value.menusFilter.setShapeMenusFilter(
  163. serialTable.value.id,
  164. (menu) => [
  165. { label: ui18n.t("sys.update"), handler: () => updateSerialTable(false) },
  166. ...menu,
  167. ]
  168. );
  169. onCleanup(un);
  170. });
  171. const updateSerialTable = (autoGen = true) => {
  172. const d = draw.value;
  173. const overview = overviewData.value?.store.layers.default;
  174. const serials = overview.serial;
  175. if (!d) {
  176. return;
  177. }
  178. const pack = autoGen
  179. ? d.history.preventTrack.bind(d.history)
  180. : (run: () => void) => run();
  181. if (!serials?.length) {
  182. if (serialTable.value) {
  183. pack(() => {
  184. d.store.delItem("table", serialTable.value!.id);
  185. });
  186. }
  187. return;
  188. }
  189. const table = serialTable.value || addTable(d as any);
  190. const syncSerials = serials.map((item) => {
  191. let desc = "";
  192. if (item.joinIds) {
  193. const names = item.joinIds
  194. .map((id) => {
  195. const img = overview.image?.find((image) => image.id === id)?.name;
  196. return img || overview.icon?.find((image) => image.id === id)?.name;
  197. })
  198. .filter(Boolean);
  199. desc = names.join("、");
  200. }
  201. return {
  202. ...item,
  203. hideSerial: false,
  204. desc,
  205. };
  206. });
  207. // 找出没有关联的痕迹物证
  208. const images = [...(overview.image || []), ...(overview.icon || [])];
  209. images?.forEach((image) => {
  210. if (
  211. image.key !== "trace" ||
  212. syncSerials.some((item) => item.joinIds?.includes(image.id))
  213. )
  214. return;
  215. syncSerials.push({
  216. id: image.id,
  217. content: getCurrentNdxRaw(syncSerials),
  218. hideSerial: true,
  219. desc: image.name || "",
  220. } as any);
  221. });
  222. // 合并相同序号数据
  223. for (let i = 0; i < syncSerials.length; i++) {
  224. const self = syncSerials[i];
  225. for (let j = i + 1; j < syncSerials.length; j++) {
  226. if (syncSerials[j].content === self.content) {
  227. if (syncSerials[j].desc && self.desc !== syncSerials[j].desc) {
  228. self.desc = self.desc + "、" + syncSerials[j].desc;
  229. }
  230. syncSerials.splice(j--, 1);
  231. }
  232. }
  233. }
  234. draw.value?.runHook(() => syncTable(table, syncSerials));
  235. if (serialTable.value) {
  236. pack(() => {
  237. d.store.setItem("table", {
  238. id: serialTable.value!.id,
  239. value: serialTable.value!,
  240. });
  241. });
  242. } else {
  243. table.key = tableSerialTableKey;
  244. pack(() => {
  245. d.store.addItem("table", table);
  246. });
  247. }
  248. };
  249. watch(
  250. draw,
  251. (draw) => {
  252. if (!draw) return;
  253. if (!serialTable.value) {
  254. setTimeout(() => updateSerialTable(true), 500);
  255. }
  256. },
  257. { immediate: true }
  258. );
  259. }
  260. const needScreenshot = ref<false | number>(false);
  261. const coverSetting = computed(() => {
  262. const cover = draw.value?.store.items.find((item) => item.key === tableCoverKey);
  263. return cover?.userData as
  264. | { showScale: boolean; showViewportLabelLine?: boolean; scale: number }
  265. | undefined;
  266. });
  267. let setOrigin: (canvas: HTMLCanvasElement) => void;
  268. const originConfig = shallowRef<{ size: Size; scale: number }>();
  269. const setCover = (paperKey: PaperKey, draw: Draw) => {
  270. const cleanups: (() => void)[] = [];
  271. const cleanup = () => {
  272. mergeFuns(cleanups)();
  273. cleanups.length = 0;
  274. };
  275. let cover = draw.store.items.find((item) => item.key === tableCoverKey) as ImageData;
  276. // if (import.meta.env.DEV && cover) {
  277. // draw.history.preventTrack(() => {
  278. // draw.store.delItem("image", cover!.id);
  279. // cover = null as any;
  280. // });
  281. // }
  282. const { margin, size } = getPaperConfig(
  283. paperConfigs[paperKey].size,
  284. paperConfigs[paperKey].scale
  285. );
  286. if (!cover) {
  287. cover = reactive({
  288. ...getBaseItem(),
  289. disableCopy: true,
  290. cornerRadius: 0,
  291. strokeWidth: 0,
  292. disableDelete: true,
  293. key: tableCoverKey,
  294. disableTransformer: true,
  295. userData: {
  296. showViewportLabelLine: true,
  297. showScale: true,
  298. scale: 0,
  299. },
  300. zIndex: -1,
  301. width: 0,
  302. height: 0,
  303. mat: [1, 0, 0, 1, 0, 0],
  304. itemName: ui18n.t("cover.itemName"),
  305. });
  306. const stopWatch = watch(
  307. originConfig,
  308. (config) => {
  309. if (!config) return;
  310. const pos = getFixPosition(
  311. {
  312. left: config.size.width / 2 + margin[3] + getRealPixel(15, paperKey),
  313. bottom: -config.size.height / 2 + margin[2] + getRealPixel(15, paperKey),
  314. },
  315. config.size,
  316. size
  317. );
  318. const mat = new Transform().translate(pos.x, pos.y).m;
  319. draw.history.preventTrack(() => {
  320. draw.store.setItem("image", {
  321. id: cover.id,
  322. value: {
  323. mat: mat,
  324. userData: {
  325. ...cover.userData,
  326. scale: config.scale,
  327. },
  328. },
  329. });
  330. });
  331. nextTick(() => stopWatch());
  332. },
  333. { flush: "sync", immediate: true }
  334. );
  335. cleanups.push(stopWatch);
  336. draw.store.addItem("image", cover);
  337. } else {
  338. delete cover.url;
  339. }
  340. let text = draw.store.items.find((item) => item.key === tableCoverScaleKey) as TextData;
  341. // if (import.meta.env.DEV && text) {
  342. // draw.history.preventTrack(() => {
  343. // draw.store.delItem("text", text!.id);
  344. // text = null as any;
  345. // });
  346. // }
  347. if (!text) {
  348. const width = getRealPixel(30, paperKey);
  349. const heihgt = getRealPixel(4.8, paperKey);
  350. const pos = getFixPosition(
  351. {
  352. right: margin[1] + getRealPixel(40, paperKey),
  353. top: margin[0] + getRealPixel(45, paperKey),
  354. },
  355. { width, height: heihgt },
  356. size
  357. );
  358. text = reactive({
  359. ...getBaseItem(),
  360. content: ``,
  361. width,
  362. heihgt,
  363. fontSize: getRealPixel(4, paperKey),
  364. disableEditText: true,
  365. key: tableCoverScaleKey,
  366. disableDelete: true,
  367. align: "center",
  368. mat: [1, 0, 0, 1, pos.x, pos.y],
  369. });
  370. draw.store.addItem("text", text);
  371. // let stopWatch = watch(
  372. // () => cover.width && cover.height,
  373. // (loaded) => {
  374. // if (loaded) {
  375. // const mat = [...cover.mat];
  376. // const x = mat[4] - (text.width || 0) / 2;
  377. // const y = mat[5] + cover.height / 2 + getRealPixel(5, paperKey);
  378. // mat[4] = x;
  379. // mat[5] = y;
  380. // draw.history.preventTrack(() => {
  381. // draw.store.setItem("text", { id: text.id, value: { mat } });
  382. // });
  383. // nextTick(() => {
  384. // stopWatch();
  385. // });
  386. // }
  387. // }
  388. // );
  389. // cleanups.push(stopWatch);
  390. }
  391. // 兼容旧数据
  392. if (!cover.userData) {
  393. const scale =
  394. (cover.widthRaw! / cover.width) *
  395. paperConfigs[paperKey].scale *
  396. cover.proportion!.scale;
  397. cover.userData = {
  398. showScale: true,
  399. scale: round(scale, 1),
  400. };
  401. }
  402. const coverQue = computed(() => draw.store.getItem("image", cover.id));
  403. const coverTexQue = computed(() => draw.store.getItem("text", text.id));
  404. const mountMenus = draw.mountFilter;
  405. const menusFilter = draw.menusFilter;
  406. draw.excludeSelection.push(cover.id);
  407. const scale = ref<number>();
  408. cleanups.push(
  409. watch(
  410. () => coverSetting.value?.scale,
  411. (v) => (scale.value = v),
  412. { immediate: true }
  413. ),
  414. watch(
  415. coverSetting,
  416. (setting) => {
  417. if (coverTexQue.value) {
  418. coverTexQue.value.content = setting ? `1:${setting.scale}` : "";
  419. coverTexQue.value.hide = !setting?.showScale;
  420. }
  421. },
  422. { deep: true }
  423. ),
  424. watch(
  425. coverTexQue,
  426. (tex, _, cleanup) => {
  427. if (!tex) return;
  428. cleanup(
  429. mountMenus.setShapeMenusFilter(tex.id, (menus) => {
  430. const newMenus = { ...menus };
  431. delete newMenus.align;
  432. delete newMenus.fontStyle;
  433. return newMenus;
  434. })
  435. );
  436. },
  437. { immediate: true }
  438. ),
  439. menusFilter.setShapeMenusFilter(cover.id, () => []),
  440. mountMenus.setShapeMenusFilter(cover.id, (menus) => ({
  441. ...menus,
  442. scale: {
  443. type: "fixProportion",
  444. label: ui18n.t("cover.fixProportion"),
  445. "layout-type": "row",
  446. get value() {
  447. return scale.value;
  448. },
  449. set value(val) {
  450. scale.value = Math.max(val || 0, 1);
  451. },
  452. onChange() {
  453. if (coverSetting.value) {
  454. coverSetting.value.scale = scale.value!;
  455. }
  456. },
  457. props: { min: 1 },
  458. },
  459. showScale: {
  460. type: "check",
  461. label: ui18n.t("cover.showProportion"),
  462. "layout-type": "row",
  463. get value() {
  464. return coverSetting.value?.showScale;
  465. },
  466. set value(val) {
  467. if (coverSetting.value) {
  468. coverSetting.value.showScale = val || false;
  469. }
  470. },
  471. props: {},
  472. },
  473. showViewportLabelLine: {
  474. type: "check",
  475. label: ui18n.t("tabulation.showLabelLine"),
  476. "layout-type": "row",
  477. get value() {
  478. return coverSetting.value?.showViewportLabelLine;
  479. },
  480. set value(val) {
  481. if (coverSetting.value) {
  482. coverSetting.value.showViewportLabelLine = val || false;
  483. }
  484. },
  485. props: {},
  486. },
  487. })),
  488. watch(coverQue, (cover) => {
  489. if (!cover) {
  490. draw.history.preventTrack(() => {
  491. draw.store.delItem("text", text.id);
  492. });
  493. cleanup();
  494. }
  495. }),
  496. watch(
  497. originConfig,
  498. (config, _) => {
  499. if (coverQue.value) {
  500. coverQue.value.width = config?.size.width || 0;
  501. coverQue.value.height = config?.size.height || 0;
  502. }
  503. },
  504. { deep: true, immediate: true }
  505. )
  506. );
  507. setOrigin = (origin) => {
  508. const stage = draw.stage!;
  509. const $group = stage.findOne<Group>(`#${cover.id}`);
  510. const $image = $group?.findOne<Image>(".repShape");
  511. if (!$group || !$image) return;
  512. // cover.url = origin!.toDataURL();
  513. $image.image(origin);
  514. };
  515. return cleanup;
  516. };
  517. let clearDraw: (() => void) | undefined;
  518. onUnmounted(() => (clearDraw = undefined));
  519. const dataLoaded = ref(false);
  520. Promise.all([refreshOverviewData(), refreshTabulationData()]).then(
  521. () => (dataLoaded.value = true)
  522. );
  523. const lowVersionHandler = (_draw: Draw) => {
  524. // 旧数据处理
  525. _draw.history.preventTrack(() => {
  526. _draw.store.getTypeItems("image").forEach((item) => {
  527. if (item.key === tableCoverKey) {
  528. _draw.store.setItem("image", {
  529. value: {
  530. itemName: undefined,
  531. disableDelete: false,
  532. },
  533. id: item.id,
  534. });
  535. }
  536. });
  537. });
  538. };
  539. const initDraw = async (_draw: Draw) => {
  540. if (_draw === draw.value) return;
  541. clearDraw && clearDraw();
  542. if (!_draw) return;
  543. const quitMerges: Array<() => void> = [];
  544. _draw.config.showCompass = false;
  545. _draw.config.showGrid = false;
  546. _draw.config.showLabelLine = false;
  547. _draw.config.showComponentSize = false;
  548. _draw.viewer.setScaleRange(0.2, 5);
  549. const config: any = tabulationData.value.store.config || {};
  550. const data = tabulationData.value;
  551. const p = tabulationData.value.paperKey;
  552. await setMap({ url: data.mapUrl!, size: { width: data.width!, height: data.high! } });
  553. _draw.store.setStore({
  554. ...tabulationData.value.store,
  555. config: {
  556. ...config,
  557. compass: {
  558. rotation: 0,
  559. ...(config.compass || {}),
  560. url: "./icons/compass.svg",
  561. },
  562. proportion: {
  563. scale: 1 / getRealPixel(1, p),
  564. unit: "mm",
  565. },
  566. strokeProportion: {
  567. scale: 1 / getRealPixel(1, p),
  568. unit: "mm",
  569. },
  570. },
  571. });
  572. quitMerges.push(tabCustomStyle(p, _draw));
  573. if (overviewData.value.store?.config?.proportion.scale) {
  574. _draw.history.preventTrack(() => {
  575. const quit = setCover(p, _draw);
  576. quit && quitMerges.push(quit);
  577. });
  578. } else {
  579. lowVersionHandler(_draw);
  580. }
  581. quitMerges.concat(
  582. Object.keys(components).map((type) =>
  583. _draw.menusFilter.setMenusFilter(type as ShapeType, (items) => {
  584. return items.filter((item) => item.label !== ui18n.t("sys.hide"));
  585. })
  586. )
  587. );
  588. clearDraw = () => {
  589. console.error("clear");
  590. draw.value = undefined;
  591. mergeFuns(quitMerges)();
  592. clearDraw = undefined;
  593. };
  594. draw.value = _draw;
  595. };
  596. const compass = computed(
  597. () => draw.value?.store.items.find((item) => item.key === tableCompassKey) as IconData
  598. );
  599. watch(compass, (compass, _, onCleanup) => {
  600. if (!compass || !draw.value) return;
  601. const mountMenus = draw.value.mountFilter;
  602. const menusFilter = draw.value.menusFilter;
  603. const quits = [
  604. menusFilter.setShapeMenusFilter(compass.id, () => []),
  605. mountMenus.setShapeMenusFilter(compass.id, () => ({
  606. // ...des,
  607. rotate: {
  608. type: "num",
  609. label: ui18n.t("describes.rotate"),
  610. default: 0,
  611. props: {
  612. min: 0,
  613. max: 360,
  614. },
  615. "layout-type": "column",
  616. sort: 3,
  617. get value() {
  618. return round((new Transform(compass.mat).decompose().rotation + 360) % 360, 1);
  619. },
  620. set value(val) {
  621. const config = new Transform(compass.mat).decompose();
  622. compass.mat = new Transform()
  623. .translate(config.x, config.y)
  624. .scale(config.scaleX, config.scaleY)
  625. .rotate(MathUtils.degToRad(val)).m;
  626. nextTick(() => {
  627. draw.value?.stage!.findOne(`#${compass.id}`)?.fire("bound-change");
  628. });
  629. },
  630. },
  631. })),
  632. ];
  633. onCleanup(mergeFuns(quits));
  634. });
  635. watchEffect((onCleanup) => {
  636. const d = draw.value;
  637. if (!d?.stage?.container()) return;
  638. const dom = d.stage.container();
  639. onCleanup(
  640. listener(dom, "dblclick", () => {
  641. d.shapesStatus.actives.some((item) => {
  642. const id = item.id();
  643. if (d.store.getItemById(id)?.key === tableCoverKey) {
  644. router.push({
  645. ...router.currentRoute.value,
  646. name: "overview",
  647. query: params.value,
  648. } as any);
  649. }
  650. });
  651. })
  652. );
  653. });
  654. const title = computed(() => tabulationData.value?.title || ui18n.t("tabulation.name1"));
  655. watchEffect(() => {
  656. document.title = title.value;
  657. });
  658. window.platform.bus.on("requestError", (res: any) => {
  659. console.log(res);
  660. });
  661. </script>