wangguoyu 3 săptămâni în urmă
părinte
comite
081164cfac
9 a modificat fișierele cu 268 adăugiri și 23 ștergeri
  1. 0 6
      App.uvue
  2. 8 3
      common/env.uts
  3. 95 0
      components/uni-button-x/uni-button-x.uvue
  4. 17 0
      index.html
  5. 1 4
      package.json
  6. 2 1
      pages.json
  7. 18 0
      utils/auth.uts
  8. 52 9
      utils/request.uts
  9. 75 0
      utils/router.uts

+ 0 - 6
App.uvue

@@ -14,9 +14,3 @@ export default {
   }
 }
 </script>
-
-<style>
-page {
-  background-color: #f5f5f5;
-}
-</style>

+ 8 - 3
common/env.uts

@@ -1,18 +1,23 @@
 export type EnvConfig = {
   baseUrl : string
   name : string
+  debug : boolean
 }
 
 const DEV : EnvConfig = {
   name: 'dev',
-  baseUrl: 'https://dev-api.example.com'
+  baseUrl: 'https://dev-api.example.com',
+  debug: true
 }
 
 const PROD : EnvConfig = {
   name: 'prod',
-  baseUrl: 'https://api.example.com'
+  baseUrl: 'https://api.example.com',
+  debug: false
 }
 
-export const env : EnvConfig = DEV
+export const ENV_NAME : string = 'dev'
+
+export const env : EnvConfig = ENV_NAME == 'prod' ? PROD : DEV
 
 export const TIMEOUT : number = 15000

+ 95 - 0
components/uni-button-x/uni-button-x.uvue

@@ -0,0 +1,95 @@
+<template>
+  <view class="btn" :class="btnClass" @click="onClick">
+    <text class="btn-text" :class="textClass">{{ loading ? loadingText : text }}</text>
+  </view>
+</template>
+
+<script setup lang="uts">
+type ButtonType = 'primary' | 'default' | 'danger'
+
+const props = defineProps({
+  text: {
+    type: String,
+    default: '按钮'
+  },
+  loadingText: {
+    type: String,
+    default: '加载中...'
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  type: {
+    type: String,
+    default: 'primary'
+  }
+})
+
+const emit = defineEmits(['click'])
+
+const btnClass = computed(() : string => {
+  if (props.disabled || props.loading) {
+    return 'btn-disabled'
+  }
+  if (props.type == 'danger') {
+    return 'btn-danger'
+  }
+  if (props.type == 'default') {
+    return 'btn-default'
+  }
+  return 'btn-primary'
+})
+
+const textClass = computed(() : string => {
+  if (props.type == 'default') {
+    return 'btn-text-default'
+  }
+  return 'btn-text-light'
+})
+
+function onClick() : void {
+  if (props.disabled || props.loading) {
+    return
+  }
+  emit('click')
+}
+</script>
+
+<style>
+.btn {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  height: 44px;
+  padding-left: 16px;
+  padding-right: 16px;
+  border-radius: 8px;
+}
+.btn-primary {
+  background-color: #007aff;
+}
+.btn-danger {
+  background-color: #ff3b30;
+}
+.btn-default {
+  background-color: #f2f2f2;
+}
+.btn-disabled {
+  background-color: #c7c7cc;
+}
+.btn-text {
+  font-size: 16px;
+}
+.btn-text-light {
+  color: #ffffff;
+}
+.btn-text-default {
+  color: #333333;
+}
+</style>

+ 17 - 0
index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+      document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main"></script>
+  </body>
+</html>

+ 1 - 4
package.json

@@ -2,8 +2,5 @@
   "name": "uniProjectX",
   "version": "1.0.0",
   "description": "uni-app x 项目",
-  "private": true,
-  "uni-app-x": {
-    "flex-direction": "column"
-  }
+  "private": true
 }

+ 2 - 1
pages.json

@@ -43,6 +43,7 @@
     "navigationBarTextStyle": "black",
     "navigationBarTitleText": "uniProjectX",
     "navigationBarBackgroundColor": "#FFFFFF",
-    "backgroundColor": "#F5F5F5"
+    "backgroundColor": "#F5F5F5",
+    "backgroundColorContent": "#F5F5F5"
   }
 }

+ 18 - 0
utils/auth.uts

@@ -0,0 +1,18 @@
+export const TOKEN_KEY : string = 'token'
+export const USER_INFO_KEY : string = 'userInfo'
+
+export function isLogin() : boolean {
+  const token = uni.getStorageSync(TOKEN_KEY)
+  if (token == null) {
+    return false
+  }
+  return (token as string).length > 0
+}
+
+export function getToken() : string {
+  const token = uni.getStorageSync(TOKEN_KEY)
+  if (token == null) {
+    return ''
+  }
+  return token as string
+}

+ 52 - 9
utils/request.uts

@@ -1,4 +1,5 @@
 import { env, TIMEOUT } from '@/common/env.uts'
