Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
zoom: { default: 1, type: Number },
|
||||
step: { default: 0.1, type: Number }
|
||||
})
|
||||
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close', 'workbench'])
|
||||
const emit = defineEmits(['export', 'workbench'])
|
||||
const importLocalImage = inject('importLocalImage') as (isRecord?: boolean) => void
|
||||
const stateManager = inject('stateManager') as any
|
||||
const toolManager = inject('toolManager') as any
|
||||
@@ -178,7 +178,8 @@
|
||||
}
|
||||
const onWorkbench = async () => {
|
||||
exportCanvasToImage(canvasManager.canvas).then((url) => {
|
||||
emit('workbench', { url })
|
||||
const json = canvasManager.getCanvasJSON()
|
||||
emit('workbench', { url, json })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="fill-repeat">
|
||||
<div class="fill-repeat h">
|
||||
<div>
|
||||
<div class="title">Image</div>
|
||||
<div class="content">
|
||||
@@ -99,6 +99,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
|
||||
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||
import DepthSlider from '../tools/depth-slider.vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||
const objectManager = inject('objectManager') as any
|
||||
const stateManager = inject('stateManager') as any
|
||||
@@ -159,7 +161,7 @@
|
||||
const options = {
|
||||
opacity: opacity.value / 100
|
||||
}
|
||||
objectManager.updateOpacity(id.value, options, isRecord)
|
||||
objectManager.updateProperty(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
stateManager.event.add('canvas:undo', updateData)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<div class="content" v-if="isShow" v-show="show">
|
||||
<!-- <basic-info :object="activeObject" /> -->
|
||||
<fill-repeat :object="activeObject" v-if="isRepeat" />
|
||||
<!-- <shape-setting :object="activeObject" v-if="isShape && !isRepeat" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -17,6 +18,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||
import FillRepeat from './fill-repeat.vue'
|
||||
import ShapeSetting from './shape-setting.vue'
|
||||
const props = defineProps({})
|
||||
const layerManager = inject('layerManager') as any
|
||||
const canvasManager = inject('canvasManager') as any
|
||||
@@ -25,6 +27,8 @@
|
||||
const layers = computed(() => layerManager.layers.value)
|
||||
const activeObject = ref(null)
|
||||
|
||||
// const shapes = ['rect', 'line', 'path', 'triangle', 'polygon', 'ellipse']
|
||||
// const isShape = computed(() => shapes.includes(activeObject.value?.type))
|
||||
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
|
||||
const isShow = computed(() => isRepeat.value)
|
||||
|
||||
@@ -85,7 +89,12 @@
|
||||
overflow-y: auto;
|
||||
&:deep(> div) {
|
||||
> div {
|
||||
margin-bottom: 1.6rem;
|
||||
margin-bottom: var(--details-item-margin-bottom, 1.6rem);
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&.h > div {
|
||||
> .title {
|
||||
font-size: 1.4rem;
|
||||
color: #000;
|
||||
@@ -97,6 +106,20 @@
|
||||
padding: 0 1.4rem;
|
||||
}
|
||||
}
|
||||
&.v > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> .label {
|
||||
min-width: 6rem;
|
||||
margin-right: 0.8rem;
|
||||
font-size: 1.2rem;
|
||||
text-align: right;
|
||||
}
|
||||
> .value {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="shape-setting v">
|
||||
<!-- <div>
|
||||
<div class="label">填充颜色</div>
|
||||
<div class="value">
|
||||
<el-color-picker
|
||||
v-model="data.fill"
|
||||
show-alpha
|
||||
:predefine="['transparent', '#000', '#f00', '#0f0', '#00f']"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive } from 'vue'
|
||||
import { ElColorPicker } from 'element-plus'
|
||||
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||
import DepthSlider from '../tools/depth-slider.vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
const objectManager = inject('objectManager') as any
|
||||
const stateManager = inject('stateManager') as any
|
||||
const props = defineProps({
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const id = computed(() => props.object.info.id)
|
||||
|
||||
const data = reactive({
|
||||
fill: '',
|
||||
stroke: '',
|
||||
strokeWidth: 0
|
||||
})
|
||||
const updateData = async () => {
|
||||
await nextTick()
|
||||
data.fill = props.object.fill
|
||||
data.stroke = props.object.stroke
|
||||
data.strokeWidth = props.object.strokeWidth
|
||||
}
|
||||
updateData()
|
||||
|
||||
const onInpot = () => setPriority(false)
|
||||
const onChange = () => setPriority(true)
|
||||
const setPriority = (isRecord: boolean) => {
|
||||
const options = { ...data }
|
||||
objectManager.updateProperty(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
stateManager.event.add('canvas:undo', updateData)
|
||||
stateManager.event.add('canvas:redo', updateData)
|
||||
onBeforeUnmount(() => {
|
||||
stateManager.event.remove('canvas:undo', updateData)
|
||||
stateManager.event.remove('canvas:redo', updateData)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.shape-setting {
|
||||
--details-item-margin-bottom: 1rem;
|
||||
> div {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,12 +6,7 @@
|
||||
<template v-if="isReady">
|
||||
<layer-panel />
|
||||
<details-panel />
|
||||
<depth-header-tools
|
||||
@export="exportCanvas"
|
||||
@export-local="exportCanvasToLocalStorage"
|
||||
@import-local="importCanvasFromLocalStorage"
|
||||
@workbench="(v) => emit('workbench', v)"
|
||||
/>
|
||||
<depth-header-tools @export="exportCanvas" @workbench="(v) => emit('workbench', v)" />
|
||||
<brush-control-panel :currentTool="toolManager.currentTool.value" />
|
||||
<zoom
|
||||
:zoom="canvasManager.currentZoom.value / 100"
|
||||
@@ -44,6 +39,9 @@
|
||||
import { KeyEventManager } from './manager/events/KeyEventManager'
|
||||
import { ObjectManager } from './manager/ObjectManager'
|
||||
|
||||
import { useGlobalStore } from '@/stores'
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
const emit = defineEmits(['workbench', 'close'])
|
||||
const props = defineProps({
|
||||
config: {
|
||||
@@ -88,15 +86,21 @@
|
||||
|
||||
const observer = ref(null)
|
||||
onMounted(async () => {
|
||||
globalStore.setLoading(true)
|
||||
keyEventManager.registerEvents()
|
||||
const url = props.config.url || ''
|
||||
const json = props.config.json || ''
|
||||
await canvasManager.initCanvas({
|
||||
canvasRef,
|
||||
canvasViewWidth: canvasContainerRef.value.clientWidth,
|
||||
canvasViewHeight: canvasContainerRef.value.clientHeight,
|
||||
canvasWidth: props.config.width || 750,
|
||||
canvasHeight: props.config.height || 600,
|
||||
url: props.config.url || ''
|
||||
url: json ? '' : url
|
||||
})
|
||||
if (json) await canvasManager.loadJSON(json)
|
||||
globalStore.setLoading(false)
|
||||
|
||||
stateManager.onMounted()
|
||||
canvasManager.onMounted()
|
||||
layerManager.onMounted()
|
||||
@@ -164,24 +168,13 @@
|
||||
a.download = 'canvas.png'
|
||||
a.click()
|
||||
})
|
||||
}
|
||||
// 导出到本地存储
|
||||
const exportCanvasToLocalStorage = () => {
|
||||
const json = canvasManager.getCanvasJSON()
|
||||
localStorage.setItem('canvasJSON', json)
|
||||
}
|
||||
// 从本地存储导入
|
||||
const importCanvasFromLocalStorage = () => {
|
||||
const json = localStorage.getItem('canvasJSON')
|
||||
if (!json) return
|
||||
canvasManager.loadJSON(json, (success) => {
|
||||
if (success) {
|
||||
console.log('导入成功')
|
||||
} else {
|
||||
console.log('导入失败')
|
||||
}
|
||||
stateManager.clearState(true)
|
||||
})
|
||||
|
||||
// console.log(canvasManager.getCanvasJSON())
|
||||
|
||||
// const object = canvasManager.getCanvasDisUrlJSON()
|
||||
// console.log(object)
|
||||
// const canvas = canvasManager.processCanvasDisUrlJSON(object)
|
||||
// console.log(canvas)
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
@@ -7,23 +7,36 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { base64Tofile } from '../tools/tools'
|
||||
import { uploadImage } from '@/api/upload'
|
||||
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
||||
import depthCanvas from './depth-canvas.vue'
|
||||
import { ref } from 'vue'
|
||||
const dialogVisible = ref(false)
|
||||
const config = ref({
|
||||
id: '',
|
||||
url: ''
|
||||
url: '',
|
||||
json: ''
|
||||
})
|
||||
|
||||
const open = (options) => {
|
||||
config.value = options
|
||||
console.log(config.value)
|
||||
config.value.json = sessionStorage.getItem('canvasJson_' + config.value.id)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 工作区
|
||||
const onWorkbench = (options) => {
|
||||
const onWorkbench = async (options) => {
|
||||
const json = options.json
|
||||
|
||||
sessionStorage.setItem('canvasJson_' + config.value.id, json)
|
||||
|
||||
const file = base64Tofile(options.url, 'canvas.png')
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const url = await uploadImage(formData)
|
||||
config.value.onWorkbench?.({ url })
|
||||
dialogVisible.value = false
|
||||
config.value.onWorkbench?.(options)
|
||||
}
|
||||
// 关闭
|
||||
const onClose = () => {
|
||||
|
||||
@@ -289,10 +289,7 @@ export class AnimationManager {
|
||||
* @param {Boolean} adaptive 是否自适应缩放
|
||||
*/
|
||||
async resetZoom(animated = true, adaptive = false) {
|
||||
const canvasViewWidth = this.canvasManager.canvasViewWidth;
|
||||
const canvasViewHeight = this.canvasManager.canvasViewHeight;
|
||||
const canvasWidth = this.canvasManager.canvasWidth;
|
||||
const canvasHeight = this.canvasManager.canvasHeight;
|
||||
const { canvasViewWidth, canvasViewHeight, canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
||||
const scaleX = canvasViewWidth / canvasWidth * 0.8
|
||||
const scaleY = canvasViewHeight / canvasHeight * 0.8
|
||||
const scale = Math.min(scaleX, scaleY, 1)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { createCanvas } from '../tools/canvasFactory'
|
||||
import { AnimationManager } from './AnimationManager'
|
||||
import { detectDeviceType } from '../tools/index'
|
||||
@@ -44,10 +44,6 @@ export class CanvasManager {
|
||||
eventManager: any
|
||||
deviceInfo: any
|
||||
canvas: any
|
||||
canvasViewWidth: number
|
||||
canvasViewHeight: number
|
||||
canvasWidth: number
|
||||
canvasHeight: number
|
||||
currentZoom: any
|
||||
constructor(options) {
|
||||
this.stateManager = options.stateManager;
|
||||
@@ -55,23 +51,31 @@ export class CanvasManager {
|
||||
this.currentZoom = ref(100)
|
||||
}
|
||||
onMounted() { }
|
||||
getCanvasSize() {
|
||||
return {
|
||||
canvasViewWidth: this.canvas.width,
|
||||
canvasViewHeight: this.canvas.height,
|
||||
canvasWidth: this.canvas.clipPath.width,
|
||||
canvasHeight: this.canvas.clipPath.height,
|
||||
}
|
||||
}
|
||||
setCanvasViewSize(options) {
|
||||
this.canvasViewWidth = options.canvasViewWidth || 1920
|
||||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
||||
this.canvas.setWidth(this.canvasViewWidth)
|
||||
this.canvas.setHeight(this.canvasViewHeight)
|
||||
var canvasViewWidth = options.canvasViewWidth || 1920
|
||||
var canvasViewHeight = options.canvasViewHeight || 1080
|
||||
this.canvas.setWidth(canvasViewWidth)
|
||||
this.canvas.setHeight(canvasViewHeight)
|
||||
}
|
||||
/** 初始化画布 */
|
||||
async initCanvas(options: CanvasInitOptions) {
|
||||
this.layerManager = this.stateManager.layerManager
|
||||
this.canvasWidth = options.canvasWidth || 750
|
||||
this.canvasHeight = options.canvasHeight || 600
|
||||
var canvasWidth = options.canvasWidth || 750
|
||||
var canvasHeight = options.canvasHeight || 600
|
||||
var image = null;
|
||||
if (options.url) {
|
||||
await new Promise((resolve) => {
|
||||
fabric.Image.fromURL(options.url, async (img) => {
|
||||
this.canvasWidth = img.width
|
||||
this.canvasHeight = img.height
|
||||
canvasWidth = img.width
|
||||
canvasHeight = img.height
|
||||
img.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
@@ -104,8 +108,8 @@ export class CanvasManager {
|
||||
this.canvas.clipPath = new fabric.Rect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: this.canvasWidth,
|
||||
height: this.canvasHeight
|
||||
width: canvasWidth,
|
||||
height: canvasHeight
|
||||
})
|
||||
|
||||
// 动画管理器
|
||||
@@ -122,19 +126,10 @@ export class CanvasManager {
|
||||
|
||||
/** 测试-开始 */
|
||||
// this.stateManager.setIsRecord(false)
|
||||
// // 创建矩形
|
||||
// const rect = await this.layerManager.createRectLayer({
|
||||
// left: 400,
|
||||
// top: 100,
|
||||
// })
|
||||
// //创建圆形
|
||||
// const circle = await this.layerManager.createCircleLayer({
|
||||
// left: 200,
|
||||
// top: 200,
|
||||
// })
|
||||
// // 文字
|
||||
// const text = await this.layerManager.createTextLayer('Hello World');
|
||||
// this.layerManager.setActiveID(text.info.id)
|
||||
// const rect = await this.layerManager.createRectLayer({ left: 200 })
|
||||
// await this.layerManager.createStarLayer({ left: 400 })
|
||||
// await this.layerManager.createArrowLayer({ left: 600 })
|
||||
// this.layerManager.setActiveID(rect.info.id)
|
||||
// this.stateManager.setIsRecord(true)
|
||||
/** 测试-结束 */
|
||||
|
||||
@@ -275,20 +270,59 @@ export class CanvasManager {
|
||||
return JSON.stringify(json)
|
||||
}
|
||||
/** 加载画布JSON */
|
||||
loadJSON(json: string, callback?: (success: boolean) => void) {
|
||||
let jsonObj = null;
|
||||
try {
|
||||
jsonObj = JSON.parse(json)
|
||||
} catch (error) {
|
||||
console.error('JSON解析错误:', error)
|
||||
}
|
||||
if (!jsonObj) return callback?.(false);
|
||||
this.canvas.loadFromJSON(jsonObj, () => {
|
||||
this.layerManager.updateLayers()
|
||||
this.renderAll()
|
||||
callback?.(true)
|
||||
loadJSON(json: string, rerecord = true) {
|
||||
return new Promise((resolve) => {
|
||||
let jsonObj = null;
|
||||
try {
|
||||
jsonObj = JSON.parse(json)
|
||||
} catch (error) {
|
||||
console.error('JSON解析错误:', error)
|
||||
}
|
||||
if (!jsonObj) return resolve(false)
|
||||
this.canvas.loadFromJSON(jsonObj, () => {
|
||||
if (rerecord) this.stateManager.clearState()
|
||||
this.resetZoom(false)
|
||||
this.layerManager.updateLayers()
|
||||
this.renderAll()
|
||||
if (rerecord) this.stateManager.recordState()
|
||||
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
/** 导出画布为处理图片的JSON */
|
||||
getCanvasDisUrlJSON() {
|
||||
const canvas = this.canvas.toJSON()
|
||||
const images = [];
|
||||
var i = 0;
|
||||
const create = (url) => {
|
||||
const logo = `xxxxxxxx_${i++}_xxxxxxxx`;
|
||||
images.push({ index: logo, url })
|
||||
return logo
|
||||
}
|
||||
canvas.objects.forEach((object: any) => {
|
||||
if (object.thumbnail) {
|
||||
object.thumbnail = create(object.thumbnail)
|
||||
}
|
||||
if (object.src) {
|
||||
object.src = create(object.src)
|
||||
}
|
||||
if (object.fill?.source) {
|
||||
object.fill.source = create(object.fill.source)
|
||||
}
|
||||
if (object.info?.fill?.source) {
|
||||
object.info.fill.source = create(object.info.fill.source)
|
||||
}
|
||||
})
|
||||
return { canvas: JSON.stringify(canvas), images }
|
||||
}
|
||||
/** 处理JSON为正常画布 */
|
||||
processCanvasDisUrlJSON(obj: { canvas: string, images: Object[] }) {
|
||||
var json = obj.canvas;
|
||||
const images = obj.images || []
|
||||
images.forEach((v: any) => json = json.replace(new RegExp(v.index), v.url))
|
||||
return JSON.parse(json)
|
||||
}
|
||||
dispose() {
|
||||
this.animationManager?.dispose()
|
||||
this.eventManager?.dispose()
|
||||
|
||||
@@ -121,8 +121,8 @@ export class LayerManager {
|
||||
|
||||
/** 设置图层位置-不设置默认居中 */
|
||||
setLayerPosition(object, options?: any) {
|
||||
const width = this.canvasManager.canvasWidth
|
||||
const height = this.canvasManager.canvasHeight
|
||||
const width = this.canvasManager.canvas.clipPath.width
|
||||
const height = this.canvasManager.canvas.clipPath.height
|
||||
|
||||
if (options && options.top !== undefined) {
|
||||
object.set({ top: options.top })
|
||||
@@ -173,6 +173,7 @@ export class LayerManager {
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("rect"),
|
||||
@@ -212,6 +213,7 @@ export class LayerManager {
|
||||
const ellipseObject = new fabric.Ellipse({
|
||||
radius: 50,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("ellipse"),
|
||||
@@ -230,6 +232,7 @@ export class LayerManager {
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("triangle"),
|
||||
@@ -249,6 +252,7 @@ export class LayerManager {
|
||||
delete options.points
|
||||
const starObject = new fabric.Polygon(getStarArr(width, height), {
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("star"),
|
||||
@@ -290,8 +294,7 @@ export class LayerManager {
|
||||
|
||||
/** 创建图片图层 */
|
||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
||||
const canvasWidth = this.canvasManager.canvasWidth
|
||||
const canvasHeight = this.canvasManager.canvasHeight
|
||||
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
||||
|
||||
const imageObject = await new Promise((resolve) => {
|
||||
const url = typeof imgOrUrl === 'string' ? imgOrUrl : imgOrUrl.src
|
||||
@@ -340,10 +343,11 @@ export class LayerManager {
|
||||
const index = this.canvasManager.getObjects().indexOf(targetLayer);
|
||||
this.deleteLayerById(targetLayer.info.id, false)
|
||||
this.setActiveID(mergedImage.info.id, false)
|
||||
await this.canvasManager.add(mergedImage);
|
||||
await this.canvasManager.add(mergedImage, false);
|
||||
this.canvasManager.canvas.moveTo(mergedImage, index);
|
||||
this.canvasManager.renderAll()
|
||||
this.updateLayers()
|
||||
this.stateManager.recordState()
|
||||
return true;
|
||||
}
|
||||
/** 设置激活对象可擦除 */
|
||||
|
||||
@@ -207,18 +207,15 @@ export class ObjectManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 修改透明度
|
||||
/** 修改属性
|
||||
* @param id 目标对象ID
|
||||
* @param options 透明度参数
|
||||
* @param options.opacity 透明度
|
||||
* @param options 参数
|
||||
* @param isRecord 是否记录
|
||||
*/
|
||||
async updateOpacity(id: string, options: any, isRecord: boolean) {
|
||||
const object = this.getFillRepeatObject(id)
|
||||
async updateProperty(id: string, options: any, isRecord: boolean) {
|
||||
const object = this.canvasManager.getObjectById(id)
|
||||
if (!object) return null
|
||||
const opacity = options.opacity
|
||||
object.set("opacity", opacity);
|
||||
object.set(options);
|
||||
this.canvasManager.renderAll()
|
||||
if (isRecord) {
|
||||
this.stateManager.recordState()
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { OperationType, OperationTypes } from "../tools/layerHelper";
|
||||
import { fabric } from 'fabric-with-all'
|
||||
/** 矩形工具管理器 */
|
||||
export class RectToolManager {
|
||||
// 管理器
|
||||
canvasManager: any
|
||||
stateManager: any
|
||||
layerManager: any
|
||||
|
||||
isDragging: boolean = false
|
||||
startX: number = 0
|
||||
startY: number = 0
|
||||
demoObject: any
|
||||
tools = [
|
||||
OperationType.RECTANGLE
|
||||
]
|
||||
constructor(options) {
|
||||
this.canvasManager = options.canvasManager
|
||||
this.stateManager = options.stateManager
|
||||
this.layerManager = options.layerManager
|
||||
}
|
||||
mouseDownEvent(e) {
|
||||
this.isDragging = true
|
||||
this.startX = e.absolutePointer.x
|
||||
this.startY = e.absolutePointer.y
|
||||
const rect = new fabric.Rect({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
evented: false,
|
||||
})
|
||||
this.demoObject = rect
|
||||
this.canvasManager.canvas.add(rect)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseMoveEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
var left = this.startX
|
||||
var top = this.startY
|
||||
if (width < 0) {
|
||||
left += width
|
||||
width = -width
|
||||
}
|
||||
if (height < 0) {
|
||||
top += height
|
||||
height = -height
|
||||
}
|
||||
this.demoObject.set({ width, height, left, top })
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseUpEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
const object = this.demoObject.toJSON("evented")
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
this.layerManager.createRectLayer(object, true)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
|
||||
}
|
||||
dispose() { }
|
||||
}
|
||||
@@ -120,6 +120,7 @@ export class ShapeToolManager {
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
return rect
|
||||
}
|
||||
@@ -159,6 +160,7 @@ export class ShapeToolManager {
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
return circle
|
||||
}
|
||||
@@ -180,6 +182,7 @@ export class ShapeToolManager {
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
return triangle
|
||||
}
|
||||
@@ -203,6 +206,7 @@ export class ShapeToolManager {
|
||||
fill: '#000',
|
||||
strokeLineJoin: 'round', // 圆角连接
|
||||
strokeLineCap: 'round', // 圆角端点
|
||||
strokeWidth: 0,
|
||||
});
|
||||
|
||||
return star
|
||||
|
||||
@@ -92,7 +92,7 @@ export class StateManager {
|
||||
if (!state) return
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, () => {
|
||||
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||
this.event.emit('canvas:undo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
@@ -105,7 +105,7 @@ export class StateManager {
|
||||
if (!state) return
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, () => {
|
||||
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||
this.event.emit('canvas:redo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
|
||||
@@ -11,10 +11,14 @@
|
||||
<svg-icon :name="v.icon" :size="v.iconSize" />
|
||||
</span>
|
||||
</template>
|
||||
<button class="export" @click="emit('export')">
|
||||
<span class="icon"><svg-icon name="export" size="11" /></span>
|
||||
<span class="text">Export</span>
|
||||
</button>
|
||||
<div>
|
||||
<button class="export" @click="emit('export')">
|
||||
<span class="icon"><svg-icon name="export" size="11" /></span>
|
||||
<span class="text">Export</span>
|
||||
</button>
|
||||
<div v-loading="true" class="mask" v-if="downloadData.status == 'loading'"></div>
|
||||
</div>
|
||||
|
||||
<!-- <button class="import" @click="emit('import')">
|
||||
<span class="text">Import</span>
|
||||
</button> -->
|
||||
@@ -26,7 +30,8 @@
|
||||
import { TOOLS } from '../manager/ToolManager'
|
||||
const props = defineProps({
|
||||
zoom: { default: 1, type: Number },
|
||||
step: { default: 0.1, type: Number }
|
||||
step: { default: 0.1, type: Number },
|
||||
downloadData: { default: {}, type: Object }
|
||||
})
|
||||
const emit = defineEmits(['export', 'import'])
|
||||
const stateManager = inject('stateManager') as any
|
||||
@@ -109,8 +114,21 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
> button {
|
||||
width: 10rem;
|
||||
> div{
|
||||
position: relative;
|
||||
> .mask{
|
||||
position: absolute !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
zoom: .6;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
button {
|
||||
// width: 10rem;
|
||||
padding: 0 2.4rem;
|
||||
height: 3rem;
|
||||
border-radius: 0.4rem;
|
||||
border: none;
|
||||
@@ -118,12 +136,15 @@
|
||||
color: #fff;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.8rem;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
const nodes = computed(() => props.stateManager.nodes.value)
|
||||
const isSubord = computed(() => nodes.value.some((v) => v.data.superiorID === props.node.id))
|
||||
const tier = computed(() => Number(props.node?.data?.tier || 0))
|
||||
//只有3d模型才有三级菜单,目前三级菜单内容少直接禁用按钮
|
||||
const isAdd3d = computed(() => (tier.value === 2 && props.node?.data?.superiorNodeType === NODE_DATATYPE.TO_3D_MODEL) || tier.value != 2)
|
||||
const isReturned = computed(() => {
|
||||
return (
|
||||
props.node.data.type == NODE_DATATYPE.RESULT_IMAGE &&
|
||||
@@ -63,7 +65,8 @@
|
||||
!isSubord.value &&
|
||||
NODE_DATATYPE.RESULT_IMAGE === props.node.data.type &&
|
||||
!(tier.value === NODE_DATATIER.TO_3VIEW) &&
|
||||
isReturned.value
|
||||
isReturned.value &&
|
||||
isAdd3d.value
|
||||
)
|
||||
const onAdd = () => {
|
||||
const tier_ = tier.value + 1
|
||||
|
||||
@@ -155,12 +155,6 @@
|
||||
}
|
||||
})
|
||||
})
|
||||
stateManager.recordState()
|
||||
// } else {
|
||||
// subordNode.data.data.url =
|
||||
// 'https://s3-alpha-sig.figma.com/img/8ce2/f1a4/12b93da90e5f17109e7430f14837fd14?Expires=1773619200&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=kmLsTFtXJqfvuxj6husWlDkRDMOIRDjzUUjb7zh79GkBKihUHc0f59k5OAImHTPdaiEREUCCpn~8sQ-si5lenuauJpApCmAU~NsxjfQhuh9m5O~GiHenr2fKu0DIJ75-oCE3859fyxoSFXQgZ9PRmeb98kikMR6uRX9nI5TPUHgKO8ZgkhDBTW~iyaDT~1ybnoK7elPa6T2VzfO-bpIyY-MZ71VRq3RxwmZRxduqHEb3Dh-jjrHyh2SoQsHmUjSJOf-uYilfvpGUResZAjAq8ZVLEjvhzKC2bmCNZIp3RmhYO8ctU7pd5t91J6Xaa6jBLtGfMxbqIm652EC79K0RoA__'
|
||||
// setTimeout(() => stateManager.recordState())
|
||||
// }
|
||||
}
|
||||
//删除功能卡片
|
||||
const onDeleteClick = ()=>{
|
||||
|
||||
@@ -28,23 +28,23 @@
|
||||
const shortcutList = ref([
|
||||
{
|
||||
label: 'Change the...',
|
||||
value: 'Change the...'
|
||||
value: 'Change the style to a realistic design. '
|
||||
},
|
||||
{
|
||||
label: 'Bright Colors...',
|
||||
value: 'Bright Colors...'
|
||||
value: 'Bright colors with modern patterns, change the style to a realistic furniture design. '
|
||||
},
|
||||
{
|
||||
label: 'Make the...',
|
||||
value: 'Make the...'
|
||||
value: 'Make the structure more refined and balanced, change the style to a realistic furniture style. '
|
||||
},
|
||||
{
|
||||
label: 'Imagine...',
|
||||
value: 'Imagine...'
|
||||
value: 'Imagine this furniture with detailed fabric textures, change the style to a realistic design. '
|
||||
},
|
||||
{
|
||||
label: 'Wood Materials with...',
|
||||
value: 'Wood Materials with...'
|
||||
value: 'Wood materials with natural oak texture and soft fabric, change the style to a realistic furniture design.'
|
||||
}
|
||||
])
|
||||
const modeList = ref([
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<img
|
||||
v-loadimg="false"
|
||||
class="image"
|
||||
v-if="item.status == 'RETURNED'"
|
||||
:src="item?.url"
|
||||
@@ -58,10 +59,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import myEvent from '@/utils/myEvent'
|
||||
import { downloadImage } from '../../../tools/tools'
|
||||
import { downloadImage, base64Tofile } from '../../../tools/tools'
|
||||
import { reactive, ref, onBeforeUnmount, useAttrs, inject, watch, computed, onMounted } from 'vue'
|
||||
import HighlightAdmin from '@/components/highlightAdmin.vue'
|
||||
import { NODE_DATATYPE } from '../../tools/index.d'
|
||||
import { uploadImage } from '@/api/upload'
|
||||
const openImagePreview = inject('openImagePreview') as (url: string) => void
|
||||
const openThreeModelPreview = inject('openThreeModelPreview') as (url: string) => void
|
||||
const props = defineProps({
|
||||
@@ -161,6 +163,7 @@
|
||||
item.scale.x = -item.scale.x
|
||||
})
|
||||
stateManager.recordState()
|
||||
stateManager.exportFlow(stateManager.saveCanvasTimeInterval)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -174,6 +177,7 @@
|
||||
item.scale.y = -item.scale.y
|
||||
})
|
||||
stateManager.recordState()
|
||||
stateManager.exportFlow(stateManager.saveCanvasTimeInterval)
|
||||
}
|
||||
}
|
||||
])
|
||||
@@ -201,11 +205,19 @@
|
||||
eventManager.registerEvents()
|
||||
}
|
||||
const depthCanvasWorkbench = (options)=>{
|
||||
data.imageProcessTasks.forEach((item) => {
|
||||
if(item.taskId == options.taskId){
|
||||
item.url = options.url
|
||||
}
|
||||
console.log(options)
|
||||
// 1. 提取 MIME 类型和 Base64 数据
|
||||
const file = base64Tofile(options.url,'image.png')
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
uploadImage(formData).then((res) => {
|
||||
data.imageProcessTasks.forEach((item) => {
|
||||
if(item.taskId == options.taskId){
|
||||
item.url = res
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const onEdit = (item: any) => {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</template>
|
||||
</VueFlow>
|
||||
</div>
|
||||
<header-tools @export="exportFlow" @import="importFlow" />
|
||||
<header-tools @export="exportFlow" @import="importFlow" :downloadData="downloadData" />
|
||||
<zoom
|
||||
:zoom="stateManager.zoom.value"
|
||||
:step="0.1"
|
||||
@@ -107,9 +107,8 @@
|
||||
|
||||
const vueFlow = ref<any>()
|
||||
const nodeTypes = ref([NODE_TYPE.INPUT, NODE_TYPE.SECONDARY, NODE_TYPE.OUTPUT, NODE_TYPE.ALONE])
|
||||
|
||||
// 状态管理器
|
||||
const stateManager = new StateManager({ vueFlow })
|
||||
const stateManager = new StateManager({ vueFlow,sketchId:props.config.imgId })
|
||||
provide('stateManager', stateManager)
|
||||
|
||||
// 事件管理器
|
||||
@@ -191,29 +190,34 @@
|
||||
}
|
||||
// 导出流程
|
||||
const getFlowJson = () => {
|
||||
if(!stateManager.isSave.value)return ''
|
||||
return JSON.stringify(stateManager.nodes.value)
|
||||
}
|
||||
const exportFlow = () => {
|
||||
const downloadData = ref<any>({
|
||||
amount: 0,
|
||||
progress: 0,
|
||||
status: 'success',//success
|
||||
})
|
||||
const exportFlow = async () => {
|
||||
// console.log(vueFlow.value)
|
||||
// console.log(vueFlow.value.toImage)
|
||||
let arr = stateManager.nodes.value.filter((v) => v.data.type === NODE_COMPONENT.RESULT_IMAGE)
|
||||
let imgList = []
|
||||
arr.forEach((v) => {
|
||||
arr.forEach((v,i) => {
|
||||
v.data.data.imageProcessTasks.forEach((item,index) => {
|
||||
let url = item.url
|
||||
let name = url?.split(".").pop().split("?").shift();
|
||||
imgList.push({url:url,name:`${v.data.type}${index == 0?'':index}.${name}`})
|
||||
imgList.push({url:url,name:`${v.data.type}${i}-${index == 0?'':index}.${name}`})
|
||||
})
|
||||
})
|
||||
downImgListToZip(imgList)
|
||||
console.log(imgList)
|
||||
downloadData.value.amount = imgList.length
|
||||
downloadData.value.status = 'loading'
|
||||
await downImgListToZip(imgList,(progress)=>{
|
||||
downloadData.value.progress = progress
|
||||
if(progress == downloadData.value.amount){
|
||||
downloadData.value.status = 'success'
|
||||
}
|
||||
})
|
||||
return
|
||||
// flowManager.exportFlow()
|
||||
const str = getFlowJson()
|
||||
stateManager.isSave.value = false
|
||||
emit('exportFlow', str)
|
||||
// localStorage.setItem('flow_json', str)
|
||||
}
|
||||
// 导入流程
|
||||
const importFlow = async (json) => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<fullscreen-dialog v-model="dialogVisible" @close="close" hide-destroy>
|
||||
<flow-canvas ref="flowCanvasRef" :config="config" @exportFlow="exportFlow" />
|
||||
<flow-canvas ref="flowCanvasRef" :config="config" />
|
||||
</fullscreen-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
||||
import flowCanvas from './flow-canvas.vue'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { getSketchFlowCanvas, putSketchFlowCanvas } from '@/api/flow-canvas'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -31,43 +31,12 @@
|
||||
config.value.json = json
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const exportFlow = async (str) => {
|
||||
if(!config.value.imgId || !str)return
|
||||
await new Promise((resolve) => {
|
||||
putSketchFlowCanvas({
|
||||
id: config.value.imgId,
|
||||
canvasData: str },true).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const close = async () => {
|
||||
const str = flowCanvasRef.value?.getFlowJson()
|
||||
await exportFlow(str)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
const handleBeforeUnload = (event) => {
|
||||
const str = flowCanvasRef.value?.getFlowJson()
|
||||
if (str) {
|
||||
event.preventDefault()
|
||||
event.returnValue = $t('flowCanvas.confirmLeave')
|
||||
return $t('flowCanvas.confirmLeave')
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
// 添加事件监听
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
exportFlow
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -38,6 +38,7 @@ export class EventManager {
|
||||
}
|
||||
})
|
||||
this.stateManager.recordState()
|
||||
this.stateManager.exportFlow(this.stateManager.saveCanvasTimeInterval)
|
||||
}
|
||||
/** 处理点击 */
|
||||
handleClick(event: any) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { ref, computed } from "vue";
|
||||
import { NODE_TYPE, NODE_DATATYPE } from '../tools/index.d'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import i18n from '@/lang'
|
||||
import { putSketchFlowCanvas } from '@/api/flow-canvas'
|
||||
|
||||
const t = i18n.global.t
|
||||
|
||||
export interface NodesItem {
|
||||
@@ -37,8 +39,12 @@ export class StateManager {
|
||||
toolManager: any
|
||||
generateManager: any
|
||||
|
||||
// 是否有数据没保存
|
||||
isSave: any
|
||||
// 保存画布数据定时器
|
||||
saveCanvasTime: any
|
||||
// 保存画布数据定时器时间间隔
|
||||
saveCanvasTimeInterval: any
|
||||
// 打开画布线稿id
|
||||
sketchId: any
|
||||
// 设置管理器
|
||||
setManager(options) {
|
||||
options.eventManager && (this.eventManager = options.eventManager)
|
||||
@@ -57,7 +63,10 @@ export class StateManager {
|
||||
this.mxHistory = ref(50)
|
||||
this.historyList = ref([])
|
||||
this.historyIndex = ref(0)
|
||||
this.isSave = ref(false)
|
||||
|
||||
this.sketchId = options.sketchId
|
||||
this.saveCanvasTimeInterval = 6000
|
||||
this.saveCanvasTime = null
|
||||
|
||||
this.activeNodeID = ref("")
|
||||
this.nodes = ref<NodesItem[]>([]);
|
||||
@@ -100,8 +109,6 @@ export class StateManager {
|
||||
})
|
||||
return arr
|
||||
})
|
||||
window.nodes = this.nodes
|
||||
window.aaa = this
|
||||
|
||||
}
|
||||
/** 设置激活节点 */
|
||||
@@ -110,6 +117,7 @@ export class StateManager {
|
||||
addNode(node: NodesItem) {
|
||||
this.nodes.value.push(node);
|
||||
this.recordState()
|
||||
this.exportFlow()
|
||||
}
|
||||
/** 删除节点 */
|
||||
async deleteNode(id: string, { isElMessageBox } = { isElMessageBox: false }) {
|
||||
@@ -136,6 +144,7 @@ export class StateManager {
|
||||
if (!deletePromise) return console.log('删除操作被取消')
|
||||
this.nodes.value = this.nodes.value.filter((node: NodesItem) => node.id !== id)
|
||||
this.recordState()
|
||||
this.exportFlow()
|
||||
}
|
||||
/** 获取节点 */
|
||||
getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) }
|
||||
@@ -176,8 +185,22 @@ export class StateManager {
|
||||
const size = this.historyList.value.length - this.mxHistory.value
|
||||
if (size > 0) this.historyList.value.splice(0, size)
|
||||
this.historyIndex.value = this.historyList.value.length - 1
|
||||
|
||||
this.isSave.value = true
|
||||
}
|
||||
/** 画布数据存储 */
|
||||
async exportFlow (time:number = 0){
|
||||
if(!this.sketchId)return
|
||||
clearTimeout(this.saveCanvasTime)
|
||||
await new Promise((resolve) => {
|
||||
this.saveCanvasTime = setTimeout(()=>{
|
||||
putSketchFlowCanvas({
|
||||
id: this.sketchId,
|
||||
canvasData: JSON.stringify(this.nodes.value) }).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
resolve(true)
|
||||
})
|
||||
},time)
|
||||
})
|
||||
}
|
||||
/** 撤回状态 */
|
||||
undoState() {
|
||||
@@ -186,6 +209,7 @@ export class StateManager {
|
||||
if (!state) return
|
||||
this.historyIndex.value = index
|
||||
this.nodes.value = JSON.parse(state.nodes)
|
||||
this.exportFlow(this.saveCanvasTimeInterval)
|
||||
}
|
||||
/** 重做状态 */
|
||||
redoState() {
|
||||
@@ -194,6 +218,7 @@ export class StateManager {
|
||||
if (!state) return
|
||||
this.historyIndex.value = index
|
||||
this.nodes.value = JSON.parse(state.nodes)
|
||||
this.exportFlow(this.saveCanvasTimeInterval)
|
||||
}
|
||||
|
||||
/** 显示指定子节点和父节点连接线,隐藏父节点和其他子节点链接线, */
|
||||
@@ -211,7 +236,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.isSave.value = false
|
||||
clearTimeout(this.saveCanvasTime)
|
||||
this.historyList.value = []
|
||||
this.historyIndex.value = 0
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ export const downloadImage = (url: string, name: string) => {
|
||||
}
|
||||
|
||||
/** 批量下载图片 */
|
||||
export const downImgListToZip = async (imagesParams) => {
|
||||
export const downImgListToZip = async (imagesParams,callback) => {
|
||||
const zip = new JSZip()
|
||||
const promises = []
|
||||
// 遍历下载每个图片
|
||||
let progress = 0
|
||||
imagesParams.forEach((img, index) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
@@ -32,18 +33,17 @@ export const downImgListToZip = async (imagesParams) => {
|
||||
if (xhr.status === 200) {
|
||||
const fileName = img.name
|
||||
zip.file(fileName, xhr.response)
|
||||
progress++
|
||||
callback(progress)
|
||||
resolve('')
|
||||
} else {
|
||||
reject(new Error(`下载失败: ${img.url}`))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => reject(new Error(`网络错误: ${img.url}`))
|
||||
xhr.send()
|
||||
})
|
||||
|
||||
promises.push(promise)
|
||||
console.log(promises,zip)
|
||||
})
|
||||
|
||||
// 等待所有图片下载完成
|
||||
@@ -56,7 +56,25 @@ export const downImgListToZip = async (imagesParams) => {
|
||||
link.href = URL.createObjectURL(content)
|
||||
link.download = 'DesignFiles'
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
// URL.revokeObjectURL(link.href)
|
||||
})
|
||||
.catch((error) => console.error('下载失败:', error))
|
||||
}
|
||||
|
||||
/** base64转二进制 */
|
||||
export const base64Tofile = (base64: string,name: string) => {
|
||||
const arr = base64.split(',')
|
||||
const mime = arr[0].match(/:(.*?);/)[1]
|
||||
const bstr = atob(arr[1])
|
||||
// 2. 转换为 Uint8Array
|
||||
let n = bstr.length
|
||||
const u8arr = new Uint8Array(n)
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n)
|
||||
}
|
||||
// 3. 创建 Blob
|
||||
const blob = new Blob([u8arr], { type: mime })
|
||||
// 4. 创建 File 对象(可选)
|
||||
const file = new File([blob], name, { type: mime })
|
||||
return file
|
||||
}
|
||||
15
src/main.ts
15
src/main.ts
@@ -30,6 +30,19 @@ app.use(router)
|
||||
.use(i18n)
|
||||
.mount('#app')
|
||||
|
||||
|
||||
const vLoadimg = (el, binding) => {
|
||||
const src = binding.value
|
||||
if (el.src === src) return
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
img.onload = () => {
|
||||
el.src = src
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.log('图片加载失败:', src)
|
||||
}
|
||||
}
|
||||
// 注册
|
||||
app.directive('loadimg', vLoadimg)
|
||||
flexible();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user