Files
WAI_Project_UNIX/uni_modules/cool-svg/components/cl-svg/cl-svg.uvue

232 lines
5.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- App 平台:使用原生视图渲染 SVG性能最佳 -->
<!-- #ifdef APP -->
<!-- @vue-ignore -->
<native-view @init="onInit"></native-view>
<!-- #endif -->
<!-- 小程序平台:使用 image 标签显示 SVG -->
<!-- #ifdef MP -->
<!-- @vue-ignore -->
<image class="cl-svg" :src="svgSrc"></image>
<!-- #endif -->
<!-- Web 平台:使用 object 标签以支持 SVG 交互和样式控制 -->
<!-- #ifdef WEB -->
<object :id="svgId" :data="svgSrc" type="image/svg+xml" class="cl-svg"></object>
<!-- #endif -->
</template>
<script lang="ts" setup>
import { computed, watch, onMounted } from "vue";
import { getColor, isDark } from "@/cool";
// #ifdef APP
// @ts-ignore
import { CoolSvg } from "@/uni_modules/cool-svg";
// #endif
// 组件属性定义
const props = defineProps({
/**
* SVG 数据源
* 支持格式:
* - 文件路径:'/static/icon.svg'
* - base64 数据:'data:image/svg+xml;base64,PHN2Zw...'
* - 标签 SVG'<svg>...</svg>'
*/
src: {
type: String,
default: ""
},
/**
* SVG 填充颜色
* 支持格式:#hex、rgb()、rgba()、颜色名称
* 会自动替换 SVG 中 path 元素的 fill 属性
*/
color: {
type: String,
default: ""
}
});
// 颜色值
const color = computed(() => {
if (props.color != "") {
if (props.color == "primary") {
return getColor("primary-500");
}
return props.color;
} else {
return isDark.value ? "white" : "black";
}
});
/**
* 将 SVG 字符串转换为数据 URL
* @param svgString 原始 SVG 字符串
* @returns 转换后的数据 URL
*/
function svgToDataUrl(svgString: string): string {
// 如果指定了颜色,替换 SVG 中所有 path 元素的 fill 属性
if (color.value != "") {
svgString = svgString.replace(
/(<path\b[^>]*\bfill=")[^"]*("[^>]*>)/g,
`$1${color.value}$2`
);
}
let encodedSvg: string;
// #ifdef APP
// App 平台:简单的空格替换即可,无需完整 URL 编码
encodedSvg = svgString.replace(/\+/g, "%20");
// #endif
// #ifndef APP
// 非 App 平台:使用标准 URL 编码
encodedSvg = encodeURIComponent(svgString)!.replace(/\+/g, "%20");
// #endif
// 确保返回完整的数据 URL 格式
return encodedSvg.startsWith("data:image/svg+xml,")
? encodedSvg
: `data:image/svg+xml,${encodedSvg}`;
}
/**
* 计算最终的 SVG 数据源
* 自动判断数据类型并进行相应处理
*/
const svgSrc = computed((): string => {
let val = props.src;
if (val == "") {
return "";
}
// 判断是否为 标签 SVG以 <svg 开头)
if (val.startsWith("<svg")) {
return svgToDataUrl(val);
}
// #ifdef MP
if (val.includes("fill")) {
val = val.replace(/fill="[^"]*"/g, `fill="${color.value}"`);
} else {
val = val.replace("<svg ", `<svg fill="${color.value}" `);
}
// #endif
// 其他情况直接返回原始数据源文件路径、base64 等)
return val;
});
/**
* 生成符合 RFC4122 标准的 UUID v4
* 用于 Web 平台创建唯一的元素 ID
* @returns UUID 字符串
*/
function generateUuid(): string {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
const uuid: string[] = [];
// 生成 36 位字符
for (let i = 0; i < 36; i++) {
const randomIndex = Math.floor(Math.random() * 16);
const char = chars[i == 19 ? (randomIndex & 0x3) | 0x8 : randomIndex];
uuid.push(char);
}
// 设置 RFC4122 标准要求的固定位
uuid[8] = "-"; // 第一个连字符
uuid[13] = "-"; // 第二个连字符
uuid[18] = "-"; // 第三个连字符
uuid[23] = "-"; // 第四个连字符
uuid[14] = "4"; // 版本号 v4
return uuid.join("");
}
// Web 平台使用的唯一元素 ID
const svgId = `cool-svg-${generateUuid()}`;
// #ifdef APP
// App 平台 SVG 渲染器实例
let svgRenderer: CoolSvg | null = null;
/**
* App 平台原生视图初始化回调
* @param e 原生视图初始化事件
*/
function onInit(e: UniNativeViewInitEvent) {
svgRenderer = new CoolSvg(e.detail.element);
// 立即加载 SVG 内容
if (svgRenderer != null) {
svgRenderer!.load(svgSrc.value, color.value);
}
}
/**
* 监听 SVG 数据源变化,重新渲染
*/
watch(svgSrc, (newSrc: string) => {
if (svgRenderer != null && newSrc != "") {
svgRenderer!.load(newSrc, color.value);
}
});
// #endif
/**
* 设置颜色
*/
function setColor() {
if (color.value == "") {
return;
}
// #ifdef WEB
const element = document.getElementById(svgId) as HTMLObjectElement;
if (element != null) {
const set = () => {
const svgDoc = element.getSVGDocument();
if (svgDoc != null) {
// 查找所有 path 元素并应用颜色
const paths = svgDoc.querySelectorAll("path");
paths?.forEach((path) => {
path.setAttribute("fill", color.value);
});
}
};
if (element.getSVGDocument() != null) {
set();
} else {
element.addEventListener("load", set);
}
}
// #endif
// #ifdef APP
if (svgRenderer != null && svgSrc.value != "") {
svgRenderer!.load(svgSrc.value, color.value);
}
// #endif
}
/**
* 监听颜色变化,重新渲染
*/
watch(
computed(() => [props.color, isDark.value]),
() => {
setColor();
}
);
onMounted(() => {
setColor();
});
</script>