Преглед изворни кода

feat(nuxt): vant有个bug更换为vuetify

emit实现没有返回数据,导致事件返回Promise无法正确冒泡到errorCaptured
曾志翔 пре 3 година
родитељ
комит
899ca5d5df

+ 1 - 0
.eslintrc.js

@@ -14,6 +14,7 @@ module.exports = {
   rules: {
     'no-var': ['warn'],
     'no-unused-vars': ['warn'],
+    'vue/multi-word-component-names': ['off'],
   },
   ignorePatterns: ['node_modules/', 'static/'],
 };

+ 4 - 0
assets/style/main.scss

@@ -1,3 +1,7 @@
 html {
   font-size: 16px;
+  // box-sizing: border-box;
+  // * {
+  //   box-sizing: inherit;
+  // }
 }

+ 11 - 3
layouts/empty.vue

@@ -1,7 +1,9 @@
 <template>
-  <div class="empty-layout">
-    <Nuxt />
-  </div>
+  <v-app ref="app" class="empty-layout">
+    <v-main ref="main">
+      <Nuxt />
+    </v-main>
+  </v-app>
 </template>
 
 <script>
@@ -10,5 +12,11 @@ export default {
   data() {
     return {};
   },
+  created() {},
+  mounted() {
+    // this.$nuxt.$vuetifyApp = this.$refs.app;
+    this.$vuetify.app = this.$refs.app;
+    this.$vuetify.main = this.$refs.main;
+  },
 };
 </script>

+ 35 - 26
nuxt.config.js

@@ -1,5 +1,5 @@
 // import colors from 'vuetify/es5/util/colors'
-// import zhHans from 'vuetify/lib/locale/zh-Hans';
+import zhHans from 'vuetify/lib/locale/zh-Hans';
 import dotenv from 'dotenv';
 // dotenv.config({
 //   path: `.env`,
@@ -72,6 +72,7 @@ export default {
     // '~/plugins/api',
     '~/plugins/file-upload',
     // '~/plugins/message',
+    '~/plugins/toast',
     '~/plugins/error-captured',
     '~/plugins/user-agent',
     '~/plugins/native',
@@ -105,9 +106,10 @@ export default {
     // '~/modules/unplugin-vue-components'
     '@aceforth/nuxt-optimized-images',
     '~/modules/postcss-px-to-viewport',
-    '~/modules/vant',
+    // '~/modules/vant',
     // https://go.nuxtjs.dev/vuetify
-    // '@nuxtjs/vuetify',
+    '@nuxtjs/vuetify',
+    '@unocss/nuxt',
     // '@nuxtjs/composition-api/module',
   ],
 
@@ -116,7 +118,6 @@ export default {
     // https://go.nuxtjs.dev/axios
     '@nuxtjs/axios',
     '@nuxtjs/auth-next',
-    '@unocss/nuxt',
   ],
   unocss: {
     uno: true,
@@ -179,31 +180,32 @@ export default {
       //   // autoFetch: true
       // },
     },
+    cookie: false,
   },
 
   // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
-  // vuetify: {
-  //   defaultAssets: false,
-  //   customVariables: ['~/assets/variables.scss'],
-  //   lang: {
-  //     locales: { zhHans },
-  //     current: 'zhHans',
-  //   },
-  //   theme: {
-  //     dark: false,
-  //     // themes: {
-  //     //   dark: {
-  //     //     primary: colors.blue.darken2,
-  //     //     accent: colors.grey.darken3,
-  //     //     secondary: colors.amber.darken3,
-  //     //     info: colors.teal.lighten1,
-  //     //     warning: colors.amber.base,
-  //     //     error: colors.deepOrange.accent4,
-  //     //     success: colors.green.accent3,
-  //     //   },
-  //     // },
-  //   },
-  // },
+  vuetify: {
+    defaultAssets: false,
+    customVariables: ['~/assets/style/variables.scss'],
+    lang: {
+      locales: { zhHans },
+      current: 'zhHans',
+    },
+    theme: {
+      dark: false,
+      // themes: {
+      //   dark: {
+      //     primary: colors.blue.darken2,
+      //     accent: colors.grey.darken3,
+      //     secondary: colors.amber.darken3,
+      //     info: colors.teal.lighten1,
+      //     warning: colors.amber.base,
+      //     error: colors.deepOrange.accent4,
+      //     success: colors.green.accent3,
+      //   },
+      // },
+    },
+  },
 
   // Build Configuration: https://go.nuxtjs.dev/config-build
   build: {
@@ -223,6 +225,13 @@ export default {
     postcss: {
       plugins: {},
     },
+    loaders: {
+      vue: {
+        transformAssetUrls: {
+          'v-img': 'src',
+        },
+      },
+    },
   },
   router: {
     base: '/h5/',

+ 114 - 4
package-lock.json

@@ -11,6 +11,8 @@
         "@nuxtjs/auth-next": "5.0.0-1648802546.c9880dc",
         "@nuxtjs/axios": "^5.13.6",
         "axios": "^0.27.2",
+        "clipboard": "^2.0.11",
+        "clipboardy": "^3.0.0",
         "core-js": "^3.19.3",
         "dayjs": "^1.11.3",
         "format-number": "^3.0.0",
@@ -4717,8 +4719,7 @@
     "node_modules/arch": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz",
-      "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
-      "dev": true
+      "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="
     },
     "node_modules/archive-type": {
       "version": "4.0.0",
@@ -6892,6 +6893,40 @@
         "node": ">= 10"
       }
     },
+    "node_modules/clipboard": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
+    "node_modules/clipboardy": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-3.0.0.tgz",
+      "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+      "dependencies": {
+        "arch": "^2.2.0",
+        "execa": "^5.1.1",
+        "is-wsl": "^2.2.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/clipboardy/node_modules/is-wsl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz",
+      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+      "dependencies": {
+        "is-docker": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz",
@@ -8210,6 +8245,11 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -11144,6 +11184,14 @@
       "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
       "dev": true
     },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
     "node_modules/got": {
       "version": "7.1.0",
       "resolved": "https://registry.npmmirror.com/got/-/got-7.1.0.tgz",
@@ -18234,6 +18282,11 @@
         "seek-table": "bin/seek-bzip-table"
       }
     },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
