Browse Source

添加作品详情

lex 9 months ago
parent
commit
8ba361b293
52 changed files with 3975 additions and 43 deletions
  1. 94 0
      package-lock.json
  2. 2 0
      package.json
  3. BIN
      src/assets/images/icon_app.png
  4. BIN
      src/assets/images/logo-black.png
  5. 20 0
      src/common/common.js
  6. 507 0
      src/components/MCropper.vue
  7. 29 0
      src/components/MTeleport.vue
  8. 162 0
      src/components/MUpload.vue
  9. 3 1
      src/main.js
  10. 39 42
      src/router/index.js
  11. 82 0
      src/views/creation/api.js
  12. 383 0
      src/views/creation/edit/index.vue
  13. BIN
      src/views/creation/img/audio-banner-bg.png
  14. BIN
      src/views/creation/img/audio-bg.png
  15. BIN
      src/views/creation/img/audio-pan.png
  16. BIN
      src/views/creation/img/audio-point.png
  17. BIN
      src/views/creation/img/audio-shadow.png
  18. BIN
      src/views/creation/img/audio-zhen.png
  19. BIN
      src/views/creation/img/icon-cropper.png
  20. BIN
      src/views/creation/img/icon-delete.png
  21. BIN
      src/views/creation/img/icon-download.png
  22. BIN
      src/views/creation/img/icon-image.png
  23. BIN
      src/views/creation/img/icon-member.png
  24. BIN
      src/views/creation/img/icon-pause.png
  25. BIN
      src/views/creation/img/icon-play.png
  26. BIN
      src/views/creation/img/icon-share.png
  27. BIN
      src/views/creation/img/icon-z.png
  28. BIN
      src/views/creation/img/icon-zan-active.png
  29. BIN
      src/views/creation/img/icon-zan.png
  30. BIN
      src/views/creation/img/music_bg.png
  31. BIN
      src/views/creation/img/video-bg.png
  32. BIN
      src/views/creation/img/wave-1.png
  33. BIN
      src/views/creation/img/wave-2.png
  34. BIN
      src/views/creation/img/wx-no-bg.png
  35. BIN
      src/views/creation/img/wx-no-top.png
  36. 1049 0
      src/views/creation/index-share.vue
  37. 949 0
      src/views/creation/index.vue
  38. BIN
      src/views/creation/login-model/images/icon-close.png
  39. BIN
      src/views/creation/login-model/images/icon-password.png
  40. BIN
      src/views/creation/login-model/images/icon-phone.png
  41. BIN
      src/views/creation/login-model/images/login-bg.png
  42. 74 0
      src/views/creation/login-model/index.less
  43. 164 0
      src/views/creation/login-model/index.vue
  44. BIN
      src/views/creation/share-model/images/icon-download.png
  45. BIN
      src/views/creation/share-model/images/icon-friend-ring.png
  46. BIN
      src/views/creation/share-model/images/icon-friend.png
  47. BIN
      src/views/creation/share-model/images/icon-link.png
  48. BIN
      src/views/creation/share-model/images/icon-logo.png
  49. BIN
      src/views/creation/share-model/images/icon-wechat.png
  50. BIN
      src/views/creation/share-model/images/music-bg.png
  51. BIN
      src/views/creation/share-model/images/share-bg.png
  52. 418 0
      src/views/creation/share-model/index.vue

+ 94 - 0
package-lock.json

@@ -20,6 +20,7 @@
         "dayjs": "^1.8.31",
         "e-icon-picker": "^1.1.7",
         "es6-promise": "^4.2.8",
+        "html2canvas": "^1.4.1",
         "install": "^0.13.0",
         "js-storage": "^1.1.0",
         "lodash": "^4.17.21",
@@ -34,6 +35,7 @@
         "vue": "^2.6.10",
         "vue-amap": "^0.5.10",
         "vue-awesome-swiper": "^3.1.3",
+        "vue-cropper": "^0.6.5",
         "vue-qr": "^4.0.9",
         "vue-router": "^3.0.3",
         "vuex": "^3.0.1"
@@ -3741,6 +3743,14 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@@ -5367,6 +5377,14 @@
         "node": ">4"
       }
     },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/css-loader": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-1.0.1.tgz",
@@ -8204,6 +8222,18 @@
         "object-assign": "^4.0.1"
       }
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -14859,6 +14889,14 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@@ -15618,6 +15656,14 @@
         "node": ">= 0.4.0"
       }
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/uuid": {
       "version": "3.4.0",
       "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
@@ -15769,6 +15815,11 @@
       "integrity": "sha512-p2b/fvPJuPBnvU8027PAAuU5DiOzUn2lku8XLG/f6c8FU0N+/MXWZAlOuHhqd9e7+KIZitwe/c8qlmv7TglbTg==",
       "dev": true
     },
+    "node_modules/vue-cropper": {
+      "version": "0.6.5",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-0.6.5.tgz",
+      "integrity": "sha512-lSvY6IpeA/Tv/iPZ/FOkMHVRBPSlm7t57nuHEZFBMRNOH8ElvfqVlnHGDOAMlvPhh9gHiddiQoASS+fY0MFX0g=="
+    },
     "node_modules/vue-eslint-parser": {
       "version": "5.0.0",
       "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz",
@@ -20110,6 +20161,11 @@
         }
       }
     },
+    "base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
+    },
     "base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@@ -21499,6 +21555,14 @@
         "timsort": "^0.3.0"
       }
     },
+    "css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "css-loader": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-1.0.1.tgz",
@@ -23841,6 +23905,15 @@
         }
       }
     },
+    "html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "requires": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      }
+    },
     "htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -29427,6 +29500,14 @@
         }
       }
     },
+    "text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "requires": {
+        "utrie": "^1.0.2"
+      }
+    },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@@ -30063,6 +30144,14 @@
       "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
       "dev": true
     },
+    "utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "requires": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "uuid": {
       "version": "3.4.0",
       "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
@@ -30195,6 +30284,11 @@
       "integrity": "sha512-p2b/fvPJuPBnvU8027PAAuU5DiOzUn2lku8XLG/f6c8FU0N+/MXWZAlOuHhqd9e7+KIZitwe/c8qlmv7TglbTg==",
       "dev": true
     },
+    "vue-cropper": {
+      "version": "0.6.5",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-0.6.5.tgz",
+      "integrity": "sha512-lSvY6IpeA/Tv/iPZ/FOkMHVRBPSlm7t57nuHEZFBMRNOH8ElvfqVlnHGDOAMlvPhh9gHiddiQoASS+fY0MFX0g=="
+    },
     "vue-eslint-parser": {
       "version": "5.0.0",
       "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz",

+ 2 - 0
package.json

@@ -20,6 +20,7 @@
     "dayjs": "^1.8.31",
     "e-icon-picker": "^1.1.7",
     "es6-promise": "^4.2.8",
+    "html2canvas": "^1.4.1",
     "install": "^0.13.0",
     "js-storage": "^1.1.0",
     "lodash": "^4.17.21",
@@ -34,6 +35,7 @@
     "vue": "^2.6.10",
     "vue-amap": "^0.5.10",
     "vue-awesome-swiper": "^3.1.3",
+    "vue-cropper": "^0.6.5",
     "vue-qr": "^4.0.9",
     "vue-router": "^3.0.3",
     "vuex": "^3.0.1"

BIN
src/assets/images/icon_app.png


BIN
src/assets/images/logo-black.png


+ 20 - 0
src/common/common.js

@@ -206,4 +206,24 @@ const changeTwoDecimal = (amt) => {
   return s_x;
 };
 
+export const getGradeCh = (grade) => {
+  const template = ["一年级", "二年级", "三年级", "四年级", "五年级", "六年级", "七年级", "八年级", "九年级"];
+
+  return template[grade];
+};
+
+export const getSecondRPM = (second, type) => {
+  if (isNaN(second)) return "00:00";
+  const mm = Math.floor(second / 60)
+    .toString()
+    .padStart(2, "0");
+  const dd = Math.floor(second % 60)
+    .toString()
+    .padStart(2, "0");
+  if (type === "cn") {
+    return mm + "分" + dd + "秒";
+  } else {
+    return mm + ":" + dd;
+  }
+};
 export { browser, calcMinute, getSTD, getYMD, getDateList, _debounce, _throttle, validStudentUrl, changeTwoDecimal };

File diff suppressed because it is too large
+ 507 - 0
src/components/MCropper.vue


+ 29 - 0
src/components/MTeleport.vue

@@ -0,0 +1,29 @@
+<template>
+  <div>
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "teleport",
+  props: {
+    to: {
+      type: String,
+      required: true,
+    },
+  },
+  mounted() {
+    const toEl = document.querySelector(this.to);
+    if (toEl) {
+      toEl.appendChild(this.$el);
+    }
+  },
+  destroyed() {
+    const toEl = document.querySelector(this.to);
+    if (toEl) {
+      toEl.removeChild(this.$el);
+    }
+  },
+};
+</script>

+ 162 - 0
src/components/MUpload.vue

@@ -0,0 +1,162 @@
+<template>
+  <div class="m-upload">
+    <van-uploader v-if="!cropper" v-model="list" :preview-full-image="false" :preview-image="previewImage" :deletable="deletable" :before-read="beforeRead" :after-read="afterRead" class="muploader" accept="image/*">
+      <slot name="default"></slot>
+    </van-uploader>
+
+    <div class="mcropper" v-if="cropper">
+      <div class="mcropperSection" v-if="previewImage">
+        <van-image v-if="value || defaultImg" fit="cover" position="center" class="uploadImg" :src="value || defaultImg" />
+        <van-icon v-else name="photograph" size="32" />
+      </div>
+      <slot name="default"></slot>
+
+      <MCropper v-if="cropper" :option="options" @getFile="getFile" />
+    </div>
+  </div>
+</template>
+
+<script>
+import MCropper from "@/components/MCropper";
+import { getUploadSign, onOnlyFileUpload } from "@/helpers/oss-file-upload";
+export default {
+  name: "m-upload",
+  components: { MCropper },
+  props: {
+    value: String,
+    cropper: Boolean,
+    deletable: Boolean,
+    defaultImg: String,
+    previewImage: {
+      type: Boolean,
+      default: true,
+    },
+    options: Object,
+  },
+  data() {
+    return {
+      fileUrl: this.value,
+      list: this.value ? [{ url: this.value }] : [],
+    };
+  },
+  methods: {
+    beforeRead(file) {
+      const isLt2M = file.size / 1024 / 1024 < 5;
+
+      if (!isLt2M) {
+        this.$toast("图片大小不能超过 5MB");
+        return false;
+      }
+      // if (name && name == "file") {
+      //   let arr = ["psd", "ai", "eps", "svg", "sketch"];
+      //   let typeArr = file.name.split(".");
+      //   let type = typeArr[typeArr.length - 1];
+      //   if (arr.indexOf(type) == -1) {
+      //     this.$toast("请上传正确的文件");
+      //     return false;
+      //   }
+      // }
+      return true;
+    },
+    async afterRead(file) {
+      try {
+        file.status = "uploading";
+        file.message = "";
+        let tempName = file.file.name || "";
+        const fileName = tempName && tempName.replace(/ /gi, "_");
+        let key = new Date().getTime() + fileName;
+        let objTemp = {
+          filename: key,
+          bucketName: "gym",
+          postData: {
+            filename: key,
+            acl: "public-read",
+            key: key,
+            unknowValueField: [],
+          },
+        };
+
+        const res = await getUploadSign(objTemp);
+        const obj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: key,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: "public-read",
+          name: key,
+          file: file.file,
+        };
+        const uploadUrl = await onOnlyFileUpload(this.ossUploadUrl, obj);
+        file.status = "done";
+        this.fileUrl = uploadUrl;
+        this.$emit("input", uploadUrl);
+      } catch (e) {
+        //
+        file.status = "failed";
+        file.message = "上传失败";
+        this.list[origin] = [];
+        this.fileUrl = "";
+        return false;
+      }
+    },
+    async getFile(file) {
+      let tempName = file.name || "";
+      const fileName = tempName && tempName.replace(/ /gi, "_");
+      let key = new Date().getTime() + fileName;
+      let objTemp = {
+        filename: key,
+        bucketName: "gym",
+        postData: {
+          filename: key,
+          acl: "public-read",
+          key: key,
+          unknowValueField: [],
+        },
+      };
+
+      const res = await getUploadSign(objTemp);
+      const obj = {
+        policy: res.data.policy,
+        signature: res.data.signature,
+        key: key,
+        KSSAccessKeyId: res.data.kssAccessKeyId,
+        acl: "public-read",
+        name: key,
+        file: file,
+      };
+      const uploadUrl = await onOnlyFileUpload(this.ossUploadUrl, obj);
+      this.$emit("input", uploadUrl);
+    },
+  },
+};
+</script>
+
+<style lang="less">
+.mcropper {
+  position: relative;
+  .mcropperSection {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    width: 80px;
+    height: 80px;
+    color: #ccc;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 3;
+  }
+
+  .cropper {
+    //   position: absolute;
+    //   top: 0;
+    //   left: 0;
+    //   right: 0;
+    //   bottom: 0;
+    z-index: 9;
+    position: relative;
+  }
+}
+</style>

