支持登录网易云账号

This commit is contained in:
qier222 2020-10-17 00:13:20 +08:00
parent 861125ea8c
commit 5f0ef06786
15 changed files with 734 additions and 153 deletions

View File

@ -10,8 +10,10 @@
"dependencies": {
"axios": "^0.20.0",
"core-js": "^3.6.5",
"crypto-js": "^4.0.0",
"dayjs": "^1.8.36",
"howler": "^2.2.0",
"js-cookie": "^2.2.1",
"nprogress": "^0.2.0",
"plyr": "^3.6.2",
"register-service-worker": "^1.7.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

42
src/api/auth.js Normal file
View File

@ -0,0 +1,42 @@
import request from "@/utils/request";
export function loginWithPhone(params) {
//必选参数 :
// phone: 手机号码
// password: 密码
// 可选参数 :
// countrycode: 国家码用于国外手机号登录例如美国传入1
// md5_password: md5加密后的密码,传入后 password 将失效
return request({
url: "/login/cellphone",
method: "post",
params,
});
}
export function loginWithEmail(params) {
// 必选参数 :
// email: 163 网易邮箱
// password: 密码
// 可选参数 :
// md5_password: md5加密后的密码,传入后 password 将失效
return request({
url: "/login",
method: "post",
params,
});
}
export function loginRefresh() {
return request({
url: "/login/refresh",
method: "post",
});
}
export function logout() {
return request({
url: "/logout",
method: "post",
});
}

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock-alt" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-lock-alt fa-w-14 fa-7x"><path fill="currentColor" d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zM264 392c0 22.1-17.9 40-40 40s-40-17.9-40-40v-48c0-22.1 17.9-40 40-40s40 17.9 40 40v48zm32-168H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z" class=""></path></svg>

After

Width:  |  Height:  |  Size: 550 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="envelope" class="svg-inline--fa fa-envelope fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"></path></svg>

After

Width:  |  Height:  |  Size: 700 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="mobile-alt" class="svg-inline--fa fa-mobile-alt fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M272 0H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h224c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zM160 480c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm112-108c0 6.6-5.4 12-12 12H60c-6.6 0-12-5.4-12-12V60c0-6.6 5.4-12 12-12h200c6.6 0 12 5.4 12 12v312z"></path></svg>

After

Width:  |  Height:  |  Size: 516 B

1
src/assets/icons/x.svg Normal file
View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times" class="svg-inline--fa fa-times fa-w-11" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>

After

Width:  |  Height:  |  Size: 645 B

View File

@ -3,6 +3,7 @@ import VueRouter from "vue-router";
import store from "@/store";
import NProgress from "nprogress";
import "@/assets/css/nprogress.css";
import Cookies from "js-cookie";
NProgress.configure({ showSpinner: false, trickleSpeed: 100 });
@ -16,7 +17,21 @@ const routes = [
keepAlive: true,
},
},
{ path: "/login", name: "login", component: () => import("@/views/login") },
{
path: "/login",
name: "login",
component: () => import("@/views/login"),
},
{
path: "/login/username",
name: "loginUsername",
component: () => import("@/views/loginUsername"),
},
{
path: "/login/account",
name: "loginAccount",
component: () => import("@/views/loginAccount"),
},
{
path: "/playlist/:id",
name: "playlist",
@ -88,11 +103,6 @@ const router = new VueRouter({
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve(savedPosition);
// }, 100);
// });
return savedPosition;
} else {
return { x: 0, y: 0 };
@ -104,6 +114,12 @@ router.beforeEach((to, from, next) => {
if (to.meta.requireLogin) {
if (store.state.settings.user.nickname === undefined) {
next({ path: "/login" });
}
if (
Cookies.get("MUSIC_U") === undefined &&
Cookies.get("loginMode") === "account"
) {
next({ path: "/login" });
} else {
next();
}

12
src/utils/auth.js Normal file
View File

@ -0,0 +1,12 @@
import Cookies from "js-cookie";
import { logout } from "@/api/auth";
export function doLogout() {
console.log("logout");
logout();
Cookies.remove("loginMode");
}
export function isLoggedIn() {
return Cookies.get("MUSIC_U") !== undefined ? true : false;
}

View File

@ -12,6 +12,10 @@ service.interceptors.response.use(
if (res.code !== 200) {
if (res.code === 401) {
alert("token expired");
} else if (res.code === 502) {
alert(res.msg);
} else if (res.code === 301) {
alert("required login");
} else {
alert("unknow error");
}

View File

@ -1,95 +1,64 @@
<template>
<div class="login">
<div>
<div class="title">Login</div>
<div class="step">
<div class="search-box">
<div class="container">
<svg-icon icon-class="search" />
<div class="input">
<input
placeholder="请输入你的用户名"
v-model="keyword"
@keydown.enter="search"
/>
</div>
</div>
</div>
</div>
<div class="step">
<div class="name" v-show="activeUser.nickname === undefined">
按Enter搜索
</div>
<div class="name" v-show="activeUser.nickname !== undefined">
在列表中选中你的账号
</div>
<div class="user-list">
<div
class="user"
v-for="user in result"
:key="user.id"
:class="{ active: user.nickname === activeUser.nickname }"
@click="activeUser = user"
>
<img class="head" :src="user.avatarUrl | resizeImage" />
<div class="nickname">
{{ user.nickname }}
</div>
</div>
</div>
</div>
<ButtonTwoTone
@click.native="confirm"
v-show="activeUser.nickname !== undefined"
>确定</ButtonTwoTone
<div class="section-1">
<img src="/img/logos/yesplaymusic.png" />
<svg-icon icon-class="x"></svg-icon>
<img src="/img/logos/netease-music.png" />
</div>
<div class="section-2">
<div
class="card"
@mouseover="activeCard = 1"
@mouseleave="activeCard = 0"
@click="goTo('account')"
>
<div class="container" :class="{ active: activeCard === 1 }">
<div class="title-info">
<div class="title">登录网易云账号</div>
<div class="info">可访问全部数据</div>
</div>
<svg-icon icon-class="arrow-right"></svg-icon>
</div>
</div>
<div
class="card"
@mouseover="activeCard = 2"
@mouseleave="activeCard = 0"
@click="goTo('username')"
>
<div class="container" :class="{ active: activeCard === 2 }">
<div class="title-info">
<div class="title">搜索网易云账号</div>
<div class="info">只能读取账号公开数据</div>
</div>
<svg-icon icon-class="arrow-right"></svg-icon>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import NProgress from "nprogress";
import { search } from "@/api/others";
import { userPlaylist } from "@/api/user";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import SvgIcon from "@/components/SvgIcon.vue";
export default {
name: "Login",
components: {
ButtonTwoTone,
SvgIcon,
},
data() {
return {
keyword: "",
result: [],
activeUser: {},
activeCard: 0,
};
},
created() {
NProgress.done();
},
methods: {
...mapMutations(["updateUser"]),
search() {
search({ keywords: this.keyword, limit: 9, type: 1002 }).then((data) => {
this.result = data.result.userprofiles;
this.activeUser = this.result[0];
});
},
confirm() {
this.updateUser(this.activeUser);
userPlaylist({
uid: this.activeUser.userId,
limit: 1,
}).then((data) => {
this.$store.commit("updateUserInfo", {
key: "likedSongPlaylistID",
value: data.playlist[0].id,
});
this.$router.push({ path: "/library" });
});
goTo(path) {
this.$router.push({ path: "/login/" + path });
},
},
};
@ -98,95 +67,83 @@ export default {
<style lang="scss" scoped>
.login {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: calc(100vh - 192px);
}
.title {
font-size: 42px;
font-weight: 700;
margin-bottom: 48px;
}
.step {
margin-top: 18px;
.name {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: rgba(0, 0, 0, 0.78);
}
}
.search-box {
.container {
display: flex;
align-items: center;
height: 48px;
border-radius: 11px;
width: 326px;
background: #eaeffd;
}
.svg-icon {
height: 22px;
width: 22px;
color: #335eea;
margin: {
left: 12px;
right: 8px;
}
}
input {
flex: 1;
font-size: 22px;
border: none;
background: transparent;
width: 100%;
font-weight: 600;
margin-top: -1px;
color: #335eea;
&::placeholder {
color: #335eeac4;
}
}
}
.user-list {
display: flex;
flex-wrap: wrap;
margin-top: 24px;
margin-bottom: 24px;
}
.user {
margin-right: 16px;
.section-1 {
margin-bottom: 16px;
display: flex;
align-items: center;
padding: 12px 12px 12px 16px;
border-radius: 8px;
width: 256px;
transition: 0.2s;
user-select: none;
.head {
border-radius: 50%;
height: 44px;
width: 44px;
img {
height: 64px;
margin: 20px;
}
.nickname {
font-size: 18px;
margin-left: 12px;
}
&:hover {
background: #f5f5f7;
.svg-icon {
height: 24px;
width: 24px;
color: rgba(82, 82, 82, 0.28);
}
}
.user.active {
transition: 0.2s;
.section-2 {
display: flex;
align-items: center;
flex-direction: column;
}
.card {
cursor: pointer;
margin-top: 14px;
margin-bottom: 14px;
display: flex;
justify-content: center;
align-items: center;
background: #eaeffd;
.name {
border-radius: 8px;
height: 128px;
width: 300px;
transition: all 0.3s;
.active {
.title-info {
transform: translateX(-8px);
}
.svg-icon {
opacity: 1;
visibility: visible;
transform: translateX(8px);
}
}
.container {
display: flex;
// justify-content: space-around;
align-items: center;
color: #335eea;
}
.title-info {
transition: all 0.3s;
}
.title {
font-size: 24px;
font-weight: 600;
}
.info {
margin-top: 2px;
font-size: 14px;
color: rgba(51, 94, 234, 0.78);
}
.svg-icon {
opacity: 0;
height: 24px;
width: 24px;
margin-left: 16px;
transition: all 0.3s;
}
}
</style>

340
src/views/loginAccount.vue Normal file
View File

@ -0,0 +1,340 @@
<template>
<div class="login">
<div class="section-1">
<img src="/img/logos/netease-music.png" />
</div>
<div class="title">登录网易云账号</div>
<div class="section-2">
<div class="input-box" v-show="mode === 'phone'">
<div class="container" :class="{ active: inputFocus === 'phone' }">
<svg-icon icon-class="mobile" />
<div class="inputs">
<input
id="countryCode"
:placeholder="inputFocus === 'phone' ? '' : '国际区号'"
v-model="countryCode"
@focus="inputFocus = 'phone'"
@blur="inputFocus = ''"
/>
<input
id="phoneNumber"
:placeholder="inputFocus === 'phone' ? '' : '手机号'"
v-model="phoneNumber"
@focus="inputFocus = 'phone'"
@blur="inputFocus = ''"
/>
</div>
</div>
</div>
<div class="input-box" v-show="mode === 'email'">
<div class="container" :class="{ active: inputFocus === 'email' }">
<svg-icon icon-class="mail" />
<div class="inputs">
<input
type="email"
id="email"
:placeholder="inputFocus === 'email' ? '' : '邮箱'"
v-model="email"
@focus="inputFocus = 'email'"
@blur="inputFocus = ''"
/>
</div>
</div>
</div>
<div class="input-box">
<div class="container" :class="{ active: inputFocus === 'password' }">
<svg-icon icon-class="lock" />
<div class="inputs">
<input
type="password"
id="password"
:placeholder="inputFocus === 'password' ? '' : '密码'"
v-model="password"
@focus="inputFocus = 'password'"
@blur="inputFocus = ''"
/>
</div>
</div>
</div>
</div>
<div class="confirm">
<button @click="login" v-show="!processing">登录</button>
<button v-show="processing" class="loading" disabled>
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="other-login">
<a v-show="mode === 'phone'" @click="mode = 'email'">使用邮箱登录</a>
<a v-show="mode === 'email'" @click="mode = 'phone'">使用手机号登录</a>
</div>
<div class="notice">
YesPlayMusic 承诺不会保存你的任何账号信息到云端<br />
你的密码会在本地进行 MD5 加密后再传输到网易云 API<br />
YesPlayMusic 并非网易云官方网站输入账号信息前请慎重考虑 你也可以前往
<a href="https://github.com/qier222/YesPlayMusic"
>YesPlayMusic GitHub 源代码仓库</a
>
自行构建并使用自托管的网易云 API
</div>
</div>
</template>
<script>
import NProgress from "nprogress";
import { loginWithPhone, loginWithEmail } from "@/api/auth";
import md5 from "crypto-js/md5";
import { mapMutations } from "vuex";
import { userPlaylist } from "@/api/user";
import Cookies from "js-cookie";
export default {
name: "Login",
data() {
return {
processing: false,
mode: "email",
countryCode: "+86",
phoneNumber: "",
email: "",
password: "",
smsCode: "",
inputFocus: "",
};
},
created() {
if (this.$route.query.mode === "phone") {
this.mode = "phone";
}
NProgress.done();
},
methods: {
...mapMutations(["updateUser", "updateUserInfo"]),
afterLogin() {
Cookies.set("loginMode", "account", { expires: 3650 });
userPlaylist({
uid: this.$store.state.settings.user.userId,
limit: 1,
}).then((data) => {
this.updateUserInfo({
key: "likedSongPlaylistID",
value: data.playlist[0].id,
});
this.$router.push({ path: "/library" });
});
},
login() {
this.processing = true;
if (this.mode === "phone") {
if (
this.countrycode === "" ||
this.phone === "" ||
this.password === ""
) {
alert("请输入完整的信息");
this.processing = false;
return;
}
loginWithPhone({
countrycode: this.countryCode.replace("+", "").replaceAll(" ", ""),
phone: this.phoneNumber.replaceAll(" ", ""),
password: "fakePassword",
md5_password: md5(this.password).toString(),
})
.then((data) => {
this.updateUser(data.profile);
this.afterLogin();
})
.catch(() => {
this.processing = false;
});
} else {
if (this.email === "" || this.password === "") {
alert("请输入完整的信息");
this.processing = false;
return;
}
loginWithEmail({
email: this.email.replaceAll(" ", ""),
password: "fakePassword",
md5_password: md5(this.password).toString(),
})
.then((data) => {
this.updateUser(data.profile);
this.afterLogin();
})
.catch(() => {
this.processing = false;
});
}
},
},
};
</script>
<style lang="scss" scoped>
.login {
display: flex;
flex-direction: column;
align-items: center;
min-height: calc(100vh - 192px);
}
.title {
font-size: 24px;
font-weight: 700;
margin-bottom: 48px;
}
.section-1 {
margin-bottom: 16px;
display: flex;
align-items: center;
img {
height: 64px;
margin: 20px;
}
.svg-icon {
height: 24px;
width: 24px;
color: rgba(82, 82, 82, 0.28);
}
}
.section-2 {
display: flex;
align-items: center;
flex-direction: column;
}
.input-box {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
.container {
display: flex;
align-items: center;
height: 46px;
background: rgba(0, 0, 0, 0.06);
border-radius: 8px;
width: 300px;
}
.svg-icon {
height: 18px;
width: 18px;
color: #aaaaaa;
margin: {
left: 12px;
right: 6px;
}
}
.inputs {
display: flex;
}
input {
font-size: 20px;
border: none;
background: transparent;
width: 96%;
font-weight: 600;
margin-top: -1px;
color: rgba(0, 0, 0, 0.88);
}
input#countryCode {
flex: 2;
}
input#phoneNumber {
flex: 14;
}
.active {
background: #eaeffd;
input,
.svg-icon {
color: #335eea;
}
}
}
.confirm button {
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 600;
background-color: rgba(51, 94, 234, 0.1);
color: #335eea;
border-radius: 8px;
margin-top: 24px;
transition: 0.2s;
padding: 8px;
width: 100%;
width: 300px;
&:hover {
transform: scale(1.06);
}
&:active {
transform: scale(0.94);
}
}
.other-login {
margin-top: 24px;
a {
cursor: pointer;
font-size: 13px;
color: rgba(0, 0, 0, 0.68);
}
}
.notice {
width: 300px;
border-top: 1px solid rgba(0, 0, 0, 0.18);
margin-top: 48px;
padding-top: 12px;
font-size: 12px;
color: rgba(0, 0, 0, 0.48);
}
@keyframes loading {
0% {
opacity: 0.2;
}
20% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
button.loading {
height: 44px;
cursor: unset;
&:hover {
transform: none;
}
}
.loading span {
width: 6px;
height: 6px;
background-color: #335eea;
border-radius: 50%;
margin: 0 2px;
animation: loading 1.4s infinite both;
}
.loading span:nth-child(2) {
animation-delay: 0.2s;
}
.loading span:nth-child(3) {
animation-delay: 0.4s;
}
</style>

194
src/views/loginUsername.vue Normal file
View File

@ -0,0 +1,194 @@
<template>
<div class="login">
<div>
<div class="title">用户名登录</div>
<div class="sestion">
<div class="search-box">
<div class="container">
<svg-icon icon-class="search" />
<div class="input">
<input
placeholder="请输入你的网易云用户名"
v-model="keyword"
@keydown.enter="search"
/>
</div>
</div>
</div>
</div>
<div class="sestion">
<div class="name" v-show="activeUser.nickname === undefined">
按Enter搜索
</div>
<div class="name" v-show="activeUser.nickname !== undefined">
在列表中选中你的账号
</div>
<div class="user-list">
<div
class="user"
v-for="user in result"
:key="user.id"
:class="{ active: user.nickname === activeUser.nickname }"
@click="activeUser = user"
>
<img class="head" :src="user.avatarUrl | resizeImage" />
<div class="nickname">
{{ user.nickname }}
</div>
</div>
</div>
</div>
<ButtonTwoTone
@click.native="confirm"
v-show="activeUser.nickname !== undefined"
>确定</ButtonTwoTone
>
</div>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import NProgress from "nprogress";
import { search } from "@/api/others";
import Cookies from "js-cookie";
import { userPlaylist } from "@/api/user";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
export default {
name: "loginUsername",
components: {
ButtonTwoTone,
},
data() {
return {
keyword: "",
result: [],
activeUser: {},
};
},
created() {
NProgress.done();
},
methods: {
...mapMutations(["updateUser", "updateUserInfo"]),
search() {
search({ keywords: this.keyword, limit: 9, type: 1002 }).then((data) => {
this.result = data.result.userprofiles;
this.activeUser = this.result[0];
});
},
confirm() {
this.updateUser(this.activeUser);
Cookies.set("loginMode", "username", { expires: 3650 });
userPlaylist({
uid: this.activeUser.userId,
limit: 1,
}).then((data) => {
this.updateUserInfo({
key: "likedSongPlaylistID",
value: data.playlist[0].id,
});
this.$router.push({ path: "/library" });
});
},
},
};
</script>
<style lang="scss" scoped>
.login {
display: flex;
}
.title {
font-size: 42px;
font-weight: 700;
margin-bottom: 48px;
}
.sestion {
margin-top: 18px;
.name {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: rgba(0, 0, 0, 0.78);
}
}
.search-box {
.container {
display: flex;
align-items: center;
height: 48px;
border-radius: 11px;
width: 326px;
background: #eaeffd;
}
.svg-icon {
height: 22px;
width: 22px;
color: #335eea;
margin: {
left: 12px;
right: 8px;
}
}
input {
flex: 1;
font-size: 22px;
border: none;
background: transparent;
width: 115%;
font-weight: 600;
margin-top: -1px;
color: #335eea;
&::placeholder {
color: #335eeac4;
}
}
}
.user-list {
display: flex;
flex-wrap: wrap;
margin-top: 24px;
margin-bottom: 24px;
}
.user {
margin-right: 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
padding: 12px 12px 12px 16px;
border-radius: 8px;
width: 256px;
transition: 0.2s;
user-select: none;
.head {
border-radius: 50%;
height: 44px;
width: 44px;
}
.nickname {
font-size: 18px;
margin-left: 12px;
}
&:hover {
background: #f5f5f7;
}
}
.user.active {
transition: 0.2s;
background: #eaeffd;
.name {
color: #335eea;
}
}
</style>

View File

@ -2840,6 +2840,11 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
crypto-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc"
integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==
css-color-names@0.0.4, css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.npm.taobao.org/css-color-names/download/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -5042,6 +5047,11 @@ js-base64@^2.1.9:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-message@1.0.5:
version "1.0.5"
resolved "https://registry.npm.taobao.org/js-message/download/js-message-1.0.5.tgz#2300d24b1af08e89dd095bc1a4c9c9cfcb892d15"