cl-svg.uvue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <template>
  2. <!-- App 平台:使用原生视图渲染 SVG,性能最佳 -->
  3. <!-- #ifdef APP-ANDROID || APP-IOS -->
  4. <!-- @vue-ignore -->
  5. <native-view @init="onInit"></native-view>
  6. <!-- #endif -->
  7. <!-- 小程序平台:使用 image 标签显示 SVG -->
  8. <!-- #ifdef MP || APP-HARMONY || H5 -->
  9. <!-- @vue-ignore -->
  10. <image class="cl-svg" :src="svgSrc"></image>
  11. <!-- #endif -->
  12. </template>
  13. <script lang="ts" setup>
  14. import { computed, watch, onMounted } from "vue";
  15. import { getColor, isDark } from "@/.cool";
  16. // #ifdef APP-ANDROID || APP-IOS
  17. import { CoolSvg } from "@/uni_modules/cool-svg";
  18. // #endif
  19. // 组件属性定义
  20. const props = defineProps({
  21. /**
  22. * SVG 数据源
  23. * 支持格式:
  24. * - 文件路径:'/static/icon.svg'
  25. * - base64 数据:'data:image/svg+xml;base64,PHN2Zw...'
  26. * - 标签 SVG:'<svg>...</svg>'
  27. */
  28. src: {
  29. type: String,
  30. default: ""
  31. },
  32. /**
  33. * SVG 填充颜色
  34. * 支持格式:#hex、rgb()、rgba()、颜色名称
  35. * 会自动替换 SVG 中 path 元素的 fill 属性
  36. * 温馨提示:建议优先在 SVG 文件中直接设置所需颜色值,因为不同平台对 SVG 颜色渲染的兼容性存在差异,统一性无法完全保证。
  37. */
  38. color: {
  39. type: String,
  40. default: ""
  41. }
  42. });
  43. // 颜色值
  44. const color = computed(() => {
  45. if (props.color == "none") {
  46. return "";
  47. }
  48. if (props.color != "") {
  49. if (props.color == "primary") {
  50. return getColor("primary-500");
  51. }
  52. return props.color;
  53. } else {
  54. return isDark.value ? "white" : "black";
  55. }
  56. });
  57. /**
  58. * 将 SVG 字符串转换为数据 URL
  59. * @param svgString 原始 SVG 字符串
  60. * @returns 转换后的数据 URL
  61. */
  62. function svgToDataUrl(svgString: string): string {
  63. let encodedSvg: string;
  64. // #ifdef APP-ANDROID || APP-IOS
  65. // App 平台:简单的空格替换即可,无需完整 URL 编码
  66. encodedSvg = svgString.replace(/\+/g, "%20");
  67. // #endif
  68. // #ifndef APP-ANDROID || APP-IOS
  69. // 非 App 平台:使用标准 URL 编码
  70. encodedSvg = encodeURIComponent(svgString)!.replace(/\+/g, "%20");
  71. // #endif
  72. // 确保返回完整的数据 URL 格式
  73. return encodedSvg.startsWith("data:image/svg+xml,")
  74. ? encodedSvg
  75. : `data:image/svg+xml,${encodedSvg}`;
  76. }
  77. /**
  78. * 计算最终的 SVG 数据源
  79. * 自动判断数据类型并进行相应处理
  80. */
  81. const svgSrc = computed((): string => {
  82. let val = props.src;
  83. if (val == "") {
  84. return "";
  85. }
  86. // 处理颜色
  87. if (color.value != "") {
  88. if (val.includes("fill")) {
  89. val = val.replace(/(<path\b[^>]*\bfill=")[^"]*("[^>]*>)/g, `$1${color.value}$2`);
  90. } else {
  91. val = val.replace(/<svg /g, `<svg fill="${color.value}" `);
  92. }
  93. }
  94. // 判断是否为 标签 SVG(以 <svg 开头)
  95. if (val.startsWith("<svg")) {
  96. return svgToDataUrl(val);
  97. }
  98. // 其他情况直接返回原始数据源(文件路径、base64 等)
  99. return val;
  100. });
  101. /**
  102. * 生成符合 RFC4122 标准的 UUID v4
  103. * 用于 Web 平台创建唯一的元素 ID
  104. * @returns UUID 字符串
  105. */
  106. function generateUuid(): string {
  107. const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
  108. const uuid: string[] = [];
  109. // 生成 36 位字符
  110. for (let i = 0; i < 36; i++) {
  111. const randomIndex = Math.floor(Math.random() * 16);
  112. const char = chars[i == 19 ? (randomIndex & 0x3) | 0x8 : randomIndex];
  113. uuid.push(char);
  114. }
  115. // 设置 RFC4122 标准要求的固定位
  116. uuid[8] = "-"; // 第一个连字符
  117. uuid[13] = "-"; // 第二个连字符
  118. uuid[18] = "-"; // 第三个连字符
  119. uuid[23] = "-"; // 第四个连字符
  120. uuid[14] = "4"; // 版本号 v4
  121. return uuid.join("");
  122. }
  123. // Web 平台使用的唯一元素 ID
  124. const svgId = `cool-svg-${generateUuid()}`;
  125. // #ifdef APP-ANDROID || APP-IOS
  126. // App 平台 SVG 渲染器实例
  127. let svgRenderer: CoolSvg | null = null;
  128. // 重新加载
  129. function reload() {
  130. if (svgRenderer != null) {
  131. svgRenderer!.load(svgSrc.value, color.value);
  132. }
  133. }
  134. /**
  135. * App 平台原生视图初始化回调
  136. * @param e 原生视图初始化事件
  137. */
  138. function onInit(e: UniNativeViewInitEvent) {
  139. svgRenderer = new CoolSvg(e.detail.element);
  140. reload();
  141. }
  142. /**
  143. * 监听 SVG 数据源变化,重新渲染
  144. */
  145. watch(svgSrc, (newSrc: string) => {
  146. if (svgRenderer != null && newSrc != "") {
  147. reload();
  148. }
  149. });
  150. // #endif
  151. /**
  152. * 设置颜色
  153. */
  154. function setColor() {
  155. if (color.value == "") {
  156. return;
  157. }
  158. // #ifdef WEB
  159. const element = document.getElementById(svgId) as HTMLObjectElement;
  160. if (element != null) {
  161. const set = () => {
  162. const svgDoc = element.getSVGDocument();
  163. if (svgDoc != null) {
  164. // 查找所有 path 元素并应用颜色
  165. const paths = svgDoc.querySelectorAll("path");
  166. paths?.forEach((path) => {
  167. path.setAttribute("fill", color.value);
  168. });
  169. }
  170. };
  171. if (element.getSVGDocument() != null) {
  172. set();
  173. } else {
  174. element.addEventListener("load", set);
  175. }
  176. }
  177. // #endif
  178. // #ifdef APP-ANDROID || APP-IOS
  179. if (svgRenderer != null && svgSrc.value != "") {
  180. reload();
  181. }
  182. // #endif
  183. }
  184. /**
  185. * 监听颜色变化,重新渲染
  186. */
  187. watch(
  188. computed(() => [props.color, isDark.value]),
  189. () => {
  190. setColor();
  191. }
  192. );
  193. onMounted(() => {
  194. setColor();
  195. });
  196. </script>