Răsfoiți Sursa

Merge branch 'dev' of haiyang/couponCenter_mpapp into master

haiyang 1 săptămână în urmă
părinte
comite
13d242e0d0

+ 35 - 3
README.md

@@ -1,6 +1,38 @@
+# 券中心微信小程序
 
-<h1 align="center">
-  券中心微信小程序
-</h1>
+## 环境要求
 
+- Node.js >= 20
+- pnpm >= 9
+- 微信开发者工具 (用于小程序开发和调试)
+
+## 安装和启动
+
+### 安装依赖
+
+```bash
+pnpm install
+```
+
+## 注意事项
+
+1. **微信开发者工具配置**:
+   - 确保微信开发者工具已启用服务端口(设置 -> 安全 -> 服务端口)
+   - 开发环境使用 `dist/dev/mp-weixin` 目录
+   - 生产构建使用 `dist/build/mp-weixin` 目录
+
+2. **环境变量**:
+   - 开发环境变量配置在 `env/.env.development`
+   - 生产环境变量配置在 `env/.env.production`
+   - 所有环境变量必须以 `VITE_` 为前缀
+
+3. **项目配置**:
+   - 页面路由配置在 `src/pages/` 目录下(约定式路由)
+   - 应用清单配置在 `manifest.config.ts`
+   - Vite 构建配置在 `vite.config.ts`
+
+4. **开发流程**:
+   - 先执行 `pnpm dev` 启动 H5 服务
+   - 再执行 `pnpm dev:mp` 启动微信小程序开发
+   - 代码修改后会自动热更新到小程序开发者工具
 

+ 2 - 1
env/.env

@@ -10,7 +10,6 @@ VITE_WX_APPID = 'wx23776edbfe90d642'
 VITE_APP_PUBLIC_BASE=/
 
 # 后台请求地址
-#VITE_SERVER_BASEURL = 'http://192.168.1.28:8082/jeecg-boot'
 VITE_SERVER_BASEURL = 'https://life.baoxianzhanggui.com/locallive-pro-java'
 # 后台静态资源请求地址
 VITE_SERVER_RESOURCE_BASEURL ='/sys/common/static'
@@ -32,3 +31,5 @@ VITE_AUTH_MODE = 'single'
 # 原生插件资源复制开关,控制是否启用 copy-native-resources 插件
 VITE_COPY_NATIVE_RES_ENABLE = false
 
+#微信开发者工具项目路径
+VITE_WECHAT_DEV_TOOL_PROJECT_PATH = 'E:\\developmentKit\\WeChatDeveloperTools\\微信web开发者工具'

+ 1 - 0
package.json

@@ -163,6 +163,7 @@
     "@vue/tsconfig": "^0.1.3",
     "autoprefixer": "^10.4.20",
     "cross-env": "^10.0.0",
+    "dotenv": "^17.2.3",
     "eslint": "^9.31.0",
     "eslint-plugin-format": "^1.0.1",
     "husky": "^9.1.7",

+ 66 - 56
scripts/open-dev-tools.js

@@ -2,82 +2,92 @@ import { exec } from 'node:child_process'
 import fs from 'node:fs'
 import path from 'node:path'
 import process from 'node:process'
