Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
fbb0d641db
@ -1,4 +1,8 @@
|
|||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
|
event.node.res.setHeader(
|
||||||
|
'Access-Control-Allow-Origin',
|
||||||
|
event.headers.get('Origin') ?? '*',
|
||||||
|
);
|
||||||
if (event.method === 'OPTIONS') {
|
if (event.method === 'OPTIONS') {
|
||||||
event.node.res.statusCode = 204;
|
event.node.res.statusCode = 204;
|
||||||
event.node.res.statusMessage = 'No Content.';
|
event.node.res.statusMessage = 'No Content.';
|
||||||
|
@ -9,7 +9,8 @@ export default defineNitroConfig({
|
|||||||
cors: true,
|
cors: true,
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Credentials': 'true',
|
'Access-Control-Allow-Credentials': 'true',
|
||||||
'Access-Control-Allow-Headers': '*',
|
'Access-Control-Allow-Headers':
|
||||||
|
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
|
||||||
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Access-Control-Expose-Headers': '*',
|
'Access-Control-Expose-Headers': '*',
|
||||||
|
@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
|
|||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy } from '@vben/common-ui';
|
import { initTippy } from '@vben/common-ui';
|
||||||
|
import { MotionPlugin } from '@vben/plugins/motion';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
@ -49,6 +50,9 @@ async function bootstrap(namespace: string) {
|
|||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 配置Motion插件
|
||||||
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||||
|
|
||||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
||||||
- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),主要使用者的交流群。
|
- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[5群](https://qm.qq.com/q/ya9XrtbS6s),主要的使用者交流群。
|
||||||
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
|
@ -336,7 +336,7 @@ function autofocus() {
|
|||||||
>
|
>
|
||||||
<VbenRenderContent
|
<VbenRenderContent
|
||||||
:content="customContentRender[name]"
|
:content="customContentRender[name]"
|
||||||
v-bind="{ ...renderSlotProps, $formContext: slotProps }"
|
v-bind="{ ...renderSlotProps, formContext: slotProps }"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- <slot></slot> -->
|
<!-- <slot></slot> -->
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-codemirror6": "1.3.4",
|
"vue-codemirror6": "1.3.4",
|
||||||
"vue-json-pretty": "^2.4.0",
|
"vue-json-pretty": "^2.4.0",
|
||||||
|
"vue-json-viewer": "catalog:",
|
||||||
"vue-router": "catalog:",
|
"vue-router": "catalog:",
|
||||||
"vue-tippy": "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 './captcha';
|
||||||
export * from './code-mirror';
|
export * from './code-mirror';
|
||||||
export * from './col-page';
|
export * from './col-page';
|
||||||
|
export * from './count-to';
|
||||||
export * from './ellipsis-text';
|
export * from './ellipsis-text';
|
||||||
export * from './icon-picker';
|
export * from './icon-picker';
|
||||||
export * from './json-preview';
|
export * from './json-preview';
|
||||||
|
export * from './json-viewer';
|
||||||
export * from './markdown';
|
export * from './markdown';
|
||||||
export * from './page';
|
export * from './page';
|
||||||
export * from './resize';
|
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 { Arrayable, MaybeElementRef } from '@vueuse/core';
|
||||||
|
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import { computed, onUnmounted, ref, unref, watch } from 'vue';
|
||||||
|
|
||||||
import { isFunction } from '@vben/utils';
|
import { isFunction } from '@vben/utils';
|
||||||
import { useMouseInElement } from '@vueuse/core';
|
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
import { useElementHover } from '@vueuse/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
|
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
|
||||||
@ -15,15 +18,19 @@ export function useHoverToggle(
|
|||||||
refElement: Arrayable<MaybeElementRef>,
|
refElement: Arrayable<MaybeElementRef>,
|
||||||
delay: (() => number) | number = 500,
|
delay: (() => number) | number = 500,
|
||||||
) {
|
) {
|
||||||
const isOutsides: Array<Ref<boolean>> = [];
|
const isHovers: Array<Ref<boolean>> = [];
|
||||||
const value = ref(false);
|
const value = ref(false);
|
||||||
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
|
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
|
||||||
const refs = Array.isArray(refElement) ? refElement : [refElement];
|
const refs = Array.isArray(refElement) ? refElement : [refElement];
|
||||||
refs.forEach((refEle) => {
|
refs.forEach((refEle) => {
|
||||||
const listener = useMouseInElement(refEle, { handleOutside: true });
|
const eleRef = computed(() => {
|
||||||
isOutsides.push(listener.isOutside);
|
const ele = unref(refEle);
|
||||||
|
return ele instanceof Element ? ele : (ele?.$el as Element);
|
||||||
});
|
});
|
||||||
const isOutsideAll = computed(() => isOutsides.every((v) => v.value));
|
const isHover = useElementHover(eleRef);
|
||||||
|
isHovers.push(isHover);
|
||||||
|
});
|
||||||
|
const isOutsideAll = computed(() => isHovers.every((v) => !v.value));
|
||||||
|
|
||||||
function setValueDelay(val: boolean) {
|
function setValueDelay(val: boolean) {
|
||||||
timer.value && clearTimeout(timer.value);
|
timer.value && clearTimeout(timer.value);
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
"./vxe-table": {
|
"./vxe-table": {
|
||||||
"types": "./src/vxe-table/index.ts",
|
"types": "./src/vxe-table/index.ts",
|
||||||
"default": "./src/vxe-table/index.ts"
|
"default": "./src/vxe-table/index.ts"
|
||||||
|
},
|
||||||
|
"./motion": {
|
||||||
|
"types": "./src/motion/index.ts",
|
||||||
|
"default": "./src/motion/index.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -34,6 +38,7 @@
|
|||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
|
"@vueuse/motion": "catalog:",
|
||||||
"echarts": "catalog:",
|
"echarts": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vxe-pc-ui": "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",
|
"placeholder": "Select an icon",
|
||||||
"search": "Search icon..."
|
"search": "Search icon..."
|
||||||
},
|
},
|
||||||
|
"jsonViewer": {
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied"
|
||||||
|
},
|
||||||
"fallback": {
|
"fallback": {
|
||||||
"pageNotFound": "Oops! Page Not Found",
|
"pageNotFound": "Oops! Page Not Found",
|
||||||
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
|
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
|
||||||
|
@ -25,6 +25,10 @@
|
|||||||
"placeholder": "选择一个图标",
|
"placeholder": "选择一个图标",
|
||||||
"search": "搜索图标..."
|
"search": "搜索图标..."
|
||||||
},
|
},
|
||||||
|
"jsonViewer": {
|
||||||
|
"copy": "复制",
|
||||||
|
"copied": "已复制"
|
||||||
|
},
|
||||||
"fallback": {
|
"fallback": {
|
||||||
"pageNotFound": "哎呀!未找到页面",
|
"pageNotFound": "哎呀!未找到页面",
|
||||||
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
|
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
|
||||||
|
@ -22,23 +22,29 @@ export namespace AuthApi {
|
|||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>('/auth/login', data, {
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新accessToken
|
* 刷新accessToken
|
||||||
*/
|
*/
|
||||||
export async function refreshTokenApi() {
|
export async function refreshTokenApi() {
|
||||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
|
||||||
|
'/auth/refresh',
|
||||||
|
null,
|
||||||
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
export async function logoutApi() {
|
export async function logoutApi() {
|
||||||
return baseRequestClient.post('/auth/logout', {
|
return baseRequestClient.post('/auth/logout', null, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -111,3 +111,9 @@ export const requestClient = createRequestClient(apiURL, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
|
|
||||||
|
export interface PageFetchParams {
|
||||||
|
[key: string]: any;
|
||||||
|
pageNo?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
|
|||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy } from '@vben/common-ui';
|
import { initTippy } from '@vben/common-ui';
|
||||||
|
import { MotionPlugin } from '@vben/plugins/motion';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
@ -49,6 +50,9 @@ async function bootstrap(namespace: string) {
|
|||||||
// 配置@tanstack/vue-query
|
// 配置@tanstack/vue-query
|
||||||
app.use(VueQueryPlugin);
|
app.use(VueQueryPlugin);
|
||||||
|
|
||||||
|
// 配置Motion插件
|
||||||
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
@ -255,6 +255,33 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: 'Tippy',
|
title: 'Tippy',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'JsonViewer',
|
||||||
|
path: '/examples/json-viewer',
|
||||||
|
component: () => import('#/views/examples/json-viewer/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'tabler:json',
|
||||||
|
title: 'JsonViewer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Motion',
|
||||||
|
path: '/examples/motion',
|
||||||
|
component: () => import('#/views/examples/motion/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:animation-play',
|
||||||
|
title: 'Motion',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CountTo',
|
||||||
|
path: '/examples/count-to',
|
||||||
|
component: () => import('#/views/examples/count-to/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:animation-play',
|
||||||
|
title: 'CountTo',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
178
playground/src/views/examples/count-to/index.vue
Normal file
178
playground/src/views/examples/count-to/index.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CountToProps, TransitionPresets } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
import { CountTo, Page, TransitionPresetsKeys } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = reactive<CountToProps & { transition: TransitionPresets }>({
|
||||||
|
decimal: '.',
|
||||||
|
decimals: 2,
|
||||||
|
decimalStyle: {
|
||||||
|
fontSize: 'small',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
delay: 0,
|
||||||
|
disabled: false,
|
||||||
|
duration: 2000,
|
||||||
|
endVal: 100_000,
|
||||||
|
mainStyle: {
|
||||||
|
color: 'hsl(var(--primary))',
|
||||||
|
fontSize: 'xx-large',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
prefix: '¥',
|
||||||
|
prefixStyle: {
|
||||||
|
paddingRight: '0.5rem',
|
||||||
|
},
|
||||||
|
separator: ',',
|
||||||
|
startVal: 0,
|
||||||
|
suffix: '元',
|
||||||
|
suffixStyle: {
|
||||||
|
paddingLeft: '0.5rem',
|
||||||
|
},
|
||||||
|
transition: 'easeOutQuart',
|
||||||
|
});
|
||||||
|
|
||||||
|
function changeNumber() {
|
||||||
|
props.endVal =
|
||||||
|
Math.floor(Math.random() * 100_000_000) / 10 ** (props.decimals || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDocumentation() {
|
||||||
|
window.open('https://vueuse.org/core/useTransition/', '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStarted() {
|
||||||
|
message.loading({
|
||||||
|
content: '动画已开始',
|
||||||
|
duration: 0,
|
||||||
|
key: 'animator-info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFinished() {
|
||||||
|
message.success({
|
||||||
|
content: '动画已结束',
|
||||||
|
duration: 2,
|
||||||
|
key: 'animator-info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page title="CountTo" description="数字滚动动画组件。使用">
|
||||||
|
<template #description>
|
||||||
|
<span>
|
||||||
|
使用useTransition封装的数字滚动动画组件,每次改变当前值都会产生过渡动画。
|
||||||
|
</span>
|
||||||
|
<Button type="link" @click="openDocumentation">
|
||||||
|
查看useTransition文档
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<Card title="基本用法">
|
||||||
|
<div class="flex w-full items-center justify-center pb-4">
|
||||||
|
<CountTo v-bind="props" @started="onStarted" @finished="onFinished" />
|
||||||
|
</div>
|
||||||
|
<Form :model="props">
|
||||||
|
<Row :gutter="20">
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="初始值" name="startVal">
|
||||||
|
<InputNumber v-model:value="props.startVal" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="当前值" name="endVal">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="props.endVal"
|
||||||
|
class="w-full"
|
||||||
|
:precision="props.decimals"
|
||||||
|
>
|
||||||
|
<template #addonAfter>
|
||||||
|
<IconifyIcon
|
||||||
|
v-tippy="`设置一个随机值`"
|
||||||
|
class="size-5 cursor-pointer outline-none"
|
||||||
|
icon="ix:random-filled"
|
||||||
|
@click="changeNumber"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</InputNumber>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="禁用动画" name="disabled">
|
||||||
|
<Switch v-model:checked="props.disabled" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="延迟动画" name="delay">
|
||||||
|
<InputNumber v-model:value="props.delay" :min="0" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="持续时间" name="duration">
|
||||||
|
<InputNumber v-model:value="props.duration" :min="0" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="小数位数" name="decimals">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="props.decimals"
|
||||||
|
:min="0"
|
||||||
|
:precision="0"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="分隔符" name="separator">
|
||||||
|
<Input v-model:value="props.separator" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="小数点" name="decimal">
|
||||||
|
<Input v-model:value="props.decimal" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="动画" name="transition">
|
||||||
|
<Select v-model:value="props.transition">
|
||||||
|
<Select.Option
|
||||||
|
v-for="preset in TransitionPresetsKeys"
|
||||||
|
:key="preset"
|
||||||
|
:value="preset"
|
||||||
|
>
|
||||||
|
{{ preset }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="前缀" name="prefix">
|
||||||
|
<Input v-model:value="props.prefix" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="后缀" name="suffix">
|
||||||
|
<Input v-model:value="props.suffix" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
66
playground/src/views/examples/json-viewer/data.ts
Normal file
66
playground/src/views/examples/json-viewer/data.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
export const json1 = {
|
||||||
|
additionalInfo: {
|
||||||
|
author: 'Your Name',
|
||||||
|
debug: true,
|
||||||
|
version: '1.3.10',
|
||||||
|
versionCode: 132,
|
||||||
|
},
|
||||||
|
additionalNotes: 'This JSON is used for demonstration purposes',
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
description: 'Description of Tool 1',
|
||||||
|
name: 'Tool 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Description of Tool 2',
|
||||||
|
name: 'Tool 2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Description of Tool 3',
|
||||||
|
name: 'Tool 3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Description of Tool 4',
|
||||||
|
name: 'Tool 4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const json2 = JSON.parse(`
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-123",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1677652288,
|
||||||
|
"model": "gpt-3.5-turbo-0613",
|
||||||
|
"system_fingerprint": "fp_44709d6fcb",
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hello there, how may I assist you today?"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 9,
|
||||||
|
"completion_tokens": 12,
|
||||||
|
"total_tokens": 21,
|
||||||
|
"debug_mode": true
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"startAt": "2021-08-01T00:00:00Z",
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"timestamp": "2021-08-01T00:00:00Z",
|
||||||
|
"message": "This is a debug message",
|
||||||
|
"extra":[ "extra1", "extra2" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2021-08-01T00:00:01Z",
|
||||||
|
"message": "This is another debug message",
|
||||||
|
"extra":[ "extra3", "extra4" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
51
playground/src/views/examples/json-viewer/index.vue
Normal file
51
playground/src/views/examples/json-viewer/index.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { JsonViewerAction, JsonViewerValue } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { JsonViewer, Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { json1, json2 } from './data';
|
||||||
|
|
||||||
|
function handleKeyClick(key: string) {
|
||||||
|
message.info(`点击了Key ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleValueClick(value: JsonViewerValue) {
|
||||||
|
message.info(`点击了Value ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopied(_event: JsonViewerAction) {
|
||||||
|
message.success('已复制JSON');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
title="Json Viewer"
|
||||||
|
description="一个渲染 JSON 结构数据的组件,支持复制、展开等,简单易用"
|
||||||
|
>
|
||||||
|
<Card title="默认配置">
|
||||||
|
<JsonViewer :value="json1" />
|
||||||
|
</Card>
|
||||||
|
<Card title="可复制、默认展开3层、显示边框、事件处理" class="mt-4">
|
||||||
|
<JsonViewer
|
||||||
|
:value="json2"
|
||||||
|
:expand-depth="3"
|
||||||
|
copyable
|
||||||
|
:sort="false"
|
||||||
|
@key-click="handleKeyClick"
|
||||||
|
@value-click="handleValueClick"
|
||||||
|
@copied="handleCopied"
|
||||||
|
boxed
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title="预览模式" class="mt-4">
|
||||||
|
<JsonViewer
|
||||||
|
:value="json2"
|
||||||
|
copyable
|
||||||
|
preview-mode
|
||||||
|
:show-array-index="false"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
213
playground/src/views/examples/motion/index.vue
Normal file
213
playground/src/views/examples/motion/index.vue
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { Motion, MotionGroup, MotionPresets } from '@vben/plugins/motion';
|
||||||
|
|
||||||
|
import { refAutoReset, watchDebounced } from '@vueuse/core';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
InputNumber,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
// 本例子用不到visible类型的动画。带有VisibleOnce和Visible的类型会在组件进入视口被显示时执行动画,
|
||||||
|
const presets = MotionPresets.filter((v) => !v.includes('Visible'));
|
||||||
|
const showCard1 = refAutoReset(true, 100);
|
||||||
|
const showCard2 = refAutoReset(true, 100);
|
||||||
|
const showCard3 = refAutoReset(true, 100);
|
||||||
|
const motionProps = reactive({
|
||||||
|
delay: 0,
|
||||||
|
duration: 300,
|
||||||
|
enter: { scale: 1 },
|
||||||
|
hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },
|
||||||
|
preset: 'fade',
|
||||||
|
tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const motionGroupProps = reactive({
|
||||||
|
delay: 0,
|
||||||
|
duration: 300,
|
||||||
|
enter: { scale: 1 },
|
||||||
|
hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },
|
||||||
|
preset: 'fade',
|
||||||
|
tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
watchDebounced(
|
||||||
|
motionProps,
|
||||||
|
() => {
|
||||||
|
showCard2.value = false;
|
||||||
|
},
|
||||||
|
{ debounce: 200, deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watchDebounced(
|
||||||
|
motionGroupProps,
|
||||||
|
() => {
|
||||||
|
showCard3.value = false;
|
||||||
|
},
|
||||||
|
{ debounce: 200, deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function openDocPage() {
|
||||||
|
window.open('https://motion.vueuse.org/', '_blank');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page title="Motion">
|
||||||
|
<template #description>
|
||||||
|
<span>一个易于使用的为其它组件赋予动画效果的组件。</span>
|
||||||
|
<Button type="link" @click="openDocPage">查看文档</Button>
|
||||||
|
</template>
|
||||||
|
<Card title="使用指令" :body-style="{ minHeight: '5rem' }">
|
||||||
|
<template #extra>
|
||||||
|
<Button type="primary" @click="showCard1 = false">重载</Button>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<div class="relative flex gap-2 overflow-hidden" v-if="showCard1">
|
||||||
|
<Button v-motion-fade-visible>fade</Button>
|
||||||
|
<Button v-motion-pop-visible :duration="500">pop</Button>
|
||||||
|
<Button v-motion-slide-left>slide-left</Button>
|
||||||
|
<Button v-motion-slide-right>slide-right</Button>
|
||||||
|
<Button v-motion-slide-bottom>slide-bottom</Button>
|
||||||
|
<Button v-motion-slide-top>slide-top</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
class="mt-2"
|
||||||
|
title="使用组件(将内部作为一个整体添加动画)"
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
|
||||||
|
>
|
||||||
|
<Motion
|
||||||
|
v-bind="motionProps"
|
||||||
|
v-if="showCard2"
|
||||||
|
class="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Button size="large">这个按钮在显示时会有动画效果</Button>
|
||||||
|
<span>附属组件,会作为整体处理动画</span>
|
||||||
|
</Motion>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div v-if="showCard2" class="flex items-center gap-2">
|
||||||
|
<span>顺序延迟</span>
|
||||||
|
<Motion
|
||||||
|
v-bind="{
|
||||||
|
...motionProps,
|
||||||
|
delay: motionProps.delay + 100 * i,
|
||||||
|
}"
|
||||||
|
v-for="i in 5"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<Button size="large">按钮{{ i }}</Button>
|
||||||
|
</Motion>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Form :model="motionProps" :label-col="{ span: 10 }">
|
||||||
|
<Row>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="preset" label="动画效果">
|
||||||
|
<Select v-model:value="motionProps.preset">
|
||||||
|
<Select.Option
|
||||||
|
:value="preset"
|
||||||
|
v-for="preset in presets"
|
||||||
|
:key="preset"
|
||||||
|
>
|
||||||
|
{{ preset }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="duration" label="持续时间">
|
||||||
|
<InputNumber v-model:value="motionProps.duration" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="delay" label="延迟动画">
|
||||||
|
<InputNumber v-model:value="motionProps.delay" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="hovered.scale" label="Hover缩放">
|
||||||
|
<InputNumber v-model:value="motionProps.hovered.scale" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="hovered.tapped" label="按下时缩放">
|
||||||
|
<InputNumber v-model:value="motionProps.tapped.scale" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
class="mt-2"
|
||||||
|
title="分组动画(每个子元素都会应用相同的独立动画)"
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
|
||||||
|
>
|
||||||
|
<MotionGroup v-bind="motionGroupProps" v-if="showCard3">
|
||||||
|
<Button size="large">按钮1</Button>
|
||||||
|
<Button size="large">按钮2</Button>
|
||||||
|
<Button size="large">按钮3</Button>
|
||||||
|
<Button size="large">按钮4</Button>
|
||||||
|
<Button size="large">按钮5</Button>
|
||||||
|
</MotionGroup>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Form :model="motionGroupProps" :label-col="{ span: 10 }">
|
||||||
|
<Row>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="preset" label="动画效果">
|
||||||
|
<Select v-model:value="motionGroupProps.preset">
|
||||||
|
<Select.Option
|
||||||
|
:value="preset"
|
||||||
|
v-for="preset in presets"
|
||||||
|
:key="preset"
|
||||||
|
>
|
||||||
|
{{ preset }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="duration" label="持续时间">
|
||||||
|
<InputNumber v-model:value="motionGroupProps.duration" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="delay" label="延迟动画">
|
||||||
|
<InputNumber v-model:value="motionGroupProps.delay" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="hovered.scale" label="Hover缩放">
|
||||||
|
<InputNumber v-model:value="motionGroupProps.hovered.scale" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem prop="hovered.tapped" label="按下时缩放">
|
||||||
|
<InputNumber v-model:value="motionGroupProps.tapped.scale" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -21,22 +21,22 @@ catalog:
|
|||||||
'@commitlint/cli': ^19.7.1
|
'@commitlint/cli': ^19.7.1
|
||||||
'@commitlint/config-conventional': ^19.7.1
|
'@commitlint/config-conventional': ^19.7.1
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.19.0
|
'@eslint/js': ^9.20.0
|
||||||
'@faker-js/faker': ^9.4.0
|
'@faker-js/faker': ^9.5.0
|
||||||
'@iconify/json': ^2.2.302
|
'@iconify/json': ^2.2.307
|
||||||
'@iconify/tailwind': ^1.2.0
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.3.0
|
'@iconify/vue': ^4.3.0
|
||||||
'@intlify/core-base': ^11.1.0
|
'@intlify/core-base': ^11.1.1
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.3
|
'@intlify/unplugin-vue-i18n': ^6.0.3
|
||||||
'@jspm/generator': ^2.4.2
|
'@jspm/generator': ^2.5.0
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.12.1
|
'@nolebase/vitepress-plugin-git-changelog': ^2.14.0
|
||||||
'@playwright/test': ^1.50.1
|
'@playwright/test': ^1.50.1
|
||||||
'@pnpm/workspace.read-manifest': ^1000.0.2
|
'@pnpm/workspace.read-manifest': ^1000.0.2
|
||||||
'@stylistic/stylelint-plugin': ^3.1.1
|
'@stylistic/stylelint-plugin': ^3.1.1
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.65.0
|
'@tanstack/vue-query': ^5.66.3
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
@ -45,13 +45,13 @@ catalog:
|
|||||||
'@types/lodash.clonedeep': ^4.5.9
|
'@types/lodash.clonedeep': ^4.5.9
|
||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
'@types/lodash.isequal': ^4.5.8
|
'@types/lodash.isequal': ^4.5.8
|
||||||
'@types/node': ^22.13.1
|
'@types/node': ^22.13.4
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.23.0
|
'@typescript-eslint/eslint-plugin': ^8.24.0
|
||||||
'@typescript-eslint/parser': ^8.23.0
|
'@typescript-eslint/parser': ^8.24.0
|
||||||
'@vee-validate/zod': ^4.15.0
|
'@vee-validate/zod': ^4.15.0
|
||||||
'@vite-pwa/vitepress': ^0.5.3
|
'@vite-pwa/vitepress': ^0.5.3
|
||||||
'@vitejs/plugin-vue': ^5.2.1
|
'@vitejs/plugin-vue': ^5.2.1
|
||||||
@ -59,8 +59,9 @@ catalog:
|
|||||||
'@vue/reactivity': ^3.5.13
|
'@vue/reactivity': ^3.5.13
|
||||||
'@vue/shared': ^3.5.13
|
'@vue/shared': ^3.5.13
|
||||||
'@vue/test-utils': ^2.4.6
|
'@vue/test-utils': ^2.4.6
|
||||||
'@vueuse/core': ^12.5.0
|
'@vueuse/core': ^12.7.0
|
||||||
'@vueuse/integrations': ^12.5.0
|
'@vueuse/motion': ^2.2.6
|
||||||
|
'@vueuse/integrations': ^12.7.0
|
||||||
ant-design-vue: ^4.2.6
|
ant-design-vue: ^4.2.6
|
||||||
archiver: ^7.0.1
|
archiver: ^7.0.1
|
||||||
autoprefixer: ^10.4.20
|
autoprefixer: ^10.4.20
|
||||||
@ -84,9 +85,9 @@ catalog:
|
|||||||
depcheck: ^1.4.7
|
depcheck: ^1.4.7
|
||||||
dotenv: ^16.4.7
|
dotenv: ^16.4.7
|
||||||
echarts: ^5.6.0
|
echarts: ^5.6.0
|
||||||
element-plus: ^2.9.3
|
element-plus: ^2.9.4
|
||||||
eslint: ^9.19.0
|
eslint: ^9.20.1
|
||||||
eslint-config-turbo: ^2.4.0
|
eslint-config-turbo: ^2.4.2
|
||||||
eslint-plugin-command: ^0.2.7
|
eslint-plugin-command: ^0.2.7
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.6.1
|
eslint-plugin-import-x: ^4.6.1
|
||||||
@ -94,7 +95,7 @@ catalog:
|
|||||||
eslint-plugin-jsonc: ^2.19.1
|
eslint-plugin-jsonc: ^2.19.1
|
||||||
eslint-plugin-n: ^17.15.1
|
eslint-plugin-n: ^17.15.1
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
eslint-plugin-no-only-tests: ^3.3.0
|
||||||
eslint-plugin-perfectionist: ^4.8.0
|
eslint-plugin-perfectionist: ^4.9.0
|
||||||
eslint-plugin-prettier: ^5.2.3
|
eslint-plugin-prettier: ^5.2.3
|
||||||
eslint-plugin-regexp: ^2.7.0
|
eslint-plugin-regexp: ^2.7.0
|
||||||
eslint-plugin-unicorn: ^56.0.1
|
eslint-plugin-unicorn: ^56.0.1
|
||||||
@ -104,8 +105,8 @@ catalog:
|
|||||||
execa: ^9.5.2
|
execa: ^9.5.2
|
||||||
find-up: ^7.0.0
|
find-up: ^7.0.0
|
||||||
get-port: ^7.1.0
|
get-port: ^7.1.0
|
||||||
globals: ^15.14.0
|
globals: ^15.15.0
|
||||||
h3: ^1.14.0
|
h3: ^1.15.0
|
||||||
happy-dom: ^16.8.1
|
happy-dom: ^16.8.1
|
||||||
html-minifier-terser: ^7.2.0
|
html-minifier-terser: ^7.2.0
|
||||||
husky: ^9.1.7
|
husky: ^9.1.7
|
||||||
@ -126,22 +127,22 @@ catalog:
|
|||||||
pinia-plugin-persistedstate: ^4.2.0
|
pinia-plugin-persistedstate: ^4.2.0
|
||||||
pkg-types: ^1.3.1
|
pkg-types: ^1.3.1
|
||||||
playwright: ^1.50.1
|
playwright: ^1.50.1
|
||||||
postcss: ^8.5.1
|
postcss: ^8.5.2
|
||||||
postcss-antd-fixes: ^0.2.0
|
postcss-antd-fixes: ^0.2.0
|
||||||
postcss-html: ^1.8.0
|
postcss-html: ^1.8.0
|
||||||
postcss-import: ^16.1.0
|
postcss-import: ^16.1.0
|
||||||
postcss-preset-env: ^10.1.3
|
postcss-preset-env: ^10.1.4
|
||||||
postcss-scss: ^4.0.9
|
postcss-scss: ^4.0.9
|
||||||
prettier: ^3.4.2
|
prettier: ^3.5.1
|
||||||
prettier-plugin-tailwindcss: ^0.6.11
|
prettier-plugin-tailwindcss: ^0.6.11
|
||||||
publint: ^0.2.12
|
publint: ^0.2.12
|
||||||
qrcode: ^1.5.4
|
qrcode: ^1.5.4
|
||||||
radix-vue: ^1.9.13
|
radix-vue: ^1.9.14
|
||||||
resolve.exports: ^2.0.3
|
resolve.exports: ^2.0.3
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.34.2
|
rollup: ^4.34.7
|
||||||
rollup-plugin-visualizer: ^5.14.0
|
rollup-plugin-visualizer: ^5.14.0
|
||||||
sass: ^1.83.4
|
sass: ^1.85.0
|
||||||
sortablejs: ^1.15.6
|
sortablejs: ^1.15.6
|
||||||
stylelint: ^16.14.1
|
stylelint: ^16.14.1
|
||||||
stylelint-config-recess-order: ^5.1.1
|
stylelint-config-recess-order: ^5.1.1
|
||||||
@ -157,29 +158,30 @@ catalog:
|
|||||||
tailwindcss-animate: ^1.0.7
|
tailwindcss-animate: ^1.0.7
|
||||||
theme-colors: ^0.1.0
|
theme-colors: ^0.1.0
|
||||||
tippy.js: ^6.2.5
|
tippy.js: ^6.2.5
|
||||||
turbo: ^2.4.0
|
turbo: ^2.4.2
|
||||||
typescript: ^5.7.3
|
typescript: ^5.7.3
|
||||||
unbuild: ^3.3.1
|
unbuild: ^3.3.1
|
||||||
unplugin-element-plus: ^0.9.0
|
unplugin-element-plus: ^0.9.1
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.0.11
|
vite: ^6.1.0
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.0
|
vite-plugin-dts: ^4.5.0
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
vite-plugin-lazy-import: ^1.0.7
|
vite-plugin-lazy-import: ^1.0.7
|
||||||
vite-plugin-pwa: ^0.21.1
|
vite-plugin-pwa: ^0.21.1
|
||||||
vite-plugin-vue-devtools: ^7.7.1
|
vite-plugin-vue-devtools: ^7.7.2
|
||||||
vitepress: ^1.6.3
|
vitepress: ^1.6.3
|
||||||
vitepress-plugin-group-icons: ^1.3.5
|
vitepress-plugin-group-icons: ^1.3.5
|
||||||
vitest: ^2.1.9
|
vitest: ^2.1.9
|
||||||
vue: ^3.5.13
|
vue: ^3.5.13
|
||||||
vue-eslint-parser: ^9.4.3
|
vue-eslint-parser: ^9.4.3
|
||||||
vue-i18n: ^11.1.0
|
vue-i18n: ^11.1.1
|
||||||
|
vue-json-viewer: ^3.0.4
|
||||||
vue-router: ^4.5.0
|
vue-router: ^4.5.0
|
||||||
vue-tippy: ^6.6.0
|
vue-tippy: ^6.6.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.1.10
|
||||||
vxe-pc-ui: ^4.3.79
|
vxe-pc-ui: ^4.3.87
|
||||||
vxe-table: 4.10.0
|
vxe-table: 4.10.0
|
||||||
watermark-js-plus: ^1.5.7
|
watermark-js-plus: ^1.5.8
|
||||||
zod: ^3.24.1
|
zod: ^3.24.2
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
Loading…
Reference in New Issue
Block a user