Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

247 lignes
8.7 KiB

  1. <template>
  2. <view
  3. class="u-switch cursor-pointer"
  4. :class="[disabled && 'u-switch--disabled']"
  5. :style="[switchStyle, addStyle(customStyle)]"
  6. @tap="clickHandler"
  7. >
  8. <view
  9. class="u-switch__bg"
  10. :style="[bgStyle]"
  11. >
  12. </view>
  13. <view
  14. class="u-switch__node"
  15. <!-- #ifdef VUE3 -->
  16. :class="[modelValue && 'u-switch__node--on']"
  17. <!-- #endif -->
  18. <!-- #ifdef VUE2 -->
  19. :class="[value && 'u-switch__node--on']"
  20. <!-- #endif -->
  21. :style="[nodeStyle]"
  22. ref="u-switch__node"
  23. >
  24. <u-loading-icon
  25. :show="loading"
  26. mode="circle"
  27. timingFunction='linear'
  28. <!-- #ifdef VUE3 -->
  29. :color="modelValue ? resolvedActiveColor : resolvedLoadingInactiveColor"
  30. <!-- #endif -->
  31. <!-- #ifdef VUE2 -->
  32. :color="value ? resolvedActiveColor : resolvedLoadingInactiveColor"
  33. <!-- #endif -->
  34. :size="size * 0.6"
  35. />
  36. </view>
  37. </view>
  38. </template>
  39. <script>
  40. import { props } from './props';
  41. import { mpMixin } from '../../libs/mixin/mpMixin';
  42. import { mixin } from '../../libs/mixin/mixin';
  43. import defProps from '../../libs/config/props.js';
  44. import { addStyle, addUnit, error } from '../../libs/function/index';
  45. /**
  46. * switch 开关选择器
  47. * @description 选择开关一般用于只有两个选择,且只能选其一的场景。
  48. * @tutorial https://uview-plus.jiangruyi.com/components/switch.html
  49. * @property {Boolean} loading 是否处于加载中(默认 false )
  50. * @property {Boolean} disabled 是否禁用(默认 false )
  51. * @property {String | Number} size 开关尺寸,单位px (默认 25 )
  52. * @property {String} activeColor 打开时的背景色 (默认 '#2979ff' )
  53. * @property {String} inactiveColor 关闭时的背景色 (默认 '#ffffff' )
  54. * @property {String} dotActiveColor 打开时圆点的颜色 (默认 '#ffffff' )
  55. * @property {String} dotInactiveColor 关闭时圆点的颜色 (默认 '#ffffff' )
  56. * @property {Boolean | String | Number} value 通过v-model双向绑定的值 (默认 false )
  57. * @property {Boolean | String | Number} activeValue 打开选择器时通过change事件发出的值 (默认 true )
  58. * @property {Boolean | String | Number} inactiveValue 关闭选择器时通过change事件发出的值 (默认 false )
  59. * @property {Boolean} asyncChange 是否开启异步变更,开启后需要手动控制输入值 (默认 false )
  60. * @property {String | Number} space 圆点与外边框的距离 (默认 0 )
  61. * @property {Object} customStyle 定义需要用到的外部样式
  62. *
  63. * @event {Function} change 在switch打开或关闭时触发
  64. * @example <u-switch v-model="checked" active-color="red" inactive-color="#eee"></u-switch>
  65. */
  66. export default {
  67. name: "u-switch",
  68. mixins: [mpMixin, mixin,props],
  69. watch: {
  70. // #ifdef VUE3
  71. modelValue: {
  72. immediate: true,
  73. handler(n) {
  74. if(n !== this.inactiveValue && n !== this.activeValue) {
  75. error('v-model绑定的值必须为inactiveValue、activeValue二者之一')
  76. }
  77. }
  78. },
  79. // #endif
  80. // #ifdef VUE2
  81. value: {
  82. immediate: true,
  83. handler(n) {
  84. if(n !== this.inactiveValue && n !== this.activeValue) {
  85. error('v-model绑定的值必须为inactiveValue、activeValue二者之一')
  86. }
  87. }
  88. }
  89. // #endif
  90. },
  91. computed: {
  92. isActive(){
  93. // #ifdef VUE3
  94. return this.modelValue === this.activeValue;
  95. // #endif
  96. // #ifdef VUE2
  97. return this.value === this.activeValue;
  98. // #endif
  99. },
  100. resolvedActiveColor() {
  101. if (this.upHasProp('activeColor') || this.activeColor !== defProps.switch.activeColor) {
  102. return this.activeColor
  103. }
  104. return this.upThemeVar('--up-switch-active-color', this.$u.color.primary || '#3c9cff')
  105. },
  106. resolvedInactiveColor() {
  107. if (this.upHasProp('inactiveColor') || this.inactiveColor !== defProps.switch.inactiveColor) {
  108. return this.inactiveColor
  109. }
  110. return this.upThemeVar('--up-switch-inactive-color', this.upThemeIsDark ? '#3a3a3c' : '#ffffff')
  111. },
  112. resolvedDotActiveColor() {
  113. if (this.upHasProp('dotActiveColor') || this.dotActiveColor !== defProps.switch.dotActiveColor) {
  114. return this.dotActiveColor
  115. }
  116. return this.upThemeVar('--up-switch-dot-active-color', '#ffffff')
  117. },
  118. resolvedDotInactiveColor() {
  119. if (this.upHasProp('dotInactiveColor') || this.dotInactiveColor !== defProps.switch.dotInactiveColor) {
  120. return this.dotInactiveColor
  121. }
  122. return this.upThemeVar('--up-switch-dot-inactive-color', this.upThemeIsDark ? '#d1d5db' : '#ffffff')
  123. },
  124. resolvedLoadingInactiveColor() {
  125. return this.upThemeVar('--up-switch-loading-inactive-color', this.upThemeIsDark ? '#9ca3af' : '#aaabad')
  126. },
  127. switchStyle() {
  128. let style = {}
  129. // 这里需要加2,是为了腾出边框的距离,否则圆点node会和外边框紧贴在一起
  130. style.width = addUnit(this.size * 2 + 2)
  131. style.height = addUnit(Number(this.size) + 2)
  132. // style.borderColor = this.value ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 0.12)'
  133. // 如果自定义了“非激活”演示,name边框颜色设置为透明(跟非激活颜色一致)
  134. // 这里不能简单的设置为非激活的颜色,否则打开状态时,会有边框,所以需要透明
  135. if(this.customInactiveColor) {
  136. style.borderColor = 'rgba(0, 0, 0, 0)'
  137. } else {
  138. style.borderColor = this.upThemeVar('--up-switch-border-color', this.upThemeIsDark ? '#4b5563' : 'rgba(0, 0, 0, 0.12)')
  139. }
  140. style.backgroundColor = this.isActive ? this.resolvedActiveColor : this.resolvedInactiveColor
  141. return style;
  142. },
  143. nodeStyle() {
  144. let style = {}
  145. // 如果自定义非激活颜色,将node圆点的尺寸减少两个像素,让其与外边框距离更大一点
  146. style.width = addUnit(this.size - this.space)
  147. style.height = addUnit(this.size - this.space)
  148. const translateX = this.isActive ? addUnit(this.space) : addUnit(this.size);
  149. style.transform = `translateX(-${translateX})`
  150. style.backgroundColor = this.isActive ? this.resolvedDotActiveColor : this.resolvedDotInactiveColor
  151. return style
  152. },
  153. bgStyle() {
  154. let style = {}
  155. // 这里配置一个多余的元素在HTML中,是为了让switch切换时,有更良好的背景色扩充体验(见实际效果)
  156. style.width = addUnit(Number(this.size) * 2 - this.size / 2)
  157. style.height = addUnit(this.size)
  158. style.backgroundColor = this.resolvedInactiveColor
  159. // 打开时,让此元素收缩,否则反之
  160. style.transform = `scale(${this.isActive ? 0 : 1})`
  161. return style
  162. },
  163. customInactiveColor() {
  164. // 之所以需要判断是否自定义了“非激活”颜色,是为了让node圆点离外边框更宽一点的距离
  165. return this.upHasProp('inactiveColor') || this.inactiveColor !== defProps.switch.inactiveColor
  166. }
  167. },
  168. // #ifdef VUE3
  169. emits: ['update:modelValue', 'change'],
  170. // #endif
  171. methods: {
  172. addStyle,
  173. clickHandler() {
  174. if (!this.disabled && !this.loading) {
  175. const oldValue = this.isActive ? this.inactiveValue : this.activeValue
  176. if(!this.asyncChange) {
  177. // #ifdef VUE3
  178. this.$emit("update:modelValue", oldValue);
  179. // #endif
  180. // #ifdef VUE2
  181. this.$emit("input", oldValue);
  182. // #endif
  183. }
  184. // 放到下一个生命周期,因为双向绑定的value修改父组件状态需要时间,且是异步的
  185. this.$nextTick(() => {
  186. this.$emit('change', oldValue)
  187. })
  188. }
  189. }
  190. }
  191. };
  192. </script>
  193. <style lang="scss">
  194. @import "./theme-vars.scss";
  195. </style>
  196. <style lang="scss" scoped>
  197. .u-switch {
  198. @include flex(row);
  199. /* #ifndef APP-NVUE */
  200. box-sizing: border-box;
  201. /* #endif */
  202. position: relative;
  203. background-color: $u-bg-color;
  204. border-width: 1px;
  205. border-radius: 100px;
  206. transition: background-color 0.4s;
  207. border-color: $u-border-color;
  208. border-style: solid;
  209. justify-content: flex-end;
  210. align-items: center;
  211. // 由于weex为阿里逗着玩的KPI项目,导致bug奇多,这必须要写这一行,
  212. // 否则在iOS上,点击页面任意地方,都会触发switch的点击事件
  213. overflow: hidden;
  214. &__node {
  215. @include flex(row);
  216. align-items: center;
  217. justify-content: center;
  218. border-radius: 100px;
  219. background-color: $u-bg-color;
  220. box-shadow: 1px 1px 1px 0 rgba(0, 0, 0, 0.25);
  221. transition-property: transform, background-color;
  222. transition-duration: 0.4s;
  223. transition-timing-function: cubic-bezier(0.3, 1.05, 0.4, 1.05);
  224. }
  225. &__bg {
  226. position: absolute;
  227. border-radius: 100px;
  228. background-color: $u-bg-color;
  229. transition-property: transform;
  230. transition-duration: 0.4s;
  231. border-top-left-radius: 0;
  232. border-bottom-left-radius: 0;
  233. transition-timing-function: ease;
  234. }
  235. &--disabled {
  236. opacity: 0.6;
  237. }
  238. }
  239. </style>