Skip to content

Commit

Permalink
feat((signature)): signature组件小程序端支持导出图片
Browse files Browse the repository at this point in the history
  • Loading branch information
yang1206 committed Nov 16, 2023
1 parent 1933f11 commit 2992d39
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 194 deletions.
97 changes: 50 additions & 47 deletions docs/components/business/signature.md
@@ -1,80 +1,83 @@
# Signature 签名 <Badge type="warning">H5</Badge>
# Signature 签名

### 介绍

基于 Canvas 的签名组件。默认竖屏模式使用,如使用横屏模式,请开发者自行设置旋转角度或者宽高。(目前微信小程序导出图片失败)
基于 Canvas 的签名组件。默认竖屏模式使用,如使用横屏模式,请开发者自行设置旋转角度或者宽高。

### 基础用法

```html
<template>
<nut-signature
@confirm="confirm"
@clear="clear"
></nut-signature>
<image :src="demoSignUrl" class="demoSignUrl" v-if="demoSignUrl" />
</template>
```vue
<script>
export default {
props: {},
setup() {
const demoSignUrl = ref('');
const confirm = (canvas, data) => {
if (data === '') {
console.log(canvas);
return false;
}
demoSignUrl.value = data;
console.log('图片地址', canvas, data);
};
const clear = () => {
demoSignUrl.value = '';
console.log('清除事件');
props: {},
setup() {
const demoSignUrl = ref('')
const confirm = (canvas, data) => {
if (data === '') {
console.log(canvas)
return false
}
return { confirm, clear, demoSignUrl };
demoSignUrl.value = data
console.log('图片地址', canvas, data)
}
const clear = () => {
demoSignUrl.value = ''
console.log('清除事件')
}
return { confirm, clear, demoSignUrl }
}
}
</script>
```

### 修改颜色和签字粗细
```html
<template>
<nut-signature
:lineWidth="lineWidth"
:strokeStyle="strokeStyle"
@confirm="confirm"
<nut-signature
@confirm="confirm"
@clear="clear"
></nut-signature>
<image :src="demoSignUrl" class="demoSignUrl" v-if="demoSignUrl" />
/>
<image v-if="demoSignUrl" :src="demoSignUrl" class="demoSignUrl" />
</template>
```

### 修改颜色和签字粗细

```vue
<script>
import { reactive } from 'vue';
import { reactive } from 'vue'
export default {
props: {},
setup() {
const state = reactive({
lineWidth: 4,
strokeStyle: 'green'
});
const demoSignUrl = ref('');
})
const demoSignUrl = ref('')
const confirm = (canvas, data) => {
if (data === '') {
console.log(canvas);
return false;
console.log(canvas)
return false
}
demoSignUrl.value = data;
console.log('图片地址', canvas, data);
};
demoSignUrl.value = data
console.log('图片地址', canvas, data)
}
const clear = () => {
demoSignUrl.value = '';
console.log('清除事件');
demoSignUrl.value = ''
console.log('清除事件')
}
return { ...state, demoSignUrl, confirm, clear };
return { ...state, demoSignUrl, confirm, clear }
}
};
}
</script>
<template>
<nut-signature
:line-width="lineWidth"
:stroke-style="strokeStyle"
@confirm="confirm"
@clear="clear"
/>
<image v-if="demoSignUrl" :src="demoSignUrl" class="demoSignUrl" />
</template>
```

## API
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/faq.md
Expand Up @@ -16,7 +16,7 @@ uniapp在小程序环境组件的 class 和 style 不会被成功编译,如果
:::

::: details 某些组件在小程序有一些问题,仅支持 H5
tour,signature等这些在文档中有标注 <Badge type="warning">H5</Badge> 的组件,代表仅支持 H5
tour等这些在文档中有标注 <Badge type="warning">H5</Badge> 的组件,代表仅支持 H5
:::

::: details 为什么使用某些插槽需要传递一个 props 属性,如`calendar`组件的footer 插槽
Expand Down
2 changes: 1 addition & 1 deletion example/config.ts
Expand Up @@ -1190,7 +1190,7 @@ export default {
cName: '签名',
desc: '签名组件',
sort: 3,
show: !!isH5,
show: true,
author: 'guoxiaoxiao',
},
{
Expand Down
160 changes: 15 additions & 145 deletions packages/nutui/components/signature/signature.vue
@@ -1,135 +1,27 @@
<script setup lang="ts">
import { type ComponentInternalInstance, computed, defineComponent, getCurrentInstance, onMounted, reactive, ref } from 'vue'
import { CLEAR_EVENT, CONFIRM_EVENT, PREFIX } from '../_constants'
import { defineComponent } from 'vue'
import NutButton from '../button/button.vue'
import { useTranslate } from '../../locale'
import { useSelectorQuery } from '../_hooks'
import { signatureEmits, signatureProps } from './signature'
import { componentName, useSignature } from './use-signature'
const props = defineProps(signatureProps)
const emit = defineEmits(signatureEmits)
const instance = getCurrentInstance() as ComponentInternalInstance
const { query } = useSelectorQuery(instance.proxy as any)
let points = reactive<any[]>([]) // 路径点集合
const classes = computed(() => {
const prefixCls = componentName
return {
[prefixCls]: true,
[`${props.customClass}`]: props.customClass,
}
})
const spcanvas: any = ref<HTMLElement | null>(null)
const canvasSetId: any = `spcanvas${new Date().getTime()}`
const state = reactive({
canvasHeight: 0,
canvasWidth: 0,
ctx: null as unknown as UniNamespace.CanvasContext,
})
function startEventHandler(event: any) {
event.preventDefault()
const startX = event.changedTouches[0].x
const startY = event.changedTouches[0].y
const startPoint = { X: startX, Y: startY }
points.push(startPoint)
// 每次触摸开始,开启新的路径
emit('start')
state.ctx.beginPath()
state.ctx.lineWidth = props.lineWidth
state.ctx.strokeStyle = props.strokeStyle
}
const isDraw = ref(false)
function moveEventHandler(event: any) {
event.preventDefault()
isDraw.value = true
const moveX = event.changedTouches[0].x
const moveY = event.changedTouches[0].y
const movePoint = { X: moveX, Y: moveY }
points.push(movePoint) // 存点
const len = points.length
if (len >= 2)
draw()
emit('signing', event)
}
// 绘制路径
function draw() {
const point1 = points[0]
const point2 = points[1]
points.shift()
state.ctx.moveTo(point1.X, point1.Y)
state.ctx.lineTo(point2.X, point2.Y)
state.ctx.stroke()
state.ctx.draw(true)
}
function endEventHandler(event: any) {
event.preventDefault()
points = []
emit('end')
}
function leaveEventHandler(event: any) {
event.preventDefault()
}
function clear() {
state.ctx.clearRect(0, 0, state.canvasWidth, state.canvasHeight)
state.ctx.closePath()
state.ctx.draw(true)
emit(CLEAR_EVENT)
isDraw.value = false
}
function confirm() {
onSave()
}
function onSave() {
// TODO 微信小程序无法导出图片
state.ctx.draw(true, () => {
uni.canvasToTempFilePath({
canvas: state.ctx,
canvasId: canvasSetId,
width: state.canvasWidth,
height: state.canvasHeight,
fileType: props.type,
success(result) {
const _canvas = !isDraw.value ? '请绘制签名' : state.ctx
const _filePath = !isDraw.value ? '' : result.tempFilePath
emit(CONFIRM_EVENT, _canvas, _filePath)
},
fail(result) {
emit(CONFIRM_EVENT, result)
},
})
})
}
onMounted(() => {
query.select(`#${canvasSetId}`)
.fields({ size: true }, (res: any) => {
canvasSetting(res.width, res.height)
})
.exec()
})
function canvasSetting(width: number, height: number) {
const dpr = uni.getSystemInfoSync().pixelRatio
state.ctx = uni.createCanvasContext(canvasSetId, instance.proxy)
state.canvasWidth = width * dpr
state.canvasHeight = height * dpr
}
const {
classes,
spcanvas,
canvasSetId,
startEventHandler,
moveEventHandler,
endEventHandler,
leaveEventHandler,
clear,
confirm,
} = useSignature(props, emit)
</script>

