tm-switchList.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <template>
  2. <view class="tm-switchList fulled overflow border-b-1" :class="[black_tmeme?'grey-darken-4 bk':bgColor]"
  3. :style="{height:height+'rpx'}">
  4. <movable-area :style="{height:height+'rpx',width:w+'px'}">
  5. <movable-view :disabled="disabled" :animation="showright" :x="activeOn"
  6. :style="{height:height+'rpx',width:(w-i_w)+'px'}" inertia @change="onChange" direction="horizontal">
  7. <view :style="{width:(w-i_w)+'px'}" :class="[disabled?'gray-100':'']"
  8. class="fulled fulled-height flex-between relative">
  9. <view @click="click" @touchend="move_action_end" @touchstart="move_action_start"
  10. :style="{width:(w+i_w)+'px',left:-(w)+'px'}" class=" fulled-height flex-shrink absolute">
  11. <view class="fulled-height " :style="{width:(w)+'px',marginLeft:(w-i_w)+'px'}">
  12. <view class="fulled fulled-height flex-start overflow ">
  13. <view v-if="icon" class=" d-inline-block">
  14. <view class="overflow flex-end" style="width: 102rpx;height: 80rpx;">
  15. <view v-if="dotObj.dot!==null" class="absolute fulled-height fulled" style="z-index: 10;">
  16. <tm-badges :dot="dotObj.dot" :label="dotObj.label" :icon="dotObj.icon"
  17. :offset="[5,10]"></tm-badges>
  18. </view>
  19. <view class=" flex-center overflow flex-shrink round-4" style="width: 80rpx;height: 80rpx;">
  20. <slot name="left" :hdata="{width:80,height:80}">
  21. <tm-images :round="4" :src="iconName" :width="80" :height="80" v-if="vtype==false"></tm-images>
  22. <view v-if="vtype==true" class="round-4 flex-center "
  23. :class="[color_tmeme,black_tmeme?'bk':'']"
  24. style="width: 80rpx;height: 80rpx;">
  25. <text :style="{fontSize: iconSize+'rpx'}"
  26. :class="[prefx_computed,iconName]"></text>
  27. </view>
  28. </slot>
  29. </view>
  30. </view>
  31. </view>
  32. <view class="d-inline-block overflow px-32 "
  33. :style="{width: `calc(100% - ${icon?164:0}rpx)`,height:height+'rpx'}">
  34. <view class="fulled-height flex-between overflow">
  35. <view :style="{width: `calc(100% - ${icon?160:150}rpx)`}">
  36. <slot name="default">
  37. <view class="text-size-n text-overflow ">{{title}}</view>
  38. <view v-if="label" class="text-size-s text-grey text-overflow pt-4">
  39. {{label}}
  40. </view>
  41. </slot>
  42. </view>
  43. <view class="flex-end fulled-height relative">
  44. <view class=" absolute flex-shrink" style="z-index: 9;">
  45. <slot name="right">
  46. <view v-if="rightLabel"
  47. class="text-size-s text-grey text-align-right nowrap">{{rightLabel}}
  48. </view>
  49. <view v-if="rightIcon" class="flex-end nowrap pt-10">
  50. <tm-icons :fllowTheme="fllowTheme" :color="color_tmeme"
  51. :name="rightIcon"></tm-icons>
  52. </view>
  53. </slot>
  54. </view>
  55. </view>
  56. </view>
  57. </view>
  58. </view>
  59. </view>
  60. </view>
  61. <view @click="click" @touchend="move_action_end" @touchstart="move_action_start"
  62. :style="{width:w+'px'}" class="fulled fulled-height flex-end absolute">
  63. <block v-if="showright">
  64. <view @click.stop="actionsClick(index,item)" v-for="(item,index) in actions" :key="index"
  65. :style="{width:(item.width||itemWidth)+'rpx'}" :class="[item['color']||'white']"
  66. class=" fulled-height flex-center text-size-n flex-shrink">{{item.text}}</view>
  67. </block>
  68. </view>
  69. </view>
  70. </movable-view>
  71. </movable-area>
  72. </view>
  73. </template>
  74. <script>
  75. /**
  76. * 滑动单元格
  77. * @property {String | Boolean} black = [true|false] 默认:null,是否开启暗黑模式
  78. * @property {String | Boolean} disabled = [true|false] 默认:false,是否禁用,禁用后无法操作。
  79. * @property {String | Boolean} on = [true|false] 默认:false,是否打开操作栏
  80. * @property {Number} width = [] 默认:0,单元格的宽度,rpx,可不提供,默认为父组件的宽度
  81. * @property {Number} height = [] 默认:120,单元格的高度度,rpx,
  82. * @property {Number} item-width = [] 默认:140,底部操作按钮的宽度,rpx,
  83. * @property {Number} icon-size = [] 默认:40,项目左边的图标大小,rpx,
  84. * @property {String} color = [] 默认:primary,主题颜色名称
  85. * @property {String} bgColor = [] 默认:white,项目的背景色
  86. * @property {String} icon = [] 默认:'',项目左边的图标
  87. * @property {String} right-icon = [] 默认:'',项目右边的图标
  88. * @property {String} right-label = [] 默认:'',项目右边的文字
  89. * @property {String} title = [] 默认:'',项目的标题
  90. * @property {String} label = [] 默认:'',项目的详细信息文字
  91. * @property {String|Boolean|Number} dot = [] 默认:false,是否显示左边图标的角标.Boolean类型时,显示dot。String类型时显示图标,Number类型时显示数字角标。
  92. * @property {String} actions = [] 默认:[],底部的操作按钮格式:{text: "删除列表",width: 190,color: 'red'}
  93. * @param {Function} click 点击项目时触发
  94. * @param {Function} actionsClick 点击操作按钮时触发,{index:index,item:item}
  95. */
  96. import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
  97. import tmBadges from '@/tm-vuetify/components/tm-badges/tm-badges.vue';
  98. import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
  99. export default {
  100. name: "tm-switchList",
  101. components: {
  102. tmIcons,
  103. tmBadges,tmImages
  104. },
  105. props: {
  106. width: {
  107. type: Number,
  108. default: 0
  109. },
  110. height: {
  111. type: Number,
  112. default: 140
  113. },
  114. itemWidth: {
  115. type: Number,
  116. default: 180
  117. },
  118. on: {
  119. type: Boolean,
  120. default: false
  121. },
  122. color: {
  123. type: String,
  124. default: 'primary'
  125. },
  126. bgColor: {
  127. type: String,
  128. default: 'white'
  129. },
  130. // 跟随主题色的改变而改变。
  131. fllowTheme: {
  132. type: Boolean | String,
  133. default: true
  134. },
  135. // 是否开启暗黑模式
  136. black: {
  137. type: String | Boolean,
  138. default: null
  139. },
  140. disabled: {
  141. type: String | Boolean,
  142. default: false
  143. },
  144. icon: {
  145. type: String,
  146. default: ''
  147. },
  148. iconSize: {
  149. type: Number | String,
  150. default: 40
  151. },
  152. rightIcon: {
  153. type: String,
  154. default: ''
  155. },
  156. rightLabel: {
  157. type: String,
  158. default: ''
  159. },
  160. title: {
  161. type: String,
  162. default: '标题'
  163. },
  164. label: {
  165. type: String,
  166. default: ''
  167. },
  168. dot: {
  169. type: String | Boolean | Number,
  170. default: false
  171. },
  172. actions: {
  173. type: Array,
  174. default: () => {
  175. return []
  176. }
  177. }
  178. },
  179. data() {
  180. return {
  181. x: 0,
  182. old_x: 0,
  183. w: 0,
  184. h: 0,
  185. i_w: 0,
  186. showright: false,
  187. isopnen: 0,
  188. timidId: 88656,
  189. isDrageUp: false,
  190. is_js_cha_old_x: true,
  191. cha_old_x: 0,
  192. last_len: 0, //最后一次的距离差,
  193. last_dir: 0 //最后一次的方向。原因在于滑动时,可能断点
  194. };
  195. },
  196. watch:{
  197. on:function(){
  198. if(this.on==true){
  199. this.$nextTick(function(){
  200. this.activeOn = 0
  201. })
  202. }else if(this.on==false){
  203. this.$nextTick(function(){
  204. this.activeOn = this.i_w;
  205. })
  206. }
  207. },
  208. actions:function () {
  209. this.initsWH();
  210. }
  211. },
  212. computed: {
  213. vtype: function() {
  214. if (this.icon[0] == "." ||
  215. this.icon[0] == "/" ||
  216. this.icon.substring(0, 4) == 'http' ||
  217. this.icon.substring(0, 5) == 'https' ||
  218. this.icon.substring(0, 3) == 'ftp'
  219. ) {
  220. return false;
  221. }
  222. return true;
  223. },
  224. iconName: function() {
  225. return this.icon;
  226. },
  227. prefx_computed(){
  228. let prefix = this.icon.split('-')[0];
  229. if(prefix=='icon') return 'iconfont';
  230. if(prefix=='mdi') return 'mdi';
  231. return prefix;
  232. },
  233. dotObj: function() {
  234. if (typeof this.dot === 'number' && this.dot) {
  235. return {
  236. dot: false,
  237. label: this.dot,
  238. icon: ''
  239. };
  240. }
  241. if (typeof this.dot === 'string' && this.dot) {
  242. if(this.dot.indexOf('-')>0){
  243. return {
  244. dot: false,
  245. label: 0,
  246. icon: this.dot
  247. };
  248. }else{
  249. return {
  250. dot: false,
  251. label: this.dot,
  252. icon: ''
  253. };
  254. }
  255. }
  256. if (typeof this.dot === 'boolean' && this.dot) {
  257. return {
  258. dot: true,
  259. label: 0,
  260. icon: ''
  261. };
  262. }
  263. return {
  264. dot: null,
  265. label: 0,
  266. icon: ''
  267. };
  268. },
  269. activeOn: {
  270. get: function() {
  271. if (this.disabled) return this.i_w;
  272. return this.isopnen;
  273. },
  274. set: function(val) {
  275. this.isopnen = val
  276. }
  277. },
  278. black_tmeme: function() {
  279. if (this.black !== null) return this.black;
  280. return this.$tm.vx.state().tmVuetify.black;
  281. },
  282. color_tmeme: function() {
  283. if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
  284. .fllowTheme) {
  285. return this.$tm.vx.state().tmVuetify.color;
  286. }
  287. return this.color;
  288. },
  289. },
  290. async mounted() {
  291. this.initsWH();
  292. },
  293. methods: {
  294. initsWH(){
  295. let t = this;
  296. let iitemw = uni.upx2px(this.itemWidth);
  297. let total = 0;
  298. for (let i = 0; i < this.actions.length; i++) {
  299. if (this.actions[i]['width']) {
  300. total += uni.upx2px(this.actions[i]['width'])
  301. } else {
  302. total += iitemw
  303. }
  304. }
  305. this.$nextTick(async function() {
  306. let p = await this.$Querey(".tm-switchList", this,30).catch(e => {})
  307. this.w = uni.upx2px(this.width) || p[0].width
  308. this.i_w = total
  309. this.x = this.on ? 0 : this.i_w;
  310. this.isopnen = this.on ? 0 : this.i_w;
  311. setTimeout(function() {
  312. t.showright = true;
  313. }, 50)
  314. })
  315. },
  316. move_action_start(e) {
  317. if (this.activeOn)
  318. this.isDrageUp = false;
  319. this.is_js_cha_old_x = true
  320. },
  321. async move_action_end(e) {
  322. this.isDrageUp = true;
  323. let t = this;
  324. await uni.$tm.sleep(50)
  325. //t.last_len移动的距离。
  326. //t.last_dir 负为左。正为右
  327. //左方向。需要达到一定的距离差才会执行
  328. //以免误触。
  329. if (t.last_dir < 0) {
  330. // console.log('左');
  331. // 如果此时,已经在左边。如果还继续往左拉,就是复位到左边。而不是右边。
  332. if (t.activeOn == 0) {
  333. t.activeOn = 20;
  334. await uni.$tm.sleep(200)
  335. t.activeOn = 0;
  336. return;
  337. }
  338. if (t.last_len > 20) {
  339. t.activeOn = t.i_w + 10;
  340. t.$nextTick(function() {
  341. t.activeOn = 0;
  342. })
  343. } else if (t.last_len <= 20) {
  344. t.activeOn = t.x - 10
  345. t.$nextTick(function() {
  346. t.activeOn = t.i_w;
  347. })
  348. }
  349. //右方向
  350. } else if (t.last_dir > 0) {
  351. // console.log('右');
  352. t.activeOn = t.i_w + 10
  353. await uni.$tm.sleep(10)
  354. t.$nextTick(function() {
  355. t.activeOn = t.i_w;
  356. })
  357. }
  358. },
  359. actionsClick(index, item) {
  360. if (this.disabled) return;
  361. this.$emit('actionsClick', {
  362. index: index,
  363. item: item
  364. });
  365. },
  366. click(e) {
  367. this.activeOn = this.i_w;
  368. if (this.disabled) return;
  369. this.$emit('click', e)
  370. },
  371. onChange: function(e) {
  372. let t = this;
  373. let pos_x = e.detail.x;
  374. this.last_dir = pos_x - this.x; //上一次移动到下一次移动的距离。正为右,负为反方向左。
  375. if (this.is_js_cha_old_x == true && this.isDrageUp == false) {
  376. this.cha_old_x = pos_x;
  377. this.is_js_cha_old_x = false;
  378. // console.log('按下', pos_x);
  379. }
  380. if (this.isDrageUp == true && this.is_js_cha_old_x == false) {
  381. this.$nextTick(function() {
  382. this.is_js_cha_old_x = true;
  383. this.last_len = Math.floor(Math.abs(this.cha_old_x - pos_x)); //从第一次移动到结束时的移动距离。
  384. // console.log('松开', this.last_len);
  385. })
  386. }
  387. t.x = pos_x;
  388. }
  389. }
  390. }
  391. </script>
  392. <style lang="scss">
  393. </style>