mirror of
https://github.com/certd/certd.git
synced 2026-07-14 01:17:31 +08:00
chore: code format
This commit is contained in:
+5
-5
@@ -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";
|
||||
|
||||
+2
-2
@@ -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";
|
||||
|
||||
+33
-59
@@ -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>
|
||||
|
||||
+13
-26
@@ -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>
|
||||
|
||||
+30
-61
@@ -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>
|
||||
|
||||
+6
-6
@@ -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;
|
||||
|
||||
+5
-10
@@ -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>
|
||||
|
||||
+4
-4
@@ -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;
|
||||
|
||||
+21
-55
@@ -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 {
|
||||
/** 初始值 */
|
||||
|
||||
+15
-16
@@ -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";
|
||||
|
||||
+47
-122
@@ -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>
|
||||
|
||||
+30
-30
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user