云链智安app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

l-switch.uvue 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <template>
  2. <view class="l-switch">
  3. <view
  4. class="l-switch__rail"
  5. ref="rootRef"
  6. :hover-start-time="20"
  7. :hover-stay-time="70"
  8. :hover-class="hoverClass"
  9. :class="classes"
  10. :style="[styles]"
  11. aria-role="switch"
  12. @touchstart="touchstart"
  13. @touchend="touchend"
  14. @touchcancel="touchend"
  15. @click="handleClick">
  16. <!-- #ifndef APP-ANDROID || APP-IOS -->
  17. <view class="l-switch__children-placeholder" v-if="placeholder.length > 0">
  18. <text class="l-switch__rail-placeholder" v-for="item in placeholder" :key="item">
  19. {{item}}
  20. </text>
  21. </view>
  22. <!-- #endif -->
  23. <view class="l-switch__dot" :class="dotClass" :style="[dotStyle]" ref="dotRef">
  24. <text class="l-switch__placeholder l-switch__placeholder--checked"
  25. ref="checkedRef" v-if="placeholder.length > 0">
  26. <!-- <slot name="checked"></slot> -->
  27. {{placeholder[0]}}
  28. </text>
  29. <text class="l-switch__placeholder l-switch__placeholder--unchecked"
  30. ref="uncheckedRef" v-if="placeholder.length > 1">
  31. <!-- <slot name="unchecked"></slot> -->
  32. {{placeholder[1]}}
  33. </text>
  34. <!-- #ifndef APP-ANDROID || APP-IOS -->
  35. <l-loading v-if="loading && $slots['icon'] == null"></l-loading>
  36. <!-- #endif -->
  37. <slot name="icon" :checked="innerValue"></slot>
  38. </view>
  39. </view>
  40. </view>
  41. </template>
  42. <script lang="uts" setup>
  43. /**
  44. * LimeSwitch 图片
  45. * @description 用于控制某个功能的开启和关闭。可嵌套内容、禁用以及颜色配置,兼容uniapp/uniappx
  46. * @tutorial https://ext.dcloud.net.cn/plugin?id=155222
  47. * @property {Boolean} value 选中时对应的值
  48. * @property {Boolean} defaultValue 默认值
  49. * @property {Boolean} disabled 是否禁用
  50. * @property {Boolean} loading 是否加载
  51. * @property {Boolean} round 是否为圆形
  52. * @property {Boolean} rubberBand 是否开启橡皮效果
  53. * @property {Array} placeholder 占位内容
  54. * @property {String} src 图片地址
  55. * @property {String} fontSize 字体大小
  56. * @property {String} width 最小宽度
  57. * @property {String} height 高度
  58. * @property {String} dotSize 按钮大小
  59. * @property {String} dotPressedSize 按下按钮大小
  60. * @property {String} checkedColor 激活背景色
  61. * @property {String} disabledColor 禁用背景色
  62. * @property {String} checkedDisabledColor 激活禁用背景色
  63. * @property {String} uncheckedColor 背景色
  64. * @event {Function} change 切换触发
  65. */
  66. import { SwitchProps } from './type';
  67. import { unitConvert } from '@/uni_modules/lime-shared/unitConvert';
  68. // #ifdef APP-ANDROID || APP-IOS
  69. import { useLoading } from '@/uni_modules/lime-loading'
  70. // #endif
  71. // #ifdef APP-ANDROID
  72. import Paint from 'android.graphics.Paint'
  73. // #endif
  74. const name = 'l-switch'
  75. const slots = defineSlots<{
  76. icon(props : { checked : boolean }) : any,
  77. }>();
  78. const emit = defineEmits(['change', 'update:modelValue'])
  79. const props = withDefaults(defineProps<SwitchProps>(), {
  80. disabled: false,
  81. loading: false,
  82. rubberBand: true,
  83. round: true,
  84. placeholder: [] as string[]
  85. })
  86. // 设置默认值
  87. const modelValue = ref((props.modelValue ?? false) || (props.defaultValue ?? false))
  88. const innerValue = computed({
  89. set(value: boolean){
  90. if(value == modelValue.value) return
  91. modelValue.value = value;
  92. emit('change', value)
  93. emit('update:modelValue', value)
  94. },
  95. get():boolean {
  96. return (props.value ?? false) || modelValue.value
  97. },
  98. } as WritableComputedOptions<boolean>)
  99. const classes = computed(():Map<string, boolean> =>{
  100. const cls = new Map<string, boolean>();
  101. cls.set(`${name}--checked`, innerValue.value)
  102. cls.set(`${name}--unchecked`, !innerValue.value)
  103. cls.set(`${name}--disabled`, props.disabled)
  104. cls.set(`${name}--round`, props.round)
  105. cls.set(`${name}--square`, !props.round)
  106. return cls
  107. })
  108. const dotClass = computed(():Map<string, boolean> =>{
  109. const cls = new Map<string, boolean>();
  110. // cls.set(`${name}__dot--checked`, innerValue.value)
  111. // cls.set(`${name}__dot--unchecked`, !innerValue.value)
  112. cls.set(`${name}__dot--disabled`, props.disabled)
  113. cls.set(`${name}__dot--round`, props.round)
  114. cls.set(`${name}__dot--square`, !props.round)
  115. return cls
  116. })
  117. const hoverClass = computed(():string =>{
  118. return props.rubberBand && !props.disabled && !props.loading ? 'l-switch--hover' : ''
  119. })
  120. const styles = computed(():Map<string, any>=>{
  121. const style = new Map<string, any>();
  122. // #ifndef APP-ANDROID || APP-IOS
  123. if(props.width != null) {
  124. style.set('--l-switch-width', props.width!)
  125. }
  126. if(props.height != null) {
  127. style.set('--l-switch-height', props.height!)
  128. }
  129. if(props.fontSize != null) {
  130. style.set('--l-swtich-font-size', props.fontSize!)
  131. }
  132. if(props.dotSize != null) {
  133. style.set('--l-switch-dot-size', props.dotSize!)
  134. }
  135. if(props.dotPressedSize != null) {
  136. style.set('--l-switch-dot-size-pressed', props.dotPressedSize!)
  137. }
  138. if(props.checkedColor != null) {
  139. style.set('--l-switch-checked-color', props.checkedColor!)
  140. }
  141. if(props.checkedDisabledColor != null) {
  142. style.set('--l-switch-checked-disabled-color', props.checkedDisabledColor!)
  143. }
  144. if(props.disabledColor != null) {
  145. style.set('--l-switch-unchecked-disabled-color', props.disabledColor!)
  146. }
  147. if(props.uncheckedColor != null) {
  148. style.set('--l-switch-unchecked-color', props.uncheckedColor!)
  149. }
  150. // #endif
  151. // #ifdef APP-ANDROID || APP-IOS
  152. if(props.width != null) {
  153. style.set('min-width', props.width!)
  154. }
  155. if(props.height != null) {
  156. style.set('height', props.height!)
  157. }
  158. // #endif
  159. return style
  160. })
  161. const dotStyle = computed(():Map<string, any>=>{
  162. const style = new Map<string, any>();
  163. // #ifdef APP-ANDROID || APP-IOS
  164. if(props.dotSize != null) {
  165. style.set('width', props.dotSize!)
  166. style.set('height', props.dotSize!)
  167. }
  168. // #endif
  169. return style
  170. })
  171. const handleClick = (e: UniPointerEvent) => {
  172. if(props.disabled || props.loading) return
  173. innerValue.value = !innerValue.value;
  174. }
  175. // #ifndef APP-ANDROID || APP-IOS
  176. const touchstart = () => {}
  177. const touchend = () => {}
  178. // #endif
  179. // #ifdef APP-ANDROID || APP-IOS
  180. const rootRef = ref<UniElement|null>(null)
  181. const dotRef = ref<UniElement|null>(null)
  182. const checkedRef = ref<UniElement|null>(null)
  183. const uncheckedRef = ref<UniElement|null>(null)
  184. const placeholderWidth = ref(0);
  185. const rootBaseWidth = ref(0)
  186. const dotWidth = ref(0);
  187. const hover = ref(false)
  188. const dotPressedSize = computed(():number => unitConvert(props.dotPressedSize ?? 0))
  189. const touchstart = (e: UniTouchEvent) => {
  190. e.stopPropagation()
  191. if(dotRef.value == null || !props.rubberBand || props.disabled || props.loading) return
  192. hover.value = true
  193. }
  194. const touchend = () => {
  195. if(dotRef.value == null || !props.rubberBand || props.disabled || props.loading) return
  196. hover.value = false
  197. }
  198. const updatePlaceholderWidth = (res: DOMRect) => {
  199. if(res.width > placeholderWidth.value || props.placeholder.length == 1) {
  200. placeholderWidth.value = res.width
  201. }
  202. }
  203. // const {play, clear} = useLoading(dotRef, 'circular', props.checkedColor ?? '#3283ff', 0.8)
  204. const loading = useLoading(dotRef);
  205. loading.type = 'circular';
  206. loading.color = props.checkedColor ?? '#3283ff';
  207. loading.ratio = 0.8
  208. watchEffect(()=>{
  209. if(props.placeholder.length == 0) {
  210. placeholderWidth.value = 0;
  211. return
  212. }
  213. // #ifdef APP-IOS
  214. nextTick(()=>{
  215. checkedRef.value?.getBoundingClientRectAsync()?.then(updatePlaceholderWidth)
  216. uncheckedRef.value?.getBoundingClientRectAsync()?.then(updatePlaceholderWidth)
  217. })
  218. // #endif
  219. // 安卓4.41获取不到文本宽度
  220. // #ifdef APP-ANDROID
  221. props.placeholder.forEach(text => {
  222. // 创建一个Paint对象
  223. const paint = new Paint();
  224. const fontSize = unitConvert(props.fontSize ?? uni.rpx2px(28))
  225. paint.setTextSize(fontSize.toFloat());
  226. // 测量文本宽度
  227. const textWidth = paint.measureText(text);
  228. if(textWidth > placeholderWidth.value || props.placeholder.length == 1) {
  229. placeholderWidth.value = textWidth
  230. nextTick(()=>{
  231. checkedRef.value?.style.setProperty('width', textWidth + 4 + 'px')
  232. uncheckedRef.value?.style.setProperty('width', textWidth + 4 + 'px')
  233. })
  234. }
  235. })
  236. // #endif
  237. })
  238. watchEffect(()=>{
  239. const root = rootRef.value?.getBoundingClientRect();
  240. const dot = dotRef.value?.getBoundingClientRect();
  241. if (root == null || dot == null) return;
  242. const dotSize = hover.value ? Math.max(dotPressedSize.value, dot.height * 1.25) : dot.height;
  243. const offset = (root.height - dot.height) / 2
  244. const bgColor = innerValue.value
  245. ? props.disabled ? props.checkedDisabledColor : props.checkedColor
  246. : props.disabled ? props.disabledColor : props.uncheckedColor;
  247. let width = rootBaseWidth.value;
  248. if(placeholderWidth.value > 0) {
  249. width = placeholderWidth.value + root.height * 1.75
  250. rootRef.value!.style.setProperty('width', `${width}px`)
  251. }
  252. dotRef.value!.style.setProperty('left', `${innerValue.value ? width - dotSize - offset : offset}`)
  253. dotRef.value!.style.setProperty('width', `${dotSize}`)
  254. dotRef.value!.style.setProperty('top', `${offset}`)
  255. checkedRef.value?.style.setProperty('padding-right', `${hover.value && !innerValue.value ? dotSize * 1.25 : 1.15 * root.height - offset}`)
  256. uncheckedRef.value?.style.setProperty('padding-left', `${hover.value && innerValue.value ? dotSize * 1.25 : 1.15 * root.height - offset}`)
  257. if(props.loading && slots['icon'] == null) {
  258. loading.play()
  259. } else {
  260. loading.clear()
  261. }
  262. if(bgColor == null) return
  263. nextTick(()=>{
  264. rootRef.value!.style.setProperty('backgroundColor', bgColor)
  265. })
  266. })
  267. onMounted(()=>{
  268. nextTick(()=>{
  269. if(rootRef.value == null) return;
  270. rootRef.value!.getBoundingClientRectAsync()?.then(res => {
  271. rootBaseWidth.value = res.width;
  272. })
  273. })
  274. })
  275. // #endif
  276. </script>
  277. <style lang="scss">
  278. @import './index-u';
  279. </style>