Explorar el Código

feat: demo of motion plugin (#5550)

添加Motion的用法例子
Netfan hace 2 meses
padre
commit
9f82052c71

+ 4 - 0
apps/web-antd/src/bootstrap.ts

@@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
 import { initTippy } from '@vben/common-ui';
+import { MotionPlugin } from '@vben/plugins/motion';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -45,6 +46,9 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 配置Motion插件
+  app.use(MotionPlugin);
+
   // 动态更新标题
   watchEffect(() => {
     if (preferences.app.dynamicTitle) {

+ 4 - 0
apps/web-ele/src/bootstrap.ts

@@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
 import { initTippy } from '@vben/common-ui';
+import { MotionPlugin } from '@vben/plugins/motion';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -47,6 +48,9 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 配置Motion插件
+  app.use(MotionPlugin);
+
   // 动态更新标题
   watchEffect(() => {
     if (preferences.app.dynamicTitle) {

+ 4 - 0
apps/web-naive/src/bootstrap.ts

@@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
 import { initTippy } from '@vben/common-ui';
+import { MotionPlugin } from '@vben/plugins/motion';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -45,6 +46,9 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 配置Motion插件
+  app.use(MotionPlugin);
+
   // 动态更新标题
   watchEffect(() => {
     if (preferences.app.dynamicTitle) {

+ 5 - 0
packages/effects/plugins/package.json

@@ -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 - 0
packages/effects/plugins/src/motion/index.ts

@@ -0,0 +1,8 @@
+export * from './types';
+
+export {
+  MotionComponent as Motion,
+  MotionDirective,
+  MotionGroupComponent as MotionGroup,
+  MotionPlugin,
+} from '@vueuse/motion';

+ 26 - 0
packages/effects/plugins/src/motion/types.ts

@@ -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];

+ 6 - 0
playground/src/api/request.ts

@@ -111,3 +111,9 @@ export const requestClient = createRequestClient(apiURL, {
 });
 
 export const baseRequestClient = new RequestClient({ baseURL: apiURL });
+
+export interface PageFetchParams {
+  [key: string]: any;
+  pageNo?: number;
+  pageSize?: number;
+}

+ 4 - 0
playground/src/bootstrap.ts

@@ -2,6 +2,7 @@ import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
 import { initTippy } from '@vben/common-ui';
+import { MotionPlugin } from '@vben/plugins/motion';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -49,6 +50,9 @@ async function bootstrap(namespace: string) {
   // 配置@tanstack/vue-query
   app.use(VueQueryPlugin);
 
+  // 配置Motion插件
+  app.use(MotionPlugin);
+
   // 动态更新标题
   watchEffect(() => {
     if (preferences.app.dynamicTitle) {

+ 213 - 0
playground/src/views/examples/motion/index.vue

@@ -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>

+ 63 - 0
pnpm-lock.yaml

@@ -141,6 +141,9 @@ catalogs:
     '@vueuse/integrations':
       specifier: ^12.7.0
       version: 12.7.0
+    '@vueuse/motion':
+      specifier: ^2.2.6
+      version: 2.2.6
     ant-design-vue:
       specifier: ^4.2.6
       version: 4.2.6
@@ -1672,6 +1675,9 @@ importers:
       '@vueuse/core':
         specifier: 'catalog:'
         version: 12.7.0(typescript@5.7.3)
+      '@vueuse/motion':
+        specifier: 'catalog:'
+        version: 2.2.6(magicast@0.3.5)(rollup@4.34.7)(vue@3.5.13(typescript@5.7.3))
       echarts:
         specifier: 'catalog:'
         version: 5.6.0
@@ -4793,6 +4799,11 @@ packages:
   '@vueuse/metadata@9.13.0':
     resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
 
+  '@vueuse/motion@2.2.6':
+    resolution: {integrity: sha512-gKFktPtrdypSv44SaW1oBJKLBiP6kE5NcoQ6RsAU3InemESdiAutgQncfPe/rhLSLCtL4jTAhMmFfxoR6gm5LQ==}
+    peerDependencies:
+      vue: ^3.5.13
+
   '@vueuse/shared@10.11.1':
     resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
 
@@ -6470,6 +6481,9 @@ packages:
   fraction.js@4.3.7:
     resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
 
+  framesync@6.1.2:
+    resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
+
   fresh@0.5.2:
     resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
     engines: {node: '>= 0.6'}
@@ -6744,6 +6758,9 @@ packages:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
 
+  hey-listen@1.0.8:
+    resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
+
   highlight.js@11.11.1:
     resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
     engines: {node: '>=12.0.0'}
@@ -8208,6 +8225,9 @@ packages:
     resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
     engines: {node: '>=10.13.0'}
 
+  popmotion@11.0.5:
+    resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
+
   possible-typed-array-names@1.0.0:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
@@ -9419,6 +9439,9 @@ packages:
   style-search@0.1.0:
     resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
 
+  style-value-types@5.1.2:
+    resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==}
+
   stylehacks@7.0.4:
     resolution: {integrity: sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==}
     engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
@@ -9694,6 +9717,9 @@ packages:
   tslib@2.3.0:
     resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
 
+  tslib@2.4.0:
+    resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
+
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
@@ -13861,6 +13887,23 @@ snapshots:
 
   '@vueuse/metadata@9.13.0': {}
 
+  '@vueuse/motion@2.2.6(magicast@0.3.5)(rollup@4.34.7)(vue@3.5.13(typescript@5.7.3))':
+    dependencies:
+      '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.7.3))
+      '@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.7.3))
+      csstype: 3.1.3
+      framesync: 6.1.2
+      popmotion: 11.0.5
+      style-value-types: 5.1.2
+      vue: 3.5.13(typescript@5.7.3)
+    optionalDependencies:
+      '@nuxt/kit': 3.15.4(magicast@0.3.5)(rollup@4.34.7)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - magicast
+      - rollup
+      - supports-color
+
   '@vueuse/shared@10.11.1(vue@3.5.13(typescript@5.7.3))':
     dependencies:
       vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
@@ -15869,6 +15912,10 @@ snapshots:
 
   fraction.js@4.3.7: {}
 
+  framesync@6.1.2:
+    dependencies:
+      tslib: 2.4.0
+
   fresh@0.5.2: {}
 
   fs-extra@10.1.0:
@@ -16203,6 +16250,8 @@ snapshots:
 
   he@1.2.0: {}
 
+  hey-listen@1.0.8: {}
+
   highlight.js@11.11.1: {}
 
   homedir-polyfill@1.0.3:
@@ -17711,6 +17760,13 @@ snapshots:
 
   pngjs@5.0.0: {}
 
+  popmotion@11.0.5:
+    dependencies:
+      framesync: 6.1.2
+      hey-listen: 1.0.8
+      style-value-types: 5.1.2
+      tslib: 2.4.0
+
   possible-typed-array-names@1.0.0: {}
 
   postcss-antd-fixes@0.2.0(postcss@8.5.2):
@@ -19148,6 +19204,11 @@ snapshots:
 
   style-search@0.1.0: {}
 
+  style-value-types@5.1.2:
+    dependencies:
+      hey-listen: 1.0.8
+      tslib: 2.4.0
+
   stylehacks@7.0.4(postcss@8.5.1):
     dependencies:
       browserslist: 4.24.4
@@ -19486,6 +19547,8 @@ snapshots:
 
   tslib@2.3.0: {}
 
+  tslib@2.4.0: {}
+
   tslib@2.8.1: {}
 
   turbo-darwin-64@2.4.2:

+ 1 - 0
pnpm-workspace.yaml

@@ -60,6 +60,7 @@ catalog:
   '@vue/shared': ^3.5.13
   '@vue/test-utils': ^2.4.6
   '@vueuse/core': ^12.7.0
+  '@vueuse/motion': ^2.2.6
   '@vueuse/integrations': ^12.7.0
   ant-design-vue: ^4.2.6
   archiver: ^7.0.1