Compare commits

..

10 Commits

Author SHA1 Message Date
Timi
3f9e2a2f1a remove Page.keyword and add likeMap 2025-12-03 11:51:49 +08:00
Timi
fae34b7fbd add light-green color 2025-12-03 11:50:43 +08:00
Timi
b9783b1f0c add Toolkit.setRandomInterval 2025-12-03 11:50:32 +08:00
Timi
00816f223a add AnimatedNumber component 2025-12-03 11:50:08 +08:00
Timi
0ac836eaa5 add toCssSize 2025-11-25 11:14:20 +08:00
Timi
007b2bf39a not important body overflow style 2025-11-25 11:12:53 +08:00
Timi
c152ad5281 upper popup z-index to 3000 2025-11-25 11:12:35 +08:00
Timi
d6d59c890e remove warn log for popup empty value 2025-11-25 11:12:21 +08:00
Timi
d8209d7d23 add doNotNull 2025-11-25 11:11:04 +08:00
Timi
553fc73ced important scrollbar style 2025-11-12 10:23:02 +08:00
9 changed files with 278 additions and 16 deletions

View File

@@ -3,18 +3,16 @@
*::-webkit-scrollbar {
width: 10px !important;
height: 10px !important;
cursor: var(--tui-cur-default);
background: #CFD2E0;
background: #CFD2E0 !important;
}
*::-webkit-scrollbar-corner {
background: #CFD2E0;
cursor: var(--tui-cur-default);
background: #CFD2E0 !important;
}
*::-webkit-scrollbar-thumb {
cursor: var(--tui-cur-default);
background: #525870;
background: #525870 !important;
border-radius: 0 !important;
}
*::selection {
@@ -30,8 +28,8 @@ body {
width: 100% !important;
margin: 0;
padding: 0;
overflow-x: hidden !important;
overflow-y: scroll !important;
overflow-x: hidden;
overflow-y: scroll;
font-family: var(--tui-font);
-webkit-text-size-adjust: 100%;

View File

@@ -5,6 +5,7 @@
blue: #006EFF;
light-blue: #00A6FF;
green: GREEN;
light-green: #3FE194;
orange: #E7913B;
gray: #666;
light-gray: #AAA;

View File

@@ -0,0 +1,5 @@
import view from "./index.vue";
import Toolkit from "~/utils/Toolkit";
export const AnimatedNumber = Toolkit.withInstall(view);
export default AnimatedNumber;

View File

@@ -0,0 +1,184 @@
<template>
<span class="tui-animated-number">{{ displayValue }}</span>
</template>
<script lang="ts" setup>
defineOptions({
name: "AnimatedNumber"
});
const props = withDefaults(defineProps<{
/** 目标数值 */
value: number;
/** 动画持续时间(毫秒),默认 1000ms */
duration?: number;
/** 小数位数undefined 表示自动 */
decimals?: number;
/** 缓动函数类型 */
easing?: "linear" | "easeOut" | "easeInOut";
/** 是否启用千分位分隔符 */
thousands?: boolean;
/** 千分位分隔符,默认逗号 */
separator?: string;
}>(), {
duration: 1000,
easing: "easeOut",
thousands: false,
separator: ","
});
const { value, duration, decimals, easing, thousands, separator } = toRefs(props);
// 当前显示的数值
const currentValue = ref(value.value);
// 格式化后的显示值
const displayValue = ref(formatNumber(value.value));
// 动画相关
let animationFrameId: number | null = null;
let startValue = value.value;
let startTime: number | null = null;
/**
* 缓动函数
*/
function ease(t: number, type: string): number {
switch (type) {
case "linear":
return t;
case "easeOut":
return 1 - Math.pow(1 - t, 3);
case "easeInOut":
return t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
default:
return t;
}
}
/**
* 添加千分位分隔符
*/
function addThousandsSeparator(numStr: string, sep: string): string {
const parts = numStr.split(".");
const integerPart = parts[0];
const decimalPart = parts[1] ? "." + parts[1] : "";
// 处理负号
const isNegative = integerPart.startsWith("-");
const absoluteInteger = isNegative ? integerPart.slice(1) : integerPart;
// 对整数部分添加千分位分隔符
const formatted = absoluteInteger.replace(/\B(?=(\d{3})+(?!\d))/g, sep);
return (isNegative ? "-" : "") + formatted + decimalPart;
}
/**
* 格式化数字
*/
function formatNumber(num: number): string {
let result: string;
if (decimals.value !== undefined) {
result = num.toFixed(decimals.value);
} else {
// 自动检测小数位数
const str = num.toString();
if (str.includes(".")) {
// 保留原有的小数位数,但最多保留 6 位
const decimalPart = str.split(".")[1];
const decimalLength = Math.min(decimalPart.length, 6);
result = num.toFixed(decimalLength);
} else {
result = num.toString();
}
}
// 应用千分位分隔符
if (thousands.value) {
result = addThousandsSeparator(result, separator.value);
}
return result;
}
/**
* 动画更新函数
*/
function animate(timestamp: number) {
if (startTime === null) {
startTime = timestamp;
}
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration.value, 1);
const easedProgress = ease(progress, easing.value);
// 计算当前值
currentValue.value = startValue + (value.value - startValue) * easedProgress;
displayValue.value = formatNumber(currentValue.value);
if (progress < 1) {
animationFrameId = requestAnimationFrame(animate);
} else {
// 动画结束,确保显示精确的目标值
currentValue.value = value.value;
displayValue.value = formatNumber(value.value);
animationFrameId = null;
startTime = null;
}
}
/**
* 开始动画
*/
function startAnimation() {
// 取消之前的动画
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
startValue = currentValue.value;
startTime = null;
animationFrameId = requestAnimationFrame(animate);
}
// 监听 value 变化
watch(value, (newValue) => {
if (newValue !== currentValue.value) {
startAnimation();
}
});
// 监听 decimals 变化,立即更新显示格式
watch(decimals, () => {
displayValue.value = formatNumber(currentValue.value);
});
// 监听 thousands 和 separator 变化,立即更新显示格式
watch([thousands, separator], () => {
displayValue.value = formatNumber(currentValue.value);
});
// 组件卸载时清理动画
onUnmounted(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
});
// 初始化
onMounted(() => {
currentValue.value = value.value;
displayValue.value = formatNumber(value.value);
});
</script>
<style lang="less" scoped>
.tui-animated-number {
display: inline-block;
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum";
}
</style>