+import dotenv from 'dotenv'
+
+dotenv.config({ path: path.resolve(process.cwd(), 'env/.env') })
 
 /**
  * 打开开发者工具
  */
 function _openDevTools() {
-  const platform = process.platform // darwin, win32, linux
-  const { UNI_PLATFORM } = process.env //  mp-weixin, mp-alipay
+    const platform = process.platform // darwin, win32, linux
+    const { UNI_PLATFORM } = process.env //  mp-weixin, mp-alipay
 
-  const uniPlatformText = 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}`)
+    // 项目路径(构建输出目录)
+    const projectPath = path.resolve(process.cwd(), `dist/dev/${UNI_PLATFORM}`)
 
-  // 检查构建输出目录是否存在
-  if (!fs.existsSync(projectPath)) {
-    console.log(`❌ ${uniPlatformText}构建目录不存在:`, projectPath)
-    return
-  }
+    // 检查构建输出目录是否存在
+    if (!fs.existsSync(projectPath)) {
+        console.log(`❌ ${uniPlatformText}构建目录不存在:`, projectPath)
+        return
+    }
 
-  console.log(`🚀 正在打开${uniPlatformText}开发者工具...`)
+    console.log(`🚀 正在打开${uniPlatformText}开发者工具...`)
 
-  // 根据不同操作系统执行不同命令
-  let command = ''
+    // 根据不同操作系统执行不同命令
+    let command = ''
 
-  if (platform === 'darwin') {
-    // macOS
-    if (UNI_PLATFORM === 'mp-weixin') {
-      command = `/Applications/wechatwebdevtools.app/Contents/MacOS/cli -o "${projectPath}"`
+    if (platform === 'darwin') {
+        // macOS
+        if (UNI_PLATFORM === 'mp-weixin') {
+            command = `/Applications/wechatwebdevtools.app/Contents/MacOS/cli -o --project "${projectPath}"`
+        }
+        else if (UNI_PLATFORM === 'mp-alipay') {
+            command = `/Applications/小程序开发者工具.app/Contents/MacOS/小程序开发者工具 --p "${projectPath}"`
+        }
     }
-    else if (UNI_PLATFORM === 'mp-alipay') {
-      command = `/Applications/小程序开发者工具.app/Contents/MacOS/小程序开发者工具 --p "${projectPath}"`
+    else if (platform === 'win32' || platform === 'win64') {
+        // Windows
+        if (UNI_PLATFORM === 'mp-weixin') {
+            // 获取微信开发者工具路径,添加默认值或错误处理
+            const wechatDevToolPath = process.env.VITE_WECHAT_DEV_TOOL_PROJECT_PATH || 'C:\\Program Files (x86)\\Tencent\\微信web开发者工具'
+            if (!wechatDevToolPath) {
+                console.log('❌ 未配置微信开发者工具路径,请在 .env 文件中设置 VITE_WECHAT_DEV_TOOL_PROJECT_PATH')
+                return
+            }
+            const cliPath = path.join(wechatDevToolPath, 'cli.bat')
+            command = `${cliPath} open --project "${projectPath}"`
+        }
     }
-  }
-  else if (platform === 'win32' || platform === 'win64') {
-    // Windows
-    if (UNI_PLATFORM === 'mp-weixin') {
-      command = `E:\developmentKit\WeChatDeveloperTools\微信web开发者工具\cli.bat -o "${projectPath}"`
+    else {
+        // Linux 或其他系统
+        console.log('❌ 当前系统不支持自动打开微信开发者工具')
+        return
     }
-  }
-  else {
-    // Linux 或其他系统
-    console.log('❌ 当前系统不支持自动打开微信开发者工具')
-    return
-  }
 
-  exec(command, (error, stdout, stderr) => {
-    if (error) {
-      console.log(`❌ 打开${uniPlatformText}开发者工具失败:`, error.message)
-      console.log(`💡 请确保${uniPlatformText}开发者工具服务端口已启用`)
-      console.log(`💡 可以手动打开${uniPlatformText}开发者工具并导入项目:`, projectPath)
-      return
-    }
+    exec(command, (error, stdout, stderr) => {
+        if (error) {
+            console.log(`❌ 打开${uniPlatformText}开发者工具失败:`, error.message)
+            console.log(`💡 请确保${uniPlatformText}开发者工具服务端口已启用`)
+            console.log(`💡 可以手动打开${uniPlatformText}开发者工具并导入项目:`, projectPath)
+            return
+        }
 
-    if (stderr) {
-      console.log('⚠️ 警告:', stderr)
-    }
+        if (stderr) {
+            console.log('⚠️ 警告:', stderr)
+        }
 
-    console.log(`✅ ${uniPlatformText}开发者工具已打开`)
+        console.log(`✅ ${uniPlatformText}开发者工具已打开`)
 
-    if (stdout) {
-      console.log(stdout)
-    }
-  })
+        if (stdout) {
+            console.log(stdout)
+        }
+    })
 }
 
 export default function openDevTools() {
-  // 首次构建标记
-  let isFirstBuild = true
+    // 首次构建标记
+    let isFirstBuild = true
 
-  return {
-    name: 'uni-devtools',
-    writeBundle() {
-      if (isFirstBuild && process.env.UNI_PLATFORM?.includes('mp')) {
-        isFirstBuild = false
-        _openDevTools()
-      }
-    },
-  }
+    return {
+        name: 'uni-devtools',
+        writeBundle() {
+            if (isFirstBuild && process.env.UNI_PLATFORM?.includes('mp')) {
+                isFirstBuild = false
+                _openDevTools()
+            }
+        },
+    }
 }

+ 1 - 1
src/App.ku.vue

@@ -65,7 +65,7 @@ defineExpose({
         <up-no-network />
 
         <KuRootView />
-        <up-loading-page :loading="loadingStore.isLoading" z-index="1000" size="50" />
+        <up-loading-page :loading="loadingStore.isLoading" :z-index="1000" size="50" />
         <FgTabbar v-if="isCurrentPageTabbar" />
     </view>
 </template>

+ 0 - 1
src/api/login.ts

@@ -1,5 +1,4 @@
 import type { IAuthLoginRes, ICaptcha, IDoubleTokenRes, IUpdateInfo, IUpdatePassword, IUserInfoRes } from './types/login'
-// import { http } from '@/http/http'
 import { http } from '@/http/alova'
 
 /**

+ 6 - 1
src/api/me.ts

@@ -25,7 +25,12 @@ export function couponIssuerApplyByAdd(couponIssuerApplyByAddForm: CouponIssuerA
 
 // 更新用户信息
 export function updateUserInfo(userInfo) {
-    return http.Post('/couponCenter/APP/couponCenterUser/updateInformation', userInfo)
+    return http.Post('/couponCenter/APP/couponCenterUser/updateInformation', userInfo, {
+        meta: {
+            showLoading: true,
+            loadingMask: true,
+        }
+    })
 }
 
 // 获取我的页面中分享统计数据,包括今日分享数、累计分享数、累计团员数

+ 5 - 6
src/components/discountCoupon.vue

@@ -59,10 +59,10 @@ const { shareCoupon, ...shareHooks } = useCouponShare()
 async function handleShareCoupon() {
     // 确保有优惠券数据
     if (coupon.value.templateId) {
-        uni.showLoading({
-            title: '获取分享链接中...',
-            mask: true,
-        })
+        // uni.showLoading({
+        //     title: '获取分享链接中...',
+        //     mask: true,
+        // })
         try {
             const shareLink = await shareCoupon(couponTemplateId.value)
             if (shareLink) {
@@ -74,7 +74,7 @@ async function handleShareCoupon() {
             }
         }
         finally {
-            uni.hideLoading()
+            // uni.hideLoading()
         }
     }
 }
@@ -226,7 +226,6 @@ function handleCloseShareModal() {
                 color: #666666;
                 line-height: 18rpx;
                 padding-left: 1rem;
-                
             }
         }
 

+ 5 - 5
src/components/spendAndSaveCoupon.vue

@@ -27,10 +27,10 @@ const { shareCoupon, ...shareHooks } = useCouponShare()
 async function handleShareCoupon() {
     // 确保有优惠券数据
     if (coupon.value.templateId) {
-        uni.showLoading({
-            title: '获取分享链接中...',
-            mask: true,
-        })
+        // uni.showLoading({
+        //     title: '获取分享链接中...',
+        //     mask: true,
+        // })
         try {
             const shareLink = await shareCoupon(couponTemplateId.value)
             if (shareLink) {
@@ -39,7 +39,7 @@ async function handleShareCoupon() {
             }
         }
         finally {
-            uni.hideLoading()
+            // uni.hideLoading()
         }
     }
 }

+ 5 - 5
src/components/spendAndSaveCoupon_large.vue

@@ -56,10 +56,10 @@ const { shareCoupon, ...shareHooks } = useCouponShare()
 async function handleShareCoupon() {
     // 确保有优惠券数据
     if (coupon.value.templateId) {
-        uni.showLoading({
-            title: '获取分享链接中...',
-            mask: true,
-        })
+        // uni.showLoading({
+        //     title: '获取分享链接中...',
+        //     mask: true,
+        // })
         try {
             const shareLink = await shareCoupon(couponTemplateId.value)
             if (shareLink) {
@@ -71,7 +71,7 @@ async function handleShareCoupon() {
             }
         }
         finally {
-            uni.hideLoading()
+            // uni.hideLoading()
         }
     }
 }

+ 0 - 13
src/http/README.md

@@ -1,13 +0,0 @@
-# 请求库
-
-目前unibest支持3种请求库:
-- 菲鸽简单封装的 `简单版本http`,路径(src/http/http.ts),对应的示例在 src/api/foo.ts
-- `alova 的 http`,路径(src/http/alova.ts),对应的示例在 src/api/foo-alova.ts
-- `vue-query`, 路径(src/http/vue-query.ts), 目前主要用在自动生成接口,详情看(https://unibest.tech/base/17-generate),示例在 src/service/app 文件夹
-
-## 如何选择
-如果您以前用过 alova 或者 vue-query,可以优先使用您熟悉的。
-如果您的项目简单,简单版本的http 就够了,也不会增加包体积。(发版的时候可以去掉alova和vue-query,如果没有超过包体积,留着也无所谓 ^_^)
-
-## roadmap
-菲鸽最近在优化脚手架,后续可以选择是否使用第三方的请求库,以及选择什么请求库。还在开发中,大概月底出来(8月31号)。

+ 26 - 3
src/http/alova.ts

@@ -54,7 +54,6 @@ const alovaInstance = createAlova({
     // timeout: 7000,
     statesHook: VueHook,
     cacheFor: null,
-
     beforeRequest: (method) => {
         // 设置默认 Content-Type
         method.config.headers = {
@@ -80,6 +79,15 @@ const alovaInstance = createAlova({
             method.config.headers.AppType = '7'
             // method.config.headers.token = token;
         }
+        const isShowLoading = !!config.meta?.showLoading
+        if (isShowLoading) {
+            const loadingText = config.meta?.loadingText || ''
+            const loadingMask = !!config.meta?.loadingMask
+            uni.showLoading({
+                title: loadingText,
+                mask: loadingMask,
+            })
+        }
 
         // 处理动态域名
         if (config.meta?.domain) {
@@ -98,6 +106,12 @@ const alovaInstance = createAlova({
                 errMsg,
             } = response as UniNamespace.RequestSuccessCallbackResult
 
+            // 关闭loading
+            const isShowLoading = !!config.meta?.showLoading
+            if (isShowLoading) {
+                uni.hideLoading()
+            }
+
             // 处理特殊请求类型(上传/下载)
             if (requestType === 'upload' || requestType === 'download') {
                 return response
@@ -168,23 +182,32 @@ const alovaInstance = createAlova({
         },
         // 添加网络错误处理
         onError: (error, method) => {
+            const { config } = method
+
             // 处理网络错误(断网、超时、DNS解析失败等)
             console.error('网络错误:', error)
 
+            // 关闭loading
+            const isShowLoading = !!config.meta?.showLoading
+            if (isShowLoading) {
+                uni.hideLoading()
+            }
+
             // 避免重复提示
             if (error.message?.includes('HTTP请求错误') || error.message?.includes('请求错误[')) {
                 return
             }
 
-            let errorMessage = '网络错误,请检查网络连接'
+            let errorMessage = '网络正在迷路中'
             if (error.message?.includes('timeout')) {
-                errorMessage = '请求超时,请稍后重试'
+                errorMessage = '服务器好像睡着了'
             }
 
             uni.showToast({
                 title: errorMessage,
                 icon: 'error',
             })
+            throw new Error(errorMessage)
         }
     },
 })

+ 0 - 200
src/http/http.ts

@@ -1,200 +0,0 @@
-import type { IDoubleTokenRes } from '@/api/types/login'
-import type { CustomRequestOptions, IResponse } from '@/http/types'
-import { nextTick } from 'vue'
-import { useTokenStore } from '@/store/token'
-import { isDoubleTokenMode } from '@/utils'
-import { toLoginPage } from '@/utils/toLoginPage'
-import { ResultEnum } from './tools/enum'
-
-// 刷新 token 状态管理
-let refreshing = false // 防止重复刷新 token 标识
-let taskQueue: (() => void)[] = [] // 刷新 token 请求队列
-
-export function http<T>(options: CustomRequestOptions) {
-    // 1. 返回 Promise 对象
-    return new Promise<T>((resolve, reject) => {
-        uni.request({
-            ...options,
-            dataType: 'json',
-            // #ifndef MP-WEIXIN
-            responseType: 'json',
-            // #endif
-            // 响应成功
-            success: async (res) => {
-                console.log('http-success: ', res)
-                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策略,清理用户信息,跳转到登录页
-                        tokenStore.logout()
-                        toLoginPage()
-                        return reject(res)
-                    }
-
-                    /* -------- 无感刷新 token ----------- */
-                    const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
-                    // token 失效的,且有刷新 token 的,才放到请求队列里
-                    if (refreshToken) {
-                        taskQueue.push(() => {
-                            resolve(http<T>(options))
-                        })
-                    }
-
-                    // 如果有 refreshToken 且未在刷新中,发起刷新 token 请求
-                    if (refreshToken && !refreshing) {
-                        refreshing = true
-                        try {
-                            // 发起刷新 token 请求(使用 store 的 refreshToken 方法)
-                            await tokenStore.refreshToken()
-                            // 刷新 token 成功
-                            refreshing = false
-                            nextTick(() => {
-                                // 关闭其他弹窗
-                                uni.hideToast()
-                                uni.showToast({
-                                    title: 'token 刷新成功',
-                                    icon: 'none',
-                                })
-                            })
-                            // 将任务队列的所有任务重新请求
-                            taskQueue.forEach(task => task())
-                        }
-                        catch (refreshErr) {
-                            console.error('刷新 token 失败:', refreshErr)
-                            refreshing = false
-                            // 刷新 token 失败,跳转到登录页
-                            nextTick(() => {
-                                // 关闭其他弹窗
-                                uni.hideToast()
-                                uni.showToast({
-                                    title: '登录已过期,请重新登录',
-                                    icon: 'none',
-                                })
-                            })
-                            // 清除用户信息
-                            await tokenStore.logout()
-                            // 跳转到登录页
-                            setTimeout(() => {
-                                toLoginPage()
-                            }, 2000)
-                        }
-                        finally {
-                            // 不管刷新 token 成功与否,都清空任务队列
-                            taskQueue = []
-                        }
-                    }
-
-                    return reject(res)
-                }
-
-                // 处理其他成功状态(HTTP状态码200-299)
-                if (res.statusCode >= 200 && res.statusCode < 300) {
-                    // 处理业务逻辑错误
-                    if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) {
-                        uni.showToast({
-                            icon: 'none',
-                            title: responseData.msg || responseData.message || '请求错误',
-                        })
-                    }
-                    return resolve(responseData.result)
-                }
-
-                // 处理其他错误
-                !options.hideErrorToast
-                    && uni.showToast({
-                        icon: 'none',
-                        title: (res.data as any).msg || '请求错误',
-                    })
-                reject(res)
-            },
-            // 响应失败
-            fail(err) {
-                uni.showToast({
-                    icon: 'none',
-                    title: '网络错误,换个网络试试',
-                })
-                reject(err)
-            },
-        })
-    })
-}
-
-/**
- * GET 请求
- * @param url 后台地址
- * @param query 请求query参数
- * @param header 请求头,默认为json格式
- * @returns
- */
-export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
-    return http<T>({
-        url,
-        query,
-        method: 'GET',
-        header,
-        ...options,
-    })
-}
-
-/**
- * POST 请求
- * @param url 后台地址
- * @param data 请求body参数
- * @param query 请求query参数,post请求也支持query,很多微信接口都需要
- * @param header 请求头,默认为json格式
- * @returns
- */
-export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
-    return http<T>({
-        url,
-        query,
-        data,
-        method: 'POST',
-        header,
-        ...options,
-    })
-}
-/**
- * PUT 请求
- */
-export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
-    return http<T>({
-        url,
-        data,
-        query,
-        method: 'PUT',
-        header,
-        ...options,
-    })
-}
-
-/**
- * DELETE 请求(无请求体,仅 query)
- */
-export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
-    return http<T>({
-        url,
-        query,
-        method: 'DELETE',
-        header,
-        ...options,
-    })
-}
-
-// 支持与 axios 类似的API调用
-http.get = httpGet
-http.post = httpPost
-http.put = httpPut
-http.delete = httpDelete
-
-// 支持与 alovaJS 类似的API调用
-http.Get = httpGet
-http.Post = httpPost
-http.Put = httpPut
-http.Delete = httpDelete

+ 1 - 7
src/pages-A/settingPage/index.vue

@@ -40,12 +40,8 @@ const rules = reactive({
 })
 
 async function changeNickName() {
-    uni.showLoading({
-        mask: true,
-    })
     try {
         const result = await updateUserInfo(formData)
-        uni.hideLoading()
         if (result) {
             userStore.setUserNickName(formData.nickname)
             nickNameVisible.value = false
@@ -62,7 +58,6 @@ async function changeNickName() {
         }
     }
     catch (error) {
-        uni.hideLoading()
         uni.showToast({
             title: '昵称更新失败',
             icon: 'error',
@@ -101,7 +96,7 @@ function onChooseAvatar(e) {
                 const newAvatarUrl = `${import.meta.env.VITE_SERVER_BASEURL}/${res.message}`
                 try {
                     const result = await updateUserInfo({ avatarUrl: newAvatarUrl })
-                    uni.hideLoading()
+
                     if (result) {
                         userStore.setUserAvatar(newAvatarUrl)
                         uni.showToast({
@@ -117,7 +112,6 @@ function onChooseAvatar(e) {
                     }
                 }
                 catch (error) {
-                    uni.hideLoading()
                     uni.showToast({
                         title: '头像更新失败',
                         icon: 'error',

+ 20 - 11
src/pages-A/unlockRewards/index.vue

@@ -83,13 +83,13 @@ function submitPay() {
 <template>
     <view class="profile-container">
         <!-- 自定义导航栏 -->
-        <CustomNavigationBar title="解锁收益" background-color="transparent" />
+        <CustomNavigationBar title="解锁收益" background-color="transparent" :is-shadow="false" text-color="#fff"
+            back-icon-color="#fff" />
 
         <!-- 顶部区域 -->
         <view class="income-header"
-            :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')}) no-repeat center center`, backgroundSize: 'cover' }">
-            <view class="income-header-avatar-info"
-                :style="{ paddingTop: `${safeAreaInsets.top + menuButtonInfo.height + 12}px` }">
+            :style="{ background: `url(${getImageUrl('@img/income/income-bg.png')}) no-repeat center center`, backgroundSize: '100% 100%', paddingTop: `${safeAreaInsets.top}px` }">
+            <view class="income-header-avatar-info" :style="{ paddingTop: `calc(${menuButtonInfo.height}px + 56rpx)` }">
                 <view class="income-header-balance">
                     锁定余额(元)
                 </view>
