Pārlūkot izejas kodu

Merge branch 'iteration-temp-http' of http://git.dayaedu.com/huangqiyong/classroom into dev

黄琪勇 9 mēneši atpakaļ
vecāks
revīzija
94f7c0c55b

BIN
src/img/cloudPractice/btn-submit.png


BIN
src/img/cloudPractice/header-ring.png


BIN
src/img/cloudPractice/icon-btn-pause.png


BIN
src/img/cloudPractice/icon-btn-play.png


BIN
src/img/cloudPractice/icon-change.png


BIN
src/img/cloudPractice/icon-left-default.png


BIN
src/img/cloudPractice/icon-search.png


BIN
src/img/cloudPractice/icon-transfer.png


BIN
src/img/cloudPractice/icon_default.png


BIN
src/img/cloudPractice/icon_next.png


BIN
src/img/cloudPractice/icon_pause.png


BIN
src/img/cloudPractice/icon_play.png


BIN
src/img/cloudPractice/icon_pre.png


+ 28 - 0
src/libs/utils.ts

@@ -80,3 +80,31 @@ export const generateAsyncMenus = (menus: menuType[]) => {
       })
    }
 }
+
+// 秒转分
+export const getSecondRPM = (second: number, type?: string) => {
+   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 const getSecond = (second: number, type?: string) => {
+   if (isNaN(second)) return "0000"
+   const mm = Math.floor(second / 60)
+      .toString()
+      .padStart(2, "0")
+   const dd = Math.floor(second % 60)
+      .toString()
+      .padStart(2, "0")
+   return `${mm}${dd}`
+}

+ 10 - 0
src/shims-vue.d.ts

@@ -5,6 +5,16 @@ declare module "*.vue" {
    export default component
 }
 
+declare module '*.module.scss' {
+   const classes: { readonly [key: string]: string }
+   export default classes
+}
+
+declare module '*.png' {
+   const classes: { readonly [key: string]: string }
+   export default classes
+}
+
 /** json 文件导出 */
 declare module "*.json" {
    const value: any

+ 0 - 40
src/views/cloudPractice copy/cloudPractice.vue

@@ -1,40 +0,0 @@
-<!--
-* @FileDescription: 云练习
-* @Author: 王新雷
-* @Date:2024年9月2日17:44:14
--->
-<template>
-   <navContainer :navs="navs">
-      <div class="leftContainer">
-         <div class="details"></div>
-      </div>
-      <div class="rightContainer"></div>
-   </navContainer>
-</template>
-
-<script setup lang="ts">
-import navContainer from "@/businessComponents/navContainer"
-
-const navs = [
-   {
-      name: "主页",
-      url: "/"
-   },
-   {
-      name: "云练习"
-   }
-]
-</script>
-
-<style lang="scss" scoped>
-.leftContainer {
-   flex-shrink: 0;
-   margin-left: 50px;
-   // margin-top: -35px;
-   width: 572px;
-   height: 764px;
-   background: url("@/img/homePage/bg1.png") no-repeat;
-   background-size: 100% 100%;
-   position: relative;
-}
-</style>

+ 0 - 2
src/views/cloudPractice copy/index.ts

@@ -1,2 +0,0 @@
-import cloudPractice from "./cloudPractice.vue"
-export default cloudPractice

+ 149 - 7
src/views/cloudPractice/cloudPractice.tsx

@@ -1,9 +1,151 @@
-import { defineComponent } from 'vue';
-import styles from './index.module.scss'
+import { defineComponent } from "vue"
+import styles from "./index.module.scss"
+import NavContainer from "@/businessComponents/navContainer"
+import { ElScrollbar } from "element-plus"
+import Dictionary from "@/components/dictionary"
+import MyInput from "@/components/myInput"
+import { NImage } from "naive-ui"
+import PlayLoading from "./component/play-loading"
+import icon_default from "../../img/cloudPractice/icon_default.png"
+// import iconBtnPause from "../../img/cloudPractice/icon-btn-pause.png"
+import iconBtnPlay from "../../img/cloudPractice/icon-btn-play.png"
+import btnSubmit from "../../img/cloudPractice/btn-submit.png"
 
 export default defineComponent({
-    name: 'cloudPractice',
-    setup() {
-        return () => <div class={styles.aa}></div>
-    }
-})
+   name: "cloudPractice",
+   setup() {
+      const navs = [
+         {
+            name: "主页",
+            url: "/"
+         },
+         {
+            name: "云练习"
+         }
+      ]
+      return () => (
+         <NavContainer navs={navs}>
+            <ElScrollbar class="elScrollbar">
+               <div class={styles.cloudPractice}>
+                  <div class={styles.leftContainer}>
+                     <div class={styles.details}>
+                        <div class={styles.leftSection}>
+                           <div class={[styles.leftSection_item, styles.leftSection_item__active]}>基 础 云 练</div>
+                           <div class={styles.leftSection_item}>独 奏 云 练</div>
+                           <div class={styles.leftSection_item}>合 奏 云 练</div>
+                           <div class={styles.leftSection_item}>考 级 云 练</div>
+                        </div>
+
+                        <div class={styles.musicList}>
+                           <div class={styles.searchHeader}>
+                              <div class={styles.searchMore}>
+                                 <div class={styles.searchSection}>
+                                    <Dictionary
+                                       popperClass="classTypePopper"
+                                       // v-model="classType"
+                                       width={96}
+                                       height={42}
+                                       options={[]}
+                                       placeholder="课程分类"
+                                       // @change="handleQuery"
+                                    />
+                                    <Dictionary
+                                       popperClass="classTypePopper"
+                                       // v-model="classType"
+                                       width={96}
+                                       height={42}
+                                       options={[]}
+                                       placeholder="课程分类"
+                                       // @change="handleQuery"
+                                    />
+                                    <Dictionary
+                                       popperClass="classTypePopper"
+                                       // v-model="classType"
+                                       width={96}
+                                       height={42}
+                                       options={[]}
+                                       placeholder="课程分类"
+                                       // @change="handleQuery"
+                                    />
+                                    <Dictionary
+                                       popperClass="classTypePopper"
+                                       // v-model="classType"
+                                       width={96}
+                                       height={42}
+                                       options={[]}
+                                       placeholder="课程分类"
+                                       // @change="handleQuery"
+                                    />
+                                 </div>
+                                 <div class={styles.btnSearch}></div>
+                              </div>
+                              <MyInput
+                                 class="queryCp"
+                                 // v-model="queryStr"
+                                 height={42}
+                                 placeholder="请输入曲目关键词"
+                                 // @keyup.enter="handleQuery"
+                                 // @input="handleInputQuery"
+                                 // @handleQuery="handleQuery"
+                                 clearable
+                              />
+                           </div>
+
+                           <div class={styles.wrapList}>
+                              <div class={styles.item}>
+                                 <div class={styles.itemInfo}>
+                                    <div class={styles.img}>
+                                       <NImage
+                                          lazy
+                                          objectFit="cover"
+                                          previewDisabled={true}
+                                          src={icon_default}
+                                          onLoad={(e: any) => {
+                                             ;(e.target as any).dataset.loaded = "true"
+                                          }}
+                                       />
+                                       <PlayLoading
+                                       // class={[
+                                       //   data.listActive === index &&
+                                       //   data.playState === 'play'
+                                       //     ? ''
+                                       //     : styles.showPlayLoading
+                                       // ]}
+                                       />
+                                    </div>
+                                    <div class={styles.title}>
+                                       <div class={styles.titleName}>
+                                          <ellipsisScroll title={"曲目名称"} />
+                                       </div>
+                                    </div>
+                                 </div>
+                                 <div class={styles.btnSection}>
+                                    <div class={styles.btn}>
+                                       播放
+                                       <img src={iconBtnPlay as any} />
+                                    </div>
+                                 </div>
+                              </div>
+                           </div>
+                        </div>
+                     </div>
+                  </div>
+                  <div class={styles.rightContainer}>
+                     <div class={styles.musicName}>曲目名称</div>
+                     <div class={styles.staffImgs}>图片</div>
+
+                     <img
+                        // style={{
+                        //   display: activeItem.value.id ? '' : 'none'
+                        // }}
+                        class={[styles.goBtn]}
+                        src={btnSubmit as any}
+                        onClick={() => {}}
+                     />
+                  </div>
+               </div>
+            </ElScrollbar>
+         </NavContainer>
+      )
+   }
+})