+ 3 - 1
src/main.js

@@ -53,6 +53,7 @@ import {
   Uploader,
   Stepper,
   SubmitBar,
+  Slider,
 } from "vant";
 Vue.use(Button)
   .use(Icon)
@@ -101,7 +102,8 @@ Vue.use(Button)
   .use(Empty)
   .use(Uploader)
   .use(Stepper)
-  .use(SubmitBar);
+  .use(SubmitBar)
+  .use(Slider);
 
 Vue.config.productionTip = false;
 

+ 39 - 42
src/router/index.js

@@ -18,10 +18,7 @@ let defaultRouter = [
   {
     path: "/queryFortuneBag",
     name: "FortuneBag",
-    component: () =>
-      import(
-        /* webpackChunkName:'CallNames'*/ "@/views/teacher/queryList/queryFortuneBag"
-      ),
+    component: () => import(/* webpackChunkName:'CallNames'*/ "@/views/teacher/queryList/queryFortuneBag"),
     meta: {
       descrition: "页面查看",
       weight: 1, // 页面权重
@@ -30,10 +27,7 @@ let defaultRouter = [
   {
     path: "/queryActiveList",
     name: "ActiveList",
-    component: () =>
-      import(
-        /* webpackChunkName:'CallNames'*/ "@/views/teacher/queryList/queryActiveList"
-      ),
+    component: () => import(/* webpackChunkName:'CallNames'*/ "@/views/teacher/queryList/queryActiveList"),
     meta: {
       descrition: "页面查看",
       weight: 1, // 页面权重
@@ -42,8 +36,7 @@ let defaultRouter = [
   {
     path: "/guide",
     name: "guide",
-    component: () =>
-      import(/* webpackChunkName:'CallNames'*/ "@/views/rules/guide"),
+    component: () => import(/* webpackChunkName:'CallNames'*/ "@/views/rules/guide"),
     meta: {
       descrition: "投屏引导",
       weight: 1, // 页面权重
@@ -52,10 +45,7 @@ let defaultRouter = [
   {
     path: "/applyActive",
     name: "applyActive",
-    component: () =>
-      import(
-        /* webpackChunkName:'applyActive' */ "@/views/applyActive/index.vue"
-      ),
+    component: () => import(/* webpackChunkName:'applyActive' */ "@/views/applyActive/index.vue"),
     meta: {
       descrition: "考级活动",
       weight: 0,
@@ -64,10 +54,7 @@ let defaultRouter = [
   {
     path: "/registerProtocol",
     name: "registerProtocol",
-    component: () =>
-      import(
-        /* webpackChunkName:'registerProtocol' */ "@/views/protocol/registerProtocol.vue"
-      ),
+    component: () => import(/* webpackChunkName:'registerProtocol' */ "@/views/protocol/registerProtocol.vue"),
     meta: {
       descrition: "查看协议",
       weight: 0,
@@ -76,8 +63,7 @@ let defaultRouter = [
   {
     path: "/auth",
     name: "auth",
-    component: () =>
-      import(/* webpackChunkName:'auth' */ "@/views/protocol/auth.vue"),
+    component: () => import(/* webpackChunkName:'auth' */ "@/views/protocol/auth.vue"),
     meta: {
       descrition: "实名认证",
       weight: 0,
@@ -86,10 +72,7 @@ let defaultRouter = [
   {
     path: "/afterClassEvaluate",
     name: "afterClassEvaluate",
-    component: () =>
-      import(
-        /* webpackChunkName:'afterClassEvaluate' */ "@/views/afterClassEvaluate/index.vue"
-      ),
+    component: () => import(/* webpackChunkName:'afterClassEvaluate' */ "@/views/afterClassEvaluate/index.vue"),
     meta: {
       descrition: "课后评价",
       weight: 0,
@@ -98,10 +81,7 @@ let defaultRouter = [
   {
     path: "/afterClassEvaluateDetail",
     name: "afterClassEvaluateDetail",
-    component: () =>
-      import(
-        /* webpackChunkName:'afterClassEvaluateDetail' */ "@/views/afterClassEvaluate/detail.vue"
-      ),
+    component: () => import(/* webpackChunkName:'afterClassEvaluateDetail' */ "@/views/afterClassEvaluate/detail.vue"),
     meta: {
       descrition: "课后详情",
       weight: 0,
@@ -110,8 +90,7 @@ let defaultRouter = [
   {
     path: "/massMessage",
     name: "massMessage",
-    component: () =>
-      import(/* webpackChunkName:'massMessage' */ "@/views/massMessage/index"),
+    component: () => import(/* webpackChunkName:'massMessage' */ "@/views/massMessage/index"),
     meta: {
       descrition: "群发消息",
       weight: 2,
@@ -120,10 +99,7 @@ let defaultRouter = [
   {
     path: "/massOperation",
     name: "massOperation",
-    component: () =>
-      import(
-        /* webpackChunkName:'massOperation' */ "@/views/massMessage/operation"
-      ),
+    component: () => import(/* webpackChunkName:'massOperation' */ "@/views/massMessage/operation"),
     meta: {
       descrition: "定时消息",
       weight: 2,
@@ -132,21 +108,42 @@ let defaultRouter = [
   {
     path: "/abnormalCourse",
     name: "abnormalCourse",
-    component: () =>
-      import(/* webpackChunkName:'massOperation' */ "@/views/abnormalCourse"),
+    component: () => import(/* webpackChunkName:'massOperation' */ "@/views/abnormalCourse"),
     meta: {
       descrition: "异常课程",
       weight: 2,
     },
   },
+  {
+    path: "/creation",
+    name: "classroom",
+    component: () => import(/* webpackChunkName:'creation'*/ "@/views/creation/index"),
+    meta: {
+      clearAuth: 1,
+      title: "作品详情",
+    },
+  },
+  {
+    path: "/creation-edit",
+    name: "creation-edit",
+    component: () => import(/* webpackChunkName:'creationedit'*/ "@/views/creation/edit/index"),
+    meta: {
+      clearAuth: 1,
+      title: "作品详情",
+    },
+  },
+  {
+    path: "/shareCreation",
+    name: "shareCreation",
+    component: () => import(/* webpackChunkName: "creationindex-share" */ "@/views/creation/index-share"),
+    meta: {
+      descrition: "作品详情",
+      weight: 8, // 页面权重
+    },
+  }, //
 ];
 
-defaultRouter = defaultRouter
-  .concat(TeacherRouter)
-  .concat(AppRouter)
-  .concat(AuditionRouter)
-  .concat(ServiceRouter)
-  .concat(ShopRouter);
+defaultRouter = defaultRouter.concat(TeacherRouter).concat(AppRouter).concat(AuditionRouter).concat(ServiceRouter).concat(ShopRouter);
 
 const router = new Router({
   // mode: 'history',

+ 82 - 0
src/views/creation/api.js

@@ -0,0 +1,82 @@
+import request from "@/helpers/request";
+
+/** 详情 */
+export const api_userMusicDetail = (params) => {
+  return request({
+    url: `/userMusic/detail/${params}`,
+    method: "get",
+    hideLoading: true,
+  });
+};
+
+/** 开放详情 */
+export const api_openUserMusicDetail = (params) => {
+  // return request.get(`/open/userMusic/detail/${params}`);
+  return request({
+    url: `/open/userMusic/detail/${params}`,
+    method: "get",
+    hideLoading: true,
+  });
+};
+
+/**  点赞分页 */
+export const api_userMusicStarPage = (params) => {
+  return request.post(`/userMusicStar/page`, {
+    ...params,
+  });
+};
+
+/**  分享推荐作品 */
+export const api_openUserMusicPage = (params) => {
+  return request.post(`/open/userMusic/page`, {
+    ...params,
+  });
+};
+
+/** 点赞 */
+export const api_userMusicStar = (params) => {
+  // return request.post("/userMusicStar/star", {
+  //   ...params,
+  // });
+  return request({
+    url: "/userMusicStar/star",
+    hideLoading: true,
+    method: "post",
+    data: params,
+  });
+};
+
+/** 保存草稿/发布作品 */
+export const api_userMusicSave = (params) => {
+  // return request.post("/userMusic/save", {
+  //   data: params,
+  // });
+  return request({
+    method: "post",
+    url: "/userMusic/save",
+    data: params,
+  });
+};
+
+/** 删除 */
+export const api_userMusicRemove = (params) => {
+  // return request.post("/userMusic/remove", {
+  //   requestType: "form",
+  //   params,
+  // });
+  return request({
+    method: "post",
+    url: "/userMusic/remove",
+    requestType: "form",
+    data: params,
+  });
+};
+
+/** 授权验证 */
+export const api_verification = (params) => {
+  return request.get("/api-auth/checkToken", {
+    headers: {
+      "content-type": "application/x-www-form-urlencoded",
+    },
+  });
+};

+ 383 - 0
src/views/creation/edit/index.vue

@@ -0,0 +1,383 @@
+<template>
+  <div style="padding-top: .12rem; overflow: hidden; min-height: 100vh;">
+    <div class="section sectionVideo" v-if="playType === 'Video'">
+      <img :src="videoImg || videoBg" class="videoBg" />
+      <div class="btnGroup">
+        <MUpload class="muploader imgCover" cropper :previewImage="false" v-model="videoImg" :options="{ fixedNumber: [16, 9] }">
+          <template #default>
+            <div class="btnImg" @click="onCropper">
+              相册获取封面
+            </div>
+          </template>
+        </MUpload>
+
+        <div class="btnCropper" @click="onCropper">
+          视频截取封面
+        </div>
+      </div>
+    </div>
+
+    <div class="section">
+      <van-field :rows="4" autosize type="textarea" maxlength="150" placeholder="我发布了一首演奏作品,快来听听吧~" showWordLimit v-model="desc" />
+    </div>
+
+    <div class="section sectionFile ">
+      <div :class="['uploadImg2']">
+        <!-- <img class="muploader showImg" :src="img || this.musicBg" /> -->
+        <MUpload class="muploader mupload uploadSingleImg opacity" cropper :defaultImg="musicBg" v-model="img" :previewImage="true" />
+      </div>
+      <div class="musicDetail">
+        <p class="musicName">{{ musicDetail.musicSheetName }}</p>
+        <p class="username">{{ musicDetail.username }}</p>
+      </div>
+    </div>
+
+    <div class="btnGroup">
+      <van-button type="primary" round block color="#2DC7AA" @click="onSubmit">
+        {{ musicDetail.type === "FORMAL" ? "保存" : "发布" }}
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { postMessage } from "@/helpers/native-message";
+import { api_userMusicSave, api_userMusicDetail } from "../api";
+import MUpload from "@/components/MUpload";
+export default {
+  name: "creation-edit",
+  components: { MUpload },
+  data() {
+    return {
+      files: [],
+      id: this.$route.query.id,
+      musicDetail: {},
+      playType: "",
+      desc: "",
+      videoImg: "", // 视频封面
+      img: "",
+      videoBg: require("../img/video-bg.png"),
+      musicBg: require("../img/music_bg.png"),
+    };
+  },
+  async mounted() {
+    try {
+      const { data } = await api_userMusicDetail(this.id);
+      this.musicDetail = data;
+      this.desc = data.desc;
+      this.videoImg = data.videoImg;
+      this.img = data.img;
+
+      if (data?.videoUrl.lastIndexOf("mp4") !== -1) {
+        this.playType = "Video";
+      } else {
+        this.playType = "Audio";
+      }
+    } catch {
+      //
+    }
+  },
+  methods: {
+    async onSubmit() {
+      try {
+        await api_userMusicSave({
+          id: this.id,
+          img: this.img,
+          videoImg: this.videoImg,
+          desc: this.desc || "我发布了一首演奏作品,快来听听吧~",
+          musicPracticeRecordId: this.musicDetail.musicPracticeRecordId,
+          type: "FORMAL",
+        });
+
+        this.$router.back();
+      } catch {
+        //
+      }
+    },
+    // 截图
+    onCropper() {
+      postMessage(
+        {
+          api: "videoCrop",
+          content: {
+            url: this.musicDetail.videoUrl,
+          },
+        },
+        (res) => {
+          if (res?.content.videoCover) {
+            this.videoImg = res.content.videoCover;
+          }
+        }
+      );
+    },
+  },
+};
+</script>
+
+<style lang="less">
+.sectionVideo {
+  position: relative;
+  line-height: 0;
+  overflow: visible !important;
+  margin-bottom: 24px !important;
+
+  .videoBg {
+    width: 100%;
+    height: 160px;
+    object-fit: cover;
+    border-radius: 10px;
+  }
+
+  .btnGroup {
+    position: absolute;
+    left: 50%;
+    bottom: -12px;
+    background: linear-gradient(180deg, rgba(128, 158, 200, 0.59) 0%, rgba(58, 101, 162, 0.59) 100%);
+    border-radius: 15px;
+    height: 30px;
+    // transform: translate(-50%);
+    display: flex;
+    align-items: center;
+    flex: 1;
+    font-size: 12px;
+    font-weight: 500;
+    color: #ffffff;
+    line-height: 17px;
+    width: 300px;
+    margin: 0 0 0 -150px;
+
+    & > div {
+      flex: 1;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .btnCropper {
+      border-left: 1px solid #fff;
+      line-height: 1;
+
+      &::before {
+        content: "";
+        width: 16px;
+        height: 16px;
+        display: inline-block;
+        background: url("../img/icon-cropper.png") no-repeat center;
+        background-size: contain;
+        margin-right: 4px;
+      }
+    }
+
+    .imgCover {
+      .mcropperSection {
+        display: none;
+      }
+    }
+
+    .btnImg {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      margin: 0;
+      line-height: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &::before {
+        content: "";
+        width: 16px;
+        height: 16px;
+        display: inline-block;
+        background: url("../img/icon-image.png") no-repeat center;
+        background-size: contain;
+        margin-right: 4px;
+        vertical-align: text-top;
+      }
+
+      // &::after {
+      //   content: "相册获取封面";
+      //   font-size: 12px;
+      //   color: #fff;
+      // }
+
+      img {
+        display: none;
+      }
+    }
+  }
+}
+
+.section {
+  margin: 12px 13px;
+  background: #ffffff;
+  border-radius: 10px;
+  font-size: 16px;
+
+  .van-field__control::placeholder {
+    color: #aaa;
+  }
+}
+
+.sectionFile {
+  padding: 12px;
+  display: flex;
+
+  .van-uploader {
+    border-radius: 8px;
+    overflow: hidden;
+  }
+
+  .van-uploader__upload,
+  .van-uploader__preview-image {
+    margin: 0;
+    width: 0.62rem !important;
+    height: 0.62rem !important;
+  }
+
+  .muploader {
+    position: relative;
+    z-index: 8;
+    width: 0.62rem;
+    height: 0.62rem;
+    border: none;
+    margin: 0;
+    border-radius: 8px;
+    background-color: #ffffff;
+  }
+
+  .uploadImg2 {
+    position: relative;
+    border-radius: 8px;
+    margin-right: 16px;
+
+    .showImg {
+      position: absolute;
+      left: 0;
+      top: 0;
+    }
+    .tip {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      z-index: 10;
+      background: #000000;
+      opacity: 0.37;
+      font-size: 13px;
+      color: #ffffff;
+      line-height: 18px;
+      text-align: center;
+      border-radius: 0 0 8px 8px;
+      pointer-events: none;
+    }
+
+    &::before {
+      content: "";
+      background: url("../img/audio-pan.png") no-repeat center;
+      background-size: contain;
+      position: absolute;
+      top: 0;
+      right: -6px;
+      z-index: 4;
+      width: 60px;
+      height: 60px;
+    }
+  }
+
+  .musicDetail {
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+
+    .musicName {
+      font-size: 16px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 22px;
+    }
+
+    .username {
+      padding-top: 4px;
+      font-size: 14px;
+      color: #777777;
+      line-height: 20px;
+    }
+  }
+}
+
+.showInfoImg {
+  width: 0.62rem;
+  height: 0.62rem;
+}
+
+.btnGroup {
+  margin: 32px 24px 12px;
+  font-weight: 500;
+  font-size: 16px;
+}
+
+.uploadSingleImg {
+  .mcropperSection,
+  .cropper {
+    width: 0.62rem !important;
+    height: 0.62rem !important;
+    border-radius: 6px !important;
+    overflow: hidden !important;
+  }
+
+  .mcropperSection .uploadImg {
+    margin-right: 0;
+  }
+  .cropper::before {
+    content: "选封面";
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+    background: #000000;
+    opacity: 0.37;
+    line-height: 20px;
+    height: 20px;
+    font-size: 13px;
+    color: #ffffff;
+    line-height: 18px;
+    text-align: center;
+    border-radius: 0 0 8px 8px;
+    pointer-events: none;
+  }
+  &.opacity {
+    // &::before {
+    //   display: none;
+    // }
+
+    .upbtn > input {
+      width: 0.62rem !important;
+      height: 0.62rem !important;
+    }
+  }
+}
+
+// .uploadShowImg::after {
+//   content: "选封面";
+//   position: absolute;
+//   bottom: 0;
+//   left: 0;
+//   right: 0;
+//   z-index: 10;
+//   background: #000000;
+//   opacity: 0.37;
+//   line-height: 20px;
+//   height: 20px;
+//   font-size: 13px;
+//   color: #ffffff;
+//   line-height: 18px;
+//   text-align: center;
+//   border-radius: 0 0 8px 8px;
+//   pointer-events: none;
+// }
+</style>

BIN
src/views/creation/img/audio-banner-bg.png


BIN
src/views/creation/img/audio-bg.png


BIN
src/views/creation/img/audio-pan.png


BIN
src/views/creation/img/audio-point.png


BIN
src/views/creation/img/audio-shadow.png


BIN
src/views/creation/img/audio-zhen.png


BIN
src/views/creation/img/icon-cropper.png


BIN
src/views/creation/img/icon-delete.png


BIN
src/views/creation/img/icon-download.png


BIN
src/views/creation/img/icon-image.png


BIN
src/views/creation/img/icon-member.png


BIN
src/views/creation/img/icon-pause.png


BIN
src/views/creation/img/icon-play.png


BIN
src/views/creation/img/icon-share.png


BIN
src/views/creation/img/icon-z.png


BIN
src/views/creation/img/icon-zan-active.png


BIN
src/views/creation/img/icon-zan.png


BIN
src/views/creation/img/music_bg.png


BIN
src/views/creation/img/video-bg.png


BIN
src/views/creation/img/wave-1.png


BIN
src/views/creation/img/wave-2.png


BIN
src/views/creation/img/wx-no-bg.png


BIN
src/views/creation/img/wx-no-top.png


+ 1049 - 0
src/views/creation/index-share.vue

@@ -0,0 +1,1049 @@
+<template>
+  <div class="creation">
+    <div class="playSection">
+      <videoTcplayer v-if="playType === 'Video'" :src="musicDetail.videoUrl" :poster="musicDetail.videoImg || videoBg" />
+      <div class="audioSection" v-if="playType === 'Audio'">
+        <div class="audioContainer">
+          <div class="waveActive" :style="{ width: audioWidth + '%' }"></div>
+          <div class="waveDefault"></div>
+        </div>
+
+        <div class="audioBox">
+          <div :class="['audioPan', paused ? 'imgRotate' : '']">
+            <van-image class="audioImg" :src="musicDetail.img || musicBg" />
+          </div>
+          <i class="audioPoint"></i>
+          <i :class="['audioZhen', paused && 'active']"></i>
+        </div>
+        <div class="controls" @click="onControls">
+          <div class="actions">
+            <div class="actionBtn" @click="onToggleAudio">
+              <img v-if="paused" src="./img/icon-play.png" />
+              <img v-else src="./img/icon-pause.png" />
+            </div>
+          </div>
+          <div class="slider">
+            <van-slider :step="0.01" class="timeProgress" v-model="currentTime" :max="duration" @input="handleChangeTime" @drag-start="dragStatus = true" @drag-end="dragStatus = false" />
+          </div>
+          <div class="time">
+            <div>{{ getSecondRPM(currentTime) }}</div>
+            <span>/</span>
+            <div>{{ getSecondRPM(duration) }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <van-cell class="userSection" center :border="false">
+      <template #icon>
+        <van-image class="userLogo" :src="musicDetail.avatar" />
+      </template>
+      <template #title>
+        <div class="userInfo">
+          <p class="name">
+            <span>{{ musicDetail.username }}</span>
+            <img v-if="musicDetail.vipFlag" src="./img/icon-member.png" class="iconMember" />
+          </p>
+          <p class="sub">
+            {{ musicDetail.subjectName }}
+            {{ getGradeCh(musicDetail.currentGradeNum - 1) }}
+          </p>
+        </div>
+      </template>
+      <template #default>
+        <div :class="['zan', musicDetail.starFlag && 'zanActive']" @click="onStarChange">
+          <img src="./img/icon-zan-active.png" v-if="musicDetail.starFlag" class="iconZan" />
+          <img src="./img/icon-zan.png" v-else class="iconZan" />
+
+          {{ musicDetail.likeNum }}
+        </div>
+      </template>
+    </van-cell>
+
+    <div class="musicSection">
+      <div class="musicName">
+        <span class="musicTag">曲目名称</span>
+        {{ musicDetail.musicSheetName }}
+      </div>
+      <div class="musicDesc">{{ musicDetail.desc }}</div>
+    </div>
+
+    <div class="likeSection">
+      <div class="likeTitle">推荐作品</div>
+
+      <van-list v-if="listState.dataShow" class="container containerInformation" :finished="listState.finished" finished-text=" " :immediate-check="false" @load="getStarList()">
+        <div class="cellGroup">
+          <div v-for="(item, index) in list" :key="index" class="cell" @click="onDetail(item)">
+            <div class="cellImg">
+              <van-image class="cellImage" :src="item.img || musicBg" fit="cover" />
+
+              <div class="iconZan">{{ item.likeNum }}</div>
+            </div>
+            <div class="cellTitle van-ellipsis">
+              {{ item.musicSheetName }}
+            </div>
+            <div class="users">
+              <van-image :src="item.avatar" class="userImg" />
+              <span class="name">{{ item.username }}</span>
+            </div>
+          </div>
+        </div>
+      </van-list>
+
+      <MEmpty v-else msg="暂无数据" />
+    </div>
+
+    <van-popup v-model="loginStatus" style="background: transparent;overflow: inherit ">
+      <LoginModel @close="() => (this.loginStatus = false)" @confirm="onConfirm" />
+    </van-popup>
+
+    <van-popup v-model="messageStatus" class="wxPopupDialog">
+      <div class="popupContainer">
+        <p class="title1">温馨提示</p>
+        <p class="popupTips">{{ message }}</p>
+      </div>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import dayjs from "dayjs";
+import { getSecondRPM, browser, getGradeCh } from "@/common/common";
+import videoTcplayer from "@/components/video-tcplayer";
+import MEmpty from "@/components/MEmpty";
+import { postMessage } from "@/helpers/native-message";
+import { api_userMusicRemove, api_openUserMusicPage, api_userMusicStar, api_openUserMusicDetail } from "./api";
+import LoginModel from "./login-model";
+const audioDom = new Audio();
+audioDom.controls = true;
+audioDom.style.width = "100%";
+audioDom.className = "audio";
+export default {
+  components: { videoTcplayer, MEmpty, LoginModel },
+  data() {
+    return {
+      videoBg: require("./img/video-bg.png"),
+      musicBg: require("./img/music_bg.png"),
+      id: this.$route.query.id,
+      loginTag: false, // 是否登录标识
+      loginStatus: false,
+      playType: "", // 播放类型
+      musicDetail: {},
+      timer: null,
+      paused: true,
+      audioWidth: 0,
+      currentTime: 0,
+      duration: 0.1,
+      loop: false,
+      dragStatus: false, // 是否开始拖动
+      isClick: false,
+      list: [],
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false,
+      },
+      params: {
+        page: 1,
+        rows: 20,
+      },
+      messageStatus: false,
+      message: "",
+    };
+  },
+  async mounted() {
+    document.title = "作品详情";
+    try {
+      const res = await api_openUserMusicDetail(this.id);
+
+      this.musicDetail = res.data || {};
+
+      this.getStarList();
+      // 判断是视频还是音频
+      if (res.data.videoUrl.lastIndexOf("mp4") !== -1) {
+        this.playType = "Video";
+      } else {
+        this.playType = "Audio";
+        // 初始化
+        this.$nextTick(() => {
+          this.initAudio();
+        });
+      }
+    } catch (e) {
+      //
+      if (e.code === 999) {
+        this.messageStatus = true;
+        this.message = e.msg;
+        // this.$dialog
+        //   .alert({
+        //     message: e.msg,
+        //     theme: "round-button",
+        //     confirmButtonColor: "#2DC7AA",
+        //   })
+        //   .then(() => {
+        //     if (browser().isApp) {
+        //       postMessage({
+        //         api: "goBack",
+        //       });
+        //     } else {
+        //       this.$router.back();
+        //     }
+        //   });
+        // return;
+      }
+    }
+
+    // window.onpagehide = function() {
+    //   if (audioDom) {
+    //     audioDom.pause();
+    //     this.paused = audioDom.paused;
+    //   }
+    // };
+
+    // 设置隐藏属性和改变可见属性的事件的名称
+    var hidden, visibilityChange;
+    if (typeof document.hidden !== "undefined") {
+      // Opera 12.10 and Firefox 18 and later support
+      hidden = "hidden";
+      visibilityChange = "visibilitychange";
+    } else if (typeof document.msHidden !== "undefined") {
+      hidden = "msHidden";
+      visibilityChange = "msvisibilitychange";
+    } else if (typeof document.webkitHidden !== "undefined") {
+      hidden = "webkitHidden";
+      visibilityChange = "webkitvisibilitychange";
+    }
+
+    // 如果页面是隐藏状态,则暂停视频
+    // 如果页面是展示状态,则播放视频
+    const that = this;
+    function handleVisibilityChange() {
+      if (document[hidden]) {
+        if (audioDom) {
+          audioDom.pause();
+          that.paused = audioDom.paused;
+        }
+      }
+    }
+
+    // 如果浏览器不支持addEventListener 或 Page Visibility API 给出警告
+    if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
+      console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
+    } else {
+      // 处理页面可见属性的改变
+      document.addEventListener(visibilityChange, handleVisibilityChange, false);
+    }
+  },
+  methods: {
+    onDetail(item) {
+      if (audioDom) {
+        audioDom.currentTime = 0;
+        audioDom.pause();
+        this.paused = audioDom.paused;
+      }
+      this.$router.push({
+        path: "/shareCreation",
+        query: {
+          id: item.id,
+        },
+      });
+    },
+    /** 改变播放时间 */
+    handleChangeTime(val) {
+      this.currentTime = val;
+      clearTimeout(this.timer);
+      this.timer = setTimeout(() => {
+        // audioRef.value.currentTime = val;
+        audioDom.currentTime = val;
+        this.timer = null;
+      }, 60);
+    },
+
+    // 切换音频播放
+    onToggleAudio(e) {
+      e.stopPropagation();
+      if (audioDom.paused) {
+        audioDom.play();
+      } else {
+        audioDom.pause();
+      }
+
+      this.paused = audioDom.paused;
+    },
+    async onStarChange() {
+      try {
+        await api_userMusicStar({
+          userMusicId: this.id,
+          star: !this.musicDetail.starFlag,
+        });
+
+        this.musicDetail.starFlag = !this.musicDetail.starFlag;
+        if (this.musicDetail.starFlag) {
+          this.musicDetail.likeNum += 1;
+        } else {
+          this.musicDetail.likeNum -= 1;
+        }
+      } catch (e) {
+        //
+        if (e.code === 403) {
+          this.loginStatus = true;
+        }
+      }
+    },
+
+    // 获取列表
+    async getStarList() {
+      try {
+        if (this.isClick) return;
+        this.isClick = true;
+        const res = await api_openUserMusicPage({
+          type: "FORMAL",
+          exclusionId: this.id,
+          sort: 1,
+          ...this.params,
+        });
+        this.listState.loading = false;
+        const result = res.data || {};
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.current === 1) {
+          return;
+        }
+        this.list = this.list.concat(result.rows || []);
+        this.listState.finished = result.current >= result.pages;
+        this.params.page = result.current + 1;
+        this.listState.dataShow = this.list.length > 0;
+        this.isClick = false;
+      } catch {
+        this.listState.dataShow = false;
+        this.listState.finished = true;
+        this.isClick = false;
+      }
+    },
+
+    initAudio() {
+      audioDom.src = this.musicDetail.videoUrl;
+      audioDom.load();
+      audioDom.oncanplaythrough = () => {
+        this.paused = audioDom.paused;
+        this.duration = audioDom.duration;
+      };
+      // 播放时监听
+      audioDom.addEventListener("timeupdate", () => {
+        this.duration = audioDom.duration;
+        this.currentTime = audioDom.currentTime;
+        const rate = (this.currentTime / this.duration) * 100;
+        this.audioWidth = rate > 100 ? 100 : rate;
+      });
+      audioDom.addEventListener("ended", () => {
+        this.paused = audioDom.paused;
+      });
+    },
+    async onConfirm(val) {
+      this.loginTag = val;
+      this.loginStatus = false;
+      const { data } = await api_openUserMusicDetail(this.id);
+      this.musicDetail = data;
+    },
+    // 删除作品
+    async onDelete() {
+      try {
+        await api_userMusicRemove({ id: this.id });
+
+        setTimeout(() => {
+          this.deleteStatus = false;
+          this.$toast("删除成功");
+        }, 100);
+
+        setTimeout(() => {
+          if (browser().isApp) {
+            postMessage({
+              api: "goBack",
+            });
+          } else {
+            this.$router.back();
+          }
+        }, 1200);
+      } catch {
+        //
+      }
+    },
+    // 下载
+    async onDownload() {
+      postMessage({
+        api: "saveFile",
+        content: {
+          url: this.musicDetail.videoUrl,
+        },
+      });
+    },
+    onDayjs(time) {
+      return dayjs(time).format("YYYY-MM-DD HH:mm");
+    },
+    getGradeCh,
+    getSecondRPM,
+    onControls(e) {
+      e.stopPropagation();
+    },
+  },
+  destory() {
+    if (audioDom) {
+      audioDom.pause();
+      this.paused = audioDom.paused;
+    }
+  },
+  watch: {
+    $router(to) {
+      this.id = to.query.id;
+      this.playType = "";
+      this.params.page = 1;
+      if (audioDom) {
+        audioDom.currentTime = 0;
+        audioDom.pause();
+        this.paused = audioDom.paused;
+      }
+      this.list = [];
+      this.__init();
+    },
+  },
+};
+</script>
+
+<style scoped lang="less">
+.creation {
+  min-height: 100vh;
+  background-color: #f8f8f8;
+}
+
+/deep/ .vjs-poster {
+  background-size: cover;
+}
+
+/deep/ .video-js .vjs-progress-control:hover .vjs-progress-holder {
+  font-size: inherit !important;
+}
+
+/deep/ .video-js .vjs-slider:focus {
+  box-shadow: none !important;
+  text-shadow: none !important;
+}
+.playSection {
+  min-height: 1.75rem;
+
+  /deep/ .vjs-poster {
+    background-size: cover;
+  }
+
+  /deep/ .video-js .vjs-progress-control:hover .vjs-progress-holder {
+    font-size: inherit !important;
+  }
+
+  /deep/ .video-js .vjs-slider:focus {
+    box-shadow: none !important;
+    text-shadow: none !important;
+  }
+}
+
+@keyframes rotateImg {
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.audioSection {
+  position: relative;
+  background: url("./img/audio-banner-bg.png") no-repeat top center;
+  background-size: cover;
+  height: 1.75rem;
+
+  .audioContainer {
+    position: absolute;
+    top: 0;
+    left: 50%;
+    width: 1.96rem;
+    height: 0.35rem;
+    transform: translate(-50%, 0.6rem);
+
+    .waveActive,
+    .waveDefault {
+      width: 100%;
+      height: 100%;
+    }
+
+    .waveDefault {
+      position: absolute;
+      top: 0;
+      left: 0;
+      background: url("./img/wave-1.png") no-repeat center left;
+      background-size: cover;
+    }
+
+    .waveActive {
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 1;
+      background: url("./img/wave-2.png") no-repeat center left;
+      background-size: cover;
+    }
+  }
+
+  .audioBox {
+    position: absolute;
+    left: 50%;
+    transform: translate(-50%, 50%);
+    z-index: 2;
+    width: 0.74rem;
+    height: 0.75rem;
+    background: url("./img/audio-bg.png") no-repeat center;
+    background-size: contain;
+
+    .audioPan {
+      position: absolute;
+      left: 0.08rem;
+      top: 0.06rem;
+      z-index: 8;
+      width: 0.59rem;
+      height: 0.6rem;
+      background: url("./img/audio-pan.png") no-repeat center;
+      background-size: contain;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      animation: rotateImg 6s linear infinite;
+
+      &.imgRotate {
+        animation-play-state: paused;
+      }
+    }
+
+    .audioImg {
+      width: 0.32rem;
+      height: 0.32rem;
+      border-radius: 50%;
+      overflow: hidden;
+    }
+
+    .audioPoint {
+      position: absolute;
+      z-index: 9;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 0.08rem;
+      height: 0.08rem;
+      background: url("./img/audio-point.png") no-repeat center;
+      background-size: contain;
+    }
+
+    .audioZhen {
+      position: absolute;
+      z-index: 9;
+      right: -0.04rem;
+      top: -0.33rem;
+      width: 0.26rem;
+      height: 0.87rem;
+      background: url("./img/audio-zhen.png") no-repeat center;
+      background-size: contain;
+      transition: transform 0.5s ease-in-out;
+
+      &.active {
+        transform: rotate(92deg) translate3d(0, 0, 3px);
+        transition: transform 0.5s ease-in-out;
+      }
+    }
+  }
+}
+
+.controls {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  height: 0.44rem;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  flex-direction: row;
+  transition: all 0.5s;
+  padding: 0 0.12rem;
+
+  & > div {
+    display: flex;
+    align-items: center;
+  }
+
+  &.hide {
+    transform: translateY(100%);
+  }
+
+  .actionBtn {
+    line-height: 0;
+    margin-right: 0.04rem;
+
+    img {
+      width: 0.14rem;
+      height: 0.14rem;
+      margin-bottom: -0.02rem;
+    }
+  }
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    flex: 1;
+    min-width: 0.86rem;
+    font-size: 0.12rem;
+    color: #131415;
+    line-height: 0.2rem;
+
+    span {
+      font-size: 0.12rem;
+      padding: 0 0.01rem;
+    }
+  }
+
+  .slider {
+    width: 100%;
+    margin: 0 0.12rem;
+    // --van-slider-bar-height: 4px;
+    // --van-slider-button-width: 0.13rem !important;
+    // --van-slider-button-height: 0.13rem !important;
+    // --van-slider-inactive-background: #fff;
+    // --van-slider-inactive-background-color: #fff;
+    // --van-slider-active-background: #2DC7AA !important;
+
+    /deep/ .van-slider {
+      height: 0.04rem;
+    }
+
+    /deep/ .van-slider__button {
+      width: 0.13rem;
+      height: 0.13rem;
+    }
+
+    /deep/ .van-loading {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.userSection {
+  padding: 0.15rem 0.12rem !important;
+  background-color: transparent !important;
+
+  .userLogo {
+    width: 0.44rem;
+    height: 0.44rem;
+    border: 0.01rem solid #ffffff;
+    margin-right: 0.1rem;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .userInfo {
+    .name {
+      display: flex;
+      align-items: center;
+      font-size: 0.16rem;
+      font-weight: 500;
+      color: #333333;
+      line-height: 0.22rem;
+
+      span {
+        display: inline-block;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 1rem;
+      }
+    }
+
+    .sub {
+      padding-top: 0.01rem;
+      font-size: 0.12rem;
+      color: #777777;
+      line-height: 0.17rem;
+    }
+
+    .iconMember {
+      margin-left: 0.06rem;
+      width: 0.14rem;
+      height: 0.14rem;
+    }
+  }
+
+  .zan {
+    background: #ffffff;
+    border-radius: 0.13rem;
+    font-size: 0.14rem;
+    color: #777777;
+    line-height: 0.2rem;
+    padding: 0.04rem 0.09rem 0.03rem;
+    display: inline-flex;
+    align-items: center;
+
+    &.zanActive {
+      background: #f7eeee;
+      color: #ff6a6a;
+    }
+
+    .iconZan {
+      width: 0.18rem;
+      height: 0.18rem;
+      margin-right: 0.01rem;
+    }
+  }
+}
+
+.musicSection {
+  margin: 0 0.13rem 0.12rem;
+  padding: 0.14rem 0.12rem;
+  background: #ffffff;
+  border-radius: 0.1rem;
+
+  .musicName {
+    font-size: 0.15rem;
+    font-weight: 500;
+    color: #333333;
+    line-height: 0.21rem;
+    // display: flex;
+    // align-items: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    max-width: 100%;
+
+    .musicTag {
+      margin-right: 0.06rem;
+      padding: 0.01rem 0.06rem;
+      font-size: 0.12rem;
+      color: #ff7b31;
+      line-height: 0.17rem;
+      background: rgba(255, 166, 115, 0.07);
+      border-radius: 0.09rem;
+      border: 0.01rem solid #ffbf9a;
+      font-weight: 400;
+      vertical-align: text-bottom;
+      display: inline-block;
+    }
+  }
+
+  .musicDesc {
+    padding-top: 0.08rem;
+    font-size: 0.14rem;
+    color: #777777;
+    line-height: 0.2rem;
+  }
+}
+
+.likeSection {
+  margin: 0 0.13rem 0.12rem;
+  background: #ffffff;
+  border-radius: 0.1rem;
+  padding: 0.1rem 0.12rem;
+
+  .likeTitle {
+    display: flex;
+    align-items: center;
+    font-size: 0.17rem;
+    font-weight: 600;
+    color: #333333;
+    line-height: 0.24rem;
+    padding-bottom: 0.08rem;
+
+    &::before {
+      display: inline-block;
+      content: "";
+      width: 0.04rem;
+      height: 0.14rem;
+      border-radius: 0.01rem;
+      background: linear-gradient(to bottom, #59e5d5, #2dc7aa);
+      margin-right: 0.06rem;
+    }
+  }
+}
+
+.likeItem {
+  padding: 0.16rem 0;
+
+  .userLogo {
+    border-radius: 50%;
+    overflow: hidden;
+    width: 0.42rem;
+    height: 0.42rem;
+    margin-right: 0.07rem;
+  }
+
+  .userInfo {
+    .name {
+      font-size: 0.16rem;
+      font-weight: 500;
+      color: #333333;
+      line-height: 0.22rem;
+    }
+
+    .sub {
+      padding-top: 0.01rem;
+      font-size: 0.13rem;
+      color: #777777;
+      line-height: 0.18rem;
+    }
+  }
+
+  .time {
+    font-size: 0.13rem;
+    color: #777777;
+    line-height: 0.18rem;
+  }
+}
+
+.bottomSection {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background-color: #fff;
+  padding: 0.15rem 0.12rem calc(0.15rem + env(safe-area-inset-bottom));
+
+  .bottomShare {
+    display: flex;
+    align-items: center;
+
+    p {
+      padding: 0 0.15rem;
+      text-align: center;
+      line-height: 0;
+
+      &:first-child {
+        padding-left: 0.05rem;
+      }
+    }
+
+    img {
+      width: 0.18rem;
+      height: 0.18rem;
+    }
+
+    span {
+      padding-top: 0.08rem;
+      font-size: 0.12rem;
+      color: #333333;
+      line-height: 0.17rem;
+      display: block;
+    }
+  }
+
+  .btnEdit {
+    font-size: 0.14rem;
+    font-weight: 500;
+    background: #2dc7aa;
+    color: #ffffff;
+    line-height: 0.22rem;
+    min-width: 0.8rem;
+    height: 0.3rem;
+    border: none;
+  }
+}
+
+.popupContainer {
+  width: 80%;
+
+  .popupContent {
+    padding: 0.29rem 0 0.25rem;
+    text-align: center;
+    font-size: 0.18rem;
+    font-weight: 500;
+    color: #333333;
+    line-height: 0.25rem;
+  }
+
+  .popupBtnGroup {
+    text-align: center;
+    margin-bottom: 0.22rem;
+
+    .van-button {
+      height: 0.4rem;
+      font-size: 0.16rem;
+      font-weight: 400 !important;
+      line-height: 0.22rem;
+      min-width: 1.22rem;
+
+      &:last-child {
+        margin-left: 0.1rem;
+        background: #2dc7aa;
+        border: none;
+      }
+    }
+  }
+}
+
+.cellGroup {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.cell {
+  // display: flex;
+  // flex-direction: column;
+  width: 0.96rem;
+  margin-right: 0.18rem;
+  margin-bottom: 0.18rem;
+
+  &:nth-child(3n + 3) {
+    margin-right: 0;
+  }
+
+  .cellImg {
+    position: relative;
+    width: 0.88rem;
+    height: 0.88rem;
+
+    &::before {
+      content: "";
+      position: absolute;
+      right: -0.06rem;
+      top: 0.03rem;
+      z-index: 8;
+      width: 0.84rem;
+      height: 0.84rem;
+      background: url("./img/audio-pan.png") no-repeat center;
+      background-size: contain;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .iconZan {
+      position: absolute;
+      bottom: 0.04rem;
+      left: 0.04rem;
+      z-index: 10;
+      padding: 0.03rem;
+      background: rgba(67, 67, 67, 0.3);
+      border-radius: 0.08rem;
+      backdrop-filter: blur(0.04rem);
+
+      font-size: 0.09rem;
+      font-weight: 500;
+      color: #ffffff;
+      line-height: 0.13rem;
+      display: flex;
+      align-items: center;
+
+      &::before {
+        content: "";
+        display: inline-block;
+        width: 0.12rem;
+        height: 0.12rem;
+        background: url("./img/icon-z.png") no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+
+  .cellImage {
+    position: relative;
+    width: 0.88rem;
+    height: 0.88rem;
+    border-radius: 0.12rem;
+    overflow: hidden;
+    z-index: 9;
+
+    img {
+      border-radius: 0.12rem;
+    }
+  }
+
+  .cellTitle {
+    font-size: 0.13rem;
+    color: #131415;
+    line-height: 0.18rem;
+    margin: 0.08rem 0 0.06rem;
+  }
+
+  .users {
+    display: flex;
+    align-items: center;
+
+    .userImg {
+      width: 0.2rem;
+      height: 0.2rem;
+      border-radius: 50%;
+      overflow: hidden;
+      margin-right: 0.04rem;
+      flex-shrink: 0;
+    }
+
+    .name {
+      font-size: 0.12rem;
+      color: #402424;
+      line-height: 0.14rem;
+    }
+  }
+}
+
+.sticky-section {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+.wxPopupDialog {
+  // position: relative;
+  overflow: initial;
+  width: 88%;
+  background: transparent;
+
+  // margin-top: -160px;
+  &::before {
+    position: absolute;
+    content: " ";
+    top: -0.2rem;
+    left: 50%;
+    margin-left: -0.51rem;
+    display: inline-block;
+    background: url("./img/wx-no-top.png") no-repeat top center;
+    background-size: contain;
+    width: 1.02rem;
+    height: 0.84rem;
+  }
+}
+
+.popupContainer {
+  background: url("./img/wx-no-bg.png") no-repeat top center #fff;
+  background-size: cover;
+  border-radius: 0.2rem;
+  overflow: hidden;
+  padding-bottom: 0.16rem;
+  text-align: center;
+  width: 100%;
+
+  .title1 {
+    padding-top: 0.57rem;
+    text-align: center;
+    font-size: 0.18rem;
+    font-weight: 500;
+    color: #3b2300;
+  }
+
+  .popupTips {
+    padding-top: 0.16rem;
+    padding-bottom: 0.16rem;
+    text-align: center;
+    font-size: 0.15rem;
+    color: #777777;
+    line-height: 0.21rem;
+  }
+
+  .button {
+    padding: 0 0.32rem;
+    height: 0.3rem;
+    font-size: 0.16rem;
+    font-size: 0.14rem;
+    color: #777;
+    border-color: #e7e7e7;
+  }
+}
+</style>

+ 949 - 0
src/views/creation/index.vue

@@ -0,0 +1,949 @@
+<template>
+  <div class="creation">
+    <div class="playSection">
+      <videoTcplayer v-if="playType === 'Video'" :src="musicDetail.videoUrl" :poster="musicDetail.videoImg || videoBg" />
+      <div class="audioSection" v-if="playType === 'Audio'">
+        <div class="audioContainer">
+          <div class="waveActive" :style="{ width: audioWidth + '%' }"></div>
+          <div class="waveDefault"></div>
+        </div>
+
+        <div class="audioBox">
+          <div :class="['audioPan', paused ? 'imgRotate' : '']">
+            <van-image class="audioImg" :src="musicDetail.img || musicBg" />
+          </div>
+          <i class="audioPoint"></i>
+          <i :class="['audioZhen', paused && 'active']"></i>
+        </div>
+        <div class="controls" @click="onControls">
+          <div class="actions">
+            <div class="actionBtn" @click="onToggleAudio">
+              <img v-if="paused" src="./img/icon-play.png" />
+              <img v-else src="./img/icon-pause.png" />
+            </div>
+          </div>
+          <div class="slider">
+            <van-slider :step="0.01" class="timeProgress" v-model="currentTime" :max="duration" @input="handleChangeTime" @drag-start="dragStatus = true" @drag-end="dragStatus = false" />
+          </div>
+          <div class="time">
+            <div>{{ getSecondRPM(currentTime) }}</div>
+            <span>/</span>
+            <div>{{ getSecondRPM(duration) }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <van-cell class="userSection" center :border="false">
+      <template #icon>
+        <van-image class="userLogo" :src="musicDetail.avatar" />
+      </template>
+      <template #title>
+        <div class="userInfo">
+          <p class="name">
+            <span>{{ musicDetail.username }}</span>
+            <img v-if="musicDetail.vipFlag" src="./img/icon-member.png" class="iconMember" />
+          </p>
+          <p class="sub">
+            {{ musicDetail.subjectName }}
+            {{ getGradeCh(musicDetail.currentGradeNum - 1) }}
+          </p>
+        </div>
+      </template>
+      <template #default>
+        <div :class="['zan', 'zanActive']">
+          <img src="./img/icon-zan-active.png" class="iconZan" />
+          {{ musicDetail.likeNum }}
+        </div>
+      </template>
+    </van-cell>
+
+    <div class="musicSection">
+      <div class="musicName">
+        <span class="musicTag">曲目名称</span>
+        {{ musicDetail.musicSheetName }}
+      </div>
+      <div class="musicDesc">{{ musicDetail.desc }}</div>
+    </div>
+
+    <div class="likeSection">
+      <div class="likeTitle">点赞记录</div>
+
+      <van-list v-if="listState.dataShow" class="container containerInformation" :finished="listState.finished" finished-text=" " :immediate-check="false" @load="getStarList()">
+        <!-- {state.list.map((item: any, index: number) => ( -->
+        <van-cell v-for="(item, index) in list" :key="index" class="likeItem" :border="list.length - 1 == index ? false : true">
+          <template #icon>
+            <van-image :src="item.userAvatar" class="userLogo" />
+          </template>
+          <template #title>
+            <div class="userInfo">
+              <p class="name">{{ item.userName }}</p>
+              <p class="sub">
+                {{ item.subjectName }}
+                {{ getGradeCh(item.currentGradeNum - 1) }}
+              </p>
+            </div>
+          </template>
+          <template #default>
+            <div class="time">
+              {{ onDayjs(item.createTime) }}
+            </div>
+          </template>
+        </van-cell>
+      </van-list>
+
+      <MEmpty v-else msg="暂无数据" style="margin-bottom: 0.5rem" />
+
+      <div class="sticky-section">
+        <div class="bottomSection">
+          <div class="bottomShare">
+            <p @click="onDownload">
+              <img src="./img/icon-download.png" />
+              <span>下载</span>
+            </p>
+            <p @click="shareStatus = true">
+              <img src="./img/icon-share.png" />
+              <span>分享</span>
+            </p>
+            <p @click="deleteStatus = true">
+              <img src="./img/icon-delete.png" />
+              <span>删除</span>
+            </p>
+          </div>
+          <van-button round class="btnEdit" type="primary" @click="onDetail"> 编辑 </van-button>
+        </div>
+      </div>
+    </div>
+
+    <van-popup position="bottom" v-model="shareStatus" style="background: transparent">
+      <ShareModel :musicDetail="musicDetail" @close="shareStatus = false" />
+    </van-popup>
+
+    <van-popup v-model="deleteStatus" round class="popupContainer">
+      <p class="popupContent">确定删除吗?</p>
+      <div class="popupBtnGroup">
+        <van-button round @click="() => (deleteStatus = false)"> 取消 </van-button>
+        <van-button round type="primary" @click="onDelete"> 确定 </van-button>
+      </div>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import dayjs from "dayjs";
+import { getSecondRPM, browser, getGradeCh } from "@/common/common";
+import videoTcplayer from "@/components/video-tcplayer";
+import MEmpty from "@/components/MEmpty";
+import { postMessage } from "@/helpers/native-message";
+import { api_userMusicRemove, api_userMusicDetail, api_userMusicStarPage } from "./api";
+import ShareModel from "./share-model";
+const audioDom = new Audio();
+audioDom.controls = true;
+audioDom.style.width = "100%";
+audioDom.className = "audio";
+export default {
+  components: { videoTcplayer, MEmpty, ShareModel },
+  data() {
+    return {
+      id: this.$route.query.id,
+      videoBg: require("./img/video-bg.png"),
+      musicBg: require("./img/music_bg.png"),
+      deleteStatus: false,
+      shareStatus: false,
+      playType: "", // 播放类型
+      musicDetail: {},
+      timer: null,
+      audioWidth: 0,
+      paused: true,
+      currentTime: 0,
+      duration: 0.1,
+      loop: false,
+      dragStatus: false, // 是否开始拖动
+      isClick: false,
+      list: [],
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false,
+      },
+      params: {
+        page: 1,
+        rows: 20,
+      },
+    };
+  },
+  async mounted() {
+    document.title = "作品详情";
+    try {
+      const res = await api_userMusicDetail(this.id);
+
+      this.musicDetail = res.data || {};
+
+      this.getStarList();
+      // 判断是视频还是音频
+      if (res.data.videoUrl.lastIndexOf("mp4") !== -1) {
+        this.playType = "Video";
+      } else {
+        this.playType = "Audio";
+        // 初始化
+        this.$nextTick(() => {
+          this.initAudio();
+        });
+      }
+    } catch (e) {
+      //
+      if (e.code === 999) {
+        this.$dialog
+          .alert({
+            message: e.msg,
+            theme: "round-button",
+            confirmButtonColor: "#2DC7AA",
+          })
+          .then(() => {
+            if (browser().isApp) {
+              postMessage({
+                api: "goBack",
+              });
+            } else {
+              this.$router.back();
+            }
+          });
+        return;
+      }
+    }
+    // 设置隐藏属性和改变可见属性的事件的名称
+    var hidden, visibilityChange;
+    if (typeof document.hidden !== "undefined") {
+      // Opera 12.10 and Firefox 18 and later support
+      hidden = "hidden";
+      visibilityChange = "visibilitychange";
+    } else if (typeof document.msHidden !== "undefined") {
+      hidden = "msHidden";
+      visibilityChange = "msvisibilitychange";
+    } else if (typeof document.webkitHidden !== "undefined") {
+      hidden = "webkitHidden";
+      visibilityChange = "webkitvisibilitychange";
+    }
+
+    // 如果页面是隐藏状态,则暂停视频
+    // 如果页面是展示状态,则播放视频
+    const that = this;
+    function handleVisibilityChange() {
+      if (document[hidden]) {
+        if (audioDom) {
+          audioDom.pause();
+          that.paused = audioDom.paused;
+        }
+      }
+    }
+
+    // 如果浏览器不支持addEventListener 或 Page Visibility API 给出警告
+    if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
+      console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
+    } else {
+      // 处理页面可见属性的改变
+      document.addEventListener(visibilityChange, handleVisibilityChange, false);
+    }
+  },
+  methods: {
+    onDetail() {
+      if (audioDom) {
+        audioDom.pause();
+        this.paused = audioDom.paused;
+      }
+      this.$router.push({
+        path: "/creation-edit",
+        query: {
+          id: this.id,
+        },
+      });
+    },
+    /** 改变播放时间 */
+    handleChangeTime(val) {
+      this.currentTime = val;
+      clearTimeout(this.timer);
+      this.timer = setTimeout(() => {
+        // audioRef.value.currentTime = val;
+        audioDom.currentTime = val;
+        this.timer = null;
+      }, 60);
+    },
+
+    // 切换音频播放
+    onToggleAudio(e) {
+      e.stopPropagation();
+      if (audioDom.paused) {
+        audioDom.play();
+      } else {
+        audioDom.pause();
+      }
+
+      this.paused = audioDom.paused;
+    },
+
+    // 获取列表
+    async getStarList() {
+      try {
+        if (this.isClick) return;
+        this.isClick = true;
+        const res = await api_userMusicStarPage({
+          userMusicId: this.id,
+          ...this.params,
+        });
+        this.listState.loading = false;
+        const result = res.data || {};
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.current === 1) {
+          return;
+        }
+        this.list = this.list.concat(result.rows || []);
+        this.listState.finished = result.current >= result.pages;
+        this.params.page = result.current + 1;
+        this.listState.dataShow = this.list.length > 0;
+        this.isClick = false;
+      } catch {
+        this.listState.dataShow = false;
+        this.listState.finished = true;
+        this.isClick = false;
+      }
+    },
+
+    initAudio() {
+      audioDom.src = this.musicDetail.videoUrl;
+      audioDom.load();
+      audioDom.oncanplaythrough = () => {
+        this.paused = audioDom.paused;
+        this.duration = audioDom.duration;
+      };
+      // 播放时监听
+      audioDom.addEventListener("timeupdate", () => {
+        this.duration = audioDom.duration;
+        this.currentTime = audioDom.currentTime;
+        const rate = (this.currentTime / this.duration) * 100;
+        this.audioWidth = rate > 100 ? 100 : rate;
+      });
+      audioDom.addEventListener("ended", () => {
+        this.paused = audioDom.paused;
+      });
+    },
+    // 删除作品
+    async onDelete() {
+      try {
+        await api_userMusicRemove({ id: this.id });
+
+        setTimeout(() => {
+          this.deleteStatus = false;
+          this.$toast("删除成功");
+        }, 100);
+
+        setTimeout(() => {
+          if (browser().isApp) {
+            postMessage({
+              api: "back",
+            });
+          } else {
+            this.$router.back();
+          }
+        }, 1200);
+      } catch {
+        //
+      }
+    },
+    // 下载
+    async onDownload() {
+      postMessage({
+        api: "saveFile",
+        content: {
+          url: this.musicDetail.videoUrl,
+        },
+      });
+    },
+    onDayjs(time) {
+      return dayjs(time).format("YYYY-MM-DD HH:mm");
+    },
+    getGradeCh,
+    getSecondRPM,
+    onControls(e) {
+      e.stopPropagation();
+    },
+  },
+  destory() {
+    if (audioDom) {
+      audioDom.pause();
+      this.paused = audioDom.paused;
+    }
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.creation {
+  min-height: 100vh;
+  background-color: #f8f8f8;
+}
+
+/deep/ .vjs-poster {
+  background-size: cover;
+}
+
+/deep/ .video-js .vjs-progress-control:hover .vjs-progress-holder {
+  font-size: inherit !important;
+}
+
+/deep/ .video-js .vjs-slider:focus {
+  box-shadow: none !important;
+  text-shadow: none !important;
+}
+
+.playSection {
+  min-height: 1.75rem;
+}
+
+@keyframes rotateImg {
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.audioSection {
+  position: relative;
+  background: url("./img/audio-banner-bg.png") no-repeat top center;
+  background-size: cover;
+  height: 1.75rem;
+
+  .audioContainer {
+    position: absolute;
+    top: 0;
+    left: 50%;
+    width: 1.96rem;
+    height: 0.35rem;
+    transform: translate(-50%, 0.6rem);
+
+    .waveActive,
+    .waveDefault {
+      width: 100%;
+      height: 100%;
+    }
+
+    .waveDefault {
+      position: absolute;
+      top: 0;
+      left: 0;
+      background: url("./img/wave-1.png") no-repeat center left;
+      background-size: cover;
+    }
+
+    .waveActive {
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 1;
+      background: url("./img/wave-2.png") no-repeat center left;
+      background-size: cover;
+    }
+  }
+
+  .audioBox {
+    position: absolute;
+    left: 50%;
+    transform: translate(-50%, 50%);
+    z-index: 2;
+    width: 0.74rem;
+    height: 0.75rem;
+    background: url("./img/audio-bg.png") no-repeat center;
+    background-size: contain;
+
+    .audioPan {
+      position: absolute;
+      left: 0.08rem;
+      top: 0.06rem;
+      z-index: 8;
+      width: 0.59rem;
+      height: 0.6rem;
+      background: url("./img/audio-pan.png") no-repeat center;
+      background-size: contain;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      animation: rotateImg 6s linear infinite;
+
+      &.imgRotate {
+        animation-play-state: paused;
+      }
+    }
+
+    .audioImg {
+      width: 0.32rem;
+      height: 0.32rem;
+      border-radius: 50%;
+      overflow: hidden;
+    }
+
+    .audioPoint {
+      position: absolute;
+      z-index: 9;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 0.08rem;
+      height: 0.08rem;
+      background: url("./img/audio-point.png") no-repeat center;
+      background-size: contain;
+    }
+
+    .audioZhen {
+      position: absolute;
+      z-index: 9;
+      right: -0.04rem;
+      top: -0.33rem;
+      width: 0.26rem;
+      height: 0.87rem;
+      background: url("./img/audio-zhen.png") no-repeat center;
+      background-size: contain;
+      transition: transform 0.5s ease-in-out;
+
+      &.active {
+        transform: rotate(92deg) translate3d(0, 0, 3px);
+        transition: transform 0.5s ease-in-out;
+      }
+    }
+  }
+}
+
+.controls {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  height: 0.44rem;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  flex-direction: row;
+  transition: all 0.5s;
+  padding: 0 0.12rem;
+
+  & > div {
+    display: flex;
+    align-items: center;
+  }
+
+  &.hide {
+    transform: translateY(100%);
+  }
+
+  .actionBtn {
+    line-height: 0;
+    margin-right: 0.04rem;
+
+    img {
+      width: 0.14rem;
+      height: 0.14rem;
+      margin-bottom: -0.02rem;
+    }
+  }
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    flex: 1;
+    min-width: 0.86rem;
+    font-size: 0.12rem;
+    color: #131415;
+    line-height: 0.2rem;
+
+    span {
+      font-size: 0.12rem;
+      padding: 0 0.01rem;
+    }
+  }
+
+  .slider {
+    width: 100%;
+    margin: 0 0.12rem;
+    // --van-slider-bar-height: 4px;
+    // --van-slider-button-width: 0.13rem !important;
+    // --van-slider-button-height: 0.13rem !important;
+    // --van-slider-inactive-background: #fff;
+    // --van-slider-inactive-background-color: #fff;
+    // --van-slider-active-background: #2DC7AA !important;
+
+    /deep/ .van-slider {
+      height: 0.04rem;
+    }
+
+    /deep/ .van-slider__button {
+      width: 0.13rem;
+      height: 0.13rem;
+    }
+
+    /deep/ .van-loading {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.userSection {
+  padding: 0.15rem 0.12rem !important;
+  background-color: transparent !important;
+
+  .userLogo {
+    width: 0.44rem;
+    height: 0.44rem;
+    border: 0.01rem solid #ffffff;
+    margin-right: 0.1rem;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .userInfo {
+    .name {
+      display: flex;
+      align-items: center;
+      font-size: 0.16rem;
+      font-weight: 500;
+      color: #333333;
+      line-height: 0.22rem;
+
+      span {
+        display: inline-block;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 1rem;
+      }
+    }
+
+    .sub {
+      padding-top: 0.01rem;
+      font-size: 0.12rem;
+      color: #777777;
+      line-height: 0.17rem;
+    }
+
+    .iconMember {
+      margin-left: 0.06rem;
+      width: 0.14rem;
+      height: 0.14rem;
+    }
+  }
+
+  .zan {
+    background: #ffffff;
+    border-radius: 0.13rem;
+    font-size: 0.14rem;
+    color: #777777;
+    line-height: 0.2rem;
+    padding: 0.04rem 0.09rem 0.03rem;
+    display: inline-flex;
+    align-items: center;
+
+    &.zanActive {
+      background: #f7eeee;
+      color: #ff6a6a;
+    }
+
+    .iconZan {
+      width: 0.18rem;
+      height: 0.18rem;
+      margin-right: 0.01rem;
+    }
+  }
+}
+
+.musicSection {
+  margin: 0 0.13rem 0.12rem;
+  padding: 0.14rem 0.12rem;
+  background: #ffffff;
+  border-radius: 0.1rem;
+
+  .musicName {
+    font-size: 0.15rem;
+    font-weight: 500;
+    color: #333333;
+    line-height: 0.21rem;
+    // display: flex;
+    // align-items: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    max-width: 100%;
+
+    .musicTag {
+      margin-right: 0.06rem;
+      padding: 0.01rem 0.06rem;
+      font-size: 0.12rem;
+      color: #ff7b31;
+      line-height: 0.17rem;
+      background: rgba(255, 166, 115, 0.07);
+      border-radius: 0.09rem;
+      border: 0.01rem solid #ffbf9a;
+      font-weight: 400;
+      vertical-align: text-bottom;
+      display: inline-block;
+    }
+  }
+
+  .musicDesc {
+    padding-top: 0.08rem;
+    font-size: 0.14rem;
+    color: #777777;
+    line-height: 0.2rem;
+  }
+}
+
+.likeSection {
+  margin: 0 0.13rem 0.12rem;
+  background: #ffffff;
+  border-radius: 0.1rem;
+  padding: 0.1rem 0.12rem;
+  margin-bottom: calc(0.77rem + env(safe-area-inset-bottom));
+
+  .likeTitle {
+    display: flex;
+    align-items: center;
+    font-size: 0.17rem;
+    font-weight: 600;
+    color: #333333;
+    line-height: 0.24rem;
+    padding-bottom: 0.08rem;
+
+    &::before {
+      display: inline-block;
+      content: "";
+      width: 0.04rem;
+      height: 0.14rem;
+      border-radius: 0.01rem;
+      background: linear-gradient(to bottom, #59e5d5, #2dc7aa);
+      margin-right: 0.06rem;
+    }
+  }
+}
+
+.likeItem {
+  padding: 0.16rem 0;
+
+  .userLogo {
+    border-radius: 50%;
+    overflow: hidden;
+    width: 0.42rem;
+    height: 0.42rem;
+    margin-right: 0.07rem;
+  }
+
+  .userInfo {
+    .name {
+      font-size: 0.16rem;
+      font-weight: 500;
+      color: #333333;
+      line-height: 0.22rem;
+    }
+
+    .sub {
+      padding-top: 0.01rem;
+      font-size: 0.13rem;
+      color: #777777;
+      line-height: 0.18rem;
+    }
+  }
+
+  .time {
+    font-size: 0.13rem;
+    color: #777777;
+    line-height: 0.18rem;
+  }
+}
+
+.bottomSection {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background-color: #fff;
+  padding: 0.15rem 0.12rem calc(0.15rem + env(safe-area-inset-bottom));
+
+  .bottomShare {
+    display: flex;
+    align-items: center;
+
+    p {
+      padding: 0 0.15rem;
+      text-align: center;
+      line-height: 0;
+
+      &:first-child {
+        padding-left: 0.05rem;
+      }
+    }
+
+    img {
+      width: 0.18rem;
+      height: 0.18rem;
+    }
+
+    span {
+      padding-top: 0.08rem;
+      font-size: 0.12rem;
+      color: #333333;
+      line-height: 0.17rem;
+      display: block;
+    }
+  }
+
+  .btnEdit {
+    font-size: 0.14rem;
+    font-weight: 500;
+    background: #2dc7aa;
+    color: #ffffff;
+    line-height: 0.22rem;
+    min-width: 0.8rem;
+    height: 0.3rem;
+    border: none;
+  }
+}
+
+.popupContainer {
+  width: 80%;
+
+  .popupContent {
+    padding: 0.29rem 0 0.25rem;
+    text-align: center;
+    font-size: 0.18rem;
+    font-weight: 500;
+    color: #333333;
+    line-height: 0.25rem;
+  }
+
+  .popupBtnGroup {
+    text-align: center;
+    margin-bottom: 0.22rem;
+
+    .van-button {
+      height: 0.4rem;
+      font-size: 0.16rem;
+      font-weight: 400 !important;
+      line-height: 0.22rem;
+      min-width: 1.22rem;
+
+      &:last-child {
+        margin-left: 0.1rem;
+        background: #2dc7aa;
+        border: none;
+      }
+    }
+  }
+}
+
+.cellGroup {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.cell {
+  // display: flex;
+  // flex-direction: column;
+  width: 0.96rem;
+  margin-right: 0.18rem;
+  margin-bottom: 0.18rem;
+
+  &:nth-child(3n + 3) {
+    margin-right: 0;
+  }
+
+  .cellImg {
+    position: relative;
+    width: 0.88rem;
+    height: 0.88rem;
+
+    &::before {
+      content: "";
+      position: absolute;
+      right: -0.06rem;
+      top: 0.03rem;
+      z-index: 8;
+      width: 0.84rem;
+      height: 0.84rem;
+      background: url("./img/audio-pan.png") no-repeat center;
+      background-size: contain;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .iconZan {
+      position: absolute;
+      bottom: 0.04rem;
+      left: 0.04rem;
+      z-index: 10;
+      padding: 0.03rem;
+      background: rgba(67, 67, 67, 0.3);
+      border-radius: 0.08rem;
+      backdrop-filter: blur(0.04rem);
+
+      font-size: 0.09rem;
+      font-weight: 500;
+      color: #ffffff;
+      line-height: 0.13rem;
+      display: flex;
+      align-items: center;
+
+      &::before {
+        content: "";
+        display: inline-block;
+        width: 0.12rem;
+        height: 0.12rem;
+        background: url("./img/icon-z.png") no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+
+  .cellImage {
+    position: relative;
+    width: 0.88rem;
+    height: 0.88rem;
+    border-radius: 0.12rem;
+    overflow: hidden;
+    z-index: 9;
+
+    img {
+      border-radius: 0.12rem;
+    }
+  }
+
+  .cellTitle {
+    font-size: 0.13rem;
+    color: #131415;
+    line-height: 0.18rem;
+    margin: 0.08rem 0 0.06rem;
+  }
+
+  .users {
+    display: flex;
+    align-items: center;
+
+    .userImg {
+      width: 0.2rem;
+      height: 0.2rem;
+      border-radius: 50%;
+      overflow: hidden;
+      margin-right: 0.04rem;
+      flex-shrink: 0;
+    }
+
+    .name {
+      font-size: 0.12rem;
+      color: #402424;
+      line-height: 0.14rem;
+    }
+  }
+}
+
+.sticky-section {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+</style>

BIN
src/views/creation/login-model/images/icon-close.png


BIN
src/views/creation/login-model/images/icon-password.png


BIN
src/views/creation/login-model/images/icon-phone.png


BIN
src/views/creation/login-model/images/login-bg.png


+ 74 - 0
src/views/creation/login-model/index.less

@@ -0,0 +1,74 @@
+.loginModel {
+  background: url('./images/login-bg.png') no-repeat center;
+  background-size: contain;
+  width: 306px;
+  height: 370px;
+
+  .iconClose {
+    width: 24px;
+    height: 24px;
+    position: absolute;
+    top: 30px;
+    right: -12px;
+    background: url('./images/icon-close.png') no-repeat center;
+    background-size: contain;
+    z-index: 1;
+  }
+}
+
+.loginSection {
+  padding: 126px 16px 0;
+}
+
+.fieldSection {
+  padding-bottom: 18px;
+
+  :global {
+    .van-field {
+      background: #F2F4F8;
+      border-radius: 24px;
+      padding: 11px 16px;
+      margin-bottom: 12px;
+    }
+
+    .van-field__left-icon {
+      display: flex;
+      align-items: center;
+    }
+
+    .van-field__control::placeholder {
+      color: #97B9C3;
+    }
+  }
+
+  .icon {
+    width: 20px;
+    height: 20px;
+  }
+
+  .codeText {
+    font-size: 14px;
+    color: #2DC7AA;
+  }
+}
+
+.btnGroup {
+  :global {
+    .van-button {
+      font-size: 16px;
+      font-weight: 600;
+      color: #FFFFFF;
+    }
+  }
+
+  .btnText {
+    display: block;
+    padding-top: 16px;
+    text-align: center;
+    font-size: 16px;
+    font-weight: 500;
+    color: #74949E;
+    line-height: 22px;
+    text-align: center;
+  }
+}

File diff suppressed because it is too large
+ 164 - 0
src/views/creation/login-model/index.vue


BIN
src/views/creation/share-model/images/icon-download.png


BIN
src/views/creation/share-model/images/icon-friend-ring.png


BIN
src/views/creation/share-model/images/icon-friend.png


BIN
src/views/creation/share-model/images/icon-link.png


BIN
src/views/creation/share-model/images/icon-logo.png


BIN
src/views/creation/share-model/images/icon-wechat.png


BIN
src/views/creation/share-model/images/music-bg.png


BIN
src/views/creation/share-model/images/share-bg.png


+ 418 - 0
src/views/creation/share-model/index.vue

@@ -0,0 +1,418 @@
+<template>
+  <div class="shareModel">
+    <div class="shareContent" id="shareContent">
+      <img src="./images/share-bg.png" class="shareBg" />
+      <div class="share_content__title">
+        <p class="large">我在管乐迷发布了演奏作品</p>
+        <p class="small">赶快扫码看看吧!</p>
+      </div>
+
+      <div class="share_music__container, sectionFile">
+        <div class="uploadImg">
+          <img src="../img/audio-pan.png" class="audioPan" crossorigin="anonymous" />
+          <img class="muploader" :src="musicDetail.img ? musicDetail.img + '?t=' + new Date().getTime() : musicBg" crossorigin="anonymous" />
+        </div>
+        <div class="musicDetail">
+          <p class="musicName van-ellipsis">
+            {{ musicDetail.musicSheetName }}
+          </p>
+          <p class="username">演奏者:{{ musicDetail.username }}</p>
+        </div>
+      </div>
+
+      <div class="downloadSection">
+        <div class="qrcode">
+          <!-- <canvas ref="canvasRef" class="qrcodeCanvas"></canvas> -->
+          <vue-qr v-if="config" :text="config" :margin="10" :size="250" />
+          <img src="../../../assets/images/icon_app.png" class="qrcodeLogo" />
+        </div>
+        <div class="qrtips">
+          <p class="tip">温馨提示:保存图片到相册或长按识别二维码进入查看喔~</p>
+          <img src="../../../assets/images/logo-black.png" class="iconLogo" />
+        </div>
+      </div>
+    </div>
+
+    <div class="shareBottom">
+      <van-icon name="cross" class="iconClose" @click="$emit('close')" />
+      <div class="share__header">海报已生成!快来分享吧!</div>
+      <van-grid :columnNum="5" :border="false" class="gridSection">
+        <van-grid-item :icon="iconDownload" text="保存本地" @click="onSavePath('')"></van-grid-item>
+        <van-grid-item :icon="iconWechat" text="微信好友" @click="onSavePath('wechat')"></van-grid-item>
+        <van-grid-item :icon="iconFriendRing" text="朋友圈" @click="onSavePath('wechat_circle')"></van-grid-item>
+        <van-grid-item :icon="iconLink" text="复制链接" @click="copyText(config)"></van-grid-item>
+      </van-grid>
+      <div class="btn van-hairline--top" @click="$emit('close')">取消</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import html2canvas from "html2canvas";
+import { postMessage } from "@/helpers/native-message";
+import VueQr from "vue-qr";
+export default {
+  name: "share-model",
+  components: { VueQr },
+  props: ["musicDetail"],
+  data() {
+    return {
+      iconDownload: require("./images/icon-download.png"),
+      iconWechat: require("./images/icon-wechat.png"),
+      iconFriendRing: require("./images/icon-friend-ring.png"),
+      iconLink: require("./images/icon-link.png"),
+      musicBg: require("./images/music-bg.png"),
+      saveLoading: false,
+      image: null,
+      canvasRef: null,
+      config: null,
+    };
+  },
+  mounted() {
+    this.config = location.origin + "/mdaya/#/shareCreation?id=" + this.musicDetail.id;
+  },
+  methods: {
+    async saveImg() {
+      this.$toast.loading({
+        message: "图片生成中...",
+        forbidClick: true,
+      });
+      setTimeout(() => {
+        this.saveLoading = false;
+      }, 100);
+      postMessage(
+        {
+          api: "savePicture",
+          content: {
+            base64: this.image,
+          },
+        },
+        (res) => {
+          if (res?.content?.status === "success") {
+            this.$toast.success("已保存到相册");
+          } else {
+            this.$toast.fail("保存失败");
+          }
+        }
+      );
+    },
+
+    async onSaveWe(type) {
+      // this.$toast.loading({
+      //   message: "图片分享中...",
+      //   forbidClick: true,
+      // });
+
+      setTimeout(() => {
+        this.saveLoading = false;
+      }, 100);
+      postMessage(
+        {
+          api: "shareTripartite",
+          content: {
+            title: "我在管乐迷发布了演奏作品",
+            desc: this.musicDetail.desc,
+            // image: this.image,
+            video: "",
+            type: "link",
+            url: this.config,
+            thumb: encodeURI(decodeURI(this.musicDetail.img)),
+            shareType: type,
+          },
+        },
+        (res) => {
+          if (res?.content?.status) {
+            this.$toast.success("分享成功");
+          } else {
+            this.$toast.fail("分享失败");
+          }
+        }
+      );
+    },
+    onSavePath(type) {
+      // 判断是否在保存中...
+      if (this.saveLoading) {
+        return;
+      }
+      this.saveLoading = true;
+      // 判断是否已经生成图片
+      if (this.image) {
+        if (type) {
+          this.onSaveWe(type);
+        } else {
+          this.saveImg();
+        }
+      } else {
+        const container = document.getElementById("shareContent");
+        html2canvas(container, {
+          allowTaint: true,
+          useCORS: true,
+          backgroundColor: null,
+        })
+          .then(async (canvas) => {
+            const url = canvas.toDataURL("image/png");
+            this.image = url;
+            if (type) {
+              this.onSaveWe(type);
+            } else {
+              this.saveImg();
+            }
+          })
+          .catch(() => {
+            this.toast.clear();
+            this.saveLoading = false;
+          });
+      }
+    },
+    copyText(text) {
+      // 数字没有 .length 不能执行selectText 需要转化成字符串
+      const textString = text.toString();
+      let input = document.querySelector("#copy-input");
+      if (!input) {
+        input = document.createElement("input");
+        input.id = "copy-input";
+        input.readOnly = true; // 防止ios聚焦触发键盘事件
+        input.style.position = "fixed";
+        input.style.left = "-1000px";
+        input.style.zIndex = "-1000";
+        // 为了处理,页面滑动到底部的问题
+        document.body.appendChild(input);
+        // document.querySelector('#input-copy-container')?.appendChild(input)
+      }
+
+      input.value = textString;
+      // ios必须先选中文字且不支持 input.select();
+      selectText(input, 0, textString.length);
+      console.log(document.execCommand("copy"), "execCommand");
+      if (document.execCommand("copy")) {
+        document.execCommand("copy");
+        this.$toast("复制成功");
+      }
+      input.blur();
+
+      // input自带的select()方法在苹果端无法进行选择,所以需要自己去写一个类似的方法
+      // 选择文本。createTextRange(setSelectionRange)是input方法
+      function selectText(textbox, startIndex, stopIndex) {
+        if (textbox.createTextRange) {
+          //ie
+          const range = textbox.createTextRange();
+          range.collapse(true);
+          range.moveStart("character", startIndex); //起始光标
+          range.moveEnd("character", stopIndex - startIndex); //结束光标
+          range.select(); //不兼容苹果
+        } else {
+          //firefox/chrome
+          textbox.setSelectionRange(startIndex, stopIndex);
+          textbox.focus();
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less">
+.shareContent {
+  position: relative;
+  width: 3.35rem;
+  height: 3.55rem;
+  margin: 0 auto 0.56rem;
+
+  .shareBg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 1;
+    width: 3.35rem;
+    height: 3.55rem;
+  }
+
+  .share_content__title {
+    position: relative;
+    z-index: 3;
+    padding-top: 0.54rem;
+    padding-left: 0.14rem;
+    color: #ffffff;
+    text-shadow: 0px 0.02rem 0.04rem rgba(10, 181, 192, 0.7);
+    width: 170px;
+
+    .large {
+      font-size: 0.19rem;
+      font-weight: 600;
+      line-height: 0.26rem;
+    }
+
+    .small {
+      padding-top: 0.06rem;
+      font-size: 0.14rem;
+      line-height: 0.2rem;
+    }
+  }
+}
+
+.sectionFile {
+  position: relative;
+  z-index: 3;
+  margin: 25px 0.15rem 0.14rem;
+  padding: 12px;
+  display: flex;
+  background: rgba(255, 255, 255, 0.79);
+  border-radius: 0.1rem;
+
+  .muploader {
+    position: relative;
+    z-index: 9;
+    width: 56px;
+    height: 56px;
+    object-fit: cover;
+    border-radius: 6px;
+  }
+
+  .uploadImg {
+    position: relative;
+    border-radius: 6px;
+    margin-right: 0.16rem;
+    line-height: 0;
+
+    .audioPan {
+      position: absolute;
+      top: 0;
+      right: -6px;
+      z-index: 1;
+      width: 56px;
+      height: 56px;
+    }
+  }
+
+  .musicDetail {
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+
+    .musicName {
+      font-size: 0.16rem;
+      font-weight: 600;
+      color: #131415;
+      line-height: 22px;
+      max-width: 200px;
+    }
+
+    .username {
+      padding-top: 0.04rem;
+      font-size: 13px;
+      color: #aaa;
+      line-height: 0.2rem;
+    }
+  }
+}
+
+.downloadSection {
+  display: flex;
+  align-items: center;
+  margin: 0 0.2rem;
+  position: relative;
+  z-index: 3;
+
+  .qrcode {
+    position: relative;
+    width: 0.84rem;
+    height: 0.84rem;
+    padding: 0.03rem;
+    border-radius: 0.04rem;
+    // opacity: 0.53;
+    border: 1px solid #2bd1b2;
+
+    flex-shrink: 0;
+    overflow: hidden;
+
+    .qrcodeCanvas {
+      width: 100% !important;
+      height: 100% !important;
+    }
+
+    & > img {
+      width: 0.84rem;
+      height: 0.84rem;
+    }
+
+    .qrcodeLogo {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      z-index: 10;
+      margin-left: -0.08rem;
+      margin-top: -0.08rem;
+      width: 0.16rem;
+      height: 0.16rem;
+      border-radius: 0.04rem;
+    }
+  }
+
+  .qrtips {
+    margin-left: 0.1rem;
+    padding-left: 0.09rem;
+    border-left: 1px dashed #d8d8d8;
+
+    .tip {
+      font-size: 0.12rem;
+      color: #777777;
+      line-height: 0.17rem;
+    }
+
+    .iconLogo {
+      width: 0.68rem;
+      height: 0.21rem;
+      margin: 0.12rem 0 0.04rem;
+    }
+
+    .downTip {
+      font-size: 0.12rem;
+      font-weight: 500;
+      color: #333333;
+      line-height: 0.17rem;
+    }
+  }
+}
+
+.shareBottom {
+  position: relative;
+  background-color: #fff;
+  border-radius: 0.12rem 0.12rem 0 0;
+  padding-bottom: env(safe-area-inset-bottom);
+
+  .iconClose {
+    position: absolute;
+    top: 0.12rem;
+    right: 0.12rem;
+    font-size: 0.16rem;
+    color: #999999;
+  }
+}
+
+.share__header {
+  padding: 0.12rem 0.15rem 0.08rem;
+  font-size: 0.16rem;
+  color: #333333;
+  line-height: 22px;
+}
+
+.gridSection {
+  .van-grid-item__icon {
+    font-size: 0.44rem;
+  }
+  .van-grid-item__text {
+    font-size: 0.12rem;
+    color: #646566;
+  }
+  .van-grid-item__content {
+    padding: 0.16rem 0.12rem;
+  }
+}
+
+.btn {
+  font-size: 0.16rem;
+  color: #aaaaaa;
+  line-height: 0.55rem;
+  text-align: center;
+}
+</style>

Some files were not shown because too many files changed in this diff