添加 useForm 事件

This commit is contained in:
icssoa
2025-08-06 16:30:49 +08:00
parent ae566bf919
commit da573d0d35
12 changed files with 108 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "cool-unix", "name": "cool-unix",
"version": "8.0.5", "version": "8.0.6",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js", "build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js",

View File

@@ -2,11 +2,11 @@
<cl-page> <cl-page>
<view class="p-3"> <view class="p-3">
<demo-item :label="t('基础用法')"> <demo-item :label="t('基础用法')">
<cl-upload v-model="form.upload1"></cl-upload> <cl-upload v-model="form.upload1" test></cl-upload>
</demo-item> </demo-item>
<demo-item :label="t('禁用')"> <demo-item :label="t('禁用')">
<cl-upload v-model="form.upload1" disabled></cl-upload> <cl-upload v-model="form.upload1" disabled test></cl-upload>
</demo-item> </demo-item>
<demo-item :label="t('自定义图标、文字、大小')"> <demo-item :label="t('自定义图标、文字、大小')">
@@ -16,15 +16,16 @@
:text="t('上传证件照')" :text="t('上传证件照')"
:width="300" :width="300"
:height="200" :height="200"
test
></cl-upload> ></cl-upload>
</demo-item> </demo-item>
<demo-item :label="t('多选')"> <demo-item :label="t('多选')">
<cl-upload multiple v-model="form.upload2"></cl-upload> <cl-upload multiple v-model="form.upload2" test></cl-upload>
</demo-item> </demo-item>
<demo-item :label="t('限制 3 个')"> <demo-item :label="t('限制 3 个')">
<cl-upload multiple :limit="3" v-model="form.upload3"></cl-upload> <cl-upload multiple :limit="3" v-model="form.upload3" test></cl-upload>
</demo-item> </demo-item>
</view> </view>
</cl-page> </cl-page>

View File

@@ -25,7 +25,7 @@
import { t } from "@/locale"; import { t } from "@/locale";
import { ref } from "vue"; import { ref } from "vue";
const height = ref(0); const height = ref(200);
const width = ref(0); const width = ref(0);
const isFullscreen = ref(false); const isFullscreen = ref(false);
@@ -49,9 +49,6 @@ function onFullscreenChange() {
} }
onReady(() => { onReady(() => {
const { windowWidth } = uni.getWindowInfo(); width.value = uni.getWindowInfo().windowWidth;
height.value = 200;
width.value = windowWidth;
}); });
</script> </script>

View File

@@ -358,10 +358,6 @@ const visible = ref(false);
* 检查禁用状态,如果未禁用则显示弹窗 * 检查禁用状态,如果未禁用则显示弹窗
*/ */
function open() { function open() {
if (props.disabled) {
return;
}
visible.value = true; visible.value = true;
} }

View File

