小程序初始提交

This commit is contained in:
jdc
2025-11-13 10:36:23 +08:00
parent f26b4f9a2f
commit 5db3b180eb
447 changed files with 83351 additions and 0 deletions

View File

@@ -0,0 +1,349 @@
<template>
<!-- #ifdef MP -->
<view
class="cl-text"
:class="[
{
'cl-text--pre-wrap': preWrap,
'cl-text--ellipsis': ellipsis,
'cl-text--default-size': isDefaultSize
},
ptClassName
]"
:style="textStyle"
:selectable="selectable"
:space="space"
:decode="decode"
:key="cache.key"
>
<slot>{{ content }}</slot>
</view>
<!-- #endif -->
<!-- #ifndef MP -->
<text
class="cl-text"
:class="[
{
'cl-text--pre-wrap': preWrap,
'cl-text--ellipsis': ellipsis,
'cl-text--default-size': isDefaultSize
},
ptClassName
]"
:style="textStyle"
:selectable="selectable"
:space="space"
:decode="decode"
:key="cache.key"
>
<slot>{{ content }}</slot>
</text>
<!-- #endif -->
</template>
<script setup lang="ts">
import { computed, type PropType } from "vue";
import { ctx, hasTextColor, hasTextSize, isDark, parsePt, useCache } from "@/cool";
import type { ClTextType } from "../../types";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-text"
});
// 组件属性定义
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 显示的值
value: {
type: [String, Number] as PropType<string | number | null>,
default: null
},
// 文本颜色
color: {
type: String,
default: ""
},
// 字体大小
size: {
type: [Number, String] as PropType<number | string | null>,
default: null
},
// 文本类型
type: {
type: String as PropType<ClTextType>,
default: "default"
},
// 是否开启脱敏/加密
mask: {
type: Boolean,
default: false
},
// 金额货币符号
currency: {
type: String,
default: "¥"
},
// 金额小数位数
precision: {
type: Number,
default: 2
},
// 脱敏起始位置
maskStart: {
type: Number,
default: 3
},
// 脱敏结束位置
maskEnd: {
type: Number,
default: 4
},
// 脱敏替换字符
maskChar: {
type: String,
default: "*"
},
// 是否省略号
ellipsis: {
type: Boolean,
default: false
},
// 最大行数仅在ellipsis时生效
lines: {
type: Number,
default: 1
},
// 是否可选择
selectable: {
type: Boolean,
default: false
},
// 显示连续空格
space: {
type: String as PropType<"ensp" | "emsp" | "nbsp">,
default: ""
},
// 是否解码 (app平台如需解析字符实体需要配置为 true)
decode: {
type: Boolean,
default: false
},
// 是否保留单词
preWrap: {
type: Boolean,
default: false
}
});
// 缓存
const { cache } = useCache(() => [props.color, props]);
// 透传样式类型
type PassThrough = {
className?: string;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 文本大小
const { getSize, getLineHeight, ptClassName } = useSize(() => pt.value.className ?? "");
// 文本颜色
const color = computed(() => {
if (props.color != "") {
switch (props.color) {
case "primary":
return ctx.color["primary-500"] as string;
case "success":
return "#22c55e";
case "warn":
return "#eab308";
case "error":
return "#ef4444";
case "info":
return isDark.value
? (ctx.color["surface-300"] as string)
: (ctx.color["surface-500"] as string);
case "dark":
return ctx.color["surface-700"] as string;
case "light":
return ctx.color["surface-50"] as string;
case "disabled":
return ctx.color["surface-400"] as string;
default:
return props.color;
}
}
return isDark.value ? "white" : (ctx.color["surface-700"] as string);
});
// 是否默认大小
const isDefaultSize = computed(() => !hasTextSize(pt.value.className ?? ""));
// 文本样式
const textStyle = computed(() => {
const style = {};
// 省略号
if (props.ellipsis) {
style["lines"] = props.lines;
}
// 判断是不是有颜色样式
if (!hasTextColor(ptClassName.value)) {
style["color"] = color.value;
}
// 字号
const fontSize = getSize(props.size);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
// 行高
const lineHeight = getLineHeight();
if (lineHeight != null) {
style["lineHeight"] = lineHeight;
}
return style;
});
/**
* 手机号脱敏处理
* 保留前3位和后4位中间4位替换为掩码
*/
function formatPhone(phone: string): string {
if (phone.length != 11 || !props.mask) return phone;
return phone.replace(/(\d{3})\d{4}(\d{4})/, `$1${props.maskChar.repeat(4)}$2`);
}
/**
* 姓名脱敏处理
* 2个字时保留第1个字
* 大于2个字时保留首尾字
*/
function formatName(name: string): string {
if (name.length <= 1 || !props.mask) return name;
if (name.length == 2) {
return name[0] + props.maskChar;
}
return name[0] + props.maskChar.repeat(name.length - 2) + name[name.length - 1];
}
/**
* 金额格式化
* 1. 处理小数位数
* 2. 添加千分位分隔符
* 3. 添加货币符号
*/
function formatAmount(amount: string | number): string {
let num: number;
if (typeof amount == "number") {
num = amount;
} else {
num = parseFloat(amount);
}
const formatted = num.toFixed(props.precision);
const parts = formatted.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return props.currency + parts.join(".");
}
/**
* 银行卡号脱敏
* 保留开头和结尾指定位数,中间用掩码替换
*/
function formatCard(card: string): string {
if (card.length < 8 || !props.mask) return card;
const start = card.substring(0, props.maskStart);
const end = card.substring(card.length - props.maskEnd);
const middle = props.maskChar.repeat(card.length - props.maskStart - props.maskEnd);
return start + middle + end;
}
/**
* 邮箱脱敏处理
* 保留用户名首尾字符和完整域名
*/
function formatEmail(email: string): string {
if (!props.mask) return email;
const atIndex = email.indexOf("@");
if (atIndex == -1) return email;
const username = email.substring(0, atIndex);
const domain = email.substring(atIndex);
if (username.length <= 2) return email;
const maskedUsername =
username[0] + props.maskChar.repeat(username.length - 2) + username[username.length - 1];
return maskedUsername + domain;
}
/**
* 根据不同类型格式化显示
*/
const content = computed(() => {
const val = props.value ?? "";
switch (props.type) {
case "phone":
return formatPhone(val as string);
case "name":
return formatName(val as string);
case "amount":
return formatAmount(val as number);
case "card":
return formatCard(val as string);
case "email":
return formatEmail(val as string);
default:
return val;
}
});
</script>
<style lang="scss" scoped>
.cl-text {
// #ifndef APP
flex-shrink: unset;
// #endif
&--pre-wrap {
// #ifndef APP
white-space: pre-wrap;
// #endif
}
&--ellipsis {
text-overflow: ellipsis;
// #ifndef APP
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: v-bind(lines);
line-break: anywhere;
-webkit-box-orient: vertical;
// #endif
}
&--default-size {
@apply text-md;
}
}
</style>

View File

@@ -0,0 +1,26 @@
import type { ClTextType } from "../../types";
export type ClTextPassThrough = {
className?: string;
};
export type ClTextProps = {
className?: string;
pt?: ClTextPassThrough;
value?: string | number | any;
color?: string;
size?: number | string | any;
type?: ClTextType;
mask?: boolean;
currency?: string;
precision?: number;
maskStart?: number;
maskEnd?: number;
maskChar?: string;
ellipsis?: boolean;
lines?: number;
selectable?: boolean;
space?: "ensp" | "emsp" | "nbsp";
decode?: boolean;
preWrap?: boolean;
};