| <template> | 
|   <view v-if="show" class="tn-sign-board-class tn-sign-board" :style="{top: `${customBarHeight}px`, height: `calc(100% - ${customBarHeight}px)`}"> | 
|     <!-- 签名canvas --> | 
|     <view class="tn-sign-board__content"> | 
|       <view class="tn-sign-board__content__wrapper"> | 
|         <canvas class="tn-sign-board__content__canvas" :canvas-id="canvasName" :disableScroll="true" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas> | 
|       </view> | 
|     </view> | 
|      | 
|     <!-- 底部工具栏 --> | 
|     <view class="tn-sign-board__tools"> | 
|       <!-- 可选颜色 --> | 
|       <view class="tn-sign-board__tools__color"> | 
|         <view | 
|           v-for="(item, index) in signSelectColor" | 
|           :key="index" | 
|           class="tn-sign-board__tools__color__item" | 
|           :class="[{'tn-sign-board__tools__color__item--active': currentSelectColor === item}]" | 
|           :style="{backgroundColor: item}" | 
|           @tap="colorSwitch(item)" | 
|         ></view> | 
|       </view> | 
|        | 
|       <!-- 按钮 --> | 
|       <view class="tn-sign-board__tools__button"> | 
|         <view class="tn-sign-board__tools__button__item tn-bg-red" @tap="reDraw">清除</view> | 
|         <view class="tn-sign-board__tools__button__item tn-bg-blue" @tap="save">保存</view> | 
|         <view class="tn-sign-board__tools__button__item tn-bg-indigo" @tap="previewImage">预览</view> | 
|         <view class="tn-sign-board__tools__button__item tn-bg-orange" @tap="closeBoard">关闭</view> | 
|       </view> | 
|     </view> | 
|      | 
|     <!-- 伪全屏生成旋转图片canvas容器,不在页面上展示 --> | 
|     <view style="position: fixed; left: -2000px;width: 0;height: 0;overflow: hidden;"> | 
|       <canvas canvas-id="temp-tn-sign-canvas" :style="{width: `${canvasHeight}px`, height: `${canvasHeight}px`}"></canvas> | 
|     </view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   export default { | 
|     name: 'tn-sign-board', | 
|     props: { | 
|       // 是否显示 | 
|       show: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 可选签名颜色 | 
|       signSelectColor: { | 
|         type: Array, | 
|         default() { | 
|           return ['#080808', '#E83A30'] | 
|         } | 
|       }, | 
|       // 是否旋转输出图片 | 
|       rotate: { | 
|         type: Boolean, | 
|         default: true | 
|       }, | 
|       // 自定义顶栏的高度 | 
|       customBarHeight: { | 
|         type: [String, Number], | 
|         default: 0 | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         canvasName: 'tn-sign-canvas', | 
|         ctx: null, | 
|         canvasWidth: 0, | 
|         canvasHeight: 0, | 
|         currentSelectColor: this.signSelectColor[0], | 
|         // 第一次触摸 | 
|         firstTouch: false, | 
|         // 透明度 | 
|         transparent: 1, | 
|         // 笔迹倍数 | 
|         lineSize: 1.5, | 
|         // 最小画笔半径 | 
|         minLine: 0.5, | 
|         // 最大画笔半径 | 
|         maxLine: 4, | 
|         // 画笔压力 | 
|         pressure: 1, | 
|         // 顺滑度,用60的距离来计算速度 | 
|         smoothness: 60, | 
|         // 当前触摸的点 | 
|         currentPoint: {}, | 
|         // 当前线条 | 
|         currentLine: [], | 
|         // 画笔圆半径 | 
|         radius: 1, | 
|         // 裁剪区域 | 
|         cutArea: { | 
|           top: 0, | 
|           right: 0, | 
|           bottom: 0, | 
|           left: 0 | 
|         }, | 
|         // 所有线条, 生成贝塞尔点 | 
|         // bethelPoint: [], | 
|         // 上一个点 | 
|         lastPoint: 0, | 
|         // 笔迹 | 
|         chirography: [], | 
|         // 当前笔迹 | 
|         // currentChirography: {}, | 
|         // 画线轨迹,生成线条的实际点 | 
|         linePrack: [] | 
|       } | 
|     }, | 
|     watch: { | 
|       show(value) { | 
|         if (value && this.canvasWidth === 0 && this.canvasHeight === 0) { | 
|           this.$nextTick(() => { | 
|             this.getCanvasInfo() | 
|           }) | 
|         } | 
|       }, | 
|       signSelectColor(value) { | 
|         if (value.length > 0) { | 
|           this.currentSelectColor = value[0] | 
|         } | 
|       } | 
|     }, | 
|     created() { | 
|       // 创建canvas | 
|       this.ctx = uni.createCanvasContext(this.canvasName, this) | 
|     }, | 
|     mounted() { | 
|       // 获取画板的相关信息 | 
|       // this.$nextTick(() => { | 
|       //   this.getCanvasInfo() | 
|       // }) | 
|     }, | 
|     methods: { | 
|       // 获取画板的相关信息 | 
|       getCanvasInfo() { | 
|         this._tGetRect('.tn-sign-board__content__canvas').then(res => { | 
|          this.canvasWidth = res.width | 
|          this.canvasHeight = res.height | 
|           | 
|          // 初始化Canvas | 
|          this.$nextTick(() => { | 
|            this.initCanvas('#FFFFFF') | 
|          }) | 
|         }) | 
|       }, | 
|       // 初始化Canvas | 
|       initCanvas(color) { | 
|         /* 将canvas背景设置为 白底,不设置  导出的canvas的背景为透明 */ | 
|         // rect() 参数说明  矩形路径左上角的横坐标,左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度 | 
|         // 矩形的宽高需要减去边框的宽度 | 
|         this.ctx.rect(0, 0, this.canvasWidth - uni.upx2px(4), this.canvasHeight - uni.upx2px(4)) | 
|         this.ctx.setFillStyle(color) | 
|         this.ctx.fill() | 
|         this.ctx.draw() | 
|       }, | 
|       // 开始画 | 
|       onTouchStart(e) { | 
|         if (e.type != 'touchstart') return false | 
|          | 
|         // 设置线条颜色 | 
|         this.ctx.setFillStyle(this.currentSelectColor) | 
|         // 设置透明度 | 
|         this.ctx.setGlobalAlpha(this.transparent) | 
|         let currentPoint = { | 
|           x: e.touches[0].x, | 
|           y: e.touches[0].y | 
|         } | 
|         let currentLine = this.currentLine | 
|         currentLine.unshift({ | 
|           time: new Date().getTime(), | 
|           dis: 0, | 
|           x: currentPoint.x, | 
|           y: currentPoint.y | 
|         }) | 
|         this.currentPoint = currentPoint | 
|          | 
|         if (this.firstTouch) { | 
|           this.cutArea = { | 
|             top: currentPoint.y, | 
|             right: currentPoint.x, | 
|             bottom: currentPoint.y, | 
|             left: currentPoint.x | 
|           } | 
|           this.firstTouch = false | 
|         } | 
|          | 
|         this.pointToLine(currentLine) | 
|       }, | 
|       // 正在画 | 
|       onTouchMove(e) { | 
|         if (e.type != 'touchmove') return false | 
|         if (e.cancelable) { | 
|           // 判断默认行为是否已经被禁用 | 
|           if (!e.defaultPrevented) { | 
|             e.preventDefault() | 
|           } | 
|         } | 
|         let point = { | 
|           x: e.touches[0].x, | 
|           y: e.touches[0].y | 
|         } | 
|          | 
|         if (point.y < this.cutArea.top) { | 
|           this.cutArea.top = point.y | 
|         } | 
|         if (point.y < 0) this.cutArea.top = 0 | 
|          | 
|         if (point.x < this.cutArea.right) { | 
|           this.cutArea.right = point.x | 
|         } | 
|         if (this.canvasWidth - point.x <= 0) { | 
|           this.cutArea.right = this.canvasWidth | 
|         } | 
|         if (point.y > this.cutArea.bottom) { | 
|           this.cutArea.bottom = this.canvasHeight | 
|         } | 
|         if (this.canvasHeight - point.y <= 0) { | 
|           this.cutArea.bottom = this.canvasHeight | 
|         } | 
|         if (point.x < this.cutArea.left) { | 
|           this.cutArea.left = point.x | 
|         } | 
|         if (point.x < 0) this.cutArea.left = 0 | 
|          | 
|         this.lastPoint = this.currentPoint | 
|         this.currentPoint = point | 
|          | 
|         let currentLine = this.currentLine | 
|         currentLine.unshift({ | 
|           time: new Date().getTime(), | 
|           dis: this.distance(this.currentPoint, this.lastPoint), | 
|           x: point.x, | 
|           y: point.y | 
|         }) | 
|          | 
|         this.pointToLine(currentLine) | 
|       }, | 
|       // 移动结束 | 
|       onTouchEnd(e) { | 
|         if (e.type != 'touchend') return false | 
|         let point = { | 
|           x: e.changedTouches[0].x, | 
|           y: e.changedTouches[0].y | 
|         } | 
|         this.lastPoint = this.currentPoint | 
|         this.currentPoint = point | 
|          | 
|         let currentLine = this.currentLine | 
|         currentLine.unshift({ | 
|           time: new Date().getTime(), | 
|           dis: this.distance(this.currentPoint, this.lastPoint), | 
|           x: point.x, | 
|           y: point.y | 
|         }) | 
|          | 
|         //一笔结束,保存笔迹的坐标点,清空,当前笔迹 | 
|         //增加判断是否在手写区域 | 
|         this.pointToLine(currentLine) | 
|         let currentChirography = { | 
|           lineSize: this.lineSize, | 
|           lineColor: this.currentSelectColor | 
|         } | 
|          | 
|         let chirography = this.chirography | 
|         chirography.unshift(currentChirography) | 
|         this.chirography = chirography | 
|          | 
|         let linePrack = this.linePrack | 
|         linePrack.unshift(this.currentLine) | 
|         this.linePrack = linePrack | 
|         this.currentLine = [] | 
|       }, | 
|       // 重置绘画板 | 
|       reDraw() { | 
|         this.initCanvas('#FFFFFF') | 
|       }, | 
|       // 保存 | 
|       save() { | 
|         // 在组件内使用需要第二个参数this | 
|         uni.canvasToTempFilePath({ | 
|           canvasId: this.canvasName, | 
|           fileType: 'png', | 
|           quality: 1, | 
|           success: (res) => { | 
|             if (this.rotate) { | 
|               this.getRotateImage(res.tempFilePath).then((res) => { | 
|                 this.$emit('save', res) | 
|               }).catch(err => { | 
|                 this.$t.message.toast('旋转图片失败') | 
|               }) | 
|             } else { | 
|               this.$emit('save', res.tempFilePath) | 
|             } | 
|           }, | 
|           fail: () => { | 
|             this.$t.message.toast('保存失败') | 
|           } | 
|         }, this) | 
|       }, | 
|       // 预览图片 | 
|       previewImage() { | 
|         // 在组件内使用需要第二个参数this | 
|         uni.canvasToTempFilePath({ | 
|           canvasId: this.canvasName, | 
|           fileType: 'png', | 
|           quality: 1, | 
|           success: (res) => { | 
|             if (this.rotate) { | 
|               this.getRotateImage(res.tempFilePath).then((res) => { | 
|                 uni.previewImage({ | 
|                   urls: [res] | 
|                 }) | 
|               }).catch(err => { | 
|                 this.$t.message.toast('旋转图片失败') | 
|               }) | 
|             } else { | 
|               uni.previewImage({ | 
|                 urls: [res.tempFilePath] | 
|               }) | 
|             } | 
|           }, | 
|           fail: (e) => { | 
|             this.$t.message.toast('预览失败') | 
|           } | 
|         }, this) | 
|       }, | 
|       // 关闭签名板 | 
|       closeBoard() { | 
|         this.$t.message.modal('提示信息','关闭后内容将被清除,是否确认关闭',() => { | 
|           this.$emit('closed') | 
|         }, true) | 
|       }, | 
|       // 切换画笔颜色 | 
|       colorSwitch(color) { | 
|         this.currentSelectColor = color | 
|       }, | 
|       // 绘制两点之间的线条 | 
|       pointToLine(line) { | 
|         this.calcBethelLine(line) | 
|       }, | 
|       // 计算插值,让线条更加圆滑 | 
|       calcBethelLine(line) { | 
|         if (line.length <= 1) { | 
|           line[0].r = this.radius | 
|           return | 
|         } | 
|         let x0, | 
|           x1, | 
|           x2, | 
|           y0, | 
|           y1, | 
|           y2, | 
|           r0, | 
|           r1, | 
|           r2, | 
|           len, | 
|           lastRadius, | 
|           dis = 0, | 
|           time = 0, | 
|           curveValue = 0.5; | 
|         if (line.length <= 2) { | 
|           x0 = line[1].x | 
|           y0 = line[1].y | 
|           x2 = line[1].x + (line[0].x - line[1].x) * curveValue | 
|           y2 = line[1].y + (line[0].y - line[1].y) * curveValue | 
|           x1 = x0 + (x2 - x0) * curveValue | 
|           y1 = y0 + (y2 - y0) * curveValue | 
|         } else { | 
|           x0 = line[2].x + (line[1].x - line[2].x) * curveValue | 
|           y0 = line[2].y + (line[1].y - line[2].y) * curveValue | 
|           x1 = line[1].x | 
|           y1 = line[1].y | 
|           x2 = x1 + (line[0].x - x1) * curveValue | 
|           y2 = y1 + (line[0].y - y1) * curveValue | 
|         } | 
|         // 三个点分别是(x0,y0),(x1,y1),(x2,y2) ;(x1,y1)这个是控制点,控制点不会落在曲线上;实际上,这个点还会手写获取的实际点,却落在曲线上 | 
|         len = this.distance({ | 
|           x: x2, | 
|           y: y2 | 
|         }, { | 
|           x: x0, | 
|           y: y0 | 
|         }) | 
|         lastRadius = this.radius | 
|         for (let i = 0; i < line.length - 1; i++) { | 
|           dis += line[i].dis | 
|           time += line[i].time - line[i + 1].time | 
|           if (dis > this.smoothness) break | 
|         } | 
|          | 
|         this.radius = Math.min((time / len) * this.pressure + this.minLine, this.maxLine) * this.lineSize | 
|         line[0].r = this.radius | 
|         // 计算笔迹半径 | 
|         if (line.length <= 2) { | 
|           r0 = (lastRadius + this.radius) / 2 | 
|           r1 = r0 | 
|           r2 = r1 | 
|         } else { | 
|           r0 = (line[2].r + line[1].r) / 2 | 
|           r1 = line[1].r | 
|           r2 = (line[1].r + line[0].r) / 2 | 
|         } | 
|         let n = 5 | 
|         let point = [] | 
|         for (let i = 0; i < n; i++) { | 
|           let t = i / (n - 1) | 
|           let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2 | 
|           let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2 | 
|           let r = lastRadius + ((this.radius - lastRadius) / n) * i | 
|           point.push({ | 
|             x, | 
|             y, | 
|             r | 
|           }) | 
|           if (point.length === 3) { | 
|             let a = this.ctaCalc(point[0].x, point[0].y, point[0].r, point[1].x, point[1].y, point[1].r, point[2].x, point[2].y, point[2].r) | 
|             a[0].color = this.currentSelectColor | 
|              | 
|             this.drawBethel(a, true) | 
|             point = [{ | 
|               x, | 
|               y, | 
|               r | 
|             }] | 
|           } | 
|         } | 
|         this.currentLine = line | 
|       }, | 
|       // 求两点之间的距离 | 
|       distance(a, b) { | 
|         let x = b.x - a.x | 
|         let y = b.y - a.y | 
|         return Math.sqrt(x * x + y * y) | 
|       }, | 
|       // 计算点信息 | 
|       ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) { | 
|         let a = [], | 
|           vx01, | 
|           vy01, | 
|           norm, | 
|           n_x0, | 
|           n_y0, | 
|           vx21, | 
|           vy21, | 
|           n_x2, | 
|           n_y2; | 
|         vx01 = x1 - x0 | 
|         vy01 = y1 - y0 | 
|         norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2 | 
|         vx01 = (vx01 / norm) * r0 | 
|         vy01 = (vy01 / norm) * r0 | 
|         n_x0 = vy01 | 
|         n_y0 = -vx01 | 
|         vx21 = x1 - x2 | 
|         vy21 = y1 - y2 | 
|         norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2 | 
|         vx21 = (vx21 / norm) * r2 | 
|         vy21 = (vy21 / norm) * r2 | 
|         n_x2 = -vy21 | 
|         n_y2 = vx21 | 
|         a.push({ | 
|           mx: x0 + n_x0, | 
|           my: y0 + n_y0, | 
|           color: '#080808' | 
|         }) | 
|         a.push({ | 
|           c1x: x1 + n_x0, | 
|           c1y: y1 + n_y0, | 
|           c2x: x1 + n_x2, | 
|           c2y: y1 + n_y2, | 
|           ex: x2 + n_x2, | 
|           ey: y2 + n_y2 | 
|         }) | 
|         a.push({ | 
|           c1x: x2 + n_x2 - vx21, | 
|           c1y: y2 + n_y2 - vy21, | 
|           c2x: x2 - n_x2 - vx21, | 
|           c2y: y2 - n_y2 - vy21, | 
|           ex: x2 - n_x2, | 
|           ey: y2 - n_y2 | 
|         }) | 
|         a.push({ | 
|           c1x: x1 - n_x2, | 
|           c1y: y1 - n_y2, | 
|           c2x: x1 - n_x0, | 
|           c2y: y1 - n_y0, | 
|           ex: x0 - n_x0, | 
|           ey: y0 - n_y0 | 
|         }) | 
|         a.push({ | 
|           c1x: x0 - n_x0 - vx01, | 
|           c1y: y0 - n_y0 - vy01, | 
|           c2x: x0 + n_x0 - vx01, | 
|           c2y: y0 + n_y0 - vy01, | 
|           ex: x0 + n_x0, | 
|           ey: y0 + n_y0 | 
|         }) | 
|         a[0].mx = a[0].mx.toFixed(1) | 
|         a[0].mx = parseFloat(a[0].mx) | 
|         a[0].my = a[0].my.toFixed(1) | 
|         a[0].my = parseFloat(a[0].my) | 
|         for (let i = 1; i < a.length; i++) { | 
|           a[i].c1x = a[i].c1x.toFixed(1) | 
|           a[i].c1x = parseFloat(a[i].c1x) | 
|           a[i].c1y = a[i].c1y.toFixed(1) | 
|           a[i].c1y = parseFloat(a[i].c1y) | 
|           a[i].c2x = a[i].c2x.toFixed(1) | 
|           a[i].c2x = parseFloat(a[i].c2x) | 
|           a[i].c2y = a[i].c2y.toFixed(1) | 
|           a[i].c2y = parseFloat(a[i].c2y) | 
|           a[i].ex = a[i].ex.toFixed(1) | 
|           a[i].ex = parseFloat(a[i].ex) | 
|           a[i].ey = a[i].ey.toFixed(1) | 
|           a[i].ey = parseFloat(a[i].ey) | 
|         } | 
|         return a | 
|       }, | 
|       // 绘制贝塞尔曲线 | 
|       drawBethel(point, is_fill, color) { | 
|         this.ctx.beginPath() | 
|         this.ctx.moveTo(point[0].mx, point[0].my) | 
|         if (color != undefined) { | 
|           this.ctx.setFillStyle(color) | 
|           this.ctx.setStrokeStyle(color) | 
|         } else { | 
|           this.ctx.setFillStyle(point[0].color) | 
|           this.ctx.setStrokeStyle(point[0].color) | 
|         } | 
|         for (let i = 1; i < point.length; i++) { | 
|           this.ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey) | 
|         } | 
|         this.ctx.stroke() | 
|         if (is_fill != undefined) { | 
|           //填充图形 ( 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 ) | 
|           this.ctx.fill() | 
|         } | 
|         this.ctx.draw(true) | 
|       }, | 
|       // 旋转图片 | 
|       async getRotateImage(dataUrl) { | 
|         // const url = await this.base64ToPath(dataUrl) | 
|         const url = dataUrl | 
|          | 
|         // 创建新画布 | 
|         const tempCtx = uni.createCanvasContext('temp-tn-sign-canvas', this) | 
|         const width = this.canvasWidth | 
|         const height = this.canvasHeight | 
|         tempCtx.restore() | 
|         tempCtx.save() | 
|         tempCtx.translate(0, height) | 
|         tempCtx.rotate(270 * Math.PI / 180) | 
|         tempCtx.drawImage(url, 0, 0, width, height) | 
|         tempCtx.draw() | 
|         return new Promise((resolve, reject) => { | 
|           setTimeout(() => { | 
|             uni.canvasToTempFilePath({ | 
|               canvasId: 'temp-tn-sign-canvas', | 
|               fileType: 'png', | 
|               x: 0, | 
|               y: height - width, | 
|               width: height, | 
|               height: width, | 
|               success: res => resolve(res.tempFilePath), | 
|               fail: reject | 
|             }, this) | 
|           }, 50) | 
|         }) | 
|       }, | 
|       // 将base64转换为本地 | 
|       base64ToPath(dataUrl) { | 
|         return new Promise((resolve, reject) => { | 
|           // 判断地址是否包含bas64字样,不包含直接返回 | 
|           if (dataUrl.indexOf('base64') !== -1) { | 
|             const data = uni.base64ToArrayBuffer(dataUrl.replace(/^data:image\/\w+;base64,/, '')) | 
|             // #ifdef MP-WEIXIN | 
|             const filePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}-${Math.random().toString(32).slice(2)}.png` | 
|             // #endif | 
|             // #ifndef MP-WEIXIN | 
|             const filePath = `${new Date().getTime()}-${Math.random().toString(32).slice(2)}.png` | 
|             // #endif | 
|             uni.getFileSystemManager().writeFile({ | 
|               filePath, | 
|               data, | 
|               encoding: 'base64', | 
|               success: () => resolve(filePath), | 
|               fail: reject | 
|             }) | 
|           } else { | 
|             resolve(dataUrl) | 
|           } | 
|         }) | 
|       } | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|    | 
|   .tn-sign-board { | 
|     position: fixed; | 
|     top: 0; | 
|     left: 0; | 
|     right: 0; | 
|     bottom: 0; | 
|     width: 100%; | 
|     height: 100%; | 
|     background-color: #E6E6E6; | 
|     z-index: 997; | 
|     display: flex; | 
|     flex-direction: row-reverse; | 
|      | 
|     &__content { | 
|       width: 84%; | 
|       height: 100%; | 
|        | 
|       &__wrapper { | 
|         width: calc(100% - 60rpx); | 
|         height: calc(100% - 60rpx); | 
|         margin: 30rpx; | 
|         border-radius: 20rpx; | 
|         border: 2rpx dotted #AAAAAA; | 
|         overflow: hidden; | 
|       } | 
|        | 
|       &__canvas { | 
|         width: 100%; | 
|         height: 100%; | 
|         background-color: #FFFFFF; | 
|       } | 
|     } | 
|      | 
|     &__tools { | 
|       width: 16%; | 
|       height: 100%; | 
|       display: flex; | 
|       flex-direction: column; | 
|       align-items: center; | 
|       justify-content: space-between; | 
|        | 
|       &__color { | 
|         margin-top: 30rpx; | 
|          | 
|         &__item { | 
|           width: 70rpx; | 
|           height: 70rpx; | 
|           border-radius: 100rpx; | 
|           margin: 20rpx auto; | 
|            | 
|           &--active { | 
|             position: relative; | 
|              | 
|             &::after { | 
|               content: ''; | 
|               position: absolute; | 
|               top: 50%; | 
|               left: 50%; | 
|               width: 40%; | 
|               height: 40%; | 
|               border-radius: 100rpx; | 
|               background-color: #FFFFFF; | 
|               transform: translate(-50%, -50%); | 
|             } | 
|           } | 
|         } | 
|       } | 
|        | 
|       &__button { | 
|         margin-bottom: 30rpx; | 
|         display: flex; | 
|         flex-direction: column; | 
|          | 
|         &__item { | 
|           width: 130rpx; | 
|           height: 60rpx; | 
|           line-height: 60rpx; | 
|           text-align: center; | 
|           margin: 60rpx auto; | 
|           border-radius: 10rpx; | 
|           color: #FFFFFF; | 
|           transform-origin: center center; | 
|           transform: rotateZ(90deg); | 
|         } | 
|       } | 
|     } | 
|   } | 
| </style> |