@@ -3,7 +3,7 @@
class="cl-checkbox" class="cl-checkbox"
:class="[ :class="[
{ {
'cl-checkbox--disabled': disabled, 'cl-checkbox--disabled': isDisabled,
'cl-checkbox--checked': isChecked 'cl-checkbox--checked': isChecked
}, },
pt.className pt.className
@@ -47,6 +47,7 @@ import { computed, useSlots, type PropType } from "vue";
import type { PassThroughProps } from "../../types"; import type { PassThroughProps } from "../../types";
import { get, parseClass, parsePt, pull } from "@/cool"; import { get, parseClass, parsePt, pull } from "@/cool";
import type { ClIconProps } from "../cl-icon/props"; import type { ClIconProps } from "../cl-icon/props";
import { useForm } from "../../hooks";
defineOptions({ defineOptions({
name: "cl-checkbox" name: "cl-checkbox"
@@ -99,6 +100,10 @@ const props = defineProps({
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);
const slots = useSlots(); const slots = useSlots();
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 透传样式类型定义 // 透传样式类型定义
type PassThrough = { type PassThrough = {
@@ -142,7 +147,7 @@ const iconName = computed(() => {
* 在非禁用状态下切换选中状态 * 在非禁用状态下切换选中状态
*/ */
function onTap() { function onTap() {
if (!props.disabled) { if (!isDisabled.value) {
let val = props.modelValue; let val = props.modelValue;
if (Array.isArray(val)) { if (Array.isArray(val)) {
@@ -166,7 +171,7 @@ function onTap() {
@apply flex flex-row items-center; @apply flex flex-row items-center;
&--disabled { &--disabled {
@apply opacity-70; @apply opacity-50;
} }
} }
</style> </style>

View File

@@ -3,7 +3,7 @@
class="cl-radio" class="cl-radio"
:class="[ :class="[
{ {
'cl-radio--disabled': disabled, 'cl-radio--disabled': isDisabled,
'cl-radio--checked': isChecked 'cl-radio--checked': isChecked
}, },
pt.className pt.className
@@ -47,6 +47,7 @@ import { computed, useSlots } from "vue";
import type { PassThroughProps } from "../../types"; import type { PassThroughProps } from "../../types";
import { get, parseClass, parsePt } from "@/cool"; import { get, parseClass, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props"; import type { ClIconProps } from "../cl-icon/props";
import { useForm } from "../../hooks";
defineOptions({ defineOptions({
name: "cl-radio" name: "cl-radio"
@@ -108,6 +109,12 @@ type PassThrough = {
// 解析透传样式配置 // 解析透传样式配置
const pt = computed(() => parsePt<PassThrough>(props.pt)); const pt = computed(() => parsePt<PassThrough>(props.pt));
// cl-form 上下文
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 是否为选中状态 // 是否为选中状态
const isChecked = computed(() => props.modelValue == props.value); const isChecked = computed(() => props.modelValue == props.value);
@@ -130,7 +137,7 @@ const iconName = computed(() => {
* 在非禁用状态下切换选中状态 * 在非禁用状态下切换选中状态
*/ */
function onTap() { function onTap() {
if (!props.disabled) { if (!isDisabled.value) {
emit("update:modelValue", props.value); emit("update:modelValue", props.value);
emit("change", props.value); emit("change", props.value);
} }
@@ -142,7 +149,7 @@ function onTap() {
@apply flex flex-row items-center; @apply flex flex-row items-center;
&--disabled { &--disabled {
@apply opacity-70; @apply opacity-50;
} }
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<view class="cl-rate" :class="[pt.className]"> <view class="cl-rate" :class="[{ 'cl-rate--disabled': isDisabled }, pt.className]">
<view <view
v-for="(item, index) in max" v-for="(item, index) in max"
:key="index" :key="index"
@@ -48,6 +48,7 @@ import { computed } from "vue";
import { parseClass, parsePt } from "@/cool"; import { parseClass, parsePt } from "@/cool";
import type { PassThroughProps } from "../../types"; import type { PassThroughProps } from "../../types";
import type { ClIconProps } from "../cl-icon/props"; import type { ClIconProps } from "../cl-icon/props";
import { useForm } from "../../hooks";
defineOptions({ defineOptions({
name: "cl-rate" name: "cl-rate"
@@ -126,6 +127,12 @@ type PassThrough = {
// 解析透传样式 // 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt)); const pt = computed(() => parsePt<PassThrough>(props.pt));
// cl-form 上下文
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 获取图标激活宽度 // 获取图标激活宽度
function getIconActiveWidth(item: number) { function getIconActiveWidth(item: number) {
// 如果评分值大于等于当前项,返回null表示完全填充 // 如果评分值大于等于当前项,返回null表示完全填充
@@ -144,7 +151,7 @@ function getIconActiveWidth(item: number) {
// 点击事件处理 // 点击事件处理
function onTap(index: number) { function onTap(index: number) {
if (props.disabled) { if (isDisabled.value) {
return; return;
} }
@@ -176,11 +183,14 @@ function onTap(index: number) {
.cl-rate { .cl-rate {
@apply flex flex-row items-center; @apply flex flex-row items-center;
&--disabled {
@apply opacity-50;
}
&__item { &__item {
@apply flex items-center justify-center relative duration-200; @apply flex items-center justify-center relative duration-200 overflow-hidden;
transition-property: color; transition-property: color;
margin-right: 6rpx; margin-right: 6rpx;
overflow: hidden;
} }
} }
</style> </style>

View File

@@ -51,7 +51,7 @@
@tap="setRange(0)" @tap="setRange(0)"
> >
<cl-text <cl-text
v-if="values[0] != ''" v-if="values.length > 0 && values[0] != ''"
:pt="{ :pt="{
className: 'text-center' className: 'text-center'
}" }"
@@ -78,7 +78,7 @@
@tap="setRange(1)" @tap="setRange(1)"
> >
<cl-text <cl-text
v-if="values[1] != ''" v-if="values.length > 1 && values[1] != ''"
:pt="{ :pt="{
className: 'text-center' className: 'text-center'
}" }"
@@ -393,10 +393,14 @@ const list = computed(() => {
).toArray(); ).toArray();
// 解析结束日期为年月日时分秒数组 // 解析结束日期为年月日时分秒数组
const [endYear, endMonth, endDate, endHour, endMinute, endSecond] = dayUts(props.end).toArray(); const [endYear, endMonth, endDate, endHour, endMinute, endSecond] = dayUts(props.end).toArray();
// 获取当前选中的年月日时分秒值
const [year, month, date, hour, minute] = value.value;
// 初始化年月日时分秒六个选项数组 // 初始化年月日时分秒六个选项数组
const arr = [[], [], [], [], [], []] as ClSelectOption[][]; const arr = [[], [], [], [], [], []] as ClSelectOption[][];
// 边界处理如果value为空返回空数组
if (isEmpty(value.value)) {
return arr;
}
// 获取当前选中的年月日时分秒值
const [year, month, date, hour, minute] = value.value;
// 判断是否为闰年 // 判断是否为闰年
const isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; const isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
// 根据月份和是否闰年获取当月天数 // 根据月份和是否闰年获取当月天数

View File

@@ -3,7 +3,7 @@
class="cl-slider" class="cl-slider"
:class="[ :class="[
{ {
'cl-slider--disabled': disabled 'cl-slider--disabled': isDisabled
}, },
pt.className pt.className
]" ]"
@@ -62,14 +62,16 @@
></view> ></view>
</view> </view>
<cl-text <slot name="value" :value="displayValue">
v-if="showValue" <cl-text
:pt="{ v-if="showValue"
className: parseClass(['text-center w-[100rpx]', pt.value?.className]) :pt="{
}" className: parseClass(['text-center w-[100rpx]', pt.value?.className])
> }"
{{ displayValue }} >
</cl-text> {{ displayValue }}
</cl-text>
</slot>
</view> </view>
</template> </template>
@@ -77,6 +79,7 @@
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch, type PropType } from "vue"; import { computed, getCurrentInstance, nextTick, onMounted, ref, watch, type PropType } from "vue";
import { parseClass, parsePt, rpx2px } from "@/cool"; import { parseClass, parsePt, rpx2px } from "@/cool";
import type { PassThroughProps } from "../../types"; import type { PassThroughProps } from "../../types";
import { useForm } from "../../hooks";
defineOptions({ defineOptions({
name: "cl-slider" name: "cl-slider"
@@ -157,6 +160,12 @@ type PassThrough = {
// 计算样式穿透对象 // 计算样式穿透对象
const pt = computed(() => parsePt<PassThrough>(props.pt)); const pt = computed(() => parsePt<PassThrough>(props.pt));
// cl-form 上下文
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 当前滑块的值,单值模式 // 当前滑块的值,单值模式
const value = ref<number>(props.modelValue); const value = ref<number>(props.modelValue);
@@ -356,7 +365,7 @@ function updateValue(newValue: number | number[]) {
// 触摸开始事件:获取轨道信息并初始化滑块位置 // 触摸开始事件:获取轨道信息并初始化滑块位置
async function onTouchStart(e: TouchEvent) { async function onTouchStart(e: TouchEvent) {
if (props.disabled) return; if (isDisabled.value) return;
// 先获取轨道的位置和尺寸信息,这是后续计算的基础 // 先获取轨道的位置和尺寸信息,这是后续计算的基础
await getTrackInfo(); await getTrackInfo();
@@ -381,7 +390,7 @@ async function onTouchStart(e: TouchEvent) {
// 触摸移动事件:实时更新滑块位置 // 触摸移动事件:实时更新滑块位置
function onTouchMove(e: TouchEvent) { function onTouchMove(e: TouchEvent) {
if (props.disabled) return; if (isDisabled.value) return;
const clientX = e.touches[0].clientX; const clientX = e.touches[0].clientX;
const calculatedValue = calculateValue(clientX); const calculatedValue = calculateValue(clientX);
@@ -399,7 +408,7 @@ function onTouchMove(e: TouchEvent) {
// 触摸结束事件完成拖动触发最终的change事件 // 触摸结束事件完成拖动触发最终的change事件
function onTouchEnd() { function onTouchEnd() {
if (props.disabled) return; if (isDisabled.value) return;
// 触发change事件表示用户完成了一次完整的拖动操作 // 触发change事件表示用户完成了一次完整的拖动操作
if (props.range) { if (props.range) {
@@ -500,8 +509,7 @@ onMounted(() => {
@apply flex flex-row items-center w-full overflow-visible; @apply flex flex-row items-center w-full overflow-visible;
&--disabled { &--disabled {
opacity: 0.6; @apply opacity-50;
pointer-events: none;
} }
&__inner { &__inner {
@@ -528,6 +536,8 @@ onMounted(() => {
transform: translateY(-50%); transform: translateY(-50%);
pointer-events: none; pointer-events: none;
z-index: 1; z-index: 1;
border-width: 4rpx;
box-shadow: 0 0 2rpx 2rpx rgba(100, 100, 100, 0.1);
&--min { &--min {
z-index: 2; z-index: 2;

View File

@@ -3,7 +3,7 @@
class="cl-switch" class="cl-switch"
:class="[ :class="[
{ {
'cl-switch--disabled': disabled, 'cl-switch--disabled': isDisabled,
'cl-switch--checked': isChecked 'cl-switch--checked': isChecked
}, },
@@ -53,6 +53,7 @@ import { computed, ref, watch } from "vue";
import { isDark, parseClass, parsePt } from "@/cool"; import { isDark, parseClass, parsePt } from "@/cool";
import type { PassThroughProps } from "../../types"; import type { PassThroughProps } from "../../types";
import { vibrate } from "@/uni_modules/cool-vibrate"; import { vibrate } from "@/uni_modules/cool-vibrate";
import { useForm } from "../../hooks";
defineOptions({ defineOptions({
name: "cl-switch" name: "cl-switch"
@@ -107,6 +108,12 @@ type PassThrough = {
// 解析透传样式配置 // 解析透传样式配置
const pt = computed(() => parsePt<PassThrough>(props.pt)); const pt = computed(() => parsePt<PassThrough>(props.pt));
// cl-form 上下文
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 绑定值 // 绑定值
const value = ref(props.modelValue); const value = ref(props.modelValue);
@@ -147,7 +154,7 @@ const rect = computed<Rect>(() => {
* 在非禁用且非加载状态下切换开关状态 * 在非禁用且非加载状态下切换开关状态
*/ */
function onTap() { function onTap() {
if (!props.disabled && !props.loading) { if (!isDisabled.value && !props.loading) {
// 切换开关状态 // 切换开关状态
const val = !value.value; const val = !value.value;
value.value = val; value.value = val;

View File

@@ -5,7 +5,8 @@
:class="[ :class="[
isDark ? 'text-surface-50' : 'text-surface-700', isDark ? 'text-surface-50' : 'text-surface-700',
{ {
'truncate w-full': ellipsis 'truncate w-full': ellipsis,
'cl-text--pre-wrap': preWrap
}, },
{ {
'!text-primary-500': color == 'primary', '!text-primary-500': color == 'primary',
@@ -35,7 +36,8 @@
:class="[ :class="[
isDark ? 'text-surface-50' : 'text-surface-700', isDark ? 'text-surface-50' : 'text-surface-700',
{ {
'truncate w-full': ellipsis 'truncate w-full': ellipsis,
'cl-text--pre-wrap': preWrap
}, },
{ {
'!text-primary-500': color == 'primary', '!text-primary-500': color == 'primary',
@@ -140,6 +142,11 @@ const props = defineProps({
decode: { decode: {
type: Boolean, type: Boolean,
default: false default: false
},
// 是否保留单词
preWrap: {
type: Boolean,
default: false
} }
}); });
@@ -268,5 +275,11 @@ const content = computed(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.cl-text { .cl-text {
@apply text-md; @apply text-md;
&--pre-wrap {
// #ifdef H5
white-space: pre-wrap;
// #endif
}
} }
</style> </style>

View File

@@ -145,6 +145,11 @@ const props = defineProps({
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
},
// 演示用,本地预览
test: {
type: Boolean,
default: false
} }
}); });
@@ -330,6 +335,13 @@ function choose(index: number) {
// 添加到列表并获取唯一ID // 添加到列表并获取唯一ID
const uid = append(file.path); const uid = append(file.path);
// 测试用,本地预览
if (props.test) {
update(uid, { url: file.path, progress: 100 });
emit("success", file.path, uid);
return;
}
// 开始上传文件 // 开始上传文件
uploadFile(file, { uploadFile(file, {
// 上传进度回调 // 上传进度回调