l-icon.uvue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <text :key="iconCode" class="l-icon" :class="[classes, lClass]" :style="[styles, lStyle]" v-if="!isImage && !isIconify && !isSVG">{{iconCode}}</text>
  3. <image class="l-icon" :class="[classes, lClass]" :style="[styles, lStyle]" v-else-if="(!isSVG && !hasIconify) && isImage" :src="iconUrl"></image>
  4. <l-svg class="l-icon" :class="[classes, lClass]" :style="[styles, lStyle]" :color="color" :src="iconUrl" v-else :web="web" @error="imageError" @load="imageload"></l-svg>
  5. </template>
  6. <script lang="uts" setup>
  7. /**
  8. * LimeIcon 图标
  9. * @description ICON集
  10. * <br> 插件类型:LIconComponentPublicInstance
  11. * @tutorial https://ext.dcloud.net.cn/plugin?id=14057
  12. * @property {String} name 图标名称
  13. * @property {String} color 颜色
  14. * @property {String} size 尺寸
  15. * @property {String} prefix 字体图标前缀
  16. * @property {Boolean} inherit 是否继承颜色
  17. * @property {Boolean} web 原生 app(nvue,uvue) 是否使用web渲染
  18. * @event {Function} click 点击事件
  19. */
  20. import { addUnit } from '@/uni_modules/lime-shared/addUnit';
  21. import { IconCollection } from './types';
  22. import { icons } from './icons'
  23. // defineOptions({
  24. // name: 'l-icon'
  25. // })
  26. const name = 'l-icon'
  27. const IconifyURL : string = 'https://api.iconify.design/';
  28. const $iconsHost : string | null = uni.getStorageSync('$limeIconsHost') as string | null
  29. const props = defineProps({
  30. name: {
  31. type: String,
  32. default: '',
  33. required: true,
  34. // validator: (value: string) : boolean => {
  35. // // 确保是字符串类型且不为空
  36. // return typeof value == 'string' && value.trim().length > 0
  37. // }
  38. },
  39. color: {
  40. type: String,
  41. // default: ''
  42. },
  43. size: {
  44. type: [String, Number],
  45. },
  46. prefix: {
  47. type: String,
  48. default: ''
  49. },
  50. lClass: {
  51. type: String,
  52. default: ''
  53. },
  54. // 对安卓IOS无效
  55. inherit: {
  56. type: Boolean,
  57. default: true
  58. },
  59. web: {
  60. type: Boolean,
  61. default: false
  62. },
  63. lStyle: {
  64. type: [String, Object, Array],
  65. default: ''
  66. },
  67. })
  68. // const emits = defineEmits(['click'])
  69. const $iconCollection = inject<IconCollection>('$iconCollection', {has: false, icons: new Map<string, any|null>()} as IconCollection)
  70. // #ifndef APP-ANDROID
  71. const innerName = computed(():string => props.name ?? '')
  72. // #endif
  73. // #ifdef APP-ANDROID
  74. const innerName = computed(():string => props.name)
  75. // #endif
  76. const collectionIcon = computed(():string|null => {
  77. return $iconCollection.icons.get(innerName.value) as string | null
  78. })
  79. const webviewRef = ref<UniWebViewElement | null>(null)
  80. const hasHost = computed<boolean>(() : boolean => innerName.value.indexOf('/') != -1)
  81. const isIconify = computed<boolean>(() : boolean => {
  82. return !hasHost.value && innerName.value.includes(':')
  83. })
  84. const hasIconify = computed<boolean>(() : boolean => {
  85. // #ifdef APP && uniVersion >= 4.82
  86. return isIconify.value //&& props.color != null
  87. // #endif
  88. // #ifndef APP && uniVersion >= 4.82
  89. return isIconify.value
  90. // #endif
  91. })
  92. const isImage = computed<boolean>(() : boolean => {
  93. // #ifdef APP && uniVersion >= 4.82
  94. return /\.(jpe?g|png|gif|bmp|webp|tiff|svg?)$/i.test(innerName.value) || /^data:image\/(jpeg|png|gif|bmp|webp|tiff|svg)/.test(innerName.value) || isIconify.value
  95. // #endif
  96. // #ifndef APP && uniVersion >= 4.82
  97. return /\.(jpe?g|png|gif|bmp|webp|tiff?)$/i.test(innerName.value) || /^data:image\/(jpeg|png|gif|bmp|webp|tiff);base64,/.test(innerName.value)
  98. // #endif
  99. })
  100. const isSVG = computed<boolean>(():boolean => {
  101. // #ifdef APP && uniVersion >= 4.82
  102. // 由于base64 安卓image尺寸不对
  103. if(innerName.value.startsWith('data:image/svg+xml')) return true
  104. // if(props.color == null) return false
  105. return (/\.svg$/i.test(innerName.value) || innerName.value.startsWith('data:image/svg+xml') || innerName.value.startsWith('<svg'))
  106. // #endif
  107. // #ifndef APP && uniVersion >= 4.82
  108. return /\.svg$/i.test(innerName.value) || innerName.value.startsWith('data:image/svg+xml') || innerName.value.startsWith('<svg')
  109. // #endif
  110. })
  111. const classes = computed<Map<string, any>>(() : Map<string, any> => {
  112. const cls = new Map<string, any>()
  113. cls.set(`${name}--font`, !isImage.value && !isIconify.value && !isSVG.value)
  114. cls.set(`${name}--image`, isImage.value || isIconify.value || isSVG.value)
  115. cls.set(props.prefix, props.prefix.length > 0)
  116. cls.set(props.lClass, props.lClass.length > 0)
  117. // #ifndef UNI-APP-X && APP
  118. cls.set(`is-inherit`, (isIconify.value) && (props.color && props.color.length > 0 || props.inherit))
  119. // #endif
  120. return cls
  121. })
  122. const styles = computed<Map<string, any>>(() : Map<string, any> => {
  123. const style = new Map<string, any>();
  124. const size = addUnit(props.size)
  125. // #ifdef APP
  126. if ((props.color != '' && props.color != null) && !isImage.value && !isIconify.value) {
  127. style.set('color', props.color!)
  128. }
  129. // #endif
  130. // #ifndef APP
  131. if(props.color) {
  132. style.set('color', props.color!)
  133. }
  134. // #endif
  135. if (size != null) {
  136. if (isImage.value || isIconify.value || isSVG.value) {
  137. style.set('height', size)
  138. style.set('width', size)
  139. } else {
  140. style.set('font-size', size)
  141. }
  142. }
  143. return style
  144. })
  145. const iconCode = computed<string>(() : string => {
  146. return icons.value.get(innerName.value) as string | null ?? (/[^\x00-\x7F]/.test(innerName.value) ? innerName.value : '')
  147. })
  148. const isError = ref(false)
  149. const cacheMap = new Map<string, string>()
  150. const iconUrl = computed(():string => {
  151. const hasIconsHost = $iconsHost != null && $iconsHost != ''
  152. // const hasIconCollection = $iconCollection.has
  153. if(isImage.value && !isIconify.value) {
  154. return hasHost.value ? innerName.value : ($iconsHost ?? '') + innerName.value
  155. } else if(isIconify.value) {
  156. // 防止重绘
  157. if(cacheMap.has(innerName.value) && !isError.value) {
  158. return cacheMap.get(innerName.value)!
  159. }
  160. // 如果存在collectionIcon则使用
  161. // 如果设置的路径加载失败 就使用网络地址 就使用iconify api
  162. const _host = `${hasIconsHost ? $iconsHost : IconifyURL}`
  163. const _icon =collectionIcon.value ?? _host + `${innerName.value}.svg`.replace(/:/g, '/')
  164. cacheMap.set(innerName.value, _icon)
  165. return _icon
  166. } else if(isSVG.value) {
  167. return (/\.svg$/i.test(innerName.value) && $iconsHost != null && !hasHost.value ? $iconsHost : '') + innerName.value.replace(/'/g, '"')
  168. } else {
  169. return ''
  170. }
  171. })
  172. const imageError = () => {
  173. isError.value = true
  174. }
  175. const imageload = () => {
  176. isError.value = false
  177. }
  178. </script>
  179. <style lang="scss">
  180. @import './index.scss';
  181. </style>