chore: code format

This commit is contained in:
xiaojunnuo
2025-06-29 14:09:09 +08:00
parent 04422a4637
commit 4fcfd089d8
644 changed files with 10845 additions and 13184 deletions
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<Props>(), {
afterFetch: undefined,
modelPropName: "modelValue",
api: undefined,
options: () => []
options: () => [],
});
const emit = defineEmits<{
@@ -96,13 +96,13 @@ const getOptions = computed(() => {
const refOptionsData = unref(refOptions);
function transformData(data: OptionsItem[]): OptionsItem[] {
return data.map((item) => {
return data.map(item => {
const value = get(item, valueField);
return {
...objectOmit(item, [labelField, valueField, childrenField]),
label: get(item, labelField),
value: numberToString ? `${value}` : value,
...(childrenField && item[childrenField] ? { children: transformData(item[childrenField]) } : {})
...(childrenField && item[childrenField] ? { children: transformData(item[childrenField]) } : {}),
};
});
}
@@ -122,9 +122,9 @@ const bindProps = computed(() => {
...objectOmit(attrs, [`onUpdate:${props.modelPropName}`]),
...(props.visibleEvent
? {
[props.visibleEvent]: handleFetchForVisible
[props.visibleEvent]: handleFetchForVisible,
}
: {})
: {}),
};
});
@@ -1 +1 @@
export { default as ApiComponent } from './api-component.vue';
export { default as ApiComponent } from "./api-component.vue";
@@ -1,6 +1,6 @@
import type { CaptchaPoint } from '../types';
import type { CaptchaPoint } from "../types";
import { reactive } from 'vue';
import { reactive } from "vue";
export function useCaptchaPoints() {
const points = reactive<CaptchaPoint[]>([]);
@@ -1,6 +1,6 @@
export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue';
export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue';
export { default as PointSelectionCaptcha } from "./point-selection-captcha/index.vue";
export { default as PointSelectionCaptchaCard } from "./point-selection-captcha/index.vue";
export { default as SliderCaptcha } from './slider-captcha/index.vue';
export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue';
export type * from './types';
export { default as SliderCaptcha } from "./slider-captcha/index.vue";
export { default as SliderRotateCaptcha } from "./slider-rotate-captcha/index.vue";
export type * from "./types";
@@ -1,23 +1,23 @@
<script setup lang="ts">
import type { CaptchaPoint, PointSelectionCaptchaProps } from '../types';
import type { CaptchaPoint, PointSelectionCaptchaProps } from "../types";
import { RotateCw } from '/@/vben/icons';
import { $t } from '/@/locales';
import { RotateCw } from "/@/vben/icons";
import { $t } from "/@/locales";
import { VbenButton, VbenIconButton } from '/@/vben/shadcn-ui';
import { VbenButton, VbenIconButton } from "/@/vben/shadcn-ui";
import { useCaptchaPoints } from '../hooks/useCaptchaPoints';
import CaptchaCard from './point-selection-captcha-card.vue';
import { useCaptchaPoints } from "../hooks/useCaptchaPoints";
import CaptchaCard from "./point-selection-captcha-card.vue";
const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
height: '220px',
hintImage: '',
hintText: '',
paddingX: '12px',
paddingY: '16px',
height: "220px",
hintImage: "",
hintText: "",
paddingX: "12px",
paddingY: "16px",
showConfirm: false,
title: '',
width: '300px',
title: "",
width: "300px",
});
const emit = defineEmits<{
click: [CaptchaPoint];
@@ -27,7 +27,7 @@ const emit = defineEmits<{
const { addPoint, clearPoints, points } = useCaptchaPoints();
if (!props.hintImage && !props.hintText) {
console.warn('At least one of hint image or hint text must be provided');
console.warn("At least one of hint image or hint text must be provided");
}
const POINT_OFFSET = 11;
@@ -43,15 +43,15 @@ function getElementPosition(element: HTMLElement) {
function handleClick(e: MouseEvent) {
try {
const dom = e.currentTarget as HTMLElement;
if (!dom) throw new Error('Element not found');
if (!dom) throw new Error("Element not found");
const { x: domX, y: domY } = getElementPosition(dom);
const mouseX = e.clientX + window.scrollX;
const mouseY = e.clientY + window.scrollY;
if (typeof mouseX !== 'number' || typeof mouseY !== 'number') {
throw new TypeError('Mouse coordinates not found');
if (typeof mouseX !== "number" || typeof mouseY !== "number") {
throw new TypeError("Mouse coordinates not found");
}
const xPos = mouseX - domX;
@@ -61,7 +61,7 @@ function handleClick(e: MouseEvent) {
// 点击位置边界校验
if (xPos < 0 || yPos < 0 || xPos > rect.width || yPos > rect.height) {
console.warn('Click position is out of the valid range');
console.warn("Click position is out of the valid range");
return;
}
@@ -77,11 +77,11 @@ function handleClick(e: MouseEvent) {
addPoint(point);
emit('click', point);
emit("click", point);
e.stopPropagation();
e.preventDefault();
} catch (error) {
console.error('Error in handleClick:', error);
console.error("Error in handleClick:", error);
}
}
@@ -89,58 +89,40 @@ function clear() {
try {
clearPoints();
} catch (error) {
console.error('Error in clear:', error);
console.error("Error in clear:", error);
}
}
function handleRefresh() {
try {
clear();
emit('refresh');
emit("refresh");
} catch (error) {
console.error('Error in handleRefresh:', error);
console.error("Error in handleRefresh:", error);
}
}
function handleConfirm() {
if (!props.showConfirm) return;
try {
emit('confirm', points, clear);
emit("confirm", points, clear);
} catch (error) {
console.error('Error in handleConfirm:', error);
console.error("Error in handleConfirm:", error);
}
}
</script>
<template>
<CaptchaCard
:captcha-image="captchaImage"
:height="height"
:padding-x="paddingX"
:padding-y="paddingY"
:title="title"
:width="width"
@click="handleClick"
>
<CaptchaCard :captcha-image="captchaImage" :height="height" :padding-x="paddingX" :padding-y="paddingY" :title="title" :width="width" @click="handleClick">
<template #title>
<slot name="title">{{ $t('ui.captcha.title') }}</slot>
<slot name="title">{{ $t("ui.captcha.title") }}</slot>
</template>
<template #extra>
<VbenIconButton
:aria-label="$t('ui.captcha.refreshAriaLabel')"
class="ml-1"
@click="handleRefresh"
>
<VbenIconButton :aria-label="$t('ui.captcha.refreshAriaLabel')" class="ml-1" @click="handleRefresh">
<RotateCw class="size-5" />
</VbenIconButton>
<VbenButton
v-if="showConfirm"
:aria-label="$t('ui.captcha.confirmAriaLabel')"
class="ml-2"
size="sm"
@click="handleConfirm"
>
{{ $t('ui.captcha.confirm') }}
<VbenButton v-if="showConfirm" :aria-label="$t('ui.captcha.confirmAriaLabel')" class="ml-2" size="sm" @click="handleConfirm">
{{ $t("ui.captcha.confirm") }}
</VbenButton>
</template>
@@ -159,17 +141,9 @@ function handleConfirm() {
{{ index + 1 }}
</div>
<template #footer>
<img
v-if="hintImage"
:alt="$t('ui.captcha.alt')"
:src="hintImage"
class="border-border h-10 w-full rounded border"
/>
<div
v-else-if="hintText"
class="border-border flex-center h-10 w-full rounded border"
>
{{ `${$t('ui.captcha.clickInOrder')}` + `${hintText}` }}
<img v-if="hintImage" :alt="$t('ui.captcha.alt')" :src="hintImage" class="border-border h-10 w-full rounded border" />
<div v-else-if="hintText" class="border-border flex-center h-10 w-full rounded border">
{{ `${$t("ui.captcha.clickInOrder")}` + `${hintText}` }}
</div>
</template>
</CaptchaCard>
@@ -1,24 +1,18 @@
<script setup lang="ts">
import type { PointSelectionCaptchaCardProps } from '../types';
import type { PointSelectionCaptchaCardProps } from "../types";
import { computed } from 'vue';
import { computed } from "vue";
import { $t } from '/@/locales';
import { $t } from "/@/locales";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '/@/vben/shadcn-ui';
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "/@/vben/shadcn-ui";
const props = withDefaults(defineProps<PointSelectionCaptchaCardProps>(), {
height: '220px',
paddingX: '12px',
paddingY: '16px',
title: '',
width: '300px',
height: "220px",
paddingX: "12px",
paddingY: "16px",
title: "",
width: "300px",
});
const emit = defineEmits<{
@@ -26,7 +20,7 @@ const emit = defineEmits<{
}>();
const parseValue = (value: number | string) => {
if (typeof value === 'number') {
if (typeof value === "number") {
return value;
}
const parsed = Number.parseFloat(value);
@@ -46,7 +40,7 @@ const captchaStyles = computed(() => {
});
function handleClick(e: MouseEvent) {
emit('click', e);
emit("click", e);
}
</script>
<template>
@@ -54,7 +48,7 @@ function handleClick(e: MouseEvent) {
<CardHeader class="p-0">
<CardTitle id="captcha-title" class="flex items-center justify-between">
<template v-if="$slots.title">
<slot name="title">{{ $t('ui.captcha.title') }}</slot>
<slot name="title">{{ $t("ui.captcha.title") }}</slot>
</template>
<template v-else>
<span>{{ title }}</span>
@@ -65,14 +59,7 @@ function handleClick(e: MouseEvent) {
</CardTitle>
</CardHeader>
<CardContent class="relative mt-2 flex w-full overflow-hidden rounded p-0">
<img
v-show="captchaImage"
:alt="$t('ui.captcha.alt')"
:src="captchaImage"
:style="captchaStyles"
class="relative z-10"
@click="handleClick"
/>
<img v-show="captchaImage" :alt="$t('ui.captcha.alt')" :src="captchaImage" :style="captchaStyles" class="relative z-10" @click="handleClick" />
<div class="absolute inset-0">
<slot></slot>
</div>
@@ -1,29 +1,25 @@
<script setup lang="ts">
import type {
CaptchaVerifyPassingData,
SliderCaptchaProps,
SliderRotateVerifyPassingData,
} from '../types';
import type { CaptchaVerifyPassingData, SliderCaptchaProps, SliderRotateVerifyPassingData } from "../types";
import { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue';
import { reactive, unref, useTemplateRef, watch, watchEffect } from "vue";
import { $t } from '/@/locales';
import { $t } from "/@/locales";
import { cn } from '/@/vben/shared/utils';
import { cn } from "/@/vben/shared/utils";
import { useTimeoutFn } from '@vueuse/core';
import { useTimeoutFn } from "@vueuse/core";
import SliderCaptchaAction from './slider-captcha-action.vue';
import SliderCaptchaBar from './slider-captcha-bar.vue';
import SliderCaptchaContent from './slider-captcha-content.vue';
import SliderCaptchaAction from "./slider-captcha-action.vue";
import SliderCaptchaBar from "./slider-captcha-bar.vue";
import SliderCaptchaContent from "./slider-captcha-content.vue";
const props = withDefaults(defineProps<SliderCaptchaProps>(), {
actionStyle: () => ({}),
barStyle: () => ({}),
contentStyle: () => ({}),
isSlot: false,
successText: '',
text: '',
successText: "",
text: "",
wrapperStyle: () => ({}),
});
@@ -49,21 +45,21 @@ defineExpose({
resume,
});
const wrapperRef = useTemplateRef<HTMLDivElement>('wrapperRef');
const barRef = useTemplateRef<typeof SliderCaptchaBar>('barRef');
const contentRef = useTemplateRef<typeof SliderCaptchaContent>('contentRef');
const actionRef = useTemplateRef<typeof SliderCaptchaAction>('actionRef');
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapperRef");
const barRef = useTemplateRef<typeof SliderCaptchaBar>("barRef");
const contentRef = useTemplateRef<typeof SliderCaptchaContent>("contentRef");
const actionRef = useTemplateRef<typeof SliderCaptchaAction>("actionRef");
watch(
() => state.isPassing,
(isPassing) => {
isPassing => {
if (isPassing) {
const { endTime, startTime } = state;
const time = (endTime - startTime) / 1000;
emit('success', { isPassing, time: time.toFixed(1) });
emit("success", { isPassing, time: time.toFixed(1) });
modelValue.value = isPassing;
}
},
}
);
watchEffect(() => {
@@ -71,9 +67,9 @@ watchEffect(() => {
});
function getEventPageX(e: MouseEvent | TouchEvent): number {
if ('pageX' in e) {
if ("pageX" in e) {
return e.pageX;
} else if ('touches' in e && e.touches[0]) {
} else if ("touches" in e && e.touches[0]) {
return e.touches[0].pageX;
}
return 0;
@@ -84,14 +80,9 @@ function handleDragStart(e: MouseEvent | TouchEvent) {
return;
}
if (!actionRef.value) return;
emit('start', e);
emit("start", e);
state.moveDistance =
getEventPageX(e) -
Number.parseInt(
actionRef.value.getStyle().left.replace('px', '') || '0',
10,
);
state.moveDistance = getEventPageX(e) - Number.parseInt(actionRef.value.getStyle().left.replace("px", "") || "0", 10);
state.startTime = Date.now();
state.isMoving = true;
}
@@ -112,7 +103,7 @@ function handleDragMoving(e: MouseEvent | TouchEvent) {
const { actionWidth, offset, wrapperWidth } = getOffset(actionEl.getEl());
const moveX = getEventPageX(e) - moveDistance;
emit('move', {
emit("move", {
event: e,
moveDistance,
moveX,
@@ -133,7 +124,7 @@ function handleDragMoving(e: MouseEvent | TouchEvent) {
function handleDragOver(e: MouseEvent | TouchEvent) {
const { isMoving, isPassing, moveDistance } = state;
if (isMoving && !isPassing) {
emit('end', e);
emit("end", e);
const actionEl = actionRef.value;
const barEl = unref(barRef);
if (!actionEl || !barEl) return;
@@ -185,12 +176,12 @@ function resume() {
const contentEl = unref(contentRef);
if (!actionEl || !barEl || !contentEl) return;
contentEl.getEl().style.width = '100%';
contentEl.getEl().style.width = "100%";
state.toLeft = true;
useTimeoutFn(() => {
state.toLeft = false;
actionEl.setLeft('0');
barEl.setWidth('0');
actionEl.setLeft("0");
barEl.setWidth("0");
}, 300);
}
</script>
@@ -198,12 +189,7 @@ function resume() {
<template>
<div
ref="wrapperRef"
:class="
cn(
'border-border bg-background-deep relative flex h-10 w-full items-center overflow-hidden rounded-md border text-center',
props.class,
)
"
:class="cn('border-border bg-background-deep relative flex h-10 w-full items-center overflow-hidden rounded-md border text-center', props.class)"
:style="wrapperStyle"
@mouseleave="handleDragOver"
@mousemove="handleDragMoving"
@@ -211,31 +197,14 @@ function resume() {
@touchend="handleDragOver"
@touchmove="handleDragMoving"
>
<SliderCaptchaBar
ref="barRef"
:bar-style="barStyle"
:to-left="state.toLeft"
/>
<SliderCaptchaContent
ref="contentRef"
:content-style="contentStyle"
:is-passing="state.isPassing"
:success-text="successText || $t('ui.captcha.sliderSuccessText')"
:text="text || $t('ui.captcha.sliderDefaultText')"
>
<SliderCaptchaBar ref="barRef" :bar-style="barStyle" :to-left="state.toLeft" />
<SliderCaptchaContent ref="contentRef" :content-style="contentStyle" :is-passing="state.isPassing" :success-text="successText || $t('ui.captcha.sliderSuccessText')" :text="text || $t('ui.captcha.sliderDefaultText')">
<template v-if="$slots.text" #text>
<slot :is-passing="state.isPassing" name="text"></slot>
</template>
</SliderCaptchaContent>
<SliderCaptchaAction
ref="actionRef"
:action-style="actionStyle"
:is-passing="state.isPassing"
:to-left="state.toLeft"
@mousedown="handleDragStart"
@touchstart="handleDragStart"
>
<SliderCaptchaAction ref="actionRef" :action-style="actionStyle" :is-passing="state.isPassing" :to-left="state.toLeft" @mousedown="handleDragStart" @touchstart="handleDragStart">
<template v-if="$slots.actionIcon" #icon>
<slot :is-passing="state.isPassing" name="actionIcon"></slot>
</template>
@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import type { CSSProperties } from "vue";
import { computed, ref, useTemplateRef } from 'vue';
import { computed, ref, useTemplateRef } from "vue";
import { Check, ChevronsRight } from '/@/vben/icons';
import { Check, ChevronsRight } from "/@/vben/icons";
import { Slot } from '/@/vben/shadcn-ui';
import { Slot } from "/@/vben/shadcn-ui";
const props = defineProps<{
actionStyle: CSSProperties;
@@ -13,9 +13,9 @@ const props = defineProps<{
toLeft: boolean;
}>();
const actionRef = useTemplateRef<HTMLDivElement>('actionRef');
const actionRef = useTemplateRef<HTMLDivElement>("actionRef");
const left = ref('0');
const left = ref("0");
const style = computed(() => {
const { actionStyle } = props;
@@ -1,16 +1,16 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import type { CSSProperties } from "vue";
import { computed, ref, useTemplateRef } from 'vue';
import { computed, ref, useTemplateRef } from "vue";
const props = defineProps<{
barStyle: CSSProperties;
toLeft: boolean;
}>();
const barRef = useTemplateRef<HTMLDivElement>('barRef');
const barRef = useTemplateRef<HTMLDivElement>("barRef");
const width = ref('0');
const width = ref("0");
const style = computed(() => {
const { barStyle } = props;
@@ -31,10 +31,5 @@ defineExpose({
</script>
<template>
<div
ref="barRef"
:class="toLeft && 'transition-width !w-0 duration-300'"
:style="style"
class="bg-success absolute h-full"
></div>
<div ref="barRef" :class="toLeft && 'transition-width !w-0 duration-300'" :style="style" class="bg-success absolute h-full"></div>
</template>
@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import type { CSSProperties } from "vue";
import { computed, useTemplateRef } from 'vue';
import { computed, useTemplateRef } from "vue";
import { VbenSpineText } from '/@/vben/shadcn-ui';
import { VbenSpineText } from "/@/vben/shadcn-ui";
const props = defineProps<{
contentStyle: CSSProperties;
@@ -12,7 +12,7 @@ const props = defineProps<{
text: string;
}>();
const contentRef = useTemplateRef<HTMLDivElement>('contentRef');
const contentRef = useTemplateRef<HTMLDivElement>("contentRef");
const style = computed(() => {
const { contentStyle } = props;
@@ -1,33 +1,28 @@
<script setup lang="ts">
import type {
CaptchaVerifyPassingData,
SliderCaptchaActionType,
SliderRotateCaptchaProps,
SliderRotateVerifyPassingData,
} from '../types';
import type { CaptchaVerifyPassingData, SliderCaptchaActionType, SliderRotateCaptchaProps, SliderRotateVerifyPassingData } from "../types";
import { computed, reactive, unref, useTemplateRef, watch } from 'vue';
import { computed, reactive, unref, useTemplateRef, watch } from "vue";
import { $t } from '/@/locales';
import { $t } from "/@/locales";
import { useTimeoutFn } from '@vueuse/core';
import { useTimeoutFn } from "@vueuse/core";
import SliderCaptcha from '../slider-captcha/index.vue';
import SliderCaptcha from "../slider-captcha/index.vue";
const props = withDefaults(defineProps<SliderRotateCaptchaProps>(), {
defaultTip: '',
defaultTip: "",
diffDegree: 20,
imageSize: 260,
maxDegree: 300,
minDegree: 120,
src: '',
src: "",
});
const emit = defineEmits<{
success: [CaptchaVerifyPassingData];
}>();
const slideBarRef = useTemplateRef<SliderCaptchaActionType>('slideBarRef');
const slideBarRef = useTemplateRef<SliderCaptchaActionType>("slideBarRef");
const state = reactive({
currentRotate: 0,
@@ -45,14 +40,14 @@ const modalValue = defineModel<boolean>({ default: false });
watch(
() => state.isPassing,
(isPassing) => {
isPassing => {
if (isPassing) {
const { endTime, startTime } = state;
const time = (endTime - startTime) / 1000;
emit('success', { isPassing, time: time.toFixed(1) });
emit("success", { isPassing, time: time.toFixed(1) });
}
modalValue.value = isPassing;
},
}
);
const getImgWrapStyleRef = computed(() => {
@@ -67,7 +62,7 @@ const getImgWrapStyleRef = computed(() => {
const getFactorRef = computed(() => {
const { maxDegree, minDegree } = props;
if (minDegree > maxDegree) {
console.warn('minDegree should not be greater than maxDegree');
console.warn("minDegree should not be greater than maxDegree");
}
if (minDegree === maxDegree) {
@@ -88,18 +83,14 @@ function handleDragBarMove(data: SliderRotateVerifyPassingData) {
if (denominator === 0) {
return;
}
const currentRotate = Math.ceil(
(moveX / denominator) * 1.5 * maxDegree! * unref(getFactorRef),
);
const currentRotate = Math.ceil((moveX / denominator) * 1.5 * maxDegree! * unref(getFactorRef));
state.currentRotate = currentRotate;
setImgRotate(state.randomRotate - currentRotate);
}
function handleImgOnLoad() {
const { maxDegree, minDegree } = props;
const ranRotate = Math.floor(
minDegree! + Math.random() * (maxDegree! - minDegree!),
); // 生成随机角度
const ranRotate = Math.floor(minDegree! + Math.random() * (maxDegree! - minDegree!)); // 生成随机角度
state.randomRotate = ranRotate;
setImgRotate(ranRotate);
}
@@ -147,15 +138,11 @@ function resume() {
}
const imgCls = computed(() => {
return state.toOrigin ? ['transition-transform duration-300'] : [];
return state.toOrigin ? ["transition-transform duration-300"] : [];
});
const verifyTip = computed(() => {
return state.isPassing
? $t('ui.captcha.sliderRotateSuccessTip', [
((state.endTime - state.startTime) / 1000).toFixed(1),
])
: $t('ui.captcha.sliderRotateFailTip');
return state.isPassing ? $t("ui.captcha.sliderRotateSuccessTip", [((state.endTime - state.startTime) / 1000).toFixed(1)]) : $t("ui.captcha.sliderRotateFailTip");
});
defineExpose({
@@ -165,22 +152,9 @@ defineExpose({
<template>
<div class="relative flex flex-col items-center">
<div
:style="getImgWrapStyleRef"
class="border-border relative cursor-pointer overflow-hidden rounded-full border shadow-md"
>
<img
:class="imgCls"
:src="src"
:style="state.imgStyle"
alt="verify"
class="w-full rounded-full"
@click="resume"
@load="handleImgOnLoad"
/>
<div
class="absolute bottom-3 left-0 z-10 block h-7 w-full text-center text-xs leading-[30px] text-white"
>
<div :style="getImgWrapStyleRef" class="border-border relative cursor-pointer overflow-hidden rounded-full border shadow-md">
<img :class="imgCls" :src="src" :style="state.imgStyle" alt="verify" class="w-full rounded-full" @click="resume" @load="handleImgOnLoad" />
<div class="absolute bottom-3 left-0 z-10 block h-7 w-full text-center text-xs leading-[30px] text-white">
<div
v-if="state.showTip"
:class="{
@@ -191,20 +165,12 @@ defineExpose({
{{ verifyTip }}
</div>
<div v-if="!state.dragging" class="bg-black/30">
{{ defaultTip || $t('ui.captcha.sliderRotateDefaultTip') }}
{{ defaultTip || $t("ui.captcha.sliderRotateDefaultTip") }}
</div>
</div>
</div>
<SliderCaptcha
ref="slideBarRef"
v-model="modalValue"
class="mt-5"
is-slot
@end="handleDragEnd"
@move="handleDragBarMove"
@start="handleStart"
>
<SliderCaptcha ref="slideBarRef" v-model="modalValue" class="mt-5" is-slot @end="handleDragEnd" @move="handleDragBarMove" @start="handleStart">
<template v-for="(_, key) in $slots" :key="key" #[key]="slotProps">
<slot :name="key" v-bind="slotProps"></slot>
</template>
@@ -1,6 +1,6 @@
import type { CSSProperties } from 'vue';
import type { CSSProperties } from "vue";
import type { ClassType } from '/@/vben/types';
import type { ClassType } from "/@/vben/types";
export interface CaptchaData {
/**
@@ -54,8 +54,7 @@ export interface PointSelectionCaptchaCardProps {
width?: number | string;
}
export interface PointSelectionCaptchaProps
extends PointSelectionCaptchaCardProps {
export interface PointSelectionCaptchaProps extends PointSelectionCaptchaCardProps {
/**
* 是否展示确定按钮
* @default false
@@ -1,18 +1,14 @@
<script lang="ts" setup>
import type { ColPageProps } from './types';
import type { ColPageProps } from "./types";
import { computed, ref, useSlots } from 'vue';
import { computed, ref, useSlots } from "vue";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from '/@/vben/shadcn-ui';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "/@/vben/shadcn-ui";
import Page from '../page/page.vue';
import Page from "../page/page.vue";
defineOptions({
name: 'ColPage',
name: "ColPage",
inheritAttrs: false,
});
@@ -33,7 +29,7 @@ const delegatedSlots = computed(() => {
const resultSlots: string[] = [];
for (const key of Object.keys(slots)) {
if (!['default', 'left'].includes(key)) {
if (!["default", "left"].includes(key)) {
resultSlots.push(key);
}
}
@@ -58,23 +54,12 @@ defineExpose({
<template>
<Page v-bind="delegatedProps">
<!-- 继承默认的slot -->
<template
v-for="slotName in delegatedSlots"
:key="slotName"
#[slotName]="slotProps"
>
<template v-for="slotName in delegatedSlots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
<ResizablePanelGroup class="w-full" direction="horizontal">
<ResizablePanel
ref="leftPanelRef"
:collapsed-size="leftCollapsedWidth"
:collapsible="leftCollapsible"
:default-size="leftWidth"
:max-size="leftMaxWidth"
:min-size="leftMinWidth"
>
<ResizablePanel ref="leftPanelRef" :collapsed-size="leftCollapsedWidth" :collapsible="leftCollapsible" :default-size="leftWidth" :max-size="leftMaxWidth" :min-size="leftMinWidth">
<template #default="slotProps">
<slot
name="left"
@@ -86,18 +71,8 @@ defineExpose({
></slot>
</template>
</ResizablePanel>
<ResizableHandle
v-if="resizable"
:style="{ backgroundColor: splitLine ? undefined : 'transparent' }"
:with-handle="splitHandle"
/>
<ResizablePanel
:collapsed-size="rightCollapsedWidth"
:collapsible="rightCollapsible"
:default-size="rightWidth"
:max-size="rightMaxWidth"
:min-size="rightMinWidth"
>
<ResizableHandle v-if="resizable" :style="{ backgroundColor: splitLine ? undefined : 'transparent' }" :with-handle="splitHandle" />
<ResizablePanel :collapsed-size="rightCollapsedWidth" :collapsible="rightCollapsible" :default-size="rightWidth" :max-size="rightMaxWidth" :min-size="rightMinWidth">
<template #default>
<slot></slot>
</template>
@@ -1,2 +1,2 @@
export { default as ColPage } from './col-page.vue';
export * from './types';
export { default as ColPage } from "./col-page.vue";
export * from "./types";
@@ -1,4 +1,4 @@
import type { PageProps } from '../page/types';
import type { PageProps } from "../page/types";
export interface ColPageProps extends PageProps {
/**
@@ -1,23 +1,23 @@
<script lang="ts" setup>
import type { CountToProps } from './types';
import type { CountToProps } from "./types";
import { computed, onMounted, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from "vue";
import { isString } from '/@/vben/shared/utils';
import { isString } from "/@/vben/shared/utils";
import { TransitionPresets, useTransition } from '@vueuse/core';
import { TransitionPresets, useTransition } from "@vueuse/core";
const props = withDefaults(defineProps<CountToProps>(), {
startVal: 0,
duration: 2000,
separator: ',',
decimal: '.',
separator: ",",
decimal: ".",
decimals: 0,
delay: 0,
transition: () => TransitionPresets.easeOutExpo,
});
const emit = defineEmits(['started', 'finished']);
const emit = defineEmits(["started", "finished"]);
const lastValue = ref(props.startVal);
@@ -27,9 +27,9 @@ onMounted(() => {
watch(
() => props.endVal,
(val) => {
val => {
lastValue.value = val;
},
}
);
const currentValue = useTransition(lastValue, {
@@ -37,62 +37,43 @@ const currentValue = useTransition(lastValue, {
duration: computed(() => props.duration),
disabled: computed(() => props.disabled),
transition: computed(() => {
return isString(props.transition)
? TransitionPresets[props.transition]
: props.transition;
return isString(props.transition) ? TransitionPresets[props.transition] : props.transition;
}),
onStarted() {
emit('started');
emit("started");
},
onFinished() {
emit('finished');
emit("finished");
},
});
const numMain = computed(() => {
const result = currentValue.value
.toFixed(props.decimals)
.split('.')[0]
.split(".")[0]
?.replaceAll(/\B(?=(\d{3})+(?!\d))/g, props.separator);
return result;
});
const numDec = computed(() => {
return (
props.decimal + currentValue.value.toFixed(props.decimals).split('.')[1]
);
return props.decimal + currentValue.value.toFixed(props.decimals).split(".")[1];
});
</script>
<template>
<div class="count-to" v-bind="$attrs">
<slot name="prefix">
<div
class="count-to-prefix"
:style="prefixStyle"
:class="prefixClass"
v-if="prefix"
>
<div v-if="prefix" class="count-to-prefix" :style="prefixStyle" :class="prefixClass">
{{ prefix }}
</div>
</slot>
<div class="count-to-main" :class="mainClass" :style="mainStyle">
<span>{{ numMain }}</span>
<span
class="count-to-main-decimal"
v-if="decimals > 0"
:class="decimalClass"
:style="decimalStyle"
>
<span v-if="decimals > 0" class="count-to-main-decimal" :class="decimalClass" :style="decimalStyle">
{{ numDec }}
</span>
</div>
<slot name="suffix">
<div
class="count-to-suffix"
:style="suffixStyle"
:class="suffixClass"
v-if="suffix"
>
<div v-if="suffix" class="count-to-suffix" :style="suffixStyle" :class="suffixClass">
{{ suffix }}
</div>
</slot>
@@ -112,7 +93,7 @@ const numDec = computed(() => {
}
&-main {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
// font-size: 1.5rem;
&-decimal {
@@ -1,2 +1,2 @@
export { default as CountTo } from './count-to.vue';
export * from './types';
export { default as CountTo } from "./count-to.vue";
export * from "./types";
@@ -1,14 +1,12 @@
import type { CubicBezierPoints, EasingFunction } from '@vueuse/core';
import type { CubicBezierPoints, EasingFunction } from "@vueuse/core";
import type { StyleValue } from 'vue';
import type { StyleValue } from "vue";
import { TransitionPresets as TransitionPresetsData } from '@vueuse/core';
import { TransitionPresets as TransitionPresetsData } from "@vueuse/core";
export type TransitionPresets = keyof typeof TransitionPresetsData;
export const TransitionPresetsKeys = Object.keys(
TransitionPresetsData,
) as TransitionPresets[];
export const TransitionPresetsKeys = Object.keys(TransitionPresetsData) as TransitionPresets[];
export interface CountToProps {
/** 初始值 */
@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import type { CSSProperties } from "vue";
import { computed, ref, watchEffect } from 'vue';
import { computed, ref, watchEffect } from "vue";
import { VbenTooltip } from '/@/vben/shadcn-ui';
import { VbenTooltip } from "/@/vben/shadcn-ui";
import { useElementSize } from '@vueuse/core';
import { useElementSize } from "@vueuse/core";
interface Props {
/**
@@ -27,7 +27,7 @@ interface Props {
* 提示框位置
* @default 'top'
*/
placement?: 'bottom' | 'left' | 'right' | 'top';
placement?: "bottom" | "left" | "right" | "top";
/**
* 是否启用文本提示框
* @default true
@@ -59,19 +59,19 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
expand: false,
line: 1,
maxWidth: '100%',
placement: 'top',
maxWidth: "100%",
placement: "top",
tooltip: true,
tooltipBackgroundColor: '',
tooltipColor: '',
tooltipBackgroundColor: "",
tooltipColor: "",
tooltipFontSize: 14,
tooltipMaxWidth: undefined,
tooltipOverlayStyle: () => ({ textAlign: 'justify' }),
tooltipOverlayStyle: () => ({ textAlign: "justify" }),
});
const emit = defineEmits<{ expandChange: [boolean] }>();
const textMaxWidth = computed(() => {
if (typeof props.maxWidth === 'number') {
if (typeof props.maxWidth === "number") {
return `${props.maxWidth}px`;
}
return props.maxWidth;
@@ -85,15 +85,14 @@ const { width: eleWidth } = useElementSize(ellipsis);
watchEffect(
() => {
if (props.tooltip && eleWidth.value) {
defaultTooltipMaxWidth.value =
props.tooltipMaxWidth ?? eleWidth.value + 24;
defaultTooltipMaxWidth.value = props.tooltipMaxWidth ?? eleWidth.value + 24;
}
},
{ flush: 'post' },
{ flush: "post" }
);
function onExpand() {
isExpand.value = !isExpand.value;
emit('expandChange', isExpand.value);
emit("expandChange", isExpand.value);
}
function handleExpand() {
@@ -130,8 +129,8 @@ function handleExpand() {
'max-width': textMaxWidth,
}"
class="cursor-text overflow-hidden"
@click="handleExpand"
v-bind="$attrs"
@click="handleExpand"
>
<slot></slot>
</div>
@@ -1 +1 @@
export { default as EllipsisText } from './ellipsis-text.vue';
export { default as EllipsisText } from "./ellipsis-text.vue";
@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { VNode } from 'vue';
import type { VNode } from "vue";
import { computed, ref, watch, watchEffect } from 'vue';
import { computed, ref, watch, watchEffect } from "vue";
import { usePagination } from '/@/vben/hooks';
import { EmptyIcon, Grip, listIcons } from '/@/vben/icons';
import { $t } from '/@/locales';
import { usePagination } from "/@/vben/hooks";
import { EmptyIcon, Grip, listIcons } from "/@/vben/icons";
import { $t } from "/@/locales";
import {
Button,
@@ -21,11 +21,11 @@ import {
VbenIcon,
VbenIconButton,
VbenPopover,
} from '/@/vben/shadcn-ui';
} from "/@/vben/shadcn-ui";
import { refDebounced, watchDebounced } from '@vueuse/core';
import { refDebounced, watchDebounced } from "@vueuse/core";
import { fetchIconsData } from './icons';
import { fetchIconsData } from "./icons";
interface Props {
pageSize?: number;
@@ -45,55 +45,51 @@ interface Props {
modelValueProp?: string;
/** 图标样式 */
iconClass?: string;
type?: 'icon' | 'input';
type?: "icon" | "input";
}
const props = withDefaults(defineProps<Props>(), {
prefix: 'ant-design',
prefix: "ant-design",
pageSize: 36,
icons: () => [],
iconSlot: 'default',
iconClass: 'size-4',
iconSlot: "default",
iconClass: "size-4",
autoFetchApi: true,
modelValueProp: 'modelValue',
modelValueProp: "modelValue",
inputComponent: undefined,
type: 'input',
type: "input",
});
const emit = defineEmits<{
change: [string];
}>();
const modelValue = defineModel({ default: '', type: String });
const modelValue = defineModel({ default: "", type: String });
const visible = ref(false);
const currentSelect = ref('');
const currentSelect = ref("");
const currentPage = ref(1);
const keyword = ref('');
const keyword = ref("");
const keywordDebounce = refDebounced(keyword, 300);
const innerIcons = ref<string[]>([]);
watchDebounced(
() => props.prefix,
async (prefix) => {
if (prefix && prefix !== 'svg' && props.autoFetchApi) {
async prefix => {
if (prefix && prefix !== "svg" && props.autoFetchApi) {
innerIcons.value = await fetchIconsData(prefix);
}
},
{ immediate: true, debounce: 500, maxWait: 1000 },
{ immediate: true, debounce: 500, maxWait: 1000 }
);
const currentList = computed(() => {
try {
if (props.prefix) {
if (
props.prefix !== 'svg' &&
props.autoFetchApi &&
props.icons.length === 0
) {
if (props.prefix !== "svg" && props.autoFetchApi && props.icons.length === 0) {
return innerIcons.value;
}
const icons = listIcons('', props.prefix);
const icons = listIcons("", props.prefix);
if (icons.length === 0) {
console.warn(`No icons found for prefix: ${props.prefix}`);
}
@@ -102,21 +98,16 @@ const currentList = computed(() => {
return props.icons;
}
} catch (error) {
console.error('Failed to load icons:', error);
console.error("Failed to load icons:", error);
return [];
}
});
const showList = computed(() => {
return currentList.value.filter((item) =>
item.includes(keywordDebounce.value),
);
return currentList.value.filter(item => item.includes(keywordDebounce.value));
});
const { paginationList, total, setCurrentPage } = usePagination(
showList,
props.pageSize,
);
const { paginationList, total, setCurrentPage } = usePagination(showList, props.pageSize);
watchEffect(() => {
currentSelect.value = modelValue.value;
@@ -124,9 +115,9 @@ watchEffect(() => {
watch(
() => currentSelect.value,
(v) => {
emit('change', v);
},
v => {
emit("change", v);
}
);
const handleClick = (icon: string) => {
@@ -158,26 +149,22 @@ function onKeywordChange(v: string) {
const searchInputProps = computed(() => {
return {
placeholder: $t('ui.iconPicker.search'),
placeholder: $t("ui.iconPicker.search"),
[props.modelValueProp]: keyword.value,
[`onUpdate:${props.modelValueProp}`]: onKeywordChange,
class: 'mx-2',
class: "mx-2",
};
});
defineExpose({ toggleOpenState, open, close });
</script>
<template>
<VbenPopover
v-model:open="visible"
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
content-class="p-0 pt-3"
>
<VbenPopover v-model:open="visible" :content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }" content-class="p-0 pt-3">
<template #trigger>
<template v-if="props.type === 'input'">
<component
v-if="props.inputComponent"
:is="inputComponent"
v-if="props.inputComponent"
:[modelValueProp]="currentSelect"
:placeholder="$t('ui.iconPicker.placeholder')"
role="combobox"
@@ -186,60 +173,24 @@ defineExpose({ toggleOpenState, open, close });
v-bind="$attrs"
>
<template #[iconSlot]>
<VbenIcon
:icon="currentSelect || Grip"
class="size-4"
aria-hidden="true"
/>
<VbenIcon :icon="currentSelect || Grip" class="size-4" aria-hidden="true" />
</template>
</component>
<div class="relative w-full" v-else>
<Input
v-bind="$attrs"
v-model="currentSelect"
:placeholder="$t('ui.iconPicker.placeholder')"
class="h-8 w-full pr-8"
role="combobox"
:aria-label="$t('ui.iconPicker.placeholder')"
aria-expanded="visible"
/>
<VbenIcon
:icon="currentSelect || Grip"
class="absolute right-1 top-1 size-6"
aria-hidden="true"
/>
<div v-else class="relative w-full">
<Input v-bind="$attrs" v-model="currentSelect" :placeholder="$t('ui.iconPicker.placeholder')" class="h-8 w-full pr-8" role="combobox" :aria-label="$t('ui.iconPicker.placeholder')" aria-expanded="visible" />
<VbenIcon :icon="currentSelect || Grip" class="absolute right-1 top-1 size-6" aria-hidden="true" />
</div>
</template>
<VbenIcon
:icon="currentSelect || Grip"
v-else
class="size-4"
v-bind="$attrs"
/>
<VbenIcon v-else :icon="currentSelect || Grip" class="size-4" v-bind="$attrs" />
</template>
<div class="mb-2 flex w-full">
<component
v-if="inputComponent"
:is="inputComponent"
v-bind="searchInputProps"
/>
<Input
v-else
class="mx-2 h-8 w-full"
:placeholder="$t('ui.iconPicker.search')"
v-model="keyword"
/>
<component :is="inputComponent" v-if="inputComponent" v-bind="searchInputProps" />
<Input v-else v-model="keyword" class="mx-2 h-8 w-full" :placeholder="$t('ui.iconPicker.search')" />
</div>
<template v-if="paginationList.length > 0">
<div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center">
<VbenIconButton
v-for="(item, index) in paginationList"
:key="index"
:tooltip="item"
tooltip-side="top"
@click="handleClick(item)"
>
<VbenIconButton v-for="(item, index) in paginationList" :key="index" :tooltip="item" tooltip-side="top" @click="handleClick(item)">
<VbenIcon
:class="{
'text-primary transition-all': currentSelect === item,
@@ -248,44 +199,18 @@ defineExpose({ toggleOpenState, open, close });
/>
</VbenIconButton>
</div>
<div
v-if="total >= pageSize"
class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3"
>
<Pagination
:items-per-page="36"
:sibling-count="1"
:total="total"
show-edges
size="small"
@update:page="handlePageChange"
>
<PaginationList
v-slot="{ items }"
class="flex w-full items-center gap-1"
>
<div v-if="total >= pageSize" class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3">
<Pagination :items-per-page="36" :sibling-count="1" :total="total" show-edges size="small" @update:page="handlePageChange">
<PaginationList v-slot="{ items }" class="flex w-full items-center gap-1">
<PaginationFirst class="size-5" />
<PaginationPrev class="size-5" />
<template v-for="(item, index) in items">
<PaginationListItem
v-if="item.type === 'page'"
:key="index"
:value="item.value"
as-child
>
<Button
:variant="item.value === currentPage ? 'default' : 'outline'"
class="size-5 p-0 text-sm"
>
<PaginationListItem v-if="item.type === 'page'" :key="index" :value="item.value" as-child>
<Button :variant="item.value === currentPage ? 'default' : 'outline'" class="size-5 p-0 text-sm">
{{ item.value }}
</Button>
</PaginationListItem>
<PaginationEllipsis
v-else
:key="item.type"
:index="index"
class="size-5"
/>
<PaginationEllipsis v-else :key="item.type" :index="index" class="size-5" />
</template>
<PaginationNext class="size-5" />
<PaginationLast class="size-5" />
@@ -297,7 +222,7 @@ defineExpose({ toggleOpenState, open, close });
<template v-else>
<div class="flex-col-center text-muted-foreground min-h-[150px] w-full">
<EmptyIcon class="size-10" />
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
<div class="mt-1 text-sm">{{ $t("common.noData") }}</div>
</div>
</template>
</VbenPopover>
@@ -1,4 +1,4 @@
import type { Recordable } from '/@/vben/types';
import type { Recordable } from "/@/vben/types";
/**
* 一个缓存对象,在不刷新页面时,无需重复请求远程接口
@@ -34,10 +34,7 @@ export async function fetchIconsData(prefix: string): Promise<string[]> {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 1000 * 10);
const response: IconifyResponse = await fetch(
`https://api.iconify.design/collection?prefix=${prefix}`,
{ signal: controller.signal },
).then((res) => res.json());
const response: IconifyResponse = await fetch(`https://api.iconify.design/collection?prefix=${prefix}`, { signal: controller.signal }).then(res => res.json());
clearTimeout(timeoutId);
const list = response.uncategorized || [];
if (response.categories) {
@@ -45,7 +42,7 @@ export async function fetchIconsData(prefix: string): Promise<string[]> {
list.push(...(response.categories[category] || []));
}
}
ICONS_MAP[prefix] = list.map((v) => `${prefix}:${v}`);
ICONS_MAP[prefix] = list.map(v => `${prefix}:${v}`);
} catch (error) {
console.error(`Failed to fetch icons for prefix ${prefix}:`, error);
return [] as string[];
@@ -1 +1 @@
export { default as IconPicker } from './icon-picker.vue';
export { default as IconPicker } from "./icon-picker.vue";
@@ -1,27 +1,18 @@
export * from './api-component';
export * from './captcha';
export * from './col-page';
export * from './count-to';
export * from './ellipsis-text';
export * from './icon-picker';
export * from './json-viewer';
export * from './loading';
export * from './page';
export * from './resize';
export * from './tippy';
export * from '/@/vben/form-ui';
export * from '/@/vben/popup-ui';
export * from "./api-component";
export * from "./captcha";
export * from "./col-page";
export * from "./count-to";
export * from "./ellipsis-text";
export * from "./icon-picker";
export * from "./json-viewer";
export * from "./loading";
export * from "./page";
export * from "./resize";
export * from "./tippy";
export * from "/@/vben/form-ui";
export * from "/@/vben/popup-ui";
// 给文档用
export {
VbenButton,
VbenButtonGroup,
VbenCheckButtonGroup,
VbenCountToAnimator,
VbenInputPassword,
VbenLoading,
VbenPinInput,
VbenSpinner,
} from '/@/vben/shadcn-ui';
export { VbenButton, VbenButtonGroup, VbenCheckButtonGroup, VbenCountToAnimator, VbenInputPassword, VbenLoading, VbenPinInput, VbenSpinner } from "/@/vben/shadcn-ui";
export { globalShareState } from '/@/vben/shared/global-state';
export { globalShareState } from "/@/vben/shared/global-state";
@@ -1,3 +1,3 @@
export { default as JsonViewer } from './index.vue';
export { default as JsonViewer } from "./index.vue";
export * from './types';
export * from "./types";
@@ -1,31 +1,26 @@
<script lang="ts" setup>
import type { SetupContext } from 'vue';
import type { SetupContext } from "vue";
import type { Recordable } from '/@/vben/types';
import type { Recordable } from "/@/vben/types";
import type {
JsonViewerAction,
JsonViewerProps,
JsonViewerToggle,
JsonViewerValue,
} from './types';
import type { JsonViewerAction, JsonViewerProps, JsonViewerToggle, JsonViewerValue } from "./types";
import { computed, useAttrs } from 'vue';
import { computed, useAttrs } from "vue";
// @ts-ignore
import VueJsonViewer from 'vue-json-viewer';
import VueJsonViewer from "vue-json-viewer";
import { $t } from '/@/locales';
import { $t } from "/@/locales";
import { isBoolean } from '/@/vben/shared/utils';
import { isBoolean } from "/@/vben/shared/utils";
defineOptions({ name: 'JsonViewer' });
defineOptions({ name: "JsonViewer" });
const props = withDefaults(defineProps<JsonViewerProps>(), {
expandDepth: 1,
copyable: false,
sort: false,
boxed: false,
theme: 'default-json-theme',
theme: "default-json-theme",
expanded: false,
previewMode: false,
showArrayIndex: true,
@@ -40,38 +35,35 @@ const emit = defineEmits<{
valueClick: [value: JsonViewerValue];
}>();
const attrs: SetupContext['attrs'] = useAttrs();
const attrs: SetupContext["attrs"] = useAttrs();
function handleClick(event: MouseEvent) {
if (
event.target instanceof HTMLElement &&
event.target.classList.contains('jv-item')
) {
const pathNode = event.target.closest('.jv-push');
if (!pathNode || !pathNode.hasAttribute('path')) {
if (event.target instanceof HTMLElement && event.target.classList.contains("jv-item")) {
const pathNode = event.target.closest(".jv-push");
if (!pathNode || !pathNode.hasAttribute("path")) {
return;
}
const param: JsonViewerValue = {
path: '',
value: '',
path: "",
value: "",
depth: 0,
el: event.target,
};
param.path = pathNode.getAttribute('path') || '';
param.depth = Number(pathNode.getAttribute('depth')) || 0;
param.path = pathNode.getAttribute("path") || "";
param.depth = Number(pathNode.getAttribute("depth")) || 0;
param.value = event.target.textContent || undefined;
param.value = JSON.parse(param.value);
emit('valueClick', param);
emit("valueClick", param);
}
emit('click', event);
emit("click", event);
}
const bindProps = computed<Recordable<any>>(() => {
const copyable = {
copyText: $t('ui.jsonViewer.copy'),
copiedText: $t('ui.jsonViewer.copied'),
copyText: $t("ui.jsonViewer.copy"),
copiedText: $t("ui.jsonViewer.copied"),
timeout: 2000,
...(isBoolean(props.copyable) ? {} : props.copyable),
};
@@ -79,8 +71,8 @@ const bindProps = computed<Recordable<any>>(() => {
return {
...props,
...attrs,
onCopied: (event: JsonViewerAction) => emit('copied', event),
onKeyclick: (key: string) => emit('keyClick', key),
onCopied: (event: JsonViewerAction) => emit("copied", event),
onKeyclick: (key: string) => emit("keyClick", key),
onClick: (event: MouseEvent) => handleClick(event),
copyable: props.copyable ? copyable : false,
};
@@ -94,5 +86,5 @@ const bindProps = computed<Recordable<any>>(() => {
</VueJsonViewer>
</template>
<style lang="scss">
@use './style.scss';
@use "./style.scss";
</style>
@@ -1,14 +1,14 @@
import type { App, Directive, DirectiveBinding } from 'vue';
import type { App, Directive, DirectiveBinding } from "vue";
import { h, render } from 'vue';
import { h, render } from "vue";
import { VbenLoading, VbenSpinner } from '/@/vben/shadcn-ui';
import { isString } from '/@/vben/shared/utils';
import { VbenLoading, VbenSpinner } from "/@/vben/shadcn-ui";
import { isString } from "/@/vben/shared/utils";
const LOADING_INSTANCE_KEY = Symbol('loading');
const SPINNER_INSTANCE_KEY = Symbol('spinner');
const LOADING_INSTANCE_KEY = Symbol("loading");
const SPINNER_INSTANCE_KEY = Symbol("spinner");
const CLASS_NAME_RELATIVE = 'spinner-parent--relative';
const CLASS_NAME_RELATIVE = "spinner-parent--relative";
const loadingDirective: Directive = {
mounted(el, binding) {
@@ -32,15 +32,12 @@ const loadingDirective: Directive = {
const options = getOptions(binding);
if (options && instance?.component) {
try {
Object.keys(options).forEach((key) => {
Object.keys(options).forEach(key => {
instance.component.props[key] = options[key];
});
instance.component.update();
} catch (error) {
console.error(
'Failed to update loading component in directive:',
error,
);
console.error("Failed to update loading component in directive:", error);
}
}
},
@@ -49,7 +46,7 @@ const loadingDirective: Directive = {
function getOptions(binding: DirectiveBinding) {
if (binding.value === undefined) {
return { spinning: true };
} else if (typeof binding.value === 'boolean') {
} else if (typeof binding.value === "boolean") {
return { spinning: binding.value };
} else {
return { ...binding.value };
@@ -78,15 +75,12 @@ const spinningDirective: Directive = {
const options = getOptions(binding);
if (options && instance?.component) {
try {
Object.keys(options).forEach((key) => {
Object.keys(options).forEach(key => {
instance.component.props[key] = options[key];
});
instance.component.update();
} catch (error) {
console.error(
'Failed to update spinner component in directive:',
error,
);
console.error("Failed to update spinner component in directive:", error);
}
}
},
@@ -104,12 +98,9 @@ type loadingDirectiveParams = {
* @param app
* @param params
*/
export function registerLoadingDirective(
app: App,
params?: loadingDirectiveParams,
) {
export function registerLoadingDirective(app: App, params?: loadingDirectiveParams) {
// 注入一个样式供指令使用,确保容器是相对定位
const style = document.createElement('style');
const style = document.createElement("style");
style.id = CLASS_NAME_RELATIVE;
style.innerHTML = `
.${CLASS_NAME_RELATIVE} {
@@ -118,15 +109,9 @@ export function registerLoadingDirective(
`;
document.head.append(style);
if (params?.loading !== false) {
app.directive(
isString(params?.loading) ? params.loading : 'loading',
loadingDirective,
);
app.directive(isString(params?.loading) ? params.loading : "loading", loadingDirective);
}
if (params?.spinning !== false) {
app.directive(
isString(params?.spinning) ? params.spinning : 'spinning',
spinningDirective,
);
app.directive(isString(params?.spinning) ? params.spinning : "spinning", spinningDirective);
}
}
@@ -1,3 +1,3 @@
export * from './directive';
export { default as Loading } from './loading.vue';
export { default as Spinner } from './spinner.vue';
export * from "./directive";
export { default as Loading } from "./loading.vue";
export { default as Spinner } from "./spinner.vue";
@@ -1,7 +1,9 @@
<script lang="ts" setup>
import { VbenLoading } from '/@/vben/shadcn-ui';
import { cn } from '/@/vben/shared/utils';
import { VbenLoading } from "/@/vben/shadcn-ui";
import { cn } from "/@/vben/shared/utils";
defineOptions({
name: "VbenLoading",
});
interface LoadingProps {
class?: string;
/**
@@ -20,17 +22,13 @@ interface LoadingProps {
text?: string;
}
defineOptions({ name: 'Loading' });
defineOptions({ name: "Loading" });
const props = defineProps<LoadingProps>();
</script>
<template>
<div :class="cn('relative min-h-20', props.class)">
<slot></slot>
<VbenLoading
:min-loading-time="props.minLoadingTime"
:spinning="props.spinning"
:text="props.text"
>
<VbenLoading :min-loading-time="props.minLoadingTime" :spinning="props.spinning" :text="props.text">
<template v-if="$slots.icon" #icon>
<slot name="icon"></slot>
</template>
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { VbenSpinner } from '/@/vben/shadcn-ui';
import { cn } from '/@/vben/shared/utils';
import { VbenSpinner } from "/@/vben/shadcn-ui";
import { cn } from "/@/vben/shared/utils";
interface SpinnerProps {
class?: string;
@@ -14,15 +14,12 @@ interface SpinnerProps {
*/
spinning?: boolean;
}
defineOptions({ name: 'Spinner' });
defineOptions({ name: "Spinner" });
const props = defineProps<SpinnerProps>();
</script>
<template>
<div :class="cn('relative min-h-20', props.class)">
<slot></slot>
<VbenSpinner
:min-loading-time="props.minLoadingTime"
:spinning="props.spinning"
/>
<VbenSpinner :min-loading-time="props.minLoadingTime" :spinning="props.spinning" />
</div>
</template>
@@ -1,89 +1,89 @@
import { mount } from '@vue/test-utils';
import { mount } from "@vue/test-utils";
import { describe, expect, it } from 'vitest';
import { describe, expect, it } from "vitest";
import { Page } from '..';
import { Page } from "..";
describe('page.vue', () => {
it('renders title when passed', () => {
describe("page.vue", () => {
it("renders title when passed", () => {
const wrapper = mount(Page, {
props: {
title: 'Test Title',
title: "Test Title",
},
});
expect(wrapper.text()).toContain('Test Title');
expect(wrapper.text()).toContain("Test Title");
});
it('renders description when passed', () => {
it("renders description when passed", () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
description: "Test Description",
},
});
expect(wrapper.text()).toContain('Test Description');
expect(wrapper.text()).toContain("Test Description");
});
it('renders default slot content', () => {
it("renders default slot content", () => {
const wrapper = mount(Page, {
slots: {
default: '<p>Default Slot Content</p>',
default: "<p>Default Slot Content</p>",
},
});
expect(wrapper.html()).toContain('<p>Default Slot Content</p>');
expect(wrapper.html()).toContain("<p>Default Slot Content</p>");
});
it('renders footer slot when showFooter is true', () => {
it("renders footer slot when showFooter is true", () => {
const wrapper = mount(Page, {
props: {
showFooter: true,
},
slots: {
footer: '<p>Footer Slot Content</p>',
footer: "<p>Footer Slot Content</p>",
},
});
expect(wrapper.html()).toContain('<p>Footer Slot Content</p>');
expect(wrapper.html()).toContain("<p>Footer Slot Content</p>");
});
it('applies the custom contentClass', () => {
it("applies the custom contentClass", () => {
const wrapper = mount(Page, {
props: {
contentClass: 'custom-class',
contentClass: "custom-class",
},
});
const contentDiv = wrapper.find('.p-4');
expect(contentDiv.classes()).toContain('custom-class');
const contentDiv = wrapper.find(".p-4");
expect(contentDiv.classes()).toContain("custom-class");
});
it('does not render title slot if title prop is provided', () => {
it("does not render title slot if title prop is provided", () => {
const wrapper = mount(Page, {
props: {
title: 'Test Title',
title: "Test Title",
},
slots: {
title: '<p>Title Slot Content</p>',
title: "<p>Title Slot Content</p>",
},
});
expect(wrapper.text()).toContain('Title Slot Content');
expect(wrapper.html()).not.toContain('Test Title');
expect(wrapper.text()).toContain("Title Slot Content");
expect(wrapper.html()).not.toContain("Test Title");
});
it('does not render description slot if description prop is provided', () => {
it("does not render description slot if description prop is provided", () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
description: "Test Description",
},
slots: {
description: '<p>Description Slot Content</p>',
description: "<p>Description Slot Content</p>",
},
});
expect(wrapper.text()).toContain('Description Slot Content');
expect(wrapper.html()).not.toContain('Test Description');
expect(wrapper.text()).toContain("Description Slot Content");
expect(wrapper.html()).not.toContain("Test Description");
});
});
@@ -1,2 +1,2 @@
export { default as Page } from './page.vue';
export * from './types';
export { default as Page } from "./page.vue";
export * from "./types";
@@ -1,15 +1,15 @@
<script setup lang="ts">
import type { StyleValue } from 'vue';
import type { StyleValue } from "vue";
import type { PageProps } from './types';
import type { PageProps } from "./types";
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { computed, nextTick, onMounted, ref, useTemplateRef } from "vue";
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '/@/vben/shared/constants';
import { cn } from '/@/vben/shared/utils';
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from "/@/vben/shared/constants";
import { cn } from "/@/vben/shared/utils";
defineOptions({
name: 'Page',
name: "Page",
});
const { autoContentHeight = false } = defineProps<PageProps>();
@@ -18,14 +18,14 @@ const headerHeight = ref(0);
const footerHeight = ref(0);
const shouldAutoHeight = ref(false);
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
const headerRef = useTemplateRef<HTMLDivElement>("headerRef");
const footerRef = useTemplateRef<HTMLDivElement>("footerRef");
const contentStyle = computed<StyleValue>(() => {
if (autoContentHeight) {
return {
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px)`,
overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
overflowY: shouldAutoHeight.value ? "auto" : "unset",
};
}
return {};
@@ -50,22 +50,7 @@ onMounted(() => {
<template>
<div class="relative">
<div
v-if="
description ||
$slots.description ||
title ||
$slots.title ||
$slots.extra
"
ref="headerRef"
:class="
cn(
'bg-card border-border relative flex items-end border-b px-6 py-4',
headerClass,
)
"
>
<div v-if="description || $slots.description || title || $slots.title || $slots.extra" ref="headerRef" :class="cn('bg-card border-border relative flex items-end border-b px-6 py-4', headerClass)">
<div class="flex-auto">
<slot name="title">
<div v-if="title" class="mb-2 flex text-lg font-semibold">
@@ -89,16 +74,7 @@ onMounted(() => {
<slot></slot>
</div>
<div
v-if="$slots.footer"
ref="footerRef"
:class="
cn(
'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
footerClass,
)
"
>
<div v-if="$slots.footer" ref="footerRef" :class="cn('bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4', footerClass)">
<slot name="footer"></slot>
</div>
</div>
@@ -1 +1 @@
export { default as VResize } from './resize.vue';
export { default as VResize } from "./resize.vue";
@@ -3,16 +3,7 @@
* This components is refactored from vue-drag-resize: https://github.com/kirillmurashov/vue-drag-resize
*/
import {
computed,
getCurrentInstance,
nextTick,
onBeforeUnmount,
onMounted,
ref,
toRefs,
watch,
} from 'vue';
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue";
const props = defineProps({
stickSize: {
@@ -87,14 +78,14 @@ const props = defineProps({
type: [String, Number],
default: 200,
validator(val: number) {
return typeof val === 'string' ? val === 'auto' : val >= 0;
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
h: {
type: [String, Number],
default: 200,
validator(val: number) {
return typeof val === 'string' ? val === 'auto' : val >= 0;
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
minw: {
@@ -115,21 +106,21 @@ const props = defineProps({
type: Number,
default: 0,
validator(val: number) {
return typeof val === 'number';
return typeof val === "number";
},
},
y: {
type: Number,
default: 0,
validator(val: number) {
return typeof val === 'number';
return typeof val === "number";
},
},
z: {
type: [String, Number],
default: 'auto',
default: "auto",
validator(val: number) {
return typeof val === 'string' ? val === 'auto' : val >= 0;
return typeof val === "string" ? val === "auto" : val >= 0;
},
},
dragHandle: {
@@ -141,45 +132,37 @@ const props = defineProps({
default: null,
},
sticks: {
type: Array<'bl' | 'bm' | 'br' | 'ml' | 'mr' | 'tl' | 'tm' | 'tr'>,
type: Array<"bl" | "bm" | "br" | "ml" | "mr" | "tl" | "tm" | "tr">,
default() {
return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'];
return ["tl", "tm", "tr", "mr", "br", "bm", "bl", "ml"];
},
},
axis: {
type: String,
default: 'both',
default: "both",
validator(val: string) {
return ['both', 'none', 'x', 'y'].includes(val);
return ["both", "none", "x", "y"].includes(val);
},
},
contentClass: {
type: String,
required: false,
default: '',
default: "",
},
});
const emit = defineEmits([
'clicked',
'dragging',
'dragstop',
'resizing',
'resizestop',
'activated',
'deactivated',
]);
const emit = defineEmits(["clicked", "dragging", "dragstop", "resizing", "resizestop", "activated", "deactivated"]);
const styleMapping = {
y: {
t: 'top',
m: 'marginTop',
b: 'bottom',
t: "top",
m: "marginTop",
b: "bottom",
},
x: {
l: 'left',
m: 'marginLeft',
r: 'right',
l: "left",
m: "marginLeft",
r: "right",
},
};
@@ -275,13 +258,7 @@ const rect = computed(() => ({
height: Math.round(height.value),
}));
const saveDimensionsBeforeMove = ({
pointerX,
pointerY,
}: {
pointerX: number;
pointerY: number;
}) => {
const saveDimensionsBeforeMove = ({ pointerX, pointerY }: { pointerX: number; pointerY: number }) => {
dimensionsBeforeMove.value.pointerX = pointerX;
dimensionsBeforeMove.value.pointerY = pointerY;
@@ -296,10 +273,7 @@ const saveDimensionsBeforeMove = ({
aspectFactor.value = width.value / height.value;
};
const sideCorrectionByLimit = (
limit: { max: number; min: number },
current: number,
) => {
const sideCorrectionByLimit = (limit: { max: number; min: number }, current: number) => {
let value = current;
if (limit.min !== null && current < limit.min) {
@@ -311,12 +285,7 @@ const sideCorrectionByLimit = (
return value;
};
const rectCorrectionByLimit = (rect: {
newBottom: number;
newLeft: number;
newRight: number;
newTop: number;
}) => {
const rectCorrectionByLimit = (rect: { newBottom: number; newLeft: number; newRight: number; newTop: number }) => {
// const { limits } = this;
let { newRight, newLeft, newBottom, newTop } = rect;
@@ -328,10 +297,7 @@ const rectCorrectionByLimit = (rect: {
newLeft = sideCorrectionByLimit(limits.value.left as RectRange, newLeft);
newRight = sideCorrectionByLimit(limits.value.right as RectRange, newRight);
newTop = sideCorrectionByLimit(limits.value.top as RectRange, newTop);
newBottom = sideCorrectionByLimit(
limits.value.bottom as RectRange,
newBottom,
);
newBottom = sideCorrectionByLimit(limits.value.bottom as RectRange, newBottom);
return {
newLeft,
@@ -341,24 +307,19 @@ const rectCorrectionByLimit = (rect: {
};
};
const rectCorrectionByAspectRatio = (rect: {
newBottom: number;
newLeft: number;
newRight: number;
newTop: number;
}) => {
const rectCorrectionByAspectRatio = (rect: { newBottom: number; newLeft: number; newRight: number; newTop: number }) => {
let { newLeft, newRight, newTop, newBottom } = rect;
// const { parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove } = this;
let newWidth = parentWidth.value! - newLeft - newRight;
let newHeight = parentHeight.value! - newTop - newBottom;
if (currentStick.value![1] === 'm') {
if (currentStick.value![1] === "m") {
const deltaHeight = newHeight - dimensionsBeforeMove.value.height;
newLeft -= (deltaHeight * aspectFactor.value!) / 2;
newRight -= (deltaHeight * aspectFactor.value!) / 2;
} else if (currentStick.value![0] === 'm') {
} else if (currentStick.value![0] === "m") {
const deltaWidth = newWidth - dimensionsBeforeMove.value.width;
newTop -= deltaWidth / aspectFactor.value! / 2;
@@ -366,7 +327,7 @@ const rectCorrectionByAspectRatio = (rect: {
} else if (newWidth / newHeight > aspectFactor.value!) {
newWidth = aspectFactor.value! * newHeight;
if (currentStick.value![1] === 'l') {
if (currentStick.value![1] === "l") {
newLeft = parentWidth.value! - newRight - newWidth;
} else {
newRight = parentWidth.value! - newLeft - newWidth;
@@ -374,7 +335,7 @@ const rectCorrectionByAspectRatio = (rect: {
} else {
newHeight = newWidth / aspectFactor.value!;
if (currentStick.value![0] === 't') {
if (currentStick.value![0] === "t") {
newTop = parentHeight.value! - newBottom - newHeight;
} else {
newBottom = parentHeight.value! - newTop - newHeight;
@@ -390,22 +351,17 @@ const stickMove = (delta: { x: number; y: number }) => {
let newLeft = dimensionsBeforeMove.value.left;
let newRight = dimensionsBeforeMove.value.right;
switch (currentStick.value![0]) {
case 'b': {
case "b": {
newBottom = dimensionsBeforeMove.value.bottom + delta.y;
if (snapToGrid.value) {
newBottom =
(parentHeight.value as number) -
Math.round(
((parentHeight.value as number) - newBottom) / gridY.value,
) *
gridY.value;
newBottom = (parentHeight.value as number) - Math.round(((parentHeight.value as number) - newBottom) / gridY.value) * gridY.value;
}
break;
}
case 't': {
case "t": {
newTop = dimensionsBeforeMove.value.top - delta.y;
if (snapToGrid.value) {
@@ -420,7 +376,7 @@ const stickMove = (delta: { x: number; y: number }) => {
}
switch (currentStick.value![1]) {
case 'l': {
case "l": {
newLeft = dimensionsBeforeMove.value.left - delta.x;
if (snapToGrid.value) {
@@ -430,14 +386,11 @@ const stickMove = (delta: { x: number; y: number }) => {
break;
}
case 'r': {
case "r": {
newRight = dimensionsBeforeMove.value.right + delta.x;
if (snapToGrid.value) {
newRight =
(parentWidth.value as number) -
Math.round(((parentWidth.value as number) - newRight) / gridX.value) *
gridX.value;
newRight = (parentWidth.value as number) - Math.round(((parentWidth.value as number) - newRight) / gridX.value) * gridX.value;
}
break;
@@ -468,7 +421,7 @@ const stickMove = (delta: { x: number; y: number }) => {
top.value = newTop;
bottom.value = newBottom;
emit('resizing', rect.value);
emit("resizing", rect.value);
};
const stickUp = () => {
@@ -498,8 +451,8 @@ const stickUp = () => {
bottom: { min: null, max: null },
};
emit('resizing', rect.value);
emit('resizestop', rect.value);
emit("resizing", rect.value);
emit("resizestop", rect.value);
};
const calcDragLimitation = () => {
@@ -546,40 +499,24 @@ const calcResizeLimits = () => {
if (aspectRatio.value) {
const aspectLimits = {
left: {
min:
left.value! -
Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
max:
left.value! +
((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
min: left.value! - Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
max: left.value! + ((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
},
right: {
min:
right.value! -
Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
max:
right.value! +
((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
min: right.value! - Math.min(top.value!, bottom.value!) * aspectFactor.value! * 2,
max: right.value! + ((height.value - minh.value!) / 2) * aspectFactor.value! * 2,
},
top: {
min:
top.value! -
(Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
max:
top.value! +
((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
min: top.value! - (Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
max: top.value! + ((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
},
bottom: {
min:
bottom.value! -
(Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
max:
bottom.value! +
((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
min: bottom.value! - (Math.min(left.value!, right.value!) / aspectFactor.value!) * 2,
max: bottom.value! + ((width.value - minw.value) / 2 / aspectFactor.value!) * 2,
},
};
if (currentStick.value![0] === 'm') {
if (currentStick.value![0] === "m") {
limits.left = {
min: Math.max(limits.left.min!, aspectLimits.left.min),
max: Math.min(limits.left.max, aspectLimits.left.max),
@@ -588,7 +525,7 @@ const calcResizeLimits = () => {
min: Math.max(limits.right.min!, aspectLimits.right.min),
max: Math.min(limits.right.max, aspectLimits.right.max),
};
} else if (currentStick.value![1] === 'm') {
} else if (currentStick.value![1] === "m") {
limits.top = {
min: Math.max(limits.top.min!, aspectLimits.top.min),
max: Math.min(limits.top.max, aspectLimits.top.max),
@@ -610,8 +547,8 @@ const positionStyle = computed(() => ({
}));
const sizeStyle = computed(() => ({
width: w.value === 'auto' ? 'auto' : `${width.value}px`,
height: h.value === 'auto' ? 'auto' : `${height.value}px`,
width: w.value === "auto" ? "auto" : `${width.value}px`,
height: h.value === "auto" ? "auto" : `${height.value}px`,
}));
const stickStyles = computed(() => (stick: string) => {
@@ -619,12 +556,8 @@ const stickStyles = computed(() => (stick: string) => {
width: `${stickSize.value / parentScaleX.value}px`,
height: `${stickSize.value / parentScaleY.value}px`,
};
stickStyle[
styleMapping.y[stick[0] as 'b' | 'm' | 't'] as 'height' | 'width'
] = `${stickSize.value / parentScaleX.value / -2}px`;
stickStyle[
styleMapping.x[stick[1] as 'l' | 'm' | 'r'] as 'height' | 'width'
] = `${stickSize.value / parentScaleX.value / -2}px`;
stickStyle[styleMapping.y[stick[0] as "b" | "m" | "t"] as "height" | "width"] = `${stickSize.value / parentScaleX.value / -2}px`;
stickStyle[styleMapping.x[stick[1] as "l" | "m" | "r"] as "height" | "width"] = `${stickSize.value / parentScaleX.value / -2}px`;
return stickStyle;
});
@@ -639,17 +572,9 @@ const bodyMove = (delta: { x: number; y: number }) => {
let alignLeft = true;
let diffT = newTop - Math.floor(newTop / gridY.value) * gridY.value;
let diffB =
(parentHeight.value as number) -
newBottom -
Math.floor(((parentHeight.value as number) - newBottom) / gridY.value) *
gridY.value;
let diffB = (parentHeight.value as number) - newBottom - Math.floor(((parentHeight.value as number) - newBottom) / gridY.value) * gridY.value;
let diffL = newLeft - Math.floor(newLeft / gridX.value) * gridX.value;
let diffR =
(parentWidth.value as number) -
newRight -
Math.floor(((parentWidth.value as number) - newRight) / gridX.value) *
gridX.value;
let diffR = (parentWidth.value as number) - newRight - Math.floor(((parentWidth.value as number) - newRight) / gridX.value) * gridX.value;
if (diffT > gridY.value / 2) {
diffT -= gridY.value;
@@ -677,20 +602,15 @@ const bodyMove = (delta: { x: number; y: number }) => {
newRight = (parentWidth.value as number) - width.value - newLeft;
}
({
newLeft: left.value,
newRight: right.value,
newTop: top.value,
newBottom: bottom.value,
} = rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));
({ newLeft: left.value, newRight: right.value, newTop: top.value, newBottom: bottom.value } = rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));
emit('dragging', rect.value);
emit("dragging", rect.value);
};
const bodyUp = () => {
bodyDrag.value = false;
emit('dragging', rect.value);
emit('dragstop', rect.value);
emit("dragging", rect.value);
emit("dragstop", rect.value);
// dimensionsBeforeMove.value = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };
Object.assign(dimensionsBeforeMove.value, {
@@ -710,11 +630,7 @@ const bodyUp = () => {
};
};
const stickDown = (
stick: string,
ev: { pageX: any; pageY: any; touches?: any },
force = false,
) => {
const stickDown = (stick: string, ev: { pageX: any; pageY: any; touches?: any }, force = false) => {
if ((!isResizable.value || !active.value) && !force) {
return;
}
@@ -753,15 +669,15 @@ const move = (ev: MouseEvent & TouchEvent) => {
if (bodyDrag.value) {
switch (axis.value) {
case 'none': {
case "none": {
return;
}
case 'x': {
case "x": {
delta.y = 0;
break;
}
case 'y': {
case "y": {
delta.x = 0;
break;
@@ -789,15 +705,15 @@ const deselect = () => {
const domEvents = ref(
new Map([
['mousedown', deselect],
['mouseleave', up],
['mousemove', move],
['mouseup', up],
['touchcancel', up],
['touchend', up],
['touchmove', move],
['touchstart', up],
]),
["mousedown", deselect],
["mouseleave", up],
["mousemove", move],
["mouseup", up],
["touchcancel", up],
["touchend", up],
["touchmove", move],
["touchstart", up],
])
);
const container = ref<HTMLDivElement>();
@@ -812,33 +728,21 @@ onMounted(() => {
left.value = x.value;
top.value = y.value;
right.value = (parentWidth.value -
(w.value === 'auto' ? container.value!.scrollWidth : (w.value as number)) -
left.value) as number;
bottom.value = (parentHeight.value -
(h.value === 'auto' ? container.value!.scrollHeight : (h.value as number)) -
top.value) as number;
right.value = (parentWidth.value - (w.value === "auto" ? container.value!.scrollWidth : (w.value as number)) - left.value) as number;
bottom.value = (parentHeight.value - (h.value === "auto" ? container.value!.scrollHeight : (h.value as number)) - top.value) as number;
addEvents(domEvents.value);
if (dragHandle.value) {
[...($el?.querySelectorAll(dragHandle.value) || [])].forEach(
(dragHandle) => {
(dragHandle as HTMLElement).dataset.dragHandle = String(
currentInstance?.uid,
);
},
);
[...($el?.querySelectorAll(dragHandle.value) || [])].forEach(dragHandle => {
(dragHandle as HTMLElement).dataset.dragHandle = String(currentInstance?.uid);
});
}
if (dragCancel.value) {
[...($el?.querySelectorAll(dragCancel.value) || [])].forEach(
(cancelHandle) => {
(cancelHandle as HTMLElement).dataset.dragCancel = String(
currentInstance?.uid,
);
},
);
[...($el?.querySelectorAll(dragCancel.value) || [])].forEach(cancelHandle => {
(cancelHandle as HTMLElement).dataset.dragCancel = String(currentInstance?.uid);
});
}
});
@@ -857,25 +761,17 @@ const bodyDown = (ev: MouseEvent & TouchEvent) => {
return;
}
emit('clicked', ev);
emit("clicked", ev);
if (!active.value) {
return;
}
if (
dragHandle.value &&
(target! as HTMLElement).dataset.dragHandle !==
getCurrentInstance()?.uid.toString()
) {
if (dragHandle.value && (target! as HTMLElement).dataset.dragHandle !== getCurrentInstance()?.uid.toString()) {
return;
}
if (
dragCancel.value &&
(target! as HTMLElement).dataset.dragCancel ===
getCurrentInstance()?.uid.toString()
) {
if (dragCancel.value && (target! as HTMLElement).dataset.dragCancel === getCurrentInstance()?.uid.toString()) {
return;
}
@@ -903,31 +799,31 @@ const bodyDown = (ev: MouseEvent & TouchEvent) => {
watch(
() => active.value,
(isActive) => {
isActive => {
if (isActive) {
emit('activated');
emit("activated");
} else {
emit('deactivated');
emit("deactivated");
}
},
}
);
watch(
() => isActive.value,
(val) => {
val => {
active.value = val;
},
{ immediate: true },
{ immediate: true }
);
watch(
() => z.value,
(val) => {
if ((val as number) >= 0 || val === 'auto') {
val => {
if ((val as number) >= 0 || val === "auto") {
zIndex.value = val as number;
}
},
{ immediate: true },
{ immediate: true }
);
watch(
@@ -939,14 +835,13 @@ watch(
const delta = oldVal - newVal;
bodyDown({ pageX: left.value!, pageY: top.value! } as MouseEvent &
TouchEvent);
bodyDown({ pageX: left.value!, pageY: top.value! } as MouseEvent & TouchEvent);
bodyMove({ x: delta, y: 0 });
nextTick(() => {
bodyUp();
});
},
}
);
watch(
@@ -958,14 +853,13 @@ watch(
const delta = oldVal - newVal;
bodyDown({ pageX: left.value, pageY: top.value } as MouseEvent &
TouchEvent);
bodyDown({ pageX: left.value, pageY: top.value } as MouseEvent & TouchEvent);
bodyMove({ x: 0, y: delta });
nextTick(() => {
bodyUp();
});
},
}
);
watch(
@@ -975,20 +869,16 @@ watch(
return;
}
const stick = 'mr';
const stick = "mr";
const delta = (oldVal as number) - (newVal as number);
stickDown(
stick,
{ pageX: right.value, pageY: top.value! + height.value / 2 },
true,
);
stickDown(stick, { pageX: right.value, pageY: top.value! + height.value / 2 }, true);
stickMove({ x: delta, y: 0 });
nextTick(() => {
stickUp();
});
},
}
);
watch(
@@ -998,36 +888,32 @@ watch(
return;
}
const stick = 'bm';
const stick = "bm";
const delta = (oldVal as number) - (newVal as number);
stickDown(
stick,
{ pageX: left.value! + width.value / 2, pageY: bottom.value },
true,
);
stickDown(stick, { pageX: left.value! + width.value / 2, pageY: bottom.value }, true);
stickMove({ x: 0, y: delta });
nextTick(() => {
stickUp();
});
},
}
);
watch(
() => parentW.value,
(val) => {
val => {
right.value = val - width.value - left.value!;
parentWidth.value = val;
},
}
);
watch(
() => parentH.value,
(val) => {
val => {
bottom.value = val - height.value - top.value!;
parentHeight.value = val;
},
}
);
</script>
@@ -1049,12 +935,8 @@ watch(
:class="[`resize-stick-${stick}`, isResizable ? '' : 'not-resizable']"
:style="stickStyles(stick)"
class="resize-stick"
@mousedown.stop.prevent="
stickDown(stick, $event as TouchEvent & MouseEvent)
"
@touchstart.stop.prevent="
stickDown(stick, $event as TouchEvent & MouseEvent)
"
@mousedown.stop.prevent="stickDown(stick, $event as TouchEvent & MouseEvent)"
@touchstart.stop.prevent="stickDown(stick, $event as TouchEvent & MouseEvent)"
></div>
</div>
</template>
@@ -1072,7 +954,7 @@ watch(
box-sizing: border-box;
width: 100%;
height: 100%;
content: '';
content: "";
outline: 1px dashed #d6d6d6;
}
@@ -1,18 +1,15 @@
import type { ComputedRef, Directive } from 'vue';
import type { ComputedRef, Directive } from "vue";
import { useTippy } from 'vue-tippy';
import { useTippy } from "vue-tippy";
export default function useTippyDirective(isDark: ComputedRef<boolean>) {
const directive: Directive = {
mounted(el, binding, vnode) {
const opts =
typeof binding.value === 'string'
? { content: binding.value }
: binding.value || {};
const opts = typeof binding.value === "string" ? { content: binding.value } : binding.value || {};
const modifiers = Object.keys(binding.modifiers || {});
const placement = modifiers.find((modifier) => modifier !== 'arrow');
const withArrow = modifiers.includes('arrow');
const placement = modifiers.find(modifier => modifier !== "arrow");
const withArrow = modifiers.includes("arrow");
if (placement) {
opts.placement = opts.placement || placement;
@@ -52,13 +49,13 @@ export default function useTippyDirective(isDark: ComputedRef<boolean>) {
};
}
if (el.getAttribute('title') && !opts.content) {
opts.content = el.getAttribute('title');
el.removeAttribute('title');
if (el.getAttribute("title") && !opts.content) {
opts.content = el.getAttribute("title");
el.removeAttribute("title");
}
if (el.getAttribute('content') && !opts.content) {
opts.content = el.getAttribute('content');
if (el.getAttribute("content") && !opts.content) {
opts.content = el.getAttribute("content");
}
useTippy(el, opts);
@@ -72,21 +69,15 @@ export default function useTippyDirective(isDark: ComputedRef<boolean>) {
},
updated(el, binding) {
const opts =
typeof binding.value === 'string'
? { content: binding.value, theme: isDark.value ? '' : 'light' }
: Object.assign(
{ theme: isDark.value ? '' : 'light' },
binding.value,
);
const opts = typeof binding.value === "string" ? { content: binding.value, theme: isDark.value ? "" : "light" } : Object.assign({ theme: isDark.value ? "" : "light" }, binding.value);
if (el.getAttribute('title') && !opts.content) {
opts.content = el.getAttribute('title');
el.removeAttribute('title');
if (el.getAttribute("title") && !opts.content) {
opts.content = el.getAttribute("title");
el.removeAttribute("title");
}
if (el.getAttribute('content') && !opts.content) {
opts.content = el.getAttribute('content');
if (el.getAttribute("content") && !opts.content) {
opts.content = el.getAttribute("content");
}
if (el.$tippy) {
@@ -1,33 +1,27 @@
import type { DefaultProps, Props } from 'tippy.js';
import type { DefaultProps, Props } from "tippy.js";
import type { App, SetupContext } from 'vue';
import type { App, SetupContext } from "vue";
import { h, watchEffect } from 'vue';
import { setDefaultProps, Tippy as TippyComponent } from 'vue-tippy';
import { h, watchEffect } from "vue";
import { setDefaultProps, Tippy as TippyComponent } from "vue-tippy";
import { usePreferences } from '/@/vben/preferences';
import { usePreferences } from "/@/vben/preferences";
import useTippyDirective from './directive';
import useTippyDirective from "./directive";
import 'tippy.js/dist/tippy.css';
import 'tippy.js/dist/backdrop.css';
import 'tippy.js/themes/light.css';
import 'tippy.js/animations/scale.css';
import 'tippy.js/animations/shift-toward.css';
import 'tippy.js/animations/shift-away.css';
import 'tippy.js/animations/perspective.css';
import "tippy.js/dist/tippy.css";
import "tippy.js/dist/backdrop.css";
import "tippy.js/themes/light.css";
import "tippy.js/animations/scale.css";
import "tippy.js/animations/shift-toward.css";
import "tippy.js/animations/shift-away.css";
import "tippy.js/animations/perspective.css";
const { isDark } = usePreferences();
export type TippyProps = Partial<
Props & {
animation?:
| 'fade'
| 'perspective'
| 'scale'
| 'shift-away'
| 'shift-toward'
| boolean;
theme?: 'auto' | 'dark' | 'light';
animation?: "fade" | "perspective" | "scale" | "shift-away" | "shift-toward" | boolean;
theme?: "auto" | "dark" | "light";
}
>;
@@ -35,25 +29,25 @@ export function initTippy(app: App<Element>, options?: DefaultProps) {
setDefaultProps({
allowHTML: true,
delay: [500, 200],
theme: isDark.value ? '' : 'light',
theme: isDark.value ? "" : "light",
...options,
});
if (!options || !Reflect.has(options, 'theme') || options.theme === 'auto') {
if (!options || !Reflect.has(options, "theme") || options.theme === "auto") {
watchEffect(() => {
setDefaultProps({ theme: isDark.value ? '' : 'light' });
setDefaultProps({ theme: isDark.value ? "" : "light" });
});
}
app.directive('tippy', useTippyDirective(isDark));
app.directive("tippy", useTippyDirective(isDark));
}
export const Tippy = (props: any, { attrs, slots }: SetupContext) => {
let theme: string = (attrs.theme as string) ?? 'auto';
if (theme === 'auto') {
theme = isDark.value ? '' : 'light';
let theme: string = (attrs.theme as string) ?? "auto";
if (theme === "auto") {
theme = isDark.value ? "" : "light";
}
if (theme === 'dark') {
theme = '';
if (theme === "dark") {
theme = "";
}
return h(
TippyComponent,
@@ -62,6 +56,6 @@ export const Tippy = (props: any, { attrs, slots }: SetupContext) => {
...attrs,
theme,
},
slots,
slots
);
};