添加全局字号,动态调整大小

This commit is contained in:
icssoa
2025-08-13 15:21:16 +08:00
parent b04cfb0066
commit e7b17195dd
49 changed files with 682 additions and 292 deletions

View File

@@ -15,7 +15,12 @@
<slot name="prepend"></slot>
<view class="cl-action-sheet__description" v-if="config.description != ''">
<text class="text-surface-400 text-md">{{ config.description }}</text>
<cl-text
:pt="{
className: '!text-surface-400 !text-md text-center'
}"
>{{ config.description }}</cl-text
>
</view>
<view class="cl-action-sheet__list" :class="[pt.list?.className]">

View File

@@ -13,10 +13,16 @@
},
pt.className
]"
:style="badgeStyle"
>
<text class="cl-badge__text" v-if="!dot">
<cl-text
:pt="{
className: parseClass(['cl-badge__text', pt.text?.className])
}"
v-if="!dot"
>
{{ value }}
</text>
</cl-text>
<slot></slot>
</view>
@@ -25,8 +31,9 @@
<script setup lang="ts">
import { computed } from "vue";
import type { PropType } from "vue";
import type { Type } from "../../types";
import { parsePt } from "@/cool";
import type { PassThroughProps, Type } from "../../types";
import { parseClass, parsePt } from "@/cool";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-badge"
@@ -55,11 +62,39 @@ const props = defineProps({
}
});
const { getRpx } = useSize();
type PassThrough = {
className?: string;
text?: PassThroughProps;
};
const pt = computed(() => parsePt<PassThrough>(props.pt));
const badgeStyle = computed(() => {
const style = {};
if (props.dot) {
style["height"] = getRpx(10);
style["width"] = getRpx(10);
style["minWidth"] = getRpx(10);
style["padding"] = 0;
} else {
style["height"] = getRpx(30);
style["minWidth"] = getRpx(30);
style["padding"] = `0 ${getRpx(6)}`;
}
if (props.position) {
style["transform"] = "translate(50%, -50%)";
if (props.dot) {
style["transform"] = `translate(-${getRpx(5)}, ${getRpx(5)})`;
}
}
return style;
});
</script>
<style lang="scss" scoped>
@@ -67,29 +102,12 @@ const pt = computed(() => parsePt<PassThrough>(props.pt));
@apply flex flex-row items-center justify-center;
@apply rounded-full;
height: 30rpx;
min-width: 30rpx;
padding: 0 6rpx;
&__text {
@apply text-white;
font-size: 18rpx;
}
&--dot {
height: 10rpx;
width: 10rpx;
min-width: 10rpx;
padding: 0;
@apply text-white text-sm;
}
&--position {
@apply absolute z-10 right-0 top-0;
transform: translate(50%, -50%);
&.cl-badge--dot {
transform: translate(-5rpx, 5rpx);
}
}
}
</style>

View File

@@ -18,13 +18,24 @@
v-if="showIcon"
></image>
<text class="cl-empty__text dark:!text-surface-100" v-if="text">{{ text }}</text>
<cl-text
:pt="{
className: parseClass([
'cl-empty__text',
{
'!text-surface-100': isDark
}
])
}"
v-if="text"
>{{ text }}</cl-text
>
</view>
</template>
<script lang="ts" setup>
import { t } from "@/locale";
import { parsePt, parseRpx } from "@/cool";
import { isDark, parseClass, parsePt, parseRpx } from "@/cool";
import { computed } from "vue";
// 组件属性定义

View File

