Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-keyboard-number/cl-keyboard-number.uvue
2025-09-03 19:03:39 +08:00

432 lines
8.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<cl-popup
:title="title"
:swipe-close-threshold="100"
:pt="{
inner: {
className: parseClass([
[isDark, '!bg-surface-700', '!bg-surface-100'],
pt.popup?.className
])
},
mask: {
className: '!bg-transparent'
}
}"
v-model="visible"
>
<view class="cl-keyboard-number" :class="[pt.className]">
<slot name="value" :value="value">
<view
v-if="showValue"
class="cl-keyboard-number__value"
:class="[pt.value?.className]"
>
<cl-text
v-if="value != ''"
:pt="{
className: '!text-2xl'
}"
>{{ value }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
</view>
</slot>
<view class="cl-keyboard-number__list">
<cl-row :gutter="10">
<cl-col :span="18">
<cl-row :gutter="10">
<cl-col :span="8" v-for="item in list" :key="item">
<view
class="cl-keyboard-number__item"
:class="[
`is-keycode-${item}`,
{
'is-dark': isDark,
'is-empty': item == ''
},
pt.item?.className
]"
hover-class="opacity-50"
:hover-stay-time="250"
@touchstart.stop="onCommand(item)"
>
<slot name="item" :item="item">
<cl-icon
v-if="item == 'delete'"
name="delete-back-2-line"
:size="36"
></cl-icon>
<view
v-else-if="item == 'confirm'"
class="cl-keyboard-number__item-confirm"
>
<cl-text
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
</view>
<view v-else-if="item == '_confirm'"></view>
<cl-text
v-else
:pt="{
className: '!text-lg'
}"
>{{ item }}</cl-text
>
</slot>
</view>
</cl-col>
</cl-row>
</cl-col>
<cl-col :span="6">
<view class="cl-keyboard-number__op">
<view
v-for="item in opList"
:key="item"
class="cl-keyboard-number__item"
:class="[
{
'is-dark': isDark
},
`is-keycode-${item}`
]"
hover-class="opacity-50"
:hover-stay-time="250"
@touchstart.stop="onCommand(item)"
>
<cl-icon
v-if="item == 'delete'"
name="delete-back-2-line"
:size="36"
></cl-icon>
<cl-text
v-if="item == 'confirm'"
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
</view>
</view>
</cl-col>
</cl-row>
</view>
</view>
</cl-popup>
</template>
<script setup lang="ts">
import { useUi } from "../../hooks";
import type { PassThroughProps } from "../../types";
import type { ClPopupProps } from "../cl-popup/props";
import { ref, computed, watch, type PropType } from "vue";
import { $t, t } from "@/locale";
import { isAppIOS, isDark, parseClass, parsePt } from "@/cool";
import { vibrate } from "@/uni_modules/cool-vibrate";
defineOptions({
name: "cl-keyboard-number"
});
defineSlots<{
value(props: { value: string }): any;
item(props: { item: string }): any;
}>();
const props = defineProps({
// 透传样式配置
pt: {
type: Object,
default: () => ({})
},
// v-model绑定的值
modelValue: {
type: String,
default: ""
},
// 键盘类型支持number、digit、idcard
type: {
type: String as PropType<"number" | "digit" | "idcard">,
default: "digit"
},
// 弹窗标题
title: {
type: String,
default: () => t("数字键盘")
},
// 输入框占位符
placeholder: {
type: String,
default: () => t("安全键盘,请放心输入")
},
// 最大输入长度
maxlength: {
type: Number,
default: 10
},
// 确认按钮文本
confirmText: {
type: String,
default: () => t("确定")
},
// 是否显示输入值
showValue: {
type: Boolean,
default: true
},
// 是否输入即绑定
inputImmediate: {
type: Boolean,
default: false
}
});
// 定义事件发射器支持v-model和change事件
const emit = defineEmits(["update:modelValue", "change"]);
// 样式穿透类型
type PassThrough = {
className?: string;
item?: PassThroughProps;
value?: PassThroughProps;
popup?: ClPopupProps;
};
// 样式穿透计算
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 获取UI相关的工具方法
const ui = useUi();
// 控制弹窗显示/隐藏
const visible = ref(false);
// 输入框当前值,双向绑定
const value = ref(props.modelValue);
// 最大输入长度
const maxlength = computed(() => {
if (props.type == "idcard") {
return 18;
}
return props.maxlength;
});
// 数字键盘的按键列表包含数字、删除、00和小数点
const list = computed(() => {
const arr = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "00", "0", ""];
// 数字键盘显示为小数点 "."
if (props.type == "digit") {
arr[11] = ".";
}
// 身份证键盘显示为 "X"
if (props.type == "idcard") {
arr[11] = "X";
}
return arr;
});
// 操作按钮列表
const opList = computed(() => {
return ["delete", "confirm"];
});
// 打开键盘弹窗
function open() {
visible.value = true;
}
// 关闭键盘弹窗
function close() {
visible.value = false;
}
// 处理键盘按键点击事件
function onCommand(key: string) {
// 震动
try {
vibrate(1);
} catch (error) {}
// 确认按钮逻辑
if (key == "confirm" || key == "_confirm") {
if (value.value == "") {
ui.showToast({
message: t("请输入内容")
});
return;
}
// 如果最后一位是小数点,去掉
if (value.value.endsWith(".")) {
value.value = value.value.slice(0, -1);
}
// 身份证号码正则校验支持15位和18位18位末尾可为X/x
if (props.type == "idcard") {
if (
!/^(^[1-9]\d{5}(18|19|20)?\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}(\d|X|x)?$)$/.test(
value.value
)
) {
ui.showToast({
message: t("身份证号码格式不正确")
});
return;
}
}
// 触发v-model和change事件
emit("update:modelValue", value.value);
emit("change", value.value);
// 关闭弹窗
close();
return;
}
// 删除键,去掉最后一位
if (key == "delete") {
value.value = value.value.slice(0, -1);
return;
}
// 超过最大输入长度,提示并返回
if (value.value.length >= maxlength.value) {
ui.showToast({
message: $t("最多输入{maxlength}位", {
maxlength: maxlength.value
})
});
return;
}
// 处理小数点输入,已存在则不再添加
if (key == ".") {
if (value.value.includes(".")) {
return;
}
if (value.value == "") {
value.value = "0.";
return;
}
}
// 处理00键首位不能输入00只能输入0
if (key == "00") {
if (value.value.length + 2 > maxlength.value) {
value.value += "0";
return;
}
if (value.value == "") {
value.value = "0";
return;
}
}
if (key == "00" || key == "0") {
if (value.value == "" || value.value == "0") {
value.value = "0";
return;
}
}
// 其他按键直接拼接到value
value.value += key;
}
watch(value, (val: string) => {
// 如果输入即绑定,则立即更新绑定值
if (props.inputImmediate) {
emit("update:modelValue", val);
emit("change", val);
}
});
// 监听外部v-model的变化保持内部value同步
watch(
computed(() => props.modelValue),
(val: string) => {
value.value = val;
}
);
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.cl-keyboard-number {
padding: 0 20rpx 20rpx 20rpx;
&__value {
@apply flex flex-row items-center justify-center;
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@apply relative overflow-visible;
}
&__op {
@apply flex flex-col h-full;
}
&__item {
@apply flex items-center justify-center rounded-xl bg-white overflow-visible;
height: 100rpx;
margin-top: 10rpx;
&.is-dark {
@apply bg-surface-800;
}
&.is-keycode-delete {
@apply bg-surface-200;
&.is-dark {
@apply bg-surface-800;
}
}
&.is-keycode-confirm {
@apply flex flex-col items-center justify-center;
@apply bg-primary-500 rounded-xl flex-1;
}
&.is-empty {
background-color: transparent !important;
}
}
}
</style>