+import { logout } from '@/store/user.uts'
 
 export type ApiResponse<T> = {
   code : number
@@ -20,6 +21,16 @@ function getToken() : string {
   return token as string
 }
 
+function toastError(msg : string) : void {
+  uni.showToast({ title: msg, icon: 'none' })
+}
+
+function handleUnauthorized() : void {
+  logout()
+  toastError('登录已失效,请重新登录')
+  uni.reLaunch({ url: '/pages/mine/mine' })
+}
+
 export function request<T>(options : RequestOptions) : Promise<T> {
   return new Promise<T>((resolve, reject) => {
     const header : UTSJSONObject = options.header ?? {}
@@ -29,6 +40,10 @@ export function request<T>(options : RequestOptions) : Promise<T> {
       header['Authorization'] = 'Bearer ' + token
     }
 
+    if (options.showLoading == true) {
+      uni.showLoading({ title: '加载中...' })
+    }
+
     uni.request({
       url: env.baseUrl + options.url,
       method: options.method ?? 'GET',
@@ -36,26 +51,46 @@ export function request<T>(options : RequestOptions) : Promise<T> {
       header: header,
       timeout: TIMEOUT,
       success: (res) => {
+        if (options.showLoading == true) {
+          uni.hideLoading()
+        }
         const statusCode = res.statusCode
+        if (statusCode == 401 || statusCode == 403) {
+          handleUnauthorized()
+          reject(new Error('登录已失效'))
+          return
+        }
         if (statusCode >= 200 && statusCode < 300) {
-          const body = res.data as UTSJSONObject
-          const code = body.getNumber('code')
-          if (code != null && code == 200) {
-            resolve(body.get('data') as T)
+          const body = res.data
+          if (body == null || !(body instanceof UTSJSONObject)) {
+            reject(new Error('响应格式错误'))
+            return
+          }
+          const json = body as UTSJSONObject
+          const code = json.getNumber('code')
+          if (code == null) {
+            resolve(json as T)
+            return
+          }
+          if (code == 200) {
+            resolve(json.get('data') as T)
           } else {
-            const msg = body.getString('message') ?? '请求失败'
-            uni.showToast({ title: msg, icon: 'none' })
+            const msg = json.getString('message') ?? '请求失败'
+            toastError(msg)
             reject(new Error(msg))
           }
         } else {
           const msg = '网络错误 ' + statusCode
-          uni.showToast({ title: msg, icon: 'none' })
+          toastError(msg)
           reject(new Error(msg))
         }
       },
       fail: (err) => {
-        const msg = err.errMsg ?? '请求失败'
-        uni.showToast({ title: msg, icon: 'none' })
+        if (options.showLoading == true) {
+          uni.hideLoading()
+        }
+        const msg = err.errMsg != null && err.errMsg.length > 0 ? err.errMsg : '请求失败'
+        toastError(msg)
         reject(new Error(msg))
       }
     })
@@ -69,3 +104,11 @@ export function get<T>(url : string, data ?: UTSJSONObject | null) : Promise<T>
 export function post<T>(url : string, data ?: UTSJSONObject | null) : Promise<T> {
   return request<T>({ url: url, method: 'POST', data: data })
 }
+
+export function put<T>(url : string, data ?: UTSJSONObject | null) : Promise<T> {
+  return request<T>({ url: url, method: 'PUT', data: data })
+}
+
+export function del<T>(url : string, data ?: UTSJSONObject | null) : Promise<T> {
+  return request<T>({ url: url, method: 'DELETE', data: data })
+}

+ 75 - 0
utils/router.uts

@@ -0,0 +1,75 @@
+import { isLogin } from '@/utils/auth.uts'
+
+export const LOGIN_PAGE : string = '/pages/mine/mine'
+
+const WHITE_LIST : string[] = [
+  '/pages/index/index',
+  '/pages/category/category',
+  '/pages/mine/mine'
+]
+
+function normalize(url : string) : string {
+  let path = url
+  const questionIndex = path.indexOf('?')
+  if (questionIndex > -1) {
+    path = path.substring(0, questionIndex)
+  }
+  return path
+}
+
+function inWhiteList(path : string) : boolean {
+  for (let i = 0; i < WHITE_LIST.length; i++) {
+    if (WHITE_LIST[i] == path) {
+      return true
+    }
+  }
+  return false
+}
+
+export function canNavigate(url : string) : boolean {
+  if (inWhiteList(normalize(url))) {
+    return true
+  }
+  return isLogin()
+}
+
+function goLogin(from : string) : void {
+  uni.showToast({ title: '请先登录', icon: 'none' })
+  uni.navigateTo({ url: LOGIN_PAGE + '?redirect=' + encodeURIComponent(from) })
+}
+
+export function navigateTo(url : string) : void {
+  if (!canNavigate(url)) {
+    goLogin(url)
+    return
+  }
+  uni.navigateTo({ url: url })
+}
+
+export function redirectTo(url : string) : void {
+  if (!canNavigate(url)) {
+    uni.redirectTo({ url: LOGIN_PAGE })
+    return
+  }
+  uni.redirectTo({ url: url })
+}
+
+export function switchTab(url : string) : void {
+  if (!canNavigate(url)) {
+    goLogin(url)
+    return
+  }
+  uni.switchTab({ url: url })
+}
+
+export function reLaunch(url : string) : void {
+  if (!canNavigate(url)) {
+    uni.reLaunch({ url: LOGIN_PAGE })
+    return
+  }
+  uni.reLaunch({ url: url })
+}
+
+export function navigateBack(delta ?: number) : void {
+  uni.navigateBack({ delta: delta ?? 1 })
+}