Explorar o código

Chore: Chore Update

Z.X.PING hai 7 meses
pai
achega
6917c5ac76
Modificáronse 51 ficheiros con 1078 adicións e 987 borrados
  1. 0 1
      .vscode/extensions.json
  2. 5 2
      env/.env
  3. 4 1
      eslint.config.mjs
  4. 3 0
      manifest.config.ts
  5. 1 1
      openapi-ts-request.config.ts
  6. 25 16
      package.json
  7. 12 37
      scripts/create-base-files.js
  8. 2 2
      scripts/open-dev-tools.js
  9. 0 29
      scripts/window-path-loader.js
  10. 4 4
      src/api/foo.ts
  11. 0 1
      src/env.d.ts
  12. 1 1
      src/hooks/useRequest.ts
  13. 74 0
      src/hooks/useScroll.ts
  14. 18 4
      src/hooks/useUpload.ts
  15. 3 2
      src/http/alova.ts
  16. 29 22
      src/http/http.ts
  17. 5 3
      src/http/interceptor.ts
  18. 14 4
      src/http/types.ts
  19. 3 0
      src/pages-fg/404/README.md
  20. 30 0
      src/pages-fg/404/index.vue
  21. 3 0
      src/pages-fg/REAME.md
  22. 0 0
      src/pages-fg/login/README.md
  23. 5 4
      src/pages/login/login.vue
  24. 0 0
      src/pages-fg/login/register.vue
  25. 15 19
      src/pages/about/about.vue
  26. 0 0
      src/pages-sub/about/alova.vue
  27. 25 0
      src/pages-sub/about/components/Upload.vue
  28. 0 0
      src/pages-sub/about/components/VBindCss.vue
  29. 64 0
      src/pages-sub/about/components/request-openapi.vue
  30. 26 5
      src/pages/about/components/request.vue
  31. 9 0
      src/pages-sub/demo/index.vue
  32. 72 0
      src/pages-sub/demo/scroll.vue
  33. 13 0
      src/pages/index/index.vue
  34. 5 3
      src/router/config.ts
  35. 10 3
      src/router/interceptor.ts
  36. 0 13
      src/service/displayEnumLabel.ts
  37. 2 4
      src/service/index.ts
  38. 18 0
      src/service/info.ts
  39. 19 0
      src/service/listAll.ts
  40. 0 185
      src/service/pet.ts
  41. 0 72
      src/service/store.ts
  42. 16 337
      src/service/types.ts
  43. 0 150
      src/service/user.ts
  44. 3 17
      src/tabbar/config.ts
  45. 2 0
      src/types/auto-import.d.ts
  46. 16 13
      src/utils/index.ts
  47. 0 26
      src/utils/platform.ts
  48. 236 0
      vite-plugins/README.md
  49. 201 0
      vite-plugins/copy-native-resources.ts
  50. 68 0
      vite-plugins/sync-manifest-plugins.ts
  51. 17 6
      vite.config.ts

+ 0 - 1
.vscode/extensions.json

@@ -12,7 +12,6 @@
     "uni-helper.uni-ui-snippets-vscode",
     "uni-helper.uni-app-snippets-vscode",
     "streetsidesoftware.code-spell-checker",
-    "foxundermoon.shell-format",
     "christian-kohler.path-intellisense"
   ]
 }

+ 5 - 2
env/.env

@@ -21,7 +21,10 @@ VITE_APP_PROXY_ENABLE = false
 VITE_APP_PROXY_PREFIX = '/fg-api'
 
 # 第二个请求地址 (目前alova中可以使用)
-VITE_API_SECONDARY_URL = 'https://ukw0y1.laf.run'
+VITE_SERVER_BASEURL_SECONDARY = 'https://ukw0y1.laf.run'
 
 # 认证模式,'single' | 'double' ==> 单token | 双token
-VITE_AUTH_MODE = 'single'
+VITE_AUTH_MODE = 'single'
+
+# 原生插件资源复制开关,控制是否启用 copy-native-resources 插件
+VITE_COPY_NATIVE_RES_ENABLE = false

+ 4 - 1
eslint.config.mjs

