guangchao.xu | 070005a | 2020-12-07 09:56:40 +0800 | [diff] [blame] | 1 | <template> |
| 2 | <view class=""> |
| 3 | <movable-area class="u-swipe-action" :style="{ backgroundColor: bgColor }"> |
| 4 | <movable-view |
| 5 | class="u-swipe-view" |
| 6 | @change="change" |
| 7 | @touchend="touchend" |
| 8 | @touchstart="touchstart" |
| 9 | direction="horizontal" |
| 10 | :disabled="disabled" |
| 11 | :x="moveX" |
| 12 | :style="{ |
| 13 | width: movableViewWidth ? movableViewWidth : '100%' |
| 14 | }" |
| 15 | > |
| 16 | <view |
| 17 | class="u-swipe-content" |
| 18 | @tap.stop="contentClick" |
| 19 | > |
| 20 | <slot></slot> |
| 21 | </view> |
| 22 | <view class="u-swipe-del" v-if="showBtn" @tap.stop="btnClick(index)" :style="[btnStyle(item.style)]" v-for="(item, index) in options" :key="index"> |
| 23 | <view class="u-btn-text">{{ item.text }}</view> |
| 24 | </view> |
| 25 | </movable-view> |
| 26 | </movable-area> |
| 27 | </view> |
| 28 | </template> |
| 29 | |
| 30 | <script> |
| 31 | /** |
| 32 | * swipeAction 左滑单元格 |
| 33 | * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。 |
| 34 | * @tutorial https://www.uviewui.com/components/swipeAction.html |
| 35 | * @property {String} bg-color 整个组件背景颜色(默认#ffffff) |
| 36 | * @property {Array} options 数组形式,可以配置背景颜色和文字 |
| 37 | * @property {String Number} index 标识符,点击时候用于区分点击了哪一个,用v-for循环时的index即可 |
| 38 | * @property {String Number} btn-width 按钮宽度,单位rpx(默认180) |
| 39 | * @property {Boolean} disabled 是否禁止某个swipeAction滑动(默认false) |
| 40 | * @property {Boolean} show 打开或者关闭某个组件(默认false) |
| 41 | * @event {Function} click 点击组件时触发 |
| 42 | * @event {Function} close 组件触发关闭状态时 |
| 43 | * @event {Function} content-click 点击内容时触发 |
| 44 | * @event {Function} open 组件触发打开状态时 |
| 45 | * @example <u-swipe-action btn-text="收藏">...</u-swipe-action> |
| 46 | */ |
| 47 | export default { |
| 48 | name: 'u-swipe-action', |
| 49 | props: { |
| 50 | // index值,用于得知点击删除的是哪个按钮 |
| 51 | index: { |
| 52 | type: [Number, String], |
| 53 | default: '' |
| 54 | }, |
| 55 | // 滑动按钮的宽度,单位为rpx |
| 56 | btnWidth: { |
| 57 | type: [String, Number], |
| 58 | default: 180 |
| 59 | }, |
| 60 | // 是否禁止某个action滑动 |
| 61 | disabled: { |
| 62 | type: Boolean, |
| 63 | default: false |
| 64 | }, |
| 65 | // 打开或者关闭组件 |
| 66 | show: { |
| 67 | type: Boolean, |
| 68 | default: false |
| 69 | }, |
| 70 | // 组件背景颜色 |
| 71 | bgColor: { |
| 72 | type: String, |
| 73 | default: '#ffffff' |
| 74 | }, |
| 75 | // 是否使手机发生短促震动,目前只在iOS的微信小程序有效(2020-05-06) |
| 76 | vibrateShort: { |
| 77 | type: Boolean, |
| 78 | default: false |
| 79 | }, |
| 80 | // 按钮操作参数 |
| 81 | options: { |
| 82 | type: Array, |
| 83 | default() { |
| 84 | return []; |
| 85 | } |
| 86 | } |
| 87 | }, |
| 88 | watch: { |
| 89 | show: { |
| 90 | immediate: true, |
| 91 | handler(nVal, oVal) { |
| 92 | if (nVal) { |
| 93 | this.open(); |
| 94 | } else { |
| 95 | this.close(); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | }, |
| 100 | data() { |
| 101 | return { |
| 102 | moveX: 0, // movable-view元素在x轴上需要移动的目标移动距离,用于展开或收起滑动的按钮 |
| 103 | scrollX: 0, // movable-view移动过程中产生的change事件中的x轴移动值 |
| 104 | status: false, // 滑动的状态,表示当前是展开还是关闭按钮的状态 |
| 105 | movableAreaWidth: 0, // 滑动区域 |
| 106 | elId: this.$u.guid(), // id,用于通知另外组件关闭时的识别 |
| 107 | showBtn: false, // 刚开始渲染视图时不显示右边的按钮,避免视图闪动 |
| 108 | }; |
| 109 | }, |
| 110 | computed: { |
| 111 | movableViewWidth() { |
| 112 | return this.movableAreaWidth + this.allBtnWidth + 'px'; |
| 113 | }, |
| 114 | innerBtnWidth() { |
| 115 | return uni.upx2px(this.btnWidth); |
| 116 | }, |
| 117 | allBtnWidth() { |
| 118 | return uni.upx2px(this.btnWidth) * this.options.length; |
| 119 | }, |
| 120 | btnStyle() { |
| 121 | return style => { |
| 122 | let css = {}; |
| 123 | style.width = this.btnWidth + 'rpx'; |
| 124 | return style; |
| 125 | }; |
| 126 | } |
| 127 | }, |
| 128 | mounted() { |
| 129 | this.getActionRect(); |
| 130 | }, |
| 131 | methods: { |
| 132 | // 点击按钮 |
| 133 | btnClick(index) { |
| 134 | this.status = false; |
| 135 | // this.index为点击的几个组件,index为点击某个组件的第几个按钮(options数组的索引) |
| 136 | this.$emit('click', this.index, index); |
| 137 | }, |
| 138 | // movable-view元素移动事件 |
| 139 | change(e) { |
| 140 | this.scrollX = e.detail.x; |
| 141 | }, |
| 142 | // 关闭按钮状态 |
| 143 | close() { |
| 144 | this.moveX = 0; |
| 145 | this.status = false; |
| 146 | }, |
| 147 | // 打开按钮的状态 |
| 148 | open() { |
| 149 | if (this.disabled) return; |
| 150 | this.moveX = -this.allBtnWidth; |
| 151 | this.status = true; |
| 152 | }, |
| 153 | // 用户手指离开movable-view元素,停止触摸 |
| 154 | touchend() { |
| 155 | this.moveX = this.scrollX; |
| 156 | // 停止触摸时候,判断当前是展开还是关闭状态 |
| 157 | // 关闭状态 |
| 158 | // 这一步很重要,需要先给this.moveX一个变化的随机值,否则因为前后设置的为同一个值 |
| 159 | // props单向数据流的原因,导致movable-view元素不会发生变化,切记,详见文档: |
| 160 | // https://uniapp.dcloud.io/use?id=%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98 |
| 161 | this.$nextTick(function() { |
| 162 | if (this.status == false) { |
| 163 | // 关闭状态左滑,产生的x轴位移为负值,也就是说滑动的距离大于按钮的四分之一宽度,自动展开按钮 |
| 164 | if (this.scrollX <= -this.allBtnWidth / 4) { |
| 165 | this.moveX = -this.allBtnWidth; // 按钮宽度的负值,即为展开状态movable-view元素左滑的距离 |
| 166 | this.status = true; // 标志当前为展开状态 |
| 167 | this.emitOpenEvent(); |
| 168 | // 产生震动效果 |
| 169 | if (this.vibrateShort) uni.vibrateShort(); |
| 170 | } else { |
| 171 | this.moveX = 0; // 如果距离没有按钮宽度的四分之一,自动收起 |
| 172 | this.status = false; |
| 173 | this.emitCloseEvent(); |
| 174 | } |
| 175 | } else { |
| 176 | // 如果在打开的状态下,右滑动的距离X轴偏移超过按钮的四分之一(负值反过来的四分之三),自动收起按钮 |
| 177 | if (this.scrollX > (-this.allBtnWidth * 3) / 4) { |
| 178 | this.moveX = 0; |
| 179 | this.$nextTick(() => { |
| 180 | this.moveX = 101; |
| 181 | }); |
| 182 | this.status = false; |
| 183 | this.emitCloseEvent(); |
| 184 | } else { |
| 185 | this.moveX = -this.allBtnWidth; |
| 186 | this.status = true; |
| 187 | this.emitOpenEvent(); |
| 188 | } |
| 189 | } |
| 190 | }); |
| 191 | }, |
| 192 | emitOpenEvent() { |
| 193 | this.$emit('open', this.index); |
| 194 | }, |
| 195 | emitCloseEvent() { |
| 196 | this.$emit('close', this.index); |
| 197 | }, |
| 198 | // 开始触摸 |
| 199 | touchstart() {}, |
| 200 | getActionRect() { |
| 201 | this.$uGetRect('.u-swipe-action').then(res => { |
| 202 | this.movableAreaWidth = res.width; |
| 203 | // 等视图更新完后,再显示右边的可滑动按钮,防止这些按钮会"闪一下" |
| 204 | this.$nextTick(() => { |
| 205 | this.showBtn = true; |
| 206 | }) |
| 207 | }); |
| 208 | }, |
| 209 | // 点击内容触发事件 |
| 210 | contentClick() { |
| 211 | // 点击内容时,如果当前为打开状态,收起组件 |
| 212 | if (this.status == true) { |
| 213 | this.status = 'close'; |
| 214 | this.moveX = 0; |
| 215 | } |
| 216 | this.$emit('content-click', this.index); |
| 217 | } |
| 218 | } |
| 219 | }; |
| 220 | </script> |
| 221 | |
| 222 | <style scoped lang="scss"> |
| 223 | @import "../../libs/css/style.components.scss"; |
| 224 | |
| 225 | .u-swipe-action { |
| 226 | width: auto; |
| 227 | height: initial; |
| 228 | position: relative; |
| 229 | overflow: hidden; |
| 230 | } |
| 231 | |
| 232 | .u-swipe-view { |
| 233 | @include vue-flex; |
| 234 | height: initial; |
| 235 | position: relative; |
| 236 | /* 这一句很关键,覆盖默认的绝对定位 */ |
| 237 | } |
| 238 | |
| 239 | .u-swipe-content { |
| 240 | flex: 1; |
| 241 | } |
| 242 | |
| 243 | .u-swipe-del { |
| 244 | position: relative; |
| 245 | font-size: 30rpx; |
| 246 | color: #ffffff; |
| 247 | } |
| 248 | |
| 249 | .u-btn-text { |
| 250 | position: absolute; |
| 251 | top: 50%; |
| 252 | left: 50%; |
| 253 | transform: translate(-50%, -50%); |
| 254 | } |
| 255 | </style> |