|
|
@@ -26,12 +26,13 @@
|
|
|
class="hidden-canvas"
|
|
|
id="signature-rotate-canvas"
|
|
|
canvas-id="signature-rotate-canvas"
|
|
|
+ :style="rotateCanvasStyle"
|
|
|
/>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { getCurrentInstance, nextTick, onMounted, ref } from 'vue'
|
|
|
+import { computed, type CSSProperties, getCurrentInstance, nextTick, onMounted, ref } from 'vue'
|
|
|
|
|
|
const CANVAS_ID = 'signature-canvas'
|
|
|
const ROTATE_CANVAS_ID = 'signature-rotate-canvas'
|
|
|
@@ -47,10 +48,17 @@ const instance = getCurrentInstance()
|
|
|
const ctx = ref<UniApp.CanvasContext | null>(null)
|
|
|
const canvasWidth = ref(0)
|
|
|
const canvasHeight = ref(0)
|
|
|
+const rotateCanvasWidth = ref(1)
|
|
|
+const rotateCanvasHeight = ref(1)
|
|
|
|
|
|
const drawing = ref(false)
|
|
|
const lastPoint = ref<{ x: number; y: number } | null>(null)
|
|
|
|
|
|
+const rotateCanvasStyle = computed<CSSProperties>(() => ({
|
|
|
+ width: `${rotateCanvasWidth.value}px`,
|
|
|
+ height: `${rotateCanvasHeight.value}px`,
|
|
|
+}))
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
await nextTick()
|
|
|
|
|
|
@@ -123,59 +131,118 @@ const handleClear = () => {
|
|
|
}
|
|
|
|
|
|
const handleSubmit = () => {
|
|
|
+ if (!ctx.value) {
|
|
|
+ emit('error', new Error('签名画布未就绪'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
uni.showLoading({ title: '生成签名中' })
|
|
|
|
|
|
- uni.canvasToTempFilePath(
|
|
|
- {
|
|
|
- canvasId: CANVAS_ID,
|
|
|
- fileType: 'png',
|
|
|
- quality: 1,
|
|
|
- success(res) {
|
|
|
- rotateImage(res.tempFilePath)
|
|
|
- },
|
|
|
- fail(err) {
|
|
|
- uni.hideLoading()
|
|
|
- emit('error', err)
|
|
|
+ // 等待最后一笔真正写入视图层后再导出,避免快速提交时拿到空画布。
|
|
|
+ ctx.value.draw(true, () => {
|
|
|
+ uni.canvasToTempFilePath(
|
|
|
+ {
|
|
|
+ canvasId: CANVAS_ID,
|
|
|
+ fileType: 'png',
|
|
|
+ quality: 1,
|
|
|
+ success(res) {
|
|
|
+ rotateImage(res.tempFilePath)
|
|
|
+ },
|
|
|
+ fail(err) {
|
|
|
+ uni.hideLoading()
|
|
|
+ emit('error', err)
|
|
|
+ },
|
|
|
},
|
|
|
- },
|
|
|
- instance?.proxy as any
|
|
|
- )
|
|
|
+ instance?.proxy as any
|
|
|
+ )
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const waitForRotateCanvasReady = (width: number, height: number): Promise<void> => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const query = uni.createSelectorQuery()
|
|
|
+ if (instance?.proxy) query.in(instance.proxy)
|
|
|
+
|
|
|
+ query
|
|
|
+ .select(`#${ROTATE_CANVAS_ID}`)
|
|
|
+ .boundingClientRect((rect) => {
|
|
|
+ if (
|
|
|
+ !rect ||
|
|
|
+ Array.isArray(rect) ||
|
|
|
+ typeof rect.width !== 'number' ||
|
|
|
+ typeof rect.height !== 'number' ||
|
|
|
+ Math.abs(rect.width - width) > 1 ||
|
|
|
+ Math.abs(rect.height - height) > 1
|
|
|
+ ) {
|
|
|
+ reject(new Error('签名画布尺寸未就绪'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve()
|
|
|
+ })
|
|
|
+ .exec()
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
const rotateImage = (src: string) => {
|
|
|
uni.getImageInfo({
|
|
|
src,
|
|
|
- success(info) {
|
|
|
- const rotateCtx = uni.createCanvasContext(ROTATE_CANVAS_ID, instance?.proxy as any)
|
|
|
-
|
|
|
- const targetHeight = 300
|
|
|
- const ratio = info.height / info.width
|
|
|
- const targetWidth = targetHeight / ratio
|
|
|
-
|
|
|
- rotateCtx.translate(targetHeight / 2, targetWidth / 2)
|
|
|
- rotateCtx.rotate((270 * Math.PI) / 180)
|
|
|
- rotateCtx.drawImage(src, -targetWidth / 2, -targetHeight / 2, targetWidth, targetHeight)
|
|
|
-
|
|
|
- rotateCtx.draw(false, () => {
|
|
|
- uni.canvasToTempFilePath(
|
|
|
- {
|
|
|
- canvasId: ROTATE_CANVAS_ID,
|
|
|
- width: targetHeight,
|
|
|
- height: targetWidth,
|
|
|
- fileType: 'png',
|
|
|
- quality: 1,
|
|
|
- success(res) {
|
|
|
- uni.hideLoading()
|
|
|
- emit('submit', res.tempFilePath)
|
|
|
- },
|
|
|
- fail(err) {
|
|
|
- uni.hideLoading()
|
|
|
- emit('error', err)
|
|
|
- },
|
|
|
- },
|
|
|
- instance?.proxy as any
|
|
|
+ async success(info) {
|
|
|
+ const outputWidth = 300
|
|
|
+ const outputHeight = Math.max(1, Math.round((outputWidth * info.width) / info.height))
|
|
|
+
|
|
|
+ try {
|
|
|
+ rotateCanvasWidth.value = outputWidth
|
|
|
+ rotateCanvasHeight.value = outputHeight
|
|
|
+ await nextTick()
|
|
|
+ await waitForRotateCanvasReady(outputWidth, outputHeight)
|
|
|
+
|
|
|
+ const rotateCtx = uni.createCanvasContext(ROTATE_CANVAS_ID, instance?.proxy as any)
|
|
|
+
|
|
|
+ rotateCtx.setFillStyle('#ffffff')
|
|
|
+ rotateCtx.fillRect(0, 0, outputWidth, outputHeight)
|
|
|
+ rotateCtx.translate(outputWidth / 2, outputHeight / 2)
|
|
|
+ rotateCtx.rotate((270 * Math.PI) / 180)
|
|
|
+ rotateCtx.drawImage(
|
|
|
+ info.path || src,
|
|
|
+ -outputHeight / 2,
|
|
|
+ -outputWidth / 2,
|
|
|
+ outputHeight,
|
|
|
+ outputWidth
|
|
|
)
|
|
|
- })
|
|
|
+
|
|
|
+ rotateCtx.draw(false, () => {
|
|
|
+ uni.canvasToTempFilePath(
|
|
|
+ {
|
|
|
+ canvasId: ROTATE_CANVAS_ID,
|
|
|
+ width: outputWidth,
|
|
|
+ height: outputHeight,
|
|
|
+ destWidth: outputWidth,
|
|
|
+ destHeight: outputHeight,
|
|
|
+ fileType: 'png',
|
|
|
+ quality: 1,
|
|
|
+ success(res) {
|
|
|
+ rotateCanvasWidth.value = 1
|
|
|
+ rotateCanvasHeight.value = 1
|
|
|
+ uni.hideLoading()
|
|
|
+ emit('submit', res.tempFilePath)
|
|
|
+ },
|
|
|
+ fail(err) {
|
|
|
+ rotateCanvasWidth.value = 1
|
|
|
+ rotateCanvasHeight.value = 1
|
|
|
+ uni.hideLoading()
|
|
|
+ emit('error', err)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ instance?.proxy as any
|
|
|
+ )
|
|
|
+ })
|
|
|
+ } catch (err) {
|
|
|
+ rotateCanvasWidth.value = 1
|
|
|
+ rotateCanvasHeight.value = 1
|
|
|
+ uni.hideLoading()
|
|
|
+ emit('error', err)
|
|
|
+ }
|
|
|
},
|
|
|
fail(err) {
|
|
|
uni.hideLoading()
|
|
|
@@ -252,8 +319,7 @@ const rotateImage = (src: string) => {
|
|
|
position: fixed;
|
|
|
left: -9999px;
|
|
|
top: -9999px;
|
|
|
- width: 1px;
|
|
|
- height: 1px;
|
|
|
+ pointer-events: none;
|
|
|
}
|
|
|
}
|
|
|
</style>
|