Newer
Older
urbanLifeline_YanAn / src / views / RongyunCommunication / RongYunBox / index.vue
@zhangqy zhangqy on 7 Oct 25 KB 打包发布
<template>
  <div class="RongYun" id="RongYun">
    <div class="rong-container" id="rongContainer">
      <div class="rong-action" id="rong-action">
        <div class="textDiv2">
          <div class="textTitle">
            <span class="dote"></span>
            <span>接收用户:</span>
          </div>
          <div class="userList" v-if="allData.userList.length > 0">
            <template v-for="(item, index) in allData.userList" :key="index">
              <div class="user" v-if="item.phone != userId">
                <div class="userInfoClass">
                  <span class="username">{{ item.name }}:</span>
                  <div class="ceninfoBox">
                    <div class="userphone" v-if="item.phone">
                      {{ geTel(item.phone) }}
                    </div>
                    <div class="userphone userdept">{{ item.deptName }}</div>
                  </div>
                </div>
                <div>
                  <span style="margin-left: 10px" :class="'classType' + item.ClassType">{{
                    item.typeFont
                  }}</span>
                </div>
              </div>
              <div class="user" v-else>
                <div class="userInfoClass">
                  <span class="username">现场用户</span>
                </div>
              </div>
            </template>
          </div>
        </div>
        <div class="textDiv">
          <span class="dote"></span>
          <div class="text">
            <span>发起人:</span
            ><span class="Rightvalue" @click="getToken()">{{ userinfo.userName }}</span>
          </div>
        </div>
        <div class="textDiv">
          <span class="dote"></span>
          <div class="text">
            <span>当前登录用户 ID:</span
            ><span id="userId" class="Rightvalue" @click="getToken()">{{
              userId + "_web"
            }}</span>
          </div>
        </div>

        <div class="textDiv">
          <span class="dote"></span>
          <div class="text">
            <span>通话类型:</span>

            <el-radio-group
              v-model="mediaType"
              style="display: flex; justify-content: center; align-items: center"
              @change="changeMediaType"
            >
              <el-radio :label="2">视频</el-radio>
              <el-radio :label="1">语音</el-radio>
            </el-radio-group>
          </div>
        </div>
        <div id="inviteList" class="textDiv">
          <span class="dote"></span>
          <div class="text">
            <span>选择邀请人:</span>

            <el-select
              v-model="Getidinvite"
              placeholder="选择被邀请人"
              filterable
              multiple
              collapse-tags
            >
              <el-option
                v-for="dict in inviteuserIdsList"
                :key="dict.registerThridUserId"
                :label="dict.name + '(' + dict.registerPlatform + ')'"
                :value="dict.registerThridUserId"
              ></el-option>
            </el-select>
          </div>
        </div>
        <div class="rong-login-box">
          <div class="btn callBtn" id="call" @click="callHandler()">
            <img src="/src/assets/images/rongYunImg/callic.png" alt="" />
            发起通话
          </div>
          <div class="btn hunupBtn hangBtn" id="hunup" @click="hungup()">
            <img src="/src/assets/images/rongYunImg/hangic.png" alt="" />
            挂断
          </div>
          <div class="btn inviteBtn hunupBtn" id="invite" @click="inviteCall()">邀请</div>
        </div>
      </div>
      <div id="videoView" class="rong-video-box newbox"></div>
    </div>
  </div>
</template>
<script setup>
import moment from "moment";
import useUserStore from "@/store/modules/user";
import { ElMessageBox } from "element-plus";

import bus from "@/utils/util";
import rongyunStore from "@/store/modules/rongyunStore";
import {
  listrcloudUnitUser,
  getUserStatus,
  rongyunGroupSave,
  groupJoinUsers,
  batchAdd,
  answer,
  hangUp,
} from "@/views/RongyunCommunication/rongyunjs/rongyunApi.js";
import { listDept } from "@/api/system/dept.js";
import { findCurrentTreeObj } from "@/utils/util";

