diff --git a/ak/config.uts b/ak/config.uts index 952154ee..14cc343b 100644 --- a/ak/config.uts +++ b/ak/config.uts @@ -4,12 +4,12 @@ // IP: 192.168.1.62 // Kong HTTP Port: 8000 //自己的配置自己解开即可 -//export const SUPA_URL: string = 'http://192.168.1.61:18000' -//export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' +export const SUPA_URL: string = 'http://192.168.1.61:18000' +export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' //export const SUPA_URL: string = 'http://192.168.1.62:18000' //export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' -export const SUPA_URL: string = 'http://192.168.1.61:18000' -export const SUPA_KEY: string = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJyb2xlIjogImFub24iLCAiaXNzIjogInN1cGFiYXNlIiwgImlhdCI6IDE3Njk4NDczMzQsICJleHAiOiAyMDg1MjA3MzM0fQ.js-2CS5_cUmf4iVv8aCmmx9iyFsQvLNDbt8YYOngeLU' +// export const SUPA_URL: string = 'http://192.168.1.61:18000' +// export const SUPA_KEY: string = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJyb2xlIjogImFub24iLCAiaXNzIjogInN1cGFiYXNlIiwgImlhdCI6IDE3Njk4NDczMzQsICJleHAiOiAyMDg1MjA3MzM0fQ.js-2CS5_cUmf4iVv8aCmmx9iyFsQvLNDbt8YYOngeLU' // WebSocket 实时连接(内网使用 ws:// 而非 wss://) // export const WS_URL: string = 'ws://192.168.1.61:18000/realtime/v1/websocket' diff --git a/components/supadb/aksupa.uts b/components/supadb/aksupa.uts index 4dc4c28d..2e52d4c1 100644 --- a/components/supadb/aksupa.uts +++ b/components/supadb/aksupa.uts @@ -1,6 +1,8 @@ +// /components/supadb/aksupa.uts import { AkReqResponse, AkReqUploadOptions, AkReq } from '@/uni_modules/ak-req/index.uts' import type { AkReqOptions } from '@/uni_modules/ak-req/index.uts' import { toUniError } from '@/utils/utils.uts' +import { IS_TEST_MODE } from '@/ak/config.uts' export type AkSupaSignInResult = { access_token : string; @@ -805,14 +807,22 @@ export class AkSupa { * { usr_id: { lt: 800 }, name: { ilike: '%foo%' }, status: 'active', age: { gte: 18, lte: 30 } } * 操作符支持 eq, neq, lt, lte, gt, gte, like, ilike, in, is, not, contains, containedBy, range, fts, plfts, phfts, wfts */ -async select(table : string, filter ?: string | null, options ?: AkSupaSelectOptions) : Promise> { - let url = this.baseUrl + '/rest/v1/' + table; - let headers = { - apikey: this.apikey, - 'Content-Type': 'application/json', - Authorization: `Bearer ${AkReq.getToken() ?? ''}` - } as UTSJSONObject; - let params : string[] = []; + async select(table : string, filter ?: string | null, options ?: AkSupaSelectOptions) : Promise> { + let url = this.baseUrl + '/rest/v1/' + table; + + const token = AkReq.getToken() + let headers = { + apikey: this.apikey, + 'Content-Type': 'application/json' + } as UTSJSONObject; + + // 只有在明确有用户 token 的情况下才发送 Authorization + // 否则只带 apikey,这样 Kong 会自动映射到 anon 角色,避免 JWT 校验失败 + if (token != null && token != '') { + headers['Authorization'] = `Bearer ${token}`; + } + + let params : string[] = []; if (options != null) { if (options.columns != null && !(options.columns == "")) params.push('select=' + encodeURIComponent(options.columns ?? "")); if (options.limit != null) { @@ -897,10 +907,17 @@ async select_uts(table : string, filter ?: UTSJSONObject | null, options ?: AkSu */ async insert(table : string, row : UTSJSONObject | Array) : Promise> { const url = this.baseUrl + '/rest/v1/' + table; - const headers = { + + const token = AkReq.getToken() + let authHeader = `Bearer ${this.apikey}`; + if (token != null && token != '') { + authHeader = `Bearer ${token}`; + } + + let headers = { apikey: this.apikey, 'Content-Type': 'application/json', - Authorization: `Bearer ${AkReq.getToken() ?? ''}`, + Authorization: authHeader, Prefer: 'return=representation' } as UTSJSONObject; @@ -910,7 +927,7 @@ async select_uts(table : string, filter ?: UTSJSONObject | null, options ?: AkSu url, method: 'POST', headers, - data: row, // 可以是单个对象或数组 + data: row, contentType: 'application/json' }; return await this.requestWithAutoRefresh(reqOptions); @@ -928,12 +945,18 @@ async update(table : string, filter : string | null, values : UTSJSONObject) : P if (filter!=null && filter !== "") { url += '?' + filter; } - const headers = { + + const token = AkReq.getToken() + let headers = { apikey: this.apikey, 'Content-Type': 'application/json', - Authorization: `Bearer ${AkReq.getToken() ?? ''}`, Prefer: 'return=representation' } as UTSJSONObject; + + if (token != null && token != '') { + headers['Authorization'] = `Bearer ${token}`; + } + let reqOptions : AkReqOptions = { url, method: 'PATCH', @@ -955,12 +978,18 @@ async delete(table : string, filter : string | null) : Promise> { const url = `${this.baseUrl}/rest/v1/rpc/${functionName}`; - const headers = { + + const token = AkReq.getToken() + let headers = { apikey: this.apikey, - 'Content-Type': 'application/json', - Authorization: `Bearer ${AkReq.getToken() ?? ''}` + 'Content-Type': 'application/json' } as UTSJSONObject; + + if (token != null && token != '') { + headers['Authorization'] = `Bearer ${token}`; + } + let reqOptions : AkReqOptions = { url, method: 'POST', @@ -1042,24 +1077,34 @@ async delete(table : string, filter : string | null) : Promise> { let res = await AkReq.request(reqOptions, false); - // JWT过期:Supabase风格 - const isJwtExpired = (res.status == 401); //res != null && res.data != null && typeof res.data == 'object' && (res.data as UTSJSONObject)?.getString('code') == 'PGRST301'; - // 401未授权 - const isUnauthorized = (res.status == 401); - if ((isJwtExpired || isUnauthorized) && !isRetry) { + // JWT过期/401未授权 + const needsHandle = (res.status == 401); + + if (needsHandle && !isRetry) { const ok = await this.refreshSession(); if (ok) { + const newToken = AkReq.getToken() ?? '' let headers = reqOptions.headers if (headers == null) { headers = new UTSJSONObject() } if (typeof headers.set == 'function') { - headers.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`) + headers.set('Authorization', `Bearer ${newToken}`) reqOptions.headers = headers } res = await AkReq.request(reqOptions, false); } else { + // 如果是测试模式且失败(401且无法刷新),不再抛出异常阻止执行,但确保 res.error 有值 + if (IS_TEST_MODE === true) { + console.warn('[TestMode] Token expired or not found, but continuing anyway. Status:', res.status) + console.log('[TestMode] Response body:', JSON.stringify(res.data)) + if (res.error == null) { + res.error = toUniError('认证失败 (401)', 'UNAUTHORIZED'); + } + return res; + } + uni.removeStorageSync('user_id'); uni.removeStorageSync('token'); @@ -1067,6 +1112,12 @@ async delete(table : string, filter : string | null) : Promise= 400 且 res.error 为空,注入一个 error + if (res.status >= 400 && res.error == null) { + res.error = toUniError(`请求失败: ${res.status}`, 'HTTP_ERROR'); + } + return res; } } diff --git a/pages/mall/admin/marketing/coupon/list.uvue b/pages/mall/admin/marketing/coupon/list.uvue index eb7b6271..88fa1fe9 100644 --- a/pages/mall/admin/marketing/coupon/list.uvue +++ b/pages/mall/admin/marketing/coupon/list.uvue @@ -7,8 +7,8 @@ 优惠券名称: - - 0/18 + + {{ filter.name.length }}/18 @@ -52,71 +52,86 @@ - + - - ID - 优惠券名称 - 优惠券类型 - 面值 - 领取方式 - 领取日期 - 使用时间 - 发布数量 - 是否开启 - 操作 + + + ID + 优惠券名称 + 优惠券类型 + 面值/门槛 + 领取/使用限制 + 领取日期 + 发布数量 + 是否开启 + 操作 - - - {{ item.id }} - {{ item.name }} - {{ item.type }} - {{ item.value.toFixed(2) }} - {{ item.receiveType }} - - 2023-10-18 00:00 - 2025-11-05 00:00 - {{ item.receiveDate }} - - {{ item.useTime }} - - - 发布: {{ item.publishTotal }} - 剩余: {{ item.publishRemain }} + + + + + {{ item.id }} + + + {{ item.name }} + {{ item.createdAt }} 创建 + {{ item.description }} + - 不限量 - - - - + {{ item.type }} + + + {{ item.value.toFixed(2) }}{{ item.type.includes('折扣') ? '折' : '元' }} + {{ item.minOrderAmount > 0 ? '满' + item.minOrderAmount + '元可用' : '无门槛' }} + 最多减{{ item.maxDiscountAmount }}元 + - - - - 领取记录 - | - 编辑 - | - 复制 - | - 删除 + + + {{ item.receiveType }} + 每人限领: {{ item.perUserLimit }}张 + 领取限制: {{ item.usageLimit === 0 ? '不限' : item.usageLimit + '次/天' }} + + + {{ item.receiveDate }} + + + {{ item.publishTotal === 0 ? '不限量' : '总: ' + item.publishTotal }} + 剩: {{ item.publishRemain }} + + + + + + + + + + + 领取记录 + | + 编辑 + | + 删除 + - + - 共 16 条 + 共 {{ dataList.length }} 条 - 15条/页 ▼ + 10 条/页 ▼ - < - 1 - 2 - > + + 1 + 2 + 前往 @@ -126,54 +141,266 @@ + + + + + + 领取记录 - {{ selectedCoupon?.name }} + × + + + + + ID + 用户名 + 用户头像 + 领取时间 + + + + {{ rec.id }} + {{ rec.username }} + + + + {{ rec.time }} + + + + + + diff --git a/pages/user/login.uvue b/pages/user/login.uvue index 64a53b22..cc8f5f12 100644 --- a/pages/user/login.uvue +++ b/pages/user/login.uvue @@ -173,13 +173,16 @@ const codeCountdown = ref(0) onMounted(() => { try { - if (IS_TEST_MODE) return + // 检查是否已有 Session const sessionInfo = supa.getSession() if (sessionInfo != null && sessionInfo.user != null) { + // 在测试模式下,依然执行自动重定向,方便登录成功后的跳转逻辑 const pages = getCurrentPages() as any[] const currentPage = pages.length > 0 ? pages[pages.length - 1] : null const opts = currentPage?.options as any const redirect = opts?.redirect as string | null + + console.log('检测到已有会话, 执行重定向...') if (redirect != null && redirect.length > 0) { uni.redirectTo({ url: decodeURIComponent(redirect) }) } else { @@ -348,19 +351,19 @@ const handleLogin = async () => { } uni.showToast({ title: '登录成功', icon: 'success' }) - if (!IS_TEST_MODE) { - setTimeout(() => { - const pages = getCurrentPages() as any[] - const currentPage = pages.length > 0 ? pages[pages.length - 1] : null - const opts = currentPage?.options as any - const redirect = opts?.redirect as string | null - if (redirect != null && redirect.length > 0) { - uni.redirectTo({ url: decodeURIComponent(redirect) }) - } else { - uni.reLaunch({ url: '/pages/mall/admin/homePage/index' }) - } - }, 500) - } + + // 即使在测试模式下,点击登录后也执行跳转,确保进入首页 + setTimeout(() => { + const pages = getCurrentPages() as any[] + const currentPage = pages.length > 0 ? pages[pages.length - 1] : null + const opts = currentPage?.options as any + const redirect = opts?.redirect as string | null + if (redirect != null && redirect.length > 0) { + uni.redirectTo({ url: decodeURIComponent(redirect) }) + } else { + uni.reLaunch({ url: '/pages/mall/admin/homePage/index' }) + } + }, 500) } catch (err) { console.error('登录错误:', err) let msg = '登录失败,请重试' diff --git a/uni_modules/ak-req/ak-req.uts b/uni_modules/ak-req/ak-req.uts index 0afd8653..27684cbe 100644 --- a/uni_modules/ak-req/ak-req.uts +++ b/uni_modules/ak-req/ak-req.uts @@ -118,7 +118,8 @@ export class AkReq { // 统一 header,自动带上 Authorization/Content-Type/Accept let headers = options.headers ?? ({} as UTSJSONObject); const token = this.getToken(); - if (token != null && token != "") { + const existAuth = headers['Authorization'] ?? headers['authorization']; + if ((token != null && token != "") && (existAuth == null)) { headers = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject; } let contentType = options.contentType ?? '';