feat: convert lineguides component to composition API

This commit is contained in:
IchliebedichZhu 2024-03-05 12:37:00 +00:00
parent e87f1ebfcb
commit baa83adfa9
3 changed files with 334 additions and 92 deletions

View File

@ -0,0 +1,107 @@
<!--
* @Author: ShawnPhang
* @Date: 2021-09-28 20:06:25
* @Description: 裁剪组件
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 17:58:00
-->
<template>
<el-dialog v-model="dialogVisible" title="裁剪图片" width="80%" :before-close="handleClose" @close="cancel">
<div id="wrap" v-loading="loading" style="height: 50vh">
<img v-show="url" ref="imgBox" style="visibility: hidden" alt="imgBox" :src="url" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button :loading="loading" plain type="primary" @click="ok">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts">
import api from '@/api'
import { ElDialog } from 'element-plus'
import { ref, defineComponent, toRefs, reactive, nextTick } from 'vue'
import { useStore } from 'vuex'
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
export default defineComponent({
components: { ElDialog },
emits: ['done'],
setup(props, context) {
const store = useStore()
const state = reactive({
loading: false,
url: '',
})
const dialogVisible = ref(false)
const imgBox = ref<HTMLImageElement | any>()
let cropData: any = null
let cropper: any = null
const handleClose = (done: any) => {
done()
}
const open = async (item: any, data = {}) => {
state.loading = true
item.rawImg = item.rawImg ? item.rawImg : item.imgUrl
cropData = data
state.url = item.rawImg
store.commit('setShowMoveable', false)
dialogVisible.value = true
await nextTick()
setEdit()
}
const setEdit = () => {
cropper = new Cropper(imgBox.value, {
// aspectRatio: imgBox.value.width / imgBox.value.height,
dragMode: 'move',
viewMode: 1,
cropBoxMovable: false,
// cropBoxResizable: false,
highlight: false,
background: true,
// crop(event) {
// console.log(event);
// },
})
imgBox.value.addEventListener('ready', function() {
state.loading = false
if (this.cropper === cropper) {
cropData && cropper.setData(cropData)
}
})
}
const ok = () => {
state.loading = true
setTimeout(async () => {
const newImg = cropper.getCroppedCanvas({ maxWidth: 4096, minWidth: 4096 }).toDataURL('image/jpeg', 0.8)
const { width, height } = cropper.getCropBoxData()
const { preview_url } = await api.material.uploadBase64({ file: newImg })
context.emit('done', { newImg: preview_url, data: cropper.getData(), width, height })
cancel()
}, 100)
}
const cancel = () => {
store.commit('setShowMoveable', true)
dialogVisible.value = false
state.url = ''
cropData = null
state.loading = false
cropper.destroy()
}
return {
...toRefs(state),
dialogVisible,
handleClose,
open,
ok,
cancel,
imgBox,
}
},
})
</script>

View File

@ -0,0 +1,114 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-03-02 13:32:00
* @Description: 裁剪组件
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 13:32:00
-->
<template>
<el-dialog v-model="dialogVisible" title="裁剪图片" width="80%" :before-close="handleClose" @close="cancel">
<div id="wrap" v-loading="state.loading" style="height: 50vh">
<img v-show="state.url" ref="imgBox" style="visibility: hidden" alt="imgBox" :src="state.url" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button :loading="state.loading" plain type="primary" @click="ok">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '@/api'
import { ElDialog } from 'element-plus'
import { ref, defineEmits, reactive, nextTick, toRefs } from 'vue'
import { useStore } from 'vuex'
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
type TDoneParams = {
newImg: string,
data: Cropper.Data,
width: string,
height: string
}
type TDoneFunc = (event: "done", data: TDoneParams) => void
type TOpenItem = {
rawImg?: string
imgUrl: string
}
const store = useStore()
const state = reactive({
loading: false,
url: '',
})
const dialogVisible = ref(false)
const imgBox = ref<HTMLImageElement>()
let cropData: Cropper.Data | null
let cropper: Cropper
const emit = defineEmits<TDoneFunc>()
const handleClose = (done: () => void) => {
done()
}
const open = async (item: TOpenItem, data: Cropper.Data) => {
state.loading = true
item.rawImg = item.rawImg ? item.rawImg : item.imgUrl
cropData = data
state.url = item.rawImg
store.commit('setShowMoveable', false)
dialogVisible.value = true
await nextTick()
setEdit()
}
const setEdit = () => {
if (!imgBox || !imgBox.value) return
cropper = new Cropper(imgBox.value, {
// aspectRatio: imgBox.value.width / imgBox.value.height,
dragMode: 'move',
viewMode: 1,
cropBoxMovable: false,
// cropBoxResizable: false,
highlight: false,
background: true,
// crop(event) {
// console.log(event);
// },
})
imgBox.value.addEventListener('ready', function() {
state.loading = false
// if (this.cropper === cropper) {
cropData && cropper.setData(cropData)
// }
})
}
const ok = () => {
state.loading = true
setTimeout(async () => {
// const newImg = cropper.getCroppedCanvas({ maxWidth: 4096, minWidth: 4096 }).toDataURL('image/jpeg', 0.8)
// const { width, height } = cropper.getCropBoxData()
// const { preview_url } = await api.material.uploadBase64({ file: newImg })
// context.emit('done', { newImg: preview_url, data: cropper.getData(), width, height })
cancel()
}, 100)
}
const cancel = () => {
store.commit('setShowMoveable', true)
dialogVisible.value = false
state.url = ''
cropData = null
state.loading = false
cropper.destroy()
}
toRefs(state),
</script>

View File