+    },
     "node_modules/semver": {
       "version": "6.3.0",
       "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz",
@@ -20331,6 +20384,11 @@
       "resolved": "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz",
       "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
     },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+    },
     "node_modules/tinycolor2": {
       "version": "1.4.2",
       "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.4.2.tgz",
@@ -27010,8 +27068,7 @@
     "arch": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz",
-      "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
-      "dev": true
+      "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="
     },
     "archive-type": {
       "version": "4.0.0",
@@ -28781,6 +28838,36 @@
       "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz",
       "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw=="
     },
+    "clipboard": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+      "requires": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
+    "clipboardy": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-3.0.0.tgz",
+      "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+      "requires": {
+        "arch": "^2.2.0",
+        "execa": "^5.1.1",
+        "is-wsl": "^2.2.0"
+      },
+      "dependencies": {
+        "is-wsl": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz",
+          "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+          "requires": {
+            "is-docker": "^2.0.0"
+          }
+        }
+      }
+    },
     "cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz",
@@ -29859,6 +29946,11 @@
       "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
     },
+    "delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+    },
     "depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -32115,6 +32207,14 @@
       "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
       "dev": true
     },
+    "good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+      "requires": {
+        "delegate": "^3.1.2"
+      }
+    },
     "got": {
       "version": "7.1.0",
       "resolved": "https://registry.npmmirror.com/got/-/got-7.1.0.tgz",
@@ -37850,6 +37950,11 @@
         "commander": "^2.8.1"
       }
     },
+    "select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
+    },
     "semver": {
       "version": "6.3.0",
       "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz",
@@ -39549,6 +39654,11 @@
       "resolved": "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz",
       "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
     },