+ 5 - 3
src/views/cloudPractice/cloudPractice1.vue → src/views/cloudPractice/cloudPractice.vue

@@ -5,10 +5,12 @@
 -->
 <template>
    <navContainer :navs="navs">
-      <div class="leftContainer">
-         <div class="details"></div>
+      <div class="cloudPractice">
+         <div class="leftContainer">
+            <div class="details"></div>
+         </div>
+         <div class="rightContainer"></div>
       </div>
-      <div class="rightContainer"></div>
    </navContainer>
 </template>
 

+ 174 - 0
src/views/cloudPractice/component/play-item/index.module.scss

@@ -0,0 +1,174 @@
+.container {
+  position: fixed;
+  left: 100px;
+  bottom: 0;
+  right: 0;
+  display: flex;
+  align-items: center;
+  height: 108px;
+  padding: 0 60px;
+  background-color: #fff;
+  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.1);
+  z-index: 10;
+  transition: all .3s;
+
+  &.previewcontainer {
+    left: 0;
+    padding-right: 380px;
+  }
+
+  &.containerModal {
+    position: absolute;
+    left: 0;
+
+  }
+}
+
+.hidden {
+  transform: translateY(100%);
+  opacity: 0;
+  display: none;
+}
+
+.item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+
+  .img {
+    position: relative;
+    width: 64px;
+    height: 64px;
+    border-radius: 50%;
+    margin-right: 12px;
+    background-color: #000;
+    box-shadow: 0 0 10px 4px rgba(27, 35, 55, .1);
+    padding: 7px;
+    overflow: hidden;
+    flex-shrink: 0;
+
+    :global {
+      .n-image {
+        border-radius: 50%;
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    img {
+      transition: opacity .3s;
+      opacity: 0;
+      animation: rotateImg 6s linear infinite;
+    }
+
+    &.imgRotate {
+      img {
+        animation-play-state: paused;
+      }
+    }
+
+    img[data-loaded="true"] {
+      opacity: 1;
+    }
+  }
+
+  .svgcontainer {
+    position: fixed;
+    z-index: -1000;
+    pointer-events: none;
+  }
+
+  .progress {
+    position: absolute;
+    left: 4px;
+    top: 4px;
+    width: 56px;
+    pointer-events: none;
+    transform: rotate(180deg);
+
+    :global {
+      .n-progress-graph .n-progress-graph-circle .n-progress-graph-circle-fill {
+        stroke: url(#GradientProgress);
+      }
+    }
+  }
+
+  .title {
+    margin-right: 15px;
+    width: 200px;
+
+    .titleName {
+      font-size: max(16px, 13Px);
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+      white-space: nowrap;
+    }
+
+    .titleDes {
+      font-size: max(14px, 12Px);
+      font-weight: 400;
+      color: #777777;
+      line-height: 20px;
+      white-space: nowrap;
+    }
+  }
+}
+
+@keyframes rotateImg {
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.playBtns {
+  margin-left: 140px;
+  display: flex;
+  align-items: center;
+
+  :global {
+    .n-button {
+      width: 40px;
+      height: 40px;
+
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .playBtn {
+    width: 50px;
+    height: 50px;
+    margin: 0 48px;
+    background: linear-gradient(to right bottom, #44CAFE, #007AFE);
+
+    img {
+      display: block;
+      // width: 18px;
+      height: 20px;
+    }
+  }
+}
+
+.timeWrap {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  margin-left: 88px;
+
+  .timeProgress {
+    margin-right: 24px;
+    border-radius: 6px;
+    --n-rail-height: 8px !important;
+  }
+
+  .time {
+    width: 90px;
+    white-space: nowrap;
+    flex-shrink: 0;
+  }
+}

+ 225 - 0
src/views/cloudPractice/component/play-item/index.tsx

@@ -0,0 +1,225 @@
+import {
+  PropType,
+  Transition,
+  computed,
+  defineComponent,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.scss';
+import { NButton, NImage, NProgress, NSlider } from 'naive-ui';
+// import { IMusicItem } from '../../type';
+import icon_pre from '../../img/cloudPractice/icon_pre.png';
+import icon_next from '../../img/cloudPractice/icon_next.png';
+import icon_play from '../../img/cloudPractice/icon_play.png';
+import icon_pause from '../../img/cloudPractice/icon_pause.png';
+import { getSecondRPM } from '@/libs/utils';
+// import TheNoticeBar from '/src/components/TheNoticeBar';
+
+export default defineComponent({
+  name: 'playItem',
+  props: {
+    item: {
+      type: Object as PropType<any>,
+      default: () => ({})
+    },
+    show: {
+      type: Boolean,
+      default: false
+    },
+    playState: {
+      type: String as PropType<'play' | 'pause'>,
+      default: 'pause'
+    },
+    type: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['change'],
+  setup(props, { emit }) {
+    let timer = null as any;
+    const audioData = reactive({
+      isFirst: true,
+      duration: 0,
+      currentTime: 0
+    });
+    const audioRef = ref();
+    /** 加载成功 */
+    const onLoadedmetadata = () => {
+      audioData.duration = audioRef.value?.duration;
+      if (audioData.isFirst) {
+        audioData.isFirst = false;
+        return;
+      }
+      if (props.playState === 'play') {
+        audioRef.value.play();
+      }
+
+      // 判断是否有链接
+      if (!props.item.audioFileUrl && !props.item.metronomeUrl) {
+        emit('change', 'pause');
+      }
+    };
+    /** 改变时间 */
+    const handleChangeTime = (val: number) => {
+      audioRef.value.pause();
+      audioData.currentTime = val;
+      clearTimeout(timer);
+      timer = setTimeout(() => {
+        audioRef.value.currentTime = val;
+        if (props.playState === 'play') {
+          audioRef.value.play();
+        }
+        timer = null;
+      }, 300);
+    };
+    const time = computed(() => {
+      return `${getSecondRPM(audioData.currentTime)} / ${getSecondRPM(
+        audioData.duration
+      )}`;
+    });
+
+    watch(
+      () => props.playState,
+      val => {
+        if (val === 'play') {
+          audioRef.value.play().catch((err: any) => {
+            console.log(err, '22');
+            audioRef.value.play();
+          });
+        } else {
+          audioRef.value.pause();
+        }
+      }
+    );
+
+    watch(
+      () => props.item,
+      () => {
+        // 判断是否有链接
+        if (!props.item.audioFileUrl && !props.item.metronomeUrl) {
+          emit('change', 'pause');
+        }
+      }
+    );
+
+    return () => (
+      <div
+        class={[
+          styles.container,
+          props.type === 'preview' && styles.previewcontainer,
+          props.type === 'modal' && styles.containerModal,
+          props.show ? styles.show : styles.hidden
+        ]}>
+        <div class={[styles.item]}>
+          <div
+            class={[
+              styles.img,
+              props.playState !== 'play' && styles.imgRotate
+            ]}>
+            <NImage
+              lazy
+              objectFit="cover"
+              previewDisabled={true}
+              src={
+                props.item.titleImg ||
+                'https://oss.dayaedu.com/klx/16983720423251690789356356.png'
+              }
+              onLoad={(e: any) => {
+                (e.target as any).dataset.loaded = 'true';
+              }}
+            />
+
+            <svg class={styles.svgcontainer}>
+              <defs>
+                <linearGradient id="GradientProgress">
+                  <stop stop-color="#5BECFF" offset="0%" />
+                  <stop stop-color="#259CFE" offset="100%" />
+                </linearGradient>
+              </defs>
+            </svg>
+
+            <NProgress
+              type="circle"
+              class={styles.progress}
+              showIndicator={false}
+              percentage={(audioData.currentTime / audioData.duration) * 100}
+            />
+          </div>
+          <div class={styles.title}>
+            <div class={styles.titleName}>
+              {/* <TheNoticeBar text={props.item.musicSheetName} /> */}
+              {props.item.musicSheetName}
+            </div>
+            <div class={styles.titleDes}>{props.item.composer}</div>
+          </div>
+
+          <div class={styles.playBtns}>
+            <NButton
+              color="rgba(246,246,246,1)"
+              circle
+              bordered={false}
+              onClick={() => emit('change', 'pre')}>
+              <img src={icon_pre as any} />
+            </NButton>
+            <NButton
+              color="rgba(57,130,246,1)"
+              class={styles.playBtn}
+              circle
+              bordered={false}
+              onClick={() =>
+                emit('change', props.playState === 'pause' ? 'play' : 'pause')
+              }>
+              <img
+                style={{
+                  display: props.playState === 'pause' ? '' : 'none'
+                  // transform: 'scale(1.5) translateX(1px)'
+                }}
+                src={icon_play as any}
+              />
+              <img
+                style={{
+                  display: props.playState === 'play' ? '' : 'none'
+                  // transform: 'scale(1.5)'
+                }}
+                src={icon_pause as any}
+              />
+            </NButton>
+            <NButton
+              color="rgba(246,246,246,1)"
+              circle
+              bordered={false}
+              onClick={() => emit('change', 'next')}>
+              <img src={icon_next as any} />
+            </NButton>
+          </div>
+
+          <div class={styles.timeWrap}>
+            <NSlider
+              tooltip={false}
+              step={0.01}
+              class={styles.timeProgress}
+              value={audioData.currentTime}
+              max={audioData.duration}
+              onUpdate:value={(val: any) => handleChangeTime(val)}
+            />
+            <div class={styles.time}>{time.value}</div>
+            <audio
+              ref={audioRef}
+              src={props.item.audioFileUrl || props.item.metronomeUrl}
+              onLoadedmetadata={onLoadedmetadata}
+              onEnded={() => {
+                emit('change', 'pause');
+              }}
+              onTimeupdate={() => {
+                if (timer) return;
+                audioData.currentTime = audioRef.value?.currentTime;
+              }}></audio>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 48 - 0
src/views/cloudPractice/component/play-loading/index.module.scss

@@ -0,0 +1,48 @@
+.audioAnimate {
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, .6);
+    display: flex;
+    justify-content: center;
+    align-items: flex-end;
+    padding-bottom: 30%;
+
+    div {
+        width: 5px;
+        height: 20px;
+        background: linear-gradient(135deg, #34FFC5 0%, #1BD2FF 100%);
+        transform-origin: bottom;
+        border-radius: 5px 5px 0 0;
+        margin: 0 2px;
+    }
+
+    & div:nth-child(1) {
+        animation: musicWave 0.5s infinite linear both alternate;
+    }
+
+    & div:nth-child(2) {
+        animation: musicWave 0.2s infinite linear both alternate;
+    }
+
+    & div:nth-child(3) {
+        animation: musicWave 0.6s infinite linear both alternate;
+    }
+
+    & div:nth-child(4) {
+        animation: musicWave 0.3s infinite linear both alternate;
+    }
+}
+
+
+@keyframes musicWave {
+    0% {
+        height: 5px;
+    }
+
+    100% {
+        height: 20px;
+    }
+}

+ 16 - 0
src/views/cloudPractice/component/play-loading/index.tsx

@@ -0,0 +1,16 @@
+import { defineComponent } from "vue"
+import styles from "./index.module.scss"
+
+export default defineComponent({
+   name: "playLoading",
+   setup() {
+      return () => (
+         <div class={styles.audioAnimate}>
+            <div></div>
+            <div></div>
+            <div></div>
+            <div></div>
+         </div>
+      )
+   }
+})

+ 236 - 0
src/views/cloudPractice/index.module.scss

@@ -0,0 +1,236 @@
+.cloudPractice {
+   width: 100%;
+   height: 100%;
+
+   & > :deep(.elScrollbar) {
+      .el-scrollbar__view {
+         width: 100%;
+         display: flex;
+         padding: 50px 50px 0;
+      }
+      .el-scrollbar__wrap {
+         overflow-x: hidden;
+      }
+   }
+}
+.leftContainer {
+   flex-shrink: 0;
+   margin-left: 60px;
+   margin-top: 47px;
+   width: 600px;
+   height: 729px;
+   position: relative;
+   background: linear-gradient(270deg, #fede94 0%, #ffe3a3 48%, #fede94 100%);
+   border-radius: 36px;
+   padding-top: 47px;
+
+   &::before {
+      content: "";
+      width: 335px;
+      height: 59px;
+      background: url("@/img/cloudPractice/header-ring.png");
+      background-size: contain;
+      display: block;
+      position: absolute;
+      top: -30px;
+      left: 50%;
+      margin-left: -167.5px;
+   }
+
+   .details {
+      position: relative;
+      margin: 0 32px 32px;
+      width: 536px;
+      height: 650px;
+      background: #ffffff;
+      border-radius: 36px;
+   }
+   .leftSection {
+      position: absolute;
+      left: -49px;
+      top: 37px;
+      .leftSection_item {
+         width: 49px;
+         height: 131px;
+         background: url("@/img/cloudPractice/icon-left-default.png");
+         background-size: contain;
+         margin-bottom: 12px;
+         display: flex;
+         align-items: center;
+         justify-content: center;
+         writing-mode: vertical-lr;
+         font-weight: 600;
+         font-size: 20px;
+         color: #ffffff;
+         line-height: 24px;
+         text-shadow: 0px 1px 1px #ffac2d;
+
+         &:last-child {
+            margin-bottom: 0;
+         }
+      }
+   }
+
+   .musicList {
+      height: 100%;
+      overflow-x: hidden;
+      overflow-y: auto;
+
+      &::-webkit-scrollbar {
+         width: 0;
+         display: none;
+      }
+   }
+
+   .searchHeader {
+      position: sticky;
+      top: 0;
+      left: 0;
+      z-index: 9;
+      padding: 28px 30px 15px;
+      background-color: #fff;
+      border-radius: 36px 36px 0 0;
+   }
+
+   .searchMore {
+      display: flex;
+      justify-content: space-between;
+   }
+
+   .searchSection {
+      gap: 0 13px;
+      display: flex;
+   }
+
+   :global {
+      .h_dictionary .el-input__wrapper {
+         background: #fff3d7;
+         padding-left: 14px;
+         padding-right: 7px;
+      }
+      .el-cascader:not(.is-disabled):hover .el-input__wrapper {
+         box-shadow: none;
+      }
+
+      .queryCp {
+         margin-top: 12px;
+         background: #fff3d7 !important;
+         .el-input__wrapper {
+            background: #fff3d7 !important;
+         }
+      }
+   }
+
+   .btnSearch {
+      width: 42px;
+      height: 42px;
+      background: url("@/img/cloudPractice/icon-search.png") no-repeat center;
+      background-size: contain;
+      cursor: pointer;
+   }
+
+   .wrapList {
+      width: 100%;
+      // min-width: 294px;
+      min-height: 100%;
+      padding: 0 20px;
+      // background: #fff;
+      // border-radius: 16px;
+      .item {
+         position: relative;
+         display: flex;
+         align-items: center;
+         justify-content: space-between;
+         padding: 15px 10px 15px 26px;
+         border-radius: 12px;
+
+         cursor: pointer;
+
+         &:hover {
+            background: #fff3d7;
+         }
+
+         &.active {
+            background: #fff3d7;
+
+            .arrow {
+               opacity: 1;
+            }
+         }
+
+         .img {
+            position: relative;
+            width: 60px;
+            height: 60px;
+            border-radius: 16px;
+            margin-right: 12px;
+            overflow: hidden;
+            flex-shrink: 0;
+            :global {
+               .n-image {
+                  width: 60px;
+                  height: 60px;
+               }
+            }
+
+            img {
+               transition: opacity 0.3s;
+               opacity: 0;
+               height: 100%;
+               width: 100%;
+            }
+
+            img[data-loaded="true"] {
+               opacity: 1;
+            }
+         }
+
+         .itemInfo {
+            display: flex;
+            align-items: center;
+         }
+
+         .titleName {
+            font-weight: 600;
+            font-size: 20px;
+            color: #a15228;
+            line-height: 28px;
+         }
+
+         .btn {
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 84px;
+            height: 36px;
+            background: linear-gradient(312deg, #ff9e49 0%, #ff531c 100%);
+            border-radius: 18px;
+            font-weight: 600;
+            font-size: 16px;
+            color: #ffffff;
+
+            img {
+               margin-left: 7px;
+               width: 14px;
+               height: 14px;
+            }
+         }
+      }
+   }
+}
+
+.rightContainer {
+   background: #FFFFFF;
+   border-radius: 36px;
+   margin-left: 32px;
+   .goBtn {
+      position: absolute;
+      left: 50%;
+      bottom: 46px;
+      transform: translateX(-50%);
+      height: 102px;
+      cursor: pointer;
+      transition: all 0.2s ease-in;
+   }
+}