This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,342 @@
<script lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { getDefaultColor, colorAddDeepen, setBgColorLightByDark, setTextColorLightByDark, isBlackAndWhite } from "../../core/util/xCoreColorUtil.uts"
import { fillArrayCssValue, fillArrayCssValueBycolor, fillArrayCssValueByround, checkIsCssUnit, getUid, getUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { xTween } from "../../core/util/xTween.uts"
import { xTweenStatus, xTweenCallbackFunType, xTweenAnimate, xTweenEventCallFunType, xTweenEventCall } from "../../interface.uts"
// #ifdef APP|| WEB
import remixicon from "./remixicon.uts"
// #endif
/**
* @name 图标 xIcon
* @description 图标使用的是开源图标:[https://remixicon.com/](https://remixicon.com/),版本是:4.5.0 ,使用时,不用带ri-前缀。
* @page /pages/index/icon
* @category 常用组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xIcon"})
const emits = defineEmits(['click'])
type xIconPropsType = {
/**
* 图标名称,不带ri-前缀
* 也可以是本地或者远程图片
*/
name: string,
/**
* 图标大小,单位任意,比如"12",12px,12rpx
*/
fontSize: string,
/**
* 自定义图标字体,前提是
* 你要在appuvue中已经安装好字体文件,
* 否则无法使用。
* 要让自定图标生效你需要配合code属性一起使用
*/
fontFamily: string,
/**
* 图标的16进制字符串,注意不含u
* 比如:ea0cea14这种,
* 如果你提供了code优化解析这,那么name将失效,如果你是纯字体图标使用
* 你可以使用这个属性,可以提供性能和自己定义的图标会比较方便。
*/
code: string,
/**
* 图标颜色
*/
color: string,
/**
* 暗黑时的文本颜色,如果你不提供,将自动反转。
* 自动反转是根据亮度反转,色相不变。
*/
darkColor: string,
/**
* 是否旋转动画
*/
spin: boolean,
/**
* 旋转角度
*/
rotation: number,
/**
* 动画时间
*/
duration: number,
}
const props = withDefaults(defineProps<xIconPropsType>(), {
name: "home-3-fill",
fontSize: "16",
fontFamily: "remixicon",
code: "",
color: "black",
darkColor: "",
spin: false,
rotation: 0,
duration: 1500,
})
// data -> refs
const xIcon = ref<Element | null>(null)
const refreshId = ref(1)
const id = ref<string>("xIconspin" + getUid())
const element = ref<UniElement | null>(null)
const rotationDeg = ref<number>(0)
const isLoad = ref<boolean>(false)
const isdestory = ref<boolean>(false)
const status = ref<string>('play')
const tid = ref<number>(0)
const xt = ref<xTween>(new xTween())
const xIcons = ref<UniElement | null>(null)
// computed replacements
const _iconName = computed((): string => {
return props.name
})
const _mpcode = computed((): string => {
let cname = ''
// #ifdef MP
cname = props.code==''?('ri-'+props.name):'';
// #endif
return cname
})
const _isFileImg = computed((): boolean => {
if (props.name.lastIndexOf(".") > -1 ||
props.name.indexOf("ftp:") > -1 ||
props.name.indexOf("https:") > -1 ||
props.name.indexOf("http:") > -1 ||
props.name.indexOf("data:image") > -1
) {
return true;
}
return false;
})
const iconName = computed((): string => {
if (_isFileImg.value) return props.name
let texts = ""
try {
// #ifdef APP||WEB
let codestr = ''
if(props.code==''){
codestr = remixicon.getString(props.name)!
}else{
codestr = props.code
}
// #endif
// #ifdef APP-ANDROID
let codePoint = Integer.parseInt(codestr, 16);
let charArray = Character.toChars(codePoint);
texts = new String(charArray);
// #endif
// #ifdef APP-IOS|| WEB||APP-HARMONY
texts = String.fromCharCode(parseInt(codestr, 16));
// #endif
// #ifdef MP
if(props.code!=''){
texts = String.fromCharCode(parseInt(props.code, 16));
}
// #endif
} catch (e) {
console.error("xicon解析失败。", e)
}
return texts
})
const _fontSize = computed((): string => {
let fontSize = checkIsCssUnit(props.fontSize, xConfig.unit);
if (xConfig.fontScale == 1) return fontSize;
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 16
}
return (sizeNumber * xConfig.fontScale).toString() + getUnit(fontSize)
})
const _color = computed((): string => {
let color = props.color==""?'black':props.color;
if (xConfig.dark == 'dark') {
if (props.darkColor != "") {
color = props.darkColor!
return getDefaultColor(color)
}
return setTextColorLightByDark(color)
}
return getDefaultColor(color);
})
const _spin = computed((): boolean => props.spin)
const _rotation = computed((): number => props.rotation)
function setRadeg() {
try {
element.value = xIcons.value as UniElement
element.value!.style.setProperty("transition-duration", props.duration.toString() + 'ms')
element.value!.style.setProperty("transform", `rotate(${_rotation.value}deg)`)
} catch (e) {
}
}
function playSpin() {
if (!_spin.value || isdestory.value) return;
let elementLocal = xIcons.value as UniElement
// #ifdef APP || WEB
xt.value.pause()
xt.value.destroy()
xt.value.startRender()
xt.value.addAnimate({
loop:-1,
duration:props.duration,
complete:(item:xTweenEventCallFunType)=>{
},
enter:(item:xTweenEventCallFunType)=>{
elementLocal!.style.setProperty("transition-duration", '0ms')
elementLocal!.style.setProperty("transform", `rotate(${360 * item.progress}deg)`)
},
pause:(item:xTweenEventCallFunType)=>{},
} as xTweenAnimate)
xt.value.play()
// #endif
}
function clickListen() {
emits("click")
}
/** app加载的字体方式 */
// https://cdn.tmui.design/public/static/remixicon.ttf
function loadFontByX() {
isLoad.value = true;
// uni.loadFontFace({
// source: "url('static/remixicon.ttf')",
// family: "remixicon",
// success() {
// this.isLoad = true;
// uni.setStorageSync("loadedFontBytmx", "true")
// },
// fail() {
// console.log('loadfontFail.---')
// uni.setStorageSync("loadedFontBytmx", "")
// },
// complete() {
// }
// })
}
// watchers
watch(():boolean => props.spin, () => {
if (_spin.value) {
playSpin();
}else{
xt.value.pause()
xt.value.destroy()
}
})
watch(():number => props.rotation, () => {
if (_spin.value) return;
setRadeg();
})
onBeforeUnmount(() => {
isdestory.value = true;
clearTimeout(tid.value)
xt.value.destroy()
})
onMounted(() => {
loadFontByX();
isdestory.value = false;
if (_spin.value) {
playSpin();
} else {
nextTick(() => {
setRadeg();
})
}
})
</script>
<template>
<!-- #ifdef MP-WEIXIN -->
<view class="faceBox">
<!-- #endif -->
<text
v-if="!_isFileImg"
@click="clickListen"
:id="id"
ref="xIcons"
class="face"
:class="[_spin?'faceSpinIcon':'',_mpcode]"
:style="{
'font-family': fontFamily,
'font-size':_fontSize,
'color':_color,
'width':_fontSize,
'height':_fontSize,
'lineHeight':_fontSize
}">
{{iconName}}
</text>
<image @click="clickListen" :id="id" v-else ref="xIcons" :style="{width:_fontSize,height:_fontSize}" :src="iconName"></image>
<!-- #ifdef MP-WEIXIN -->
</view>
<!-- #endif -->
</template>
<style scoped >
/* #ifdef MP-WEIXIN */
.faceBox{
display: inline-block;
}
/* #endif */
.face {
transition-property: transform;
transition-duration: 0ms;
transition-timing-function: linear;
transform: rotate(0deg);
text-align: center;
/* #ifdef WEB || MP-WEIXIN */
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
line-height: 1 !important;
/* #endif */
}
/* #ifdef WEB || MP-WEIXIN */
.faceSpinIcon{
animation:xFontIconRotate 1s linear infinite ;
}
@keyframes xFontIconRotate {
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
/* #endif */
</style>