sapling-vue-scale.vue 14 KB

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