simpleScale.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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. active(index){
  200. this.init();
  201. }
  202. },
  203. onReady() {
  204. // const min = parseInt(this.min, 10) || 0;
  205. // const max = parseInt(this.max, 10) || 100;
  206. // this.min = min;
  207. // this.max = max;
  208. },
  209. created() {
  210. },
  211. mounted() {
  212. //this.init();
  213. },
  214. methods: {
  215. // 初始化
  216. init() {
  217. // 设置默认值
  218. // const min = this.min || 0;
  219. // const max = this.max || 0;
  220. const min = parseInt(this.minVal, 10) || 0;
  221. const max = parseInt(this.maxVal, 10) || 100;
  222. this.min = min;
  223. this.max = max;
  224. /**
  225. * grid 外层的刻度尺,里面有10个小刻度尺(10个小刻度尺直接拿10遍历出来)
  226. * 整数:
  227. * 需要除以10,此时里面的一个小刻度尺代表1
  228. * 例如:30-80 此时需要5个外层刻度尺。
  229. * 小数:
  230. * 不需要除以10,此时里面的一个小刻度尺代表0.1
  231. * 例如:30-80 此时需要50个外层刻度尺。
  232. *
  233. */
  234. let grid;
  235. if (this.int) {
  236. grid = (max - min) / 10;
  237. } else {
  238. grid = (max - min);
  239. }
  240. this.stylesObj = Object.assign(this.defaultStyles, this.styles);
  241. this.grid = grid;
  242. // 当前选中的 active
  243. let activeVal = this.selectActiveVal();
  244. if (activeVal < min || activeVal > max) { // 默认数字不合理
  245. activeVal = (min + max) / 2;
  246. }
  247. if (this.int) {
  248. let diff = (activeVal - min) / 10; // 移动diff格
  249. /* eslint-disable-next-line */
  250. if (diff < 0 || isNaN(diff) || !diff) diff = 0;
  251. // this.single 每个小格子长度
  252. const centerNum = diff * this.single * 10;
  253. setTimeout(() => { this.centerNum = centerNum; }, 200);
  254. } else {
  255. const diff1 = (activeVal - min) * 10; // 移动diff格
  256. const centerNum = diff1 * this.single;
  257. setTimeout(() => { this.centerNum = centerNum; }, 200);
  258. }
  259. // 获取节点信息,获取节点宽度
  260. const query = uni.createSelectorQuery().in(this);
  261. query.select('#scale-wrapper').boundingClientRect(() => {
  262. // res.top; // 这个组件内 #the-id 节点的上边界坐标
  263. }).exec((e) => {
  264. this.windowWidth = e[0].width;
  265. this.windowHeight = e[0].height;
  266. });
  267. },
  268. // 给定的选中默认值
  269. selectActiveVal() {
  270. // 当前选中位置设置
  271. let activeVal;
  272. if (this.active === 'min') {
  273. activeVal = this.min;
  274. } else if (this.active === 'max') {
  275. activeVal = this.max;
  276. } else if (this.active === 'center') {
  277. activeVal = (this.min + this.max) / 2;
  278. } else {
  279. activeVal = this.active ? this.active : this.min;
  280. }
  281. return activeVal;
  282. },
  283. // 滚动
  284. bindscroll(e) {
  285. // 移动的距离
  286. let offset = 0;
  287. if (this.direction === 'vertical') {
  288. offset = e.detail.scrollTop;
  289. } else {
  290. offset = e.detail.scrollLeft;
  291. }
  292. // 选中的值
  293. let value;
  294. if (this.int) {
  295. value = this.min + (offset / this.single);
  296. value = Math.round(value);
  297. if (value > this.max) value = this.max;
  298. this.$emit('value', value);
  299. const centerNum = (value - this.min) * this.single + Math.random() ** 10;
  300. clearTimeout(this.horizontalTime);
  301. this.horizontalTime = setTimeout(() => {
  302. this.centerNum = centerNum;
  303. this.$emit('value', value);
  304. }, 100);
  305. } else {
  306. value = this.min + ((offset / this.single) / 10);
  307. value = value.toFixed(1);
  308. if (value > this.max) value = this.max;
  309. this.$emit('value', value);
  310. const centerNum = (value - this.min) * this.single * 10 + Math.random() ** 10;
  311. clearTimeout(this.verticalTime);
  312. this.verticalTime = setTimeout(() => {
  313. this.centerNum = centerNum;
  314. this.$emit('value', value);
  315. }, 100);
  316. }
  317. },
  318. },
  319. };
  320. </script>
  321. <style lang="less" scoped>
  322. view,text {
  323. box-sizing: border-box;
  324. }
  325. .wrapper {
  326. position: relative;
  327. }
  328. .scale-mask {
  329. width: 100%;
  330. height: 100%;
  331. position: absolute;
  332. z-index: 100;
  333. }
  334. .horizontal-box {
  335. // padding-top: 7%;
  336. .scroll-wrapper {
  337. position: relative;
  338. display: flex;
  339. }
  340. .zz {
  341. position: absolute;
  342. left: 50%;
  343. top: 0;
  344. transform: translate(-50%);
  345. height: 100%;
  346. width: 2rpx;
  347. background-color: #FF7700;
  348. z-index: 10;
  349. }
  350. .triangle::after {
  351. position: absolute;
  352. left: 50%;
  353. top: 0;
  354. transform: translateX(-50%);
  355. content: "";
  356. height: 0;
  357. border-top: 16rpx solid #FF7700;
  358. border-bottom: 16rpx solid transparent;
  359. border-left: 10rpx solid transparent;
  360. border-right: 10rpx solid transparent;
  361. }
  362. .scale-wrapper {
  363. display: flex;
  364. border-top: 1px solid #dddddd;
  365. }
  366. .scale-grip {
  367. position: relative;
  368. height: 100rpx;
  369. display: flex;
  370. &::before {
  371. content: "";
  372. position: absolute;
  373. top: 0;
  374. border-width: 1px;
  375. border-color: inherit;
  376. border-style: solid;
  377. height: 100%;
  378. transform: translateX(-50%);
  379. left: 0rpx;
  380. }
  381. &:last-child {
  382. &::after {
  383. content: "";
  384. position: absolute;
  385. top: 0;
  386. right: 0;
  387. border-width: 1px;
  388. border-color: inherit;
  389. border-style: solid;
  390. height: 100%;
  391. }
  392. }
  393. }
  394. .scale-grip-item {
  395. height: 50%;
  396. padding-top: 10rpx;
  397. &:not(:last-child) {
  398. border-right: 1px solid #000000;
  399. }
  400. }
  401. .scale-vaule-wrapper {
  402. position: relative;
  403. display: flex;
  404. text-align: center;
  405. }
  406. .scale-vaule {
  407. padding: 30rpx 0;
  408. transform: translateX(50%);
  409. }
  410. .first-scale-value {
  411. position: absolute;
  412. left: 0;
  413. bottom: 0;
  414. padding: 20rpx 0;
  415. transform: translateX(-50%);
  416. }
  417. .seat {
  418. flex-shrink: 0;
  419. box-sizing: border-box;
  420. border-top: 1px solid #ddd;
  421. }
  422. }
  423. /* .scale-container{
  424. display: flex;
  425. } */
  426. .vertical-box {
  427. height: 100%;
  428. .scroll-wrapper {
  429. position: relative;
  430. }
  431. .scroll-view {
  432. height: 100%;
  433. }
  434. .zz {
  435. position: absolute;
  436. top: 50%;
  437. left: 0;
  438. transform: translate(-50%);
  439. width: 40%;
  440. height: 2px;
  441. background-color: #FF7700;
  442. z-index: 10;
  443. }
  444. .scale-container {
  445. display: flex;
  446. width: 100%;
  447. }
  448. .scale-wrapper {
  449. flex: 1;
  450. }
  451. .scale-grip {
  452. position: relative;
  453. border-left: 1px solid #000000;
  454. &:first-child {
  455. &::before {
  456. content: "";
  457. position: absolute;
  458. top: 0;
  459. left: 0;
  460. width: 80%;
  461. height: 0;
  462. border-top: 1px solid #dbdbdb;
  463. }
  464. }
  465. }
  466. .scale-grip-item {
  467. height: 60%;
  468. padding-top: 10rpx;
  469. border-bottom: 1px solid #000000;
  470. }
  471. .scale-vaule-wrapper {
  472. position: relative;
  473. text-align: left;
  474. flex: 1;
  475. }
  476. .scale-vaule {
  477. }
  478. }
  479. /* .vertical-box .scale-grip:last-child::after{
  480. content: "";
  481. position: absolute;
  482. top: 0;
  483. left:0;
  484. border-width: 1px;
  485. border-color: inherit;
  486. border-style: solid;
  487. width: 100%;
  488. } */
  489. /* .vertical-box .first-scale-value{
  490. position: absolute;
  491. left: 0;
  492. bottom: 0;
  493. padding: 20rpx 0;
  494. transform: translateX(-50%);
  495. } */
  496. /* .vertical-box .seat {
  497. flex-shrink: 0;
  498. box-sizing: border-box;
  499. } */
  500. </style>