const appStore = useUserStore();
const useRongyunStore = rongyunStore();
const { proxy } = getCurrentInstance();
const inviteuserIdsList = ref([]); //被邀请人的列表
const Getidinvite = ref([]);
const allData = reactive({
  typeList: [
    {
      label: "视频",
      value: 2,
    },
    {
      label: "语音",
      value: 1,
    },
  ],
  userId: appStore.userInfo.phonenumber,
  userName: "",
  mediaType: useRongyunStore.mediaType,
  userList: [],
  idList: [],
  userinfo: appStore.userInfo,
  unitArr: [],
});
const {
  typeList,
  userId,
  userName,
  mediaType,
  userList,
  idList,
  userinfo,
  unitArr,
} = toRefs(allData);
const props = defineProps({
  ryUser: Object,
  callType: Number,
});

const paramSave = ref({
  groupId: undefined, //群聊Id
  promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人id
  answerThridUserIds: "", //接收人id
  type: "", //通话类型  1是视频 2是语音
  createBy: localStorage.getItem("userNo"),
});

onMounted(() => {
  nextTick(() => {
    initGroupData();
  });
  bus.on("removeEle", (e) => {
    removeVideoEl();
  });
  bus.on("ZhuDongBoHao", (e) => {
    removeVideoEl();
  });
});

onBeforeUnmount(() => {
  close();
});
const geTel = (tel) => {
  var reg = /^(\d{3})\d{4}(\d{4})$/;
  return tel.replace(reg, "$1****$2");
};

// 处理群呼数据
const initGroupData = () => {
  console.log("props.ryUser 处理群呼/单呼数据", props.ryUser);
  // debugger;
  allData.userList = [];
  if (props.callType == 2) {
    let arr = [].concat(props.ryUser.userList);
    arr.forEach((v) => {
      allData.userList.push({
        name: v.name,
        phone: v.registerThridUserId,
        typeFont: null,
        ClassType: null,
      });
    });
  } else {
    allData.userList.push({
      name: props.ryUser.name,
      phone: props.ryUser.phone,
      typeFont: null,
      ClassType: null,
    });
  }
  console.log("allData.userList", allData.userList);
};

const getDom = (key) => {
  return document.querySelector(key);
};
const changeMediaType = (value) => {
  console.log("value", value);
  useRongyunStore.SET_MediaType(value);
};
/**
 * CallSession 事件
 */
const getCallSessionEvent = () => {
  return {
    onRinging: (sender) => {
      // 当远端用户已开始响铃, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以在业务层做响铃的 UI 展示。
      // ;
      proxy.$modal.msgWarning(` ${sender.userId} 振铃`);
      allData.userList.forEach((element) => {
        if (element.phone == sender.userId) {
          element.typeFont = "对方已响铃";
          element.ClassType = 1;
        }
      });
    },
    onAccept: (sender) => {
      // 当远端用户已同意接听, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以把 UI 的‘响铃’变成‘通话中’。
      proxy.$modal.msgWarning(` ${sender.userId} 已接听`);
      console.log("接听监听,", allData.userList, sender);
      allData.userList.forEach((element) => {
        if (element.phone == sender.userId) {
          element.typeFont = "已接听";
          element.ClassType = 2;
          element.answerType = 4;
          acceptApi(element);
        }
      });
    },
    onHungup: (sender, reason) => {
      // 当有远端用户挂断, 该函数有 3 个参数: sender 是发送者,reason 是挂断原因,session 是通话实例。这时用户可以在 UI 层提示‘xxx已挂断’。
      proxy.$modal.msgWarning(` ${sender.userId} 已挂断`);
      console.log("挂断监听,", allData.userList, sender);
      console.log("挂断监听挂断原因,", reason);

      allData.userList.forEach((element) => {
        if (element.phone == sender.userId) {
          element.typeFont = "已挂断";
          element.answerType = reason;
          // 挂断原因转义成后端所需字段
          //  1.未响铃  2.响铃未接听 3.响铃后挂断 4.已接听

          // 响铃后挂断
          // if (reason == 12) {
          //   element.answerType = 3;
          // }

          // // 响铃未接听
          // if (reason == 15) {
          //   element.answerType = 2;
          // }

          // // 已接听
          // if (reason == 13) {
          //   element.answerType = 4;
          // }

          hungupApi(element);
        }
      });
      // 群组中移除相应节点
      const videoViewDom = getDom("#videoView");
      const videoDom = getDom(`#video-${sender.userId}`);
      videoDom && videoViewDom.removeChild(videoDom);
      const itemdomList = document.getElementsByClassName("video-item");
      if (itemdomList.length <= 1) {
        restore();
      }
    },
    onTrackReady: (track) => {
      // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。
      setTimeout(() => {
        appendVideoEl(track);
      }, 1500);
      // 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。
      if (!track.isLocalTrack()) {
        proxy.$modal.msgWarning("通话已建立");
      }
    },
    onMemberModify: (sender, invitedUsers, session) => {
      console.log("触发邀请监听", sender, invitedUsers, session);
      console.log("触发邀请监听", allData.userList);

      // 群组通话中有其他人被邀请加入, 该函数有 3 个参数: sender 是发送者,invitedUsers 是被邀请的用户列表, session 是通话实例。这时用户可以在 UI 层提示‘xxx加入通话’。
    },
    onMediaModify: (sender) => {
      // 通话类型改变时触发, 该函数有3个参数: sender 是发送者,mediaType 通话类型, session 是通话实例。这时用户可以在 UI 层提示‘已降级成音频通话’。
    },
    onAudioMuteChange: (muteUser) => {
      // 对方静音后触发, 该函数有2个参数: muteUser 是已静音的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已静音’。
      proxy.$modal.msgWarning(` ${sender.userId} 已静音`);
    },
    onVideoMuteChange: (muteUser) => {
      // 对方禁用视频后触发, 该函数有2个参数: muteUser 是已禁用视频的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已禁用视频’。
      proxy.$modal.msgWarning(` ${sender.userId} 已禁用视频`);
    },
  };
};
/**
 * callSession 事件注册
 */
