添加 cl-svg 组件,支持 base64、本地文件、<svg 标签

This commit is contained in:
icssoa
2025-08-25 19:09:26 +08:00
parent b4d1073132
commit 6a26dbb718
18 changed files with 694 additions and 126 deletions

View File

@@ -6,8 +6,8 @@
"type" : "uni-app:app-ios_simulator"
},
{
"customPlaygroundType" : "device",
"playground" : "standard",
"customPlaygroundType" : "local",
"playground" : "custom",
"type" : "uni-app:app-android"
}
]

View File

@@ -10,8 +10,9 @@
ref="popupRef"
direction="center"
:title="t('获取短信验证码')"
:size="500"
>
<view class="p-3 pt-2 pb-4 w-[460rpx]" v-if="captcha.visible">
<view class="p-3 pt-2 pb-4 w-full" v-if="captcha.visible">
<view class="flex flex-row items-center">
<cl-input
v-model="code"
@@ -25,19 +26,17 @@
@confirm="send"
></cl-input>
<cl-image
:src="captcha.img"
:height="70"
:width="200"
:pt="{
className: '!rounded-lg',
error: {
className: parseClass([[isDark, '!bg-surface-800', '!bg-surface-200']]),
name: 'refresh-line'
}
}"
<view
class="dark:!bg-surface-800 bg-surface-100 rounded-lg h-[70rpx] w-[200rpx] flex flex-row justify-center items-center"
@tap="getCaptcha"
></cl-image>
>
<cl-loading v-if="captcha.loading" :size="28"></cl-loading>
<cl-svg
v-else
class="h-full w-full pointer-events-none"
:src="captcha.img"
></cl-svg>
</view>
</view>
<cl-button
@@ -59,7 +58,7 @@
import { computed, reactive, ref } from "vue";
import { useUi } from "@/uni_modules/cool-ui";
import { $t, t } from "@/locale";
import { isDark, parse, parseClass, service, type Response } from "@/cool";
import { isDark, parse, service, type Response } from "@/cool";
const props = defineProps({
phone: String
@@ -158,7 +157,9 @@ async function getCaptcha() {
});
});
captcha.loading = false;
setTimeout(() => {
captcha.loading = false;
}, 200);
}
// 发送短信

View File