@@ -24,8 +24,9 @@
<script setup lang="ts">
import { computed, type PropType } from "vue";
import { forInObject, get, has, parseRpx, parsePt, useCache, isDark } from "@/cool";
import { forInObject, get, has, parsePt, useCache, isDark } from "@/cool";
import { icons } from "@/icons";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-icon"
@@ -68,6 +69,9 @@ const props = defineProps({
// 缓存
const { cache } = useCache(() => [props.color]);
// 字号
const { getRpx } = useSize();
// 透传样式类型定义
type PassThrough = {
className?: string;
@@ -126,11 +130,14 @@ const iconStyle = computed(() => {
style["color"] = props.color;
}
style["fontFamily"] = icon.value.font;
style["fontSize"] = parseRpx(props.size!);
style["height"] = parseRpx(props.height ?? props.size!);
style["width"] = parseRpx(props.width ?? props.size!);
style["lineHeight"] = parseRpx(props.size!);
if (icon.value.font != "") {
style["fontFamily"] = icon.value.font;
}
style["fontSize"] = getRpx(props.size!);
style["height"] = getRpx(props.height ?? props.size!);
style["width"] = getRpx(props.width ?? props.size!);
style["lineHeight"] = getRpx(props.size!);
return style;
});

View File

@@ -1,5 +1,13 @@
<template>
<view class="cl-input-otp" :class="[pt.className]">
<view
class="cl-input-otp"
:class="[
{
'cl-input-otp--disabled': disabled
},
pt.className
]"
>
<view class="cl-input-otp__inner">
<cl-input
v-model="value"
@@ -33,16 +41,11 @@
pt.item?.className
]"
>
<text
class="cl-input-otp__value"
:class="[
{
'is-disabled': disabled,
'is-dark': isDark
},
pt.value?.className
]"
>{{ item }}</text
<cl-text
:pt="{
className: pt.value?.className
}"
>{{ item }}</cl-text
>
<view
class="cl-input-otp__cursor"
@@ -268,14 +271,6 @@ function animateCursor(isIncreasing: boolean) {
}
}
&__value {
@apply text-surface-700 text-md;
&.is-dark {
@apply text-white;
}
}
&__cursor {
@apply absolute;
@apply bg-primary-500;
@@ -302,5 +297,9 @@ function animateCursor(isIncreasing: boolean) {
}
}
// #endif
&--disabled {
@apply opacity-50;
}
}
</style>

View File

