人民医院前端

l-circle.vue 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <view class="l-circle" :class="[classes]" :style="[styles]">
  3. <!-- #ifndef APP-NVUE -->
  4. <view class="l-circle__trail" :style="[trailStyles]">
  5. <text class="cap start"></text>
  6. <text class="cap end"></text>
  7. </view>
  8. <view class="l-circle__stroke" :style="[strokeStyles]">
  9. <text class="cap start" v-if="current"></text>
  10. <text class="cap end" v-if="current"></text>
  11. </view>
  12. <!-- #endif -->
  13. <!-- #ifdef APP-NVUE -->
  14. <web-view @pagefinish="finished = true" class="l-circle__view" ref="webview"
  15. src="/uni_modules/l-circle/hybrid/html/index.html"></web-view>
  16. <!-- #endif -->
  17. <view class="l-circle__inner">
  18. <slot></slot>
  19. </view>
  20. </view>
  21. </template>
  22. <script lang="ts">
  23. // @ts-nocheck
  24. import { ref, computed, onMounted, watch, reactive, defineComponent } from './vue';
  25. import { toPx, addUnit } from './utils';
  26. import { useTransition } from './useTransition';
  27. import CircleProps from './props';
  28. // import { toPx, addUnit } from '@/uni_modules/lime-ui';
  29. // import { useTransition } from '@/uni_modules/lime-use';
  30. export default defineComponent({
  31. name: 'l-circle',
  32. props: CircleProps,
  33. emits: ['update:current'],
  34. setup(props, {emit}) {
  35. const RADIAN = Math.PI / 180
  36. const ratio = computed(() => 100 / props.max)
  37. const percent = ref<number>(0)
  38. const angle = computed(() => props.dashborad ? 135 : -90)
  39. const isShowCap = computed(() => {
  40. const { dashborad } = props
  41. return current.value > 0 && (dashborad ? true : current.value < props.max)
  42. })
  43. const offsetTop = ref<number | string>(0)
  44. const strokeEndCap = reactive({
  45. x: '0',
  46. y: '0'
  47. })
  48. const styles = computed(() => ({
  49. width: props.size,
  50. height: props.size,
  51. // #ifdef APP-NVUE
  52. transform: `translateY(${offsetTop.value})`,
  53. // #endif
  54. // #ifndef APP-NVUE
  55. '--l-circle-offset-top': offsetTop.value,
  56. // #endif
  57. }))
  58. const classes = computed(() => {
  59. const { clockwise, lineCap } = props
  60. return {
  61. clockwise,
  62. [`is-${lineCap}`]: lineCap
  63. }
  64. })
  65. const trailStyles = computed(() => {
  66. const { size, trailWidth, trailColor, dashborad } = props
  67. const circle = getCircle(size, trailWidth)
  68. const mask = `radial-gradient(transparent ${circle.r - 0.5}px, #000 ${circle.r}px)`
  69. let background = ''
  70. let capStart = { x: '', y: '' }
  71. let capEnd = capStart
  72. if (dashborad) {
  73. background = `conic-gradient(from 225deg, ${trailColor} 0%, ${trailColor} 75%, transparent 75%, transparent 100%)`
  74. capStart = calcPosition(circle.c, 135)
  75. capEnd = calcPosition(circle.c, 45)
  76. offsetTop.value = (toPx(size) - (toPx(capStart.y) + toPx(trailWidth) / 2)) / 4 + 'px'
  77. } else {
  78. background = `${trailColor}`
  79. }
  80. return {
  81. color: trailColor,
  82. mask,
  83. '-webkit-mask': mask,
  84. background,
  85. '--l-circle-trail-cap-start-x': capStart.x,
  86. '--l-circle-trail-cap-start-y': capStart.y,
  87. '--l-circle-trail-cap-end-x': capEnd.x,
  88. '--l-circle-trail-cap-end-y': capEnd.y,
  89. // '--l-circle-trail-cap-color': trailColor,
  90. '--l-circle-trail-cap-size': addUnit(trailWidth)
  91. }
  92. })
  93. const strokeStyles = computed(() => {
  94. const { size, strokeWidth, strokeColor, duration, dashborad, max } = props
  95. const circle = getCircle(size, strokeWidth)
  96. const percent = dashborad ? current.value * 0.75 * ratio.value : current.value * ratio.value;
  97. const mask = `radial-gradient(transparent ${circle.r - 0.5}px, #000 ${circle.r}px)`
  98. const cap = calcPosition(circle.c, angle.value)
  99. let startColor = '';
  100. let endColor = '';
  101. let gradient = `conic-gradient(${dashborad ? 'from 225deg,' : ''} transparent 0%,`;
  102. let gradientEnd = `transparent var(--l-circle-percent), transparent ${dashborad ? '75%' : '100%'})`
  103. if (typeof strokeColor == 'string') {
  104. gradient += ` ${strokeColor} 0%, ${strokeColor} var(--l-circle-percent), ${gradientEnd}`
  105. startColor = endColor = strokeColor
  106. } else {
  107. const len = strokeColor.length
  108. for (let i = 0; i < len; i++) {
  109. const color = strokeColor[i] as string
  110. if (i === 0) {
  111. gradient += `${color} 0%,`
  112. startColor = color
  113. } else {
  114. gradient += `${color} calc(var(--l-circle-percent) * ${(i + 1) / len}),`
  115. }
  116. if (i == len - 1) {
  117. endColor = color
  118. }
  119. }
  120. gradient += gradientEnd
  121. }
  122. const a = percent / ratio.value == max ? percent + 0.1 : percent
  123. return {
  124. mask,
  125. '-webkit-mask': mask,
  126. background: gradient,
  127. // transition: `--l-circle-percent ${duration}ms`,
  128. '--l-circle-percent': `${percent / ratio.value == max ? percent + 0.1 : percent}%`,
  129. '--l-circle-stroke-cap-start-color': startColor,
  130. '--l-circle-stroke-cap-end-color': endColor,
  131. '--l-circle-stroke-cap-start-x': cap.x,
  132. '--l-circle-stroke-cap-start-y': cap.y,
  133. '--l-circle-stroke-cap-end-x': strokeEndCap.x,
  134. '--l-circle-stroke-cap-end-y': strokeEndCap.y,
  135. '--l-circle-stroke-cap-size': addUnit(strokeWidth),
  136. '--l-circle-stroke-cap-opacity': isShowCap.value ? 1 : 0
  137. }
  138. })
  139. const calcStrokeCap = () => {
  140. const { size, strokeWidth, dashborad, max } = props
  141. const circle = getCircle(size, strokeWidth)
  142. const arc = dashborad ? 180 / 2 * 3 : 180 * 2
  143. const step = arc / max * current.value + angle.value
  144. const cap = calcPosition(circle.c, step)
  145. strokeEndCap.x = cap.x
  146. strokeEndCap.y = cap.y
  147. }
  148. const calcPosition = (r : number, angle : number) => {
  149. return {
  150. x: r + r * Math.cos(angle * RADIAN) + 'px',
  151. y: r + r * Math.sin(angle * RADIAN) + 'px'
  152. }
  153. }
  154. const getCircle = (size : number | string, lineWidth : number | string) => {
  155. const s = toPx(size)
  156. const w = toPx(lineWidth)
  157. const c = (s - w) / 2
  158. const r = s / 2 - w
  159. return {
  160. s, w, c, r
  161. }
  162. }
  163. const current = useTransition(percent, {
  164. duration: props.duration,
  165. })
  166. watch(() => props.percent, () => {
  167. percent.value = props.percent
  168. }, { immediate: true })
  169. watch(current, (v) => {
  170. calcStrokeCap()
  171. emit('update:current', v.toFixed(2))
  172. })
  173. // #ifdef APP-NVUE
  174. const finished = ref(false)
  175. const webview = ref(null)
  176. watch(finished, () => {
  177. webview.value.evalJs(`setClass('.l-circle', 'is-round', ${props.lineCap == 'round'})`)
  178. webview.value.evalJs(`setClass('.l-circle', 'clockwise', ${props.clockwise})`)
  179. watch([trailStyles, strokeStyles], (v) => {
  180. webview.value.evalJs(`setStyle(0,${JSON.stringify(v[0])})`)
  181. webview.value.evalJs(`setStyle(1,${JSON.stringify(v[1])})`)
  182. }, { immediate: true })
  183. })
  184. // #endif
  185. return {
  186. classes,
  187. styles,
  188. trailStyles,
  189. strokeStyles,
  190. current,
  191. // #ifdef APP-NVUE
  192. finished
  193. // #endif
  194. }
  195. }
  196. })
  197. </script>
  198. <style lang="scss">
  199. @import './index';
  200. </style>