const registerCallSessionEvent = (session) => {
  const events = getCallSessionEvent();
  session.registerSessionListener(events);
};
const callHandler = () => {
  console.log("..??", props);
  props.callType == 1 ? rongYunCall() : groupCall();
};
// 单呼
const call = () => {
  getDom("#call").style.display = "none";
  getDom("#hunup").style.display = "block";
  const events = getCallSessionEvent();
  console.log("props.ryUser", props.ryUser);
  console.log("props.ryUser.phone2", props.ryUser.phone);
  // 这里是传给融云拨打电话的传递userid
  const params = {
    targetId: props.ryUser.phone,
    mediaType: useRongyunStore.mediaType,
    listener: events,
  };
  useRongyunStore.callClient.call(params).then(({ code, session }) => {
    if (code === 10000) {
      registerCallSessionEvent(session);
      useRongyunStore.SET_CALLSESSION(session);

      saveOnAccept();
    } else {
      proxy.$modal.msgWarning(`呼叫失败,错误原因:${code}`);
    }
  });
};
//单呼前判登录用户在线状态
const rongYunCall = async (item) => {
  console.log("allData.userId", allData.userId, appStore.userInfo);
  let params = {
    registerThridUserId: allData.userId + "_web",
  };

  let res1 = await getUserStatus(params);
  let flag1 = null;
  if (res1.code == 200) {
    flag1 = JSON.parse(res1.data);
  }
  if (flag1 != 1) {
    ElMessageBox.confirm(
      `${appStore.userInfo.userName || "当前"}用户视频会商不在线,是否重新连接?, "提示"`,
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
    )
      .then(() => {
        setTimeout(() => {
          bus.emit("rystatus");
          getCallStatus();
        }, 1500);
      })
      .catch(() => {});
  } else {
    setTimeout(() => {
      getCallStatus();
    }, 1500);
  }
};
//单呼前判对方用户在线状态
const getCallStatus = async () => {
  let params = {
    registerThridUserId: props.ryUser.phone,
  };

  let res = await getUserStatus(params);
  let flag = null;
  if (res.code == 200) {
    flag = JSON.parse(res.data);
  }
  if (flag != 1) {
    return proxy.$modal.msgWarning(`${props.ryUser.name}用户视频会商不在线!`);
  } else {
    call();
  }
};
//群呼
const groupCall = () => {
  getDom("#call").style.display = "none";
  getDom("#hunup").style.display = "block";
  getDom("#invite").style.display = "block";
  getDom("#inviteList").style.visibility = "visible";

  console.log("allData.userList", allData.userList, props.ryUser);
  allData.userList.forEach((v) => {
    v.typeFont = "已拨号";
    v.ClassType = 0;
    v.answerType = 1;
  });

  const events = getCallSessionEvent();
  const params = {
    targetId: props.ryUser.groupId,
    userIds: props.ryUser.userIds.split(","),
    mediaType: useRongyunStore.mediaType,
    listener: events,
  };
  console.log("param", params);
  useRongyunStore.callClient.callInGroup(params).then(({ code, session }) => {
    if (code === 10000) {
      registerCallSessionEvent(session);
      useRongyunStore.SET_CALLSESSION(session);
      saveOnAccept();
      GetallOnline();
    } else {
      const reason = code === RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code;
      proxy.$modal.msgWarning(`呼叫失败,错误原因:${reason}`);
      removeVideoEl();
    }
  });
};

