Commit 3153acf2 authored by zhangsan's avatar zhangsan

1

parent c6d52004
......@@ -36,7 +36,6 @@ export default (options: Options): Plugin => {
if (options.enabled) {
fs.writeFileSync(`${outDir}/__toc__.html`, getHTML())
}
},
}
}
......
......@@ -40,12 +40,11 @@
},
"dependencies": {
"@vueuse/core": "^10.9.0",
"axios": "^1.6.0",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"mitt": "^3.0.1",
"vant": "^4.8.5",
"vue": "^3.3.0",
"vue-router": "^4.2.0"
"vue": "^3.4.21"
},
"devDependencies": {
"@antfu/eslint-config": "^2.8.2",
......@@ -100,6 +99,5 @@
"src/**": [
"ls-lint"
]
},
"__npminstall_done": false
}
}
......@@ -4,19 +4,11 @@
"title": "404-页面未找到"
},
{
"path": "home",
"title": "首页"
},
{
"path": "login",
"title": "登录"
"path": "demo",
"title": "测试页面"
},
{
"path": "my",
"title": "我的"
},
{
"path": "shop",
"title": "商城"
"path": "index",
"title": "首页"
}
]
This diff is collapsed.
import request from '@/utils/request'
import type { PagingRequest, PagingResult } from '@/types'
import type { ArticleType } from './types'
import type { AxiosRequestConfig } from 'axios'
export function getArticleList(params?: PagingRequest & ArticleType.ListParams) {
return request.get<PagingResult<ArticleType.ListItem[]>>('https://api.hsmy.fun/mock/list', params)
}
export function getArticleDetail(id: number) {
return request.get<ArticleType.Detail>(`https://api.hsmy.fun/mock/detail/${id}`)
export function getArticleList(params: PagingRequest & ArticleType.ListParams, config?: AxiosRequestConfig) {
return request.get<PagingResult<ArticleType.ListItem[]>>('https://api.hsmy.fun/mock/list', params, config)
}
export namespace ArticleType {
// 列表项
export interface ListItem {
id: number
title: string
content: string
createTime: string
updateTime: string
}
// 列表查询参数
export interface ListParams {
keyword?: string
startTime?: string
endTime?: string
}
// 详情
export interface Detail extends ListItem {
// 可以添加列表项中没有的其他字段
author: string
views: number
}
}
\ No newline at end of file
<template>
<div v-if="visible" class="loading-mask">
<div class="loading-spinner">
<div class="spinner"></div>
<div class="loading-text">加载中...</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
const show = () => {
visible.value = true
}
const hide = () => {
visible.value = false
}
defineExpose({
show,
hide
})
</script>
<style scoped>
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 40px;
height: 40px;
margin: 0 auto;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
margin-top: 10px;
color: #666;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
\ No newline at end of file
<template>
<div class="tab-bar">
<span @click="handleClick('/home.html')">
<i class="iconfont icon-home"></i>
<span>首页</span>
</span>
<span @click="handleClick('/shop.html')">
<i class="iconfont icon-shop"></i>
<span>商城</span>
</span>
<span @click="handleClick('/my.html')">
<i class="iconfont icon-user"></i>
<span>我的</span>
</span>
</div>
</template>
<script setup>
function gengerateUrl(name) {
const pathname = location.pathname.substring(0, location.pathname.lastIndexOf('/'))
const url = `${location.origin}${pathname}${name}`
return url
}
const handleClick = (path) => {
window.location.href = gengerateUrl(path)
}
// // 带参数跳转
// router.push({
// path: '/home',
// query: { id: 1 }
// })
// // 替换当前页面
// router.replace('/home')
// // 带参数对象跳转
// router.push({
// path: '/shop',
// query: {
// category: 'electronics',
// sort: 'price'
// }
// })
</script>
<style scoped>
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background: #fff;
display: flex;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
text-decoration: none;
}
.tab-item.router-link-active {
color: #3498db;
}
.tab-item i {
font-size: 20px;
}
.tab-item span {
font-size: 12px;
margin-top: 2px;
}
</style>
\ No newline at end of file
export const useLoading = () => {
const showLoading = () => {
const loadingInstance = document.querySelector('#global-loading')
if (loadingInstance) {
// @ts-ignore
loadingInstance.show()
}
}
const hideLoading = () => {
const loadingInstance = document.querySelector('#global-loading')
if (loadingInstance) {
// @ts-ignore
loadingInstance.hide()
}
}
return {
showLoading,
hideLoading
}
}
\ No newline at end of file
<template>
<div class="basic-layout">
<div class="layout-content">
<slot></slot>
</div>
<TabBar />
<Loading id="global-loading" />
</div>
</template>
<script setup lang="ts">
import TabBar from '@/components/TabBar.vue'
import Loading from '@/components/Loading.vue'
</script>
<style lang="scss" scoped>
.basic-layout {
min-height: 100vh;
background-color: #f5f6f6;
padding-bottom: 50px;
}
.layout-content {
padding: 15px;
}
</style>
\ No newline at end of file
<template>
<div class="blank-layout">
<slot></slot>
<Loading id="global-loading" />
</div>
</template>
<script setup lang="ts">
import Loading from '@/components/Loading.vue'
</script>
<style lang="scss" scoped>
.blank-layout {
min-height: 100vh;
background-color: #f5f6f6;
}
</style>
\ No newline at end of file
......@@ -8,12 +8,13 @@
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>登录</title>
<link rel="icon" href="./favicon.ico">
<title>测试页面</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./login/main.ts"></script>
<script type="module" src="./demo/main.ts"></script>
</body>
</html>
\ No newline at end of file
</html>
<template>
<div class="container bg-slate-100 text-xs overflow-hidden">
<section class="bg-white p-4 m-4 rounded-1 ">
<h1 text-center text-xl color-primary>
Ares Mobile
</h1>
<p text-center text-gray>
基于 Vant4 和 Vue3 的 H5 移动端模板
</p>
</section>
<section class="bg-white p-4 m-4 rounded-1 ">
<h3 mt-2 mb-4>
✨ 特性
</h3>
<p>1. <a href="https://github.com/zhangsanplus/ares-mobile" class="underline">单页面SPA</a><a href="https://github.com/zhangsanplus/ares-mobile/tree/mpa" class="underline">多页面MPA</a>模板适用于不同的需求</p>
<p>2. 移动端组件库 - <strong>Vant</strong></p>
<p>3. 原子化 CSS - <strong>UnoCSS</strong></p>
<p>4. 应用程序级 JavaScript 的语言 - <strong>TypeScript</strong></p>
<p>
5. 实用的字体图标库 <i-carbon-worship-muslim text-primary />、自定义字体图标 <i-custom-github />
</p>
<p>6. 移动端调试插件 - <strong>eruda</strong></p>
<p>7. 移动端适配插件 - <strong>postcss-px-to-viewport</strong></p>
<p>8. 前端规范配置 - <strong>eslint</strong><strong>stylelint</strong><strong>commitlint</strong></p>
</section>
<section class="bg-white p-4 m-4 rounded-1">
<h3 mt-2 mb-4>
🔥 组件封装
</h3>
<div flex gap-2>
<button
class="btn"
@click="visible = true"
>
自定义组件
</button>
<button
class="btn"
@click="onClick"
>
函数调用
</button>
<a
class="btn"
href="./index.html"
target="_self"
>
返回首页
</a>
</div>
</section>
<section class="bg-white p-2 m-4 rounded-1">
<van-tabs v-model:active="active">
<van-tab title="幸运抽奖">
<luck-draw />
</van-tab>
<van-tab title="无限加载">
<scroll-list />
</van-tab>
</van-tabs>
</section>
<x-modal
:visible="visible"
title="我是标题"
content="我是内容"
@ok="visible = false"
@close="visible = false"
/>
</div>
</template>
<script setup lang="ts">
import Modal from '@/components/x-modal'
import XModal from '@/components/x-modal/x-modal.vue'
import LuckDraw from './components/luck-draw.vue'
import ScrollList from './components/scroll-list.vue'
const visible = ref(false)
const active = ref(0)
function onClick() {
Modal.open({
title: 'Ares Moblie',
content: '基于 Vue3 和 Vant4 的H5多页面模板,让开发变得更简单。',
})
}
</script>
<template>
<div class="luckdraw-content">
<!-- 滚动号码 -->
<ul class="luckdraw-scroll">
<li v-for="(item, i) in list" :key="i" :class="{ anim: animate && i === 0 }">
<span class="lkq-name">{{ item.phone }}</span>
</li>
</ul>
<!-- 抽奖弹窗 -->
<div class="turntable">
<!-- 跑马灯 -->
<svg class="bulb" viewBox="-6 -6 316 316" fill="currentColor" fill-rule="evenodd">
<g class="bulb-1">
<circle cx="10" cy="10" r="4" />
<circle cx="78" cy="4" r="4" />
<circle cx="152" cy="4" r="4" />
<circle cx="226" cy="4" r="4" />
<circle cx="294" cy="10" r="4" />
<circle cx="4" cy="89" r="4" />
<circle cx="4" cy="173" r="4" />
<circle cx="4" cy="258" r="4" />
<circle cx="41" cy="300" r="4" />
<circle cx="115" cy="300" r="4" />
<circle cx="189" cy="300" r="4" />
<circle cx="263" cy="300" r="4" />
<circle cx="300" cy="258" r="4" />
<circle cx="300" cy="173" r="4" />
<circle cx="300" cy="89" r="4" />
</g>
<g class="bulb-2 ">
<circle cx="41" cy="4" r="4" />
<circle cx="115" cy="4" r="4" />
<circle cx="189" cy="4" r="4" />
<circle cx="263" cy="4" r="4" />
<circle cx="4" cy="46" r="4" />
<circle cx="4" cy="131" r="4" />
<circle cx="4" cy="215" r="4" />
<circle cx="10" cy="294" r="4" />
<circle cx="294" cy="294" r="4" />
<circle cx="300" cy="215" r="4" />
<circle cx="300" cy="131" r="4" />
<circle cx="300" cy="46" r="4" />
<circle cx="78" cy="300" r="4" />
<circle cx="152" cy="300" r="4" />
<circle cx="226" cy="300" r="4" />
</g>
</svg>
<!-- 奖品 -->
<ul class="awards-list">
<li
v-for="(item, key) in awardList" :key="item.id" class="awards-item"
:class="{ 'awards-item-draw': key === 4, 'run-item': item.runId === current }"
>
<div v-if="key === 4" class="draw-btn" @click="handleStart">
<span class="draw-btn-text">点击抽奖</span>
</div>
<div v-else>
{{ item.name }}
</div>
</li>
</ul>
</div>
</div>
</template>
<script lang="ts" setup>
import { showToast } from 'vant'
// https://github.com/vincentzyc/vue3-demo/blob/main/src/views/LuckDraw.vue
interface AwardTypes {
id: number
runId: number
name: string
}
const initSpeed = 200 // 初始速度
const diff = 20 // 速度变化的值,值越大,变化地越快
const minRotateTime = 2.5 // 抽奖动画最少转动时间
const rotateTime = 5 // 抽奖动画转动时间
let speed = 0 // 变化速度
let time = 0 // 记录开始抽奖的时间
let isRuningLucky = false // 是否正在抽奖
let award: AwardTypes // 抽中的奖品
const animate = ref(false) // 中奖名单滚动动画控制
const current = ref(0) // 当前转动的位置
const list = reactive([
{ phone: '186****2336抽中0元话费' },
{ phone: '166****2336抽中1元话费' },
{ phone: '156****2336抽中2元话费' },
])
const awards = reactive([
{ id: 1, runId: 0, name: '潘多拉音箱' },
{ id: 2, runId: 1, name: '小酷M1耳机' },
{ id: 3, runId: 2, name: '酷狗VIP会员' },
{ id: 4, runId: 7, name: '8元话费' },
{ id: 5, runId: 3, name: '12元话费' },
{ id: 6, runId: 6, name: '谢谢参与1' },
{ id: 7, runId: 5, name: '4元话费' },
{ id: 8, runId: 4, name: '谢谢参与2' },
])
// 添加"开始抽奖"按钮到奖品列表中
const awardList = computed(() => {
const newArr = JSON.parse(JSON.stringify(awards))
newArr.splice(4, 0, { name: 'drawBtn' })
return newArr
})
// 中奖名单滚动
function scroll() {
animate.value = true
setTimeout(() => {
list.push(list[0])
list.shift()
animate.value = false
}, 500)
}
function move() {
const timer = setTimeout(() => {
current.value++
if (current.value > 7) current.value = 0
// 若抽中的奖品id存在,并且转动时间大于2.5秒后,则开始减速转动
if (award?.id && (Date.now() - time) / 1000 > minRotateTime) {
console.log('奖品出来了')
speed += diff // 转动减速
// 若转动时间超过5秒,等到当前格子是对应奖品id数组,则停下来
if (
(Date.now() - time) / 1000 > rotateTime
&& award.runId === current.value
) {
clearTimeout(timer)
setTimeout(() => {
isRuningLucky = false
// 这里写停下来要执行的操作(弹出奖品框之类的)
const getAward = awards.find(v => v.id === award.id)
if (getAward) {
showToast(`您抽中的奖品是${getAward.name},奖品id是${getAward.id}`)
}
}, 400)
return
}
// 若抽中的奖品不存在,则加速转动
} else {
if (speed >= 50) speed -= diff // 转动加速
}
move()
}, speed)
}
// 请求接口,模拟一个抽奖数据(假设请求时间为2s)
function drawAward() {
setTimeout(() => {
const awardId = Math.ceil(Math.random() * 8) // 随机奖品
const getAward = awards.find(v => v.id === awardId)
if (getAward) award = getAward
console.log('返回的抽奖结果是', award)
}, 2000)
move()
}
// 开始抽奖
function handleStart() {
if (isRuningLucky) {
console.log('正在抽奖中...')
return
}
if (Number.isNaN(Number(initSpeed))) {
return false
}
speed = initSpeed
isRuningLucky = true
time = Date.now()
drawAward()
console.log('开始抽奖')
}
onMounted(() => {
setInterval(scroll, 2000)
})
</script>
<style lang="scss" scoped>
.luckdraw-scroll {
position: relative;
height: 40px;
margin: 30px auto;
padding: 0;
overflow: hidden;
font-size: 16px;
line-height: 40px;
text-align: center;
list-style: none;
background: #bcbcc1;
border-radius: 20px;
.lkq-name {
margin: 0 auto;
color: #fff;
}
.anim {
margin-top: -40px;
transition: all 0.5s linear;
}
}
.luckdraw-content {
// 转盘
.turntable {
position: relative;
width: 320px;
height: 320px;
margin: 30px auto;
padding: 20px;
background: #fed479;
border-radius: 20px;
.awards-list {
display: grid;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 3%;
height: 100%;
}
.awards-item {
display: flex;
align-items: center;
justify-content: center;
color: #d65c23;
font-size: 12px;
background-color: #fff;
border: 3px solid #fff;
border-color: #fff;
border-radius: 25%;
transition: border-color 0.1s;
}
.run-item {
border-color: #ff7051;
}
.awards-item-draw {
overflow: hidden;
background: #963434;
border: none;
}
.draw-btn {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: #fff;
background: #ff7051;
border: 3px solid #ff7051;
border-radius: 25%;
animation: draw-btn-jump 0.5s infinite;
}
}
}
.bulb {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.bulb .bulb-1 {
animation: bulb-animation 0.5s -0.25s infinite;
}
.bulb .bulb-2 {
animation: bulb-animation 0.5s infinite;
}
@keyframes bulb-animation {
0% {
color: #FFF;
}
50% {
color: #FFE37F;
}
100% {
color: #FFF;
}
}
@keyframes draw-btn-jump {
0% {
top: -6px;
}
50% {
top: 0;
}
100% {
top: -6px;
}
}
</style>
<template>
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item.id" :title="item.title" />
</van-list>
</template>
<script setup lang="ts">
import { getArticleList } from '@/api/article'
const list = ref<ArticleType.ListItem[]>([])
const loading = ref(false)
const finished = ref(false)
const form = reactive({
title: '',
pageNum: 0,
pageSize: 10,
})
function onLoad() {
++form.pageNum
getList()
}
async function getList() {
loading.value = true
try {
const { data } = await getArticleList(form)
list.value = form.pageNum === 1 ? data.list : [...list.value, ...data.list]
finished.value = list.value.length >= data.count
} catch (error) {
finished.value = true
console.error(error)
}
loading.value = false
}
</script>
......@@ -3,4 +3,4 @@ import '@/styles/index.scss'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
\ No newline at end of file
createApp(App).mount('#app')
<template>
<BasicLayout>
<div class="content">
首页内容
</div>
</BasicLayout>
</template>
<script setup lang="ts">
import BasicLayout from '@/layouts/BasicLayout.vue'
</script>
<style lang="scss">
body {
background-color: #f5f6f6;
margin: 0;
padding: 0;
}
.container {
padding-bottom: 50px;
}
.content {
padding: 15px;
}
</style>
\ No newline at end of file
......@@ -8,12 +8,13 @@
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<link rel="icon" href="./favicon.ico">
<title>首页</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./home/main.ts"></script>
<script type="module" src="./index/main.ts"></script>
</body>
</html>
\ No newline at end of file
</html>
<template>
<div class="container">
<img src="@/assets/logo.png">
<h2>Ares Mobile</h2>
<p>基于 Vant4 和 Vue3 的 H5 多页面前端模板</p>
<div class="nav">
<van-button type="primary" url="https://github.com/zhangsanplus/ares-mobile/tree/mpa">
github
</van-button>
<van-button type="primary" url="./__toc__.html">
页面目录
</van-button>
</div>
<p style="color: #aaa;">
PC端建议打开 <strong style="color: #a0a0a0;">F12</strong> 在移动端模式下访问
</p>
</div>
</template>
<script setup lang="ts">
console.log('drop console test')
</script>
<style lang="scss">
.container {
padding-top: 15vh;
text-align: center;
img {
width: 80px;
}
.nav {
margin: 30px auto;
}
.van-button + .van-button {
margin-left: 10px;
}
}
</style>
......@@ -3,4 +3,4 @@ import '@/styles/index.scss'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
\ No newline at end of file
createApp(App).mount('#app')
<template>
<BlankLayout>
<div class="login-container">
登录内容
</div>
</BlankLayout>
</template>
<script setup lang="ts">
import BlankLayout from '@/layouts/BlankLayout.vue'
</script>
<style lang="scss">
body {
background-color: #f5f6f6;
}
</style>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>我的</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./my/main.ts"></script>
</body>
</html>
\ No newline at end of file
<template>
<BasicLayout>
<div class="content">
我的内容
</div>
</BasicLayout>
</template>
<script setup lang="ts">
import BasicLayout from '@/layouts/BasicLayout.vue'
</script>
<style lang="scss">
body {
background-color: #f5f6f6;
margin: 0;
padding: 0;
}
.container {
padding-bottom: 50px;
}
.content {
padding: 15px;
}
</style>
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>商城</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./shop/main.ts"></script>
</body>
</html>
\ No newline at end of file
<template>
<BasicLayout>
<div class="content">
商城内容
</div>
</BasicLayout>
</template>
<script setup lang="ts">
import BasicLayout from '@/layouts/BasicLayout.vue'
</script>
<style lang="scss">
body {
background-color: #f5f6f6;
margin: 0;
padding: 0;
}
.container {
padding-bottom: 50px;
}
.content {
padding: 15px;
}
</style>
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
\ No newline at end of file
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
redirect: '/home.html'
},
{
path: '/home.html',
component: () => import('../pages/home/App.vue')
},
{
path: '/shop.html',
component: () => import('../pages/shop/App.vue')
},
{
path: '/my.html',
component: () => import('../pages/my/App.vue')
},
{
path: '/login',
component: () => import('../pages/login/App.vue')
}
]
})
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path !== '/login' && !token) {
next('/login')
} else {
const loadingInstance = document.querySelector('#global-loading')
if (loadingInstance) {
// @ts-ignore
loadingInstance.show()
}
next()
}
})
router.afterEach(() => {
setTimeout(() => {
const loadingInstance = document.querySelector('#global-loading')
if (loadingInstance) {
// @ts-ignore
loadingInstance.hide()
}
}, 500) // 添加一个小延迟,确保页面内容加载完成
})
export default router
\ No newline at end of file
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
}
.slide-leave-to {
transform: translateX(-100%);
}
\ No newline at end of file
// 分页请求参数
export interface PagingRequest {
page: number
pageSize: number
}
// 分页响应结果
export interface PagingResult<T> {
code: number
msg: string
data: {
list: T
total: number
page: number
pageSize: number
}
}
// 通用响应结果
export interface ApiResult<T = any> {
code: number
msg: string
data: T
}
\ No newline at end of file
......@@ -2,21 +2,28 @@ import axios from 'axios'
import { showToast } from 'vant'
import { ResponseEnum } from '@/enums/http'
import { getToken } from './auth'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import { useLoading } from '@/hooks/useLoading'
import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'
const { showLoading, hideLoading } = useLoading()
const URL = import.meta.env.VITE_API_BASE_URL
const config = {
// 默认地址
baseURL: URL,
// 设置超时时间
timeout: 10000,
// 跨域时候允许携带凭证
// withCredentials: true,
}
class Request {
private instance: AxiosInstance
class RequestHttp {
service: AxiosInstance
public constructor(config: AxiosRequestConfig) {
this.service = axios.create(config)
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
showLoading()
/**
* 请求拦截器
*/
this.service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
......@@ -24,15 +31,16 @@ class Request {
return config
},
(error) => {
hideLoading()
return Promise.reject(error)
}
},
)
// 响应拦截器
this.instance.interceptors.response.use(
/**
* 响应拦截器
*/
this.service.interceptors.response.use(
// 2xx 时触发
(response) => {
hideLoading()
const res = response.data
// 响应数据为二进制流
if (res instanceof ArrayBuffer) return res
......@@ -42,37 +50,25 @@ class Request {
}
return res
},
// 非 2xx 时触发
(error) => {
hideLoading()
if (axios.isCancel(error)) {
console.error(error.message)
} else if (error?.message) {
showToast(error.message)
}
return Promise.reject(error)
}
},
)
}
// 修改 get 方法的参数
get<T>(url: string, params?: any): Promise<T> {
return this.instance.get(url, { params })
}
post<T>(url: string, data?: any): Promise<T> {
return this.instance.post(url, data)
}
put<T>(url: string, data?: any): Promise<T> {
return this.instance.put(url, data)
get<T = any>(url: string, params?: Record<string, any>, config: AxiosRequestConfig = {}): Promise<HttpResponse<T>> {
return this.service.get(url, { params, ...config })
}
delete<T>(url: string, params?: any): Promise<T> {
return this.instance.delete(url, { params })
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<HttpResponse<T>> {
return this.service.post(url, data, config)
}
}
export default new Request({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
export default new RequestHttp(config)
......@@ -7,10 +7,13 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Loading: typeof import('./../src/components/Loading.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TabBar: typeof import('./../src/components/TabBar.vue')['default']
ICarbonWorshipMuslim: typeof import('~icons/carbon/worship-muslim')['default']
ICustomGithub: typeof import('~icons/custom/github')['default']
VanButton: typeof import('vant/es')['Button']
VanCell: typeof import('vant/es')['Cell']
VanList: typeof import('vant/es')['List']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
XModal: typeof import('./../src/components/x-modal/x-modal.vue')['default']
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment