A request handler centralizes HTTP opertaions, managing authentication, error handling, and API interactions. Below is a refactored implementation using async/await for clarity.
import authStore from '@/store/auth'
import appConfig from '@/config/app'
import { fetchAuthToken } from '@/utils/authentication'
import { errorMessages } from '@/utils/errorMapping'
import { displayToast, showDialog, serializeQueryParams } from '@/utils/helpers'
const DEFAULT_TIMEOUT = 10000
const API_BASE_URL = appConfig.apiBaseUrl
const createRequest = async (requestConfig) => {
const requiresToken = !(requestConfig.headers?.skipToken)
const headers = requestConfig.headers || {}
const authToken = fetchAuthToken()
if (authToken && requiresToken) {
headers.Authorization = `Bearer ${authToken}`
}
let requestUrl = requestConfig.url
if (requestConfig.queryParams) {
const queryString = serializeQueryParams(requestConfig.queryParams)
requestUrl = `${requestUrl}?${queryString}`
}
const requestOptions = {
method: requestConfig.method || 'GET',
timeout: requestConfig.timeout || DEFAULT_TIMEOUT,
url: requestConfig.baseUrl || API_BASE_URL + requestUrl,
data: requestConfig.body,
header: headers,
dataType: 'json'
}
try {
const [requestError, response] = await uni.request(requestOptions)
if (requestError) {
displayToast('Server connection failed')
throw new Error('Server connection failed')
}
const statusCode = response.data?.code || response.statusCode || 200
const message = errorMessages[statusCode] || response.data?.message || errorMessages.default
if (statusCode === 401) {
const confirmResult = await showDialog('Session expired. Re-login or stay on this page?')
if (confirmResult.confirm) {
await authStore.dispatch('logout')
uni.reLaunch({ url: '/pages/auth/login' })
}
throw new Error('Invalid or expired session')
}
if (statusCode === 500 || statusCode !== 200) {
displayToast(message)
throw new Error(`Request failed with status: ${statusCode}`)
}
return response.data
} catch (networkError) {
let errorText = networkError.message
if (errorText === 'Network Error') {
errorText = 'Server connection failed'
} else if (errorText.includes('timeout')) {
errorText = 'Request timeout'
} else if (errorText.includes('Request failed with status code')) {
const errorCode = errorText.slice(-3)
errorText = `Server error ${errorCode}`
}
displayToast(errorText)
throw networkError
}
}
export default createRequest
A user interface component organizes system management features with a banner carousel and permission-based navigation grid.
<template>
<view class="page-container">
<carousel-component
class="banner-carousel"
:items="bannerItems"
:active-index="activeBannerIndex"
@change="handleBannerChange"
>
<swiper-item v-for="(banner, idx) in bannerItems" :key="idx">
<view class="banner-item" @tap="selectBanner(banner)">
<image :src="banner.imageUrl" mode="aspectFill" :draggable="false" />
</view>
</swiper-item>
</carousel-component>
<section-header title="System Administration" divider="line" />
<view class="navigation-grid">
<grid-layout :columns="4" :border="false" @select="navigateToSection">
<grid-cell :index="0" v-if="hasPermission(['system:user:list'])">
<view class="grid-cell-content">
<icon-component type="person-filled" size="30" />
<text class="cell-label">User Management</text>
</view>
</grid-cell>
<grid-cell :index="1" v-if="hasPermission(['system:role:list'])">
<view class="grid-cell-content">
<icon-component type="staff-filled" size="30" />
<text class="cell-label">Role Management</text>
</view>
</grid-cell>
<grid-cell :index="2" v-if="hasPermission(['system:menu:list'])">
<view class="grid-cell-content">
<icon-component type="color" size="30" />
<text class="cell-label">Menu Management</text>
</view>
</grid-cell>
<grid-cell :index="3" v-if="hasPermission(['system:dept:list'])">
<view class="grid-cell-content">
<icon-component type="settings-filled" size="30" />
<text class="cell-label">Department Management</text>
</view>
</grid-cell>
<grid-cell :index="4" v-if="hasPermission(['system:post:list'])">
<view class="grid-cell-content">
<icon-component type="heart-filled" size="30" />
<text class="cell-label">Position Management</text>
</view>
</grid-cell>
<grid-cell :index="5" v-if="hasPermission(['system:dict:list'])">
<view class="grid-cell-content">
<icon-component type="bars" size="30" />
<text class="cell-label">Dictionary Management</text>
</view>
</grid-cell>
<grid-cell :index="6" v-if="hasPermission(['system:config:list'])">
<view class="grid-cell-content">
<icon-component type="gear-filled" size="30" />
<text class="cell-label">Configuration</text>
</view>
</grid-cell>
<grid-cell :index="7" v-if="hasPermission(['monitor:logininfor:list'])">
<view class="grid-cell-content">
<icon-component type="chat-filled" size="30" />
<text class="cell-label">Login Logs</text>
</view>
</grid-cell>
<grid-cell :index="8" v-if="hasPermission(['monitor:operlog:list'])">
<view class="grid-cell-content">
<icon-component type="wallet-filled" size="30" />
<text class="cell-label">Operation Logs</text>
</view>
</grid-cell>
<grid-cell :index="9" v-if="hasPermission(['tool:upload:upload-demo'])">
<view class="grid-cell-content">
<icon-component type="wallet-filled" size="30" />
<text class="cell-label">Upload Demo</text>
</view>
</grid-cell>
<grid-cell :index="10" v-if="hasPermission(['system:beijingsubway:index'])">
<view class="grid-cell-content">
<icon-component type="wallet-filled" size="30" />
<text class="cell-label">Route Planner</text>
</view>
</grid-cell>
<grid-cell :index="11" v-if="hasPermission(['system:lines:index'])">
<view class="grid-cell-content">
<icon-component type="settings-filled" size="30" />
<text class="cell-label">Subway Lines</text>
</view>
</grid-cell>
<grid-cell :index="12" v-if="hasPermission(['system:stations:index'])">
<view class="grid-cell-content">
<icon-component type="heart-filled" size="30" />
<text class="cell-label">Subway Stations</text>
</view>
</grid-cell>
</grid-layout>
</view>
</view>
</template>
<script>
import { verifyPermission } from '@/utils/authorization'
export default {
data() {
return {
activeBannerIndex: 0,
bannerItems: [
{ imageUrl: '/static/images/banner/banner02.jpg' },
{ imageUrl: '/static/images/banner/banner03.jpg' }
]
}
},
methods: {
hasPermission: verifyPermission,
selectBanner(item) {
console.log('Banner selected:', item)
},
handleBannerChange(event) {
this.activeBannerIndex = event.detail.current
},
navigateToSection(event) {
const routeMap = {
0: '/pages/system/users',
1: '/pages/system/roles',
2: '/pages/system/menus',
3: '/pages/system/departments',
4: '/pages/system/positions',
5: '/pages/system/dictionaries',
6: '/pages/system/configurations',
7: '/pages/monitoring/login-logs',
8: '/pages/monitoring/operation-logs',
9: '/pages/tools/upload-demo',
10: '/pages/transportation/route-planner',
11: '/pages/transportation/subway-lines',
12: '/pages/transportation/subway-stations'
}
const targetRoute = routeMap[event.detail.index]
if (targetRoute) {
this.$router.navigateTo(targetRoute)
}
}
}
}
</script>
<style lang="scss">
page {
display: flex;
flex-direction: column;
background-color: #ffffff;
min-height: 100vh;
}
.cell-label {
text-align: center;
font-size: 13px;
margin-top: 5px;
}
.grid-cell-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15px 0;
}
.banner-carousel {
height: 150px;
}
.banner-item {
display: flex;
justify-content: center;
align-items: center;
height: 150px;
}
@media (min-width: 500px) {
.banner-carousel {
width: 400px;
margin: 8px auto 0;
}
.banner-item image {
width: 100%;
}
}
</style>