// 邀请用户加入当前通话(仅限群组)
const inviteCall = () => {
  if (Getidinvite.value && Getidinvite.value.length) {
    let params = {
      groupId: props.ryUser.groupId,
      ids: Getidinvite.value.join(","),
    };
    groupJoinUsers(params).then((res) => {
      console.log(res);

      console.log("点击了邀请", Getidinvite.value);
      useRongyunStore.callSession.invite(Getidinvite.value).then(({ code }) => {
        console.log(code);
        if (code === 10000) {
          proxy.$modal.msgSuccess(`邀请成功`);
        } else {
          proxy.$modal.msgWarning(`邀请失败,错误原因:${code}`);
        }
      });
    });
  } else {
    proxy.$modal.msgWarning(`请选择邀请人`);
  }
};

// 批量新增融合通讯通话
const saveOnAccept = () => {
  console.log("批量新增", props.ryUser);
  paramSave.value.type = useRongyunStore.mediaType;
  paramSave.value.groupId = props.callType == 1 ? -1 : props.ryUser.groupId;
  paramSave.value.answerThridUserIds = props.ryUser.phone || props.ryUser.userIds;
  //接听人单呼就是phone userIds 两个  数据不一样

  console.log("paramSave.value", paramSave.value, props.ryUser);

  batchAdd(paramSave.value).then((res) => {
    if (res.code == 200) {
      console.log("批量新增融合通讯通话");
    }
  });
};

// 接听后端接口
const acceptApi = (item) => {
  console.log("调取了接听接口", item);

  let params = {
    promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人 当前登陆人
    answerThridUserId: item.phone, //接听人
    updateBy: localStorage.getItem("userNo"), //当前登录用户
  };
  answer(params).then((res) => {
    console.log("调取了接听接口", res);
  });
};

// 挂断融合通讯通话
const hungupApi = (item) => {
  console.log("调取了挂断接口", item);
  let params = {
    promoterThridUserId: appStore.userInfo.phonenumber + "_web", //发起人
    answerThridUserId: item.phone, //接听人
    updateBy: localStorage.getItem("userNo"),
    answerType: item.answerType || null,
  };

  hangUp(params).then((res) => {
    console.log("调取了挂断接口", res);
  });
};

// 获取所有注册用户
const GetallOnline = (item) => {
  inviteuserIdsList.value = [];

  // let params = {
  //   isOnlion: 1,
  // };
  listrcloudUnitUser().then((res) => {
    res.data.map((item) => {
      item.userTokenList.map((p) => {
        p.name = item.name;
        // 过滤数据 保留当前在线并且不在已呼叫名单上的人
        if (!props.ryUser.userIds.includes(p.registerThridUserId)) {
          if (p.isOnlion == 1 && p.registerPlatform == "web") {
            inviteuserIdsList.value.push(p);
          } else if (p.registerPlatform == "app") {
            inviteuserIdsList.value.push(p);
          }
        }
      });
    });
    console.log("被邀请人", inviteuserIdsList.value, props.ryUser.userIds);
  });
};

/**
 * video 视图渲染
 */
