Implementing a Unified Request Handler and User Interface in a Uni-App Project

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>

Tags: uni-app HTTP request Authentication Vue.js Frontend Development

Posted on Thu, 28 May 2026 21:10:23 +0000 by vinny69