index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <template>
  2. <div class="canvas-design-panel">
  3. <div class="mb-10">
  4. <b>{{ t("style.canvasSize") }}</b>
  5. </div>
  6. <div class="mb-10">
  7. <el-row class="size-row">
  8. <el-col :span="11">
  9. <el-input
  10. v-model="canvasWidth"
  11. :value="pageSizeWidth"
  12. @change="changeTemplateWidth"
  13. oninput="value=value.replace(/[^\d.]/g,'')"
  14. >
  15. <template #prepend>{{ t("style.w") }}</template>
  16. </el-input>
  17. </el-col>
  18. <el-col :span="2" class="fixed-ratio">
  19. <el-tooltip
  20. effect="dark"
  21. placement="top"
  22. :content="t('style.unlockAspectRatio')"
  23. v-if="isFixed"
  24. >
  25. <IconLock class="icon-btn" @click="changeFixedRatio(false)"/>
  26. </el-tooltip>
  27. <el-tooltip
  28. effect="dark"
  29. placement="top"
  30. :content="t('style.lockAspectRatio')"
  31. v-else
  32. >
  33. <IconUnlock class="icon-btn" @click="changeFixedRatio(true)"/>
  34. </el-tooltip>
  35. </el-col>
  36. <el-col :span="11">
  37. <el-input
  38. v-model="canvasHeight"
  39. :value="pageSizeHeight"
  40. @change="changeTemplateHeight"
  41. oninput="value=value.replace(/[^\d.]/g,'')"
  42. >
  43. <template #prepend>{{ t("style.h") }}</template>
  44. </el-input>
  45. </el-col>
  46. </el-row>
  47. </div>
  48. <div class="mb-10">
  49. <el-row>
  50. <el-col :span="11">
  51. <el-input
  52. v-model="clip"
  53. @change="changeTemplateClip"
  54. oninput="value=value.replace(/[^\d]/g,'')"
  55. :disabled="unitMode === 1"
  56. >
  57. <template #prepend>
  58. <el-tooltip
  59. placement="top"
  60. :hide-after="0"
  61. :content="t('style.bleedingLine')"
  62. >
  63. <IconCuttingOne/>
  64. </el-tooltip>
  65. </template>
  66. </el-input>
  67. </el-col>
  68. <!-- <el-col :span="2" class="fixed-ratio">-->
  69. <!-- <el-tooltip-->
  70. <!-- effect="dark"-->
  71. <!-- placement="top"-->
  72. <!-- :content="t('style.fillet')"-->
  73. <!-- v-if="isRound"-->
  74. <!-- >-->
  75. <!-- <IconRound class="icon-btn" @click="changeWorkRound(false)"/>-->
  76. <!-- </el-tooltip>-->
  77. <!-- <el-tooltip-->
  78. <!-- effect="dark"-->
  79. <!-- placement="top"-->
  80. <!-- :content="t('style.rightAngle')"-->
  81. <!-- v-else-->
  82. <!-- >-->
  83. <!-- <IconRightAngle class="icon-btn" @click="changeWorkRound(true)"/>-->
  84. <!-- </el-tooltip>-->
  85. <!-- </el-col>-->
  86. <!-- <el-col :span="11">-->
  87. <!-- <el-input-->
  88. <!-- v-model="safe"-->
  89. <!-- @change="changeTemplateSafe"-->
  90. <!-- oninput="value=value.replace(/[^\d]/g,'')"-->
  91. <!-- :disabled="unitMode === 1"-->
  92. <!-- >-->
  93. <!-- <template #prepend>-->
  94. <!-- <el-tooltip-->
  95. <!-- placement="top"-->
  96. <!-- :hide-after="0"-->
  97. <!-- :content="t('style.safetyLine')"-->
  98. <!-- >-->
  99. <!-- <IconShield/>-->
  100. <!-- </el-tooltip>-->
  101. <!-- </template>-->
  102. <!-- </el-input>-->
  103. <!-- </el-col>-->
  104. </el-row>
  105. </div>
  106. <!-- <div class="mt-10">-->
  107. <!-- <el-row>-->
  108. <!-- <el-col :span="11">-->
  109. <!-- <el-select v-model="unitMode" @change="changeUnitMode">-->
  110. <!-- <template #prefix>-->
  111. <!-- <el-tooltip-->
  112. <!-- placement="top"-->
  113. <!-- :hide-after="0"-->
  114. <!-- :content="t('style.unit')"-->
  115. <!-- >-->
  116. <!-- <IconRuler/>-->
  117. <!-- </el-tooltip>-->
  118. <!-- </template>-->
  119. <!-- <el-option-->
  120. <!-- v-for="item in DesignUnitMode"-->
  121. <!-- :key="item.id"-->
  122. <!-- :label="item.name"-->
  123. <!-- :value="item.id"-->
  124. <!-- ></el-option>-->
  125. <!-- </el-select>-->
  126. <!-- </el-col>-->
  127. <!-- <el-col :span="2"></el-col>-->
  128. <!-- &lt;!&ndash; <el-col :span="11">&ndash;&gt;-->
  129. <!-- &lt;!&ndash; <el-select v-model="sizeMode">&ndash;&gt;-->
  130. <!-- &lt;!&ndash; <template #prefix>&ndash;&gt;-->
  131. <!-- &lt;!&ndash; <el-tooltip&ndash;&gt;-->
  132. <!-- &lt;!&ndash; placement="top"&ndash;&gt;-->
  133. <!-- &lt;!&ndash; :hide-after="0"&ndash;&gt;-->
  134. <!-- &lt;!&ndash; :content="t('style.template')"&ndash;&gt;-->
  135. <!-- &lt;!&ndash; >&ndash;&gt;-->
  136. <!-- &lt;!&ndash; <IconIdCard />&ndash;&gt;-->
  137. <!-- &lt;!&ndash; </el-tooltip>&ndash;&gt;-->
  138. <!-- &lt;!&ndash; </template>&ndash;&gt;-->
  139. <!-- &lt;!&ndash; <el-option&ndash;&gt;-->
  140. <!-- &lt;!&ndash; v-for="item in DesignSizeMode"&ndash;&gt;-->
  141. <!-- &lt;!&ndash; :key="item.id"&ndash;&gt;-->
  142. <!-- &lt;!&ndash; :label="item.name"&ndash;&gt;-->
  143. <!-- &lt;!&ndash; :value="item.id"&ndash;&gt;-->
  144. <!-- &lt;!&ndash; :disabled="item.disabled"&ndash;&gt;-->
  145. <!-- &lt;!&ndash; ></el-option>&ndash;&gt;-->
  146. <!-- &lt;!&ndash; </el-select>&ndash;&gt;-->
  147. <!-- &lt;!&ndash; </el-col>&ndash;&gt;-->
  148. <!-- </el-row>-->
  149. <!-- </div>-->
  150. <el-divider style="margin: 12px 0"/>
  151. <div class="title">
  152. <b>{{ t("style.canvasFill") }}</b>
  153. </div>
  154. <!-- <div class="row">-->
  155. <!-- <el-button class="full-row" @click="changeAllBackgroud">{{ t("style.applyCanvasToAll") }}</el-button>-->
  156. <!-- </div>-->
  157. <Backgrounds/>
  158. <el-divider style="margin: 12px 0"/>
  159. <!-- <Watermark/>-->
  160. <!-- <el-divider style="margin: 12px 0"/>-->
  161. <!-- <div class="title">-->
  162. <!-- <b>{{ t("style.canvasMask") }}</b>-->
  163. <!-- </div>-->
  164. <!-- <el-row>-->
  165. <!-- <el-col :span="7" class="slider-name">{{ t("style.opacity") }}:</el-col>-->
  166. <!-- <el-col :span="13">-->
  167. <!-- <el-slider :min="0.1" :max="1" :step="0.01" v-model="opacity" @change="changeMaskOpacity"></el-slider>-->
  168. <!-- </el-col>-->
  169. <!-- <el-col :span="4" class="slider-num">{{ opacity }}</el-col>-->
  170. <!-- </el-row>-->
  171. </div>
  172. </template>
  173. <script lang="ts" setup>
  174. import {Rect} from "fabric";
  175. import {storeToRefs} from "pinia";
  176. import {ElMessage} from "element-plus";
  177. import {ref, watch, onMounted, computed} from "vue";
  178. import {mm2px, px2mm} from "@/utils/image";
  179. import useI18n from "@/hooks/useI18n";
  180. import {useFabricStore, useMainStore, useTemplatesStore} from "@/store";
  181. import {
  182. WorkSpaceClipType,
  183. WorkSpaceDrawType,
  184. WorkSpaceMaskType,
  185. } from "@/configs/canvas";
  186. import {
  187. DesignUnitMode,
  188. DesignSizeMode,
  189. MinSize,
  190. MaxSize,
  191. } from "@/configs/background";
  192. import useCanvas from "@/views/Canvas/useCanvas";
  193. import Backgrounds from "../Backgrounds/index.vue";
  194. import Watermark from "./Watermark/index.vue";
  195. import useHistorySnapshot from "@/hooks/useHistorySnapshot";
  196. import useCanvasScale from '@/hooks/useCanvasScale'
  197. import {WorkSpaceElement} from "@/types/canvas";
  198. import {GradientColorLibs} from "@/configs/colorGradient";
  199. const {t} = useI18n();
  200. const mainStore = useMainStore();
  201. const templatesStore = useTemplatesStore();
  202. const fabricStore = useFabricStore();
  203. const {addHistorySnapshot} = useHistorySnapshot();
  204. const {sizeMode, unitMode} = storeToRefs(mainStore);
  205. const {currentTemplate} = storeToRefs(templatesStore);
  206. const {clip, safe, zoom, opacity} = storeToRefs(fabricStore);
  207. const {setCanvasSize, resetCanvas} = useCanvasScale()
  208. const pageSizeWidth = computed(() => {
  209. return Math.round(templateWidth.value * 100) / 100
  210. })
  211. const pageSizeHeight = computed(() => {
  212. return Math.round(templateHeight.value * 100) / 100
  213. })
  214. const templateWidth = computed(() => {
  215. // const [ canvas ] = useCanvas()
  216. // if (!canvas) return 0
  217. const workWidth = currentTemplate.value.width / currentTemplate.value.zoom;
  218. return unitMode.value === 0 ? px2mm(workWidth) : workWidth;
  219. });
  220. const templateHeight = computed(() => {
  221. // const [ canvas ] = useCanvas()
  222. // if (!canvas) return 0
  223. const workHeight = currentTemplate.value.height / currentTemplate.value.zoom;
  224. return unitMode.value === 0 ? px2mm(workHeight) : workHeight;
  225. });
  226. // const canvasWidth = ref<number>(px2mm(currentTemplate.value.width / currentTemplate.value.zoom))
  227. const canvasWidth = ref<number>(templateWidth.value);
  228. const canvasHeight = ref<number>(templateHeight.value);
  229. // 固定宽高
  230. const isFixed = ref(false);
  231. // 直角圆角
  232. const isRound = ref(false);
  233. // 网格 预定义 参数
  234. const RECENT_GRIDS = "RECENT_GRIDS";
  235. const gridColorRecent = ref<[string[]]>([[]]);
  236. // 获取画布尺寸
  237. const getCanvasSize = () => {
  238. let width =
  239. unitMode.value === 0 ? mm2px(canvasWidth.value) : canvasWidth.value;
  240. let height =
  241. unitMode.value === 0 ? mm2px(canvasHeight.value) : canvasHeight.value;
  242. width = width * zoom.value;
  243. height = height * zoom.value;
  244. return {width, height};
  245. };
  246. // 修改画布宽度
  247. const changeTemplateWidth = () => {
  248. const [canvas] = useCanvas();
  249. const workSpaceDraw = canvas
  250. .getObjects()
  251. .filter((item) => item.id === WorkSpaceDrawType)[0];
  252. if (!workSpaceDraw) return;
  253. const ratio = currentTemplate.value.height / currentTemplate.value.width;
  254. let {width, height} = getCanvasSize();
  255. if (width / zoom.value < mm2px(MinSize)) {
  256. ElMessage({
  257. message: t("style.minimumSizeLimit") + MinSize,
  258. type: "warning",
  259. });
  260. width = mm2px(MinSize) * zoom.value;
  261. }
  262. if (width / zoom.value > mm2px(MaxSize)) {
  263. ElMessage({
  264. message: t("style.maximumSizeLimit") + MaxSize,
  265. type: "warning",
  266. });
  267. width = mm2px(MaxSize) * zoom.value;
  268. }
  269. height = isFixed.value ? width * ratio : height;
  270. workSpaceDraw.set({width: width / zoom.value, height: height / zoom.value});
  271. templatesStore.setSize(width, height, zoom.value);
  272. sizeMode.value = 2;
  273. canvas.renderAll();
  274. // resetCanvas()
  275. addHistorySnapshot();
  276. };
  277. // 修改画布高度
  278. const changeTemplateHeight = () => {
  279. const [canvas] = useCanvas();
  280. const workSpaceDraw = canvas
  281. .getObjects()
  282. .filter((item) => item.id === WorkSpaceDrawType)[0];
  283. if (!workSpaceDraw) return;
  284. const ratio = currentTemplate.value.height / currentTemplate.value.width;
  285. let {width, height} = getCanvasSize();
  286. if (height / zoom.value < mm2px(MinSize)) {
  287. ElMessage({
  288. message: t("style.minimumSizeLimit") + MinSize,
  289. type: "warning",
  290. });
  291. height = mm2px(MinSize) * zoom.value;
  292. }
  293. if (height / zoom.value > mm2px(MaxSize)) {
  294. ElMessage({
  295. message: t("style.maximumSizeLimit") + MaxSize,
  296. type: "warning",
  297. });
  298. height = mm2px(MaxSize) * zoom.value;
  299. }
  300. width = isFixed.value ? height / ratio : width;
  301. workSpaceDraw.set({width: width / zoom.value, height: height / zoom.value});
  302. templatesStore.setSize(width, height, zoom.value);
  303. sizeMode.value = 2;
  304. canvas.renderAll();
  305. // resetCanvas()
  306. addHistorySnapshot();
  307. };
  308. // 修改出血尺寸
  309. const changeTemplateClip = async () => {
  310. templatesStore.setClip(clip.value);
  311. await templatesStore.renderTemplate();
  312. };
  313. // 修改安全尺寸
  314. const changeTemplateSafe = async () => {
  315. safe.value = Number(safe.value);
  316. await templatesStore.renderTemplate();
  317. };
  318. // 修改固定宽高比
  319. const changeFixedRatio = (fixedStatus: boolean) => {
  320. isFixed.value = fixedStatus;
  321. };
  322. // 修改直角圆角
  323. const changeWorkRound = (roundStatus: boolean) => {
  324. const [canvas] = useCanvas();
  325. const workSpaceclip = canvas
  326. .getObjects()
  327. .filter(
  328. (item) => WorkSpaceClipType === item.id && item.isType("Rect")
  329. )[0] as Rect;
  330. let rx = 0,
  331. ry = 0;
  332. isRound.value = roundStatus;
  333. if (isRound.value) rx = ry = 10;
  334. workSpaceclip.set({rx, ry});
  335. canvas.renderAll();
  336. };
  337. // 修改尺寸单位
  338. const changeUnitMode = async () => {
  339. const width = currentTemplate.value.width / currentTemplate.value.zoom;
  340. const heigth = currentTemplate.value.height / currentTemplate.value.zoom;
  341. if (unitMode.value === 0) {
  342. canvasWidth.value = px2mm(width);
  343. canvasHeight.value = px2mm(heigth);
  344. clip.value = 2;
  345. safe.value = 3;
  346. } else {
  347. canvasWidth.value = width;
  348. canvasHeight.value = heigth;
  349. clip.value = safe.value = 0;
  350. }
  351. await changeTemplateClip();
  352. await changeTemplateSafe();
  353. };
  354. // 应用背景到所有页面
  355. const changeAllBackgroud = () => {
  356. templatesStore.templates.forEach((item) => {
  357. item.workSpace = currentTemplate.value.workSpace;
  358. const currentWorkSpace = currentTemplate.value.objects.filter(
  359. (ele) => ele.id === WorkSpaceDrawType
  360. )[0];
  361. item.objects = item.objects.map((ele) =>
  362. ele.id === WorkSpaceDrawType ? currentWorkSpace : ele
  363. ) as any;
  364. });
  365. };
  366. // 加载缓存最近添加的网格
  367. onMounted(() => {
  368. const recentGridCache = localStorage.getItem(RECENT_GRIDS);
  369. if (recentGridCache) gridColorRecent.value = JSON.parse(recentGridCache);
  370. });
  371. // 保存缓存最近添加的网格
  372. watch(
  373. gridColorRecent,
  374. () => {
  375. const recentGridCache = JSON.stringify(gridColorRecent.value);
  376. localStorage.setItem(RECENT_GRIDS, recentGridCache);
  377. },
  378. {deep: true}
  379. );
  380. const changeMaskOpacity = () => {
  381. const [canvas] = useCanvas();
  382. const workMask = canvas
  383. .getObjects()
  384. .filter((ele) => ele.id === WorkSpaceMaskType)[0];
  385. if (!workMask) return;
  386. workMask.set("opacity", opacity.value);
  387. canvas.renderAll();
  388. };
  389. </script>
  390. <style lang="scss" scoped>
  391. .icon-btn {
  392. cursor: pointer;
  393. }
  394. .canvas-design-panel {
  395. user-select: none;
  396. }
  397. .row {
  398. width: 100%;
  399. display: flex;
  400. align-items: center;
  401. margin-bottom: 10px;
  402. }
  403. .title {
  404. margin-bottom: 10px;
  405. }
  406. .fixed-ratio {
  407. display: flex;
  408. flex-direction: column;
  409. align-items: center;
  410. justify-content: center;
  411. }
  412. .slider-name {
  413. display: flex;
  414. align-items: center;
  415. }
  416. .mb-10 {
  417. margin-bottom: 10px;
  418. }
  419. .full-row {
  420. flex: 1;
  421. width: 100%;
  422. }
  423. .full-group {
  424. display: flex;
  425. flex: 1;
  426. .el-button {
  427. width: 50%;
  428. }
  429. }
  430. .full-ratio {
  431. display: flex;
  432. flex: 1;
  433. .el-radio-button {
  434. width: 50%;
  435. }
  436. .el-radio-button__inner {
  437. width: 100%;
  438. }
  439. }
  440. .background-image {
  441. height: 0;
  442. padding-bottom: 56.25%;
  443. border: 1px dashed var(--el-border-color);
  444. border-radius: $borderRadius;
  445. position: relative;
  446. transition: all $transitionDelay;
  447. &:hover {
  448. border-color: var(--el-color-primary);
  449. color: var(--el-color-primary);
  450. }
  451. .content {
  452. @include absolute-0();
  453. display: flex;
  454. justify-content: center;
  455. align-items: center;
  456. background-position: center;
  457. background-size: contain;
  458. background-repeat: no-repeat;
  459. cursor: pointer;
  460. }
  461. }
  462. .theme-list {
  463. @include flex-grid-layout();
  464. }
  465. .theme-item {
  466. @include flex-grid-layout-children(2, 48%);
  467. padding-bottom: 30%;
  468. border-radius: $borderRadius;
  469. position: relative;
  470. cursor: pointer;
  471. .theme-item-content {
  472. @include absolute-0();
  473. display: flex;
  474. flex-direction: column;
  475. justify-content: center;
  476. padding: 8px;
  477. border: 1px solid $borderColor;
  478. }
  479. .text {
  480. font-size: 16px;
  481. }
  482. .colors {
  483. display: flex;
  484. }
  485. .color-block {
  486. margin-top: 8px;
  487. width: 12px;
  488. height: 12px;
  489. margin-right: 2px;
  490. }
  491. &:hover .btns {
  492. display: flex;
  493. }
  494. .btns {
  495. @include absolute-0();
  496. flex-direction: column;
  497. justify-content: center;
  498. align-items: center;
  499. display: none;
  500. background-color: rgba($color: #000, $alpha: 0.25);
  501. }
  502. .btn {
  503. width: 72px;
  504. padding: 5px 0;
  505. text-align: center;
  506. background-color: $themeColor;
  507. color: #fff;
  508. font-size: 12px;
  509. border-radius: $borderRadius;
  510. &:hover {
  511. background-color: #c42f19;
  512. }
  513. & + .btn {
  514. margin-top: 5px;
  515. }
  516. }
  517. }
  518. .mt-10 {
  519. margin-top: 10px;
  520. }
  521. .slider-num {
  522. display: flex;
  523. align-items: center;
  524. justify-content: center;
  525. }
  526. </style>
  527. <style scoped>
  528. :deep(.el-input .el-input-group__prepend) {
  529. padding: 0 5px;
  530. }
  531. :deep(.el-input .el-input-group__append) {
  532. padding: 0 5px;
  533. }
  534. :deep(.full-ratio .el-radio-button__inner) {
  535. width: 100%;
  536. }
  537. :deep(.size-row .el-input-group__prepend) {
  538. min-width: 24px;
  539. }
  540. </style>