+    "tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+    },
     "tinycolor2": {
       "version": "1.4.2",
       "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.4.2.tgz",

+ 2 - 0
package.json

@@ -24,6 +24,8 @@
     "@nuxtjs/auth-next": "5.0.0-1648802546.c9880dc",
     "@nuxtjs/axios": "^5.13.6",
     "axios": "^0.27.2",
+    "clipboard": "^2.0.11",
+    "clipboardy": "^3.0.0",
     "core-js": "^3.19.3",
     "dayjs": "^1.11.3",
     "format-number": "^3.0.0",

+ 57 - 27
pages/activity/invite-user.vue

@@ -1,9 +1,9 @@
 <template>
-  <div class="invite-user">
+  <v-container class="invite-user pa-0" fluid>
     <div class="box box1">
-      <div class="box-title">参与步骤</div>
-      <div class="box-content">
-        <van-image
+      <div class="box-header">参与步骤</div>
+      <div class="box-main">
+        <v-img
           class="cybz-content"
           src="~/assets/image/activity/invite-user/cybz-content@2x.png"
         />
@@ -12,13 +12,14 @@
           <div class="cybz-content-text-item">邀请购买云机</div>
           <div class="cybz-content-text-item">返星币换现金</div>
         </div>
-        <van-button class="share-button" round @click="share()"></van-button>
+        <v-btn class="share-button" rounded @click="share()"></v-btn>
+        <!-- <button @click="share()">邀请</button> -->
       </div>
       <!-- <div class="h-40">13</div> -->
     </div>
     <div class="box box2">
-      <div class="box-title">收益明细</div>
-      <div class="box-content">
+      <div class="box-header">收益明细</div>
+      <div class="box-main">
         <div class="grid grid-cols-2 gap-x-4 gap-y-8">
           <div
             v-for="(item, index) in dataList"
@@ -38,12 +39,12 @@
       </div>
     </div>
     <!-- <div class="box box3">
-      <div class="box-title">返利套餐</div>
-      <div class="box-content"></div>
+      <div class="box-header">返利套餐</div>
+      <div class="box-main"></div>
     </div> -->
     <div class="box box4">
-      <div class="box-title">活动规则</div>
-      <div class="box-content text-sm">
+      <div class="box-header">活动规则</div>
+      <div class="box-main text-sm">
         <p>1、传播过程中有任何疑问,可直接咨询客服</p>
         <p>
           2、用户可以通过分享页面或者套餐给想要分享的人,被分享人购买云机套餐后,分享人可获得套餐对应的星币
@@ -75,12 +76,14 @@
 
     <div class="ccc">{{ 36666666.123456 | formatNumber }}</div>
     <div class="ccc">{{ '2020-01-01' | formatDate }}</div> -->
-  </div>
+  </v-container>
 </template>
 
 <script>
 import qs from 'qs';
+import clipboard from 'clipboardy/browser';
 import { getStarCoinOverview } from '~/api/activity/invite-user.js';
+
 export default {
   // auth: false,
   name: 'InviteUser',
@@ -132,17 +135,44 @@ export default {
   },
   mounted() {},
   methods: {
-    share() {
+    async share() {
+      console.log(this);
       this.$tongji.trackEvent('活动', '分享', '', 0);
-      this.$native.share({
-        title: '标题',
-        content: '内容',
-        gotoUrl: `${location.origin}${location.pathname}${qs.stringify(
-          { id: 666 },
-          { addQueryPrefix: true },
-        )}`,
-        shareImg: 'http://localhost',
-      });
+
+      const url =
+        location.origin +
+        this.$router.resolve({
+          path: '/register-for-invite',
+          query: {
+            invitationUserName: this.$auth.user?.phone,
+            activityId: 1,
+          },
+        }).href;
+
+      if (this.$userAgent.isMiniProgram) {
+        // 小程序环境
+        await clipboard.write(url);
+        this.$toast.success('链接复制成功');
+      } else if (this.$userAgent.isApp) {
+        // app环境
+        this.$native.share({
+          title: '双子星APP',
+          content: '分享好友购买云机套餐,返星币换现金',
+          gotoUrl: `${location.origin}${location.pathname}${qs.stringify(
+            { id: 666 },
+            { addQueryPrefix: true },
+          )}`,
+          shareImg: url,
+        });
+      } else {
+        // 浏览器环境
+        // await clipboard.write(url);
+        throw new Error('1231');
+        // this.$toast.success('链接复制成功');
+      }
+    },
+    share2() {
+      throw new Error('1231');
     },
   },
 };
@@ -173,7 +203,7 @@ export default {
   + .box {
     margin-top: 30px;
   }
-  .box-title {
+  .box-header {
     position: absolute;
     top: -30px;
     // left: 141px;
@@ -183,13 +213,13 @@ export default {
     padding: 0 130px;
     color: #fff;
   }
-  .box-content {
+  .box-main {
     padding: 30px 15px 20px;
   }
 }
 .box1 {
   margin-top: 275px;
-  .box-content {
+  .box-main {
     // padding: 30px 15px 20px;
     padding-left: 0;
     padding-right: 0;
@@ -237,13 +267,13 @@ export default {
     color: #ff6600;
     // font-size: 24px;
   }
-  .box-content {
+  .box-main {
     padding-left: 20px;
     padding-right: 20px;
   }
 }
 .box4 {
-  .box-content {
+  .box-main {
     // padding-left: 20px;
     padding-top: 0;
   }

+ 1 - 0
pages/register-for-invite.vue

@@ -16,6 +16,7 @@
 import { registerForInvite } from '~/api/user/client.js';
 import { sendSmsCode } from '~/api/message/phone.js';
 export default {
+  auth: false,
   name: 'RegisterForInvite',
   data() {
     return {

+ 2 - 0
plugins/axios.js

@@ -2,7 +2,9 @@ import AxiosError from 'axios/lib/core/AxiosError';
 
 export default function ({ $axios, redirect }) {
   // $axios.defaults.dataKey = 'data';
+
   $axios.onRequest((config) => {
+    config.headers.client = 7;
     return config;
   });
   $axios.onResponse((response) => {

+ 11 - 1
plugins/error-captured.js

@@ -1,10 +1,20 @@
+// import Vue from 'vue';
+
 export default function ({ app, $message }) {
   app.errorCaptured = (err, vm, info) => {
     if (process.client) {
       // console.error(err);
       // $message.error({ content: err.message });
-      vm.$toast.fail({ message: err.message, position: 'bottom' });
+      // vm.$toast.fail({ message: err.message, position: 'bottom' });
+      vm.$toast.error({ message: err.message });
       return false;
     }
   };
+
+  // Vue.config.errorHandler = function (err, vm, info) {
+  //   if (process.client) {
+  //     vm.$toast.fail({ message: err.message, position: 'bottom' });
+  //     return false;
+  //   }
+  // };
 }

+ 3 - 0
plugins/native.js

@@ -3,6 +3,9 @@ export default function ({ $userAgent }, i) {
     if ($userAgent.isApp) {
       return;
     }
+    //  else if ($userAgent.isMiniProgram) {
+    //   throw new Error('小程序环境不支持');
+    // }
     throw new Error('非App环境');
   };
 

+ 40 - 0
plugins/toast/index.js

@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import toastOptions from './toast.vue';
+const Toast = Vue.extend(toastOptions);
+export default function (c, i) {
+  const isString = (v) =>
+    Object.prototype.toString.call(v) === '[object String]';
+  const fixOptios = (optios) =>
+    isString(optios)
+      ? {
+          message: optios,
+        }
+      : optios;
+
+  // 插入到app组件下
+  const getParent = () => c?.$vuetify?.app ?? window?.$nuxt;
+
+  const toast = (optios) => {
+    optios = fixOptios(optios);
+
+    return new Toast({
+      parent: getParent(),
+      propsData: {
+        // message: optios.message,
+        // icon: optios.icon,
+        ...optios,
+      },
+    }).$mount();
+  };
+
+  ['error', 'success', 'warning', 'info'].forEach((key) => {
+    toast[key] = (optios) => {
+      optios = fixOptios(optios);
+      return toast({
+        ...optios,
+        icon: '$' + key,
+      });
+    };
+  });
+  i('toast', toast);
+}

+ 57 - 0
plugins/toast/toast.vue

@@ -0,0 +1,57 @@
+<template>
+  <v-snackbar
+    app
+    class="toast"
+    :value.sync="value"
+    color="rgba(0,0,0,.8)"
+    v-bind="attrs"
+    @input="!$event && destroy()"
+  >
+    <v-icon v-if="icon" left>{{ icon }}</v-icon>
+    <span>{{ message }}</span>
+  </v-snackbar>
+</template>
+
+<script>
+export default {
+  name: 'Toast',
+  props: {
+    message: {
+      type: String,
+      default: '',
+    },
+    icon: {
+      type: String,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      value: false,
+    };
+  },
+  computed: {
+    attrs() {
+      return Object.entries(this.$options.propsData).reduce(
+        (o, [key, value]) => {
+          !Object.hasOwn(this.$props, key) && (o[key] = value);
+          return o;
+        },
+        {},
+      );
+    },
+  },
+  mounted() {
+    this.$parent.$el.append(this.$el);
+    this.value = true;
+  },
+  destroyed() {
+    this.$el.remove();
+  },
+  methods: {
+    destroy() {
+      setTimeout(() => this.$destroy(), 1000);
+    },
+  },
+};
+</script>