Compare commits

..

6 Commits

Author SHA1 Message Date
xingyy
89a3652003 refactor(api): 移除 HTTP 模块
移除了 HTTP模块的实现,包括:
- 删除了 http.ts 文件,其中包含 HTTP 客户端的配置和错误处理逻辑
- 删除了 plugins/http.ts 文件,用于在 Nuxt 中初始化 HTTP 客户端- 删除了 prose.ts 文件,其中包含获取散文数据的 API调用

此次更改清除了不再需要的 HTTP 模块相关代码,简化了代码库结构。
2025-01-16 11:51:10 +08:00
xingyy
7dfda1a3d3 feat(service): 添加自定义请求模块并优化认证逻辑
- 新增 request 目录,实现自定义请求类和拦截器
- 添加全局状态管理,用于存储认证信息
- 优化 token 刷新逻辑,提高请求安全性
- 移除不必要的 console.log 语句- 更新项目依赖,添加 axios库
2025-01-16 11:48:12 +08:00
xingyy
5315b0fc0c refactor(home): 优化首页布局和国际化配置
- 添加默认布局和国际化配置
- 调整全局样式和组件样式
- 优化代码结构,提高可维护性
2025-01-16 11:27:12 +08:00
xingyy
e86c8dbf3b refactor(app): 重构首页组件
- 优化了拍品列表的渲染逻辑,使用计算属性生成左右两列数据
- 移除了冗余代码和注释,
2025-01-16 11:23:46 +08:00
xingyy
9ca23ceca7 refactor(LiveRoom): 优化出价按钮组件并引入 PressableButton
- 引入 PressableButton 组件用于出价按钮
2025-01-16 11:12:52 +08:00
xingyy
38e0cfcdb6 feat(LiveRoom): 实现支付功能并优化直播页面
- 添加支付输入和支付结果组件
- 集成环境变量配置- 优化直播播放器配置- 调整对话框样式
2025-01-16 11:07:38 +08:00
21 changed files with 508 additions and 280 deletions

1
.env
View File

@ -1 +0,0 @@
NUXT_PUBLIC_API_BASE=https://easyapi.devv.zone

View File

@ -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
}

View File

@ -1,8 +0,0 @@
import { getHttp } from './http'
export async function getProse() {
const http = getHttp()
return await http('/api/prose', {
method: 'GET',
})
}

View File

@ -22,7 +22,6 @@ const addItem = () => {
d: 'RMB5,500',
e: ''
});
// DOM
nextTick(() => {
scrollToBottom();
});

View 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>

View 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>

View File

@ -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"

View File

@ -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>

View File

@ -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,17 +67,17 @@ 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>
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
<div class="absolute top-505px left-1/2 transform -translate-x-1/2 flex flex-col items-center">
<div class="text-16px text-#FFB25F font-600">
当前价RMB
@ -79,32 +87,29 @@ const goPay=()=>{
下口价RMB
<van-rolling-text class="my-rolling-text1" :start-num="0" :target-num="3500" direction="up"/>
</div>
<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>
<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>
</div>
</van-dialog>
<paymentInput v-model:show="show"/>
<div>
</div>
<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;

View File

@ -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,205 +59,101 @@ 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" >
<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"
>
<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 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">
<div class="w-full flex gap-[16px]">
<Column v-for="(column, colIndex) in columns" :key="colIndex" :items="column" @openShow="openShow" />
</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>
</div>
</van-list>
</van-pull-refresh>
</div>
</van-tab>
<van-tab title="拍卖说明">
<div class="px-16px pt-14px">
<div class="text-#575757 text-14px">
这里是后台富文本配置的说明啊即可打开三等奖撒度老师的湿答答是快乐的阿四大皆空
</van-list>
</van-pull-refresh>
</div>
<div></div>
</van-tab>
<van-tab title="拍卖说明">
<div class="px-16px pt-14px">
<div class="text-#575757 text-14px">
这里是后台富文本配置的说明啊即可打开三等奖撒度老师的湿答答是快乐的阿四大皆空
</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/>
</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>
</van-action-sheet>
</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;

View File

@ -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

View File

@ -1,5 +0,0 @@
import { setupHttp } from '~/api/http'
export default defineNuxtPlugin(() => {
setupHttp()
})

107
app/service/index.js Normal file
View 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; // 导出请求函数

View 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; // 导出自定义请求类

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

11
app/stores/auth/index.js Normal file
View 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
}
})

View File

@ -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

View File

@ -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: [

View File

@ -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",

View File

@ -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: {}