兼容 ios
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ npm-debug.log*
|
|||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.project
|
.project
|
||||||
.idea
|
.idea
|
||||||
|
.hbuilderx
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
@@ -6,18 +6,18 @@
|
|||||||
</demo-item>
|
</demo-item>
|
||||||
|
|
||||||
<demo-item :label="t('不同类型')">
|
<demo-item :label="t('不同类型')">
|
||||||
<view class="flex flex-row flex-wrap mb-2">
|
<view class="flex flex-row flex-wrap mb-2 overflow-visible">
|
||||||
<cl-button type="primary">{{ t("主要") }}</cl-button>
|
<cl-button type="primary">{{ t("主要") }}</cl-button>
|
||||||
<cl-button type="success">{{ t("成功") }}</cl-button>
|
<cl-button type="success">{{ t("成功") }}</cl-button>
|
||||||
<cl-button type="warn">{{ t("警告") }}</cl-button>
|
<cl-button type="warn">{{ t("警告") }}</cl-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="flex flex-row mb-2">
|
<view class="flex flex-row mb-2 overflow-visible">
|
||||||
<cl-button type="error">{{ t("危险") }}</cl-button>
|
<cl-button type="error">{{ t("危险") }}</cl-button>
|
||||||
<cl-button type="info">{{ t("信息") }}</cl-button>
|
<cl-button type="info">{{ t("信息") }}</cl-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="flex flex-row">
|
<view class="flex flex-row overflow-visible">
|
||||||
<cl-button type="light">{{ t("浅色") }}</cl-button>
|
<cl-button type="light">{{ t("浅色") }}</cl-button>
|
||||||
<cl-button type="dark">{{ t("深色") }}</cl-button>
|
<cl-button type="dark">{{ t("深色") }}</cl-button>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<cl-page>
|
<cl-page>
|
||||||
<view class="p-3 overflow-visible">
|
<view class="p-3">
|
||||||
|
<demo-item>
|
||||||
|
<cl-text color="info">
|
||||||
|
{{ t("长按项即可拖动排序") }}
|
||||||
|
</cl-text>
|
||||||
|
</demo-item>
|
||||||
|
|
||||||
<demo-item :label="t('单列排序')">
|
<demo-item :label="t('单列排序')">
|
||||||
<cl-draggable v-model="list">
|
<cl-draggable v-model="list">
|
||||||
<template #item="{ item, index }">
|
<template #item="{ item, index }">
|
||||||
@@ -16,6 +22,18 @@
|
|||||||
</cl-draggable>
|
</cl-draggable>
|
||||||
</demo-item>
|
</demo-item>
|
||||||
|
|
||||||
|
<demo-item :label="t('不需要长按')">
|
||||||
|
<cl-draggable v-model="list5" :long-press="false">
|
||||||
|
<template #item="{ item }">
|
||||||
|
<view
|
||||||
|
class="flex flex-row items-center p-3 bg-surface-100 rounded-lg mb-2 dark:!bg-surface-700"
|
||||||
|
>
|
||||||
|
<cl-text>{{ (item as UTSJSONObject).label }}</cl-text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</cl-draggable>
|
||||||
|
</demo-item>
|
||||||
|
|
||||||
<demo-item :label="t('结合列表使用')">
|
<demo-item :label="t('结合列表使用')">
|
||||||
<cl-list border>
|
<cl-list border>
|
||||||
<cl-draggable v-model="list2">
|
<cl-draggable v-model="list2">
|
||||||
@@ -26,8 +44,12 @@
|
|||||||
arrow
|
arrow
|
||||||
:pt="{
|
:pt="{
|
||||||
inner: {
|
inner: {
|
||||||
className:
|
className: parseClass([
|
||||||
dragging && dragIndex == index ? '!bg-surface-100' : ''
|
[
|
||||||
|
dragging && dragIndex == index,
|
||||||
|
isDark ? '!bg-surface-700' : '!bg-surface-100'
|
||||||
|
]
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
></cl-list-item>
|
></cl-list-item>
|
||||||
@@ -75,41 +97,57 @@
|
|||||||
import { t } from "@/locale";
|
import { t } from "@/locale";
|
||||||
import DemoItem from "../components/item.uvue";
|
import DemoItem from "../components/item.uvue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { isDark, parseClass } from "@/cool";
|
||||||
|
|
||||||
|
// list:李白《将进酒》
|
||||||
const list = ref<UTSJSONObject[]>([
|
const list = ref<UTSJSONObject[]>([
|
||||||
{
|
{
|
||||||
label: "明月几时有,把酒问青天"
|
label: "君不见黄河之水天上来"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "不知天上宫阙,今夕是何年",
|
label: "奔流到海不复回",
|
||||||
disabled: true
|
disabled: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "我欲乘风归去,又恐琼楼玉宇"
|
label: "君不见高堂明镜悲白发"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "高处不胜寒,起舞弄清影"
|
label: "朝如青丝暮成雪"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "何似在人间"
|
label: "人生得意须尽欢"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// list5:杜甫《春望》
|
||||||
|
const list5 = ref<UTSJSONObject[]>([
|
||||||
|
{
|
||||||
|
label: "国破山河在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "城春草木深"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "感时花溅泪"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// list2:王之涣《登鹳雀楼》
|
||||||
const list2 = ref<UTSJSONObject[]>([
|
const list2 = ref<UTSJSONObject[]>([
|
||||||
{
|
{
|
||||||
label: "明月几时有,把酒问青天"
|
label: "白日依山尽"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "不知天上宫阙,今夕是何年"
|
label: "黄河入海流"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "我欲乘风归去,又恐琼楼玉宇"
|
label: "欲穷千里目"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "高处不胜寒,起舞弄清影"
|
label: "更上一层楼"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "何似在人间"
|
label: "一览众山小"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,11 @@
|
|||||||
|
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<view class="py-3">
|
<view class="py-3">
|
||||||
<cl-loadmore :loading="loading" v-if="list.length > 0"></cl-loadmore>
|
<cl-loadmore
|
||||||
|
v-if="list.length > 0"
|
||||||
|
:loading="loading"
|
||||||
|
safe-area-bottom
|
||||||
|
></cl-loadmore>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</cl-list-view>
|
</cl-list-view>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</cl-waterfall>
|
</cl-waterfall>
|
||||||
|
|
||||||
<cl-loadmore :loading="true"></cl-loadmore>
|
<cl-loadmore :loading="true" safe-area-bottom></cl-loadmore>
|
||||||
</view>
|
</view>
|
||||||
</cl-page>
|
</cl-page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
20
types/uni-app.d.ts
vendored
20
types/uni-app.d.ts
vendored
@@ -446,6 +446,26 @@ declare interface UniElement {
|
|||||||
success?: (res: { tempFilePath: string }) => void;
|
success?: (res: { tempFilePath: string }) => void;
|
||||||
fail?: (err: { errCode: number; errMsg: string }) => void;
|
fail?: (err: { errCode: number; errMsg: string }) => void;
|
||||||
}): void;
|
}): void;
|
||||||
|
getDrawableContext(): DrawableContext;
|
||||||
|
animate(
|
||||||
|
keyframes: UniAnimationKeyframe | UniAnimationKeyframe[],
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
delay?: number;
|
||||||
|
direction?: "normal" | "reverse" | "alternate" | "alternate-reverse";
|
||||||
|
duration?: number;
|
||||||
|
easing?:
|
||||||
|
| "ease"
|
||||||
|
| "ease-in"
|
||||||
|
| "ease-out"
|
||||||
|
| "ease-in-out"
|
||||||
|
| "linear"
|
||||||
|
| "cubic-bezier";
|
||||||
|
fill?: "backwards" | "forwards" | "both" | "none";
|
||||||
|
iterations?: number;
|
||||||
|
}
|
||||||
|
| number
|
||||||
|
): { id: string; playState: "running" | "paused" | "finished" | "idle" } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface CanvasContext extends HTMLCanvasElement {
|
declare interface CanvasContext extends HTMLCanvasElement {
|
||||||
|
|||||||
5
uni_modules/cool-svg/utssdk/app-harmony/config.json
Normal file
5
uni_modules/cool-svg/utssdk/app-harmony/config.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@ohos/svg": "2.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
uni_modules/cool-svg/utssdk/app-harmony/index.uts
Normal file
5
uni_modules/cool-svg/utssdk/app-harmony/index.uts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { BuilderNode } from "@kit.ArkUI";
|
||||||
|
|
||||||
|
export class CoolSvg {
|
||||||
|
load(src: string, color: string) {}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
>
|
>
|
||||||
<!-- 图片容器 - 可拖拽和缩放的图片区域 -->
|
<!-- 图片容器 - 可拖拽和缩放的图片区域 -->
|
||||||
<view class="cl-cropper__image">
|
<view class="cl-cropper__image">
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<image
|
<image
|
||||||
class="cl-cropper__image-inner"
|
class="cl-cropper__image-inner"
|
||||||
:class="[
|
:class="[
|
||||||
|
|||||||
@@ -3,28 +3,35 @@
|
|||||||
class="cl-draggable"
|
class="cl-draggable"
|
||||||
:class="[
|
:class="[
|
||||||
{
|
{
|
||||||
'cl-draggable--grid': props.columns > 1
|
'cl-draggable--columns': props.columns > 1
|
||||||
},
|
},
|
||||||
pt.className
|
pt.className
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<view
|
<view
|
||||||
v-for="(item, index) in list"
|
v-for="(item, index) in list"
|
||||||
:key="getItemKey(item, index)"
|
:key="getItemKey(item, index)"
|
||||||
class="cl-draggable__item"
|
class="cl-draggable__item"
|
||||||
:class="[
|
:class="[
|
||||||
{
|
{
|
||||||
'cl-draggable__item--disabled': disabled
|
'cl-draggable__item--disabled': disabled,
|
||||||
},
|
'cl-draggable__item--dragging': dragging && dragIndex == index,
|
||||||
dragging && dragIndex == index ? `opacity-80 ${pt.ghost?.className}` : ''
|
'cl-draggable__item--animating': dragging && dragIndex != index
|
||||||
|
}
|
||||||
]"
|
]"
|
||||||
:style="getItemStyle(index)"
|
:style="getItemStyle(index)"
|
||||||
@touchstart="
|
@touchstart="
|
||||||
(event: UniTouchEvent) => {
|
(event: UniTouchEvent) => {
|
||||||
onTouchStart(event, index);
|
onTouchStart(event, index, 'touch');
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@touchmove.stop.prevent="onTouchMove"
|
@longpress="
|
||||||
|
(event: UniTouchEvent) => {
|
||||||
|
onTouchStart(event, index, 'longpress');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@touchmove="onTouchMove"
|
||||||
@touchend="onTouchEnd"
|
@touchend="onTouchEnd"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
@@ -44,6 +51,7 @@
|
|||||||
import { computed, ref, getCurrentInstance, type PropType, watch } from "vue";
|
import { computed, ref, getCurrentInstance, type PropType, watch } from "vue";
|
||||||
import { isNull, parsePt, uuid } from "@/cool";
|
import { isNull, parsePt, uuid } from "@/cool";
|
||||||
import type { PassThroughProps } from "../../types";
|
import type { PassThroughProps } from "../../types";
|
||||||
|
import { vibrate } from "@/uni_modules/cool-vibrate";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "cl-draggable"
|
name: "cl-draggable"
|
||||||
@@ -89,15 +97,15 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
/** 动画持续时间(毫秒) */
|
|
||||||
animation: {
|
|
||||||
type: Number,
|
|
||||||
default: 150
|
|
||||||
},
|
|
||||||
/** 列数:1为单列纵向布局,>1为多列网格布局 */
|
/** 列数:1为单列纵向布局,>1为多列网格布局 */
|
||||||
columns: {
|
columns: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
|
},
|
||||||
|
// 是否需要长按触发
|
||||||
|
longPress: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -376,12 +384,6 @@ function getItemStyle(index: number) {
|
|||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为非拖拽元素添加过渡动画
|
|
||||||
if (props.animation > 0 && !isCurrent) {
|
|
||||||
style["transition-property"] = "transform";
|
|
||||||
style["transition-duration"] = `${props.animation}ms`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拖拽状态下的样式处理
|
// 拖拽状态下的样式处理
|
||||||
if (dragging.value) {
|
if (dragging.value) {
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
@@ -391,11 +393,9 @@ function getItemStyle(index: number) {
|
|||||||
} else {
|
} else {
|
||||||
// 其他元素:显示排序预览位移
|
// 其他元素:显示排序预览位移
|
||||||
const translateOffset = getItemTranslateOffset(index);
|
const translateOffset = getItemTranslateOffset(index);
|
||||||
if (translateOffset.x != 0 || translateOffset.y != 0) {
|
|
||||||
style["transform"] = `translate(${translateOffset.x}px, ${translateOffset.y}px)`;
|
style["transform"] = `translate(${translateOffset.x}px, ${translateOffset.y}px)`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
@@ -513,22 +513,31 @@ function checkMovedToOtherElement(): boolean {
|
|||||||
* @param event 触摸事件对象
|
* @param event 触摸事件对象
|
||||||
* @param index 触摸的项目索引
|
* @param index 触摸的项目索引
|
||||||
*/
|
*/
|
||||||
async function onTouchStart(event: UniTouchEvent, index: number): Promise<void> {
|
async function onTouchStart(event: UniTouchEvent, index: number, type: string) {
|
||||||
|
// 如果是长按触发,但未开启长按功能,则直接返回
|
||||||
|
if (type == "longpress" && !props.longPress) return;
|
||||||
|
// 如果是普通触摸触发,但已开启长按功能,则直接返回
|
||||||
|
if (type == "touch" && props.longPress) return;
|
||||||
|
|
||||||
// 检查是否禁用或索引无效
|
// 检查是否禁用或索引无效
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
if (getItemDisabled(index)) return;
|
if (getItemDisabled(index)) return;
|
||||||
if (index < 0 || index >= list.value.length) return;
|
if (index < 0 || index >= list.value.length) return;
|
||||||
|
|
||||||
|
// 获取触摸点
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
|
|
||||||
// 初始化拖拽状态
|
// 初始化拖拽状态
|
||||||
dragging.value = true;
|
dragging.value = true;
|
||||||
|
|
||||||
|
// 初始化拖拽索引
|
||||||
dragIndex.value = index;
|
dragIndex.value = index;
|
||||||
insertIndex.value = index; // 初始插入位置为原位置
|
insertIndex.value = index; // 初始插入位置为原位置
|
||||||
startX.value = touch.clientX;
|
startX.value = touch.clientX;
|
||||||
startY.value = touch.clientY;
|
startY.value = touch.clientY;
|
||||||
offsetX.value = 0;
|
offsetX.value = 0;
|
||||||
offsetY.value = 0;
|
offsetY.value = 0;
|
||||||
|
// 初始化拖拽数据项
|
||||||
dragItem.value = list.value[index];
|
dragItem.value = list.value[index];
|
||||||
|
|
||||||
// 先获取所有项目的位置信息,为后续计算做准备
|
// 先获取所有项目的位置信息,为后续计算做准备
|
||||||
@@ -536,6 +545,14 @@ async function onTouchStart(event: UniTouchEvent, index: number): Promise<void>
|
|||||||
|
|
||||||
// 触发开始事件
|
// 触发开始事件
|
||||||
emit("start", index);
|
emit("start", index);
|
||||||
|
|
||||||
|
// 震动
|
||||||
|
vibrate(1);
|
||||||
|
|
||||||
|
// 阻止事件冒泡
|
||||||
|
event.stopPropagation();
|
||||||
|
// 阻止默认行为
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -575,7 +592,7 @@ function onTouchMove(event: TouchEvent): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻止默认行为和事件冒泡
|
// 阻止默认行为
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +602,10 @@ function onTouchMove(event: TouchEvent): void {
|
|||||||
function onTouchEnd(): void {
|
function onTouchEnd(): void {
|
||||||
if (!dragging.value) return;
|
if (!dragging.value) return;
|
||||||
|
|
||||||
|
// 旧索引
|
||||||
const oldIndex = dragIndex.value;
|
const oldIndex = dragIndex.value;
|
||||||
|
|
||||||
|
// 新索引
|
||||||
const newIndex = insertIndex.value;
|
const newIndex = insertIndex.value;
|
||||||
|
|
||||||
// 如果位置发生变化,立即更新数组
|
// 如果位置发生变化,立即更新数组
|
||||||
@@ -604,15 +624,11 @@ function onTouchEnd(): void {
|
|||||||
dropping.value = true;
|
dropping.value = true;
|
||||||
dragging.value = false;
|
dragging.value = false;
|
||||||
|
|
||||||
// 让拖拽元素回到自然位置(偏移归零)
|
// 重置所有状态
|
||||||
offsetX.value = 0;
|
reset();
|
||||||
offsetY.value = 0;
|
|
||||||
|
|
||||||
// 等待放下动画完成后重置所有状态
|
// 等待放下动画完成后重置所有状态
|
||||||
setTimeout(() => {
|
|
||||||
emit("end", newIndex >= 0 ? newIndex : oldIndex);
|
emit("end", newIndex >= 0 ? newIndex : oldIndex);
|
||||||
reset();
|
|
||||||
}, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -652,21 +668,30 @@ watch(
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cl-draggable {
|
.cl-draggable {
|
||||||
@apply flex-col relative overflow-visible;
|
@apply flex-col relative overflow-visible;
|
||||||
}
|
|
||||||
|
|
||||||
.cl-draggable--grid {
|
&--columns {
|
||||||
@apply flex-row flex-wrap;
|
@apply flex-row flex-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cl-draggable__item {
|
&__item {
|
||||||
@apply relative;
|
@apply relative z-10;
|
||||||
}
|
|
||||||
|
|
||||||
.cl-draggable__item--dragging {
|
// #ifdef APP-IOS
|
||||||
@apply opacity-80;
|
@apply transition-none opacity-100;
|
||||||
}
|
// #endif
|
||||||
|
|
||||||
.cl-draggable__item--disabled {
|
&--dragging {
|
||||||
|
@apply opacity-80 z-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
@apply opacity-60;
|
@apply opacity-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--animating {
|
||||||
|
@apply duration-200;
|
||||||
|
transition-property: transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,8 +22,6 @@
|
|||||||
:hold-keyboard="false"
|
:hold-keyboard="false"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@focus="animateCursor(true)"
|
|
||||||
@blur="animateCursor(true)"
|
|
||||||
></cl-input>
|
></cl-input>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -36,12 +34,13 @@
|
|||||||
{
|
{
|
||||||
'is-disabled': disabled,
|
'is-disabled': disabled,
|
||||||
'is-dark': isDark,
|
'is-dark': isDark,
|
||||||
'is-active': value.length == index && isFocus
|
'is-active': value.length >= index && isFocus
|
||||||
},
|
},
|
||||||
pt.item?.className
|
pt.item?.className
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<cl-text
|
<cl-text
|
||||||
|
:color="value.length >= index && isFocus ? 'primary' : ''"
|
||||||
:pt="{
|
:pt="{
|
||||||
className: pt.value?.className
|
className: pt.value?.className
|
||||||
}"
|
}"
|
||||||
@@ -50,9 +49,6 @@
|
|||||||
<view
|
<view
|
||||||
class="cl-input-otp__cursor"
|
class="cl-input-otp__cursor"
|
||||||
:class="[pt.cursor?.className]"
|
:class="[pt.cursor?.className]"
|
||||||
:style="{
|
|
||||||
opacity: cursorOpacity
|
|
||||||
}"
|
|
||||||
v-if="value.length == index && isFocus && item == ''"
|
v-if="value.length == index && isFocus && item == ''"
|
||||||
></view>
|
></view>
|
||||||
</view>
|
</view>
|
||||||
@@ -184,51 +180,6 @@ function onChange(val: string) {
|
|||||||
emit("done", val);
|
emit("done", val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 光标闪烁透明度值
|
|
||||||
* 范围: 0.3-1.0
|
|
||||||
*/
|
|
||||||
const cursorOpacity = ref(0.3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 光标闪烁动画帧ID
|
|
||||||
*/
|
|
||||||
let cursorAnimationId = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 控制光标闪烁动画
|
|
||||||
* @param isIncreasing 透明度是否递增
|
|
||||||
*/
|
|
||||||
function animateCursor(isIncreasing: boolean) {
|
|
||||||
// #ifdef APP
|
|
||||||
// 未获得焦点时不执行动画
|
|
||||||
if (!isFocus.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消上一次动画
|
|
||||||
if (cursorAnimationId != 0) {
|
|
||||||
cancelAnimationFrame(cursorAnimationId);
|
|
||||||
cursorAnimationId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行动画帧
|
|
||||||
cursorAnimationId = requestAnimationFrame(() => {
|
|
||||||
// 根据方向调整透明度值
|
|
||||||
cursorOpacity.value += isIncreasing ? 0.01 : -0.01;
|
|
||||||
|
|
||||||
// 到达边界值时改变方向
|
|
||||||
if (cursorOpacity.value > 1) {
|
|
||||||
animateCursor(false);
|
|
||||||
} else if (cursorOpacity.value <= 0.3) {
|
|
||||||
animateCursor(true);
|
|
||||||
} else {
|
|
||||||
animateCursor(isIncreasing);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -248,7 +199,7 @@ function animateCursor(isIncreasing: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
@apply flex flex-row items-center justify-center;
|
@apply flex flex-row items-center justify-center duration-100;
|
||||||
@apply border border-solid border-surface-200 rounded-lg bg-white;
|
@apply border border-solid border-surface-200 rounded-lg bg-white;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
width: 80rpx;
|
width: 80rpx;
|
||||||
@@ -272,10 +223,10 @@ function animateCursor(isIncreasing: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__cursor {
|
&__cursor {
|
||||||
@apply absolute;
|
@apply absolute duration-100;
|
||||||
@apply bg-primary-500;
|
@apply bg-primary-500;
|
||||||
width: 2rpx;
|
width: 2rpx;
|
||||||
height: 36rpx;
|
height: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// #ifdef H5 || MP
|
// #ifdef H5 || MP
|
||||||
@@ -285,7 +236,7 @@ function animateCursor(isIncreasing: boolean) {
|
|||||||
|
|
||||||
@keyframes flash {
|
@keyframes flash {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.3;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
@@ -293,7 +244,7 @@ function animateCursor(isIncreasing: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.3;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
|
ref="loadingRef"
|
||||||
class="cl-loading"
|
class="cl-loading"
|
||||||
:class="[
|
:class="[
|
||||||
{
|
{
|
||||||
'cl-loading--dark': isDark && color == '',
|
'cl-loading--dark': isDark && color == '',
|
||||||
'cl-loading--spin': loading,
|
'cl-loading--spin': loading,
|
||||||
'!border-primary-500': color == 'primary',
|
|
||||||
'!border-green-500': color == 'success',
|
|
||||||
'!border-yellow-500': color == 'warn',
|
|
||||||
'!border-red-500': color == 'error',
|
|
||||||
'!border-surface-500': color == 'info',
|
|
||||||
'!border-surface-700': color == 'dark',
|
|
||||||
'!border-white': color == 'light',
|
|
||||||
'!border-surface-300': color == 'disabled',
|
|
||||||
'!border-r-transparent': true
|
'!border-r-transparent': true
|
||||||
},
|
},
|
||||||
pt.className
|
pt.className
|
||||||
]"
|
]"
|
||||||
:style="{
|
:style="{
|
||||||
// #ifdef APP
|
height: getPx(size!),
|
||||||
transform: `rotate(${rotate}deg)`,
|
width: getPx(size!),
|
||||||
// #endif
|
// #ifndef APP
|
||||||
height: getRpx(size!),
|
borderWidth: '1px',
|
||||||
width: getRpx(size!),
|
|
||||||
borderWidth: getRpx(2),
|
|
||||||
borderTopColor: color,
|
borderTopColor: color,
|
||||||
borderRightColor: 'transparent',
|
borderRightColor: 'transparent',
|
||||||
borderBottomColor: color,
|
borderBottomColor: color,
|
||||||
borderLeftColor: color
|
borderLeftColor: color
|
||||||
|
// #endif
|
||||||
}"
|
}"
|
||||||
v-if="loading"
|
v-if="loading"
|
||||||
>
|
>
|
||||||
@@ -35,8 +27,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from "vue";
|
import { computed, nextTick, onMounted, ref, shallowRef, watch } from "vue";
|
||||||
import { isDark, parsePt } from "@/cool";
|
import { ctx, isDark, parsePt } from "@/cool";
|
||||||
import type { ClIconProps } from "../cl-icon/props";
|
import type { ClIconProps } from "../cl-icon/props";
|
||||||
import { useSize } from "../../hooks";
|
import { useSize } from "../../hooks";
|
||||||
|
|
||||||
@@ -68,7 +60,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getRpx } = useSize();
|
const { getPxValue, getPx } = useSize();
|
||||||
|
|
||||||
// 透传样式类型定义
|
// 透传样式类型定义
|
||||||
type PassThrough = {
|
type PassThrough = {
|
||||||
@@ -79,25 +71,113 @@ type PassThrough = {
|
|||||||
// 解析透传样式
|
// 解析透传样式
|
||||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||||
|
|
||||||
// 旋转角度
|
// 组件引用
|
||||||
const rotate = ref(0);
|
const loadingRef = shallowRef<UniElement | null>(null);
|
||||||
|
|
||||||
|
const color = computed<string>(() => {
|
||||||
|
if (props.color == "") {
|
||||||
|
return isDark.value ? "#ffffff" : (ctx.color["surface-700"] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "#71717a";
|
||||||
|
case "dark":
|
||||||
|
return "#3f3f46";
|
||||||
|
case "light":
|
||||||
|
return "#ffffff";
|
||||||
|
case "disabled":
|
||||||
|
return "#d4d4d8";
|
||||||
|
default:
|
||||||
|
return props.color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function drawLoading() {
|
||||||
|
// #ifdef APP
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
if (loadingRef.value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawContext = loadingRef.value!.getDrawableContext();
|
||||||
|
|
||||||
|
// 重置画布,准备绘制新的loading图形
|
||||||
|
drawContext!.reset();
|
||||||
|
drawContext!.beginPath();
|
||||||
|
|
||||||
|
// 获取loading图标的尺寸和半径
|
||||||
|
const size = getPxValue(props.size!);
|
||||||
|
const radius = size / 2;
|
||||||
|
const centerX = radius;
|
||||||
|
const centerY = radius;
|
||||||
|
|
||||||
|
// 设置线宽
|
||||||
|
const lineWidth = 1;
|
||||||
|
|
||||||
|
// 缺口角度为60度(Math.PI / 3),用于形成loading的缺口效果
|
||||||
|
const gapAngle = Math.PI / 3; // 缺口60度
|
||||||
|
|
||||||
|
// 起始角度为顶部(-90度)
|
||||||
|
const startAngle = -Math.PI / 2; // 从顶部开始
|
||||||
|
|
||||||
|
// 结束角度为起始角度加上300度(360-60),形成环形缺口
|
||||||
|
const endAngle = startAngle + (2 * Math.PI - gapAngle); // 画300度
|
||||||
|
|
||||||
|
// 绘制圆弧,形成loading环
|
||||||
|
drawContext!.arc(centerX, centerY, radius - lineWidth, startAngle, endAngle, false);
|
||||||
|
|
||||||
|
// 设置描边颜色和线宽
|
||||||
|
drawContext!.strokeStyle = color.value;
|
||||||
|
drawContext!.lineWidth = lineWidth;
|
||||||
|
|
||||||
|
// 执行描边操作
|
||||||
|
drawContext!.stroke();
|
||||||
|
|
||||||
|
// 更新画布显示
|
||||||
|
drawContext!.update();
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
// 开始旋转动画
|
// 开始旋转动画
|
||||||
function start() {
|
async function start() {
|
||||||
requestAnimationFrame(() => {
|
// #ifdef APP
|
||||||
// 增加旋转角度
|
await drawLoading();
|
||||||
rotate.value += 1;
|
|
||||||
|
|
||||||
// 如果仍在加载中则继续旋转
|
if (loadingRef.value == null) {
|
||||||
if (props.loading) {
|
return;
|
||||||
start();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
loadingRef.value!.animate(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
transform: "rotate(0deg)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: "rotate(360deg)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
duration: 2500,
|
||||||
|
easing: "linear",
|
||||||
|
iterations: 999999
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件挂载后监听loading状态
|
// 组件挂载后监听loading状态
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// #ifdef APP-UVUE
|
// #ifdef APP
|
||||||
watch(
|
watch(
|
||||||
computed(() => props.loading),
|
computed(() => props.loading),
|
||||||
(val: boolean) => {
|
(val: boolean) => {
|
||||||
@@ -110,6 +190,13 @@ onMounted(() => {
|
|||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
computed(() => [props.color, props.size, isDark.value]),
|
||||||
|
() => {
|
||||||
|
drawLoading();
|
||||||
|
}
|
||||||
|
);
|
||||||
// #endif
|
// #endif
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -117,7 +204,10 @@ onMounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cl-loading {
|
.cl-loading {
|
||||||
@apply flex flex-row items-center justify-center rounded-full;
|
@apply flex flex-row items-center justify-center rounded-full;
|
||||||
|
|
||||||
|
// #ifndef APP
|
||||||
@apply border-surface-700 border-solid;
|
@apply border-surface-700 border-solid;
|
||||||
|
// #endif
|
||||||
|
|
||||||
&--dark {
|
&--dark {
|
||||||
border-color: white !important;
|
border-color: white !important;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<view class="cl-loadmore-wrapper">
|
<view class="cl-loadmore-wrapper">
|
||||||
<view class="cl-loadmore">
|
<view class="cl-loadmore">
|
||||||
<cl-loading
|
<cl-loading
|
||||||
:size="30"
|
:size="28"
|
||||||
:pt="{
|
:pt="{
|
||||||
className: `mr-2 ${pt.icon?.className}`
|
className: `mr-2 ${pt.icon?.className}`
|
||||||
}"
|
}"
|
||||||
@@ -59,7 +59,7 @@ const props = defineProps({
|
|||||||
// 是否显示底部安全区
|
// 是否显示底部安全区
|
||||||
safeAreaBottom: {
|
safeAreaBottom: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ function onTouchEnd() {
|
|||||||
angle: currentAngle.value
|
angle: currentAngle.value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
vibrate(2); // 震动反馈
|
vibrate(1); // 震动反馈
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听模式变化,重新初始化
|
// 监听模式变化,重新初始化
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { computed, type ComputedRef } from "vue";
|
import { computed, type ComputedRef } from "vue";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
import { rpx2px } from "@/cool";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字号管理类
|
* 字号管理类
|
||||||
@@ -73,6 +74,35 @@ class Size {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取px值
|
||||||
|
* @param val - 需要转换的值 10、10rpx、10px
|
||||||
|
* @returns 转换后的px值
|
||||||
|
*/
|
||||||
|
getPxValue = (val: number | string) => {
|
||||||
|
const scale = this.getScale();
|
||||||
|
|
||||||
|
if (typeof val == "string") {
|
||||||
|
const num = parseFloat(val);
|
||||||
|
const unit = val.replace(`${num}`, "");
|
||||||
|
|
||||||
|
if (unit == "px") {
|
||||||
|
return num * scale;
|
||||||
|
} else {
|
||||||
|
return rpx2px(num * scale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return rpx2px(val * scale);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取px值
|
||||||
|
*/
|
||||||
|
getPx = (val: number | string) => {
|
||||||
|
return this.getPxValue(val) + "px";
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前字号在预设中的索引
|
* 获取当前字号在预设中的索引
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user