tm-sliders.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <template>
  2. <view class="d-block tm-slider " :class="[step > 0 ? 'pb-36' : 'pb-24']"
  3. :style="{ height: vertical ? heightpx + 'px' : 'auto', width: vertical ? '5px' : 'auto' }">
  4. <view class=" tm-slider-id " :class="[vertical ? 'vertical' : 'flex-between']">
  5. <!-- 左边label -->
  6. <view v-if="showLeft" class="label_slider left flex-col text-size-xs text-grey-darken-1"
  7. :class="[vertical ? '' : 'flex-center']">
  8. <slot name="left" :data="{ value: value, color: color_tmeme, icon: leftIcon,max:max }">
  9. <tm-icons v-if="leftIcon && !vertical" size="28" :name="leftIcon" :color="color_tmeme"></tm-icons>
  10. <view class="text-size-xs text-grey-darken-1 ">{{ value[0] }}</view>
  11. </slot>
  12. </view>
  13. <!-- 条子内容 -->
  14. <view class="slider_id "
  15. :style="{ width: vertical ? 'auto' : sliderWidth + 'px', height: vertical ? sliderWidth + 'px' : '5px' }">
  16. <view class="slider_id_bg round-10" :class="[bgColor,black_tmeme?'bk':'']"></view>
  17. <view
  18. :style="{left:vertical?'0':bar_minLeft+'px',
  19. top:vertical?bar_minLeft+'px':0,
  20. width: vertical ? '100%' : active_width + '%', height: vertical ? active_width + '%' : '100%' }"
  21. class="slider_id_active round-10 " :class="[color_tmeme,black_tmeme?'bk':'']"></view>
  22. <view :style="{ left: vertical ? 0 : barLeft + 'px', top: vertical ? barLeft + 'px' : 0 }"
  23. @touchcancel="barEnd" @touchstart="barStart" @touchend="barEnd" @touchmove.stop.prevent="barMove"
  24. @mouseleave="barEnd" @mousedown="barStart" @mouseup="barEnd" @mousemove.stop.prevent="barMove"
  25. class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]">
  26. <view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']">
  27. </view>
  28. <view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']">
  29. <slot name="tipsLeft" :data="value">{{ value[0] }}</slot>
  30. </view>
  31. </view>
  32. <view :style="{ left: vertical ? 0 : barLeft_1 + 'px', top: vertical ? barLeft_1 + 'px' : 0 }"
  33. @touchcancel="barEnd_1" @touchstart="barStart_1" @touchend="barEnd_1" @touchmove.stop.prevent="barMove_1"
  34. @mouseleave="barEnd_1" @mousedown="barStart_1" @mouseup="barEnd_1" @mousemove.stop.prevent="barMove_1"
  35. class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]">
  36. <view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']">
  37. </view>
  38. <view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']">
  39. <slot name="tipsRight" :data="value">{{ value[1] }}</slot>
  40. </view>
  41. </view>
  42. <!-- 步长刻度尺 -->
  43. <view class="kdc_wk ">
  44. <view v-for="(item, index) in kdcNum" :key="index"
  45. :style="{ left: vertical ? 0 : item.left, top: vertical ? item.left : 0 }" :class="[color_tmeme,black_tmeme?'bk':'']"
  46. class="kdc_wk_item rounded border-white-a-1 ">
  47. <view class="kdc_wk_item_label pl-0" :class="[vertical ? 'pl-24' : 'pa-24']">
  48. <text class="text-size-xs text-grey-darken-1 ">{{ item.kedu }}</text>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. <!-- 右边label -->
  54. <view v-if="showRight" class="label_slider right flex-col text-size-xs text-grey-darken-1"
  55. :class="[vertical ? 'flex-end' : 'flex-center']">
  56. <slot name="right" :data="{ value: value, color: color_tmeme, icon: rightIcon,max:max }">
  57. <tm-icons v-if="rightIcon && !vertical" size="28" :name="rightIcon" :color="color_tmeme"></tm-icons>
  58. <view class="text-size-xs text-grey-darken-1 ">{{ max }}</view>
  59. </slot>
  60. </view>
  61. </view>
  62. </view>
  63. </template>
  64. <script>
  65. /**
  66. * 滑块
  67. * @property {Number} value = [0] 赋值,如果需要同步使用value.sync推荐使用v-model绑定。
  68. * @property {Boolean} vertical = [true|false] 默认:false,是否启用竖向模式。
  69. * @property {Number} height = [200] 默认:200,竖向模式时才有作用。
  70. * @property {Number} width = [] 默认:0,横向时起作用,如果为0自动获取外层宽度。
  71. * @property {Boolean} show-left = [true|false] 默认:false,显示左边数据。
  72. * @property {Boolean} show-right = [true|false] 默认:false,显示右边数据。
  73. * @property {Number} max = [] 默认:100,显示的最大刻度。
  74. * @property {Number} value-diog = [] 默认:0,取值小数点后几位
  75. * @property {Boolean} disabled = [true|false] 默认:false, 是否禁用。
  76. * @property {Boolean} showTip = [true|false] 默认:false, 始终显示进度标签。
  77. * @property {Number} step = [10|20] 默认:0, 步长,设置请尽量大于20.太小滑动容易过小出问题。
  78. * @property {String} color = [primary] 默认:primary, 主题颜色名称。
  79. * @property {String} bg-color = [grey-lighten-2] 默认:grey-lighten-2, 底部不活动的背景色,颜色名称。
  80. * @property {String} left-icon = [] 默认:icon-minus, 左边图标
  81. * @property {String} right-icon = [] 默认:icon-plus, 右边图标
  82. * @property {Function} change 同v-model和value相等的参数,变动时触发。
  83. * @example <tm-slider v-model="checked" ></tm-slider>
  84. *
  85. */
  86. import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
  87. export default {
  88. components:{tmIcons},
  89. name: "tm-sliders",
  90. model: {
  91. prop: 'value',
  92. event: 'input'
  93. },
  94. props: {
  95. black: {
  96. type: Boolean,
  97. default: null
  98. },
  99. vertical: Boolean, //是否启用竖向模式。需要和height配合使用。
  100. // 单位upx.
  101. height: {
  102. type: Number,
  103. default: 200
  104. },
  105. width: {
  106. type: Number,
  107. default: 0
  108. },
  109. showLeft: Boolean, //显示左边
  110. showRight: Boolean, //显示右边
  111. // 最大刻度。
  112. max: {
  113. type: Number,
  114. default: 100
  115. },
  116. // 默认的数据。不能大于max.,使用.sync修饰。双向绑定数据。
  117. value: {
  118. type: Array,
  119. default: ()=>{
  120. return [0,0];
  121. }
  122. },
  123. // 取值小数点后几位。默认为0
  124. valueDiog: {
  125. type: Number,
  126. default: 0
  127. },
  128. // 是否禁用。
  129. disabled: Boolean,
  130. //步长。默认为0,设置请尽量大于0.太小滑动容易过小出问题。
  131. step: {
  132. type: Number,
  133. default: 0
  134. },
  135. // 主题颜色名称。
  136. color: {
  137. type: String,
  138. default: 'primary'
  139. },
  140. // 底部不活动的背景色,颜色名称。。
  141. bgColor: {
  142. type: String,
  143. default: 'grey-lighten-2'
  144. },
  145. // 左边图标名称。
  146. leftIcon: {
  147. type: String | Boolean,
  148. default: 'icon-minus'
  149. },
  150. // 右边图标名称。
  151. rightIcon: {
  152. type: String | Boolean,
  153. default: 'icon-plus'
  154. },
  155. // 始终显示进度提示窗
  156. showTip: {
  157. type: Boolean,
  158. default: false
  159. },
  160. // 跟随主题色的改变而改变。
  161. fllowTheme:{
  162. type:Boolean|String,
  163. default:true
  164. }
  165. },
  166. watch: {
  167. value: {
  168. handler(val) {
  169. let rdl = this.sliderWidth * (Math.abs(val[0]) / Math.abs(this.max));
  170. this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
  171. rdl = this.sliderWidth * (Math.abs(val[1]) / Math.abs(this.max));
  172. this.barLeft_1 = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
  173. },
  174. deep:true
  175. },
  176. barLeft: function() {
  177. let value = (this.barLeft / this.sliderWidth) * this.max;
  178. if (!isNaN(value)) {
  179. if (this.valueDiog > 0) {
  180. let s = value[0].toString();
  181. let st = s.split('.');
  182. let ruslt = 0;
  183. if (st.length > 1) {
  184. ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog));
  185. } else {
  186. ruslt = parseFloat(s);
  187. }
  188. this.$emit('update:value', [ruslt,this.value[1]]);
  189. this.$emit('input', [ruslt,this.value[1]]);
  190. this.$emit('change', [ruslt,this.value[1]]);
  191. } else {
  192. this.$emit('update:value', [parseInt(value),this.value[1]]);
  193. this.$emit('input',[parseInt(value),this.value[1]]);
  194. this.$emit('change', Math.abs(parseInt(value-this.value[1])));
  195. }
  196. }
  197. },
  198. barLeft_1: function() {
  199. let value = (this.barLeft_1 / this.sliderWidth) * this.max;
  200. if (!isNaN(value)) {
  201. if (this.valueDiog > 0) {
  202. let s = value.toString();
  203. let st = s.split('.');
  204. let ruslt = 0;
  205. if (st.length > 1) {
  206. ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog));
  207. } else {
  208. ruslt = parseFloat(s);
  209. }
  210. this.$emit('update:value', [this.value[0],ruslt]);
  211. this.$emit('input', [this.value[0],ruslt]);
  212. this.$emit('change', Math.abs(this.value[0]-ruslt));
  213. } else {
  214. this.$emit('update:value', [this.value[0],parseInt(value)]);
  215. this.$emit('input', [this.value[0],parseInt(value)]);
  216. this.$emit('change', Math.abs(parseInt(value-this.value[0])));
  217. }
  218. }
  219. }
  220. },
  221. computed: {
  222. black_tmeme: function() {
  223. if (this.black !== null) return this.black;
  224. return this.$tm.vx.state().tmVuetify.black;
  225. },
  226. // 计算刻度尺的数量
  227. kdcNum: function() {
  228. if (Math.abs(this.step) <= 0) return [];
  229. let jlv = Math.abs(this.step) / Math.abs(this.max);
  230. let left_width = jlv * (this.sliderWidth - 0);
  231. let kd_num = parseInt(this.sliderWidth / left_width);
  232. let ar = [];
  233. for (let i = 0; i <= kd_num; i++) {
  234. ar.push({
  235. index: i, //顺序
  236. left: i * left_width + 'px', //距离左边距离
  237. kedu: this.step * i //当前刻度数量。
  238. });
  239. }
  240. return ar;
  241. },
  242. //比例
  243. active_width: function() {
  244. return (Math.abs(this.barLeft - this.barLeft_1) / this.sliderWidth) * 100;
  245. },
  246. heightpx: function() {
  247. return uni.upx2px(this.height);
  248. },
  249. bar_minLeft:function(){
  250. let value = this.barLeft;
  251. if(this.barLeft >= this.barLeft_1){
  252. value = this.barLeft_1;
  253. }
  254. return value;
  255. },
  256. color_tmeme:function(){
  257. if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
  258. return this.$tm.vx.state().tmVuetify.color;
  259. }
  260. return this.color;
  261. },
  262. },
  263. data() {
  264. return {
  265. sliderWidth: 0,
  266. x: 0,
  267. startMove: false,
  268. barLeft: 0,
  269. barLeft_1: 0,
  270. isError: false,
  271. showTopTips: false
  272. };
  273. },
  274. async mounted() {
  275. this.$nextTick(async function() {
  276. await this.getwidth();
  277. if (Math.abs(this.value[1]) >= Math.abs(this.max)) {
  278. this.isError = true;
  279. return;
  280. }
  281. let rdl = this.sliderWidth * (Math.abs(this.value[0]) / Math.abs(this.max));
  282. this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
  283. let rdl_1 = this.sliderWidth * (Math.abs(this.value[1]) / Math.abs(this.max));
  284. this.barLeft_1 = rdl_1 >= this.sliderWidth || rdl_1 < 0 ? this.sliderWidth : rdl_1;
  285. });
  286. },
  287. methods: {
  288. barStart(e) {
  289. if (this.disabled || this.isError) return;
  290. if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
  291. this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft;
  292. this.startMove = true;
  293. this.showTopTips = true;
  294. }else{
  295. if (e.changedTouches.length > 0) {
  296. this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft;
  297. this.startMove = true;
  298. this.showTopTips = true;
  299. }
  300. }
  301. this.$nextTick(function(){
  302. this.$emit('start',this.value)
  303. })
  304. },
  305. barEnd(e) {
  306. if (this.disabled || this.isError) return;
  307. this.startMove = false;
  308. this.showTopTips = false;
  309. this.$nextTick(function(){
  310. this.$emit('end',this.value)
  311. })
  312. },
  313. barMove(e) {
  314. if (this.disabled || this.isError) return;
  315. if (!this.startMove) return;
  316. let left = 0;
  317. if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
  318. left = (this.vertical ? e.pageY : e.pageX) - this.x;
  319. }else{
  320. left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x;
  321. }
  322. if (left <= 0) {
  323. this.barLeft = 0;
  324. return;
  325. }
  326. if (left >= this.sliderWidth) {
  327. this.barLeft = this.sliderWidth;
  328. return;
  329. }
  330. let nowStep = parseInt(Math.abs(left - this.barLeft));
  331. let bdi = parseInt((this.step / this.max) * this.sliderWidth);
  332. if (nowStep >= bdi) {
  333. // 每一小段的值。
  334. if (this.step !== 0) {
  335. let kedud = this.sliderWidth / (this.max / this.step);
  336. this.barLeft = Math.round(left / kedud) * kedud;
  337. } else {
  338. this.barLeft = left;
  339. }
  340. }
  341. },
  342. barStart_1(e) {
  343. if (this.disabled || this.isError) return;
  344. if(e.type.indexOf('mouse')>-1){
  345. this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft_1;
  346. this.startMove = true;
  347. this.showTopTips = true;
  348. }else{
  349. if (e.changedTouches.length > 0) {
  350. this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft_1;
  351. this.startMove = true;
  352. this.showTopTips = true;
  353. }
  354. }
  355. },
  356. barEnd_1(e) {
  357. if (this.disabled || this.isError) return;
  358. this.startMove = false;
  359. this.showTopTips = false;
  360. },
  361. barMove_1(e) {
  362. if (this.disabled || this.isError) return;
  363. if (!this.startMove) return;
  364. let left = 0;
  365. if(e.type.indexOf('mouse')>-1){
  366. left = (this.vertical ? e.pageY : e.pageX) - this.x;
  367. }else{
  368. left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x;
  369. }
  370. if (left <= 0) {
  371. this.barLeft_1 = 0;
  372. return;
  373. }
  374. if (left >= this.sliderWidth) {
  375. this.barLeft_1 = this.sliderWidth;
  376. return;
  377. }
  378. let nowStep = parseInt(Math.abs(left - this.barLeft_1));
  379. let bdi = parseInt((this.step / this.max) * this.sliderWidth);
  380. if (nowStep >= bdi) {
  381. // 每一小段的值。
  382. if (this.step !== 0) {
  383. let kedud = this.sliderWidth / (this.max / this.step);
  384. this.barLeft_1 = Math.round(left / kedud) * kedud;
  385. } else {
  386. this.barLeft_1 = left;
  387. }
  388. }
  389. },
  390. async getwidth() {
  391. let res = await this.$Querey('.tm-slider-id', this).catch(e=>{});
  392. res[0].width = res[0].width||uni.upx2px(this.width);
  393. res[0].height = res[0].height||uni.upx2px(this.height);
  394. if (this.showLeft === false && this.showRight === false) {
  395. this.sliderWidth = this.vertical ? res[0].height : res[0].width;
  396. return;
  397. }
  398. if (this.showLeft !== false && this.showRight !== false) {
  399. this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
  400. 100) * 2;
  401. return;
  402. }
  403. if (this.showLeft === true || this.showRight === true) {
  404. this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
  405. 100);
  406. }
  407. }
  408. }
  409. };
  410. </script>
  411. <style lang="scss" scoped>
  412. .label_slider {
  413. flex-shrink: 0;
  414. &.left,
  415. &.right {
  416. width: 100upx;
  417. }
  418. }
  419. .tm-slider-id {
  420. width: 100%;
  421. .slider_id {
  422. position: relative;
  423. height: 4px;
  424. .slider_id_bg {
  425. width: 100%;
  426. height: 100%;
  427. }
  428. .slider_id_active {
  429. width: 100%;
  430. height: 100%;
  431. position: absolute;
  432. top: 0;
  433. left: 0;
  434. z-index: 5;
  435. }
  436. .slider_bar {
  437. position: absolute;
  438. width: 40upx;
  439. height: 40upx;
  440. left: 0;
  441. top: 0;
  442. margin-top: -22upx;
  443. z-index: 10;
  444. .slider_bar_showbg {
  445. position: absolute;
  446. width: 50upx;
  447. height: 50upx;
  448. border-radius: 30upx;
  449. border-bottom-left-radius: 5upx;
  450. transform: rotate(-45deg);
  451. bottom: 60upx;
  452. left: -9upx;
  453. // transition: translateY;
  454. animation: roteScaleTop_BG 0.3s ease-in-out;
  455. }
  456. .slider_bar_num {
  457. position: absolute;
  458. width: 50upx;
  459. height: 50upx;
  460. border-radius: 50upx;
  461. left: -8upx;
  462. bottom: 60upx;
  463. line-height: 50upx;
  464. text-align: center;
  465. background: transparent !important;
  466. animation: roteScaleTop 0.3s ease-in-out;
  467. }
  468. }
  469. .kdc_wk {
  470. width: 100%;
  471. height: 100%;
  472. position: absolute;
  473. z-index: 6;
  474. left: 0;
  475. top: -2upx;
  476. .kdc_wk_item {
  477. width: 10upx;
  478. height: 10upx;
  479. position: absolute;
  480. z-index: 7;
  481. text-align: center;
  482. .kdc_wk_item_label {
  483. margin-left: -100%;
  484. }
  485. }
  486. }
  487. }
  488. &.vertical {
  489. height: 100%;
  490. .label_slider {
  491. flex-shrink: 0;
  492. &.left,
  493. &.right {
  494. width: auto;
  495. height: 50upx;
  496. }
  497. }
  498. .slider_bar {
  499. margin-left: -18upx;
  500. }
  501. .kdc_wk {
  502. top: 0upx;
  503. .kdc_wk_item {
  504. .kdc_wk_item_label {
  505. margin-left: 100%;
  506. margin-top: -15upx;
  507. }
  508. }
  509. }
  510. }
  511. }
  512. @keyframes roteScaleTop_BG{
  513. 0%{
  514. transform: scale(0.9) translateY(20rpx) rotate(-45deg);
  515. opacity: 0;
  516. }
  517. 100%{
  518. transform: scale(1) translateY(0rpx) rotate(-45deg);
  519. opacity: 1;
  520. }
  521. }
  522. @keyframes roteScaleTop{
  523. 0%{
  524. transform: scale(0.9) translateY(20rpx);
  525. opacity: 0;
  526. }
  527. 100%{
  528. transform: scale(1) translateY(0rpx);
  529. opacity: 1;
  530. }
  531. }
  532. </style>