Compare commits

...

11 Commits

Author SHA1 Message Date
bc6643a6c2 fix body overflow scroll 2026-01-06 17:38:18 +08:00
ec6b72042e generic KeyValue and LabelValue 2026-01-06 17:38:01 +08:00
207e242696 add Time.toString 2026-01-05 15:11:07 +08:00
d74ddf4ba2 add Time.parseToMS 2026-01-05 15:03:55 +08:00
31879a511d add IOSize.parse 2026-01-05 15:03:44 +08:00
5240b57c47 ignored AI Agent prompt 2026-01-05 15:03:32 +08:00
7cf87a75fe loadSetting() support only key 2026-01-05 14:54:28 +08:00
05e354f148 update Page and Response T 2026-01-05 14:54:09 +08:00
e20d2ea351 support custom copyright text 2025-12-19 11:42:47 +08:00
c9d209d673 support wxml,wxss 2025-12-05 12:04:01 +08:00
381dc73163 remove Page.keyword and add likeMap 2025-12-03 12:01:13 +08:00
11 changed files with 217 additions and 37 deletions

5
.gitignore vendored
View File

@ -26,3 +26,8 @@ dist-ssr
/.eslintrc-auto-import.json
/components.d.ts
/examples/auto-imports.d.ts
/CLAUDE.md
/AGENTS.md
/.claude

View File

@ -22,6 +22,7 @@
],
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/timi-web.mjs",
"require": "./dist/timi-web.umd.js"
},

View File

