tm-slider.vue 13 KB

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