@@ -1,96 +1,96 @@
{
"name" : "cool-unix",
"appid" : "__UNI__651711F",
"description" : "完全开源、永久免费、上手容易、效率极高的开发脚手架",
"versionName" : "1.0.0",
"versionCode" : "100",
"uni-app-x" : {},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"darkmode" : true,
"appid" : "wxdebc4de0b5584ca4",
"setting" : {
"urlCheck" : false,
"es6" : true
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"app" : {
"distribute" : {
"icons" : {
"android" : {
"hdpi" : "",
"xhdpi" : "",
"xxhdpi" : "",
"xxxhdpi" : ""
}
}
}
},
"web" : {
"darkmode" : true,
"router" : {
"mode" : "",
"base" : "./"
},
"title" : "cool-unix"
},
"app-harmony" : {
"distribute" : {
"bundleName" : "com.cool.unix",
"signingConfigs" : {
"default" : {
"certpath" : "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.cer",
"keyAlias" : "debugKey",
"keyPassword" : "0000001B0CD2170B509D76F6435F878B7ED2FE2E3EA6E7454E26523487B093238D4F7C8B7033D30DE80163",
"profile" : "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.p7b",
"signAlg" : "SHA256withECDSA",
"storeFile" : "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.p12",
"storePassword" : "0000001B0CD2170B509D76F6435F878B7ED2FE2E3EA6E7454E26523487B093238D4F7C8B7033D30DE80163"
}
},
"icons" : {
"foreground" : "static/logo2.png",
"background" : "static/logo2.png"
}
}
},
"app-android" : {
"distribute" : {
"modules" : {},
"icons" : {
"hdpi" : "static/logo2.png",
"xhdpi" : "static/logo2.png",
"xxhdpi" : "static/logo2.png",
"xxxhdpi" : "static/logo2.png"
},
"splashScreens" : {
"default" : {}
}
}
},
"app-ios" : {
"distribute" : {
"modules" : {},
"icons" : {
"appstore" : "static/logo2.png"
},
"splashScreens" : {}
}
}
}
"name": "cool-unix",
"appid": "__UNI__651711F",
"description": "完全开源、永久免费、上手容易、效率极高的开发脚手架",
"versionName": "1.0.0",
"versionCode": "100",
"uni-app-x": {},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"darkmode": true,
"appid": "wxdebc4de0b5584ca4",
"setting": {
"urlCheck": false,
"es6": true
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3",
"app": {
"distribute": {
"icons": {
"android": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": "",
"xxxhdpi": ""
}
}
}
},
"web": {
"darkmode": true,
"router": {
"mode": "",
"base": "./"
},
"title": "cool-unix"
},
"app-harmony": {
"distribute": {
"bundleName": "com.cool.unix",
"signingConfigs": {
"default": {
"certpath": "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.cer",
"keyAlias": "debugKey",
"keyPassword": "0000001B0CD2170B509D76F6435F878B7ED2FE2E3EA6E7454E26523487B093238D4F7C8B7033D30DE80163",
"profile": "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "/Users/icssoa/Library/Application Support/HBuilder X/extensions/launcher/agc-certs/1749115146522.p12",
"storePassword": "0000001B0CD2170B509D76F6435F878B7ED2FE2E3EA6E7454E26523487B093238D4F7C8B7033D30DE80163"
}
},
"icons": {
"foreground": "static/logo2.png",
"background": "static/logo2.png"
}
}
},
"app-android": {
"distribute": {
"modules": {},
"icons": {
"hdpi": "static/logo2.png",
"xhdpi": "static/logo2.png",
"xxhdpi": "static/logo2.png",
"xxxhdpi": "static/logo2.png"
},
"splashScreens": {
"default": {}
}
}
},
"app-ios": {
"distribute": {
"modules": {},
"icons": {
"appstore": "static/logo2.png"
},
"splashScreens": {}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "cool-unix",
"version": "8.0.15",
"version": "8.0.16",
"license": "MIT",
"scripts": {
"build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js",
@@ -13,8 +13,8 @@
"devDependencies": {
"@babel/parser": "^7.27.5",
"@babel/types": "^7.27.6",
"@cool-vue/ai": "^1.1.5",
"@cool-vue/vite-plugin": "^8.2.6",
"@cool-vue/ai": "^1.1.6",
"@cool-vue/vite-plugin": "^8.2.7",
"@dcloudio/types": "^3.4.16",
"@types/node": "^24.0.15",
"@vue/compiler-sfc": "^3.5.16",

View File

@@ -423,6 +423,12 @@
"style": {
"navigationBarTitleText": "Canvas 画布"
}
},
{
"path": "other/svg",
"style": {
"navigationBarTitleText": "SVG 图标"
}
}
]
},

69
pages/demo/other/svg.uvue Normal file
View File

@@ -0,0 +1,69 @@
<template>
<cl-page>
<view class="p-3">
<demo-item :label="t('基础用法')">
<view class="flex flex-row">
<cl-svg src="/static/demo/svg/category.svg" class="h-6 w-6"></cl-svg>
<cl-svg src="/static/demo/svg/shopping-cart.svg" class="h-6 w-6 ml-3"></cl-svg>
<cl-svg src="/static/demo/svg/points.svg" class="h-6 w-6 ml-3"></cl-svg>
</view>
</demo-item>
<demo-item :label="t('不同大小')">
<view class="flex flex-row">
<cl-svg src="/static/demo/svg/points.svg" class="h-10 w-10"></cl-svg>
<cl-svg src="/static/demo/svg/points.svg" class="h-8 w-8 ml-3"></cl-svg>
<cl-svg src="/static/demo/svg/points.svg" class="h-6 w-6 ml-3"></cl-svg>
</view>
</demo-item>
<demo-item :label="t('不同颜色')">
<view class="flex flex-row">
<!-- #ifdef MP -->
<cl-svg :src="svg" color="primary" class="h-6 w-6"></cl-svg>
<cl-svg :src="svg" color="#d946ef" class="h-6 w-6 ml-3"></cl-svg>
<!-- #endif -->
<!-- #ifndef MP -->
<cl-svg
src="/static/demo/svg/category.svg"
color="primary"
class="h-6 w-6"
></cl-svg>
<cl-svg
src="/static/demo/svg/shopping-cart.svg"
color="#eab308"
class="h-6 w-6 ml-3"
></cl-svg>
<cl-svg
src="/static/demo/svg/points.svg"
color="#a855f7"
class="h-6 w-6 ml-3"
></cl-svg>
<!-- #endif -->
</view>
</demo-item>
<demo-item :label="t('使用base64')">
<view class="flex flex-row">
<cl-svg :src="base64" class="h-6 w-6"></cl-svg>
</view>
</demo-item>
</view>
</cl-page>
</template>
<script lang="ts" setup>
import { t } from "@/locale";
import DemoItem from "../components/item.uvue";
import { ref } from "vue";
const svg = ref(
`<svg t="1756119341770" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7779" width="64" height="64"><path d="M783.1 899.3H242.9c-97.7 0-177.3-79.5-177.3-177.3V302.6c0-97.7 79.5-177.3 177.3-177.3H783c97.7 0 177.3 79.5 177.3 177.3V722c0.1 97.7-79.5 177.3-177.2 177.3zM242.9 214.8c-48.4 0-87.8 39.4-87.8 87.8V722c0 48.4 39.4 87.8 87.8 87.8H783c48.4 0 87.8-39.4 87.8-87.8V302.6c0-48.4-39.4-87.8-87.8-87.8H242.9z" fill="#333333" p-id="7780"></path><path d="M513 600.5c-24.9 0-49.9-7.3-71.6-21.8l-2.9-2.1-214.9-170.1c-19.4-15.3-22.7-43.5-7.3-62.8 15.3-19.4 43.5-22.6 62.8-7.3l213.2 168.8c12.7 7.8 28.7 7.8 41.5 0L747 336.4c19.3-15.3 47.5-12.1 62.8 7.3 15.3 19.4 12.1 47.5-7.3 62.8L584.6 578.7c-21.7 14.5-46.7 21.8-71.6 21.8z" fill="#333333" p-id="7781"></path></svg>`
);
const base64 = ref(
`data:image/svg+xml;base64,PHN2ZyB0PSIxNzU2MTE2OTQxMjk0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijc2MTYiIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHBhdGggZD0iTTU0MS44IDkyOC41aC02MS4xYy01MC42IDAtOTEuOC00MS4yLTkxLjgtOTEuOFY2MTMuNmMwLTUwLjYgNDEuMi05MS44IDkxLjgtOTEuOGg2MS4xYzUwLjYgMCA5MS44IDQxLjIgOTEuOCA5MS44djIyMy4xYzAgNTAuNy00MS4xIDkxLjgtOTEuOCA5MS44eiBtLTYxLjEtMzE2LjJsLTEuMyAyMjQuNCA2Mi41IDEuM2MwLjcgMCAxLjMtMC42IDEuMy0xLjNWNjEzLjZsLTYyLjUtMS4zek04MzQuOSA5MjguNWgtNjEuMWMtNTAuNiAwLTkxLjgtNDEuMi05MS44LTkxLjhWNDA5LjVjMC01MC42IDQxLjItOTEuOCA5MS44LTkxLjhoNjEuMWM1MC42IDAgOTEuOCA0MS4yIDkxLjggOTEuOHY0MjcuM2MwIDUwLjYtNDEuMiA5MS43LTkxLjggOTEuN3ogbS02MS4yLTUyMC40bC0xLjMgNDI4LjYgNjIuNSAxLjNjMC43IDAgMS4zLTAuNiAxLjMtMS4zVjQwOS41bC02Mi41LTEuNHpNMjUyLjQgOTI4LjVoLTYxLjFjLTUwLjYgMC05MS44LTQxLjItOTEuOC05MS44di04MC4yYzAtNTAuNiA0MS4yLTkxLjggOTEuOC05MS44aDYxLjFjNTAuNiAwIDkxLjggNDEuMiA5MS44IDkxLjh2ODAuMmMwIDUwLjctNDEuMiA5MS44LTkxLjggOTEuOHogbS02MS4xLTE3My4zbC0xLjMgODEuNSA2Mi41IDEuM2MwLjcgMCAxLjMtMC42IDEuMy0xLjN2LTgwLjJsLTYyLjUtMS4zek0xNDQuNiA2MjUuNWMtMTEuNiAwLTIzLjItNC40LTMyLTEzLjMtMTcuNy0xNy43LTE3LjYtNDYuMyAwLTY0TDUyNiAxMzUuM2MxNy43LTE3LjYgNDYuMy0xNy42IDY0IDAgMTcuNyAxNy43IDE3LjYgNDYuMyAwIDY0TDE3Ni42IDYxMi4yYy04LjkgOC44LTIwLjUgMTMuMy0zMiAxMy4zeiIgZmlsbD0iIzMzMzMzMyIgcC1pZD0iNzYxNyI+PC9wYXRoPjxwYXRoIGQ9Ik01ODguNCAzNjQuN2MtMjUgMC00NS4yLTIwLjMtNDUuMi00NS4yVjIxOC45YzAtMjAuMy0xNi41LTM2LjgtMzYuOC0zNi44SDQwNS44Yy0yNSAwLTQ1LjItMjAuMy00NS4yLTQ1LjJzMjAuMy00NS4yIDQ1LjItNDUuMmgxMDAuNmM3MC4yIDAgMTI3LjIgNTcuMSAxMjcuMiAxMjcuMnYxMDAuNmMwIDI1LTIwLjIgNDUuMi00NS4yIDQ1LjJ6IiBmaWxsPSIjMzMzMzMzIiBwLWlkPSI3NjE4Ij48L3BhdGg+PC9zdmc+`
);
</script>

View File

@@ -97,6 +97,13 @@ import Tabbar from "@/components/tabbar.uvue";
const ui = useUi();
const refs = useRefs();
const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M6.31 4.32001L25.69 4.32001C27.5154 4.32001 29 5.80466 29 7.63001L29 20.85C29 22.6754 27.5154 24.15 25.69 24.15L16.66 24.15L16.66 26.35L22.17 26.35C22.5351 26.35 22.83 26.6449 22.83 27.01C22.83 27.3751 22.5351 27.68 22.17 27.68L9.83 27.68C9.46493 27.68 9.17 27.3751 9.17 27.01C9.17 26.6449 9.46493 26.35 9.83 26.35L15.34 26.35L15.34 24.15L6.31 24.15C4.48465 24.15 3 22.6754 3 20.85L3 7.63001C3 5.80466 4.48465 4.32001 6.31 4.32001ZM7.69411 10.7451L6.1817 10.7451L8.57634 18.605L10.0544 18.605L12.449 10.7451L11.0168 10.7451L9.38984 16.3708L9.34401 16.3708L7.69411 10.7451ZM16.6883 10.7451L14.9812 10.7451L12.449 18.605L13.8698 18.605L14.3395 17.0583L17.2383 17.0583L17.7195 18.605L19.2205 18.605L16.6883 10.7451ZM23.2077 10.7451L19.9194 10.7451L19.9194 18.605L21.386 18.605L21.386 15.3282L22.9098 15.3282L24.216 18.605L25.8086 18.605L24.2962 15.0073C24.2962 15.0073 24.9149 14.7553 25.2644 14.2282C25.2644 14.2282 25.6138 13.7012 25.6138 12.9908C25.6138 12.9908 25.6138 11.9825 24.9894 11.3638C24.9894 11.3638 24.365 10.7451 23.2077 10.7451ZM23.7749 13.7642C23.7749 13.7642 23.5171 14.0334 23.0015 14.0334L21.386 14.0334L21.386 12.0513L23.0588 12.0513C23.0588 12.0513 23.5515 12.0513 23.7921 12.2976C23.7921 12.2976 24.0327 12.544 24.0327 12.9908C24.0327 12.9908 24.0327 13.4949 23.7749 13.7642ZM16.0467 13.1283L16.8373 15.7521L14.7405 15.7521L15.5426 13.1283C15.5426 13.1283 15.6572 12.7846 15.7717 12.0627L15.8176 12.0627C15.8176 12.0627 15.9321 12.7387 16.0467 13.1283Z" fill-rule="evenodd" fill="#15273C" >
</path>
</svg>`;
const svg1 = "/static/svg/upload.svg";
type Item = {
label: string;
icon?: string;
@@ -426,6 +433,11 @@ const data = computed<Item[]>(() => {
label: "Vibrate",
icon: "volume-vibrate-line",
path: "/pages/demo/other/vibrate"
},
{
label: "SVG",
icon: "bubble-chart-line",
path: "/pages/demo/other/svg"
}
]
}

20
pnpm-lock.yaml generated
View File

@@ -22,11 +22,11 @@ importers:
specifier: ^7.27.6
version: 7.28.1
'@cool-vue/ai':
specifier: ^1.1.5
version: 1.1.5
specifier: ^1.1.6
version: 1.1.6
'@cool-vue/vite-plugin':
specifier: ^8.2.6
version: 8.2.6
specifier: ^8.2.7
version: 8.2.7
'@dcloudio/types':
specifier: ^3.4.16
version: 3.4.16
@@ -81,12 +81,12 @@ packages:
resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==}
engines: {node: '>=6.9.0'}
'@cool-vue/ai@1.1.5':
resolution: {integrity: sha512-H3A9uml1uiux+g9UPcZT119W3WepvxTx5hs38chwnaj3/zBEF0J2pDI0HNq5FShoHZLQ6+Rq+R7Se0X+CmNU5Q==}
'@cool-vue/ai@1.1.6':
resolution: {integrity: sha512-+5vEnjuMHhmOlAlozasGMaSkx2TZ5p45nOuLzx88ZVyqO0dMYXUJ5I8eVR5XV7huYCLCw7dYwfVg5B03ngsYwg==}
hasBin: true
'@cool-vue/vite-plugin@8.2.6':
resolution: {integrity: sha512-Ey1qIMoHZOZdl/PxZ7qSa5A8hah0IlMVoyxPH7PQk6tAB1PNJiUfTkrPFi9vohFEeuj3WMZ5NhlRh+UIC3ZRXw==}
'@cool-vue/vite-plugin@8.2.7':
resolution: {integrity: sha512-Q3mKFvvVit7EYk+lmkUJnnUuDBlfsKL4i6oxyvAlbQDg1BQnANBI50V76ur6/BV9W8Onsk/GAo6a6yhFsThOqA==}
'@dcloudio/types@3.4.16':
resolution: {integrity: sha512-gJIr1OWtePTDDdjtp8Kh72S/ZGLunoSfHiUvRtXhBmAFNkDWuAKFO90hv62k3GYN/st04xUBQNtBfvhu/YHjww==}
@@ -1358,7 +1358,7 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@cool-vue/ai@1.1.5':
'@cool-vue/ai@1.1.6':
dependencies:
axios: 1.10.0
chalk: 4.1.2
@@ -1370,7 +1370,7 @@ snapshots:
transitivePeerDependencies:
- debug
'@cool-vue/vite-plugin@8.2.6':
'@cool-vue/vite-plugin@8.2.7':
dependencies:
'@vue/compiler-sfc': 3.5.17
axios: 1.10.0

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756115471186" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7124" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M337.4 927.3H235.7c-76 0-137.7-61.8-137.7-137.7V688c0-76 61.8-137.7 137.7-137.7h101.6c76 0 137.7 61.8 137.7 137.7v101.6c0.1 75.9-61.7 137.7-137.6 137.7z m-101.7-287c-26.2 0-47.6 21.4-47.6 47.6v101.6c0 26.2 21.4 47.6 47.6 47.6h101.6c26.2 0 47.6-21.4 47.6-47.6V688c0-26.2-21.4-47.6-47.6-47.6H235.7zM769.9 927.3H668.3c-76 0-137.7-61.8-137.7-137.7V688c0-76 61.8-137.7 137.7-137.7h101.6c76 0 137.7 61.8 137.7 137.7v101.6c0 75.9-61.7 137.7-137.7 137.7z m-101.6-287c-26.2 0-47.6 21.4-47.6 47.6v101.6c0 26.2 21.4 47.6 47.6 47.6h101.6c26.2 0 47.6-21.4 47.6-47.6V688c0-26.2-21.4-47.6-47.6-47.6H668.3zM342.4 499.8H235.7C159.8 499.8 98 438 98 362.1V255.4c0-76 61.8-137.7 137.7-137.7h106.7c76 0 137.7 61.8 137.7 137.7v106.7c0 75.9-61.7 137.7-137.7 137.7z m-106.7-292c-26.2 0-47.6 21.4-47.6 47.6v106.7c0 26.2 21.4 47.6 47.6 47.6h106.7c26.2 0 47.6-21.4 47.6-47.6V255.4c0-26.2-21.4-47.6-47.6-47.6H235.7zM718.7 516c-35.3 0-70.5-13.4-97.4-40.3L549.6 404c-53.7-53.7-53.7-141.1 0-194.8l71.7-71.7c53.7-53.7 141.1-53.7 194.8 0l71.7 71.6c53.7 53.7 53.7 141.1 0 194.8l-71.7 71.7c-26.9 26.9-62.2 40.4-97.4 40.4z m0-328.6c-12.2 0-24.4 4.6-33.7 13.9L613.4 273c-9 9-13.9 20.9-13.9 33.7 0 12.7 5 24.7 13.9 33.7L685 412c18.6 18.6 48.8 18.6 67.3 0l71.7-71.7c9-9 13.9-20.9 13.9-33.7 0-12.7-5-24.7-13.9-33.7l-71.7-71.6c-9.2-9.3-21.4-13.9-33.6-13.9z" fill="#333333" p-id="7125"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756115494148" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7451" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M297 518.5c-3.3 0-6.6-0.4-9.9-1.1-132.8-30.1-218.6-110-218.6-203.6 0-120.9 144-215.5 327.9-215.5 135.9 0 255.4 52.6 304.5 134.1 12.7 21 5.9 48.3-15.1 61-21.1 12.7-48.3 5.9-61-15.1-32.3-53.6-126.3-91.1-228.4-91.1-140.9 0-239 66.8-239 126.7 0 48.9 61.4 97 149.3 116.9 23.9 5.4 38.9 29.2 33.5 53.2-4.5 20.5-22.9 34.5-43.2 34.5z" fill="#333333" p-id="7452"></path><path d="M297.1 719.3c-3.3 0-6.6-0.4-9.9-1.1-132.8-30.1-218.6-110-218.6-203.6V313.8c0-24.5 19.9-44.4 44.4-44.4s44.4 19.9 44.4 44.4v200.8c0 48.9 61.4 97 149.3 116.9 23.9 5.4 38.9 29.2 33.5 53.2-4.5 20.6-22.9 34.6-43.1 34.6z" fill="#333333" p-id="7453"></path><path d="M326.4 925.6c-2.2 0-4.5-0.2-6.8-0.5-147.8-22.7-251-109-251-209.7V514.6c0-24.5 19.9-44.4 44.4-44.4s44.4 19.9 44.4 44.4v200.8c0 54.1 75.5 106.5 175.6 121.9 24.3 3.7 40.9 26.4 37.2 50.7-3.3 21.9-22.2 37.6-43.8 37.6zM667.4 749.4c-162.6 0-290-87.5-290-199.1 0-111.7 127.4-199.1 290-199.1s290 87.5 290 199.1-127.4 199.1-290 199.1z m0-309.4c-118.5 0-201.1 58.1-201.1 110.3 0 52.1 82.6 110.2 201.1 110.2s201.1-58.1 201.1-110.2c0-52.2-82.6-110.3-201.1-110.3z" fill="#333333" p-id="7454"></path><path d="M667.4 930.9c-162.6 0-290-87.5-290-199.1V550.3c0-24.5 19.9-44.4 44.4-44.4s44.4 19.9 44.4 44.4v181.5c0 52.1 82.6 110.2 201.1 110.2s201.1-58.1 201.1-110.2V550.3c0-24.5 19.9-44.4 44.4-44.4s44.4 19.9 44.4 44.4v181.5c0.2 111.6-127.2 199.1-289.8 199.1z" fill="#333333" p-id="7455"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756115483803" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7288" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M772.2 654.3H425.6c-58.6 0-109.8-39.2-124.4-95.2l-90.5-346c-4.4-16.9-20.1-28.7-38.2-28.7h-65c-24.6 0-44.6-20-44.6-44.6s20-44.6 44.6-44.6h65c58.6 0 109.8 39.2 124.4 95.3l90.5 346c4.4 16.9 20.1 28.6 38.2 28.6h346.5c18.5 0 34.3-12.1 38.4-29.5l53.6-226.1c3.6-15-2.9-26.1-7-31.3-7.5-9.5-19-15-31.4-15H406.1c-24.6 0-44.6-20-44.6-44.6s20-44.6 44.6-44.6h419.7c39.8 0 76.7 17.8 101.3 48.8 24.1 30.5 32.8 69.5 23.9 107.2l-53.6 226.1c-13.8 57.9-65.3 98.2-125.2 98.2zM378.5 929.2c-69.4 0-125.8-56.5-125.8-125.9s56.4-125.9 125.8-125.9 125.9 56.5 125.9 125.9c-0.1 69.5-56.5 125.9-125.9 125.9z m0-162.6c-20.2 0-36.7 16.5-36.7 36.7 0 20.3 16.5 36.7 36.7 36.7 20.3 0 36.7-16.5 36.7-36.7 0-20.2-16.5-36.7-36.7-36.7zM769.8 929.2c-69.4 0-125.8-56.5-125.8-125.9s56.4-125.9 125.8-125.9 125.9 56.5 125.9 125.9c0 69.5-56.5 125.9-125.9 125.9z m0-162.6c-20.2 0-36.7 16.5-36.7 36.7 0 20.3 16.5 36.7 36.7 36.7 20.3 0 36.7-16.5 36.7-36.7 0.1-20.2-16.4-36.7-36.7-36.7z" fill="#333333" p-id="7289"></path><path d="M682.2 455.2H506c-24.6 0-44.6-20-44.6-44.6s20-44.6 44.6-44.6h176.2c24.6 0 44.6 20 44.6 44.6-0.1 24.7-20 44.6-44.6 44.6z" fill="#333333" p-id="7290"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -8,7 +8,12 @@
"resolveJsonModule": true,
"esModuleInterop": true,
"noImplicitAny": false,
"types": ["@dcloudio/types", "vue", "./uni_modules/cool-ui/index.d.ts"],
"types": [
"@dcloudio/types",
"vue",
"./uni_modules/cool-ui/index.d.ts",
"./uni_modules/cool-svg/index.d.ts"
],
"lib": ["esnext", "dom"],
"outDir": "esbuild",
"paths": {

16
types/uni-app.d.ts vendored
View File

@@ -456,3 +456,19 @@ declare interface CanvasContext extends HTMLCanvasElement {
declare type Image = HTMLImageElement;
declare type VueApp = any;
declare interface UniNativeViewElement extends UniElement {
bindAndroidView(view: any): void;
bindIOSView(): void;
bindHarmonyFrameNode(node: FrameNode): void;
bindHarmonyWrappedBuilder<O extends Object>(
builder: WrappedBuilder<[options: O]>
): BuilderNode<[O]>;
getHarmonyFrameNode(): FrameNode | null;
}
declare type UniNativeViewInitEvent = {
detail: {
element: UniNativeViewElement;
};
};

View File

@@ -0,0 +1,231 @@
<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>

7
uni_modules/cool-svg/index.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
export {};
declare module "vue" {
export interface GlobalComponents {
"cl-svg": (typeof import("./components/cl-svg/cl-svg.uvue"))["default"];
}
}

View 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"
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
{
"minSdkVersion": "21",
"dependencies": ["com.caverock:androidsvg:1.4"]
}

View 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);
}
}