• uni-app 微信小程序中如何通过 canvas 画布实现电子签名?


    一、实际应用场景

    电子签名软件应用场景:电子签名在金融、银行、贷款行业中可以用于对内日常办公流转的文档的盖章签字,对外涉及业务合作协议,采购合同,贷款申请、信用评估、贷款合同、贷款文件表、说明函等等。

    可以说,只要是涉及纸质文档签字盖章的场景,都能够使用电子印章,电子签名

    二、实现方式

    利用 canvas 画布,模拟人工在纸张上手写的笔迹。原理其实很简单,就是你在画布上任意涂写即可,最后保存整个画布区域的内容为图片。

    三、案例 demo 分享

    这里我加了几个操作按钮,应该能满足您基本的数字签名需要了,最后我也会贴上我的源码。

     ps:微信小程序关于画布的官方文档

    https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html

    1、先在 view 视图中添加 canvas 组件。

    1. <view class="handCenter">
    2. <canvas class="handWriting" :disable-scroll="true" @touchstart="uploadScaleStart" @touchmove="uploadScaleMove"
    3. @touchend="uploadScaleEnd" canvas-id="handWriting">canvas>
    4. view>

    canvas-id="handWriting" 是唯一标识一个 canvas,用于后续获取 Canvas 对象

    2、页面加载时,初始化 canvas

    1. onLoad() {
    2. let canvasName = this.canvasName;
    3. let ctx = wx.createCanvasContext(canvasName);
    4. this.ctx = ctx;
    5. var query = wx.createSelectorQuery();
    6. query
    7. .select('.handCenter')
    8. .boundingClientRect(rect => {
    9. this.canvasWidth = rect.width;
    10. this.canvasHeight = rect.height;
    11. /* 将canvas背景设置为 白底,不设置 导出的canvas的背景为透明 */
    12. this.setCanvasBg('rgba(0,0,0,0)');
    13. })
    14. .exec();
    15. },

    3、笔迹开始,移动,结束,还有底部预览,重写,保存,上传等附加操作按钮。

    完成页面代码如下,包含我的效果图样式。

    1. <template>
    2. <view>
    3. <view class="wrapper">
    4. <view class="handBtn">
    5. <image @click="selectColorEvent('black','#1A1A1A')" :src="selectColor === 'black' ? '/static/img/sign/color_black_selected.png' : '/static/img/sign/color_black.png'"
    6. :class="[selectColor === 'black' ? 'color_select' : '', 'black-select']">image>
    7. <image @click="selectColorEvent('red','#ca262a')" :src="selectColor === 'red' ? '/static/img/sign/color_red_selected.png' : '/static/img/sign/color_red.png'"
    8. :class="[selectColor === 'red' ? 'color_select' : '', 'black-select']">image>
    9. <button @click="retDraw" class="delBtn">重写button>
    10. <button @click="saveCanvasAsImg" class="saveBtn">保存button>
    11. <button @click="previewCanvasImg" class="previewBtn">预览button>
    12. <button @click="uploadCanvasImg" class="uploadBtn">上传button>
    13. <button @click="subCanvas" class="subBtn">完成button>
    14. <button @click="back" class="subBtn">返回button>
    15. view>
    16. <view class="handCenter">
    17. <canvas class="handWriting" :disable-scroll="true" @touchstart="uploadScaleStart" @touchmove="uploadScaleMove"
    18. @touchend="uploadScaleEnd" canvas-id="handWriting">canvas>
    19. view>
    20. <view class="handRight">
    21. <view class="handTitle">请签名view>
    22. view>
    23. view>
    24. view>
    25. template>
    26. <script>
    27. export default {
    28. data() {
    29. return {
    30. canvasName: 'handWriting',
    31. ctx: '',
    32. canvasWidth: 0,
    33. canvasHeight: 0,
    34. transparent: 1, // 透明度
    35. selectColor: 'black',
    36. lineColor: '#1A1A1A', // 颜色
    37. lineSize: 1.5, // 笔记倍数
    38. lineMin: 0.5, // 最小笔画半径
    39. lineMax: 4, // 最大笔画半径
    40. pressure: 1, // 默认压力
    41. smoothness: 60, //顺滑度,用60的距离来计算速度
    42. currentPoint: {},
    43. currentLine: [], // 当前线条
    44. firstTouch: true, // 第一次触发
    45. radius: 1, //画圆的半径
    46. cutArea: {
    47. top: 0,
    48. right: 0,
    49. bottom: 0,
    50. left: 0
    51. }, //裁剪区域
    52. bethelPoint: [], //保存所有线条 生成的贝塞尔点;
    53. lastPoint: 0,
    54. chirography: [], //笔迹
    55. currentChirography: {}, //当前笔迹
    56. linePrack: [] //划线轨迹 , 生成线条的实际点
    57. };
    58. },
    59. onLoad() {
    60. let canvasName = this.canvasName;
    61. let ctx = wx.createCanvasContext(canvasName);
    62. this.ctx = ctx;
    63. var query = wx.createSelectorQuery();
    64. query
    65. .select('.handCenter')
    66. .boundingClientRect(rect => {
    67. this.canvasWidth = rect.width;
    68. this.canvasHeight = rect.height;
    69. /* 将canvas背景设置为 白底,不设置 导出的canvas的背景为透明 */
    70. this.setCanvasBg('rgba(0,0,0,0)');
    71. })
    72. .exec();
    73. },
    74. methods: {
    75. // 笔迹开始
    76. uploadScaleStart(e) {
    77. if (e.type != 'touchstart') return false;
    78. let ctx = this.ctx;
    79. ctx.setFillStyle(this.lineColor); // 初始线条设置颜色
    80. ctx.setGlobalAlpha(this.transparent); // 设置半透明
    81. let currentPoint = {
    82. x: e.touches[0].x,
    83. y: e.touches[0].y
    84. };
    85. let currentLine = this.currentLine;
    86. currentLine.unshift({
    87. time: new Date().getTime(),
    88. dis: 0,
    89. x: currentPoint.x,
    90. y: currentPoint.y
    91. });
    92. this.currentPoint = currentPoint;
    93. // currentLine
    94. if (this.firstTouch) {
    95. this.cutArea = {
    96. top: currentPoint.y,
    97. right: currentPoint.x,
    98. bottom: currentPoint.y,
    99. left: currentPoint.x
    100. };
    101. this.firstTouch = false;
    102. }
    103. this.pointToLine(currentLine);
    104. },
    105. // 笔迹移动
    106. uploadScaleMove(e) {
    107. if (e.type != 'touchmove') return false;
    108. if (e.cancelable) {
    109. // 判断默认行为是否已经被禁用
    110. if (!e.defaultPrevented) {
    111. e.preventDefault();
    112. }
    113. }
    114. let point = {
    115. x: e.touches[0].x,
    116. y: e.touches[0].y
    117. };
    118. //测试裁剪
    119. if (point.y < this.cutArea.top) {
    120. this.cutArea.top = point.y;
    121. }
    122. if (point.y < 0) this.cutArea.top = 0;
    123. if (point.x > this.cutArea.right) {
    124. this.cutArea.right = point.x;
    125. }
    126. if (this.canvasWidth - point.x <= 0) {
    127. this.cutArea.right = this.canvasWidth;
    128. }
    129. if (point.y > this.cutArea.bottom) {
    130. this.cutArea.bottom = point.y;
    131. }
    132. if (this.canvasHeight - point.y <= 0) {
    133. this.cutArea.bottom = this.canvasHeight;
    134. }
    135. if (point.x < this.cutArea.left) {
    136. this.cutArea.left = point.x;
    137. }
    138. if (point.x < 0) this.cutArea.left = 0;
    139. this.lastPoint = this.currentPoint;
    140. this.currentPoint = point;
    141. let currentLine = this.currentLine;
    142. currentLine.unshift({
    143. time: new Date().getTime(),
    144. dis: this.distance(this.currentPoint, this.lastPoint),
    145. x: point.x,
    146. y: point.y
    147. });
    148. this.pointToLine(currentLine);
    149. },
    150. // 笔迹结束
    151. uploadScaleEnd(e) {
    152. if (e.type != 'touchend') return 0;
    153. let point = {
    154. x: e.changedTouches[0].x,
    155. y: e.changedTouches[0].y
    156. };
    157. this.lastPoint = this.currentPoint;
    158. this.currentPoint = point;
    159. let currentLine = this.currentLine;
    160. currentLine.unshift({
    161. time: new Date().getTime(),
    162. dis: this.distance(this.currentPoint, this.lastPoint),
    163. x: point.x,
    164. y: point.y
    165. });
    166. if (currentLine.length > 2) {
    167. var info = (currentLine[0].time - currentLine[currentLine.length - 1].time) / currentLine.length;
    168. //$("#info").text(info.toFixed(2));
    169. }
    170. //一笔结束,保存笔迹的坐标点,清空,当前笔迹
    171. //增加判断是否在手写区域;
    172. this.pointToLine(currentLine);
    173. var currentChirography = {
    174. lineSize: this.lineSize,
    175. lineColor: this.lineColor
    176. };
    177. var chirography = this.chirography;
    178. chirography.unshift(currentChirography);
    179. this.chirography = chirography;
    180. var linePrack = this.linePrack;
    181. linePrack.unshift(this.currentLine);
    182. this.linePrack = linePrack;
    183. this.currentLine = [];
    184. },
    185. retDraw() {
    186. this.ctx.clearRect(0, 0, 700, 730);
    187. this.ctx.draw();
    188. //设置canvas背景
    189. this.setCanvasBg('rgba(0,0,0,0)');
    190. },
    191. //画两点之间的线条;参数为:line,会绘制最近的开始的两个点;
    192. pointToLine(line) {
    193. this.calcBethelLine(line);
    194. return;
    195. },
    196. //计算插值的方式;
    197. calcBethelLine(line) {
    198. if (line.length <= 1) {
    199. line[0].r = this.radius;
    200. return;
    201. }
    202. let x0,
    203. x1,
    204. x2,
    205. y0,
    206. y1,
    207. y2,
    208. r0,
    209. r1,
    210. r2,
    211. len,
    212. lastRadius,
    213. dis = 0,
    214. time = 0,
    215. curveValue = 0.5;
    216. if (line.length <= 2) {
    217. x0 = line[1].x;
    218. y0 = line[1].y;
    219. x2 = line[1].x + (line[0].x - line[1].x) * curveValue;
    220. y2 = line[1].y + (line[0].y - line[1].y) * curveValue;
    221. //x2 = line[1].x;
    222. //y2 = line[1].y;
    223. x1 = x0 + (x2 - x0) * curveValue;
    224. y1 = y0 + (y2 - y0) * curveValue;
    225. } else {
    226. x0 = line[2].x + (line[1].x - line[2].x) * curveValue;
    227. y0 = line[2].y + (line[1].y - line[2].y) * curveValue;
    228. x1 = line[1].x;
    229. y1 = line[1].y;
    230. x2 = x1 + (line[0].x - x1) * curveValue;
    231. y2 = y1 + (line[0].y - y1) * curveValue;
    232. }
    233. //从计算公式看,三个点分别是(x0,y0),(x1,y1),(x2,y2) ;(x1,y1)这个是控制点,控制点不会落在曲线上;实际上,这个点还会手写获取的实际点,却落在曲线上
    234. len = this.distance({
    235. x: x2,
    236. y: y2
    237. }, {
    238. x: x0,
    239. y: y0
    240. });
    241. lastRadius = this.radius;
    242. for (let n = 0; n < line.length - 1; n++) {
    243. dis += line[n].dis;
    244. time += line[n].time - line[n + 1].time;
    245. if (dis > this.smoothness) break;
    246. }
    247. this.radius = Math.min((time / len) * this.pressure + this.lineMin, this.lineMax) * this.lineSize;
    248. line[0].r = this.radius;
    249. //计算笔迹半径;
    250. if (line.length <= 2) {
    251. r0 = (lastRadius + this.radius) / 2;
    252. r1 = r0;
    253. r2 = r1;
    254. //return;
    255. } else {
    256. r0 = (line[2].r + line[1].r) / 2;
    257. r1 = line[1].r;
    258. r2 = (line[1].r + line[0].r) / 2;
    259. }
    260. let n = 5;
    261. let point = [];
    262. for (let i = 0; i < n; i++) {
    263. let t = i / (n - 1);
    264. let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2;
    265. let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2;
    266. let r = lastRadius + ((this.radius - lastRadius) / n) * i;
    267. point.push({
    268. x: x,
    269. y: y,
    270. r: r
    271. });
    272. if (point.length == 3) {
    273. 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]
    274. .y, point[2].r);
    275. a[0].color = this.lineColor;
    276. // let bethelPoint = this.bethelPoint;
    277. // bethelPoint = bethelPoint.push(a);
    278. this.bethelDraw(a, 1);
    279. point = [{
    280. x: x,
    281. y: y,
    282. r: r
    283. }];
    284. }
    285. }
    286. this.currentLine = line;
    287. },
    288. //求两点之间距离
    289. distance(a, b) {
    290. let x = b.x - a.x;
    291. let y = b.y - a.y;
    292. return Math.sqrt(x * x + y * y);
    293. },
    294. ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) {
    295. let a = [],
    296. vx01,
    297. vy01,
    298. norm,
    299. n_x0,
    300. n_y0,
    301. vx21,
    302. vy21,
    303. n_x2,
    304. n_y2;
    305. vx01 = x1 - x0;
    306. vy01 = y1 - y0;
    307. norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2;
    308. vx01 = (vx01 / norm) * r0;
    309. vy01 = (vy01 / norm) * r0;
    310. n_x0 = vy01;
    311. n_y0 = -vx01;
    312. vx21 = x1 - x2;
    313. vy21 = y1 - y2;
    314. norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2;
    315. vx21 = (vx21 / norm) * r2;
    316. vy21 = (vy21 / norm) * r2;
    317. n_x2 = -vy21;
    318. n_y2 = vx21;
    319. a.push({
    320. mx: x0 + n_x0,
    321. my: y0 + n_y0,
    322. color: '#1A1A1A'
    323. });
    324. a.push({
    325. c1x: x1 + n_x0,
    326. c1y: y1 + n_y0,
    327. c2x: x1 + n_x2,
    328. c2y: y1 + n_y2,
    329. ex: x2 + n_x2,
    330. ey: y2 + n_y2
    331. });
    332. a.push({
    333. c1x: x2 + n_x2 - vx21,
    334. c1y: y2 + n_y2 - vy21,
    335. c2x: x2 - n_x2 - vx21,
    336. c2y: y2 - n_y2 - vy21,
    337. ex: x2 - n_x2,
    338. ey: y2 - n_y2
    339. });
    340. a.push({
    341. c1x: x1 - n_x2,
    342. c1y: y1 - n_y2,
    343. c2x: x1 - n_x0,
    344. c2y: y1 - n_y0,
    345. ex: x0 - n_x0,
    346. ey: y0 - n_y0
    347. });
    348. a.push({
    349. c1x: x0 - n_x0 - vx01,
    350. c1y: y0 - n_y0 - vy01,
    351. c2x: x0 + n_x0 - vx01,
    352. c2y: y0 + n_y0 - vy01,
    353. ex: x0 + n_x0,
    354. ey: y0 + n_y0
    355. });
    356. a[0].mx = a[0].mx.toFixed(1);
    357. a[0].mx = parseFloat(a[0].mx);
    358. a[0].my = a[0].my.toFixed(1);
    359. a[0].my = parseFloat(a[0].my);
    360. for (let i = 1; i < a.length; i++) {
    361. a[i].c1x = a[i].c1x.toFixed(1);
    362. a[i].c1x = parseFloat(a[i].c1x);
    363. a[i].c1y = a[i].c1y.toFixed(1);
    364. a[i].c1y = parseFloat(a[i].c1y);
    365. a[i].c2x = a[i].c2x.toFixed(1);
    366. a[i].c2x = parseFloat(a[i].c2x);
    367. a[i].c2y = a[i].c2y.toFixed(1);
    368. a[i].c2y = parseFloat(a[i].c2y);
    369. a[i].ex = a[i].ex.toFixed(1);
    370. a[i].ex = parseFloat(a[i].ex);
    371. a[i].ey = a[i].ey.toFixed(1);
    372. a[i].ey = parseFloat(a[i].ey);
    373. }
    374. return a;
    375. },
    376. bethelDraw(point, is_fill, color) {
    377. let ctx = this.ctx;
    378. ctx.beginPath();
    379. ctx.moveTo(point[0].mx, point[0].my);
    380. if (undefined != color) {
    381. ctx.setFillStyle(color);
    382. ctx.setStrokeStyle(color);
    383. } else {
    384. ctx.setFillStyle(point[0].color);
    385. ctx.setStrokeStyle(point[0].color);
    386. }
    387. for (let i = 1; i < point.length; i++) {
    388. ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey);
    389. }
    390. ctx.stroke();
    391. if (undefined != is_fill) {
    392. ctx.fill(); //填充图形 ( 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 )
    393. }
    394. ctx.draw(true);
    395. },
    396. selectColorEvent(str, color) {
    397. this.selectColor = str;
    398. this.lineColor = color;
    399. },
    400. //将Canvas内容转成 临时图片 --> cb 为回调函数 形参 tempImgPath 为 生成的图片临时路径
    401. canvasToImg(cb) {
    402. //这种写法移动端 出不来
    403. this.ctx.draw(true, () => {
    404. wx.canvasToTempFilePath({
    405. canvasId: 'handWriting',
    406. fileType: 'png',
    407. quality: 1, //图片质量
    408. success(res) {
    409. // console.log(res.tempFilePath, 'canvas生成图片地址');
    410. wx.showToast({
    411. title: '执行了吗?'
    412. });
    413. cb(res.tempFilePath);
    414. }
    415. });
    416. });
    417. },
    418. //完成
    419. subCanvas() {
    420. this.ctx.draw(true, () => {
    421. wx.canvasToTempFilePath({
    422. canvasId: 'handWriting',
    423. fileType: 'png',
    424. quality: 1, //图片质量
    425. success(res) {
    426. // console.log(res.tempFilePath, '已完成,canvas生成图片地址');
    427. // wx.showToast({
    428. // title: '已保存'
    429. // });
    430. //保存到系统相册
    431. wx.saveImageToPhotosAlbum({
    432. filePath: res.tempFilePath,
    433. success(res) {
    434. console.log("已完成并保存到相册");
    435. wx.showToast({
    436. title: '已成功保存到相册',
    437. duration: 2000
    438. });
    439. }
    440. });
    441. }
    442. });
    443. });
    444. },
    445. //保存到相册
    446. saveCanvasAsImg() {
    447. /*
    448. this.canvasToImg( tempImgPath=>{
    449. // console.log(tempImgPath, '临时路径');
    450. wx.saveImageToPhotosAlbum({
    451. filePath: tempImgPath,
    452. success(res) {
    453. wx.showToast({
    454. title: '已保存到相册',
    455. duration: 2000
    456. });
    457. }
    458. })
    459. } );
    460. */
    461. wx.canvasToTempFilePath({
    462. canvasId: 'handWriting',
    463. fileType: 'png',
    464. quality: 1, //图片质量
    465. success(res) {
    466. // console.log(res.tempFilePath, 'canvas生成图片地址');
    467. wx.saveImageToPhotosAlbum({
    468. filePath: res.tempFilePath,
    469. success(res) {
    470. wx.showToast({
    471. title: '已保存到相册',
    472. duration: 2000
    473. });
    474. }
    475. });
    476. }
    477. });
    478. },
    479. //预览
    480. previewCanvasImg() {
    481. wx.canvasToTempFilePath({
    482. canvasId: 'handWriting',
    483. fileType: 'jpg',
    484. quality: 1, //图片质量
    485. success(res) {
    486. // console.log(res.tempFilePath, 'canvas生成图片地址');
    487. wx.previewImage({
    488. urls: [res.tempFilePath] //预览图片 数组
    489. });
    490. }
    491. });
    492. /* //移动端出不来 ^~^!!
    493. this.canvasToImg( tempImgPath=>{
    494. wx.previewImage({
    495. urls: [tempImgPath], //预览图片 数组
    496. })
    497. } );
    498. */
    499. },
    500. //上传
    501. uploadCanvasImg() {
    502. wx.canvasToTempFilePath({
    503. canvasId: 'handWriting',
    504. fileType: 'png',
    505. quality: 1, //图片质量
    506. success(res) {
    507. console.log(res.tempFilePath, '待上传服务器canvas生成图片地址');
    508. //上传
    509. wx.uploadFile({
    510. url: 'https://xxx/uploadSignFile', // 仅为示例,非真实的接口地址
    511. filePath: res.tempFilePath,
    512. name: 'file_signature',
    513. formData: {
    514. user: 'test'
    515. },
    516. success(res) {
    517. if (res.code == 1 ) {
    518. wx.showToast({
    519. title: '上传成功',
    520. duration: 2000
    521. });
    522. }else{
    523. uni.showToast({
    524. title: res.message,
    525. icon: "none",
    526. position: 'bottom',
    527. duration: 3000
    528. });
    529. }
    530. }
    531. });
    532. }
    533. });
    534. },
    535. //设置canvas背景色 不设置 导出的canvas的背景为黑色
    536. //@params:字符串 color
    537. setCanvasBg(color) {
    538. /* 将canvas背景设置为 白底,不设置 导出的canvas的背景默认为黑色 */
    539. //rect() 参数说明 矩形路径左上角的横坐标,左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度
    540. //这里是 canvasHeight - 4 是因为下边盖住边框了,所以手动减了写
    541. this.ctx.rect(0, 0, this.canvasWidth, this.canvasHeight - 4);
    542. this.ctx.setFillStyle(color);
    543. this.ctx.fill(); //设置填充
    544. this.ctx.draw(); //开画
    545. },
    546. //返回
    547. back(){
    548. uni.reLaunch({
    549. url:'/pages/ucenter/ucenter'
    550. })
    551. }
    552. }
    553. };
    554. script>
    555. <style>
    556. page {
    557. background: #fbfbfb;
    558. height: auto;
    559. overflow: hidden;
    560. }
    561. .wrapper {
    562. width: 100%;
    563. height: 95vh;
    564. margin: 30rpx 0;
    565. overflow: hidden;
    566. display: flex;
    567. align-content: center;
    568. flex-direction: row;
    569. justify-content: center;
    570. font-size: 28rpx;
    571. }
    572. .handWriting {
    573. background: #fff;
    574. width: 100%;
    575. height: 95vh;
    576. }
    577. .handRight {
    578. display: inline-flex;
    579. align-items: center;
    580. }
    581. .handCenter {
    582. border: 4rpx dashed #e9e9e9;
    583. flex: 5;
    584. overflow: hidden;
    585. box-sizing: border-box;
    586. }
    587. .handTitle {
    588. transform: rotate(90deg);
    589. flex: 1;
    590. color: #666;
    591. }
    592. .handBtn button {
    593. font-size: 28rpx;
    594. }
    595. .handBtn {
    596. height: 95vh;
    597. display: inline-flex;
    598. flex-direction: column;
    599. justify-content: space-between;
    600. align-content: space-between;
    601. flex: 1;
    602. }
    603. .delBtn {
    604. position: absolute;
    605. top: 250rpx;
    606. left: 0rpx;
    607. transform: rotate(90deg);
    608. color: #666;
    609. }
    610. .delBtn image {
    611. position: absolute;
    612. top: 13rpx;
    613. left: 25rpx;
    614. }
    615. .subBtn {
    616. position: absolute;
    617. bottom: 52rpx;
    618. left: -3rpx;
    619. display: inline-flex;
    620. transform: rotate(90deg);
    621. background: #008ef6;
    622. color: #fff;
    623. margin-bottom: 30rpx;
    624. text-align: center;
    625. justify-content: center;
    626. }
    627. /*Peach - 新增 - 保存*/
    628. .saveBtn {
    629. position: absolute;
    630. top: 375rpx;
    631. left: 0rpx;
    632. transform: rotate(90deg);
    633. color: #666;
    634. }
    635. .previewBtn {
    636. position: absolute;
    637. top: 500rpx;
    638. left: 0rpx;
    639. transform: rotate(90deg);
    640. color: #666;
    641. }
    642. .uploadBtn {
    643. position: absolute;
    644. top: 625rpx;
    645. left: 0rpx;
    646. transform: rotate(90deg);
    647. color: #666;
    648. }
    649. /*Peach - 新增 - 保存*/
    650. .black-select {
    651. width: 60rpx;
    652. height: 60rpx;
    653. position: absolute;
    654. top: 30rpx;
    655. left: 25rpx;
    656. }
    657. .black-select.color_select {
    658. width: 90rpx;
    659. height: 90rpx;
    660. top: 100rpx;
    661. left: 10rpx;
    662. }
    663. .red-select {
    664. width: 60rpx;
    665. height: 60rpx;
    666. position: absolute;
    667. top: 140rpx;
    668. left: 25rpx;
    669. }
    670. .red-select.color_select {
    671. width: 90rpx;
    672. height: 90rpx;
    673. top: 120rpx;
    674. left: 10rpx;
    675. }
    676. style>

    代码中都有注释说明,希望能帮助到需要的小伙伴。具体api细节 不懂的还请自行查阅官方文档说明。

    写在最后,小编是一个注重解决实际问题的博主,日常分享案例皆是工作中的实际项目背景。喜欢博主案例分享的可以给博主的文章来个点赞、收藏、加关注。

  • 相关阅读:
    Ubuntu安装docker,并换镜像源详细教程,建议收藏
    【PowerQuery】Excel的PowerQuery的复制
    SpringBoot整合Groovy脚本,实现动态编程
    flume数据无法发送
    JAVAWeb 小技巧-利用验证码防止表单重复提交
    通过VScode连接远程 Linux 服务器修改vue代码
    androidx.appcompat.widget.Toolbar最右边设置控件不能仅靠最右边
    redission
    对文件的 SQL 式运算
    java培训技术SpringMVC视图解析器
  • 原文地址:https://blog.csdn.net/weixin_36754290/article/details/126673180