am-sign-input.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. <template>
  2. <view class="sign">
  3. <view class="imgBox">
  4. <view class="nom_img" v-if="!showImg" @click="signModShow=true">
  5. <image v-if="!showImg" src="/static/other/signs.png" style="width: 34px;height: 34px;">
  6. </image>
  7. </view>
  8. <view class="across_img" v-if="showImg">
  9. <view v-if="showImg" class="delete_icon" @click.stop="deleteImg">
  10. x
  11. </view>
  12. <image v-if="showImg" :src="showImg" style="width: 140px;height: 80px;" @click="previewImg(showImg)">
  13. </image>
  14. </view>
  15. </view>
  16. <umask :show="signModShow" @click="signModShow=false" :duration="0">
  17. <view class="warp">
  18. <view class="signBox" @tap.stop>
  19. <view class="wrapper">
  20. <view class="handBtn">
  21. <!-- #ifdef MP-WEIXIN -->
  22. <image @click="selectColorEvent('black','#1A1A1A')"
  23. :src="selectColor === 'black' ? '/static/other/color_black_selected.png' : '/static/other/color_black.png'"
  24. class="black-select"></image>
  25. <image @click="selectColorEvent('red','#ca262a')"
  26. :src="selectColor === 'red' ? '/static/other/color_red_selected.png' : '/static/other/color_red.png'"
  27. class="red-select"></image>
  28. <!-- #endif -->
  29. <!-- #ifndef MP-WEIXIN -->
  30. <view class="color_pic" :style="{background:lineColor}" @click="showPickerColor=true">
  31. </view>
  32. <!-- #endif -->
  33. <button @click="clear" class="delBtn">清空</button>
  34. <button @click="saveCanvasAsImg" class="saveBtn">保存</button>
  35. <button @click="previewCanvasImg" class="previewBtn">预览</button>
  36. <button @click="subCanvas" class="subBtn">完成</button>
  37. <button @click="undo" class="undoBtn">撤销</button>
  38. <span class="emptyInfo" style="color: red;" v-if="emptyShow">你还没有绘制任何东西哦</span>
  39. </view>
  40. <view class="handCenter" :style="{left:canvasLeft+'px'}">
  41. <canvas :disable-scroll="true" @touchstart="uploadScaleStart" @touchmove="uploadScaleMove"
  42. @touchend="uploadScaleEnd" :style="{width:'100%',height:'calc(85vh - 8rpx)'}"
  43. :canvas-id="canvasId"></canvas>
  44. </view>
  45. <view class="handCenters">
  46. <canvas :canvas-id="canvasIds"
  47. :style="{width:outSignWidth+'px',height:outSignHeight+'px'}"></canvas>
  48. </view>
  49. <view class="handRight">
  50. <view class="handTitle">请签名
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. </view>
  56. </umask>
  57. <pickerColor :isShow="showPickerColor" :bottom="0" @callback='getPickerColor' />
  58. </view>
  59. </template>
  60. <script>
  61. import umask from "./u-mask/u-mask.vue"
  62. import pickerColor from "./pickerColor.vue"
  63. export default {
  64. components: {
  65. umask,
  66. pickerColor
  67. },
  68. data() {
  69. return {
  70. canvasLeft: 10000,
  71. emptyShow: false,
  72. signModShow: false,
  73. showImg: "",
  74. showPickerColor: false,
  75. ctx: '',
  76. ctxs: '',
  77. canvasWidth: 0,
  78. canvasHeight: 0,
  79. selectColor: 'black',
  80. lineColor: '#1A1A1A',
  81. points: [],
  82. historyList: [],
  83. canAddHistory: true,
  84. getImagePath: () => {
  85. let that = this
  86. return new Promise((resolve) => {
  87. uni.canvasToTempFilePath({
  88. canvasId: that.canvasId,
  89. fileType: 'png',
  90. quality: 1, //图片质量
  91. success: res => resolve(res.tempFilePath)
  92. }, this)
  93. })
  94. },
  95. requestAnimationFrame: void 0,
  96. };
  97. },
  98. watch: {
  99. signModShow(newValue, oldValue) {
  100. newValue ? this.canvasLeft = 74 : this.canvasLeft = 10000
  101. }
  102. },
  103. props: { //可用于修改的参数放在props里 也可单独放在外面做成组件调用 传值
  104. action: {
  105. type: String,
  106. default: ''
  107. },
  108. canvasId: {
  109. type: String,
  110. default: 'canvasDr'
  111. },
  112. canvasIds: {
  113. type: String,
  114. default: 'canvasRo'
  115. },
  116. header: {
  117. type: Object,
  118. default: {}
  119. },
  120. outSignWidth: {
  121. type: Number,
  122. default: 54
  123. },
  124. outSignHeight: {
  125. type: Number,
  126. default: 24
  127. },
  128. minSpeed: { //画笔最小速度
  129. type: Number,
  130. default: 1.5
  131. },
  132. minWidth: { //线条最小粗度
  133. type: Number,
  134. default: 3,
  135. },
  136. maxWidth: { //线条最大粗度
  137. type: Number,
  138. default: 10
  139. },
  140. openSmooth: { //开启平滑线条(笔锋)
  141. type: Boolean,
  142. default: true
  143. },
  144. maxHistoryLength: { //历史最大长度
  145. type: Number,
  146. default: 20
  147. },
  148. maxWidthDiffRate: { //最大差异率
  149. type: Number,
  150. default: 20
  151. },
  152. undoScan: { //撤销重新渲染偏移缩放校准
  153. type: Number,
  154. default: 0.83
  155. },
  156. bgColor: { //背景色
  157. type: String,
  158. default: ''
  159. },
  160. },
  161. mounted() {
  162. if (!this.ctx) {
  163. this.ctx = uni.createCanvasContext(this.canvasId, this);
  164. }
  165. if (!this.ctxs) {
  166. this.ctxs = uni.createCanvasContext(this.canvasIds, this);
  167. }
  168. let that = this
  169. this.$nextTick(() => {
  170. uni.createSelectorQuery().in(this).select('.handCenter').boundingClientRect(rect => {
  171. that.canvasWidth = rect.width;
  172. that.canvasHeight = rect.height;
  173. that.drawBgColor()
  174. })
  175. .exec();
  176. })
  177. },
  178. methods: {
  179. getPickerColor(color) {
  180. this.showPickerColor = false;
  181. if (color) {
  182. this.lineColor = color;
  183. }
  184. },
  185. // 笔迹开始
  186. uploadScaleStart(e) {
  187. this.canAddHistory = true
  188. this.ctx.setStrokeStyle(this.lineColor)
  189. this.ctx.setLineCap("round") //'butt'、'round'、'square'
  190. },
  191. // 笔迹移动
  192. uploadScaleMove(e) {
  193. let temX = e.changedTouches[0].x
  194. let temY = e.changedTouches[0].y
  195. this.initPoint(temX, temY)
  196. this.onDraw()
  197. },
  198. /**
  199. * 触摸结束
  200. */
  201. uploadScaleEnd() {
  202. this.canAddHistory = true;
  203. this.points = [];
  204. },
  205. /**
  206. * 记录点属性
  207. */
  208. initPoint(x, y) {
  209. var point = {
  210. x: x,
  211. y: y,
  212. t: Date.now()
  213. };
  214. var prePoint = this.points.slice(-1)[0];
  215. if (prePoint && (prePoint.t === point.t || prePoint.x === x && prePoint.y === y)) {
  216. return;
  217. }
  218. if (prePoint && this.openSmooth) {
  219. var prePoint2 = this.points.slice(-2, -1)[0];
  220. point.distance = Math.sqrt(Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2));
  221. point.speed = point.distance / (point.t - prePoint.t || 0.1);
  222. point.lineWidth = this.getLineWidth(point.speed);
  223. if (prePoint2 && prePoint2.lineWidth && prePoint.lineWidth) {
  224. var rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth;
  225. var maxRate = this.maxWidthDiffRate / 100;
  226. maxRate = maxRate > 1 ? 1 : maxRate < 0.01 ? 0.01 : maxRate;
  227. if (Math.abs(rate) > maxRate) {
  228. var per = rate > 0 ? maxRate : -maxRate;
  229. point.lineWidth = prePoint.lineWidth * (1 + per);
  230. }
  231. }
  232. }
  233. this.points.push(point);
  234. this.points = this.points.slice(-3);
  235. },
  236. /**
  237. * @param {Object}
  238. * 线宽
  239. */
  240. getLineWidth(speed) {
  241. var minSpeed = this.minSpeed > 10 ? 10 : this.minSpeed < 1 ? 1 : this.minSpeed; //1.5
  242. var addWidth = (this.maxWidth - this.minWidth) * speed / minSpeed;
  243. var lineWidth = Math.max(this.maxWidth - addWidth, this.minWidth);
  244. return Math.min(lineWidth, this.maxWidth);
  245. },
  246. /**
  247. * 绘画逻辑
  248. */
  249. onDraw() {
  250. if (this.points.length < 2) return;
  251. this.addHistory();
  252. var point = this.points.slice(-1)[0];
  253. var prePoint = this.points.slice(-2, -1)[0];
  254. let that = this
  255. var onDraw = function onDraw() {
  256. if (that.openSmooth) {
  257. that.drawSmoothLine(prePoint, point);
  258. } else {
  259. that.drawNoSmoothLine(prePoint, point);
  260. }
  261. };
  262. if (typeof this.requestAnimationFrame === 'function') {
  263. this.requestAnimationFrame(function() {
  264. return onDraw();
  265. });
  266. } else {
  267. onDraw();
  268. }
  269. },
  270. //添加历史图片地址
  271. addHistory() {
  272. if (!this.maxHistoryLength || !this.canAddHistory) return;
  273. this.canAddHistory = false;
  274. if (!this.getImagePath) {
  275. this.historyList.length++;
  276. return;
  277. }
  278. //历史地址 (暂时无用)
  279. let that = this
  280. that.getImagePath().then(function(url) {
  281. if (url) {
  282. that.historyList.push(url)
  283. that.historyList = that.historyList.slice(-that.maxHistoryLength);
  284. }
  285. });
  286. },
  287. //画平滑线
  288. drawSmoothLine(prePoint, point) {
  289. var dis_x = point.x - prePoint.x;
  290. var dis_y = point.y - prePoint.y;
  291. if (Math.abs(dis_x) + Math.abs(dis_y) <= 2) {
  292. point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5;
  293. point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5;
  294. } else {
  295. point.lastX1 = prePoint.x + dis_x * 0.3;
  296. point.lastY1 = prePoint.y + dis_y * 0.3;
  297. point.lastX2 = prePoint.x + dis_x * 0.7;
  298. point.lastY2 = prePoint.y + dis_y * 0.7;
  299. }
  300. point.perLineWidth = (prePoint.lineWidth + point.lineWidth) / 2;
  301. if (typeof prePoint.lastX1 === 'number') {
  302. this.drawCurveLine(prePoint.lastX2, prePoint.lastY2, prePoint.x, prePoint.y, point.lastX1, point
  303. .lastY1, point.perLineWidth);
  304. if (prePoint.isFirstPoint) return;
  305. if (prePoint.lastX1 === prePoint.lastX2 && prePoint.lastY1 === prePoint.lastY2) return;
  306. var data = this.getRadianData(prePoint.lastX1, prePoint.lastY1, prePoint.lastX2, prePoint.lastY2);
  307. var points1 = this.getRadianPoints(data, prePoint.lastX1, prePoint.lastY1, prePoint.perLineWidth / 2);
  308. var points2 = this.getRadianPoints(data, prePoint.lastX2, prePoint.lastY2, point.perLineWidth / 2);
  309. this.drawTrapezoid(points1[0], points2[0], points2[1], points1[1]);
  310. } else {
  311. point.isFirstPoint = true;
  312. }
  313. },
  314. //画不平滑线
  315. drawNoSmoothLine(prePoint, point) {
  316. point.lastX = prePoint.x + (point.x - prePoint.x) * 0.5;
  317. point.lastY = prePoint.y + (point.y - prePoint.y) * 0.5;
  318. if (typeof prePoint.lastX === 'number') {
  319. this.drawCurveLine(prePoint.lastX, prePoint.lastY, prePoint.x, prePoint.y, point.lastX, point.lastY,
  320. this.maxWidth);
  321. }
  322. },
  323. //画线
  324. drawCurveLine(x1, y1, x2, y2, x3, y3, lineWidth) {
  325. lineWidth = Number(lineWidth.toFixed(1));
  326. this.ctx.setLineWidth && this.ctx.setLineWidth(lineWidth);
  327. this.ctx.lineWidth = lineWidth;
  328. this.ctx.beginPath();
  329. this.ctx.moveTo(Number(x1.toFixed(1)), Number(y1.toFixed(1)));
  330. this.ctx.quadraticCurveTo(Number(x2.toFixed(1)), Number(y2.toFixed(1)), Number(x3.toFixed(1)), Number(y3
  331. .toFixed(1)));
  332. this.ctx.stroke();
  333. this.ctx.draw && this.ctx.draw(true);
  334. },
  335. //画梯形
  336. drawTrapezoid(point1, point2, point3, point4) {
  337. this.ctx.beginPath();
  338. this.ctx.moveTo(Number(point1.x.toFixed(1)), Number(point1.y.toFixed(1)));
  339. this.ctx.lineTo(Number(point2.x.toFixed(1)), Number(point2.y.toFixed(1)));
  340. this.ctx.lineTo(Number(point3.x.toFixed(1)), Number(point3.y.toFixed(1)));
  341. this.ctx.lineTo(Number(point4.x.toFixed(1)), Number(point4.y.toFixed(1)));
  342. this.ctx.setFillStyle && this.ctx.setFillStyle(this.lineColor);
  343. this.ctx.fillStyle = this.lineColor;
  344. this.ctx.fill();
  345. this.ctx.draw && this.ctx.draw(true);
  346. },
  347. //获取弧度
  348. getRadianData(x1, y1, x2, y2) {
  349. var dis_x = x2 - x1;
  350. var dis_y = y2 - y1;
  351. if (dis_x === 0) {
  352. return {
  353. val: 0,
  354. pos: -1
  355. };
  356. }
  357. if (dis_y === 0) {
  358. return {
  359. val: 0,
  360. pos: 1
  361. };
  362. }
  363. var val = Math.abs(Math.atan(dis_y / dis_x));
  364. if (x2 > x1 && y2 < y1 || x2 < x1 && y2 > y1) {
  365. return {
  366. val: val,
  367. pos: 1
  368. };
  369. }
  370. return {
  371. val: val,
  372. pos: -1
  373. };
  374. },
  375. //获取弧度点
  376. getRadianPoints(radianData, x, y, halfLineWidth) {
  377. if (radianData.val === 0) {
  378. if (radianData.pos === 1) {
  379. return [{
  380. x: x,
  381. y: y + halfLineWidth
  382. }, {
  383. x: x,
  384. y: y - halfLineWidth
  385. }];
  386. }
  387. return [{
  388. y: y,
  389. x: x + halfLineWidth
  390. }, {
  391. y: y,
  392. x: x - halfLineWidth
  393. }];
  394. }
  395. var dis_x = Math.sin(radianData.val) * halfLineWidth;
  396. var dis_y = Math.cos(radianData.val) * halfLineWidth;
  397. if (radianData.pos === 1) {
  398. return [{
  399. x: x + dis_x,
  400. y: y + dis_y
  401. }, {
  402. x: x - dis_x,
  403. y: y - dis_y
  404. }];
  405. }
  406. return [{
  407. x: x + dis_x,
  408. y: y - dis_y
  409. }, {
  410. x: x - dis_x,
  411. y: y + dis_y
  412. }];
  413. },
  414. /**
  415. * 背景色
  416. */
  417. drawBgColor() {
  418. if (!this.bgColor) return;
  419. this.ctx.setFillStyle && this.ctx.setFillStyle(this.bgColor);
  420. this.ctx.fillStyle = this.bgColor;
  421. this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
  422. this.ctx.draw && this.ctx.draw(true);
  423. },
  424. //图片绘制
  425. drawByImage(url) {
  426. this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  427. try {
  428. this.ctx.drawImage(url, 0, 0, this.canvasWidth * this.undoScan, this.canvasHeight * this.undoScan);
  429. this.ctx.draw && this.ctx.draw(true);
  430. } catch (e) {
  431. this.historyList.length = 0;
  432. }
  433. },
  434. /**
  435. * 清空
  436. */
  437. clear() {
  438. this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  439. this.ctx.draw && this.ctx.draw();
  440. this.drawBgColor();
  441. this.historyList.length = 0;
  442. },
  443. //撤消
  444. undo() {
  445. if (!this.getImagePath || !this.historyList.length) return;
  446. var pngURL = this.historyList.splice(-1)[0];
  447. this.drawByImage(pngURL);
  448. if (this.historyList.length === 0) {
  449. this.clear();
  450. }
  451. },
  452. //是否为空
  453. isEmpty() {
  454. return this.historyList.length === 0;
  455. },
  456. /**
  457. * @param {Object} str
  458. * @param {Object} color
  459. * 选择颜色
  460. */
  461. selectColorEvent(str, color) {
  462. this.selectColor = str;
  463. this.lineColor = color;
  464. this.ctx.setStrokeStyle(this.lineColor)
  465. },
  466. //完成
  467. subCanvas() {
  468. let that = this
  469. if (that.isEmpty()) {
  470. that.emptyShow = true
  471. setTimeout(function() {
  472. that.emptyShow = false
  473. }, 1000)
  474. return
  475. }
  476. uni.canvasToTempFilePath({
  477. canvasId: that.canvasId,
  478. fileType: 'png',
  479. quality: 1, //图片质量
  480. success(res) {
  481. that.ctxs.translate(0, that.outSignHeight);
  482. that.ctxs.rotate(-90 * Math.PI / 180)
  483. that.ctxs.drawImage(res.tempFilePath, 0, 0, that.outSignHeight, that.outSignWidth)
  484. that.ctxs.draw()
  485. setTimeout(() => {
  486. uni.canvasToTempFilePath({
  487. canvasId: that.canvasIds,
  488. fileType: 'png',
  489. quality: 1, //图片质量
  490. success: function(res1) {
  491. if (that.action) {
  492. uni.showLoading()
  493. uni.uploadFile({
  494. url: that.action, //图片上传post请求的地址
  495. filePath: res1.tempFilePath,
  496. name: "file",
  497. header: that.header,
  498. success: (uploadFileRes) => {
  499. uni.hideLoading()
  500. that.showImg = res1.tempFilePath
  501. that.$emit('signToUrl',
  502. uploadFileRes)
  503. that.signModShow = false
  504. that.clear()
  505. },
  506. fail: (error) => {
  507. uni.hideLoading()
  508. }
  509. });
  510. } else {
  511. that.showImg = res1.tempFilePath
  512. that.$emit('signToUrl', {
  513. error_code: "201",
  514. msg: "请配置上传文件接口参数action"
  515. })
  516. that.signModShow = false
  517. that.clear()
  518. }
  519. },
  520. fail: (err) => {}
  521. }, that)
  522. }, 200);
  523. }
  524. }, this);
  525. },
  526. //保存到相册
  527. saveCanvasAsImg() {
  528. uni.canvasToTempFilePath({
  529. canvasId: this.canvasId,
  530. fileType: 'png',
  531. quality: 1, //图片质量
  532. success(res) {
  533. uni.saveImageToPhotosAlbum({
  534. filePath: res.tempFilePath,
  535. success(res) {
  536. uni.showToast({
  537. title: '已保存到相册',
  538. duration: 2000
  539. });
  540. }
  541. });
  542. }
  543. }, this);
  544. },
  545. //预览
  546. previewCanvasImg() {
  547. uni.canvasToTempFilePath({
  548. canvasId: this.canvasId,
  549. fileType: 'jpg',
  550. quality: 1, //图片质量
  551. success(res) {
  552. uni.previewImage({
  553. urls: [res.tempFilePath] //预览图片 数组
  554. });
  555. }
  556. }, this);
  557. },
  558. deleteImg() {
  559. this.showImg = ""
  560. },
  561. previewImg(img) {
  562. uni.previewImage({
  563. urls: [img] //预览图片 数组
  564. });
  565. },
  566. }
  567. };
  568. </script>
  569. <style lang="scss">
  570. page {
  571. background: #d9d9d9;
  572. height: auto;
  573. overflow: hidden;
  574. }
  575. .imgBox {
  576. width: 140px;
  577. height: 80px;
  578. position: relative;
  579. .nom_img {
  580. border-radius: 8px;
  581. border: 1px dashed;
  582. border-color: #a3a3a3;
  583. overflow: hidden;
  584. display: flex;
  585. justify-content: center;
  586. align-items: center;
  587. height: 80px;
  588. width: 140px;
  589. }
  590. .nom_img:hover {
  591. border-color: #008ef6 !important;
  592. }
  593. .across_img {
  594. border: 1px dashed #a3a3a3;
  595. border-radius: 8px;
  596. height: 80px;
  597. width: 140px;
  598. .delete_icon {
  599. position: absolute;
  600. top: -12px;
  601. right: -12px;
  602. width: 24px;
  603. height: 24px;
  604. overflow: hidden;
  605. color: #ffffff;
  606. font-size: 24px;
  607. text-align: center;
  608. line-height: 20px;
  609. background: #ff3c0c;
  610. border-radius: 25px;
  611. z-index: 1;
  612. }
  613. }
  614. }
  615. .warp {
  616. width: 100%;
  617. height: 100vh;
  618. display: flex;
  619. justify-content: center;
  620. align-items: center;
  621. .signBox {
  622. width: 85vw;
  623. height: 85vh;
  624. background: #ffffff;
  625. border-radius: 8px;
  626. }
  627. }
  628. .wrapper {
  629. width: 85vw;
  630. height: 85vh;
  631. overflow: hidden;
  632. display: flex;
  633. align-content: center;
  634. flex-direction: row;
  635. justify-content: center;
  636. font-size: 28rpx;
  637. }
  638. .handRight {
  639. display: inline-flex;
  640. align-items: center;
  641. }
  642. .handCenter {
  643. position: fixed;
  644. border: 4rpx dashed #e9e9e9;
  645. flex: 5;
  646. margin-top: 4rpx;
  647. overflow: hidden;
  648. box-sizing: border-box;
  649. width: calc(100% - 84rpx - 200rpx);
  650. height: calc(85vh - 8rpx)
  651. }
  652. .handCenters {
  653. position: fixed;
  654. top: 0;
  655. left: 10000rpx;
  656. flex: 5;
  657. overflow: hidden;
  658. box-sizing: border-box;
  659. }
  660. .handTitle {
  661. transform: rotate(90deg);
  662. flex: 1;
  663. color: #666;
  664. }
  665. .handBtn button {
  666. font-size: 28rpx;
  667. }
  668. .handBtn {
  669. height: 85vh;
  670. display: inline-flex;
  671. flex-direction: column;
  672. justify-content: space-between;
  673. align-content: space-between;
  674. flex: 1;
  675. }
  676. .delBtn {
  677. position: absolute;
  678. top: 380rpx;
  679. left: 46rpx;
  680. transform: rotate(90deg);
  681. color: #666;
  682. }
  683. .subBtn {
  684. position: absolute;
  685. bottom: 158rpx;
  686. left: 46rpx;
  687. display: inline-flex;
  688. transform: rotate(90deg);
  689. background: #008ef6;
  690. color: #fff;
  691. text-align: center;
  692. justify-content: center;
  693. }
  694. /*Peach - 新增 - 保存*/
  695. .saveBtn {
  696. position: absolute;
  697. top: 650rpx;
  698. left: 46rpx;
  699. transform: rotate(90deg);
  700. color: #666;
  701. }
  702. .previewBtn {
  703. position: absolute;
  704. top: 516rpx;
  705. left: 46rpx;
  706. transform: rotate(90deg);
  707. color: #666;
  708. }
  709. .undoBtn {
  710. position: absolute;
  711. top: 780rpx;
  712. left: 46rpx;
  713. transform: rotate(90deg);
  714. color: #666;
  715. }
  716. .emptyInfo {
  717. position: absolute;
  718. bottom: 418rpx;
  719. left: -56rpx;
  720. transform: rotate(90deg);
  721. color: #666;
  722. }
  723. .color_pic {
  724. width: 70rpx;
  725. height: 70rpx;
  726. border-radius: 25px;
  727. position: absolute;
  728. top: 200rpx;
  729. left: 62rpx;
  730. border: 1px solid #ddd;
  731. }
  732. /*Peach - 新增 - 保存*/
  733. .black-select {
  734. width: 60rpx;
  735. height: 60rpx;
  736. position: absolute;
  737. top: 150rpx;
  738. left: 70rpx;
  739. }
  740. .red-select {
  741. width: 60rpx;
  742. height: 60rpx;
  743. position: absolute;
  744. top: 260rpx;
  745. left: 70rpx;
  746. }
  747. </style>