@@ -129,8 +129,13 @@ function submitPay() {
                 充值解锁金额
             </view>
             <view class="income-header-menu-input">
-                <text class="income-header-menu-input-symbol">¥</text>
-                <u-input v-model="unlockAmount" type="number" border="none" />
+                <!-- <text class="income-header-menu-input-symbol">¥</text> -->
+                <u-input v-model="unlockAmount" cursor-color="#ed6b66" cursor-spacing="0" type="digit" :border="false"
+                    :adjust-position="false" style="height: 120rpx;">
+                    <template #prefix>
+                        <text class="income-header-menu-input-symbol">¥</text>
+                    </template>
+                </u-input>
             </view>
         </view>
         <view class="income-header-menu-btn" @click="submitPay">
@@ -141,8 +146,8 @@ function submitPay() {
 
 <style lang="scss" scoped>
 ::v-deep .u-input__content__field-wrapper__field {
-    height: 80rpx !important;
-    font-size: 80rpx !important;
+    height: 120rpx !important;
+    font-size: 65rpx !important;
 }
 
 .profile-container {
@@ -152,7 +157,7 @@ function submitPay() {
 
     //   padding-top: 44px; /* 为固定导航栏留出空间 */
     .income-header {
-        height: 525rpx;
+        height: 526rpx;
 
         .income-header-avatar-info {
             display: flex;
@@ -206,7 +211,7 @@ function submitPay() {
             display: flex;
             background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
             border-radius: 10rpx;
-            margin: 47rpx 24rpx 0 24rpx;
+            margin: 64rpx 24rpx 0 24rpx;
 
             .income-header-tips-item {
                 flex: 1;
@@ -249,7 +254,7 @@ function submitPay() {
         background: #ffffff;
         border-radius: 10rpx;
         margin: 24rpx 24rpx 0;
-        margin-top: -30rpx;
+        margin-top: -45rpx;
 
         .income-header-menu-title {
             font-weight: 400;
@@ -264,12 +269,16 @@ function submitPay() {
             align-items: flex-end;
             gap: 17rpx;
             padding: 66rpx 20rpx;
+            height: 120rpx;
 
             .income-header-menu-input-symbol {
                 font-weight: 400;
                 font-size: 60rpx;
                 color: #333333;
+                padding: 6px 0;
             }
+
+            :deep(.u-input) {}
         }
     }
 

+ 58 - 7
src/pages-fg/login/login.vue

@@ -16,6 +16,8 @@ definePage({
 
 const tokenStore = useTokenStore()
 const agreeToTerms = ref(false)
+const popoverRef = ref(null)
+const showPopover = ref(false)
 
 // 跳转至用户服务协议
 function goToUserServiceAgreement() {
@@ -31,6 +33,11 @@ function goToPrivacyPolicy() {
     })
 }
 
+function checkAgreement() {
+    agreeToTerms.value = !agreeToTerms.value
+    showPopover.value = false
+}
+
 async function doLogin() {
     if (tokenStore.hasLogin) {
         uni.navigateBack()
@@ -39,6 +46,7 @@ async function doLogin() {
 
     // 检查是否同意协议
     if (!agreeToTerms.value) {
+        showPopover.value = true
         uni.showToast({
             title: '请先阅读并同意用户服务协议和隐私政策',
             icon: 'none',
@@ -57,7 +65,6 @@ async function doLogin() {
         const pages = getCurrentPages()
         const currentPage = pages[pages.length - 1]
         const redirect = currentPage?.options?.redirect
-        uni.hideLoading()
         if (redirect) {
             const redirectUrl = decodeURIComponent(redirect)
             if (isPageTabbar(redirectUrl)) {
@@ -78,12 +85,11 @@ async function doLogin() {
         }
     }
     catch (error) {
-        uni.hideLoading()
         console.log('登录失败', error)
-        uni.showToast({
-            title: '登录失败,请稍后重试',
-            icon: 'none',
-        })
+        // uni.showToast({
+        //     title: '登录失败,请稍后重试',
+        //     icon: 'none',
+        // })
     }
 }
 </script>
@@ -107,7 +113,12 @@ async function doLogin() {
         <view class="agreement-section">
             <view class="agreement-text">
                 <view class="checkbox-container">
-                    <checkbox :checked="agreeToTerms" @tap="agreeToTerms = !agreeToTerms" />
+                    <transition name="transition-popover">
+                        <view v-if="showPopover" class="custom-popover">
+                            <text class="popover-text">请阅读并勾选用户协议</text>
+                        </view>
+                    </transition>
+                    <checkbox :checked="agreeToTerms" @tap="checkAgreement" />
                     <text>我已阅读并同意</text>
                     <text class="link" @click="goToUserServiceAgreement">《用户服务协议》</text>
                     <text>和</text>
@@ -175,6 +186,46 @@ async function doLogin() {
             align-items: center;
             justify-content: center;
             flex-wrap: wrap;
+            position: relative;
+
+            .custom-popover {
+                position: absolute;
+                bottom: 100%;
+                // left: 50%;
+                transform: translate(-4px, -4px);
+                background-color: rgba(51, 51, 51, 0.8);
+                color: #fff;
+                padding: 8rpx 16rpx;
+                border-radius: 4rpx;
+                font-size: 24rpx;
+                margin-bottom: 8rpx;
+                white-space: nowrap;
+
+                // 添加三角形指示器
+                &::after {
+                    content: '';
+                    position: absolute;
+                    top: 100%;
+                    left: 5%;
+
+                    width: 0;
+                    height: 0;
+                    border-left: 16rpx solid transparent;
+                    border-right: 16rpx solid transparent;
+                    border-top: 16rpx solid rgba(51, 51, 51, 0.8);
+                }
+            }
+
+            .custom-popover.transition-popover-enter-active,
+            .custom-popover.transition-popover-leave-active {
+                transition: all 1.3s ease;
+            }
+
+            .custom-popover.transition-popover-enter-from,
+            .custom-popover.transition-popover-leave-to {
+                opacity: 0;
+                transform: translate(-74%, 4px);
+            }
         }
 
         checkbox {

+ 39 - 13
src/pages/home/home.vue

@@ -68,8 +68,13 @@ onShareAppMessage(() => {
 })
 
 // 分享到朋友圈生命周期函数
-onShareTimeline(async () => {
-    return await getTimelineShareConfig()
+onShareTimeline(() => {
+    const promise = new Promise((resolve, reject) => {
+        getTimelineShareConfig().then(resolve).catch(reject)
+    })
+    return {
+        promise,
+    }
 })
 
 // #endif
@@ -80,8 +85,6 @@ onShow((options) => {
     // 登录后查询收益数据
     if (hasLogin.value) {
         Promise.allSettled([getAccountCountRequest(), getCouponSituationRequest()])
-        // getAccountCountRequest()
-        // getCouponSituationRequest()
     }
 })
 
@@ -128,12 +131,13 @@ function toCouponRedemptionList(state) {
 
 async function onRefresh() {
     refreshing.value = true
-    await userStore.fetchUserInfo()
     await couponStore.getCouponListByType()
-
-    setTimeout(() => {
-        refreshing.value = false
-    }, 1000)
+    if (hasLogin.value) {
+        await userStore.fetchUserInfo()
+        await getAccountCountRequest()
+        await getCouponSituationRequest()
+    }
+    refreshing.value = false
 }
 </script>
 
@@ -212,10 +216,13 @@ async function onRefresh() {
                         <spend-and-save-coupon :coupon="item" />
                     </template>
                     <view v-if="loading" class="coupon-content-loading">
-                        <up-loading-icon text-color="#333" color="#ed6b66" text="加载中" text-size="18" />
+                        <up-loading-icon text-color="#666" color="#ed6b66" text="加载中" text-size="18" />
+                    </view>
+                    <view v-if="couponList.length === 0 && !loading" class="coupon-content-empty">
+                        <up-empty mode="coupon" text="暂无可分享的满减券" text-color="#666" icon-color="#ed6b66" />
                     </view>
                 </view>
-                <view class="home-header-coupon-btn">
+                <view v-show="couponList.length !== 0 && !loading" class="home-header-coupon-btn">
                     <up-button class="home-header-coupon-btn-text" text="查看更多优惠券"
                         color="linear-gradient(0deg, #FFE8CE 0%, #FBB8A0 100%)" @click="toSpendAndSaveCouponList" />
                 </view>
@@ -231,7 +238,8 @@ async function onRefresh() {
                     <view class="home-coupon-title-des">
                         分享折扣&nbsp;&nbsp;立享优惠
                     </view>
-                    <view class="home-coupon-title-more" @click="toDiscountCouponList">
+                    <view :style="{ display: discountVoucherList.length !== 0 && !loading ? 'flex' : 'none' }"
+                        class="home-coupon-title-more" @click="toDiscountCouponList">
                         <view class="home-coupon-title-more-text">
                             更多
                         </view>
@@ -243,7 +251,10 @@ async function onRefresh() {
                         <discount-coupon :coupon="item" />
                     </template>
                     <view v-if="loading" class="home-coupon-content-loading">
-                        <up-loading-icon text-color="#333" color="#ed6b66" text="加载中" text-size="18" />
+                        <up-loading-icon text-color="#666" color="#ed6b66" text="加载中" text-size="18" />
+                    </view>
+                    <view v-if="discountVoucherList.length === 0 && !loading" class="home-coupon-content-empty">
+                        <up-empty mode="coupon" text="暂无可分享的折扣券" text-color="#666" icon-color="#ed6b66" />
                     </view>
                 </view>
             </view>
@@ -256,6 +267,7 @@ async function onRefresh() {
     background-color: #f5f5f5;
     line-height: 1;
     position: relative;
+    min-height: calc(100vh - 100rpx - var(--safe-area-inset-bottom));
 
     .home-header {
         height: 550rpx;
@@ -480,6 +492,13 @@ async function onRefresh() {
                 background: rgba(255, 232, 206, 0.5);
                 z-index: 1;
             }
+
+            .coupon-content-empty {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                width: 100%;
+            }
         }
 
         .home-header-coupon-btn {
@@ -563,6 +582,13 @@ async function onRefresh() {
                 align-self: center;
                 z-index: 1;
             }
+
+            .home-coupon-content-empty {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                width: 100%;
+            }
         }
     }
 }

+ 0 - 6
src/service/index.ts

@@ -1,6 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-export * from './types';
-
-export * from './listAll';
-export * from './info';

+ 0 - 14
src/service/info.ts

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

+ 0 - 18
src/service/listAll.ts

@@ -1,18 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-import request from '@/http/vue-query';
-import { CustomRequestOptions_ } from '@/http/types';
-
-import * as API from './types';
-
-/** 用户列表 GET /user/listAll */
-export function listAllUsingGet({
-  options,
-}: {
-  options?: CustomRequestOptions_;
-}) {
-  return request<API.ListAllUsingGetResponse>('/user/listAll', {
-    method: 'GET',
-    ...(options || {}),
-  });
-}

+ 0 - 29
src/service/types.ts

@@ -1,29 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-
-export type InfoUsingGetResponse = {
-  code: number;
-  msg: string;
-  data: UserItem;
-};
-
-export type InfoUsingGetResponses = {
-  200: InfoUsingGetResponse;
-};
-
-export type ListAllUsingGetResponse = {
-  code: number;
-  msg: string;
-  data: UserItem[];
-};
-
-export type ListAllUsingGetResponses = {
-  200: ListAllUsingGetResponse;
-};
-
-export type UserItem = {
-  userId: number;
-  username: string;
-  nickname: string;
-  avatar: string;
-};

+ 10 - 5
src/store/coupon.ts

@@ -9,11 +9,16 @@ export const useCouponStore = defineStore('coupon', {
     }),
     actions: {
         async getCouponListByType() {
-            this.loading = true
-            const res = await getCouponList()
-            this.loading = false
-            this.discountVoucherList = (res.discountCoupon || []).slice(0, 3)
-            this.couponList = (res.fullReduceCoupon || []).slice(0, 3)
+            try {
+                this.loading = true
+                const res = await getCouponList()
+                this.loading = false
+                this.discountVoucherList = (res.discountCoupon || []).slice(0, 3)
+                this.couponList = (res.fullReduceCoupon || []).slice(0, 3)
+            }
+            catch (error) {
+                this.loading = false
+            }
         }
     }
 })

+ 35 - 35
src/store/token.ts

@@ -61,8 +61,8 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                 * 判断token是否过期
-                                                                                 */
+                                                                                         * 判断token是否过期
+                                                                                         */
         const isTokenExpired = computed(() => {
             if (!tokenInfo.value) {
                 return true
@@ -85,8 +85,8 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                 * 判断refreshToken是否过期
-                                                                                 */
+                                                                                         * 判断refreshToken是否过期
+                                                                                         */
         const isRefreshTokenExpired = computed(() => {
             if (!isDoubleTokenMode)
                 return true
@@ -100,9 +100,9 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                 * 登录成功后处理逻辑
-                                                                                 * @param tokenInfo 登录返回的token信息
-                                                                                 */
+                                                                                         * 登录成功后处理逻辑
+                                                                                         * @param tokenInfo 登录返回的token信息
+                                                                                         */
         async function _postLogin(tokenInfo: IAuthLoginRes) {
             setTokenInfo(tokenInfo)
             const userStore = useUserStore()
@@ -118,12 +118,12 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                 * 用户登录
-                                                                                 * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
-                                                                                 * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
-                                                                                 * @param loginForm 登录参数
-                                                                                 * @returns 登录结果
-                                                                                 */
+                                                                                         * 用户登录
+                                                                                         * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
+                                                                                         * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
+                                                                                         * @param loginForm 登录参数
+                                                                                         * @returns 登录结果
+                                                                                         */
         const login = async (loginForm: ILoginForm) => {
             try {
                 const res = await _login(loginForm)
@@ -147,11 +147,11 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                                                                                                                                             * 微信登录
-                                                                                                                                                                                                                             * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
-                                                                                                                                                                                                                             * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
-                                                                                                                                                                                                                             * @returns 登录结果
-                                                                                                                                                                                                                             */
+                                                                                                                                                                                                                                     * 微信登录
+                                                                                                                                                                                                                                     * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
+                                                                                                                                                                                                                                     * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
+                                                                                                                                                                                                                                     * @returns 登录结果
+                                                                                                                                                                                                                                     */
         const wxLogin = async () => {
             try {
                 // 获取微信小程序登录的code
@@ -175,7 +175,7 @@ export const useTokenStore = defineStore(
             catch (error) {
                 console.error('微信登录失败:', error)
                 uni.showToast({
-                    title: '微信登录失败,请重试',
+                    title: '登录失败啦,换个姿势试试',
                     icon: 'error',
                 })
                 throw error
@@ -183,8 +183,8 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                                                                                                                                             * 退出登录 并 删除用户信息
-                                                                                                                                                                                                                             */
+                                                                                                                                                                                                                                     * 退出登录 并 删除用户信息
+                                                                                                                                                                                                                                     */
         const logout = async () => {
             try {
                 // TODO 实现自己的退出登录逻辑
@@ -207,9 +207,9 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                                                                                                                                             * 刷新token
-                                                                                                                                                                                                                             * @returns 刷新结果
-                                                                                                                                                                                                                             */
+                                                                                                                                                                                                                                     * 刷新token
+                                                                                                                                                                                                                                     * @returns 刷新结果
+                                                                                                                                                                                                                                     */
         const refreshToken = async () => {
             if (!isDoubleTokenMode) {
                 console.error('单token模式不支持刷新token')
@@ -235,10 +235,10 @@ export const useTokenStore = defineStore(
         }
 
         /**
-                                                                                                                                                                                                                             * 获取有效的token
-                                                                                                                                                                                                                             * 注意:在computed中不直接调用异步函数,只做状态判断
-                                                                                                                                                                                                                             * 实际的刷新操作应由调用方处理
-                                                                                                                                                                                                                             */
+                                                                                                                                                                                                                                     * 获取有效的token
+                                                                                                                                                                                                                                     * 注意:在computed中不直接调用异步函数,只做状态判断
+                                                                                                                                                                                                                                     * 实际的刷新操作应由调用方处理
+                                                                                                                                                                                                                                     */
         const getValidToken = computed(() => {
             // token已过期,返回空
             if (isTokenExpired.value) {
@@ -254,8 +254,8 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                                                                                                                                                             * 检查是否有登录信息(不考虑token是否过期)
-                                                                                                                                                                                                                             */
+                                                                                                                                                                                                                                     * 检查是否有登录信息(不考虑token是否过期)
+                                                                                                                                                                                                                                     */
         const hasLoginInfo = computed(() => {
             if (!tokenInfo.value) {
                 return false
@@ -269,17 +269,17 @@ export const useTokenStore = defineStore(
         })
 
         /**
-                                                                                                                                                                             * 检查是否已登录且token有效
-                                                                                                                                                                             */
+                                                                                                                                                                                     * 检查是否已登录且token有效
+                                                                                                                                                                                     */
         const hasValidLogin = computed(() => {
             console.log('hasValidLogin', hasLoginInfo.value && !isTokenExpired.value, hasLoginInfo.value, !isTokenExpired.value)
             return hasLoginInfo.value && !isTokenExpired.value
         })
 
         /**
-                                                                                                                                                                         * 尝试获取有效的token,如果过期且可刷新,则刷新token
-                                                                                                                                                                         * @returns 有效的token或空字符串
-                                                                                                                                                                         */
+                                                                                                                                                                                 * 尝试获取有效的token,如果过期且可刷新,则刷新token
+                                                                                                                                                                                 * @returns 有效的token或空字符串
+                                                                                                                                                                                 */
         const tryGetValidToken = async (): Promise<string> => {
             if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) {
                 try {

+ 101 - 101
uno.config.ts

@@ -1,5 +1,5 @@
 import type {
-  Preset,
+    Preset,
 } from 'unocss'
 import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
 
@@ -8,112 +8,112 @@ import { presetUni } from '@uni-helper/unocss-preset-uni'
 // @see https://unocss.dev/presets/legacy-compat
 import { presetLegacyCompat } from '@unocss/preset-legacy-compat'
 import {
-  defineConfig,
-  presetIcons,
-  transformerDirectives,
-  transformerVariantGroup,
+    defineConfig,
+    presetIcons,
+    transformerDirectives,
+    transformerVariantGroup,
 } from 'unocss'
 
 export default defineConfig({
-  presets: [
-    presetUni({
-      attributify: false,
-    }),
-    presetIcons({
-      scale: 1.2,
-      warn: true,
-      extraProperties: {
-        'display': 'inline-block',
-        'vertical-align': 'middle',
-      },
-      collections: {
-        // 注册本地 SVG 图标集合, 从本地文件系统加载图标
-        // 在 './src/static/my-icons' 目录下的所有 svg 文件将被注册为图标,
-        // my-icons 是图标集合名称,使用 `i-my-icons-图标名` 调用
-        'my-icons': FileSystemIconLoader(
-          './src/static/my-icons',
-          // 可选的,你可以提供一个 transform 回调来更改每个图标
-          (svg) => {
-            let svgStr = svg
+    presets: [
+        presetUni({
+            attributify: false,
+        }),
+        presetIcons({
+            scale: 1.2,
+            warn: true,
+            extraProperties: {
+                'display': 'inline-block',
+                'vertical-align': 'middle',
+            },
+            collections: {
+                // 注册本地 SVG 图标集合, 从本地文件系统加载图标
+                // 在 './src/static/my-icons' 目录下的所有 svg 文件将被注册为图标,
+                // my-icons 是图标集合名称,使用 `i-my-icons-图标名` 调用
+                'my-icons': FileSystemIconLoader(
+                    './src/static/my-icons',
+                    // 可选的,你可以提供一个 transform 回调来更改每个图标
+                    (svg) => {
+                        let svgStr = svg
 
-            // 如果 SVG 文件未定义 `fill` 属性,则默认填充 `currentColor`, 这样图标颜色会继承文本颜色,方便在不同场景下适配
-            svgStr = svgStr.includes('fill="') ? svgStr : svgStr.replace(/^<svg /, '<svg fill="currentColor" ')
+                        // 如果 SVG 文件未定义 `fill` 属性,则默认填充 `currentColor`, 这样图标颜色会继承文本颜色,方便在不同场景下适配
+                        svgStr = svgStr.includes('fill="') ? svgStr : svgStr.replace(/^<svg /, '<svg fill="currentColor" ')
 
-            // 如果 svg 有 width, 和 height 属性,将这些属性改为 1em,否则无法显示图标
-            svgStr = svgStr.replace(/(<svg.*?width=)"(.*?)"/, '$1"1em"').replace(/(<svg.*?height=)"(.*?)"/, '$1"1em"')
+                        // 如果 svg 有 width, 和 height 属性,将这些属性改为 1em,否则无法显示图标
+                        svgStr = svgStr.replace(/(<svg.*?width=)"(.*?)"/, '$1"1em"').replace(/(<svg.*?height=)"(.*?)"/, '$1"1em"')
 
-            return svgStr
-          },
-        ),
-      },
-    }),
-    // TODO: check 是否会有别的影响
-    // 处理低端安卓机的样式问题
-    // 将颜色函数 (rgb()和hsl()) 从空格分隔转换为逗号分隔,更好的兼容性app端,example:
-    // `rgb(255 0 0)` -> `rgb(255, 0, 0)`
-    // `rgba(255 0 0 / 0.5)` -> `rgba(255, 0, 0, 0.5)`
-    presetLegacyCompat({
-      commaStyleColorFunction: true,
-      legacyColorSpace: true, // by QQ4群-量子蔷薇
-      // @菲鸽 unocss 配置中,建议在 presetLegacyCompat 中添加 legacyColorSpace: true,以去除生成的颜色样式中的 in oklch 关键字,现在发现有些渐变色生成不符合预期
-    }) as Preset,
-  ],
-  transformers: [
-    // 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令
-    transformerDirectives(),
-    // 启用 () 分组功能
-    // 支持css class组合,eg: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
-    transformerVariantGroup(),
-  ],
-  shortcuts: [
-    {
-      center: 'flex justify-center items-center',
-    },
-  ],
-  // 动态图标需要在这里配置,或者写在vue页面中注释掉
-  safelist: ['i-carbon-home', 'i-carbon-user', 'i-carbon-chart-line'],
-  rules: [
-    [
-      'p-safe',
-      {
-        padding:
-          'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
-      },
+                        return svgStr
+                    },
+                ),
+            },
+        }),
+        // TODO: check 是否会有别的影响
+        // 处理低端安卓机的样式问题
+        // 将颜色函数 (rgb()和hsl()) 从空格分隔转换为逗号分隔,更好的兼容性app端,example:
+        // `rgb(255 0 0)` -> `rgb(255, 0, 0)`
+        // `rgba(255 0 0 / 0.5)` -> `rgba(255, 0, 0, 0.5)`
+        presetLegacyCompat({
+            commaStyleColorFunction: true,
+            legacyColorSpace: true, // by QQ4群-量子蔷薇
+            // @菲鸽 unocss 配置中,建议在 presetLegacyCompat 中添加 legacyColorSpace: true,以去除生成的颜色样式中的 in oklch 关键字,现在发现有些渐变色生成不符合预期
+        }) as Preset,
     ],
-    ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
-    ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
-  ],
-  theme: {
-    colors: {
-      /** 主题色,用法如: text-primary */
-      primary: 'var(--wot-color-theme,#0957DE)',
-    },
-    fontSize: {
-      /** 提供更小号的字体,用法如:text-2xs */
-      '2xs': ['20rpx', '28rpx'],
-      '3xs': ['18rpx', '26rpx'],
+    transformers: [
+        // 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令
+        transformerDirectives(),
+        // 启用 () 分组功能
+        // 支持css class组合,eg: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
+        transformerVariantGroup(),
+    ],
+    shortcuts: [
+        {
+            center: 'flex justify-center items-center',
+        },
+    ],
+    // 动态图标需要在这里配置,或者写在vue页面中注释掉
+    safelist: ['i-carbon-home', 'i-carbon-user', 'i-carbon-chart-line'],
+    rules: [
+        [
+            'p-safe',
+            {
+                padding:
+                    'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
+            },
+        ],
+        ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
+        ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
+    ],
+    theme: {
+        colors: {
+            /** 主题色,用法如: text-primary */
+            primary: 'var(--wot-color-theme,#0957DE)',
+        },
+        fontSize: {
+            /** 提供更小号的字体,用法如:text-2xs */
+            '2xs': ['20rpx', '28rpx'],
+            '3xs': ['18rpx', '26rpx'],
+        },
     },
-  },
-  // windows 系统会报错:[plugin:unocss:transformers:pre] Cannot overwrite a zero-length range - use append Left or prependRight instead.
-  // 去掉下面的就正常了
-  // content: {
-  //   /**
-  //    * 解决小程序报错 `./app.wxss(78:2814): unexpected unexpected at pos 5198`
-  //    * 为什么同时使用include和exclude?虽然看起来多余,但同时配置两者是一种常见的 `防御性编程` 做法。
-  //      1. 结构变化保障 : 如果未来项目结构发生变化,某些排除目录可能被移动到包含路径下,exclude配置可以确保它们仍被排除
-  //      2. 明确性 : 明确列出要排除的目录使配置意图更加清晰
-  //      3. 性能优化 : 避免处理不必要的文件,提高构建性能
-  //      4. 防止冲突 : 排除第三方库和构建输出目录,避免潜在的CSS冲突
-  //    */
-  //   pipeline: {
-  //     exclude: [
-  //       'node_modules/**/*',
-  //       'public/**/*',
-  //       'dist/**/*',
-  //     ],
-  //     include: [
-  //       './src/**/*',
-  //     ],
-  //   },
-  // },
+    // windows 系统会报错:[plugin:unocss:transformers:pre] Cannot overwrite a zero-length range - use append Left or prependRight instead.
+    // 去掉下面的就正常了
+    // content: {
+    //   /**
+    //    * 解决小程序报错 `./app.wxss(78:2814): unexpected unexpected at pos 5198`
+    //    * 为什么同时使用include和exclude?虽然看起来多余,但同时配置两者是一种常见的 `防御性编程` 做法。
+    //      1. 结构变化保障 : 如果未来项目结构发生变化,某些排除目录可能被移动到包含路径下,exclude配置可以确保它们仍被排除
+    //      2. 明确性 : 明确列出要排除的目录使配置意图更加清晰
+    //      3. 性能优化 : 避免处理不必要的文件,提高构建性能
+    //      4. 防止冲突 : 排除第三方库和构建输出目录,避免潜在的CSS冲突
+    //    */
+    //   pipeline: {
+    //     exclude: [
+    //       'node_modules/**/*',
+    //       'public/**/*',
+    //       'dist/**/*',
+    //     ],
+    //     include: [
+    //       './src/**/*',
+    //     ],
+    //   },
+    // },
 })