| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- <template>
- <view v-if="visible" class="signature-landscape">
- <!-- 左侧操作栏 -->
- <view class="left-toolbar">
- <view class="toolbar-item" @click="handleRewrite">重写</view>
- <view class="toolbar-item" @click="handleSave">保存</view>
- <view class="toolbar-item" @click="handlePreview">预览</view>
- <view class="toolbar-item" @click="handleUpload">上传</view>
- <view class="back-btn" @click="handleBack">返回</view>
- </view>
- <!-- 签名画布区域 -->
- <view class="canvas-area">
- <canvas
- class="signature-canvas"
- canvas-id="landscapeSignCanvas"
- @touchstart="onTouchStart"
- @touchmove="onTouchMove"
- @touchend="onTouchEnd"
- @touchcancel="onTouchEnd"
- ></canvas>
- <text class="placeholder">请签名</text>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, watch } from 'vue'
- const props = defineProps({
- visible: { type: Boolean, default: false }
- })
- const emit = defineEmits(['back', 'save', 'preview', 'upload'])
- // 画布状态
- let ctx = null
- let canvasWidth = 0
- let canvasHeight = 0
- let lastX = 0
- let lastY = 0
- const hasSign = ref(false)
- let isDrawing = ref(false)
- let retryCount = 0
- const MAX_RETRY = 5
- // 监听显示,初始化画布
- watch(() => props.visible, (val) => {
- if (val) {
- hasSign.value = false
- isDrawing.value = false
- retryCount = 0
- setTimeout(() => {
- initCanvas()
- }, 300)
- }
- })
- // 初始化画布
- function initCanvas() {
- if (retryCount >= MAX_RETRY) {
- uni.showToast({ title: '画布加载失败', icon: 'none' })
- return
- }
- const query = uni.createSelectorQuery()
- query.select('.signature-canvas').boundingClientRect()
- query.exec((res) => {
- if (!res || !res[0]) {
- retryCount++
- setTimeout(initCanvas, 200)
- return
- }
- const rect = res[0]
- canvasWidth = rect.width
- canvasHeight = rect.height
- // 初始化画笔
- ctx = uni.createCanvasContext('landscapeSignCanvas')
- ctx.setLineWidth(3)
- ctx.setStrokeStyle('#000000')
- ctx.setLineCap('round')
- ctx.setLineJoin('round')
- ctx.draw()
- })
- }
- // 触摸开始
- function onTouchStart(e) {
- if (!ctx) return
- isDrawing.value = true
- const touch = e.touches[0]
- lastX = touch.x
- lastY = touch.y
- ctx.beginPath()
- ctx.moveTo(lastX, lastY)
- }
- // 触摸移动
- function onTouchMove(e) {
- if (!isDrawing.value || !ctx) return
- const touch = e.touches[0]
- const x = touch.x
- const y = touch.y
- ctx.lineTo(x, y)
- ctx.stroke()
- ctx.draw(true)
- lastX = x
- lastY = y
- hasSign.value = true
- }
- // 触摸结束
- function onTouchEnd() {
- isDrawing.value = false
- }
- // 重写:清空画布
- function handleRewrite() {
- if (!ctx) return
- ctx.clearRect(0, 0, canvasWidth, canvasHeight)
- ctx.draw()
- hasSign.value = false
- }
- // 保存:生成签名图片
- function handleSave() {
- if (!hasSign.value) {
- uni.showToast({ title: '请先签名', icon: 'none' })
- return
- }
- uni.canvasToTempFilePath({
- canvasId: 'landscapeSignCanvas',
- success: (res) => {
- emit('save', res.tempFilePath)
- uni.showToast({ title: '保存成功', icon: 'success' })
- },
- fail: () => {
- uni.showToast({ title: '保存失败', icon: 'none' })
- }
- })
- }
- // 预览:弹窗预览签名
- function handlePreview() {
- if (!hasSign.value) {
- uni.showToast({ title: '请先签名', icon: 'none' })
- return
- }
- uni.canvasToTempFilePath({
- canvasId: 'landscapeSignCanvas',
- success: (res) => {
- emit('preview', res.tempFilePath)
- // 这里可以加预览弹窗,示例直接展示
- uni.previewImage({
- urls: [res.tempFilePath]
- })
- }
- })
- }
- // 上传:上传签名图片
- function handleUpload() {
- if (!hasSign.value) {
- uni.showToast({ title: '请先签名', icon: 'none' })
- return
- }
- uni.canvasToTempFilePath({
- canvasId: 'landscapeSignCanvas',
- success: (res) => {
- emit('upload', res.tempFilePath)
- uni.showToast({ title: '上传中...', icon: 'loading' })
- // 这里写你的上传接口逻辑
- }
- })
- }
- // 返回:关闭组件
- function handleBack() {
- handleRewrite()
- emit('back')
- }
- </script>
- <style scoped>
- /* 横屏容器:全屏横向布局 */
- .signature-landscape {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: #ffffff;
- z-index: 999999;
- display: flex;
- flex-direction: row;
- overflow: hidden;
- }
- /* 左侧操作栏 */
- .left-toolbar {
- width: 120rpx;
- height: 100vh;
- background: #f5f5f5;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 40rpx 0;
- box-sizing: border-box;
- gap: 30rpx;
- }
- .toolbar-item {
- width: 100%;
- text-align: center;
- font-size: 32rpx;
- color: #333;
- padding: 20rpx 0;
- box-sizing: border-box;
- }
- .back-btn {
- margin-top: auto;
- width: 100rpx;
- height: 100rpx;
- background: #007aff;
- color: #fff;
- border-radius: 10rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- }
- /* 签名画布区域 */
- .canvas-area {
- flex: 1;
- position: relative;
- margin: 40rpx;
- border: 2rpx dashed #ccc;
- box-sizing: border-box;
- }
- .signature-canvas {
- width: 100%;
- height: 100%;
- display: block;
- }
- /* 占位文字:右下角 */
- .placeholder {
- position: absolute;
- right: 30rpx;
- bottom: 30rpx;
- font-size: 32rpx;
- color: #999;
- transform: rotate(90deg);
- transform-origin: right bottom;
- }
- </style>
|