View File

@@ -3,17 +3,20 @@ import Popup from "./popup";
import Captcha from "./captcha";
import MarkdownView from "./markdown-view";
import MarkdownEditor from "./markdown-editor";
import AnimatedNumber from "./animated-number";
export default [
Popup,
Captcha,
MarkdownView,
MarkdownEditor
MarkdownEditor,
AnimatedNumber
];
export {
Popup,
Captcha,
MarkdownView,
MarkdownEditor
MarkdownEditor,
AnimatedNumber
};

View File

@@ -11,7 +11,7 @@ defineOptions({
<style lang="less" scoped>
#tui-popup {
border: var(--tui-border);
z-index: 20;
z-index: 3000;
position: fixed;
font-size: 13px;
visibility: hidden;

View File

@@ -22,8 +22,8 @@ export type Response = {
export type Page = {
index: number;
size: number;
keyword?: string;
orderMap?: { [key: string]: OrderType };
likeMap?: { [key: string]: string };
}
export enum OrderType {

View File

@@ -142,15 +142,25 @@ export default class Toolkit {
}
/**
* 生成随机数
* 生成随机数(整数)
*
* @param min 最小值
* @param max 最大值
* @param min 最小值
* @param max 最大值
*/
public static random(min = 0, max = 100): number {
return Math.floor(Math.random() * (max + 1 - min)) + min;
}
/**
* 生成随机数(浮点数)
*
* @param min 最小值
* @param max 最大值
*/
public static randomDouble(min = 0, max = 1): number {
return Math.random() * (max - min) + min;
}
/**
* Base64 数据转文件
*
@@ -321,4 +331,66 @@ export default class Toolkit {
callback();
}
}
public static doNotNull(arg: any, func: (arg: any) => void): void {
if (arg) {
func(arg);
}
}
public static toCssSize(value: number | string): string {
if (typeof value === "number") {
return `${value}px`;
}
return value;
};
/**
* 设置随机间隔执行
*
* @param config 配置对象
* @param config.handler 处理函数,如果提供了 min 和 max则会接收随机数作为参数
* @param config.handleRate 执行概率0-1 之间,默认 1总是执行
* @param config.interval 间隔时间(毫秒)
* @param config.min 随机数最小值(可选),提供时会生成随机数传给 handler
* @param config.max 随机数最大值(可选),提供时会生成随机数传给 handler
* @returns 定时器 ID
*
* @example
* ```js
* // 简单的随机执行
* setRandomInterval({
* handler: () => console.log('executed'),
* handleRate: 0.5,
* interval: 1000
* })
*
* // 带随机数参数的执行
* setRandomInterval({
* handler: (value) => console.log('random value:', value),
* handleRate: 1,
* min: 0,
* max: 100,
* interval: 1000
* })
* ```
*/
public static setRandomInterval(config: {
handler: Function | ((value: number) => void);
handleRate?: number;
interval?: number;
min?: number;
max?: number;
}): NodeJS.Timeout {
const { handler, handleRate = 1, interval, min, max } = config;
return setInterval(() => {
if (Math.random() < handleRate) {
if (min !== undefined && max !== undefined) {
(handler as (value: number) => void)(this.randomDouble(min, max));
} else {
(handler as Function)();
}
}
}, interval);
}
}

View File

@@ -43,7 +43,6 @@ const VPopup: Directive = {
// 显示
el.addEventListener("mouseenter", async e => {
if (!config.value) {
console.warn("not found popup value", config);
return;
}
if (config.beforeShow) {