<script lang="ts">
const componentName = `${PREFIX}-signature`
const { translate } = useTranslate(componentName)
export default defineComponent({
name: componentName,
Expand All @@ -145,33 +37,11 @@ export default defineComponent({
<template>
<div :class="classes">
<div class="nut-signature-inner spcanvas_WEAPP">
<!-- #ifdef MP-WEIXIN || MP-ALIPAY || MP-QQ || APP-NVUE -->
<canvas
:id="canvasSetId"
ref="spcanvas"
class="spcanvas"
type="2d"
:canvasId="canvasSetId"
:disable-scroll="true"
@touchstart="startEventHandler"
@touchmove="moveEventHandler"
@touchend="endEventHandler"
@touchleave="leaveEventHandler"
/>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-ALIPAY || MP-QQ || APP-NVUE -->
<canvas
:id="canvasSetId"
ref="spcanvas"
class="spcanvas"
:canvasId="canvasSetId"
:disable-scroll="true"
@touchstart="startEventHandler"
@touchmove="moveEventHandler"
@touchend="endEventHandler"
:id="canvasSetId" ref="spcanvas" class="spcanvas" :canvasId="canvasSetId" :disable-scroll="true"
@touchstart="startEventHandler" @touchmove="moveEventHandler" @touchend="endEventHandler"
@touchleave="leaveEventHandler"
/>
<!-- #endif -->
</div>
<NutButton custom-class="nut-signature-btn" type="default" @click="clear()">
{{ translate('reSign') }}
Expand Down

1 comment on commit 2992d39

@vercel
Copy link

@vercel vercel bot commented on 2992d39 Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.