Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -336,7 +336,7 @@ function autofocus() {
|
||||
>
|
||||
<VbenRenderContent
|
||||
:content="customContentRender[name]"
|
||||
v-bind="{ ...renderSlotProps, $formContext: slotProps }"
|
||||
v-bind="{ ...renderSlotProps, formContext: slotProps }"
|
||||
/>
|
||||
</template>
|
||||
<!-- <slot></slot> -->
|
||||
|
@@ -47,6 +47,7 @@
|
||||
"vue": "catalog:",
|
||||
"vue-codemirror6": "1.3.4",
|
||||
"vue-json-pretty": "^2.4.0",
|
||||
"vue-json-viewer": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vue-tippy": "catalog:"
|
||||
},
|
||||
|
123
packages/effects/common-ui/src/components/count-to/count-to.vue
Normal file
123
packages/effects/common-ui/src/components/count-to/count-to.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CountToProps } from './types';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { isString } from '@vben-core/shared/utils';
|
||||
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core';
|
||||
|
||||
const props = withDefaults(defineProps<CountToProps>(), {
|
||||
startVal: 0,
|
||||
duration: 2000,
|
||||
separator: ',',
|
||||
decimal: '.',
|
||||
decimals: 0,
|
||||
delay: 0,
|
||||
transition: () => TransitionPresets.easeOutExpo,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['started', 'finished']);
|
||||
|
||||
const lastValue = ref(props.startVal);
|
||||
|
||||
onMounted(() => {
|
||||
lastValue.value = props.endVal;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.endVal,
|
||||
(val) => {
|
||||
lastValue.value = val;
|
||||
},
|
||||
);
|
||||
|
||||
const currentValue = useTransition(lastValue, {
|
||||
delay: computed(() => props.delay),
|
||||
duration: computed(() => props.duration),
|
||||
disabled: computed(() => props.disabled),
|
||||
transition: computed(() => {
|
||||
return isString(props.transition)
|
||||
? TransitionPresets[props.transition]
|
||||
: props.transition;
|
||||
}),
|
||||
onStarted() {
|
||||
emit('started');
|
||||
},
|
||||
onFinished() {
|
||||
emit('finished');
|
||||
},
|
||||
});
|
||||
|
||||
const numMain = computed(() => {
|
||||
const result = currentValue.value
|
||||
.toFixed(props.decimals)
|
||||
.split('.')[0]
|
||||
?.replaceAll(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return result;
|
||||
});
|
||||
|
||||
const numDec = computed(() => {
|
||||
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"
|
||||
>
|
||||
{{ 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"
|
||||
>
|
||||
{{ numDec }}
|
||||
</span>
|
||||
</div>
|
||||
<slot name="suffix">
|
||||
<div
|
||||
class="count-to-suffix"
|
||||
:style="suffixStyle"
|
||||
:class="suffixClass"
|
||||
v-if="suffix"
|
||||
>
|
||||
{{ suffix }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.count-to {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
&-prefix {
|
||||
// font-size: 1rem;
|
||||
}
|
||||
|
||||
&-suffix {
|
||||
// font-size: 1rem;
|
||||
}
|
||||
|
||||
&-main {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
// font-size: 1.5rem;
|
||||
|
||||
&-decimal {
|
||||
// font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,2 @@
|
||||
export { default as CountTo } from './count-to.vue';
|
||||
export * from './types';
|
53
packages/effects/common-ui/src/components/count-to/types.ts
Normal file
53
packages/effects/common-ui/src/components/count-to/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { CubicBezierPoints, EasingFunction } from '@vueuse/core';
|
||||
|
||||
import type { StyleValue } from 'vue';
|
||||
|
||||
import { TransitionPresets as TransitionPresetsData } from '@vueuse/core';
|
||||
|
||||
export type TransitionPresets = keyof typeof TransitionPresetsData;
|
||||
|
||||
export const TransitionPresetsKeys = Object.keys(
|
||||
TransitionPresetsData,
|
||||
) as TransitionPresets[];
|
||||
|
||||
export interface CountToProps {
|
||||
/** 初始值 */
|
||||
startVal?: number;
|
||||
/** 当前值 */
|
||||
endVal: number;
|
||||
/** 是否禁用动画 */
|
||||
disabled?: boolean;
|
||||
/** 延迟动画开始的时间 */
|
||||
delay?: number;
|
||||
/** 持续时间 */
|
||||
duration?: number;
|
||||
/** 小数位数 */
|
||||
decimals?: number;
|
||||
/** 小数点 */
|
||||
decimal?: string;
|
||||
/** 分隔符 */
|
||||
separator?: string;
|
||||
/** 前缀 */
|
||||
prefix?: string;
|
||||
/** 后缀 */
|
||||
suffix?: string;
|
||||
/** 过渡效果 */
|
||||
transition?: CubicBezierPoints | EasingFunction | TransitionPresets;
|
||||
/** 整数部分的类名 */
|
||||
mainClass?: string;
|
||||
/** 小数部分的类名 */
|
||||
decimalClass?: string;
|
||||
/** 前缀部分的类名 */
|
||||
prefixClass?: string;
|
||||
/** 后缀部分的类名 */
|
||||
suffixClass?: string;
|
||||
|
||||
/** 整数部分的样式 */
|
||||
mainStyle?: StyleValue;
|
||||
/** 小数部分的样式 */
|
||||
decimalStyle?: StyleValue;
|
||||
/** 前缀部分的样式 */
|
||||
prefixStyle?: StyleValue;
|
||||
/** 后缀部分的样式 */
|
||||
suffixStyle?: StyleValue;
|
||||
}
|
@@ -2,9 +2,11 @@ export * from './api-component';
|
||||
export * from './captcha';
|
||||
export * from './code-mirror';
|
||||
export * from './col-page';
|
||||
export * from './count-to';
|
||||
export * from './ellipsis-text';
|
||||
export * from './icon-picker';
|
||||
export * from './json-preview';
|
||||
export * from './json-viewer';
|
||||
export * from './markdown';
|
||||
export * from './page';
|
||||
export * from './resize';
|
||||
|
@@ -0,0 +1,3 @@
|
||||
export { default as JsonViewer } from './index.vue';
|
||||
|
||||
export * from './types';
|
@@ -0,0 +1,98 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SetupContext } from 'vue';
|
||||
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type {
|
||||
JsonViewerAction,
|
||||
JsonViewerProps,
|
||||
JsonViewerToggle,
|
||||
JsonViewerValue,
|
||||
} from './types';
|
||||
|
||||
import { computed, useAttrs } from 'vue';
|
||||
// @ts-ignore
|
||||
import VueJsonViewer from 'vue-json-viewer';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { isBoolean } from '@vben-core/shared/utils';
|
||||
|
||||
defineOptions({ name: 'JsonViewer' });
|
||||
|
||||
const props = withDefaults(defineProps<JsonViewerProps>(), {
|
||||
expandDepth: 1,
|
||||
copyable: false,
|
||||
sort: false,
|
||||
boxed: false,
|
||||
theme: 'default-json-theme',
|
||||
expanded: false,
|
||||
previewMode: false,
|
||||
showArrayIndex: true,
|
||||
showDoubleQuotes: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent];
|
||||
copied: [event: JsonViewerAction];
|
||||
keyClick: [key: string];
|
||||
toggle: [param: JsonViewerToggle];
|
||||
valueClick: [value: JsonViewerValue];
|
||||
}>();
|
||||
|
||||
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')) {
|
||||
return;
|
||||
}
|
||||
const param: JsonViewerValue = {
|
||||
path: '',
|
||||
value: '',
|
||||
depth: 0,
|
||||
el: event.target,
|
||||
};
|
||||
|
||||
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('click', event);
|
||||
}
|
||||
|
||||
const bindProps = computed<Recordable<any>>(() => {
|
||||
const copyable = {
|
||||
copyText: $t('ui.jsonViewer.copy'),
|
||||
copiedText: $t('ui.jsonViewer.copied'),
|
||||
timeout: 2000,
|
||||
...(isBoolean(props.copyable) ? {} : props.copyable),
|
||||
};
|
||||
|
||||
return {
|
||||
...props,
|
||||
...attrs,
|
||||
onCopied: (event: JsonViewerAction) => emit('copied', event),
|
||||
onKeyclick: (key: string) => emit('keyClick', key),
|
||||
onClick: (event: MouseEvent) => handleClick(event),
|
||||
copyable: props.copyable ? copyable : false,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VueJsonViewer v-bind="bindProps">
|
||||
<template #copy="slotProps">
|
||||
<slot name="copy" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
</VueJsonViewer>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@use './style.scss';
|
||||
</style>
|
@@ -0,0 +1,98 @@
|
||||
.default-json-theme {
|
||||
font-family: Consolas, Menlo, Courier, monospace;
|
||||
font-size: 14px;
|
||||
color: hsl(var(--foreground));
|
||||
white-space: nowrap;
|
||||
background: hsl(var(--background));
|
||||
|
||||
&.jv-container.boxed {
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.jv-ellipsis {
|
||||
display: inline-block;
|
||||
padding: 0 4px 2px;
|
||||
font-size: 0.9em;
|
||||
line-height: 0.9;
|
||||
color: hsl(var(--secondary-foreground));
|
||||
vertical-align: 2px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: hsl(var(--secondary));
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.jv-button {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.jv-key {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
}
|
||||
|
||||
.jv-item {
|
||||
&.jv-array {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
}
|
||||
|
||||
&.jv-boolean {
|
||||
color: hsl(var(--red-400));
|
||||
}
|
||||
|
||||
&.jv-function {
|
||||
color: hsl(var(--destructive-foreground));
|
||||
}
|
||||
|
||||
&.jv-number {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-number-float {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-number-integer {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-object {
|
||||
color: hsl(var(--accent-darker));
|
||||
}
|
||||
|
||||
&.jv-undefined {
|
||||
color: hsl(var(--secondary-foreground));
|
||||
}
|
||||
|
||||
&.jv-string {
|
||||
color: hsl(var(--primary));
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&.jv-container .jv-code {
|
||||
padding: 10px;
|
||||
|
||||
&.boxed:not(.open) {
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.jv-toggle {
|
||||
&::before {
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background: hsl(var(--accent-foreground));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
export interface JsonViewerProps {
|
||||
/** 要展示的结构数据 */
|
||||
value: any;
|
||||
/** 展开深度 */
|
||||
expandDepth?: number;
|
||||
/** 是否可复制 */
|
||||
copyable?: boolean;
|
||||
/** 是否排序 */
|
||||
sort?: boolean;
|
||||
/** 显示边框 */
|
||||
boxed?: boolean;
|
||||
/** 主题 */
|
||||
theme?: string;
|
||||
/** 是否展开 */
|
||||
expanded?: boolean;
|
||||
/** 时间格式化函数 */
|
||||
timeformat?: (time: Date | number | string) => string;
|
||||
/** 预览模式 */
|
||||
previewMode?: boolean;
|
||||
/** 显示数组索引 */
|
||||
showArrayIndex?: boolean;
|
||||
/** 显示双引号 */
|
||||
showDoubleQuotes?: boolean;
|
||||
}
|
||||
|
||||
export interface JsonViewerAction {
|
||||
action: string;
|
||||
text: string;
|
||||
trigger: HTMLElement;
|
||||
}
|
||||
|
||||
export interface JsonViewerValue {
|
||||
value: any;
|
||||
path: string;
|
||||
depth: number;
|
||||
el: HTMLElement;
|
||||
}
|
||||
|
||||
export interface JsonViewerToggle {
|
||||
/** 鼠标事件 */
|
||||
event: MouseEvent;
|
||||
/** 当前展开状态 */
|
||||
open: boolean;
|
||||
}
|
@@ -1,9 +1,12 @@
|
||||
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, onUnmounted, ref, unref, watch } from 'vue';
|
||||
|
||||
import { isFunction } from '@vben/utils';
|
||||
import { useMouseInElement } from '@vueuse/core';
|
||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { useElementHover } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
|
||||
@@ -15,15 +18,19 @@ export function useHoverToggle(
|
||||
refElement: Arrayable<MaybeElementRef>,
|
||||
delay: (() => number) | number = 500,
|
||||
) {
|
||||
const isOutsides: Array<Ref<boolean>> = [];
|
||||
const isHovers: Array<Ref<boolean>> = [];
|
||||
const value = ref(false);
|
||||
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
|
||||
const refs = Array.isArray(refElement) ? refElement : [refElement];
|
||||
refs.forEach((refEle) => {
|
||||
const listener = useMouseInElement(refEle, { handleOutside: true });
|
||||
isOutsides.push(listener.isOutside);
|
||||
const eleRef = computed(() => {
|
||||
const ele = unref(refEle);
|
||||
return ele instanceof Element ? ele : (ele?.$el as Element);
|
||||
});
|
||||
const isHover = useElementHover(eleRef);
|
||||
isHovers.push(isHover);
|
||||
});
|
||||
const isOutsideAll = computed(() => isOutsides.every((v) => v.value));
|
||||
const isOutsideAll = computed(() => isHovers.every((v) => !v.value));
|
||||
|
||||
function setValueDelay(val: boolean) {
|
||||
timer.value && clearTimeout(timer.value);
|
||||
|
@@ -21,6 +21,10 @@
|
||||
"./vxe-table": {
|
||||
"types": "./src/vxe-table/index.ts",
|
||||
"default": "./src/vxe-table/index.ts"
|
||||
},
|
||||
"./motion": {
|
||||
"types": "./src/motion/index.ts",
|
||||
"default": "./src/motion/index.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -34,6 +38,7 @@
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"@vueuse/motion": "catalog:",
|
||||
"echarts": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vxe-pc-ui": "catalog:",
|
||||
|
8
packages/effects/plugins/src/motion/index.ts
Normal file
8
packages/effects/plugins/src/motion/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './types';
|
||||
|
||||
export {
|
||||
MotionComponent as Motion,
|
||||
MotionDirective,
|
||||
MotionGroupComponent as MotionGroup,
|
||||
MotionPlugin,
|
||||
} from '@vueuse/motion';
|
26
packages/effects/plugins/src/motion/types.ts
Normal file
26
packages/effects/plugins/src/motion/types.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export const MotionPresets = [
|
||||
'fade',
|
||||
'fadeVisible',
|
||||
'fadeVisibleOnce',
|
||||
'rollBottom',
|
||||
'rollLeft',
|
||||
'rollRight',
|
||||
'rollTop',
|
||||
'rollVisibleBottom',
|
||||
'rollVisibleLeft',
|
||||
'rollVisibleRight',
|
||||
'rollVisibleTop',
|
||||
'pop',
|
||||
'popVisible',
|
||||
'popVisibleOnce',
|
||||
'slideBottom',
|
||||
'slideLeft',
|
||||
'slideRight',
|
||||
'slideTop',
|
||||
'slideVisibleBottom',
|
||||
'slideVisibleLeft',
|
||||
'slideVisibleRight',
|
||||
'slideVisibleTop',
|
||||
] as const;
|
||||
|
||||
export type MotionPreset = (typeof MotionPresets)[number];
|
@@ -25,6 +25,10 @@
|
||||
"placeholder": "Select an icon",
|
||||
"search": "Search icon..."
|
||||
},
|
||||
"jsonViewer": {
|
||||
"copy": "Copy",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"fallback": {
|
||||
"pageNotFound": "Oops! Page Not Found",
|
||||
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
|
||||
|
@@ -25,6 +25,10 @@
|
||||
"placeholder": "选择一个图标",
|
||||
"search": "搜索图标..."
|
||||
},
|
||||
"jsonViewer": {
|
||||
"copy": "复制",
|
||||
"copied": "已复制"
|
||||
},
|
||||
"fallback": {
|
||||
"pageNotFound": "哎呀!未找到页面",
|
||||
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
|
||||
|
Reference in New Issue
Block a user