const appendVideoEl = async (track) => {
  const uid = track.getUserId();
  let params = {
    phone: uid.split("_")[0],
  };
  let mediaType = useRongyunStore.mediaType;
  let data = await listrcloudUnitUser(params);
  console.log("datar", data);
  if (data.code == 200) {
    const container = getDom("#videoView");
    const node = document.createElement("div");
    node.setAttribute("id", `video-${uid}`);
    if (allData.idList.findIndex((i) => i == `video-${uid}`) == -1) {
      allData.idList.push(`video-${uid}`);
    }
    let videoTpl = null;
    if (mediaType == 1) {
      videoTpl = `<span class="video-user-id">${data.data[0].name}: ${uid} </span><span class="video-media-type"></span><video id="${uid}"></video>`;
    } else {
      videoTpl = `<span class="video-user-id">${data.data[0].name}: ${uid} </span><video id="${uid}"></video>`;
    }
    node.innerHTML = videoTpl;
    node.classList.add("video-item");

    let length = allData.userList.length;
    console.log("什么东西", allData.userId, uid, props.callType, length);

    // 根据人数判断渲染不同的视图样式
    if (allData.userId + "_web" == uid && length <= 2) {
      // 判断是当前用户 人数小于等于2人时给予单独样式
      node.classList.add("first-child");
    }
    if (length > 2 && length <= 4) {
      node.classList.add("video-item-4");
    }
    if (length > 4 && length <= 8) {
      node.classList.add("video-item-8");
    }
    if (track.isAudioTrack()) {
      if (mediaType == 1) {
        container.appendChild(node);
      }
      track.play();
    } else {
      container.appendChild(node);
      const videoEl = document.getElementById(`${track.getUserId()}`);
      track.play(videoEl);
    }
  }
};

/**
 * 挂断当前 callSession
 */
const hungup = () => {
  // console.log("1231------------------------", useRongyunStore.callSession);
  useRongyunStore.callSession.hungup().then(({ code }) => {
    if (code === 10000) {
      // proxy.$modal.msgWarning("挂断成功");
      getDom("#call").style.display = "block";
      getDom("#hunup").style.display = "none";
      getDom("#invite").style.display = "none";
      getDom("#inviteList").style.visibility = "hidden";

      removeVideoEl();
      restore();
    } else {
      proxy.$modal.msgWarning(`挂断失败,错误原因:${code}`);
    }

    allData.userList.forEach((element) => {
      element.typeFont = "已挂断";
      element.ClassType = 3;
      element.answerType = code;
      hungupApi(element);
    });
  });
};
const removeVideoEl = () => {
  getDom("#videoView").innerHTML = "";
  getDom("#videoView").style = "";
  getDom("#call").style.display = "block";
  getDom("#hunup").style.display = "none";
  getDom("#invite").style.display = "none";
  getDom("#inviteList").style.visibility = "hidden";
  allData.idList = [];
};
const close = () => {
  if (useRongyunStore.callSession != null) {
    hungup();
  }
  restore();
};
// 输出组件的方法,让外部组件可以调用
defineExpose({
  close,
});

