l-dialer.uvue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <template>
  2. <view class="l-dialer" :style="rootStyles">
  3. <view class="l-dialer__inner" :style="innerStyle">
  4. <view class="l-dialer__inner-border" v-if="$slots['border'] != null">
  5. <slot name="border" />
  6. </view>
  7. <view class="l-dialer__inner-wrap" ref="drawbleRef" :style="wrapStyle">
  8. <view class="l-dialer__inner-item" v-for="(item, index) in prizeList" :key="index"
  9. :style="itemStyle(index)">
  10. <view class="l-dialer__inner-content" :style="contentStyle(index)">
  11. <slot v-if="$slots['prize'] != null" name="prize" :item="item" :even="index % 2"></slot>
  12. <template v-else>
  13. <text class="l-dialer__inner-name" :style="nameStyle">{{ item['name'] }}</text>
  14. <image class="l-dialer__inner-img" :src="item['img']"></image>
  15. </template>
  16. </view>
  17. </view>
  18. </view>
  19. </view>
  20. <view class="l-dialer__pointer" :style="pointerStyle">
  21. <slot v-if="$slots['pointer'] != null" name="pointer" />
  22. <image v-else :class="!isTurnIng ? 'heart' : ''" src="/uni_modules/lime-dialer/static/turnable_btn.png"
  23. style="width: 100%" mode="widthFix" @tap="$emit('click')" />
  24. </view>
  25. </view>
  26. </template>
  27. <script lang="uts" setup>
  28. const emits = defineEmits(['click', 'done'])
  29. const slots = defineSlots<{
  30. prize : {
  31. item : UTSJSONObject,
  32. even : number
  33. }
  34. }>()
  35. const props = defineProps({
  36. size: {
  37. // #ifdef APP-ANDROID
  38. type: Object,
  39. // #endif
  40. // #ifndef APP-ANDROID
  41. type: [String, Number],
  42. // #endif
  43. default: 300
  44. },
  45. prizeList: {
  46. type: Array as PropType<UTSJSONObject[]>,
  47. default: () : UTSJSONObject[] => []
  48. },
  49. turns: {
  50. type: Number,
  51. default: 10
  52. },
  53. duration: {
  54. type: Number,
  55. default: 3
  56. },
  57. styleOpt: {
  58. type: Object as PropType<UTSJSONObject>,
  59. default: () : UTSJSONObject => ({
  60. // 每一块扇形的背景色,默认值,可通过父组件来改变
  61. prizeBgColors: ['#fff0a3', '#fffce6'],
  62. // 每一块扇形的外边框颜色,默认值,可通过父组件来改变
  63. borderColor: '#ffd752',
  64. } as UTSJSONObject)
  65. },
  66. customStyle: {
  67. type: String,
  68. },
  69. dialStyle: {
  70. type: String,
  71. },
  72. pointerStyle: {
  73. type: String,
  74. default: `width: 30%`
  75. }
  76. })
  77. const drawbleRef = ref<UniElement | null>(null)
  78. const startRotateDegree = ref(0)
  79. const rotateAngle = ref('rotate(0deg)')
  80. const rotateTransition = ref('')
  81. const isTurnIng = ref(false)
  82. const getStyleOpt = computed(() : UTSJSONObject => {
  83. const style = {
  84. // 每一块扇形的背景色,默认值,可通过父组件来改变
  85. prizeBgColors: ['#fff0a3', '#fffce6'],
  86. // 每一块扇形的外边框颜色,默认值,可通过父组件来改变
  87. borderColor: '#ffd752',
  88. }
  89. return UTSJSONObject.assign(style, props.styleOpt)
  90. })
  91. const rootStyles = computed(() : string => {
  92. const size = /\d$/.test(`${props.size}`) ? `${props.size}px` : props.size;
  93. return `width: ${size}; height: ${size}; ${props.customStyle}`
  94. })
  95. const innerStyle = computed(() : string => {
  96. // const style = new Map<string, string>()
  97. let style = ''
  98. const padding = getStyleOpt.value['padding'] ?? 0
  99. style += `padding: ${padding};`
  100. style += `transform: ${rotateAngle.value};`
  101. style += `${rotateTransition.value};`//`transition: ${rotateTransition.value};`
  102. style += `${props.dialStyle};`
  103. return style
  104. })
  105. const wrapStyle = computed(() : string => {
  106. const borderColor = getStyleOpt.value['borderColor']
  107. if (borderColor != null) {
  108. return `border: 1rpx solid ${borderColor}`
  109. }
  110. return ''
  111. })
  112. const itemStyle = computed(() : ((index : number) => Map<string, any>) => {
  113. return (index : number) : Map<string, any> => {
  114. const length = props.prizeList.length;
  115. const prizeBgColors : string[] = (getStyleOpt.value['prizeBgColors'] ?? [] as string[]) as string[]
  116. const prizeBgColorsLength = prizeBgColors.length;
  117. const borderColor = getStyleOpt.value['borderColor']
  118. const style = new Map<string, any>();
  119. // #ifndef APP
  120. if (length == 2) {
  121. // style['transform'] = index == 0 ? 0 : `rotate(270deg)`
  122. style.set('transform', index == 0 ? `rotate(0deg)` : `rotate(270deg)`)
  123. style.set('top', 0)
  124. } else {
  125. style.set('transform', `rotate(${(360 / length) * index}deg) skewX(0deg) skewY(${360 / length - 90}deg)`);
  126. }
  127. if (prizeBgColorsLength > 0) {
  128. style.set('backgroundColor', `${prizeBgColors[index % prizeBgColorsLength]}`)
  129. }
  130. if (borderColor != null) {
  131. style.set('border', `1rpx solid ${borderColor}`)
  132. }
  133. // #endif
  134. // #ifdef APP
  135. if (length == 2) {
  136. style.set('backgroundColor', `${prizeBgColors[index % prizeBgColorsLength]}`)
  137. style.set('transform', index == 0 ? `rotate(0deg)` : `rotate(270deg)`);
  138. style.set('top', 0)
  139. if (borderColor != null) {
  140. style.set('border', `1rpx solid ${borderColor}`)
  141. }
  142. } else {
  143. style.set('transform', `rotate(${(360 / length) * index}deg)`);
  144. }
  145. // #endif
  146. return style
  147. }
  148. })
  149. const contentStyle = computed(() : ((index : number) => string) => {
  150. return (index : number) : string => {
  151. // #ifndef APP
  152. if (props.prizeList.length != 2) {
  153. return `transform: skewY(${90 - 360 / props.prizeList.length}deg) skewX(0deg) rotate(${180 / props.prizeList.length}deg)`
  154. } else {
  155. return index == 0
  156. ? `transform: rotate(90deg); bottom: 0`
  157. : `transform: rotate(0deg); bottom: -50%; left: 0`
  158. }
  159. // #endif
  160. // #ifdef APP
  161. if (props.prizeList.length != 2) {
  162. return `transform: rotate(${180 / props.prizeList.length}deg)`
  163. } else {
  164. return index == 0
  165. ? `transform: rotate(90deg); bottom: 0`
  166. : `transform: rotate(0deg); bottom: -50%; left: 0`
  167. }
  168. // #endif
  169. }
  170. })
  171. const nameStyle = computed(() : Map<string, any> => {
  172. const fontSize = getStyleOpt.value['fontSize']
  173. const color = getStyleOpt.value['color']
  174. const style = new Map<string, any>()
  175. if (fontSize != null) {
  176. style.set('fontSize', fontSize)
  177. }
  178. if (color != null) {
  179. style.set('color', color)
  180. }
  181. return style
  182. })
  183. const run = (index : number) => {
  184. if (isTurnIng.value) return
  185. const duration = props.duration;
  186. const length = props.prizeList.length;
  187. const _rotateAngle = startRotateDegree.value + props.turns * 360 + 360 - (180 / length + (360 / length) * index) - (startRotateDegree.value % 360);
  188. startRotateDegree.value = _rotateAngle;
  189. rotateAngle.value = `rotate(${_rotateAngle}deg)`;
  190. rotateTransition.value = `transition-duration: ${duration}s`;
  191. isTurnIng.value = true
  192. setTimeout(() => {
  193. emits('done', index);
  194. isTurnIng.value = false
  195. }, duration * 1000 + 500);
  196. }
  197. // #ifdef APP
  198. onMounted(() => {
  199. nextTick(() => {
  200. if (drawbleRef.value == null) return;
  201. const ctx = drawbleRef.value!.getDrawableContext()!;
  202. const size = drawbleRef.value!.offsetWidth;
  203. watch(props.prizeList, () => {
  204. ctx.reset()
  205. const length = props.prizeList.length;
  206. if (length == 2) return
  207. const prizeBgColors : string[] = (getStyleOpt.value['prizeBgColors'] ?? [] as string[]) as string[]
  208. const prizeBgColorsLength = prizeBgColors.length;
  209. const borderColor = getStyleOpt.value['borderColor'] as string | null
  210. const centerX = size / 2;
  211. const centerY = size / 2;
  212. const radius = size / 2;
  213. const angle = (2 * Math.PI) / length;
  214. for (let i = 0; i < length; i++) {
  215. ctx.beginPath();
  216. ctx.moveTo(centerX, centerY);
  217. ctx.arc(centerX, centerY, radius, i * angle, (i + 1) * angle);
  218. ctx.lineTo(centerX, centerY);
  219. ctx.closePath();
  220. ctx.fillStyle = prizeBgColors[i % prizeBgColorsLength];
  221. if (borderColor != null) {
  222. ctx.lineWidth = 2
  223. ctx.strokeStyle = borderColor;
  224. ctx.stroke()
  225. }
  226. ctx.fill();
  227. }
  228. ctx.update()
  229. }, { immediate: true })
  230. })
  231. })
  232. // #endif
  233. defineExpose({
  234. run
  235. })
  236. </script>
  237. <style lang="scss" scoped>
  238. @import './index';
  239. </style>