小程序初始提交
This commit is contained in:
233
cool-unix/uni_modules/cool-svg/components/cl-svg/cl-svg.uvue
Normal file
233
cool-unix/uni_modules/cool-svg/components/cl-svg/cl-svg.uvue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<!-- App 平台:使用原生视图渲染 SVG,性能最佳 -->
|
||||
<!-- #ifdef APP-ANDROID || APP-IOS -->
|
||||
<!-- @vue-ignore -->
|
||||
<native-view @init="onInit"></native-view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 小程序平台:使用 image 标签显示 SVG -->
|
||||
<!-- #ifdef MP || APP-HARMONY -->
|
||||
<!-- @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-ANDROID || APP-IOS
|
||||
// @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 == "none") {
|
||||
return "";
|
||||
}
|
||||
|
||||
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 {
|
||||
let encodedSvg: string;
|
||||
|
||||
// #ifdef APP-ANDROID || APP-IOS
|
||||
// App 平台:简单的空格替换即可,无需完整 URL 编码
|
||||
encodedSvg = svgString.replace(/\+/g, "%20");
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-ANDROID || APP-IOS
|
||||
// 非 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 "";
|
||||
}
|
||||
|
||||
// 处理颜色
|
||||
if (color.value != "") {
|
||||
if (val.includes("fill")) {
|
||||
val = val.replace(/(<path\b[^>]*\bfill=")[^"]*("[^>]*>)/g, `$1${color.value}$2`);
|
||||
} else {
|
||||
val = val.replace(/<svg /g, `<svg fill="${color.value}" `);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为 标签 SVG(以 <svg 开头)
|
||||
if (val.startsWith("<svg")) {
|
||||
return svgToDataUrl(val);
|
||||
}
|
||||
|
||||
// 其他情况直接返回原始数据源(文件路径、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-ANDROID || APP-IOS
|
||||
// App 平台 SVG 渲染器实例
|
||||
let svgRenderer: CoolSvg | null = null;
|
||||
|
||||
// 重新加载
|
||||
function reload() {
|
||||
if (svgRenderer != null) {
|
||||
svgRenderer!.load(svgSrc.value, color.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* App 平台原生视图初始化回调
|
||||
* @param e 原生视图初始化事件
|
||||
*/
|
||||
function onInit(e: UniNativeViewInitEvent) {
|
||||
svgRenderer = new CoolSvg(e.detail.element);
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听 SVG 数据源变化,重新渲染
|
||||
*/
|
||||
watch(svgSrc, (newSrc: string) => {
|
||||
if (svgRenderer != null && newSrc != "") {
|
||||
reload();
|
||||
}
|
||||
});
|
||||
// #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-ANDROID || APP-IOS
|
||||
if (svgRenderer != null && svgSrc.value != "") {
|
||||
reload();
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听颜色变化,重新渲染
|
||||
*/
|
||||
watch(
|
||||
computed(() => [props.color, isDark.value]),
|
||||
() => {
|
||||
setColor();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setColor();
|
||||
});
|
||||
</script>
|
||||
7
cool-unix/uni_modules/cool-svg/index.d.ts
vendored
Normal file
7
cool-unix/uni_modules/cool-svg/index.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export {};
|
||||
|
||||
declare module "vue" {
|
||||
export interface GlobalComponents {
|
||||
"cl-svg": (typeof import("./components/cl-svg/cl-svg.uvue"))["default"];
|
||||
}
|
||||
}
|
||||
82
cool-unix/uni_modules/cool-svg/package.json
Normal file
82
cool-unix/uni_modules/cool-svg/package.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"id": "cool-svg",
|
||||
"displayName": "cool-svg",
|
||||
"version": "1.0.0",
|
||||
"description": "cool-svg",
|
||||
"keywords": [
|
||||
"cool-svg"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^4.75"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "",
|
||||
"data": "",
|
||||
"permissions": ""
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "u",
|
||||
"aliyun": "u",
|
||||
"alipay": "u"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "u",
|
||||
"vue3": "u"
|
||||
},
|
||||
"App": {
|
||||
"app-android": "u",
|
||||
"app-ios": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"minSdkVersion": "21",
|
||||
"dependencies": ["com.caverock:androidsvg:1.4"]
|
||||
}
|
||||
132
cool-unix/uni_modules/cool-svg/utssdk/app-android/index.uts
Normal file
132
cool-unix/uni_modules/cool-svg/utssdk/app-android/index.uts
Normal file
@@ -0,0 +1,132 @@
|
||||
import PictureDrawable from "android.graphics.drawable.PictureDrawable";
|
||||
import ImageView from "android.widget.ImageView";
|
||||
import File from "java.io.File";
|
||||
import FileInputStream from "java.io.FileInputStream";
|
||||
import Color from "android.graphics.Color";
|
||||
import RenderOptions from "com.caverock.androidsvg.RenderOptions";
|
||||
import Base64 from "android.util.Base64";
|
||||
import Charset from "java.nio.charset.Charset";
|
||||
import StandardCharsets from "java.nio.charset.StandardCharsets";
|
||||
|
||||
/**
|
||||
* CoolSvg Android 平台 SVG 渲染器
|
||||
* 支持多种 SVG 数据格式:
|
||||
* - base64 编码的数据 URL
|
||||
* - URL 编码的数据 URL
|
||||
* - 本地文件路径
|
||||
* - Android 资源文件
|
||||
*/
|
||||
export class CoolSvg {
|
||||
/** 原生视图元素 */
|
||||
$element: UniNativeViewElement;
|
||||
/** Android ImageView 实例 */
|
||||
imageView: ImageView | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param element uni-app x 原生视图元素
|
||||
*/
|
||||
constructor(element: UniNativeViewElement) {
|
||||
this.$element = element;
|
||||
this.imageView = new ImageView(UTSAndroid.getAppContext()!);
|
||||
this.$element.bindAndroidView(this.imageView!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并渲染 SVG
|
||||
* @param src SVG 数据源,支持以下格式:
|
||||
* - data:image/svg+xml;base64,<base64数据>
|
||||
* - data:image/svg+xml,<URL编码的SVG>
|
||||
* - 本地文件路径
|
||||
* @param color 填充颜色,用于替换 SVG 中 path 元素的 fill 属性
|
||||
*/
|
||||
load(src: string, color: string) {
|
||||
// 空字符串检查
|
||||
if (src == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (src.startsWith("data:image/svg")) {
|
||||
// 处理数据 URL 格式的 SVG
|
||||
this.loadFromDataUrl(src, color);
|
||||
} else {
|
||||
// 处理本地文件或资源文件
|
||||
this.loadFromFile(src, color);
|
||||
}
|
||||
} catch (e) {
|
||||
// 打印异常信息用于调试
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据 URL 加载 SVG
|
||||
* @param dataUrl 数据 URL 字符串
|
||||
* @param color 填充颜色
|
||||
*/
|
||||
private loadFromDataUrl(dataUrl: string, color: string) {
|
||||
let svgString: string;
|
||||
|
||||
if (dataUrl.startsWith("data:image/svg+xml;base64,")) {
|
||||
// 处理 base64 编码的 SVG
|
||||
const base64Prefix = "data:image/svg+xml;base64,";
|
||||
const base64Data = dataUrl.substring(base64Prefix.length);
|
||||
const decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
|
||||
svgString = String(decodedBytes, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
// 处理 URL 编码的 SVG
|
||||
const urlPrefix = "data:image/svg+xml,";
|
||||
const encodedSvg = dataUrl.substring(urlPrefix.length);
|
||||
svgString = decodeURIComponent(encodedSvg) ?? '';
|
||||
}
|
||||
|
||||
const svg = com.caverock.androidsvg.SVG.getFromString(svgString);
|
||||
this.render(svg, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载 SVG
|
||||
* @param src 文件路径
|
||||
* @param color 填充颜色
|
||||
*/
|
||||
private loadFromFile(src: string, color: string) {
|
||||
// uni-app x 正式打包会将资源文件放在 Android asset 中
|
||||
const path = UTSAndroid.getResourcePath(src);
|
||||
|
||||
if (path.startsWith("/android_asset")) {
|
||||
// 从 Android 资源文件中加载
|
||||
const assetPath = path.substring(15); // 移除 "/android_asset" 前缀
|
||||
const svg = com.caverock.androidsvg.SVG.getFromAsset(
|
||||
UTSAndroid.getAppContext()!.getAssets(),
|
||||
assetPath
|
||||
);
|
||||
this.render(svg, color);
|
||||
} else {
|
||||
// 从本地文件系统加载
|
||||
const file = new File(path);
|
||||
if (file.exists()) {
|
||||
const svg = com.caverock.androidsvg.SVG.getFromInputStream(
|
||||
new FileInputStream(file)
|
||||
);
|
||||
this.render(svg, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染 SVG 到 ImageView
|
||||
* @param svg AndroidSVG 对象
|
||||
* @param color 填充颜色,应用到所有 path 元素
|
||||
*/
|
||||
private render(svg: com.caverock.androidsvg.SVG, color: string) {
|
||||
// 创建渲染选项,设置 CSS 样式来改变 SVG 的填充颜色
|
||||
const options = RenderOptions.create().css(`path { fill: ${color}; }`);
|
||||
|
||||
// 将 SVG 渲染为 Picture,然后转换为 Drawable
|
||||
const drawable = new PictureDrawable(svg.renderToPicture(options));
|
||||
|
||||
// 设置到 ImageView 中显示
|
||||
this.imageView?.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"deploymentTarget": "9",
|
||||
"dependencies-pods": [
|
||||
{
|
||||
"name": "SVGKit",
|
||||
"version": "2.1.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
68
cool-unix/uni_modules/cool-svg/utssdk/app-ios/index.uts
Normal file
68
cool-unix/uni_modules/cool-svg/utssdk/app-ios/index.uts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { UIView } from "UIKit";
|
||||
import { SVGKFastImageView, SVGKImage } from "SVGKit";
|
||||
|
||||
/**
|
||||
* CoolSvg iOS 平台 SVG 渲染器
|
||||
* - 本地文件路径
|
||||
*/
|
||||
export class CoolSvg {
|
||||
/** uni-app x 原生视图元素 */
|
||||
$element: UniNativeViewElement;
|
||||
/** iOS SVGKFastImageView 实例 */
|
||||
imageView: SVGKFastImageView | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param element uni-app x 原生视图元素
|
||||
*/
|
||||
constructor(element: UniNativeViewElement) {
|
||||
this.$element = element;
|
||||
|
||||
// 创建占位符 SVG 图像
|
||||
let placeholderImage = SVGKImage((contentsOf = URL((fileURLWithPath = ""))));
|
||||
// 初始化 SVG 图像视图
|
||||
this.imageView = new SVGKFastImageView((svgkImage = placeholderImage));
|
||||
// 将视图绑定到 uni-app x 元素
|
||||
this.$element.bindIOSView(this.imageView!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并渲染 SVG
|
||||
* @param src SVG 数据源
|
||||
* @param color 填充颜色,用于替换 SVG 中 path 元素的 fill 属性
|
||||
*/
|
||||
load(src: string, color: string) {
|
||||
// 空字符串检查
|
||||
if (src == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 从资源路径加载 SVG 图像
|
||||
let svgImage = SVGKImage(
|
||||
(contentsOf = URL((fileURLWithPath = UTSiOS.getResourcePath(src))))
|
||||
);
|
||||
|
||||
// 加载失败检查
|
||||
if (svgImage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 SVG 图像到视图
|
||||
this.imageView!.image = svgImage;
|
||||
|
||||
// 应用颜色覆盖
|
||||
if (color != "") {
|
||||
// 创建颜色覆盖层视图
|
||||
let colorView = new UIView();
|
||||
colorView.frame = this.imageView!.bounds;
|
||||
colorView.backgroundColor = UTSiOS.colorWithString(color);
|
||||
// 设置合成滤镜为 sourceAtop,实现颜色覆盖效果
|
||||
colorView.layer.compositingFilter = "sourceAtop";
|
||||
this.imageView!.addSubview(colorView);
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默处理异常,避免影响应用运行
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user