@@ -5,7 +5,10 @@ export default uniHelper({
   vue: true,
   markdown: false,
   ignores: [
-    'src/uni_modules/',
+    // 忽略uni_modules目录
+    '**/uni_modules/',
+    // 忽略原生插件目录
+    '**/nativeplugins/',
     'dist',
     // unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
     'auto-import.d.ts',

+ 3 - 0
manifest.config.ts

@@ -128,6 +128,9 @@ export default defineManifestConfig({
     optimization: {
       subPackages: true,
     },
+    // 是否合并组件虚拟节点外层属性,uni-app 3.5.1+ 开始支持。目前仅支持 style、class 属性。
+    // 默认不开启(undefined),这里设置为开启。
+    mergeVirtualHostAttributes: true,
     // styleIsolation: 'shared',
     usingComponents: true,
     // __usePrivacyCheck__: true,

+ 1 - 1
openapi-ts-request.config.ts

@@ -2,7 +2,7 @@ import type { GenerateServiceProps } from 'openapi-ts-request'
 
 export default [
   {
-    schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
+    schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
     serversPath: './src/service',
     requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions } from '@/http/types';`,
     requestOptionsType: 'CustomRequestOptions',

+ 25 - 16
package.json

@@ -1,9 +1,9 @@
 {
   "name": "unibest",
   "type": "module",
-  "version": "3.16.3",
-  "unibest-version": "3.16.3",
-  "update-time": "2025-09-16",
+  "version": "3.18.5",
+  "unibest-version": "3.18.5",
+  "update-time": "2025-10-03",
   "packageManager": "pnpm@10.10.0",
   "description": "unibest - 最好的 uniapp 开发模板",
   "generate-time": "用户创建项目时生成",
@@ -36,7 +36,7 @@
     "dev:app-android": "uni -p app-android",
     "dev:app-ios": "uni -p app-ios",
     "dev:custom": "uni -p",
-    "dev": "node --experimental-loader ./scripts/window-path-loader.js node_modules/@dcloudio/vite-plugin-uni/bin/uni.js",
+    "dev": "uni",
     "dev:test": "uni --mode test",
     "dev:prod": "uni --mode production",
     "dev:h5": "uni",
@@ -87,7 +87,7 @@
     "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
     "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
     "type-check": "vue-tsc --noEmit",
-    "openapi-ts-request": "openapi-ts",
+    "openapi": "openapi-ts",
     "prepare": "git init && husky && node ./scripts/create-base-files.js",
     "docker:prepare": "node ./scripts/create-base-files.js",
     "lint": "eslint",
@@ -135,18 +135,18 @@
     "@iconify-json/carbon": "^1.2.4",
     "@rollup/rollup-darwin-x64": "^4.28.0",
     "@types/node": "^20.17.9",
-    "@uni-helper/eslint-config": "^0.5.0",
+    "@uni-helper/eslint-config": "0.5.0",
     "@uni-helper/plugin-uni": "0.1.0",
-    "@uni-helper/uni-env": "^0.1.8",
-    "@uni-helper/uni-types": "^1.0.0-alpha.6",
-    "@uni-helper/unocss-preset-uni": "^0.2.11",
-    "@uni-helper/vite-plugin-uni-components": "0.2.0",
+    "@uni-helper/uni-env": "0.1.8",
+    "@uni-helper/uni-types": "1.0.0-alpha.6",
+    "@uni-helper/unocss-preset-uni": "0.2.11",
+    "@uni-helper/vite-plugin-uni-components": "0.2.3",
     "@uni-helper/vite-plugin-uni-layouts": "0.1.11",
-    "@uni-helper/vite-plugin-uni-manifest": "^0.2.8",
-    "@uni-helper/vite-plugin-uni-pages": "^0.3.8",
-    "@uni-helper/vite-plugin-uni-platform": "^0.0.5",
-    "@uni-ku/bundle-optimizer": "^1.3.3",
-    "@uni-ku/root": "^1.3.4",
+    "@uni-helper/vite-plugin-uni-manifest": "0.2.8",
+    "@uni-helper/vite-plugin-uni-pages": "0.3.13",
+    "@uni-helper/vite-plugin-uni-platform": "0.0.5",
+    "@uni-ku/bundle-optimizer": "v1.3.15-beta.2",
+    "@uni-ku/root": "1.4.1",
     "@unocss/eslint-plugin": "^66.2.3",
     "@unocss/preset-legacy-compat": "66.0.0",
     "@vue/runtime-core": "^3.4.21",
@@ -172,8 +172,17 @@
     "vite-plugin-restart": "^1.0.0",
     "vue-tsc": "^3.0.6"
   },
+  "pnpm": {
+    "overrides": {
+      "unconfig": "7.3.2"
+    }
+  },
+  "overrides": {
+    "unconfig": "7.3.2"
+  },
   "resolutions": {
-    "bin-wrapper": "npm:bin-wrapper-china"
+    "bin-wrapper": "npm:bin-wrapper-china",
+    "unconfig": "7.3.2"
   },
   "lint-staged": {
     "*": "eslint --fix"

+ 12 - 37
scripts/create-base-files.js

@@ -1,4 +1,6 @@
-// 生成 src/manifest.json 和 src/pages.json
+// 基础配置文件生成脚本
+// 此脚本用于生成 src/manifest.json 和 src/pages.json 基础文件
+// 由于这两个配置文件会被添加到 .gitignore 中,因此需要通过此脚本确保项目能正常运行
 import fs from 'node:fs'
 import path from 'node:path'
 import { fileURLToPath } from 'node:url'
@@ -157,10 +159,6 @@ const pages = {
         text: '首页',
         pagePath: 'pages/index/index',
       },
-      {
-        text: '关于',
-        pagePath: 'pages/about/about',
-      },
       {
         text: '我的',
         pagePath: 'pages/me/me',
@@ -176,35 +174,6 @@ const pages = {
         navigationBarTitleText: '首页',
       },
     },
-    {
-      path: 'pages/about/about',
-      type: 'page',
-      style: {
-        navigationBarTitleText: '关于',
-      },
-      excludeLoginPath: false,
-    },
-    {
-      path: 'pages/about/alova',
-      type: 'page',
-      style: {
-        navigationBarTitleText: 'Alova 演示',
-      },
-    },
-    {
-      path: 'pages/login/login',
-      type: 'page',
-      style: {
-        navigationBarTitleText: '登录',
-      },
-    },
-    {
-      path: 'pages/login/register',
-      type: 'page',
-      style: {
-        navigationBarTitleText: '注册',
-      },
-    },
     {
       path: 'pages/me/me',
       type: 'page',
@@ -239,6 +208,12 @@ if (!fs.existsSync(srcDir)) {
   fs.mkdirSync(srcDir, { recursive: true })
 }
 
-// 写入文件
-fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
-fs.writeFileSync(pagesPath, JSON.stringify(pages, null, 2))
+// 如果 src/manifest.json 不存在,就创建它;存在就不处理,以免覆盖
+if (!fs.existsSync(manifestPath)) {
+  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
+}
+
+// 如果 src/pages.json 不存在,就创建它;存在就不处理,以免覆盖
+if (!fs.existsSync(pagesPath)) {
+  fs.writeFileSync(pagesPath, JSON.stringify(pages, null, 2))
+}

+ 2 - 2
scripts/open-dev-tools.js

@@ -8,9 +8,9 @@ import process from 'node:process'
  */
 function _openDevTools() {
   const platform = process.platform // darwin, win32, linux
-  const { UNI_PLATFORM } = process.env // mp, mp-weixin, mp-alipay
+  const { UNI_PLATFORM } = process.env //  mp-weixin, mp-alipay
 
-  const uniPlatformText = UNI_PLATFORM === 'mp' || UNI_PLATFORM === 'mp-weixin' ? '微信小程序' : UNI_PLATFORM === 'mp-alipay' ? '支付宝小程序' : '小程序'
+  const uniPlatformText = UNI_PLATFORM === 'mp-weixin' ? '微信小程序' : UNI_PLATFORM === 'mp-alipay' ? '支付宝小程序' : '小程序'
 
   // 项目路径(构建输出目录)
   const projectPath = path.resolve(process.cwd(), `dist/dev/${UNI_PLATFORM}`)

+ 0 - 29
scripts/window-path-loader.js

@@ -1,29 +0,0 @@
-// fix: https://github.com/unibest-tech/unibest/issues/219
-
-// Windows path loader for Node.js ESM
-// This loader converts Windows absolute paths to file:// URLs
-
-import { pathToFileURL } from 'node:url'
-
-/**
- * Resolve hook for ESM loader
- * Converts Windows absolute paths to file:// URLs
- */
-export function resolve(specifier, context, defaultResolve) {
-  // Check if this is a Windows absolute path (starts with drive letter like C:)
-  if (specifier.match(/^[a-z]:\\/i) || specifier.match(/^[a-z]:\//i)) {
-    // Convert Windows path to file:// URL
-    const fileUrl = pathToFileURL(specifier).href
-    return defaultResolve(fileUrl, context, defaultResolve)
-  }
-
-  // For all other specifiers, use the default resolve
-  return defaultResolve(specifier, context, defaultResolve)
-}
-
-/**
- * Load hook for ESM loader
- */
-export function load(url, context, defaultLoad) {
-  return defaultLoad(url, context, defaultLoad)
-}

+ 4 - 4
src/api/foo.ts

@@ -21,8 +21,8 @@ export interface IFooItem {
 }
 
 /** GET 请求 */
-export function getFooAPI(name: string) {
-  return http.get<IFooItem>('/foo', { name })
+export async function getFooAPI(name: string) {
+  return await http.get<IFooItem>('/foo', { name })
 }
 /** GET 请求;支持 传递 header 的范例 */
 export function getFooAPI2(name: string) {
@@ -35,9 +35,9 @@ export function postFooAPI(name: string) {
 }
 /** POST 请求;需要传递 query 参数的范例;微信小程序经常有同时需要query参数和body参数的场景 */
 export function postFooAPI2(name: string) {
-  return http.post<IFooItem>('/foo', { name })
+  return http.post<IFooItem>('/foo', { name }, { a: 1, b: 2 })
 }
 /** POST 请求;支持 传递 header 的范例 */
 export function postFooAPI3(name: string) {
-  return http.post<IFooItem>('/foo', { name }, { name }, { 'Content-Type-100': '100' })
+  return http.post<IFooItem>('/foo', { name }, { a: 1, b: 2 }, { 'Content-Type-100': '100' })
 }

+ 0 - 1
src/env.d.ts

@@ -33,4 +33,3 @@ interface ImportMeta {
 }
 
 declare const __VITE_APP_PROXY__: 'true' | 'false'
-declare const __UNI_PLATFORM__: 'app' | 'h5' | 'mp-alipay' | 'mp-baidu' | 'mp-kuaishou' | 'mp-lark' | 'mp-qq' | 'mp-tiktok' | 'mp-weixin' | 'mp-xiaochengxu'

+ 1 - 1
src/hooks/useRequest.ts

@@ -48,7 +48,7 @@ export default function useRequest<T, P = undefined>(
   }
 
   if (options.immediate) {
-    (run as (args?: P) => Promise<T | undefined>)({} as P)
+    (run as (args: P) => Promise<T | undefined>)({} as P)
   }
   return { loading, error, data, run }
 }

+ 74 - 0
src/hooks/useScroll.ts

@@ -0,0 +1,74 @@
+import type { Ref } from 'vue'
+import { onMounted, ref } from 'vue'
+
+interface UseScrollOptions<T> {
+  fetchData: (page: number, pageSize: number) => Promise<T[]>
+  pageSize?: number
+}
+
+interface UseScrollReturn<T> {
+  list: Ref<T[]>
+  loading: Ref<boolean>
+  finished: Ref<boolean>
+  error: Ref<any>
+  refresh: () => Promise<void>
+  loadMore: () => Promise<void>
+}
+
+export function useScroll<T>({
+  fetchData,
+  pageSize = 10,
+}: UseScrollOptions<T>): UseScrollReturn<T> {
+  const list = ref<T[]>([]) as Ref<T[]>
+  const loading = ref(false)
+  const finished = ref(false)
+  const error = ref<any>(null)
+  const page = ref(1)
+
+  const loadData = async () => {
+    if (loading.value || finished.value)
+      return
+
+    loading.value = true
+    error.value = null
+
+    try {
+      const data = await fetchData(page.value, pageSize)
+      if (data.length < pageSize) {
+        finished.value = true
+      }
+      list.value.push(...data)
+      page.value++
+    }
+    catch (err) {
+      error.value = err
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  const refresh = async () => {
+    page.value = 1
+    finished.value = false
+    list.value = []
+    await loadData()
+  }
+
+  const loadMore = async () => {
+    await loadData()
+  }
+
+  onMounted(() => {
+    refresh()
+  })
+
+  return {
+    list,
+    loading,
+    finished,
+    error,
+    refresh,
+    loadMore,
+  }
+}

+ 18 - 4
src/hooks/useUpload.ts

@@ -1,4 +1,7 @@
 import { ref } from 'vue'
+import { getEnvBaseUrl } from '@/utils/index'
+
+const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/upload`
 
 type TfileType = 'image' | 'file'
 type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
@@ -52,10 +55,21 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
       tempFilePath,
       formData,
       onSuccess: (res) => {
-        const { data: _data } = JSON.parse(res)
-        data.value = _data
+        // 修改这里的解析逻辑,适应不同平台的返回格式
+        let parsedData = res
+        try {
+          // 尝试解析为JSON
+          const jsonData = JSON.parse(res)
+          // 检查是否包含data字段
+          parsedData = jsonData.data || jsonData
+        }
+        catch (e) {
+          // 如果解析失败,使用原始数据
+          console.log('Response is not JSON, using raw data:', res)
+        }
+        data.value = parsedData
         // console.log('上传成功', res)
-        success?.(_data)
+        success?.(parsedData)
       },
       onError: (err) => {
         error.value = err
@@ -135,7 +149,7 @@ async function uploadFile({
   onComplete: () => void
 }) {
   uni.uploadFile({
-    url: '/upload',
+    url: VITE_UPLOAD_BASEURL,
     filePath: tempFilePath,
     name: 'file',
     formData,

+ 3 - 2
src/http/alova.ts

@@ -10,7 +10,7 @@ import { ContentTypeEnum, ResultEnum, ShowMessage } from './tools/enum'
 // 配置动态Tag
 export const API_DOMAINS = {
   DEFAULT: import.meta.env.VITE_SERVER_BASEURL,
-  SECONDARY: import.meta.env.VITE_API_SECONDARY_URL,
+  SECONDARY: import.meta.env.VITE_SERVER_BASEURL_SECONDARY,
 }
 
 /**
@@ -20,6 +20,7 @@ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthenticati
   typeof VueHook,
   typeof uniappRequestAdapter
 >({
+  // 如果下面拦截不到,请使用 refreshTokenOnSuccess by 群友@琛
   refreshTokenOnError: {
     isExpired: (error) => {
       return error.response?.status === ResultEnum.Unauthorized
@@ -41,7 +42,7 @@ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthenticati
  * alova 请求实例
  */
 const alovaInstance = createAlova({
-  baseURL: import.meta.env.VITE_APP_PROXY_PREFIX,
+  baseURL: API_DOMAINS.DEFAULT,
   ...AdapterUniapp(),
   timeout: 5000,
   statesHook: VueHook,

+ 29 - 22
src/http/http.ts

@@ -21,18 +21,13 @@ export function http<T>(options: CustomRequestOptions) {
       // #endif
       // 响应成功
       success: async (res) => {
-        // 状态码 2xx,参考 axios 的设计
-        if (res.statusCode >= 200 && res.statusCode < 300) {
-          // 2.1  处理业务逻辑错误
-          const { code, message, data } = res.data as IResponse<T>
-          // 0和200当做成功都很普遍,这里直接兼容两者,见 ResultEnum
-          if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) {
-            throw new Error(`请求错误[${code}]:${message}`)
-          }
-          return resolve(data as T)
-        }
-        const resData: IResData<T> = res.data as IResData<T>
-        if ((res.statusCode === 401) || (resData.code === 401)) {
+        const responseData = res.data as IResponse<T>
+        const { code } = responseData
+
+        // 检查是否是401错误(包括HTTP状态码401或业务码401)
+        const isTokenExpired = res.statusCode === 401 || code === 401
+
+        if (isTokenExpired) {
           const tokenStore = useTokenStore()
           if (!isDoubleTokenMode) {
             // 未启用双token策略,清理用户信息,跳转到登录页
@@ -40,16 +35,18 @@ export function http<T>(options: CustomRequestOptions) {
             uni.navigateTo({ url: LOGIN_PAGE })
             return reject(res)
           }
+
           /* -------- 无感刷新 token ----------- */
           const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
           // token 失效的,且有刷新 token 的,才放到请求队列里
-          if ((res.statusCode === 401 || resData.code === 401) && refreshToken) {
+          if (refreshToken) {
             taskQueue.push(() => {
               resolve(http<T>(options))
             })
           }
+
           // 如果有 refreshToken 且未在刷新中,发起刷新 token 请求
-          if ((res.statusCode === 401 || resData.code === 401) && refreshToken && !refreshing) {
+          if (refreshToken && !refreshing) {
             refreshing = true
             try {
               // 发起刷新 token 请求(使用 store 的 refreshToken 方法)
@@ -91,16 +88,26 @@ export function http<T>(options: CustomRequestOptions) {
               taskQueue = []
             }
           }
+
+          return reject(res)
         }
-        else {
-          // 其他错误 -> 根据后端错误信息轻提示
-          !options.hideErrorToast
-          && uni.showToast({
-            icon: 'none',
-            title: (res.data as IResData<T>).msg || '请求错误',
-          })
-          reject(res)
+
+        // 处理其他成功状态(HTTP状态码200-299)
+        if (res.statusCode >= 200 && res.statusCode < 300) {
+          // 处理业务逻辑错误
+          if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) {
+            throw new Error(`请求错误[${code}]:${responseData.message || responseData.msg}`)
+          }
+          return resolve(responseData.data as T)
         }
+
+        // 处理其他错误
+        !options.hideErrorToast
+        && uni.showToast({
+          icon: 'none',
+          title: (res.data as any).msg || '请求错误',
+        })
+        reject(res)
       },
       // 响应失败
       fail(err) {

+ 5 - 3
src/http/interceptor.ts

@@ -1,7 +1,6 @@
 import type { CustomRequestOptions } from '@/http/types'
 import { useTokenStore } from '@/store'
 import { getEnvBaseUrl } from '@/utils'
-import { platform } from '@/utils/platform'
 import { stringifyQuery } from './tools/queryString'
 
 // 请求基准地址
@@ -11,6 +10,11 @@ const baseUrl = getEnvBaseUrl()
 const httpInterceptor = {
   // 拦截前触发
   invoke(options: CustomRequestOptions) {
+    // 如果您使用了alova,则请把下面的代码放开注释
+    // alova 执行流程:alova beforeRequest --> 本拦截器 --> alova responded
+    // return options
+
+    // 非 alova 请求,正常执行
     // 接口请求支持通过 query 参数配置 queryString
     if (options.query) {
       const queryStr = stringifyQuery(options.query)
@@ -43,8 +47,6 @@ const httpInterceptor = {
     // 2. (可选)添加小程序端请求头标识
     options.header = {
       ...options.header,
-      'Content-Type': 'application/json; charset=utf-8',
-      platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源
     }
     // 3. 添加 token 请求头标识
     const tokenStore = useTokenStore()

+ 14 - 4
src/http/types.ts

@@ -7,12 +7,22 @@ export type CustomRequestOptions = UniApp.RequestOptions & {
   hideErrorToast?: boolean
 } & IUniUploadFileOptions // 添加uni.uploadFile参数类型
 
-// 通用响应格式
-export interface IResponse<T = any> {
-  code: number | string
+export interface HttpRequestResult<T> {
+  promise: Promise<T>
+  requestTask: UniApp.RequestTask
+}
+
+// 通用响应格式(兼容 msg + message 字段)
+export type IResponse<T = any> = {
+  code: number
   data: T
   message: string
-  status: string | number
+  [key: string]: any // 允许额外属性
+} | {
+  code: number
+  data: T
+  msg: string
+  [key: string]: any // 允许额外属性
 }
 
 // 分页请求参数

+ 3 - 0
src/pages-fg/404/README.md

@@ -0,0 +1,3 @@
+# 404 页面
+
+`404页面` 只有在路由不存在时才会显示,如果您不需要可以删除该页面。但是建议保留。

+ 30 - 0
src/pages-fg/404/index.vue

@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import { HOME_PAGE } from '@/utils'
+
+definePage({
+  style: {
+    // 'custom' 表示开启自定义导航栏,默认 'default'
+    navigationStyle: 'custom',
+  },
+})
+
+function goBack() {
+  // 当pages.config.ts中配置了tabbar页面时,使用switchTab切换到首页
+  // 否则使用navigateTo返回首页
+  uni.switchTab({ url: HOME_PAGE })
+}
+</script>
+
+<template>
+  <view class="h-screen flex flex-col items-center justify-center">
+    <view> 404 </view>
+    <view> 页面不存在 </view>
+    <button class="mt-6 w-40 text-center" @click="goBack">
+      返回首页
+    </button>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+//
+</style>

+ 3 - 0
src/pages-fg/REAME.md

@@ -0,0 +1,3 @@
+# pages-fg 说明
+
+为了尽量减少主包的大小,一些无关紧要但经常需要的页面(如登录页、注册页、404页等)放在了 `pages-fg` 目录下。

src/pages/login/README.md → src/pages-fg/login/README.md


+ 5 - 4
src/pages/login/login.vue

@@ -61,10 +61,11 @@ async function doLogin() {
     // })
   }
   else {
-    console.log('redirectTo:', path)
-    uni.redirectTo({
-      url: path,
-    })
+    // 自己决定是 redirectTo 还是 navigateBack
+    // uni.redirectTo({
+    //   url: path,
+    // })
+    uni.navigateBack()
   }
 }
 </script>

src/pages/login/register.vue → src/pages-fg/login/register.vue


+ 15 - 19
src/pages/about/about.vue

@@ -2,8 +2,9 @@
 import { isApp, isAppAndroid, isAppHarmony, isAppIOS, isAppPlus, isH5, isMpWeixin, isWeb } from '@uni-helper/uni-env'
 import { LOGIN_PAGE } from '@/router/config'
 import { useTokenStore } from '@/store'
-import { tabbarStore } from '@/tabbar/store'
+import RequestOpenApiComp from './components/request-openapi.vue'
 import RequestComp from './components/request.vue'
+import UploadComp from './components/Upload.vue'
 import VBindCss from './components/VBindCss.vue'
 
 definePage({
@@ -27,7 +28,7 @@ function gotoLogin() {
     return
   }
   uni.navigateTo({
-    url: `${LOGIN_PAGE}?redirect=${encodeURIComponent('/pages/about/about?a=1&b=2')}`,
+    url: `${LOGIN_PAGE}?redirect=${encodeURIComponent('/pages-sub/about/about?a=1&b=2')}`,
   })
 }
 function logout() {
@@ -40,20 +41,15 @@ function logout() {
   })
 }
 
-function gotoTabbar() {
-  uni.switchTab({
-    url: '/pages/index/index',
+function gotoScroll() {
+  uni.navigateTo({
+    url: '/pages-sub/demo/scroll',
   })
 }
-// #region setTabbarBadge
-function setTabbarBadge() {
-  tabbarStore.setTabbarItemBadge(1, 100)
-}
-// #endregion
 
 function gotoAlova() {
   uni.navigateTo({
-    url: '/pages/about/alova',
+    url: '/pages-sub/about/alova',
   })
 }
 function gotoSubPage() {
@@ -108,20 +104,20 @@ onShow(() => {
         点击退出登录
       </button>
     </view>
-    <button class="mt-4 w-60 text-center" @click="setTabbarBadge">
-      设置tabbarBadge
-    </button>
+    <RequestOpenApiComp />
     <RequestComp />
+    <UploadComp />
     <VBindCss />
     <view class="mb-6 h-1px bg-#eee" />
-    <view class="text-center">
-      <button type="primary" size="mini" class="w-160px" @click="gotoAlova">
-        前往 alova 示例页面
+    <view class="mb-2 text-center">
+      <button type="primary" size="mini" class="w-240px" @click="gotoScroll">
+        下拉刷新和下拉加载更多
       </button>
+      <view>简单hooks(非z-paging组件)</view>
     </view>
     <view class="text-center">
-      <button type="primary" size="mini" class="w-160px" @click="gotoTabbar">
-        切换tabbar
+      <button type="primary" size="mini" class="w-160px" @click="gotoAlova">
+        前往 alova 示例页面
       </button>
     </view>
     <view class="text-center">

src/pages/about/alova.vue → src/pages-sub/about/alova.vue


+ 25 - 0
src/pages-sub/about/components/Upload.vue

@@ -0,0 +1,25 @@
+<template>
+  <view class="p-4 text-center">
+    <button type="primary" size="mini" class="w-160px" @click="run">
+      选择图片并上传
+    </button>
+    <view v-if="loading" class="h-10 text-blue">
+      上传...
+    </view>
+    <template v-else>
+      <view class="m-2">
+        上传后返回的接口数据:
+      </view>
+      <view class="m-2">
+        {{ data }}
+      </view>
+      <view class="m-auto h-40 max-w-40">
+        <image v-if="data" :src="data.url" mode="scaleToFill" />
+      </view>
+    </template>
+  </view>
+</template>
+
+<script lang="ts" setup>
+const { loading, data, run } = useUpload()
+</script>

src/pages/about/components/VBindCss.vue → src/pages-sub/about/components/VBindCss.vue


+ 64 - 0
src/pages-sub/about/components/request-openapi.vue

@@ -0,0 +1,64 @@
+<script lang="ts" setup>
+import type { UserItem } from '@/service'
+import { infoUsingGet, listAllUsingGet } from '@/service'
+
+const loading = ref(false)
+const error = ref<Error | null>(null)
+const data = ref<UserItem>()
+
+// openapi 请求示例
+async function getUserInfo() {
+  try {
+    loading.value = true
+    const res = await infoUsingGet({})
+    console.log(res)
+    data.value = res
+    error.value = null
+  }
+  catch (err) {
+    error.value = err as Error
+    data.value = null
+  }
+  finally {
+    loading.value = false
+  }
+}
+
+// openapi + useRequest 请求示例
+const { data: data2, loading: loading2, run } = useRequest(() => listAllUsingGet({}), {
+  immediate: false,
+})
+</script>
+
+<template>
+  <view class="p-6 text-center">
+    <view class="my-4 text-center">
+      1)直接使用 openapi 生成的请求
+    </view>
+    <view class="my-4 text-center">
+      <button type="primary" size="mini" class="w-160px" @click="getUserInfo">
+        发送请求
+      </button>
+      <view class="text-xl">
+        请求数据如下
+      </view>
+      <view class="text-green leading-8">
+        {{ JSON.stringify(data) }}
+      </view>
+    </view>
+    <view class="my-4 text-center">
+      2)直接使用 openapi + useRequest 生成的请求
+    </view>
+    <view class="my-4 flex items-center gap-2 text-center">
+      <button type="primary" size="mini" class="w-160px" @click="run">
+        发送请求
+      </button>
+    </view>
+    <view class="text-xl">
+      请求数据如下
+    </view>
+    <view class="text-green leading-8">
+      {{ JSON.stringify(data2) }}
+    </view>
+  </view>
+</template>

+ 26 - 5
src/pages/about/components/request.vue

@@ -8,6 +8,19 @@ import { getFooAPI } from '@/api/foo'
 // }
 const initialData = undefined
 
+// 直接请求示例
+async function reqFooAPI() {
+  try {
+    const res = await getFooAPI('菲鸽')
+    console.log('直接请求示例res', res)
+  }
+  catch (err) {
+    console.log(err)
+  }
+}
+reqFooAPI()
+
+// 直接useRequest请求示例
 const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
   immediate: true,
   initialData,
@@ -24,7 +37,7 @@ function reset() {
       pages 里面的 vue 文件会扫描成页面,将自动添加到 pages.json 里面。
     </view>
     <view class="my-2 text-green-400">
-      但是 pages/components 里面的 vue 不会。
+      但是 components 里面的 vue 不会。
     </view>
 
     <view class="my-4 text-center">
@@ -37,11 +50,19 @@ function reset() {
         loading...
       </view>
       <block v-else>
-        <view class="text-xl">
-          请求数据如下
+        <view v-if="error instanceof Error" class="text-red leading-8">
+          错误: {{ error.message }}
+        </view>
+        <view v-else-if="error" class="text-red leading-8">
+          错误: 未知错误
         </view>
-        <view class="text-green leading-8">
-          {{ JSON.stringify(data) }}
+        <view v-else>
+          <view class="text-xl">
+            请求数据如下
+          </view>
+          <view class="text-green leading-8">
+            {{ JSON.stringify(data) }}
+          </view>
         </view>
       </block>
     </view>

+ 9 - 0
src/pages-sub/demo/index.vue

@@ -7,6 +7,12 @@ definePage({
     navigationBarTitleText: '分包页面',
   },
 })
+
+function gotoScroll() {
+  uni.navigateTo({
+    url: '/pages-sub/demo/scroll',
+  })
+}
 </script>
 
 <template>
@@ -20,6 +26,9 @@ definePage({
     <view class="text-blue-500">
       分包页面里面的components示例
     </view>
+    <button class="my-4" type="primary" size="mini" @click="gotoScroll">
+      跳转到上拉刷新和下拉加载更多
+    </button>
     <view>
       <RequestComp />
     </view>

+ 72 - 0
src/pages-sub/demo/scroll.vue

@@ -0,0 +1,72 @@
+<script setup lang="ts">
+// uniapp 页面生命周期
+import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
+
+import { useScroll } from '@/hooks/useScroll'
+
+definePage({
+  style: {
+    navigationBarTitleText: '下拉刷新和下拉加载更多',
+    enablePullDownRefresh: true,
+    onReachBottomDistance: 100,
+  },
+})
+
+// 模拟异步获取数据的函数
+function mockFetchData(page: number, pageSize: number): Promise<{ id: number, name: string }[]> {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      if (page > 5) {
+        // 模拟没有更多数据
+        resolve([])
+        return
+      }
+      const data = Array.from({ length: pageSize }, (_, i) => ({
+        id: (page - 1) * pageSize + i + 1,
+        name: `item ${(page - 1) * pageSize + i + 1}`,
+      }))
+      resolve(data)
+    }, 1000)
+  })
+}
+
+const { list, loading, finished, error, refresh, loadMore } = useScroll({
+  fetchData: mockFetchData,
+  pageSize: 10,
+})
+
+onPullDownRefresh(async () => {
+  console.log('onPullDownRefresh')
+  console.log('onPullDownRefresh')
+  console.log('onPullDownRefresh')
+  await refresh()
+  uni.stopPullDownRefresh()
+})
+
+onReachBottom(() => {
+  loadMore()
+})
+</script>
+
+<template>
+  <view class="h-screen p-4">
+    <view v-if="error" class="text-center text-red-500">
+      加载失败,请重试
+    </view>
+    <view v-else>
+      <view
+        v-for="item in list"
+        :key="item.id"
+        class="my-2 h-20 flex items-center justify-center rounded bg-gray-100"
+      >
+        {{ item.name }}
+      </view>
+      <view v-if="loading" class="py-4 text-center text-gray-500">
+        加载中...
+      </view>
+      <view v-if="finished" class="py-4 text-center text-gray-500">
+        没有更多了
+      </view>
+    </view>
+  </view>
+</template>

+ 13 - 0
src/pages/index/index.vue

@@ -25,6 +25,14 @@ console.log('index/index 首页打印了')
 onLoad(() => {
   console.log('测试 uni API 自动引入: onLoad')
 })
+
+// #region gotoAbout
+function gotoAbout() {
+  uni.navigateTo({
+    url: '/pages-sub/about/about',
+  })
+}
+// #endregion
 </script>
 
 <template>
@@ -96,6 +104,11 @@ onLoad(() => {
         https://wot-design-uni.cn
       </text>
     </view>
+    <view class="mt-4 text-center">
+      <wd-button type="primary" class="ml-2" @click="gotoAbout">
+        前往示例页
+      </wd-button>
+    </view>
     <view class="h-6" />
   </view>
 </template>

+ 5 - 3
src/router/config.ts

@@ -8,8 +8,9 @@ export const LOGIN_STRATEGY_MAP = {
 export const LOGIN_STRATEGY = LOGIN_STRATEGY_MAP.DEFAULT_NO_NEED_LOGIN
 export const isNeedLoginMode = LOGIN_STRATEGY === LOGIN_STRATEGY_MAP.DEFAULT_NEED_LOGIN
 
-export const LOGIN_PAGE = '/pages/login/login'
-export const REGISTER_PAGE = '/pages/login/register'
+export const LOGIN_PAGE = '/pages-fg/login/login'
+export const REGISTER_PAGE = '/pages-fg/login/register'
+export const NOT_FOUND_PAGE = '/pages-fg/404/index'
 
 export const LOGIN_PAGE_LIST = [LOGIN_PAGE, REGISTER_PAGE]
 
@@ -19,7 +20,8 @@ export const excludeLoginPathList = getAllPages('excludeLoginPath').map(page =>
 // 排除在外的列表,白名单策略指白名单列表,黑名单策略指黑名单列表
 // TODO: 2/3 在 definePage 配置 excludeLoginPath,或者在下面配置 EXCLUDE_LOGIN_PATH_LIST
 export const EXCLUDE_LOGIN_PATH_LIST = [
-  '/pages/xxx/index',
+  '/pages/xxx/index', // 示例值
+  '/pages-sub/xxx/index', // 示例值
   ...excludeLoginPathList, // 都是以 / 开头的 path
 ]
 

+ 10 - 3
src/router/interceptor.ts

@@ -2,12 +2,12 @@ import { isMp } from '@uni-helper/uni-env'
 /**
  * by 菲鸽 on 2025-08-19
  * 路由拦截,通常也是登录拦截
- * 黑白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
+ * 黑白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
  */
 import { useTokenStore } from '@/store/token'
 import { isPageTabbar, tabbarStore } from '@/tabbar/store'
 import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index'
-import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP } from './config'
+import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP, NOT_FOUND_PAGE } from './config'
 
 export const FG_LOG_ENABLE = false
 export function judgeIsExcludePath(path: string) {
@@ -43,6 +43,13 @@ export const navigateToInterceptor = {
       path = `${baseDir}/${path}`
     }
 
+    // 处理路由不存在的情况
+    if (getAllPages().every(page => page.path !== path) && path !== '/') {
+      console.warn('路由不存在:', path)
+      uni.navigateTo({ url: NOT_FOUND_PAGE })
+      return false // 明确表示阻止原路由继续执行
+    }
+
     // 处理直接进入路由非首页时,tabbarIndex 不正确的问题
     tabbarStore.setAutoCurIdx(path)
 
@@ -104,9 +111,9 @@ export const navigateToInterceptor = {
         uni.navigateTo({ url: redirectUrl })
         return false // 修改为false,阻止原路由继续执行
       }
+      return true // 明确表示允许路由继续执行
     }
     // #endregion 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
-    return true // 明确表示允许路由继续执行
   },
 }
 

+ 0 - 13
src/service/displayEnumLabel.ts

@@ -1,13 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-import * as API from './types';
-
-export function displayStatusEnum(field: API.StatusEnum) {
-  return { available: 'available', pending: 'pending', sold: 'sold' }[field];
-}
-
-export function displayStatusEnum2(field: API.StatusEnum2) {
-  return { placed: 'placed', approved: 'approved', delivered: 'delivered' }[
-    field
-  ];
-}

+ 2 - 4
src/service/index.ts

@@ -1,8 +1,6 @@
 /* eslint-disable */
 // @ts-ignore
 export * from './types';
-export * from './displayEnumLabel';
 
-export * from './pet';
-export * from './store';
-export * from './user';
+export * from './listAll';
+export * from './info';

+ 18 - 0
src/service/info.ts

@@ -0,0 +1,18 @@
+/* eslint-disable */
+// @ts-ignore
+import request from '@/http/vue-query';
+import { CustomRequestOptions } from '@/http/types';
+
+import * as API from './types';
+
+/** 用户信息 GET /user/info */
+export async function infoUsingGet({
+  options,
+}: {
+  options?: CustomRequestOptions;
+}) {
+  return request<API.UserItem>('/user/info', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}

+ 19 - 0
src/service/listAll.ts

@@ -0,0 +1,19 @@
+/* eslint-disable */
+// @ts-ignore
+import request from '@/http/vue-query';
+import { CustomRequestOptions } from '@/http/types';
+
+import * as API from './types';
+const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+/** 用户列表 GET /user/listAll */
+export async function listAllUsingGet({
+  options,
+}: {
+  options?: CustomRequestOptions;
+}) {
+  return request<API.UserItem[]>('/user/listAll', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}

+ 0 - 185
src/service/pet.ts

@@ -1,185 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-import request from '@/http/vue-query';
-import { CustomRequestOptions } from '@/http/types';
-
-import * as API from './types';
-
-/** Update an existing pet PUT /pet */
-export async function petUsingPut({
-  body,
-  options,
-}: {
-  body: API.Pet;
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/pet', {
-    method: 'PUT',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Add a new pet to the store POST /pet */
-export async function petUsingPost({
-  body,
-  options,
-}: {
-  body: API.Pet;
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/pet', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Find pet by ID Returns a single pet GET /pet/${param0} */
-export async function petPetIdUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetPetIdUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  const { petId: param0, ...queryParams } = params;
-
-  return request<API.Pet>(`/pet/${param0}`, {
-    method: 'GET',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}
-
-/** Updates a pet in the store with form data POST /pet/${param0} */
-export async function petPetIdUsingPost({
-  params,
-  body,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetPetIdUsingPostParams;
-  body: API.PetPetIdUsingPostBody;
-  options?: CustomRequestOptions;
-}) {
-  const { petId: param0, ...queryParams } = params;
-
-  return request<unknown>(`/pet/${param0}`, {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/x-www-form-urlencoded',
-    },
-    params: { ...queryParams },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Deletes a pet DELETE /pet/${param0} */
-export async function petPetIdUsingDelete({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetPetIdUsingDeleteParams;
-  options?: CustomRequestOptions;
-}) {
-  const { petId: param0, ...queryParams } = params;
-
-  return request<unknown>(`/pet/${param0}`, {
-    method: 'DELETE',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}
-
-/** uploads an image POST /pet/${param0}/uploadImage */
-export async function petPetIdUploadImageUsingPost({
-  params,
-  body,
-  file,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetPetIdUploadImageUsingPostParams;
-  body: API.PetPetIdUploadImageUsingPostBody;
-  file?: File;
-  options?: CustomRequestOptions;
-}) {
-  const { petId: param0, ...queryParams } = params;
-  const formData = new FormData();
-
-  if (file) {
-    formData.append('file', file);
-  }
-
-  Object.keys(body).forEach((ele) => {
-    const item = (body as { [key: string]: any })[ele];
-
-    if (item !== undefined && item !== null) {
-      if (typeof item === 'object' && !(item instanceof File)) {
-        if (item instanceof Array) {
-          item.forEach((f) => formData.append(ele, f || ''));
-        } else {
-          formData.append(ele, JSON.stringify(item));
-        }
-      } else {
-        formData.append(ele, item);
-      }
-    }
-  });
-
-  return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'multipart/form-data',
-    },
-    params: { ...queryParams },
-    data: formData,
-    ...(options || {}),
-  });
-}
-
-/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
-export async function petFindByStatusUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetFindByStatusUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  return request<API.Pet[]>('/pet/findByStatus', {
-    method: 'GET',
-    params: {
-      ...params,
-    },
-    ...(options || {}),
-  });
-}
-
-/** Finds Pets by tags Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
-export async function petFindByTagsUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.PetFindByTagsUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  return request<API.Pet[]>('/pet/findByTags', {
-    method: 'GET',
-    params: {
-      ...params,
-    },
-    ...(options || {}),
-  });
-}

+ 0 - 72
src/service/store.ts

@@ -1,72 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-import request from '@/http/vue-query';
-import { CustomRequestOptions } from '@/http/types';
-
-import * as API from './types';
-
-/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
-export async function storeInventoryUsingGet({
-  options,
-}: {
-  options?: CustomRequestOptions;
-}) {
-  return request<Record<string, number>>('/store/inventory', {
-    method: 'GET',
-    ...(options || {}),
-  });
-}
-
-/** Place an order for a pet POST /store/order */
-export async function storeOrderUsingPost({
-  body,
-  options,
-}: {
-  body: API.Order;
-  options?: CustomRequestOptions;
-}) {
-  return request<API.Order>('/store/order', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
-export async function storeOrderOrderIdUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.StoreOrderOrderIdUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  const { orderId: param0, ...queryParams } = params;
-
-  return request<API.Order>(`/store/order/${param0}`, {
-    method: 'GET',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}
-
-/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
-export async function storeOrderOrderIdUsingDelete({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.StoreOrderOrderIdUsingDeleteParams;
-  options?: CustomRequestOptions;
-}) {
-  const { orderId: param0, ...queryParams } = params;
-
-  return request<unknown>(`/store/order/${param0}`, {
-    method: 'DELETE',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}

+ 16 - 337
src/service/types.ts

@@ -1,350 +1,29 @@
 /* eslint-disable */
 // @ts-ignore
 
-export type ApiResponse = {
-  code?: number;
-  type?: string;
-  message?: string;
+export type InfoUsingGetResponse = {
+  code: number;
+  msg: string;
+  data: UserItem;
 };
 
-export type Category = {
-  id?: number;
-  name?: string;
+export type InfoUsingGetResponses = {
+  200: InfoUsingGetResponse;
 };
 
-export type Order = {
-  id?: number;
-  petId?: number;
-  quantity?: number;
-  shipDate?: string;
-  /** Order Status */
-  status?: 'placed' | 'approved' | 'delivered';
-  complete?: boolean;
+export type ListAllUsingGetResponse = {
+  code: number;
+  msg: string;
+  data: UserItem[];
 };
 
-export type Pet = {
-  id?: number;
-  category?: Category;
-  name: string;
-  photoUrls: string[];
-  tags?: Tag[];
-  /** pet status in the store */
-  status?: 'available' | 'pending' | 'sold';
+export type ListAllUsingGetResponses = {
+  200: ListAllUsingGetResponse;
 };
 
-export type PetFindByStatusUsingGetParams = {
-  /** Status values that need to be considered for filter */
-  status: ('available' | 'pending' | 'sold')[];
-};
-
-export type PetFindByStatusUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: Pet[];
-  /**
-   * Invalid status value
-   */
-  400: unknown;
-};
-
-export type PetFindByTagsUsingGetParams = {
-  /** Tags to filter by */
-  tags: string[];
-};
-
-export type PetFindByTagsUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: Pet[];
-  /**
-   * Invalid tag value
-   */
-  400: unknown;
-};
-
-export type PetPetIdUploadImageUsingPostBody = {
-  /** Additional data to pass to server */
-  additionalMetadata?: string;
-  /** file to upload */
-  file?: string;
-};
-
-export type PetPetIdUploadImageUsingPostParams = {
-  /** ID of pet to update */
-  petId: number;
-};
-
-export type PetPetIdUploadImageUsingPostResponses = {
-  /**
-   * successful operation
-   */
-  200: ApiResponse;
-};
-
-export type PetPetIdUsingDeleteParams = {
-  /** Pet id to delete */
-  petId: number;
-};
-
-export type PetPetIdUsingDeleteResponses = {
-  /**
-   * Invalid ID supplied
-   */
-  400: unknown;
-  /**
-   * Pet not found
-   */
-  404: unknown;
-};
-
-export type PetPetIdUsingGetParams = {
-  /** ID of pet to return */
-  petId: number;
-};
-
-export type PetPetIdUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: Pet;
-  /**
-   * Invalid ID supplied
-   */
-  400: unknown;
-  /**
-   * Pet not found
-   */
-  404: unknown;
-};
-
-export type PetPetIdUsingPostBody = {
-  /** Updated name of the pet */
-  name?: string;
-  /** Updated status of the pet */
-  status?: string;
-};
-
-export type PetPetIdUsingPostParams = {
-  /** ID of pet that needs to be updated */
-  petId: number;
-};
-
-export type PetPetIdUsingPostResponses = {
-  /**
-   * Invalid input
-   */
-  405: unknown;
-};
-
-export type PetUsingPostResponses = {
-  /**
-   * Invalid input
-   */
-  405: unknown;
-};
-
-export type PetUsingPutResponses = {
-  /**
-   * Invalid ID supplied
-   */
-  400: unknown;
-  /**
-   * Pet not found
-   */
-  404: unknown;
-  /**
-   * Validation exception
-   */
-  405: unknown;
-};
-
-export enum StatusEnum {
-  'available' = 'available',
-  'pending' = 'pending',
-  'sold' = 'sold',
-}
-
-export type IStatusEnum = keyof typeof StatusEnum;
-
-export enum StatusEnum2 {
-  'placed' = 'placed',
-  'approved' = 'approved',
-  'delivered' = 'delivered',
-}
-
-export type IStatusEnum2 = keyof typeof StatusEnum2;
-
-export type StoreInventoryUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: Record<string, number>;
-};
-
-export type StoreOrderOrderIdUsingDeleteParams = {
-  /** ID of the order that needs to be deleted */
-  orderId: number;
-};
-
-export type StoreOrderOrderIdUsingDeleteResponses = {
-  /**
-   * Invalid ID supplied
-   */
-  400: unknown;
-  /**
-   * Order not found
-   */
-  404: unknown;
-};
-
-export type StoreOrderOrderIdUsingGetParams = {
-  /** ID of pet that needs to be fetched */
-  orderId: number;
-};
-
-export type StoreOrderOrderIdUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: Order;
-  /**
-   * Invalid ID supplied
-   */
-  400: unknown;
-  /**
-   * Order not found
-   */
-  404: unknown;
-};
-
-export type StoreOrderUsingPostResponses = {
-  /**
-   * successful operation
-   */
-  200: Order;
-  /**
-   * Invalid Order
-   */
-  400: unknown;
-};
-
-export type Tag = {
-  id?: number;
-  name?: string;
-};
-
-export type User = {
-  id?: number;
-  username?: string;
-  firstName?: string;
-  lastName?: string;
-  email?: string;
-  password?: string;
-  phone?: string;
-  /** User Status */
-  userStatus?: number;
-};
-
-export type UserCreateWithArrayUsingPostBody = User[];
-
-export type UserCreateWithArrayUsingPostResponses = {
-  /**
-   * successful operation
-   */
-  default: unknown;
-};
-
-export type UserCreateWithListUsingPostBody = User[];
-
-export type UserCreateWithListUsingPostResponses = {
-  /**
-   * successful operation
-   */
-  default: unknown;
-};
-
-export type UserLoginUsingGetParams = {
-  /** The user name for login */
+export type UserItem = {
+  userId: number;
   username: string;
-  /** The password for login in clear text */
-  password: string;
-};
-
-export type UserLoginUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: string;
-  /**
-   * Invalid username/password supplied
-   */
-  400: unknown;
-};
-
-export type UserLogoutUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  default: unknown;
-};
-
-export type UserUsernameUsingDeleteParams = {
-  /** The name that needs to be deleted */
-  username: string;
-};
-
-export type UserUsernameUsingDeleteResponses = {
-  /**
-   * Invalid username supplied
-   */
-  400: unknown;
-  /**
-   * User not found
-   */
-  404: unknown;
-};
-
-export type UserUsernameUsingGetParams = {
-  /** The name that needs to be fetched. Use user1 for testing.  */
-  username: string;
-};
-
-export type UserUsernameUsingGetResponses = {
-  /**
-   * successful operation
-   */
-  200: User;
-  /**
-   * Invalid username supplied
-   */
-  400: unknown;
-  /**
-   * User not found
-   */
-  404: unknown;
-};
-
-export type UserUsernameUsingPutParams = {
-  /** name that need to be updated */
-  username: string;
-};
-
-export type UserUsernameUsingPutResponses = {
-  /**
-   * Invalid user supplied
-   */
-  400: unknown;
-  /**
-   * User not found
-   */
-  404: unknown;
-};
-
-export type UserUsingPostResponses = {
-  /**
-   * successful operation
-   */
-  default: unknown;
+  nickname: string;
+  avatar: string;
 };

+ 0 - 150
src/service/user.ts

@@ -1,150 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-import request from '@/http/vue-query';
-import { CustomRequestOptions } from '@/http/types';
-
-import * as API from './types';
-
-/** Create user This can only be done by the logged in user. 返回值: successful operation POST /user */
-export async function userUsingPost({
-  body,
-  options,
-}: {
-  body: API.User;
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/user', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Get user by user name GET /user/${param0} */
-export async function userUsernameUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.UserUsernameUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  const { username: param0, ...queryParams } = params;
-
-  return request<API.User>(`/user/${param0}`, {
-    method: 'GET',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}
-
-/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
-export async function userUsernameUsingPut({
-  params,
-  body,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.UserUsernameUsingPutParams;
-  body: API.User;
-  options?: CustomRequestOptions;
-}) {
-  const { username: param0, ...queryParams } = params;
-
-  return request<unknown>(`/user/${param0}`, {
-    method: 'PUT',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    params: { ...queryParams },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
-export async function userUsernameUsingDelete({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.UserUsernameUsingDeleteParams;
-  options?: CustomRequestOptions;
-}) {
-  const { username: param0, ...queryParams } = params;
-
-  return request<unknown>(`/user/${param0}`, {
-    method: 'DELETE',
-    params: { ...queryParams },
-    ...(options || {}),
-  });
-}
-
-/** Creates list of users with given input array 返回值: successful operation POST /user/createWithArray */
-export async function userCreateWithArrayUsingPost({
-  body,
-  options,
-}: {
-  body: API.UserCreateWithArrayUsingPostBody;
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/user/createWithArray', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Creates list of users with given input array 返回值: successful operation POST /user/createWithList */
-export async function userCreateWithListUsingPost({
-  body,
-  options,
-}: {
-  body: API.UserCreateWithListUsingPostBody;
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/user/createWithList', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    data: body,
-    ...(options || {}),
-  });
-}
-
-/** Logs user into the system GET /user/login */
-export async function userLoginUsingGet({
-  params,
-  options,
-}: {
-  // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
-  params: API.UserLoginUsingGetParams;
-  options?: CustomRequestOptions;
-}) {
-  return request<string>('/user/login', {
-    method: 'GET',
-    params: {
-      ...params,
-    },
-    ...(options || {}),
-  });
-}
-
-/** Logs out current logged in user session 返回值: successful operation GET /user/logout */
-export async function userLogoutUsingGet({
-  options,
-}: {
-  options?: CustomRequestOptions;
-}) {
-  return request<unknown>('/user/logout', {
-    method: 'GET',
-    ...(options || {}),
-  });
-}

+ 3 - 17
src/tabbar/config.ts

@@ -32,12 +32,6 @@ export const nativeTabbarList: NativeTabBarItem[] = [
     pagePath: 'pages/index/index',
     text: '首页',
   },
-  {
-    iconPath: 'static/tabbar/example.png',
-    selectedIconPath: 'static/tabbar/exampleHL.png',
-    pagePath: 'pages/about/about',
-    text: '关于',
-  },
   {
     iconPath: 'static/tabbar/personal.png',
     selectedIconPath: 'static/tabbar/personalHL.png',
@@ -71,22 +65,14 @@ export const customTabbarList: CustomTabBarItem[] = [
     icon: 'i-carbon-home',
     // badge: 'dot',
   },
-  {
-    text: '关于',
-    pagePath: 'pages/about/about',
-    // 注意 unocss 图标需要如下处理:(二选一)
-    // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
-    // 2)配置到 unocss.config.ts 的 safelist 中
-    iconType: 'unocss',
-    icon: 'i-carbon-code',
-    // badge: 10,
-  },
   {
     pagePath: 'pages/me/me',
     text: '我的',
+    // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+    // 2)配置到 unocss.config.ts 的 safelist 中
     iconType: 'unocss',
     icon: 'i-carbon-user',
-    // badge: 100,
+    // badge: 10,
   },
   // 其他类型演示
   // 1、uiLib

+ 2 - 0
src/types/auto-import.d.ts

@@ -83,6 +83,7 @@ declare global {
   const useId: typeof import('vue')['useId']
   const useModel: typeof import('vue')['useModel']
   const useRequest: typeof import('../hooks/useRequest')['default']
+  const useScroll: typeof import('../hooks/useScroll')['useScroll']
   const useSlots: typeof import('vue')['useSlots']
   const useTemplateRef: typeof import('vue')['useTemplateRef']
   const useUpload: typeof import('../hooks/useUpload')['default']
@@ -180,6 +181,7 @@ declare module 'vue' {
     readonly useId: UnwrapRef<typeof import('vue')['useId']>
     readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
     readonly useRequest: UnwrapRef<typeof import('../hooks/useRequest')['default']>
+    readonly useScroll: UnwrapRef<typeof import('../hooks/useScroll')['useScroll']>
     readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
     readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
     readonly useUpload: UnwrapRef<typeof import('../hooks/useUpload')['default']>

+ 16 - 13
src/utils/index.ts

@@ -1,12 +1,15 @@
+import type { PageMetaDatum, SubPackages } from '@uni-helper/vite-plugin-uni-pages'
+import { isMpWeixin } from '@uni-helper/uni-env'
 import { pages, subPackages } from '@/pages.json'
-import { isMpWeixin } from './platform'
+
+export type PageInstance = Page.PageInstance<AnyObject, object> & { $page: Page.PageInstance<AnyObject, object> & { fullPath: string } }
 
 export function getLastPage() {
   // getCurrentPages() 至少有1个元素,所以不再额外判断
   // const lastPage = getCurrentPages().at(-1)
   // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】
   const pages = getCurrentPages()
-  return pages[pages.length - 1]
+  return pages[pages.length - 1] as PageInstance
 }
 
 /**
@@ -15,20 +18,20 @@ export function getLastPage() {
  * redirectPath 如 '/pages/demo/base/route-interceptor'
  */
 export function currRoute() {
-  const lastPage = getLastPage()
+  const lastPage = getLastPage() as PageInstance
   if (!lastPage) {
     return {
       path: '',
       query: {},
     }
   }
-  const currRoute = (lastPage as any).$page
+  const currRoute = lastPage.$page
   // console.log('lastPage.$page:', currRoute)
   // console.log('lastPage.$page.fullpath:', currRoute.fullPath)
   // console.log('lastPage.$page.options:', currRoute.options)
   // console.log('lastPage.options:', (lastPage as any).options)
   // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱
-  const { fullPath } = currRoute as { fullPath: string }
+  const { fullPath } = currRoute
   // console.log(fullPath)
   // eg: /pages/login/login?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序)
   // eg: /pages/login/login?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
@@ -69,9 +72,9 @@ export function parseUrlToObj(url: string) {
  * 这里设计得通用一点,可以传递 key 作为判断依据,默认是 excludeLoginPath, 与 route-block 配对使用
  * 如果没有传 key,则表示所有的 pages,如果传递了 key, 则表示通过 key 过滤
  */
-export function getAllPages(key = 'excludeLoginPath') {
+export function getAllPages(key?: string) {
   // 这里处理主包
-  const mainPages = pages
+  const mainPages = (pages as PageMetaDatum[])
     .filter(page => !key || page[key])
     .map(page => ({
       ...page,
@@ -79,14 +82,14 @@ export function getAllPages(key = 'excludeLoginPath') {
     }))
 
   // 这里处理分包
-  const subPages: any[] = []
-  subPackages.forEach((subPageObj) => {
+  const subPages: PageMetaDatum[] = []
+  ;(subPackages as SubPackages).forEach((subPageObj) => {
     // console.log(subPageObj)
     const { root } = subPageObj
 
     subPageObj.pages
       .filter(page => !key || page[key])
-      .forEach((page: { path: string } & Record<string, any>) => {
+      .forEach((page) => {
         subPages.push({
           ...page,
           path: `/${root}/${page.path}`,
@@ -100,14 +103,14 @@ export function getAllPages(key = 'excludeLoginPath') {
 
 export function getCurrentPageI18nKey() {
   const routeObj = currRoute()
-  const currPage = pages.find(page => `/${page.path}` === routeObj.path)
+  const currPage = (pages as PageMetaDatum[]).find(page => `/${page.path}` === routeObj.path)
   if (!currPage) {
     console.warn('路由不正确')
     return ''
   }
   console.log(currPage)
   console.log(currPage.style.navigationBarTitleText)
-  return currPage.style.navigationBarTitleText
+  return currPage.style?.navigationBarTitleText || ''
 }
 
 /**
@@ -153,4 +156,4 @@ export const isDoubleTokenMode = import.meta.env.VITE_AUTH_MODE === 'double'
  * 首页路径,通过 page.json 里面的 type 为 home 的页面获取,如果没有,则默认是第一个页面
  * 通常为 /pages/index/index
  */
-export const HOME_PAGE = `/${pages.find(page => page.type === 'home')?.path || pages[0].path}`
+export const HOME_PAGE = `/${(pages as PageMetaDatum[]).find(page => page.type === 'home')?.path || (pages as PageMetaDatum[])[0].path}`

+ 0 - 26
src/utils/platform.ts

@@ -1,26 +0,0 @@
-/*
- * @Author: 菲鸽
- * @Date: 2024-03-28 19:13:55
- * @Last Modified by: 菲鸽
- * @Last Modified time: 2024-03-28 19:24:55
- */
-export const platform = __UNI_PLATFORM__
-export const isH5 = __UNI_PLATFORM__ === 'h5'
-export const isApp = __UNI_PLATFORM__ === 'app'
-export const isMp = __UNI_PLATFORM__.startsWith('mp-')
-export const isMpWeixin = __UNI_PLATFORM__.startsWith('mp-weixin')
-export const isMpAplipay = __UNI_PLATFORM__.startsWith('mp-alipay')
-export const isMpToutiao = __UNI_PLATFORM__.startsWith('mp-toutiao')
-export const isHarmony = __UNI_PLATFORM__.startsWith('app-harmony')
-
-const PLATFORM = {
-  platform,
-  isH5,
-  isApp,
-  isMp,
-  isMpWeixin,
-  isMpAplipay,
-  isMpToutiao,
-  isHarmony,
-}
-export default PLATFORM

+ 236 - 0
vite-plugins/README.md

@@ -0,0 +1,236 @@
+# unibest原生插件资源复制插件
+
+## 概述
+
+`copy-native-resources.ts` 是一个专为 基于unibest框架的UniApp 项目设计的 Vite 插件,用于解决使用原生插件时打包后出现"插件找不到"的问题。该插件会在构建过程中自动将本地原生插件资源复制到正确的目标目录。
+
+## 功能特性
+
+- ✅ 自动复制原生插件资源到构建目录
+- ✅ 支持环境变量控制插件启用/禁用
+- ✅ 支持详细日志输出用于调试
+- ✅ 智能检测源目录是否存在
+
+## 目录结构
+
+根据 [UniApp 官方文档](https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6),本地原生插件应存储在项目根目录的 `nativeplugins` 目录下:
+
+```
+项目根目录/
+├── nativeplugins/                    # 原生插件存储目录(官方规范)
+│   ├── HL-HHWUHFController/         # 示例:RFID 控制器插件
+│   │   ├── android/                 # Android 平台资源
+│   │   │   ├── libs/               # Android 库文件
+│   │   │   └── res/                # Android 资源文件
+│   │   ├── ios/                    # iOS 平台资源(如果有)
+│   │   └── package.json            # 插件配置文件
+│   └── 其他原生插件/
+├── src/
+├── vite-plugins/
+│   ├── copy-native-resources.ts    # 本插件文件
+│   └── README.md                   # 本文档
+└── vite.config.ts
+```
+
+## 安装配置
+
+### 1. 环境变量配置
+
+在 `env/.env` 文件中添加以下配置:
+
+```bash
+# 是否启用原生插件资源复制
+VITE_COPY_NATIVE_RES_ENABLE = true
+```
+
+### 2. Vite 配置
+
+在 `vite.config.ts` 中引入并使用插件:
+
+```typescript
+import { createCopyNativeResourcesPlugin } from './vite-plugins/copy-native-resources'
+
+export default defineConfig({
+  plugins: [
+    // 其他插件...
+    
+    // 原生插件资源复制插件
+    createCopyNativeResourcesPlugin(
+      UNI_PLATFORM === 'app' && VITE_COPY_NATIVE_RES_ENABLE === 'true',
+      {
+        verbose: mode === 'development', // 开发模式显示详细日志
+      },
+    ),
+    
+    // 其他插件...
+  ],
+})
+```
+
+### 3. manifest.config.ts 配置
+
+在 `manifest.config.ts` 中配置原生插件:
+
+```typescript
+export default defineManifest({
+  // 其他配置...
+  
+  'app-plus': {
+    // 其他配置...
+    
+    // 原生插件配置
+    nativePlugins: {
+      // RFID 控制器插件示例
+      'HL-HHWUHFController': {
+        __plugin_info__: {
+          name: 'HL-HHWUHFController',
+          description: 'RFID UHF 控制器插件',
+          platforms: 'Android',
+          url: '',
+          android_package_name: '',
+          ios_bundle_id: '',
+          isCloud: false,
+          bought: -1,
+          pid: '',
+          parameters: {}
+        }
+      }
+    }
+  }
+})
+```
+
+## 插件配置选项
+
+```typescript
+interface CopyNativeResourcesOptions {
+  /** 是否启用插件 */
+  enable?: boolean
+  
+  /** 
+   * 源目录路径,相对于项目根目录
+   * 默认为 'nativeplugins',符合 UniApp 官方规范
+   */
+  sourceDir?: string
+  
+  /** 
+   * 目标目录名称,构建后在 dist 目录中的文件夹名
+   * 默认为 'nativeplugins',与源目录保持一致
+   */
+  targetDirName?: string
+  
+  /** 是否显示详细日志 */
+  verbose?: boolean
+}
+```
+
+## 使用示例
+
+### 基础使用
+
+```typescript
+// 使用默认配置
+createCopyNativeResourcesPlugin(true)
+```
+
+### 自定义配置
+
+```typescript
+// 自定义配置
+createCopyNativeResourcesPlugin(true, {
+  sourceDir: 'nativeplugins',      // 源目录
+  targetDirName: 'nativeplugins',  // 目标目录名
+  verbose: true                    // 显示详细日志
+})
+```
+
+### 条件启用
+
+```typescript
+// 仅在 app 平台且环境变量启用时生效
+createCopyNativeResourcesPlugin(
+  UNI_PLATFORM === 'app' && VITE_COPY_NATIVE_RES_ENABLE === 'true',
+  { verbose: mode === 'development' }
+)
+```
+
+## 工作原理
+
+1. **构建时机**:插件在 Vite 的 `writeBundle` 阶段执行
+2. **目录检测**:检查源目录 `nativeplugins` 是否存在
+3. **资源复制**:将整个 `nativeplugins` 目录复制到构建输出目录
+4. **路径处理**:自动处理不同平台的路径差异
+5. **日志输出**:根据配置显示复制过程的详细信息
+
+## 构建输出结构
+
+插件会将原生插件资源复制到以下位置:
+
+```
+dist/
+├── build/
+│   └── app/
+│       └── nativeplugins/          # 生产环境构建
+│           └── HL-HHWUHFController/
+└── dev/
+    └── app/
+        └── nativeplugins/          # 开发环境构建
+            └── HL-HHWUHFController/
+```
+
+## 常见问题
+
+### Q: 为什么要使用这个插件?
+
+A: 目前使用unibest框架在打包时可能不会自动复制原生插件资源,导致运行时出现"插件找不到"的错误。此插件确保原生插件资源被正确复制到构建目录。
+
+### Q: 插件不生效怎么办?
+
+A: 检查以下几点:
+1. 确认 `nativeplugins` 目录存在且包含插件文件
+2. 确认环境变量 `VITE_COPY_NATIVE_RES_ENABLE` 设置为 `true`
+3. 确认当前平台为 `app`(插件仅在 app 平台生效)
+4. 开启 `verbose: true` 查看详细日志
+
+### Q: 可以自定义源目录吗?
+
+A: 可以,但不推荐。UniApp 官方规范要求使用 `nativeplugins` 目录,自定义可能导致其他问题。
+
+### Q: 支持哪些平台?
+
+A: 插件本身支持所有平台,但通常只在 `app` 平台(目前只测试了Android环境,iOS有条件的伙伴可以测试后反馈)使用原生插件。
+
+
+### Q: 产生权限冲突问题?
+
+A: 有伙伴反馈过接入的原生插件之前使用【Lastly1999】提交的版本初步解决了问题,但是又遇到两个新的问题:
+- 导入的两个插件内的权限配置有版本冲突,在云打包的最后一步会报错,然后通过修改其中一个aar配置版本解决的。
+- 测试发现在android版本大于12的手机,获取相册权限后,打开相册看不到里面的照片,将两个插件删除就没问题 ,可以正常显示,不删除就会有问题,怀疑是插件的AndroidManifest.xml覆盖了项目内manifest.config.ts的安卓权限申请
+也欢迎其他有伙伴反馈,望能一起解决。
+
+## 更新日志
+
+### v1.0.0
+- 初始版本发布
+- 支持基础的原生插件资源复制功能
+
+### v1.1.0
+- 更新为符合 UniApp 官方规范的 `nativeplugins` 目录结构
+- 修复 ESLint 警告
+- 增加详细的代码注释和文档
+- 优化错误处理和日志输出
+
+## 技术支持
+
+如果在使用过程中遇到问题,请检查:
+
+1. UniApp 官方文档:[本地插件配置](https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6)
+2. 插件配置是否正确
+3. 目录结构是否符合规范
+4. 环境变量是否正确设置
+
+## 特别声明及感谢
+
+- 感谢【Lastly1999】,此插件时基于他pr的代码进行的还原和修改。[fix: app-plus、dev/prod、nativeResources插件未被正确移](https://gitee.com/feige996/unibest/commit/22e0bd5cfb47a4927373fe88be6809216f43d046)
+- 感谢【菲鸽】造了这么好用的框架
+

+ 201 - 0
vite-plugins/copy-native-resources.ts

@@ -0,0 +1,201 @@
+import type { Plugin } from 'vite'
+import path from 'node:path'
+import process from 'node:process'
+import fs from 'fs-extra'
+
+/**
+ * 原生插件资源复制配置接口
+ *
+ * 根据 UniApp 官方文档:https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
+ * 本地插件应该存储在项目根目录的 nativeplugins 目录下
+ */
+export interface CopyNativeResourcesOptions {
+  /** 是否启用插件 */
+  enable?: boolean
+  /**
+   * 源目录路径,相对于项目根目录
+   * 默认为 'nativeplugins',符合 UniApp 官方规范
+   * @see https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
+   */
+  sourceDir?: string
+  /**
+   * 目标目录名称,构建后在 dist 目录中的文件夹名
+   * 默认为 'nativeplugins',与源目录保持一致
+   */
+  targetDirName?: string
+  /** 是否显示详细日志,便于调试和监控复制过程 */
+  verbose?: boolean
+  /** 自定义日志前缀,用于区分不同插件的日志输出 */
+  logPrefix?: string
+}
+
+/**
+ * 默认配置
+ *
+ * 根据 UniApp 官方文档规范设置默认值:
+ * - sourceDir: 'nativeplugins' - 符合官方本地插件存储规范
+ * - targetDirName: 'nativeplugins' - 构建后保持相同的目录结构
+ */
+const DEFAULT_OPTIONS: Required<CopyNativeResourcesOptions> = {
+  enable: true,
+  sourceDir: 'nativeplugins',
+  targetDirName: 'nativeplugins',
+  verbose: true,
+  logPrefix: '[copy-native-resources]',
+}
+
+/**
+ * UniApp 原生插件资源复制插件
+ *
+ * 功能说明:
+ * 1. 解决 UniApp 使用本地原生插件时,打包后原生插件资源找不到的问题
+ * 2. 将项目根目录下的 nativeplugins 目录复制到构建输出目录中
+ * 3. 支持 Android 和 iOS 平台的原生插件资源复制
+ * 4. 仅在 app 平台构建时生效,其他平台(H5、小程序)不执行
+ *
+ * 使用场景:
+ * - 使用了 UniApp 本地原生插件(非云端插件)
+ * - 原生插件包含额外的资源文件(如 .so 库文件、配置文件等)
+ * - 需要在打包后保持原生插件的完整目录结构
+ *
+ * 官方文档参考:
+ * @see https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
+ * @see https://uniapp.dcloud.net.cn/tutorial/nvue-api.html#dom
+ *
+ * @param options 插件配置选项
+ * @returns Vite 插件对象
+ */
+export function copyNativeResources(options: CopyNativeResourcesOptions = {}): Plugin {
+  const config = { ...DEFAULT_OPTIONS, ...options }
+
+  // 如果插件被禁用,返回一个空插件
+  if (!config.enable) {
+    return {
+      name: 'copy-native-resources-disabled',
+      apply: 'build',
+      writeBundle() {
+        // 插件已禁用,不执行任何操作
+      },
+    }
+  }
+
+  return {
+    name: 'copy-native-resources',
+    apply: 'build', // 只在构建时应用
+    enforce: 'post', // 在其他插件执行完毕后执行
+
+    async writeBundle() {
+      const { sourceDir, targetDirName, verbose, logPrefix } = config
+
+      try {
+        // 获取项目根目录路径
+        const projectRoot = process.cwd()
+
+        // 构建源目录绝对路径(项目根目录下的 nativeplugins 目录)
+        const sourcePath = path.resolve(projectRoot, sourceDir)
+
+        // 构建目标路径:dist/[build|dev]/[platform]/nativeplugins
+        // buildMode: 'build' (生产环境) 或 'dev' (开发环境)
+        // platform: 'app' (App平台) 或其他平台标识
+        const buildMode = process.env.NODE_ENV === 'production' ? 'build' : 'dev'
+        const platform = process.env.UNI_PLATFORM || 'app'
+        const targetPath = path.resolve(
+          projectRoot,
+          'dist',
+          buildMode,
+          platform,
+          targetDirName,
+        )
+
+        // 检查源目录是否存在
+        // 如果不存在 nativeplugins 目录,说明项目没有使用本地原生插件
+        const sourceExists = await fs.pathExists(sourcePath)
+        if (!sourceExists) {
+          if (verbose) {
+            console.warn(`${logPrefix} 源目录不存在,跳过复制操作`)
+            console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
+            console.warn(`${logPrefix} 如需使用本地原生插件,请在项目根目录创建 nativeplugins 目录`)
+            console.warn(`${logPrefix} 并按照官方文档放入原生插件文件`)
+            console.warn(`${logPrefix} 参考: https://uniapp.dcloud.net.cn/plugin/native-plugin.html`)
+          }
+          return
+        }
+
+        // 检查源目录是否为空
+        // 如果目录存在但为空,也跳过复制操作
+        const sourceFiles = await fs.readdir(sourcePath)
+        if (sourceFiles.length === 0) {
+          if (verbose) {
+            console.warn(`${logPrefix} 源目录为空,跳过复制操作`)
+            console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
+            console.warn(`${logPrefix} 请在 nativeplugins 目录中放入原生插件文件`)
+          }
+          return
+        }
+
+        // 确保目标目录及其父目录存在
+        await fs.ensureDir(targetPath)
+
+        if (verbose) {
+          console.log(`${logPrefix} 开始复制 UniApp 本地原生插件...`)
+          console.log(`${logPrefix} 源目录: ${sourcePath}`)
+          console.log(`${logPrefix} 目标目录: ${targetPath}`)
+          console.log(`${logPrefix} 构建模式: ${buildMode}`)
+          console.log(`${logPrefix} 目标平台: ${platform}`)
+          console.log(`${logPrefix} 发现 ${sourceFiles.length} 个原生插件文件/目录`)
+        }
+
+        // 执行文件复制操作
+        // 将整个 nativeplugins 目录复制到构建输出目录
+        await fs.copy(sourcePath, targetPath, {
+          overwrite: true, // 覆盖已存在的文件,确保使用最新版本
+          errorOnExist: false, // 如果目标文件存在不报错
+          preserveTimestamps: true, // 保持文件的时间戳
+        })
+
+        if (verbose) {
+          console.log(`${logPrefix} ✅ UniApp 本地原生插件复制完成`)
+          console.log(`${logPrefix} 已成功复制 ${sourceFiles.length} 个文件/目录到构建目录`)
+          console.log(`${logPrefix} 原生插件现在可以在 App 中正常使用`)
+        }
+      }
+      catch (error) {
+        console.error(`${config.logPrefix} ❌ 复制 UniApp 本地原生插件失败:`, error)
+        console.error(`${config.logPrefix} 错误详情:`, error instanceof Error ? error.message : String(error))
+        console.error(`${config.logPrefix} 请检查源目录权限和磁盘空间`)
+        // 不抛出错误,避免影响整个构建过程,但会记录详细的错误信息
+      }
+    },
+  }
+}
+
+/**
+ * 创建 UniApp 本地原生插件资源复制插件的便捷函数
+ *
+ * 这是一个便捷的工厂函数,用于快速创建插件实例
+ * 特别适用于在 vite.config.ts 中进行条件性插件配置
+ *
+ * 使用示例:
+ * ```typescript
+ * // 在 vite.config.ts 中
+ * plugins: [
+ *   // 仅在 app 平台且启用时生效
+ *   UNI_PLATFORM === 'app'
+ *     ? createCopyNativeResourcesPlugin(
+ *         VITE_COPY_NATIVE_RES_ENABLE === 'true',
+ *         { verbose: mode === 'development' }
+ *       )
+ *     : null,
+ * ]
+ * ```
+ *
+ * @param enable 是否启用插件,通常通过环境变量控制
+ * @param options 其他配置选项,不包含 enable 属性
+ * @returns Vite 插件对象
+ */
+export function createCopyNativeResourcesPlugin(
+  enable: boolean = true,
+  options: Omit<CopyNativeResourcesOptions, 'enable'> = {},
+): Plugin {
+  return copyNativeResources({ enable, ...options })
+}

+ 68 - 0
vite-plugins/sync-manifest-plugins.ts

@@ -0,0 +1,68 @@
+import type { Plugin } from 'vite'
+import fs from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+
+interface ManifestType {
+  'plus'?: {
+    distribute?: {
+      plugins?: Record<string, any>
+    }
+  }
+  'app-plus'?: {
+    distribute?: {
+      plugins?: Record<string, any>
+    }
+  }
+}
+
+export default function syncManifestPlugin(): Plugin {
+  return {
+    name: 'sync-manifest',
+    apply: 'build',
+    enforce: 'post',
+    writeBundle: {
+      order: 'post',
+      handler() {
+        const srcManifestPath = path.resolve(process.cwd(), './src/manifest.json')
+        const distAppPath = path.resolve(process.cwd(), './dist/dev/app/manifest.json')
+
+        try {
+          // 读取源文件
+          const srcManifest = JSON.parse(fs.readFileSync(srcManifestPath, 'utf8')) as ManifestType
+
+          // 确保目标目录存在
+          const distAppDir = path.dirname(distAppPath)
+          if (!fs.existsSync(distAppDir)) {
+            fs.mkdirSync(distAppDir, { recursive: true })
+          }
+
+          // 读取目标文件(如果存在)
+          let distManifest: ManifestType = {}
+          if (fs.existsSync(distAppPath)) {
+            distManifest = JSON.parse(fs.readFileSync(distAppPath, 'utf8'))
+          }
+
+          // 如果源文件存在 plugins
+          if (srcManifest['app-plus']?.distribute?.plugins) {
+            // 确保目标文件中有必要的对象结构
+            if (!distManifest.plus)
+              distManifest.plus = {}
+            if (!distManifest.plus.distribute)
+              distManifest.plus.distribute = {}
+
+            // 复制 plugins 内容
+            distManifest.plus.distribute.plugins = srcManifest['app-plus'].distribute.plugins
+
+            // 写入更新后的内容
+            fs.writeFileSync(distAppPath, JSON.stringify(distManifest, null, 2))
+            console.log('✅ Manifest plugins 同步成功')
+          }
+        }
+        catch (error) {
+          console.error('❌ 同步 manifest plugins 失败:', error)
+        }
+      },
+    },
+  }
+}

+ 17 - 6
vite.config.ts

@@ -25,6 +25,8 @@ import AutoImport from 'unplugin-auto-import/vite'
 import { defineConfig, loadEnv } from 'vite'
 import ViteRestart from 'vite-plugin-restart'
 import openDevTools from './scripts/open-dev-tools'
+import { createCopyNativeResourcesPlugin } from './vite-plugins/copy-native-resources'
+import syncManifestPlugin from './vite-plugins/sync-manifest-plugins'
 
 // https://vitejs.dev/config/
 export default defineConfig(({ command, mode }) => {
@@ -54,6 +56,7 @@ export default defineConfig(({ command, mode }) => {
     VITE_APP_PUBLIC_BASE,
     VITE_APP_PROXY_ENABLE,
     VITE_APP_PROXY_PREFIX,
+    VITE_COPY_NATIVE_RES_ENABLE,
   } = env
   console.log('环境变量 env -> ', env)
 
@@ -63,9 +66,12 @@ export default defineConfig(({ command, mode }) => {
     plugins: [
       UniPages({
         exclude: ['**/components/**/**.*'],
-        // homePage 通过 vue 文件的 route-block 的type="home"来设定
-        // pages 目录为 src/pages,分包目录不能配置在pages目录下
-        subPackages: ['src/pages-sub'], // 是个数组,可以配置多个,但是不能为pages里面的目录
+        // pages 目录为 src/pages,分包目录不能配置在pages目录下!!
+        // 是个数组,可以配置多个,但是不能为pages里面的目录!!
+        subPackages: [
+          'src/pages-fg', // 这个是相对必要的路由,尽量留着(登录页、注册页、404页等)
+          'src/pages-sub', // 这个多为示例代码,参考用的,开发完后注释掉即可(或者直接删除)
+        ],
         dts: 'src/types/uni-pages.d.ts',
       }),
       UniLayouts(),
@@ -123,8 +129,14 @@ export default defineConfig(({ command, mode }) => {
         gzipSize: true,
         brotliSize: true,
       }),
-      // 只有在 app 平台时才启用 copyNativeRes 插件
-      // UNI_PLATFORM === 'app' && copyNativeRes(),
+      // 原生插件资源复制插件 - 仅在 app 平台且启用时生效
+      createCopyNativeResourcesPlugin(
+        UNI_PLATFORM === 'app' && VITE_COPY_NATIVE_RES_ENABLE === 'true',
+        {
+          verbose: mode === 'development', // 开发模式显示详细日志
+        },
+      ),
+      syncManifestPlugin(),
       Components({
         extensions: ['vue'],
         deep: true, // 是否递归扫描子目录,
@@ -138,7 +150,6 @@ export default defineConfig(({ command, mode }) => {
       openDevTools(),
     ],
     define: {
-      __UNI_PLATFORM__: JSON.stringify(UNI_PLATFORM),
       __VITE_APP_PROXY__: JSON.stringify(VITE_APP_PROXY_ENABLE),
     },
     css: {