simpleScale.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <view>
  3. <!-- 横向 -->
  4. <view class='wrapper horizontal-box' id='scale-wrapper' :style="{background: stylesObj.bgoutside}" v-if="direction=== 'horizontal'">
  5. <view class='scale-mask' v-if="!scroll"/>
  6. <!-- 选中的横条 -->
  7. <view class='zz triangle' :style="{backgroundColor: stylesObj.lineSelect}"></view>
  8. <scroll-view
  9. class='scroll-view'
  10. :scroll-x="true"
  11. :scroll-left="centerNum"
  12. :scroll-with-animation="true"
  13. @scroll="bindscroll"
  14. show-scrollbar="false"
  15. >
  16. <view class='scroll-wrapper'>
  17. <!-- 左补白 -->
  18. <view class='seat' :style="{width: windowWidth/2 + 'px'}"></view>
  19. <!-- 标尺容器 -->
  20. <view class='scale-container'>
  21. <view class='scale-wrapper'>
  22. <view class='scale-grip'
  23. v-for="(item, index) in grid"
  24. :key="index"
  25. :style="{height:h + 'px', borderColor: stylesObj.line}">
  26. <view class='scale-grip-item'
  27. v-for="(it, idx) in 10"
  28. :key="idx"
  29. :style="{width: single + 'px', height: idx===4?'86%':'50%', borderColor: stylesObj.line}"
  30. />
  31. </view>
  32. </view>
  33. <!-- 标尺数显示,长度:每格长度*个数 -->
  34. <view class='scale-vaule-wrapper' :style="{width: single*10*grid + 'px', color: stylesObj.fontColor, fontSize: stylesObj.fontSize + 'px'}">
  35. <view class='scale-value first-scale-value' :style="{width: single*10 + 'px'}">{{min}}</view>
  36. <view v-if="int" style="display: flex;">
  37. <view
  38. class='scale-vaule'
  39. v-for="(item, index) in grid"
  40. :key="index"
  41. :style="{width:single*10 + 'px'}">{{min+10*(index+1)}}
  42. </view>
  43. </view>
  44. <view v-else style="display: flex;">
  45. <view
  46. class='scale-vaule'
  47. v-for="(it, index) in grid"
  48. :key="index"
  49. :style="{width: single*10 + 'px'}">{{min+(index+1)}}
  50. </view>
  51. </view>
  52. </view>
  53. </view>
  54. <!-- 右补白 -->
  55. <view class='seat' :style="{width: windowWidth/2 + 'px'}"/>
  56. </view>
  57. </scroll-view>
  58. </view>
  59. <!-- 竖向 -->
  60. <view class='wrapper vertical-box' id='scale-wrapper' :style="{background: stylesObj.bgoutside}" v-else-if="direction === 'vertical'">
  61. <view class='scale-mask' v-if="!scroll"/>
  62. <view class='zz' :style="{backgroundColor: stylesObj.lineSelect}"/>
  63. <scroll-view
  64. class='scroll-view'
  65. style="height: 600rpx;"
  66. :scroll-y="true"
  67. :scroll-top="centerNum"
  68. :scroll-with-animation='true'
  69. @scroll="bindscroll"
  70. :show-scrollbar="false"
  71. :enhanced="true">
  72. <view class='scroll-wrapper'>
  73. <!-- 左补白 -->
  74. <view class='seat' :style="{height: windowHeight/2 - single*10/2 + 'px'}"/>
  75. <!-- 标尺容器 -->
  76. <view class='scale-container'>
  77. <view class='scale-wrapper' :style="{height: single*10*grid + 'px', paddingTop: single*10/2 + 'px'}">
  78. <view class='scale-grip'
  79. v-for="(item, index) in grid"
  80. :key="index"
  81. :style="{borderColor: stylesObj.line}">
  82. <view class='scale-grip-item'
  83. v-for="(it, idx) in 10"
  84. :key="idx"
  85. :style="{height: single + 'px', width: (idx==4||idx==9) ? '80':'60' + '%', borderColor: stylesObj.line}"
  86. />
  87. </view>
  88. </view>
  89. <!-- 标尺数显示,长度:每格长度*个数 -->
  90. <view class='scale-vaule-wrapper'
  91. :style="{height: single*10*(grid+1) + 'px', color: stylesObj.fontColor, fontSize: stylesObj.fontSize + 'px'}">
  92. <view class='scale-value' :style="{height: single*10 + 'px', lineHeight: single*10 + 'px'}">{{min}}</view>
  93. <view v-if="int">
  94. <view class='scale-vaule'
  95. v-for="(item, index) in grid"
  96. :key="index"
  97. :style="{height: single*10 + 'px', lineHeight: single*10 + 'px'}">{{min+10*(index+1)}}
  98. </view>
  99. </view>
  100. <view v-else>
  101. <view class='scale-vaule'
  102. v-for="(it, index) in grid"
  103. :key="index"
  104. :style="{height: single*10 + 'px', lineHeight: single*10 + 'px'}">{{min+(index+1)}}
  105. </view>
  106. </view>
  107. </view>
  108. </view>
  109. <!-- 右补白 -->
  110. <view class='seat' :style="{height: windowHeight/2 - single*10/2 + 'px'}"/>
  111. </view>
  112. </scroll-view>
  113. </view>
  114. </view>
  115. </template>
  116. <script>
  117. /**
  118. minVal[number] 默认值 0, // 最小值
  119. maxVal[number] 默认值 100, // 最大值
  120. int[boolean] 默认值 true, // 是否开启整数模式 ,false为小数模式 true 整数模式
  121. single[number] 默认值 10, // 单个格子的实际长度(单位rpx)
  122. h[number] 默认值 0,// 自定义高度 初始值为80
  123. active[null] 默认值 center ,// 自定义选中位置 (三个值 minVal, maxVal ,center , 范围内合法数值)
  124. styles[object] // 自定义卡尺样式
  125. */
  126. export default {
  127. name: '',
  128. components: {},
  129. props: {
  130. // 最小值
  131. minVal: {
  132. type: Number,
  133. default: 0,
  134. },
  135. // 最大值
  136. maxVal: {
  137. type: Number,
  138. default: 100,
  139. },
  140. // 是否开启整数模式
  141. int: {
  142. type: Boolean,
  143. default: false,
  144. },
  145. // 每个格子的实际行度 (单位px ,相对默认值)
  146. single: {
  147. type: Number,
  148. default: 10,
  149. },
  150. // 高度
  151. h: {
  152. type: Number,
  153. default: 40,
  154. },
  155. // 是否禁止滚动
  156. scroll: {
  157. type: Boolean,
  158. default: true,
  159. },
  160. // 方向
  161. direction: {
  162. type: String,
  163. default: 'horizontal',
  164. },
  165. // 当前选中
  166. active: {
  167. type: null,
  168. default: '0',
  169. },
  170. styles: {
  171. type: Object,
  172. default:()=> {},
  173. },
  174. },
  175. data() {
  176. return {
  177. defaultStyles: {
  178. line: '#dbdbdb', // 刻度颜色
  179. bginner: '#fbfbfb', // 前景色颜色
  180. bgoutside: '#ffffff', // 背景色颜色
  181. lineSelect: '#FF7700', // 选中线颜色
  182. fontColor: '#404040', // 刻度数字颜色
  183. fontSize: 16, // 字体大小
  184. },
  185. rul: {},
  186. windowHeight: 0,
  187. windowWidth: '',
  188. horizontalTime: null,
  189. verticalTime: null,
  190. grid: '',
  191. centerNum: '',
  192. stylesObj: {},
  193. min: 0,
  194. max: 100,
  195. };
  196. },
  197. computed: {},
  198. watch: {},
  199. onReady() {
  200. // const min = parseInt(this.min, 10) || 0;
  201. // const max = parseInt(this.max, 10) || 100;
  202. // this.min = min;
  203. // this.max = max;
  204. // this.init();
  205. },
  206. created() {
  207. },
  208. mounted() {
  209. // this.init();
  210. },
  211. methods: {
  212. // 初始化
  213. init() {
  214. // 设置默认值
  215. // const min = this.min || 0;
  216. // const max = this.max || 0;
  217. const min = parseInt(this.minVal, 10) || 0;
  218. const max = parseInt(this.maxVal, 10) || 100;
  219. this.min = min;
  220. this.max = max;
  221. /**
  222. * grid 外层的刻度尺,里面有10个小刻度尺(10个小刻度尺直接拿10遍历出来)
  223. * 整数:
  224. * 需要除以10,此时里面的一个小刻度尺代表1
  225. * 例如:30-80 此时需要5个外层刻度尺。
  226. * 小数:
  227. * 不需要除以10,此时里面的一个小刻度尺代表0.1
  228. * 例如:30-80 此时需要50个外层刻度尺。
  229. *
  230. */
  231. let grid;
  232. if (this.int) {
  233. grid = (max - min) / 10;
  234. } else {
  235. grid = (max - min);
  236. }
  237. this.stylesObj = Object.assign(this.defaultStyles, this.styles);
  238. this.grid = grid;
  239. // 当前选中的 active
  240. let activeVal = this.selectActiveVal();
  241. if (activeVal < min || activeVal > max) { // 默认数字不合理
  242. activeVal = (min + max) / 2;
  243. }
  244. if (this.int) {
  245. let diff = (activeVal - min) / 10; // 移动diff格
  246. /* eslint-disable-next-line */
  247. if (diff < 0 || isNaN(diff) || !diff) diff = 0;
  248. // this.single 每个小格子长度
  249. const centerNum = diff * this.single * 10;
  250. setTimeout(() => { this.centerNum = centerNum; }, 200);
  251. } else {
  252. const diff1 = (activeVal - min) * 10; // 移动diff格
  253. const centerNum = diff1 * this.single;
  254. setTimeout(() => { this.centerNum = centerNum; }, 200);
  255. }
  256. // 获取节点信息,获取节点宽度
  257. const query = uni.createSelectorQuery().in(this);
  258. query.select('#scale-wrapper').boundingClientRect(() => {
  259. // res.top; // 这个组件内 #the-id 节点的上边界坐标
  260. }).exec((e) => {
  261. this.windowWidth = e[0].width;
  262. this.windowHeight = e[0].height;
  263. });
  264. },
  265. // 给定的选中默认值
  266. selectActiveVal() {
  267. // 当前选中位置设置
  268. let activeVal;
  269. if (this.active === 'min') {
  270. activeVal = this.min;
  271. } else if (this.active === 'max') {
  272. activeVal = this.max;
  273. } else if (this.active === 'center') {
  274. activeVal = (this.min + this.max) / 2;
  275. } else {
  276. activeVal = this.active ? this.active : this.min;
  277. }
  278. return activeVal;
  279. },
  280. // 滚动
  281. bindscroll(e) {
  282. // 移动的距离
  283. let offset = 0;
  284. if (this.direction === 'vertical') {
  285. offset = e.detail.scrollTop;
  286. } else {
  287. offset = e.detail.scrollLeft;
  288. }
  289. // 选中的值
  290. let value;
  291. if (this.int) {
  292. value = this.min + (offset / this.single);
  293. value = Math.round(value);
  294. if (value > this.max) value = this.max;
  295. this.$emit('value', value);
  296. const centerNum = (value - this.min) * this.single + Math.random() ** 10;
  297. clearTimeout(this.horizontalTime);
  298. this.horizontalTime = setTimeout(() => {
  299. this.centerNum = centerNum;
  300. this.$emit('value', value);
  301. }, 100);
  302. } else {
  303. value = this.min + ((offset / this.single) / 10);
  304. value = value.toFixed(1);
  305. if (value > this.max) value = this.max;
  306. this.$emit('value', value);
  307. const centerNum = (value - this.min) * this.single * 10 + Math.random() ** 10;
  308. clearTimeout(this.verticalTime);
  309. this.verticalTime = setTimeout(() => {
  310. this.centerNum = centerNum;
  311. this.$emit('value', value);
  312. }, 100);
  313. }
  314. },
  315. },
  316. };
  317. </script>
  318. <style lang="less" scoped>
  319. view,text {
  320. box-sizing: border-box;
  321. }
  322. .wrapper {
  323. position: relative;
  324. }
  325. .scale-mask {
  326. width: 100%;
  327. height: 100%;
  328. position: absolute;
  329. z-index: 100;
  330. }
  331. .horizontal-box {
  332. // padding-top: 7%;
  333. .scroll-wrapper {
  334. position: relative;
  335. display: flex;
  336. }
  337. .zz {
  338. position: absolute;
  339. left: 50%;
  340. top: 0;
  341. transform: translate(-50%);
  342. height: 100%;
  343. width: 2rpx;
  344. background-color: #FF7700;
  345. z-index: 10;
  346. }
  347. .triangle::after {
  348. position: absolute;
  349. left: 50%;
  350. top: 0;
  351. transform: translateX(-50%);
  352. content: "";
  353. height: 0;
  354. border-top: 16rpx solid #FF7700;
  355. border-bottom: 16rpx solid transparent;
  356. border-left: 10rpx solid transparent;
  357. border-right: 10rpx solid transparent;
  358. }
  359. .scale-wrapper {
  360. display: flex;
  361. border-top: 1px solid #dddddd;
  362. }
  363. .scale-grip {
  364. position: relative;
  365. height: 100rpx;
  366. display: flex;
  367. &::before {
  368. content: "";
  369. position: absolute;
  370. top: 0;
  371. border-width: 1px;
  372. border-color: inherit;
  373. border-style: solid;
  374. height: 100%;
  375. transform: translateX(-50%);
  376. left: 0rpx;
  377. }
  378. &:last-child {
  379. &::after {
  380. content: "";
  381. position: absolute;
  382. top: 0;
  383. right: 0;
  384. border-width: 1px;
  385. border-color: inherit;
  386. border-style: solid;
  387. height: 100%;
  388. }
  389. }
  390. }
  391. .scale-grip-item {
  392. height: 50%;
  393. padding-top: 10rpx;
  394. &:not(:last-child) {
  395. border-right: 1px solid #000000;
  396. }
  397. }
  398. .scale-vaule-wrapper {
  399. position: relative;
  400. display: flex;
  401. text-align: center;
  402. }
  403. .scale-vaule {
  404. padding: 30rpx 0;
  405. transform: translateX(50%);
  406. }
  407. .first-scale-value {
  408. position: absolute;
  409. left: 0;
  410. bottom: 0;
  411. padding: 20rpx 0;
  412. transform: translateX(-50%);
  413. }
  414. .seat {
  415. flex-shrink: 0;
  416. box-sizing: border-box;
  417. border-top: 1px solid #ddd;
  418. }
  419. }
  420. /* .scale-container{
  421. display: flex;
  422. } */
  423. .vertical-box {
  424. height: 100%;
  425. .scroll-wrapper {
  426. position: relative;
  427. }
  428. .scroll-view {
  429. height: 100%;
  430. }
  431. .zz {
  432. position: absolute;
  433. top: 50%;
  434. left: 0;
  435. transform: translate(-50%);
  436. width: 40%;
  437. height: 2px;
  438. background-color: #FF7700;
  439. z-index: 10;
  440. }
  441. .scale-container {
  442. display: flex;
  443. width: 100%;
  444. }
  445. .scale-wrapper {
  446. flex: 1;
  447. }
  448. .scale-grip {
  449. position: relative;
  450. border-left: 1px solid #000000;
  451. &:first-child {
  452. &::before {
  453. content: "";
  454. position: absolute;
  455. top: 0;
  456. left: 0;
  457. width: 80%;
  458. height: 0;
  459. border-top: 1px solid #dbdbdb;
  460. }
  461. }
  462. }
  463. .scale-grip-item {
  464. height: 60%;
  465. padding-top: 10rpx;
  466. border-bottom: 1px solid #000000;
  467. }
  468. .scale-vaule-wrapper {
  469. position: relative;
  470. text-align: left;
  471. flex: 1;
  472. }
  473. .scale-vaule {
  474. }
  475. }
  476. /* .vertical-box .scale-grip:last-child::after{
  477. content: "";
  478. position: absolute;
  479. top: 0;
  480. left:0;
  481. border-width: 1px;
  482. border-color: inherit;
  483. border-style: solid;
  484. width: 100%;
  485. } */
  486. /* .vertical-box .first-scale-value{
  487. position: absolute;
  488. left: 0;
  489. bottom: 0;
  490. padding: 20rpx 0;
  491. transform: translateX(-50%);
  492. } */
  493. /* .vertical-box .seat {
  494. flex-shrink: 0;
  495. box-sizing: border-box;
  496. } */
  497. </style>