@ -10,106 +10,127 @@
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { watch, defineProps } from 'vue'
import { useStore } from 'vuex'
import Guides from '@scena/guides'
import Guides, { GuideOptions } from '@scena/guides'
export default defineComponent({
props: ['show'],
setup(props) {
const store = useStore()
const container = 'page-design' // page-design out-page
let guidesTop: any = null
let guidesLeft: any = null
type TProps = {
show: boolean
}
watch(
() => props.show,
(open) => {
open ? render() : destroy()
},
)
type TSameParams = {
backgroundColor: string,
lineColor: string
textColor: string
// direction: 'start',
// height: 30,
displayDragPos: boolean,
dragPosFormat: (v: string | number) => string,
}
watch(
() => store.getters.dZoom,
() => {
changeScroll()
},
)
type TGuidesData = Guides & GuideOptions
// onMounted(() => {
// // let scrollX = 0
// // let scrollY = 0
// // window.addEventListener('resize', () => {
// // guides.resize()
// // })
// // window.addEventListener('wheel', (e) => {
// // scrollX += e.deltaX
// // scrollY += e.deltaY
// // guides.scrollGuides(scrollY)
// // guides.scroll(scrollX)
// // })
// })
const props = defineProps<TProps>()
function destroy() {
guidesTop.destroy()
guidesLeft.destroy()
guidesTop = null
guidesLeft = null
}
function render() {
const sameParams: any = {
backgroundColor: '#f9f9fa',
lineColor: '#bec2c7',
textColor: '#999999',
// direction: 'start',
// height: 30,
displayDragPos: true,
dragPosFormat: (v: any) => v + 'px',
}
guidesTop = new Guides(document.getElementById(container), {
...sameParams,
type: 'horizontal',
className: 'my-horizontal',
}).on('changeGuides', (e) => {
console.log(e, e.guides)
// const el = document.getElementById('out-page')
// const top = 20 + (el?.offsetTop || 0)
// store.commit('updateGuidelines', { horizontalGuidelines: e.guides.map((x) => x + top) })
})
const store = useStore()
const container = 'page-design' // page-design out-page
let guidesTop: TGuidesData | null = null
let guidesLeft: TGuidesData | null = null
guidesLeft = new Guides(document.getElementById(container), {
...sameParams,
type: 'vertical',
className: 'my-vertical',
}).on('changeGuides', (e) => {
console.log(e, e.guides)
// store.commit('updateGuidelines', { verticalGuidelines: e.guides })
})
changeScroll()
}
function changeScroll() {
if (guidesTop && guidesLeft) {
const zoom = store.getters.dZoom / 100
guidesTop.zoom = zoom
guidesLeft.zoom = zoom
if (zoom < 0.9) {
guidesTop.unit = Math.floor(1 / zoom) * 50
guidesLeft.unit = Math.floor(1 / zoom) * 50
}
setTimeout(() => {
const el = document.getElementById('out-page')
const left = 60 + (el?.offsetLeft || 0)
const top = 30 + (el?.offsetTop || 0)
guidesTop.scroll(-left / zoom)
guidesTop.scrollGuides(-top / zoom)
guidesLeft.scroll(-top / zoom)
guidesLeft.scrollGuides(-(left - 30) / zoom)
}, 300)
}
}
watch(
() => props.show,
(open) => {
open ? render() : destroy()
},
})
)
watch(
() => store.getters.dZoom,
() => {
changeScroll()
},
)
// onMounted(() => {
// // let scrollX = 0
// // let scrollY = 0
// // window.addEventListener('resize', () => {
// // guides.resize()
// // })
// // window.addEventListener('wheel', (e) => {
// // scrollX += e.deltaX
// // scrollY += e.deltaY
// // guides.scrollGuides(scrollY)
// // guides.scroll(scrollX)
// // })
// })
function destroy() {
guidesTop?.destroy()
guidesLeft?.destroy()
guidesTop = null
guidesLeft = null
}
function render() {
const sameParams: TSameParams = {
backgroundColor: '#f9f9fa',
lineColor: '#bec2c7',
textColor: '#999999',
// direction: 'start',
// height: 30,
displayDragPos: true,
dragPosFormat: (v) => v + 'px',
}
const containerEl = document.getElementById(container)
if (!containerEl) return
guidesTop = new Guides(containerEl, {
...sameParams,
type: 'horizontal',
className: 'my-horizontal',
}).on('changeGuides', (e) => {
console.log(e, e.guides)
// const el = document.getElementById('out-page')
// const top = 20 + (el?.offsetTop || 0)
// store.commit('updateGuidelines', { horizontalGuidelines: e.guides.map((x) => x + top) })
})
guidesLeft = new Guides(containerEl, {
...sameParams,
type: 'vertical',
className: 'my-vertical',
}).on('changeGuides', (e) => {
console.log(e, e.guides)
// store.commit('updateGuidelines', { verticalGuidelines: e.guides })
})
changeScroll()
}
function changeScroll() {
if (guidesTop && guidesLeft) {
const zoom = store.getters.dZoom / 100
guidesTop.zoom = zoom
guidesLeft.zoom = zoom
if (zoom < 0.9) {
guidesTop.unit = Math.floor(1 / zoom) * 50
guidesLeft.unit = Math.floor(1 / zoom) * 50
}
setTimeout(() => {
if (guidesTop && guidesLeft) {
const el = document.getElementById('out-page')
const left = 60 + (el?.offsetLeft ?? 0)
const top = 30 + (el?.offsetTop ?? 0)
guidesTop.scroll(-left / zoom)
guidesTop.scrollGuides(-top / zoom)
guidesLeft.scroll(-top / zoom)
guidesLeft.scrollGuides(-(left - 30) / zoom)
}
}, 300)
}
}
</script>
<style lang="less">