// 还原
const restore = () => {
  let style = { width: "934px" };
  getDom("#rong-action").style.display = "block";
  const dom = getDom("#RongYun");
  const dom1 = getDom("#videoView");
  getDom("#videoView").style = "";
  allData.idList.forEach((item, index) => {
    let videodom = getDom(`#${item}`);
    if (videodom) {
      videodom.style = null;
    }
    const titledom = getDom(`#${item} .video-user-id`);
    if (titledom) {
      titledom.style = null;
    }
  });
};
</script>
<style lang="scss">
@import "@/views/RongyunCommunication/rongyuncss/index.scss";
.rong-container {
  padding: 0 20px;
  display: flex;
  width: 100%;
  height: 100%;

  .rong-action {
    width: 35%;
    display: flex;
    flex-direction: column;
    margin-right: 20px;

    #inviteList {
      visibility: hidden;
    }

    .textDiv {
      min-height: 38px;
      line-height: 38px;
      display: flex;
      align-items: center;
      :deep(.el-radio) {
        color: #fff;
      }
      .dote {
        display: block;
        margin-right: 20px;
        width: 10px;
        height: 10px;
        background: #58fdff;
        border-radius: 10px;
      }

      .text {
        display: flex;
        flex: 1;
        justify-content: space-between;
        font-size: 16px;
        font-family: Alibaba PuHuiTi;
        font-weight: 500;
        color: #81b6d4;
      }
      .Rightvalue {
        max-width: 160px;
        overflow: hidden; //溢出隐藏
        text-overflow: ellipsis; //超出显示省略号
        white-space: nowrap; //强制文本在一行内显示
        color: #fff;
      }
    }

    .textDiv2 {
      // min-height: 45px;
      // line-height: 45px;

      .dote {
        display: block;
        margin-right: 20px;
        width: 10px;
        height: 10px;
        background: #58fdff;
        border-radius: 10px;
      }

      .textTitle {
        height: 40px;
        line-height: 40px;
        display: flex;
        align-items: center;
        font-size: 16px;
        font-weight: 500;
        color: #81b6d4;
      }

      .userList {
        max-height: 220px;
        overflow-y: auto;
        .user {
          // background-color: #14306a;
          // padding: 0 10px;
          // margin-left: 20px;
          // border-bottom: 1px solid #0a0a0a;
          display: flex;
          justify-content: space-between;
          background: #093a73;
          align-items: center;
          border-radius: 1px;
          padding: 2px 8px;
        }
        .username {
          min-width: 52px;
          font-weight: 500;
          color: #13bfff;
        }
        .userphone {
          font-weight: bold;
          color: #ffffff;
        }
        .userInfoClass {
          display: flex;
          align-items: center;
        }
        .ceninfoBox {
          padding-left: 5px;
          .userdept {
            max-width: 140px;
            font-size: 12px;
            text-align: center;
          }
        }
      }
    }

    .rong-login-box {
      // margin-left: 20px;
      display: flex;
      width: 300px;
      height: 40px;
      align-items: center;
      margin-top: 50px;
      .btn {
        margin-right: 20px;
        background: #14306a;
        border-radius: 4px;
        padding: 6px 10px;
        font-size: 16px;
        font-family: Alibaba PuHuiTi;
        font-weight: 400;
        display: flex;
        justify-content: space-around;
        align-items: center;
        color: #fff;
        margin: 0 auto;
        cursor: pointer;
        img {
          width: 18px;
          height: 18px;
        }
      }
      .callBtn {
        background: #67c23a;
      }
      .hangBtn {
        background: #ff3a5a;
      }

      .inviteBtn {
        background: #158bd2;
      }

      .hunupBtn {
        display: none;
      }

      .acceptBtn {
        display: none;
      }
    }
  }

  .rong-video-box {
    width: 70%;
    height: 600px;
    display: flex;
    flex-wrap: wrap;
    overflow: auto;
    position: relative;

    .video-item {
      box-sizing: border-box;
      width: 100%;
      height: calc(100% - 10px);
      position: relative;

      .video-user-id {
        position: absolute;
        top: 5px;
        padding: 0 5px;
        border-radius: 2px;
        z-index: 100;
        font-size: 14px;
        color: #58fdff;
        display: inline-block;
        width: 100%;
        text-align: center;
      }

      .video-media-type {
        position: absolute;
        top: 30px;
        left: 0;
        display: block;
        width: 100%;
        height: calc(100% - 30px);
        background: url("@/assets/images/rongYunImg/yuyin_bg.png") no-repeat;
        background-size: 100% 100%;
      }

      video {
        width: 100%;
        height: 100%;
      }
    }

    .video-item-4 {
      width: 50%;
      height: calc(50% - 10px);
    }

    .video-item-8 {
      width: 25%;
      height: calc(50% - 10px);
    }

    .first-child {
      position: absolute;
      right: 0px;
      top: 0;
      width: 40%;
      height: 30%;
      z-index: 999;
    }
  }
}
</style>

<style lang="scss" scoped>
.RongYun {
  width: 100%;
  height: calc(100% - 40px);
  position: relative;

  .closeIcon {
    position: absolute;
    right: 0px;
    top: -30px;

    &:hover {
      color: chocolate;
    }
  }
}
</style>