@ -30,8 +30,8 @@ body {
width: 100% !important;
margin: 0;
padding: 0;
overflow-x: hidden !important;
overflow-y: scroll !important;
overflow-x: hidden;
overflow-y: auto;
font-family: var(--tui-font);
-webkit-text-size-adjust: 100%;

View File

@ -1,6 +1,6 @@
<template>
<div class="tui-copyright">
<p>朝朝频顾惜夜夜不相忘</p>
<p v-text="text"></p>
<p v-if="icp" class="selectable">
<a href="https://beian.miit.gov.cn/" v-text="icp" :title="icp" target="_blank"></a>
</p>
@ -20,7 +20,9 @@ withDefaults(defineProps<{
icp?: string;
domain?: string;
author?: string;
text?: string;
}>(), {
text: "朝朝频顾惜,夜夜不相忘"
});
</script>

View File

@ -53,7 +53,7 @@ export type CommentReplyView = {
export type CommentPage = {
bizType?: CommentBizType;
bizId?: number;
} & Page;
} & Page<Comment>;
export enum CommentReplyBizType {
@ -67,7 +67,7 @@ export enum CommentReplyBizType {
export type CommentReplyPage = {
bizType: CommentReplyBizType
bizId?: number
} & Page;
} & Page<Comment>;
export enum CommentBizType {
ARTICLE = "ARTICLE",

View File

@ -13,17 +13,18 @@ export type Model = {
deletedAt?: number;
}
export type Response = {
export type Response<T> = {
code: number;
msg?: string;
data: object;
data: T;
}
export type Page = {
export type Page<T> = {
index: number;
size: number;
keyword?: string;
orderMap?: { [key: string]: OrderType };
equalsExample?: T;
likesExample?: T;
}
export enum OrderType {
@ -68,12 +69,12 @@ export enum ImageType {
PIXELATED = "ir-pixelated"
}
export type KeyValue<T> = {
key: string;
value: T;
export type KeyValue<V, K = string> = {
key: K;
value: V;
}
export type LabelValue<T> = {
label: string;
value: T;
export type LabelValue<L, V = string> = {
label: L;
value: V;
}

View File

@ -77,4 +77,96 @@ export default class IOSize {
}
return "0 B";
}
/**
* 将格式化的储存量字符串解析为字节量
* <p>支持格式10GB, 10 GB, 1TB, 1.24 KB 等(单位不区分大小写)</p>
* <pre>
* // 返回 102400
* IOSize.parse("100 KB");
* // 返回 1073741824
* IOSize.parse("1GB");
* </pre>
*
* @param sizeStr 格式化后的储存量字符串
* @return 字节量
* @throws Error 格式无效
*/
public static parse(sizeStr: string): number {
if (!sizeStr || sizeStr.trim() === "") {
throw new Error("not found sizeStr");
}
// 正则匹配:可选符号 + 数字(含小数点)+ 可选空格 + 单位
const pattern = /([-+]?\d+(?:\.\d+)?)\s*([a-zA-Z]+)/;
const matcher = pattern.exec(sizeStr.trim());
if (matcher) {
let value: number;
try {
value = parseFloat(matcher[1]);
} catch (e) {
throw new Error("invalid number format: " + matcher[1]);
}
if (value < 0) {
throw new Error("size cannot be negative: " + value);
}
const unitStr = matcher[2].toUpperCase();
let unit: Unit;
// 先尝试精确匹配枚举
if (Object.values(Unit).includes(unitStr as Unit)) {
unit = unitStr as Unit;
} else {
// 处理单字母单位缩写K/M/G/T/P/E
switch (unitStr.charAt(0)) {
case "K":
unit = Unit.KB;
break;
case "M":
unit = Unit.MB;
break;
case "G":
unit = Unit.GB;
break;
case "T":
unit = Unit.TB;
break;
case "P":
unit = Unit.PB;
break;
case "E":
unit = Unit.EB;
break;
case "B":
unit = Unit.B;
break;
default:
throw new Error("Unknown unit: " + unitStr);
}
}
return IOSize.toBytes(value, unit);
}
// 尝试解析纯数字(无单位,默认字节)
try {
const bytes = parseInt(sizeStr.trim());
if (bytes < 0) {
throw new Error("size cannot be negative: " + bytes);
}
return bytes;
} catch (e) {
throw new Error("invalid size format: " + sizeStr);
}
}
/**
* 转换值到字节量
*
* @param value 值
* @param unit 单位
* @return 字节量
*/
private static toBytes(value: number, unit: Unit): number {
const units = Object.values(Unit);
const ordinal = units.indexOf(unit);
return Math.round(value * Math.pow(1024, ordinal));
}
}

View File

@ -2,7 +2,7 @@ import axios, { InternalAxiosRequestConfig } from "axios";
import { Response } from "~/types/Model";
import { Cooker, Time, userStore } from "~/index";
type ErrorCallback = (response: Response) => void;
type ErrorCallback = (response: Response<any>) => void;
let globalErrorCallback: ErrorCallback | null = null;
@ -36,7 +36,7 @@ axios.defaults.withCredentials = true;
axios.interceptors.response.use((axiosResp: any) => {
if (!axiosResp.config.responseType) {
// 服务端返回
const serverResp = axiosResp.data as Response;
const serverResp = axiosResp.data as Response<any>;
if (serverResp.code < 40000) {
// 200 或 300 HTTP 状态段视为成功
return serverResp.data;

View File

@ -50,8 +50,8 @@ export default class Prismjs {
this.map.set(PrismjsType.Initialization, {extensions: ["ini"], prismjs: "ini", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.PHP, {extensions: ["php"], prismjs: "php", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.SQL, {extensions: ["sql"], prismjs: "sql", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.XML, {extensions: ["xml", "fxml"], prismjs: "xml", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.CSS, {extensions: ["css"], prismjs: "css", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.XML, {extensions: ["xml", "fxml", "wxml"], prismjs: "xml", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.CSS, {extensions: ["css", "wxss"], prismjs: "css", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.LESS, {extensions: ["less"], prismjs: "less", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.Markup, {extensions: ["htm", "html"], prismjs: "markup", viewer: PrismjsViewer.CODE});
this.map.set(PrismjsType.YAML, {extensions: ["yml", "yaml"], prismjs: "yaml", viewer: PrismjsViewer.CODE});

View File

@ -9,7 +9,7 @@ export default class SettingMapper {
private map = new Map<string, Ref<string>>();
public static async loadSetting(...settings: { key: string, args?: { [key: string]: any }}[]): Promise<void> {
public static async loadSetting(...settings: (string | { key: string, args?: { [key: string]: any }})[]): Promise<void> {
const map = new Map<string, object | undefined>();
{
// 默认配置
@ -34,7 +34,12 @@ export default class SettingMapper {
{
// 附加配置
for (let i = 0; i < settings.length; i++) {
map.set(settings[i].key, settings[i].args);
const setting = settings[i];
if (typeof setting === 'string') {
map.set(setting, undefined);
} else {
map.set(setting.key, setting.args);
}
}
}
const instance = this.getInstance();

View File

@ -69,6 +69,26 @@ export default class Time {
}
}
/**
* 将毫秒值转换为可读的时间字符串
*
* @param ms 毫秒值
* @return 时间字符串,如 "10 天"、"5 小时"、"30 分钟"、"15 秒"、"500 毫秒"
*/
public static toString(ms: number): string {
if (Time.D <= ms) {
return `${Math.floor(ms / Time.D)}`;
} else if (Time.H <= ms) {
return `${Math.floor(ms / Time.H)} 小时`;
} else if (Time.M <= ms) {
return `${Math.floor(ms / Time.M)} 分钟`;
} else if (Time.S <= ms) {
return `${Math.floor(ms / Time.S)}`;
} else {
return `${Math.floor(ms)} 毫秒`;
}
}
public static between(begin: Date, end?: Date) : any {
if (!end) {
end = new Date();
@ -94,4 +114,58 @@ export default class Time {
}
return `${minutes.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`;
}
/**
* 将时间字符串解析为毫秒值
*
* <p>支持的格式示例:
* <pre>
* 10 = 10
* 10ms = 10
* 10s = 10,000
* 10m = 600,000
* 10h = 36,000,000
* 10d = 10D = 10 d = 864,000,000
* 10.5d = 907,200,000
* </pre>
*
* @param timeStr 时间字符串
* @return 毫秒
* @throws Error 输入格式无效
*/
public static parseToMS(timeStr: string): number {
if (!timeStr || timeStr.trim() === "") {
throw new Error("not found timeStr");
}
const normalized = timeStr.replace(/\s+/g, "").toLowerCase();
const pattern = /^(\d+(?:\.\d+)?)(ms|[dhms])?$/;
const matcher = pattern.exec(normalized);
if (!matcher) {
throw new Error("invalid format: " + timeStr);
}
const value = parseFloat(matcher[1]);
const unit = matcher[2];
if (!unit || unit === "ms") {
return Math.round(value);
}
let multiplier: number;
switch (unit) {
case "s":
multiplier = Time.S;
break;
case "m":
multiplier = Time.M;
break;
case "h":
multiplier = Time.H;
break;
case "d":
multiplier = Time.D;
break;
default:
throw new Error("invalid format unit: " + unit);
}
return Math.round(value * multiplier);
}
}