@@ -30,8 +30,9 @@
'is-disabled': isDisabled,
'is-dark': isDark
},
pt.inner?.className
ptClassName
]"
:style="inputStyle"
:value="value"
:disabled="readonly ?? isDisabled"
:type="type"
@@ -86,7 +87,7 @@ import type { ClInputType, PassThroughProps } from "../../types";
import { isDark, parseClass, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import { t } from "@/locale";
import { useForm, useFormItem } from "../../hooks";
import { useForm, useFormItem, useSize } from "../../hooks";
defineOptions({
name: "cl-input"
@@ -225,6 +226,22 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 输入框样式
const inputStyle = computed(() => {
const style = {};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
// 绑定值
const value = ref<string>("");

View File

@@ -22,10 +22,21 @@
class="cl-keyboard-car__value"
:class="[pt.value?.className]"
>
<text class="cl-keyboard-car__value-text dark:!text-white" v-if="value != ''">{{
valueText
}}</text>
<text class="cl-keyboard-car__value-placeholder" v-else>{{ placeholder }}</text>
<cl-text
v-if="value != ''"
:pt="{
className: '!text-2xl'
}"
>{{ valueText }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
</view>
</slot>
@@ -70,9 +81,7 @@
color="white"
></cl-icon>
<text v-else class="cl-keyboard-car__item-text dark:!text-white">{{
item
}}</text>
<cl-text v-else>{{ item }}</cl-text>
</slot>
</view>
</view>
@@ -289,16 +298,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -327,10 +328,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg;
}
&.is-fill {
flex: 1;
}
@@ -347,10 +344,6 @@ defineExpose({
&.is-keycode-confirm {
@apply bg-primary-500;
flex: 1;
.cl-keyboard-car__item-text {
@apply text-white;
}
}
&.is-empty {

View File

@@ -22,14 +22,21 @@
class="cl-keyboard-number__value"
:class="[pt.value?.className]"
>
<text
class="cl-keyboard-number__value-text dark:!text-white"
<cl-text
v-if="value != ''"
>{{ value }}</text
:pt="{
className: '!text-2xl'
}"
>{{ value }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
<text class="cl-keyboard-number__value-placeholder" v-else>{{
placeholder
}}</text>
</view>
</slot>
@@ -61,15 +68,21 @@
v-else-if="item == 'confirm'"
class="cl-keyboard-number__item-confirm"
>
<text class="cl-keyboard-number__item-text">{{
confirmText
}}</text>
<cl-text
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
</view>
<text
<cl-text
v-else
class="cl-keyboard-number__item-text dark:!text-white"
>{{ item }}</text
:pt="{
className: '!text-lg'
}"
>{{ item }}</cl-text
>
</slot>
</view>
@@ -346,16 +359,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -372,10 +377,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg;
}
&.is-keycode-delete {
@apply bg-surface-200;
@@ -386,10 +387,6 @@ defineExpose({
&.is-keycode-confirm {
@apply bg-transparent relative;
.cl-keyboard-number__item-text {
@apply text-white;
}
}
&-confirm {

View File

@@ -22,14 +22,21 @@
class="cl-keyboard-password__value"
:class="[pt.value?.className]"
>
<text
class="cl-keyboard-password__value-text dark:!text-white"
<cl-text
v-if="value != ''"
>{{ valueText }}</text
:pt="{
className: '!text-2xl'
}"
>{{ valueText }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
<text class="cl-keyboard-password__value-placeholder" v-else>{{
placeholder
}}</text>
</view>
</slot>
@@ -66,22 +73,29 @@
:size="36"
></cl-icon>
<text
<cl-text
v-else-if="item == 'confirm'"
class="cl-keyboard-password__item-text"
>{{ confirmText }}</text
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
<text
<cl-text
v-else-if="item == 'letter'"
class="cl-keyboard-password__item-text dark:!text-white"
>ABC</text
:pt="{
className: '!text-lg'
}"
>ABC</cl-text
>
<text
<cl-text
v-else-if="item == 'number'"
class="cl-keyboard-password__item-text dark:!text-white"
>123</text
:pt="{
className: '!text-lg'
}"
>123</cl-text
>
<template v-else-if="item == 'caps'">
@@ -97,9 +111,13 @@
></cl-badge>
</template>
<text v-else class="cl-keyboard-password__item-text dark:!text-white">{{
item
}}</text>
<cl-text
v-else
:pt="{
className: '!text-lg'
}"
>{{ item }}</cl-text
>
</slot>
</view>
</view>
@@ -364,16 +382,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -403,10 +413,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg bg-transparent;
}
&.is-keycode-number,
&.is-keycode-letter {
width: 150rpx;
@@ -434,10 +440,6 @@ defineExpose({
@apply bg-primary-500;
width: 150rpx !important;
flex: none;
.cl-keyboard-password__item-text {
@apply text-white;
}
}
&.is-empty {

View File

@@ -21,8 +21,9 @@
// #ifdef APP
transform: `rotate(${rotate}deg)`,
// #endif
height: parseRpx(size!),
width: parseRpx(size!),
height: getRpx(size!),
width: getRpx(size!),
borderWidth: getRpx(2),
borderTopColor: color,
borderRightColor: 'transparent',
borderBottomColor: color,
@@ -35,8 +36,9 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { isDark, parsePt, parseRpx } from "@/cool";
import { isDark, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-loading"
@@ -66,6 +68,8 @@ const props = defineProps({
}
});
const { getRpx } = useSize();
// 透传样式类型定义
type PassThrough = {
className?: string;
@@ -114,7 +118,6 @@ onMounted(() => {
.cl-loading {
@apply flex flex-row items-center justify-center rounded-full;
@apply border-surface-700 border-solid;
border-width: 2rpx;
&--dark {
border-color: white !important;

View File

@@ -16,10 +16,11 @@
}"
>
<slot name="text" :item="item">
<text
class="cl-noticebar__text dark:!text-white"
:class="[pt.text?.className]"
>{{ item }}</text
<cl-text
:pt="{
className: parseClass(['cl-noticebar__text', pt.text?.className])
}"
>{{ item }}</cl-text
>
</slot>
</view>
@@ -37,7 +38,7 @@ import {
type PropType,
watch
} from "vue";
import { parseRpx, parsePt } from "@/cool";
import { parseRpx, parsePt, parseClass } from "@/cool";
import type { PassThroughProps } from "../../types";
defineOptions({
@@ -248,7 +249,6 @@ onUnmounted(() => {
}
&__text {
@apply text-md text-surface-700;
white-space: nowrap;
}
}

View File

@@ -30,13 +30,17 @@
]"
@tap="toPage(item)"
>
<text
class="cl-pagination__item-text"
:class="{
'is-active': item == value,
'is-dark': isDark
<cl-text
:pt="{
className: parseClass([
'cl-pagination__item-text',
{
'is-active': item == value,
'is-dark': isDark
}
])
}"
>{{ item }}</text
>{{ item }}</cl-text
>
</view>
@@ -61,7 +65,7 @@
<script setup lang="ts">
import type { PassThroughProps } from "../../types";
import { isDark, parsePt } from "@/cool";
import { isDark, parseClass, parsePt } from "@/cool";
import { computed, ref, watch } from "vue";
defineOptions({

View File

@@ -1,11 +1,13 @@
<template>
<view class="cl-picker-view">
<view class="cl-picker-view__header" v-if="headers.length > 0">
<text
class="cl-picker-view__header-item dark:!text-white"
<cl-text
:pt="{
className: 'flex-1 !text-sm text-center'
}"
v-for="(label, index) in headers"
:key="index"
>{{ label }}</text
>{{ label }}</cl-text
>
</view>
@@ -168,11 +170,6 @@ function onChange(e: UniPickerViewChangeEvent) {
&__header {
@apply flex flex-row items-center py-4;
&-item {
@apply text-center text-sm text-surface-700;
flex: 1;
}
}
&__item {

View File

@@ -252,7 +252,7 @@ const paddingBottom = computed(() => {
let h = 0;
if (props.direction == "bottom") {
h += hasCustomTabBar() ? getTabBarHeight() : getSafeAreaHeight("bottom");
h += getSafeAreaHeight("bottom");
}
return h + "px";

View File

@@ -28,13 +28,14 @@
{{ text }}
</cl-text>
<text
class="cl-select-trigger__placeholder"
:class="[pt.placeholder?.className]"
<cl-text
:pt="{
className: parseClass(['!text-surface-400', pt.placeholder?.className])
}"
v-else
>
{{ placeholder }}
</text>
</cl-text>
</view>
<view v-if="showText && !isDisabled" class="cl-select-trigger__icon" @tap.stop="clear">
@@ -152,10 +153,6 @@ function open() {
height: 66rpx;
padding: 0 20rpx;
&__placeholder {
@apply text-surface-400 text-md;
}
&__content {
flex: 1;
}

View File

@@ -26,6 +26,8 @@
}"
>
<view class="cl-select-popup" @touchmove.stop>
<slot name="prepend"></slot>
<view class="cl-select-popup__picker">
<cl-picker-view
:value="indexes"
@@ -34,6 +36,8 @@
></cl-picker-view>
</view>
<slot name="append"></slot>
<view class="cl-select-popup__op">
<cl-button
v-if="showCancel"
@@ -73,6 +77,11 @@ defineOptions({
name: "cl-select"
});
defineSlots<{
prepend(): any;
append(): any;
}>();
// 值类型
type Value = string[] | number[] | number | string | null;
@@ -146,7 +155,7 @@ const props = defineProps({
});
// 定义事件
const emit = defineEmits(["update:modelValue", "change"]);
const emit = defineEmits(["update:modelValue", "change", "changing"]);
// 弹出层引用
const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
@@ -230,6 +239,11 @@ const text = computed(() => {
.join(props.splitor);
});
// 获取当前选中值
function getValue() {
return props.columnCount == 1 ? value.value[0] : value.value;
}
// 选择器值改变事件
function onChange(a: number[]) {
// 复制当前组件内部维护的索引数组
@@ -258,6 +272,9 @@ function onChange(a: number[]) {
indexes.value = b;
// 根据最新的索引数组,更新选中的值数组
value.value = b.map((e, i) => columns.value[i][e].value);
// 触发changing事件
emit("changing", getValue());
}
// 选择器显示状态
@@ -291,7 +308,7 @@ function clear() {
// 确认选择
function confirm() {
// 根据列数返回单个值或数组
const val = props.columnCount == 1 ? value.value[0] : value.value;
const val = getValue();
// 触发更新事件
emit("update:modelValue", val);

View File

@@ -35,19 +35,21 @@
@tap="change(index)"
>
<slot name="item" :item="item" :active="item.isActive">
<text
class="cl-tabs__item-label"
:class="[
{
'is-active': item.isActive,
'is-disabled': item.disabled,
'is-dark': isDark,
'is-fill': fill
},
pt.text?.className
]"
<cl-text
:pt="{
className: parseClass([
'cl-tabs__item-label',
{
'is-active': item.isActive,
'is-disabled': item.disabled,
'is-dark': isDark,
'is-fill': fill
},
pt.text?.className
])
}"
:style="getTextStyle(item)"
>{{ item.label }}</text
>{{ item.label }}</cl-text
>
</slot>
</view>
@@ -73,7 +75,7 @@
<script lang="ts" setup>
import { type PropType, computed, getCurrentInstance, nextTick, onMounted, ref, watch } from "vue";
import { isDark, isEmpty, isHarmony, isNull, parsePt, parseRpx, rpx2px } from "@/cool";
import { isDark, isEmpty, isHarmony, isNull, parseClass, parsePt, parseRpx, rpx2px } from "@/cool";
import type { ClTabsItem, PassThroughProps } from "../../types";
// 定义标签类型

View File

@@ -18,7 +18,7 @@
'!text-surface-50': color == 'light',
'!text-surface-400': color == 'disabled'
},
pt.className
ptClassName
]"
:style="textStyle"
:selectable="selectable"
@@ -49,7 +49,7 @@
'!text-surface-50': color == 'light',
'!text-surface-400': color == 'disabled'
},
pt.className
ptClassName
]"
:style="textStyle"
:selectable="selectable"
@@ -66,6 +66,7 @@
import { computed, type PropType } from "vue";
import { isDark, parsePt, useCache } from "@/cool";
import type { ClTextType } from "../../types";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-text"
@@ -88,6 +89,11 @@ const props = defineProps({
type: String,
default: ""
},
// 字体大小
size: {
type: [Number, String] as PropType<number | string | null>,
default: null
},
// 文本类型
type: {
type: String as PropType<ClTextType>,
@@ -161,6 +167,9 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 文本大小
const { getSize, getLineHeight, ptClassName } = useSize(() => pt.value.className ?? "");
// 文本样式
const textStyle = computed(() => {
const style = {};
@@ -169,6 +178,18 @@ const textStyle = computed(() => {
style["color"] = props.color;
}
// 字号
const fontSize = getSize(props.size);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
// 行高
const lineHeight = getLineHeight();
if (lineHeight != null) {
style["lineHeight"] = lineHeight;
}
return style;
});

View File

@@ -20,11 +20,9 @@
'is-disabled': isDisabled,
'is-dark': isDark
},
pt.inner?.className
ptClassName
]"
:style="{
height: parseRpx(height)
}"
:style="textareaStyle"
:value="value"
:name="name"
:disabled="readonly ?? isDisabled"
@@ -48,12 +46,14 @@
@input="onInput"
@linechange="onLineChange"
@blur="onBlur"
@keyboardheightchange="onKeyboardheightchange"
@focus="onFocus"
@keyboardheightchange="onKeyboardheightchange"
/>
<text class="cl-textarea__count" v-if="showWordLimit"
>{{ value.length }}/{{ maxlength }}</text
<cl-text
:pt="{ className: 'absolute right-2 bottom-2 !text-xs !text-surface-400' }"
v-if="showWordLimit"
>{{ value.length }} / {{ maxlength }}</cl-text
>
</view>
</template>
@@ -64,7 +64,7 @@ import { parsePt, parseRpx } from "@/cool";
import type { PassThroughProps } from "../../types";
import { isDark } from "@/cool";
import { t } from "@/locale";
import { useForm, useFormItem } from "../../hooks";
import { useForm, useFormItem, useSize } from "../../hooks";
defineOptions({
name: "cl-textarea"
@@ -243,6 +243,24 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 文本框样式
const textareaStyle = computed(() => {
const style = {
height: parseRpx(props.height)
};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
// 绑定值
const value = ref(props.modelValue);
@@ -348,10 +366,6 @@ defineExpose({
padding-left: 20rpx;
}
&__count {
@apply text-surface-400 text-sm absolute right-2 bottom-2;
}
&--border {
@apply border border-solid border-surface-200;
}

View File

@@ -37,7 +37,13 @@
></cl-icon>
</view>
<text class="cl-toast__text">{{ item.message }}</text>
<cl-text
color="white"
:pt="{
className: 'text-center'
}"
>{{ item.message }}</cl-text
>
</view>
</view>
</cl-popup>
@@ -157,10 +163,6 @@ defineExpose({
max-width: 600rpx;
opacity: 0;
&__text {
@apply text-md text-center font-bold text-white;
}
&.is-open {
opacity: 1;
}

View File

@@ -31,7 +31,7 @@
<cl-text
:color="color"
:pt="{
className: parseClass(['!text-[16px]', pt.title?.className])
className: parseClass(['!text-md', pt.title?.className])
}"
>
{{ title }}

View File

@@ -43,7 +43,7 @@
<view
v-if="isAdd"
class="cl-upload"
class="cl-upload is-add"
:class="[
{
'is-dark': isDark,
@@ -109,7 +109,7 @@ const props = defineProps({
// 上传按钮显示的文本
text: {
type: String,
default: () => t("上传/拍摄")
default: () => t("上传 / 拍摄")
},
// 图片压缩方式original原图compressed压缩图
sizeType: {
@@ -421,6 +421,10 @@ watch(
@apply opacity-50;
}
&.is-add {
@apply p-1;
}
&__image {
@apply w-full h-full absolute top-0 left-0;
transition-property: opacity;

View File

@@ -1,11 +1,15 @@
import { reactive } from "vue";
type Config = {
fontSize: number | null;
zIndex: number;
startDate: string;
endDate: string;
};
export const config: Config = {
export const config = reactive<Config>({
fontSize: null,
zIndex: 600,
startDate: "2000-01-01 00:00:00",
endDate: "2050-12-31 23:59:59"
};
});

View File

@@ -1,4 +1,5 @@
export * from "./component";
export * from "./form";
export * from "./page";
export * from "./size";
export * from "./ui";

View File

@@ -0,0 +1,130 @@
import { computed, type ComputedRef } from "vue";
import { config } from "../config";
import { px2rpx } from "@/cool";
/**
* 字号管理类
* 用于处理文本大小的缩放和样式
*/
class Size {
// 预设的字号类名
public names = [
"text-xs",
"text-sm",
"text-md",
"text-lg",
"text-xl",
"text-2xl",
"text-3xl",
"text-4xl",
"text-5xl",
"text-6xl",
"text-7xl",
"text-8xl",
"text-9xl"
];
// 对应的字号大小(px)
public sizes = [10, 12, 14, 16, 18, 22, 28, 34, 46, 58, 70, 94, 126];
// 对应的行高(px)
public lineHeights = [14, 18, 22, 26, 26, 1, 1, 1, 1, 1, 1, 1, 1];
// 原始类名
public className: ComputedRef<string> = computed(() => "");
// 计算后的类名
public ptClassName: ComputedRef<string>;
constructor(cb: (() => string) | null) {
this.className = computed(cb ?? (() => ""));
// 根据全局字号配置动态计算类名
this.ptClassName = computed(() => {
if (config.fontSize == null) {
return this.className.value;
}
const name = this.names[this.getIndex()];
return this.className.value.replace(`-important-${name}`, "").replace(name, "");
});
}
/**
* 获取全局字号缩放比例
*/
getScale = () => {
return config.fontSize ?? 1;
};
/**
* 根据缩放比例计算rpx值
* @param val - 需要转换的值
*/
getRpx = (val: number | string) => {
const scale = this.getScale();
if (typeof val == "number") {
return val * scale + "rpx";
} else {
const num = parseFloat(val);
const unit = val.replace(`${num}`, "");
return num * scale + unit;
}
};
/**
* 获取当前字号在预设中的索引
*/
getIndex = () => {
let index = this.names.findIndex((name) => {
if (this.className.value.includes(name)) {
return true;
}
return false;
});
// 默认使用 text-md (14px)
if (index < 0) {
index = 2;
}
return index;
};
/**
* 获取最终的字号大小
* @param size - 指定字号大小,为空则使用预设值
*/
getSize = (size: number | string | null): null | string => {
// 未设置全局字号时返回null
if (config.fontSize == null) {
return null;
}
return this.getRpx(size ?? px2rpx(this.sizes[this.getIndex()]));
};
/**
* 获取当前行高
*/
getLineHeight = (): null | string => {
// 未设置全局字号时返回null
if (config.fontSize == null) {
return null;
}
const lineHeight = this.lineHeights[this.getIndex()];
return lineHeight == 1 ? `1` : this.getRpx(px2rpx(lineHeight));
};
}
/**
* 字号管理Hook
* @param className - 类名
*/
export function useSize(cb: (() => string) | null = null): Size {
return new Size(cb);
}

View File

@@ -6,9 +6,9 @@ import Build from "android.os.Build";
/**
* 震动
* @param {number} duriation 震动时间单位ms
* @param {number} duration 震动时间单位ms
*/
export function vibrate(duriation: number) {
export function vibrate(duration: number) {
try {
const context = UTSAndroid.getAppContext() as Context;
let vb: Vibrator | null = null;
@@ -25,7 +25,7 @@ export function vibrate(duriation: number) {
// Android 8.0 (API 26) 及以上使用 VibrationEffect
if (Build.VERSION.SDK_INT >= 26) {
const effect = VibrationEffect.createOneShot(
duriation.toLong(),
duration.toLong(),
VibrationEffect.DEFAULT_AMPLITUDE
);
vb.vibrate(effect);

View File

@@ -1,8 +1,8 @@
/**
* 震动
* @param {number} duriation 震动时间单位ms,ios微信失效
* @param {number} duration 震动时间单位ms,ios微信失效
*/
export function vibrate(duriation: number) {
export function vibrate(duration: number) {
wx.vibrateShort({
type: "medium",
success() {},

View File

@@ -1,10 +1,10 @@
/**
* 震动
* @param {number} duriation 震动时间单位ms,ios微信失效
* @param {number} duration 震动时间单位ms,ios微信失效
*/
export function vibrate(duriation: number) {
export function vibrate(duration: number) {
try {
navigator.vibrate(duriation);
navigator.vibrate(duration);
} catch (error) {
console.error("WEB震动失败:", error);
}