合同小程序前端代码仓库
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.

215 lines
8.2 KiB

  1. <template>
  2. <view class="l-radio" :class="classes" :style="[styles]" @click="onClick">
  3. <slot name="radio" :checked="radioChecked" :disabled="isDisabled">
  4. <slot name="icon" :checked="radioChecked" :disabled="isDisabled">
  5. <view class="l-radio__icon" ref="iconRef" :class="iconClasses" :style="[iconStyle]"></view>
  6. </slot>
  7. <text class="l-radio__label" :class="labelClass" v-if="label != null || $slots['default'] != null">
  8. <slot>{{label}}</slot>
  9. </text>
  10. </slot>
  11. </view>
  12. </template>
  13. <script lang="uts" setup>
  14. import { RadioProps } from './type';
  15. // #ifndef APP
  16. import type { ComputedRef } from 'vue';
  17. type ComputedRefImpl<T> = ComputedRef<T>
  18. // #endif
  19. const emit = defineEmits(['change', 'update:checked'])
  20. const props = withDefaults(defineProps<RadioProps>(), {
  21. allowUncheck: false,
  22. checked: false,
  23. disabled: false,
  24. icon: 'circle',
  25. size: 'medium'
  26. })
  27. defineSlots<{
  28. radio(props : { checked : boolean, disabled: boolean}) : any,
  29. icon(props : { checked : boolean, disabled: boolean}) : any,
  30. default(props : { checked : boolean, disabled: boolean}) : any,
  31. }>()
  32. const name = 'l-radio' const radioGroupProps = inject<LRadioGroupComponentPublicInstance|null>('limeRadioGroupProps', null); const radioGroupValue = inject<ComputedRefImpl<any>|null>('limeRadioGroupValue', null); const radioGroupChange = inject<((value: any|null) => void)|null>('limeRadioGroupChange', null);
  33. const modelValue = defineModel({type: Boolean, default: false});
  34. const innerChecked = computed({
  35. set(value: boolean){
  36. modelValue.value = value;
  37. emit('change', value)
  38. },
  39. get(): boolean{
  40. return props.checked || modelValue.value
  41. },
  42. } as WritableComputedOptions<boolean>)
  43. const isDisabled = computed(():boolean => (props.disabled || (radioGroupProps?.disabled ?? false)))
  44. const groupDisabled = computed(():boolean|null => radioGroupProps?.disabled);
  45. const radioChecked = computed(():boolean => innerChecked.value || (props.name != null && props.name == radioGroupValue?.value) || (props.value != null && props.value == radioGroupValue?.value));
  46. const finalAllowUncheck = computed(():boolean => props.allowUncheck || (radioGroupProps?.allowUncheck ?? false));
  47. const innerIcon = computed(():string => radioGroupProps?.icon ?? props.icon)
  48. const innerSize = computed(():string => radioGroupProps?.size ?? props.size)
  49. const innerIconSize = computed(():string|null => radioGroupProps?.iconSize ?? props.iconSize)
  50. const innerFontSize = computed(():string|null => radioGroupProps?.fontSize ?? props.fontSize)
  51. const innerCheckedColor = computed(():string|null => radioGroupProps?.checkedColor ?? props.checkedColor)
  52. const innerIconBgColor = computed(():string => props.iconBgColor ?? radioGroupProps?.iconBgColor ?? 'white')
  53. const innerIconBorderColor = computed(():string => props.iconBorderColor ?? radioGroupProps?.iconBorderColor ?? '#c5c5c5')
  54. const innerIconDisabledColor = computed(():string => props.iconDisabledColor ?? radioGroupProps?.iconDisabledColor ?? '#c5c5c5')
  55. const innerIconDisabledBgColor = computed(():string => props.iconDisabledBgColor ?? radioGroupProps?.iconDisabledBgColor ?? '#f3f3f3')
  56. const classes = computed(():Map<string, boolean>=>{
  57. const cls = new Map<string, boolean>();
  58. cls.set(`${name}--disabled`, isDisabled.value)
  59. return cls
  60. })
  61. const iconClasses = computed(():Map<string, boolean>=>{
  62. const cls = new Map<string, boolean>();
  63. // #ifdef APP
  64. cls.set(`${name}__icon--checked`, radioChecked.value && innerCheckedColor.value == null)
  65. cls.set(`${name}__icon--${innerIcon.value}`, true)
  66. cls.set(`${name}__icon--disabled`, isDisabled.value)
  67. // #endif
  68. // #ifndef APP
  69. cls.set(`${name}__icon--checked`, radioChecked.value)
  70. cls.set(`${name}__icon--${innerIcon.value}`, true)
  71. cls.set(`${name}__icon--disabled`, isDisabled.value)
  72. // #endif
  73. return cls
  74. })
  75. const labelClass = computed(():Map<string, boolean>=>{
  76. const cls = new Map<string, boolean>();
  77. cls.set(`${name}__label--disabled`, isDisabled.value)
  78. return cls
  79. })
  80. const styles = computed(():Map<string, any>=>{
  81. const style = new Map<string, any>();
  82. if(radioGroupProps != null && radioGroupProps.gap != null) {
  83. style.set(radioGroupProps.direction == 'horizontal' ? 'margin-right' : 'margin-bottom', radioGroupProps.gap!)
  84. }
  85. // #ifndef APP
  86. if(innerCheckedColor.value != null) {
  87. style.set('--l-radio-icon-checked-color', innerCheckedColor.value!)
  88. }
  89. if(innerIconBorderColor.value != null) {
  90. style.set('--l-radio-icon-border-color', innerIconBorderColor.value!)
  91. }
  92. if(innerIconDisabledColor.value != null) {
  93. style.set('--l-radio-icon-disabled-color', innerIconDisabledColor.value!)
  94. }
  95. if(innerIconDisabledBgColor.value != null) {
  96. style.set('--l-radio-icon-disabled-bg-color', innerIconDisabledBgColor.value!)
  97. }
  98. if(innerFontSize.value != null) {
  99. style.set('--l-radio-font-size', innerFontSize.value!)
  100. }
  101. // #endif
  102. return style
  103. })
  104. const iconStyle = computed(():Map<string, any>=>{
  105. const style = new Map<string, any>();
  106. if(innerIconSize.value != null) {
  107. style.set('width', innerIconSize.value!)
  108. style.set('height', innerIconSize.value!)
  109. // #ifndef APP
  110. style.set('--l-radio-icon-size', innerIconSize.value!)
  111. // #endif
  112. }
  113. if(innerCheckedColor.value != null) {
  114. // #ifndef APP
  115. style.set('--l-radio-icon-checked-color', innerCheckedColor.value!)
  116. // #endif
  117. // #ifdef APP
  118. if(radioChecked.value && ['dot', 'circle'].includes(innerIcon.value)) {
  119. style.set('background', innerCheckedColor.value!)
  120. style.set('border-color', innerCheckedColor.value!)
  121. }
  122. if(!isDisabled.value && !radioChecked.value && ['dot', 'circle'].includes(innerIcon.value)) {
  123. style.set('background', innerIconBgColor.value)
  124. style.set('border-color', innerIconBorderColor.value)
  125. }
  126. if(isDisabled.value && ['dot', 'circle'].includes(innerIcon.value)) {
  127. style.set('background', innerIconDisabledBgColor.value)
  128. style.set('border-color', innerIconDisabledColor.value)
  129. }
  130. // #endif
  131. }
  132. return style
  133. })
  134. const labelStyle = computed(():Map<string, any>=>{
  135. const style = new Map<string, any>();
  136. const fontSize = props.fontSize ?? radioGroupProps?.fontSize
  137. if(fontSize != null) {
  138. style.set('font-size', fontSize)
  139. }
  140. return style
  141. })
  142. const onClick = (e: UniPointerEvent) => {
  143. if(isDisabled.value) return;
  144. const _name = props.value ?? props.name
  145. if(radioGroupChange != null && _name != null) {
  146. const value = finalAllowUncheck.value && radioChecked.value ? null : _name;
  147. radioGroupChange(value);
  148. } else {
  149. const value = finalAllowUncheck.value ? !radioChecked.value : true;
  150. innerChecked.value = value
  151. }
  152. }
  153. // #ifdef APP
  154. const iconRef = ref<UniElement|null>(null)
  155. const update = () => {
  156. if(iconRef.value == null) return
  157. const ctx = iconRef.value!.getDrawableContext()!;
  158. const rect = iconRef.value!.getBoundingClientRect();
  159. const x = rect.width / 2;
  160. const y = rect.height / 2;
  161. ctx.reset();
  162. if(innerIcon.value == 'circle') {
  163. if(radioChecked.value) {
  164. ctx.strokeStyle = isDisabled.value ? innerIconDisabledColor.value : 'white';
  165. ctx.lineWidth = rect.width * 0.12;
  166. ctx.lineCap = 'round';
  167. ctx.moveTo(rect.width * 0.2967, rect.height * 0.53)
  168. ctx.lineTo(rect.width * 0.4342, rect.height * 0.6675)
  169. ctx.lineTo(rect.width * 0.7092, rect.height * 0.3925)
  170. ctx.stroke()
  171. }
  172. } else if(innerIcon.value == 'line') {
  173. if(radioChecked.value) {
  174. ctx.strokeStyle = isDisabled.value ? innerIconDisabledColor.value : props.checkedColor ?? '#3283ff';
  175. ctx.lineWidth = rect.width * 0.16;
  176. ctx.lineCap = 'round';
  177. ctx.moveTo(rect.width * 0.10, rect.height * 0.5466)
  178. ctx.lineTo(rect.width * 0.3666, rect.height * 0.8134)
  179. ctx.lineTo(rect.width * 0.90, rect.height * 0.28)
  180. ctx.stroke()
  181. }
  182. } else if(innerIcon.value == 'dot'){
  183. if(radioChecked.value) {
  184. ctx.fillStyle = isDisabled.value ? innerIconDisabledColor.value : 'white';
  185. ctx.arc(x, y, rect.width * 0.22, 0, Math.PI * 2)
  186. ctx.fill()
  187. }
  188. }
  189. ctx.update();
  190. }
  191. watchEffect(update)
  192. onMounted(()=>{
  193. nextTick(update)
  194. })
  195. // #endif
  196. </script>
  197. <style lang="scss">
  198. @import './index-u';
  199. </style>