Compare commits
6 Commits
f37f283f09
...
89a3652003
Author | SHA1 | Date | |
---|---|---|---|
|
89a3652003 | ||
|
7dfda1a3d3 | ||
|
5315b0fc0c | ||
|
e86c8dbf3b | ||
|
9ca23ceca7 | ||
|
38e0cfcdb6 |
@ -1,55 +0,0 @@
|
||||
import type { $Fetch } from 'ofetch'
|
||||
|
||||
import { useRuntimeConfig } from '#app'
|
||||
import { ofetch } from 'ofetch'
|
||||
|
||||
type HttpStatusErrorHandler = (message: string, statusCode: number) => void
|
||||
let httpStatusErrorHandler: HttpStatusErrorHandler
|
||||
|
||||
let http: $Fetch
|
||||
|
||||
export function setupHttp() {
|
||||
if (http)
|
||||
return http
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.apiBase as string
|
||||
|
||||
http = ofetch.create({
|
||||
baseURL,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
async onRequest({ options }) {
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
}
|
||||
},
|
||||
async onResponseError({ response }) {
|
||||
const { message } = response._data
|
||||
if (Array.isArray(message)) {
|
||||
message.forEach((item) => {
|
||||
httpStatusErrorHandler?.(item, response.status)
|
||||
})
|
||||
}
|
||||
else {
|
||||
httpStatusErrorHandler?.(message, response.status)
|
||||
}
|
||||
return Promise.reject(response._data)
|
||||
},
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function injectHttpStatusErrorHandler(handler: HttpStatusErrorHandler) {
|
||||
httpStatusErrorHandler = handler
|
||||
}
|
||||
|
||||
export function getHttp() {
|
||||
if (!http) {
|
||||
throw new Error('HTTP client not initialized. Call setupHttp first.')
|
||||
}
|
||||
return http
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { getHttp } from './http'
|
||||
|
||||
export async function getProse() {
|
||||
const http = getHttp()
|
||||
return await http('/api/prose', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
@ -22,7 +22,6 @@ const addItem = () => {
|
||||
d: 'RMB5,500',
|
||||
e: ''
|
||||
});
|
||||
// 确保 DOM 更新后再滚动
|
||||
nextTick(() => {
|
||||
scrollToBottom();
|
||||
});
|
||||
|
55
app/pages/LiveRoom/components/paymentInput/index.vue
Normal file
55
app/pages/LiveRoom/components/paymentInput/index.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:show'])
|
||||
const payStatus=ref(0)
|
||||
const changePayStatus=()=>{
|
||||
payStatus.value=payStatus.value===0?1:0
|
||||
}
|
||||
const close=()=>{
|
||||
emit('update:show',false)
|
||||
}
|
||||
const confirm=()=>{
|
||||
emit('update:show',false)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<van-dialog :show="show" show-cancel-button @close="close" @confirm="confirm">
|
||||
<div class="flex flex-col pt-18px pb-13px justify-between items-center h-144px">
|
||||
<template v-if="payStatus===0">
|
||||
<div class="text-#000 text-16px font-600 ">支付全部</div>
|
||||
<div class="text-#000 text-16px ">RMB 5,000</div>
|
||||
</template>
|
||||
<template v-if="payStatus===1">
|
||||
<div class="text-#000 text-16px font-600 ">支付部分</div>
|
||||
<input class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" placeholder="最多RMB5,000">
|
||||
</template>
|
||||
|
||||
<div class="text-#2B53AC text-14px" @click="changePayStatus">{{payStatus===0 ? '支付部分' : '支付全部'}}</div>
|
||||
</div>
|
||||
</van-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.van-hairline--top.van-dialog__footer){
|
||||
&>.van-button{
|
||||
border-top: 1px solid #E7E7E7;
|
||||
&.van-dialog__cancel{
|
||||
border-right: 1px solid #E7E7E7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
44
app/pages/LiveRoom/components/paymentResults/index.vue
Normal file
44
app/pages/LiveRoom/components/paymentResults/index.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import successImg from '@/static/images/zu5554@2x.png'
|
||||
import errorImg from '@/static/images/zu5561@2x.png'
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['cancel','update:show'])
|
||||
const cancel= () => {
|
||||
emit('update:show', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<van-dialog :show="show" show-cancel-button :show-confirm-button="false" cancelButtonText="返回" cancelButtonColor="#2B53AC" @cancel="cancel">
|
||||
<div class="h-145px relative flex justify-center">
|
||||
<img :src="type==='success' ? successImg : errorImg" class="w-119px h-120px absolute top--74px z-9999 left-1/2 transform translate-x--1/2" alt="">
|
||||
<div class="mt-94px text-#A9A9A9 text-16px">{{price}}</div>
|
||||
</div>
|
||||
</van-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.van-dialog) {
|
||||
overflow: visible!important;
|
||||
}
|
||||
:deep(.van-hairline--top.van-dialog__footer){
|
||||
border-top: 1px solid #E7E7E7;
|
||||
border-bottom-left-radius:8px ;
|
||||
border-bottom-right-radius:8px ;
|
||||
}
|
||||
</style>
|
@ -16,8 +16,8 @@ const handleButtonRelease = () => {
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'bg-white transition-all duration-200',
|
||||
isButtonActive ? 'scale-95 bg-gray-200' : ''
|
||||
'transition-all duration-200',
|
||||
isButtonActive ? 'scale-95' : ''
|
||||
]"
|
||||
@touchstart="handleButtonPress"
|
||||
@touchend="handleButtonRelease"
|
||||
|
@ -4,7 +4,7 @@ import lockClosed from "~/static/images/lockdfd@2x.png";
|
||||
import lockOpen from "~/static/images/lock4@2x.png";
|
||||
import { liveStore } from "~/stores/live/index.js";
|
||||
import PressableButton from './PressableButton.vue'
|
||||
const { quoteStatus, changeStatus,show } = liveStore();
|
||||
const { quoteStatus, changeStatus,show,show1 } = liveStore();
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -2,13 +2,15 @@
|
||||
import {ref, onMounted, onBeforeUnmount} from 'vue'
|
||||
import Aliplayer from 'aliyun-aliplayer'
|
||||
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
|
||||
import lock4 from '@/static/images/lock4@2x.png'
|
||||
import sideButton from './components/sideButton/index.vue'
|
||||
import broadcast from './components/broadcast/index.vue'
|
||||
import lockdfd from '@/static/images/lockdfd@2x.png'
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
import paymentResults from './components/paymentResults/index.vue'
|
||||
import paymentInput from './components/paymentInput/index.vue'
|
||||
import PressableButton from './components/sideButton/PressableButton.vue'
|
||||
|
||||
const player = ref(null)
|
||||
const {quoteStatus,changeStatus,show}= liveStore()
|
||||
const {quoteStatus, changeStatus, show, playerId, show1} = liveStore()
|
||||
const isPlayerReady = ref(false)
|
||||
const props = defineProps({
|
||||
fullLive: {
|
||||
@ -16,9 +18,15 @@ const props = defineProps({
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
title: '主页',
|
||||
i18n: 'login.title',
|
||||
})
|
||||
const config = useRuntimeConfig()
|
||||
const playerConfig = {
|
||||
id: 'J_prismPlayer',
|
||||
source: 'artc://live-pull-sh-01.szjixun.cn/live/live?auth_key=1736748343-0-0-feef65166e5cc62957c35b6e3eec82a1',
|
||||
id: playerId.value,
|
||||
source: config.public.NUXT_PUBLIC_PLAYER_SOURCE,
|
||||
isLive: true,
|
||||
preload: true,
|
||||
autoplayPolicy: {fallbackToMute: true},
|
||||
@ -59,14 +67,14 @@ onBeforeUnmount(() => {
|
||||
player.value = null
|
||||
}
|
||||
})
|
||||
const goPay=()=>{
|
||||
show.value=true
|
||||
const goPay = () => {
|
||||
show.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full">
|
||||
<div id="J_prismPlayer" class="w-screen"
|
||||
<div :id="playerId" class="w-screen"
|
||||
:style="fullLive?'height: calc(100vh - var(--van-nav-bar-height))':'height:100%'"></div>
|
||||
<template v-if="fullLive">
|
||||
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
|
||||
@ -79,32 +87,29 @@ const goPay=()=>{
|
||||
下口价:RMB
|
||||
<van-rolling-text class="my-rolling-text1" :start-num="0" :target-num="3500" direction="up"/>
|
||||
</div>
|
||||
<PressableButton>
|
||||
<div
|
||||
:class="`w-344px h-[40px] ${quoteStatus ? 'bg-#FFB25F' : 'bg-#D6D6D8'} rounded-4px ${quoteStatus ? 'text-#fff' : 'text-#7D7D7F'} text-14px flex justify-center items-center mt-10px mb-10px`">
|
||||
{{ quoteStatus ? '确认出价 RMB 3,000' : '点击"开启出价",即刻参与竞拍 ' }}
|
||||
</div>
|
||||
</PressableButton>
|
||||
<broadcast></broadcast>
|
||||
</div>
|
||||
<van-dialog v-model:show="show" show-cancel-button>
|
||||
<div class="flex flex-col pt-18px pb-13px justify-between items-center h-144px">
|
||||
<!-- <div class="text-#000 text-16px font-600 ">支付全部</div>
|
||||
<div class="text-#000 text-16px ">RMB 5,000</div>-->
|
||||
<div class="text-#000 text-16px font-600 ">支付部分</div>
|
||||
<input class="w-272px h-48px bg-#F3F3F3 px-11px text-16px" type="text" placeholder="最多RMB5,000">
|
||||
|
||||
<div class="text-#2B53AC text-14px">支付部分</div>
|
||||
<paymentInput v-model:show="show"/>
|
||||
<div>
|
||||
</div>
|
||||
</van-dialog>
|
||||
<paymentResults v-model:show="show1" type="error"/>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
:root:root{
|
||||
--van-dialog-radius:8px
|
||||
:root:root {
|
||||
--van-dialog-radius: 8px
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
|
||||
.my-rolling-text {
|
||||
--van-rolling-text-item-width: 10px;
|
||||
--van-rolling-text-font-size: 16px;
|
||||
|
@ -1,17 +1,23 @@
|
||||
<script setup>
|
||||
import {useRect} from '@vant/use';
|
||||
import LiveRoom from '@/pages/LiveRoom/index.client.vue'
|
||||
import itemDetail from '@/components/itemDetail/index.vue'
|
||||
import { homeStore } from "@/stores/home/index.js";
|
||||
const { fullLive } = homeStore()
|
||||
import LiveRoom from '@/pages/LiveRoom/index.client.vue';
|
||||
import itemDetail from '@/components/itemDetail/index.vue';
|
||||
import {homeStore} from "@/stores/home/index.js";
|
||||
import Column from './components/Column/index.vue'
|
||||
const {fullLive} = homeStore();
|
||||
import { useFetch } from '#app'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
i18n: 'menu.home',
|
||||
})
|
||||
const liveRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const liveRef = ref(null);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const page = ref(1);
|
||||
const show = ref(false);
|
||||
const showHeight = ref('');
|
||||
const list = ref([{
|
||||
image: 'https://e-cdn.fontree.cn/fonchain-main/prod/file/default/setting/637d95b4-2ae9-4a74-bd60-a3a9d2ca2ca0.png',
|
||||
title: '张天赐 | 日出而作,日落而息',
|
||||
@ -53,158 +59,55 @@ const list = ref([{
|
||||
startingPrice: 'RMB 1,000',
|
||||
transactionPrice: 'RMB 10,000',
|
||||
}])
|
||||
const page = ref(1)
|
||||
|
||||
// 加载数据
|
||||
async function loadData() {
|
||||
// try {
|
||||
// loading.value = true
|
||||
// // 模拟API请求
|
||||
// const {data} = await fetchAuctionList({page: page.value})
|
||||
//
|
||||
// if (refreshing.value) {
|
||||
// list.value = []
|
||||
// refreshing.value = false
|
||||
// }
|
||||
//
|
||||
// list.value.push(...data)
|
||||
// page.value++
|
||||
//
|
||||
// // 如果没有更多数据了
|
||||
// if (data.length < 10) {
|
||||
// finished.value = true
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error(error)
|
||||
// } finally {
|
||||
// loading.value = false
|
||||
// }
|
||||
}
|
||||
const loadData = async () => {
|
||||
// 加载数据逻辑...
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
function onRefresh() {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
const onRefresh = () => {
|
||||
finished.value = false;
|
||||
page.value = 1;
|
||||
refreshing.value = true;
|
||||
loadData();
|
||||
};
|
||||
|
||||
const columns = computed(() => {
|
||||
const result = [[], []];
|
||||
list.value.forEach((item, index) => {
|
||||
result[index % 2].push(item);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
const leftColumn = computed(() => {
|
||||
return list.value.filter((_, index) => index % 2 === 0)
|
||||
})
|
||||
const rightColumn = computed(() => {
|
||||
return list.value.filter((_, index) => index % 2 === 1)
|
||||
})
|
||||
const show = ref(false)
|
||||
const showHeight = ref('')
|
||||
const openShow = () => {
|
||||
const rect = useRect(liveRef.value.$el);
|
||||
showHeight.value = rect.height
|
||||
showHeight.value = rect.height;
|
||||
nextTick(() => {
|
||||
show.value = true
|
||||
})
|
||||
}
|
||||
const changeLive=()=>{
|
||||
fullLive.value=true
|
||||
}
|
||||
show.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
const changeLive = () => {
|
||||
fullLive.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-grow-1">
|
||||
<div
|
||||
@click="changeLive"
|
||||
:class="[
|
||||
'changeLive',
|
||||
fullLive ? 'expanded' : 'collapsed'
|
||||
]"
|
||||
>
|
||||
<div @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']">
|
||||
<client-only>
|
||||
<LiveRoom ref="liveRef" :fullLive="fullLive" />
|
||||
<LiveRoom ref="liveRef" :fullLive="fullLive"/>
|
||||
</client-only>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-show="!fullLive" class="bg-#fff" >
|
||||
<div v-show="!fullLive" class="bg-#fff">
|
||||
<van-tabs sticky animated>
|
||||
<van-tab title="拍品列表">
|
||||
<div class="px-[16px] pt-[16px]">
|
||||
<van-pull-refresh>
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="loadData"
|
||||
>
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="loadData">
|
||||
<div class="w-full flex gap-[16px]">
|
||||
<div class="flex flex-1 flex-col gap-[16px]">
|
||||
<div
|
||||
v-for="(item, index) in leftColumn"
|
||||
:key="index"
|
||||
class="w-full"
|
||||
@click="openShow"
|
||||
>
|
||||
<div class="relative w-full">
|
||||
<van-image
|
||||
:src="item.image"
|
||||
:style="{ aspectRatio: item.ratio }"
|
||||
fit="cover"
|
||||
class="w-full"
|
||||
/>
|
||||
<div
|
||||
class="absolute left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]">
|
||||
LOT{{ index * 2 + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-[8px]">
|
||||
<div class="text-[14px] text-[#000000] leading-[20px]">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="mt-[4px] text-[12px] text-[#575757]">
|
||||
起拍价:{{ item.startingPrice }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item.transactionPrice"
|
||||
class="mt-[4px] text-[12px] text-[#b58047]"
|
||||
>
|
||||
成交价:{{ item.transactionPrice }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-[16px]">
|
||||
<div
|
||||
v-for="(item, index) in rightColumn"
|
||||
:key="index"
|
||||
class="w-full"
|
||||
@click="openShow"
|
||||
>
|
||||
<div class="relative w-full">
|
||||
<van-image
|
||||
:src="item.image"
|
||||
:style="{ aspectRatio: item.ratio }"
|
||||
fit="cover"
|
||||
class="w-full"
|
||||
/>
|
||||
<div
|
||||
class="absolute left-[8px] top-[8px] h-[17px] w-[45px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]">
|
||||
LOT{{ index * 2 + 2 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-[8px]">
|
||||
<div class="text-[14px] text-[#000000] leading-[20px]">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="mt-[4px] text-[12px] text-[#575757]">
|
||||
起拍价:{{ item.startingPrice }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item.transactionPrice"
|
||||
class="mt-[4px] text-[12px] text-[#b58047]"
|
||||
>
|
||||
成交价:{{ item.transactionPrice }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Column v-for="(column, colIndex) in columns" :key="colIndex" :items="column" @openShow="openShow" />
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
@ -215,43 +118,42 @@ const changeLive=()=>{
|
||||
<div class="text-#575757 text-14px">
|
||||
这里是后台富文本配置的说明,啊即可打开三等奖撒度老师的湿答答是快乐的阿四大皆空
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
<van-back-top right="15vw" bottom="10vh"/>
|
||||
<van-action-sheet :round="false" v-model:show="show" title="拍品详情">
|
||||
<div class="content bg-[#F0F0F0]" :style="`height: calc(100vh - ${showHeight+85}px)`">
|
||||
<itemDetail></itemDetail>
|
||||
<div class="content bg-[#F0F0F0]" :style="`height: calc(100vh - ${showHeight + 85}px)`">
|
||||
<itemDetail/>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root:root {
|
||||
--van-action-sheet-header-height: 39px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
:deep(.van-swipe__indicator) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
:deep(.van-swipe__indicator:not(.van-swipe__indicator--active) ) {
|
||||
|
||||
:deep(.van-swipe__indicator:not(.van-swipe__indicator--active)) {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.changeLive {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
@ -25,7 +25,6 @@ const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||||
showPicker.value = false
|
||||
}
|
||||
const onBirthdayConfirm = (value) => {
|
||||
console.log(value)
|
||||
birthdayDate.value = value.selectedValues
|
||||
birthday.value = value.selectedValues.join('-')
|
||||
showBirthdayPicker.value = false
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { setupHttp } from '~/api/http'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
setupHttp()
|
||||
})
|
107
app/service/index.js
Normal file
107
app/service/index.js
Normal file
@ -0,0 +1,107 @@
|
||||
// 导入必要的模块和函数
|
||||
import { useRouter } from "vue-router"; // 用于路由导航
|
||||
import Request from "@/service/request/index.js"; // 自定义请求类
|
||||
import { message } from '@/components/x-message/useMessage.js';
|
||||
import {authStore} from "~/stores/auth/index.js"; // 消息提示组件
|
||||
|
||||
// 初始化路由
|
||||
const router = useRouter();
|
||||
let isRefreshing = false; // 标记是否正在刷新token
|
||||
let refreshSubscribers = []; // 存储刷新token的回调函数
|
||||
const config = useRuntimeConfig(); // 获取运行时配置
|
||||
|
||||
// 创建请求实例
|
||||
const request = new Request({
|
||||
baseURL: config.public.NUXT_PUBLIC_API_BASE, // 基础URL
|
||||
timeout: 1000 * 60 * 5, // 超时时间设置为5分钟
|
||||
showMsg: true, // 是否显示消息提示
|
||||
interceptors: {
|
||||
// 请求拦截器
|
||||
requestInterceptors: (config) => {
|
||||
// 根据是否是表单数据设置Content-Type
|
||||
const contentType = config.isFormData ? 'multipart/form-data' : config.contentType || 'application/json';
|
||||
config.headers = {
|
||||
"Content-Type": contentType,
|
||||
"Authorization": localStorage.getItem('token') || '' // 从本地存储获取token
|
||||
};
|
||||
return config; // 返回修改后的配置
|
||||
},
|
||||
// 响应拦截器
|
||||
responseInterceptors: async (response) => {
|
||||
// 如果需要显示消息且状态为1,显示警告消息
|
||||
if (response.config.showMsg && response.data.status === 1) {
|
||||
message.warning(response.data.msg);
|
||||
}
|
||||
// 如果响应码为401,尝试刷新token
|
||||
if (response.data.code === 401) {
|
||||
return getRefreshToken(response);
|
||||
}
|
||||
// 如果响应状态为200, 201, 204,直接返回响应
|
||||
if ([200, 201, 204].includes(response.status)) {
|
||||
return response.config.responseType === "blob" ? response : response;
|
||||
} else {
|
||||
// 处理其他错误情况
|
||||
const errorMsg = response.data.msg || 'An error occurred.';
|
||||
message.error(errorMsg);
|
||||
return Promise.reject(new Error(errorMsg));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 刷新token的函数
|
||||
async function getRefreshToken(response) {
|
||||
const {token,RefreshToken,userInfo}= authStore()
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true; // 标记正在刷新
|
||||
const refreshToken = RefreshToken.value; // 获取刷新token
|
||||
if (refreshToken) {
|
||||
try {
|
||||
// 发送请求刷新token
|
||||
const res = await request.post("/user/refresh/token", { refreshToken });
|
||||
if (res.code === 200) {
|
||||
// 更新本地存储的token和用户信息
|
||||
token.value = res.data.Token;
|
||||
userInfo.value=res.data.AccountInfo
|
||||
RefreshToken.value = res.data.RefreshToken;
|
||||
response.config.headers["Authorization"] = res.data.Token; // 更新请求头的token
|
||||
return request.request(response.config); // 重新发送原始请求
|
||||
} else {
|
||||
handleAuthError(res); // 处理认证错误
|
||||
}
|
||||
} catch (error) {
|
||||
throw error; // 抛出错误
|
||||
} finally {
|
||||
isRefreshing = false; // 重置刷新标记
|
||||
refreshSubscribers.forEach(callback => callback()); // 执行所有等待的回调
|
||||
refreshSubscribers = []; // 清空回调列表
|
||||
}
|
||||
} else {
|
||||
handleAuthError(); // 没有刷新token时处理错误
|
||||
}
|
||||
} else {
|
||||
// 如果正在刷新,返回一个Promise,等待刷新完成后重新发送请求
|
||||
return new Promise(resolve => {
|
||||
refreshSubscribers.push(() => resolve(request.request(response.config)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理认证错误的函数
|
||||
function handleAuthError(res = {}) {
|
||||
router.push("/login"); // 跳转到登录页面
|
||||
const errorMsg = res.message || res.msg || "No refresh token available."; // 错误消息
|
||||
message.error(errorMsg); // 显示错误消息
|
||||
throw new Error(errorMsg); // 抛出错误
|
||||
}
|
||||
|
||||
// 发送请求的函数
|
||||
const fontRequest = (config) => {
|
||||
// 如果是GET请求,将data作为params
|
||||
if (["get", "GET"].includes(config.method)) {
|
||||
config.params = config.data;
|
||||
}
|
||||
return request.request(config); // 发送请求
|
||||
};
|
||||
|
||||
export default fontRequest; // 导出请求函数
|
84
app/service/request/index.js
Normal file
84
app/service/request/index.js
Normal file
@ -0,0 +1,84 @@
|
||||
import axios from 'axios'; // 导入axios库
|
||||
|
||||
// 自定义请求类
|
||||
class Request {
|
||||
constructor(config) {
|
||||
this.instance = axios.create(config); // 创建axios实例
|
||||
this.abortControllerMap = new Map(); // 存储请求的AbortController
|
||||
this.interceptorsObj = config.interceptors; // 存储拦截器对象
|
||||
|
||||
// 默认请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(res) => {
|
||||
const controller = new AbortController(); // 创建新的AbortController
|
||||
const url = res.url || ''; // 获取请求的URL
|
||||
res.signal = controller.signal; // 将信号绑定到请求
|
||||
this.abortControllerMap.set(url, controller); // 存储控制器
|
||||
return res; // 返回请求配置
|
||||
},
|
||||
(err) => Promise.reject(err) // 处理请求错误
|
||||
);
|
||||
|
||||
// 自定义请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
this.interceptorsObj?.requestInterceptors,
|
||||
this.interceptorsObj?.requestInterceptorsCatch
|
||||
);
|
||||
|
||||
// 自定义响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
this.interceptorsObj?.responseInterceptors,
|
||||
this.interceptorsObj?.responseInterceptorsCatch
|
||||
);
|
||||
|
||||
// 默认响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(res) => {
|
||||
const url = res.config.url || ''; // 获取响应的URL
|
||||
this.abortControllerMap.delete(url); // 删除已完成请求的控制器
|
||||
return res.data; // 返回响应数据
|
||||
},
|
||||
(err) => Promise.reject(err) // 处理响应错误
|
||||
);
|
||||
}
|
||||
|
||||
// 发送请求的方法
|
||||
request(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 如果有自定义请求拦截器,先执行
|
||||
if (config.interceptors?.requestInterceptors) {
|
||||
config = config.interceptors.requestInterceptors(config);
|
||||
}
|
||||
// 发送请求
|
||||
this.instance
|
||||
.request(config)
|
||||
.then((res) => {
|
||||
// 如果有自定义响应拦截器,先执行
|
||||
if (config.interceptors?.responseInterceptors) {
|
||||
res = config.interceptors.responseInterceptors(res);
|
||||
}
|
||||
resolve(res); // 解析响应
|
||||
})
|
||||
.catch(reject); // 捕获错误
|
||||
});
|
||||
}
|
||||
|
||||
// 取消所有请求的方法
|
||||
cancelAllRequest() {
|
||||
for (const [, controller] of this.abortControllerMap) {
|
||||
controller.abort(); // 取消请求
|
||||
}
|
||||
this.abortControllerMap.clear(); // 清空控制器映射
|
||||
}
|
||||
|
||||
// 取消特定请求的方法
|
||||
cancelRequest(url) {
|
||||
const urlList = Array.isArray(url) ? url : [url]; // 确保url是数组
|
||||
for (const _url of urlList) {
|
||||
this.abortControllerMap.get(_url)?.abort(); // 取消请求
|
||||
this.abortControllerMap.delete(_url); // 删除控制器
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Request; // 导出自定义请求类
|
BIN
app/static/images/zu5554@2x.png
Normal file
BIN
app/static/images/zu5554@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
app/static/images/zu5561@2x.png
Normal file
BIN
app/static/images/zu5561@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
11
app/stores/auth/index.js
Normal file
11
app/stores/auth/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { createGlobalState,useLocalStorage } from '@vueuse/core'
|
||||
export const authStore = createGlobalState(() => {
|
||||
const token=useLocalStorage('token','')
|
||||
const RefreshToken=useLocalStorage('RefreshToken','')
|
||||
const userInfo=useLocalStorage('userInfo','')
|
||||
return{
|
||||
userInfo,
|
||||
RefreshToken,
|
||||
token
|
||||
}
|
||||
})
|
@ -3,12 +3,14 @@ import {ref} from "vue";
|
||||
export const liveStore = createGlobalState(() => {
|
||||
const quoteStatus = ref(false)
|
||||
const show = ref(false)
|
||||
const show1=ref(false)
|
||||
const playerId=ref('J_prismPlayer')
|
||||
const changeStatus = () => {
|
||||
console.log('changeStatus1231')
|
||||
quoteStatus.value = !quoteStatus.value
|
||||
console.log('quoteStatus.value',quoteStatus.value)
|
||||
}
|
||||
return{
|
||||
show1,
|
||||
playerId,
|
||||
show,
|
||||
quoteStatus,
|
||||
changeStatus
|
||||
|
@ -1,6 +1,16 @@
|
||||
import dotenv from 'dotenv'
|
||||
import process from 'node:process'
|
||||
import preload from './app/utils/preload'
|
||||
import { currentLocales } from './i18n/i18n'
|
||||
const envFile = process.env.ENV_FILE || '.env.test'
|
||||
dotenv.config({ path: `./env/${envFile}` })
|
||||
const publicConfig = Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith('NUXT_PUBLIC_'))
|
||||
.reduce((config, [key, value]) => {
|
||||
config[key] = value
|
||||
return config
|
||||
}, {})
|
||||
|
||||
export default defineNuxtConfig({
|
||||
|
||||
hooks: {
|
||||
@ -23,9 +33,10 @@ export default defineNuxtConfig({
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: process.env.NUXT_PUBLIC_API_BASE,
|
||||
},
|
||||
// 私有配置,只有在服务端可用
|
||||
apiSecret: process.env.NUXT_API_SECRET || 'default_secret',
|
||||
// 公共配置,客户端和服务端都可用
|
||||
public: publicConfig,
|
||||
},
|
||||
|
||||
css: [
|
||||
|
14
package.json
14
package.json
@ -5,21 +5,24 @@
|
||||
"packageManager": "pnpm@9.15.1",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev --mode test",
|
||||
"dev:prod": "nuxt dev --mode prod",
|
||||
"build:test": "nuxt build --mode test",
|
||||
"build:prod": "nuxt build --mode prod",
|
||||
"dev": "cross-env ENV_FILE=.env.test nuxt dev",
|
||||
"dev:prod": "NODE_ENV=production ENV_FILE=.env.prod nuxt dev",
|
||||
"build:test": "cross-env ENV_FILE=.env.test nuxt build",
|
||||
"build:prod": "cross-env ENV_FILE=.env.prod nuxt build",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"release": "bumpp --commit --push --tag"
|
||||
"release": "bumpp --commit --push --tag",
|
||||
"start": "cross-env ENV_FILE=.env.prod nuxt start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/i18n": "^9.1.1",
|
||||
"@vueuse/core": "^12.4.0",
|
||||
"aliyun-aliplayer": "^2.28.5",
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
"nuxt": "^3.15.0",
|
||||
"pinyin": "4.0.0-alpha.2",
|
||||
"segmentit": "^2.0.3",
|
||||
@ -32,6 +35,7 @@
|
||||
"@unocss/preset-rem-to-px": "0.65.2",
|
||||
"@vant/nuxt": "^1.0.6",
|
||||
"bumpp": "^9.9.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"postcss-mobile-forever": "^4.3.1",
|
||||
"sass": "^1.83.1",
|
||||
"sass-loader": "^16.0.4",
|
||||
|
@ -23,6 +23,12 @@ importers:
|
||||
aliyun-aliplayer:
|
||||
specifier: ^2.28.5
|
||||
version: 2.28.5
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.4.7
|
||||
nuxt:
|
||||
specifier: ^3.15.0
|
||||
version: 3.15.0(@parcel/watcher@2.5.0)(@types/node@22.10.2)(db0@0.2.1)(eslint@9.17.0(jiti@2.4.2))(ioredis@5.4.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.29.1)(sass@1.83.1)(terser@5.37.0)(tsx@4.19.2)(typescript@5.7.2)(vite@6.0.5(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(yaml@2.6.1)
|
||||
@ -54,6 +60,9 @@ importers:
|
||||
bumpp:
|
||||
specifier: ^9.9.2
|
||||
version: 9.9.2(magicast@0.3.5)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
postcss-mobile-forever:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1(postcss@8.4.49)
|
||||
@ -1657,6 +1666,9 @@ packages:
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
autoprefixer@10.4.20:
|
||||
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@ -1664,6 +1676,9 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.7.9:
|
||||
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
|
||||
|
||||
b4a@1.6.7:
|
||||
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
|
||||
|
||||
@ -1816,6 +1831,10 @@ packages:
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@1.1.1:
|
||||
resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==}
|
||||
engines: {node: '>= 0.6.x'}
|
||||
@ -1889,6 +1908,11 @@ packages:
|
||||
resolution: {integrity: sha512-NKgHbWkSZXJUcaBHSsyzC8eegD6bBd4O0oCI6XMIJ+y4Bq3v4w7sY3wfWoKPuVlq9pQHRB6od0lmKpIqi8TlKA==}
|
||||
hasBin: true
|
||||
|
||||
cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -2017,6 +2041,10 @@ packages:
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
denque@2.1.0:
|
||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
@ -2293,10 +2321,23 @@ packages:
|
||||
flatted@3.3.2:
|
||||
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.0:
|
||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.1:
|
||||
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@ -3326,6 +3367,9 @@ packages:
|
||||
protocols@2.0.1:
|
||||
resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -5994,6 +6038,8 @@ snapshots:
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autoprefixer@10.4.20(postcss@8.4.49):
|
||||
dependencies:
|
||||
browserslist: 4.24.3
|
||||
@ -6004,6 +6050,14 @@ snapshots:
|
||||
postcss: 8.4.49
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
axios@1.7.9:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.1
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
b4a@1.6.7: {}
|
||||
|
||||
babel-plugin-macros@2.8.0:
|
||||
@ -6176,6 +6230,10 @@ snapshots:
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@1.1.1:
|
||||
dependencies:
|
||||
keypress: 0.1.0
|
||||
@ -6235,6 +6293,10 @@ snapshots:
|
||||
|
||||
cronstrue@2.52.0: {}
|
||||
|
||||
cross-env@7.0.3:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -6357,6 +6419,8 @@ snapshots:
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
denque@2.1.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
@ -6704,11 +6768,19 @@ snapshots:
|
||||
|
||||
flatted@3.3.2: {}
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
foreground-child@3.3.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.1:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
fresh@0.5.2: {}
|
||||
@ -7855,6 +7927,8 @@ snapshots:
|
||||
|
||||
protocols@2.0.1: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
Loading…
Reference in New Issue
Block a user